PageSourceSearch

https://dvd-rom.netlify.app/static/video.js

js dvd-rom.netlify.app collected 2026-10-03 10:40:16 UTC 1,885,740 bytes, 58,865 lines download raw bytes

1/**
2 * @license
3 * Video.js 7.5.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) {
18  window$1 = window$1 && window$1.hasOwnProperty('default') ? window$1['default'] : window$1;
19  document = document && document.hasOwnProperty('default') ? document['default'] : document;
20
21  var version = "7.5.5";
22
23  function _inheritsLoose(subClass, superClass) {
24    subClass.prototype = Object.create(superClass.prototype);
25    subClass.prototype.constructor = subClass;
26    subClass.__proto__ = superClass;
27  }
28
29  function _setPrototypeOf(o, p) {
30    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
31      o.__proto__ = p;
32      return o;
33    };
34
35    return _setPrototypeOf(o, p);
36  }
37
38  function isNativeReflectConstruct() {
39    if (typeof Reflect === "undefined" || !Reflect.construct) return false;
40    if (Reflect.construct.sham) return false;
41    if (typeof Proxy === "function") return true;
42
43    try {
44      Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
45      return true;
46    } catch (e) {
47      return false;
48    }
49  }
50
51  function _construct(Parent, args, Class) {
52    if (isNativeReflectConstruct()) {
53      _construct = Reflect.construct;
54    } else {
55      _construct = function _construct(Parent, args, Class) {
56        var a = [null];
57        a.push.apply(a, args);
58        var Constructor = Function.bind.apply(Parent, a);
59        var instance = new Constructor();
60        if (Class) _setPrototypeOf(instance, Class.prototype);
61        return instance;
62      };
63    }
64
65    return _construct.apply(null, arguments);
66  }
67
68  function _assertThisInitialized(self) {
69    if (self === void 0) {
70      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
71    }
72
73    return self;
74  }
75
76  function _taggedTemplateLiteralLoose(strings, raw) {
77    if (!raw) {
78      raw = strings.slice(0);
79    }
80
81    strings.raw = raw;
82    return strings;
83  }
84
85  /**
86   * @file create-logger.js
87   * @module create-logger
88   */
89
90  var history = [];
91  /**
92   * Log messages to the console and history based on the type of message
93   *
94   * @private
95   * @param  {string} type
96   *         The name of the console method to use.
97   *
98   * @param  {Array} args
99   *         The arguments to be passed to the matching console method.
100   */
101
102  var LogByTypeFactory = function LogByTypeFactory(name, log) {
103    return function (type, level, args) {
104      var lvl = log.levels[level];
105      var lvlRegExp = new RegExp("^(" + lvl + ")$");
106
107      if (type !== 'log') {
108        // Add the type to the front of the message when it's not "log".
109        args.unshift(type.toUpperCase() + ':');
110      } // Add console prefix after adding to history.
111
112
113      args.unshift(name + ':'); // Add a clone of the args at this point to history.
114
115      if (history) {
116        history.push([].concat(args));
117      } // If there's no console then don't try to output messages, but they will
118      // still be stored in history.
119
120
121      if (!window$1.console) {
122        return;
123      } // Was setting these once outside of this function, but containing them
124      // in the function makes it easier to test cases where console doesn't exist
125      // when the module is executed.
126
127
128      var fn = window$1.console[type];
129
130      if (!fn && type === 'debug') {
131        // Certain browsers don't have support for console.debug. For those, we
132        // should default to the closest comparable log.
133        fn = window$1.console.info || window$1.console.log;
134      } // Bail out if there's no console or if this type is not allowed by the
135      // current logging level.
136
137
138      if (!fn || !lvl || !lvlRegExp.test(type)) {
139        return;
140      }
141
142      fn[Array.isArray(args) ? 'apply' : 'call'](window$1.console, args);
143    };
144  };
145
146  function createLogger(name) {
147    // This is the private tracking variable for logging level.
148    var level = 'info'; // the curried logByType bound to the specific log and history
149
150    var logByType;
151    /**
152     * Logs plain debug messages. Similar to `console.log`.
153     *
154     * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
155     * of our JSDoc template, we cannot properly document this as both a function
156     * and a namespace, so its function signature is documented here.
157     *
158     * #### Arguments
159     * ##### *args
160     * Mixed[]
161     *
162     * Any combination of values that could be passed to `console.log()`.
163     *
164     * #### Return Value
165     *
166     * `undefined`
167     *
168     * @namespace
169     * @param    {Mixed[]} args
170     *           One or more messages or objects that should be logged.
171     */
172
173    var log = function log() {
174      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
175        args[_key] = arguments[_key];
176      }
177
vendor: 9,004 bytes, lines 178-522
178      logByType('log', level, args);
179    }; // This is the logByType helper that the logging methods below use
180
181
182    logByType = LogByTypeFactory(name, log);
183    /**
184     * Create a new sublogger which chains the old name to the new name.
185     *
186     * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following:
187     * ```js
188     *  mylogger('foo');
189     *  // > VIDEOJS: player: foo
190     * ```
191     *
192     * @param {string} name
193     *        The name to add call the new logger
194     * @return {Object}
195     */
196
197    log.createLogger = function (subname) {
198      return createLogger(name + ': ' + subname);
199    };
200    /**
201     * Enumeration of available logging levels, where the keys are the level names
202     * and the values are `|`-separated strings containing logging methods allowed
203     * in that logging level. These strings are used to create a regular expression
204     * matching the function name being called.
205     *
206     * Levels provided by Video.js are:
207     *
208     * - `off`: Matches no calls. Any value that can be cast to `false` will have
209     *   this effect. The most restrictive.
210     * - `all`: Matches only Video.js-provided functions (`debug`, `log`,
211     *   `log.warn`, and `log.error`).
212     * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
213     * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
214     * - `warn`: Matches `log.warn` and `log.error` calls.
215     * - `error`: Matches only `log.error` calls.
216     *
217     * @type {Object}
218     */
219
220
221    log.levels = {
222      all: 'debug|log|warn|error',
223      off: '',
224      debug: 'debug|log|warn|error',
225      info: 'log|warn|error',
226      warn: 'warn|error',
227      error: 'error',
228      DEFAULT: level
229    };
230    /**
231     * Get or set the current logging level.
232     *
233     * If a string matching a key from {@link module:log.levels} is provided, acts
234     * as a setter.
235     *
236     * @param  {string} [lvl]
237     *         Pass a valid level to set a new logging level.
238     *
239     * @return {string}
240     *         The current logging level.
241     */
242
243    log.level = function (lvl) {
244      if (typeof lvl === 'string') {
245        if (!log.levels.hasOwnProperty(lvl)) {
246          throw new Error("\"" + lvl + "\" in not a valid log level");
247        }
248
249        level = lvl;
250      }
251
252      return level;
253    };
254    /**
255     * Returns an array containing everything that has been logged to the history.
256     *
257     * This array is a shallow clone of the internal history record. However, its
258     * contents are _not_ cloned; so, mutating objects inside this array will
259     * mutate them in history.
260     *
261     * @return {Array}
262     */
263
264
265    log.history = function () {
266      return history ? [].concat(history) : [];
267    };
268    /**
269     * Allows you to filter the history by the given logger name
270     *
271     * @param {string} fname
272     *        The name to filter by
273     *
274     * @return {Array}
275     *         The filtered list to return
276     */
277
278
279    log.history.filter = function (fname) {
280      return (history || []).filter(function (historyItem) {
281        // if the first item in each historyItem includes `fname`, then it's a match
282        return new RegExp(".*" + fname + ".*").test(historyItem[0]);
283      });
284    };
285    /**
286     * Clears the internal history tracking, but does not prevent further history
287     * tracking.
288     */
289
290
291    log.history.clear = function () {
292      if (history) {
293        history.length = 0;
294      }
295    };
296    /**
297     * Disable history tracking if it is currently enabled.
298     */
299
300
301    log.history.disable = function () {
302      if (history !== null) {
303        history.length = 0;
304        history = null;
305      }
306    };
307    /**
308     * Enable history tracking if it is currently disabled.
309     */
310
311
312    log.history.enable = function () {
313      if (history === null) {
314        history = [];
315      }
316    };
317    /**
318     * Logs error messages. Similar to `console.error`.
319     *
320     * @param {Mixed[]} args
321     *        One or more messages or objects that should be logged as an error
322     */
323
324
325    log.error = function () {
326      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
327        args[_key2] = arguments[_key2];
328      }
329
330      return logByType('error', level, args);
331    };
332    /**
333     * Logs warning messages. Similar to `console.warn`.
334     *
335     * @param {Mixed[]} args
336     *        One or more messages or objects that should be logged as a warning.
337     */
338
339
340    log.warn = function () {
341      for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
342        args[_key3] = arguments[_key3];
343      }
344
345      return logByType('warn', level, args);
346    };
347    /**
348     * Logs debug messages. Similar to `console.debug`, but may also act as a comparable
349     * log if `console.debug` is not available
350     *
351     * @param {Mixed[]} args
352     *        One or more messages or objects that should be logged as debug.
353     */
354
355
356    log.debug = function () {
357      for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
358        args[_key4] = arguments[_key4];
359      }
360
361      return logByType('debug', level, args);
362    };
363
364    return log;
365  }
366
367  /**
368   * @file log.js
369   * @module log
370   */
371  var log = createLogger('VIDEOJS');
372  var createLogger$1 = log.createLogger;
373
374  function clean(s) {
375    return s.replace(/\n\r?\s*/g, '');
376  }
377
378  var tsml = function tsml(sa) {
379    var s = '',
380        i = 0;
381
382    for (; i < arguments.length; i++) {
383      s += clean(sa[i]) + (arguments[i + 1] || '');
384    }
385
386    return s;
387  };
388
389  /**
390   * @file obj.js
391   * @module obj
392   */
393
394  /**
395   * @callback obj:EachCallback
396   *
397   * @param {Mixed} value
398   *        The current key for the object that is being iterated over.
399   *
400   * @param {string} key
401   *        The current key-value for object that is being iterated over
402   */
403
404  /**
405   * @callback obj:ReduceCallback
406   *
407   * @param {Mixed} accum
408   *        The value that is accumulating over the reduce loop.
409   *
410   * @param {Mixed} value
411   *        The current key for the object that is being iterated over.
412   *
413   * @param {string} key
414   *        The current key-value for object that is being iterated over
415   *
416   * @return {Mixed}
417   *         The new accumulated value.
418   */
419  var toString = Object.prototype.toString;
420  /**
421   * Get the keys of an Object
422   *
423   * @param {Object}
424   *        The Object to get the keys from
425   *
426   * @return {string[]}
427   *         An array of the keys from the object. Returns an empty array if the
428   *         object passed in was invalid or had no keys.
429   *
430   * @private
431   */
432
433  var keys = function keys(object) {
434    return isObject(object) ? Object.keys(object) : [];
435  };
436  /**
437   * Array-like iteration for objects.
438   *
439   * @param {Object} object
440   *        The object to iterate over
441   *
442   * @param {obj:EachCallback} fn
443   *        The callback function which is called for each key in the object.
444   */
445
446
447  function each(object, fn) {
448    keys(object).forEach(function (key) {
449      return fn(object[key], key);
450    });
451  }
452  /**
453   * Array-like reduce for objects.
454   *
455   * @param {Object} object
456   *        The Object that you want to reduce.
457   *
458   * @param {Function} fn
459   *         A callback function which is called for each key in the object. It
460   *         receives the accumulated value and the per-iteration value and key
461   *         as arguments.
462   *
463   * @param {Mixed} [initial = 0]
464   *        Starting value
465   *
466   * @return {Mixed}
467   *         The final accumulated value.
468   */
469
470  function reduce(object, fn, initial) {
471    if (initial === void 0) {
472      initial = 0;
473    }
474
475    return keys(object).reduce(function (accum, key) {
476      return fn(accum, object[key], key);
477    }, initial);
478  }
479  /**
480   * Object.assign-style object shallow merge/extend.
481   *
482   * @param  {Object} target
483   * @param  {Object} ...sources
484   * @return {Object}
485   */
486
487  function assign(target) {
488    for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
489      sources[_key - 1] = arguments[_key];
490    }
491
492    if (Object.assign) {
493      return Object.assign.apply(Object, [target].concat(sources));
494    }
495
496    sources.forEach(function (source) {
497      if (!source) {
498        return;
499      }
500
501      each(source, function (value, key) {
502        target[key] = value;
503      });
504    });
505    return target;
506  }
507  /**
508   * Returns whether a value is an object of any kind - including DOM nodes,
509   * arrays, regular expressions, etc. Not functions, though.
510   *
511   * This avoids the gotcha where using `typeof` on a `null` value
512   * results in `'object'`.
513   *
514   * @param  {Object} value
515   * @return {boolean}
516   */
517
518  function isObject(value) {
519    return !!value && typeof value === 'object';
520  }
521  /**
522   * Returns whether an object appears to be a "plain" object - that is, a
523   * direct instance of `Object`.
524   *
525   * @param  {Object} value
526   * @return {boolean}
527   */
528
529  function isPlain(value) {
530    return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object;
531  }
532
533  /**
534   * @file computed-style.js
535   * @module computed-style
536   */
537  /**
538   * A safe getComputedStyle.
539   *
540   * This is needed because in Firefox, if the player is loaded in an iframe with
541   * `display:none`, then `getComputedStyle` returns `null`, so, we do a
542   * null-check to make sure that the player doesn't break in these cases.
543   *
544   * @function
545   * @param    {Element} el
546   *           The element you want the computed style of
547   *
548   * @param    {string} prop
549   *           The property name you want
550   *
551   * @see      https://bugzilla.mozilla.org/show_bug.cgi?id=548397
552   */
553
554  function computedStyle(el, prop) {
555    if (!el || !prop) {
556      return '';
557    }
558
559    if (typeof window$1.getComputedStyle === 'function') {
560      var cs = window$1.getComputedStyle(el);
561      return cs ? cs[prop] : '';
562    }
563
564    return '';
565  }
566
567  function _templateObject() {
568    var data = _taggedTemplateLiteralLoose(["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 ", " to ", "."]);
569
570    _templateObject = function _templateObject() {
571      return data;
572    };
573
574    return data;
575  }
576  /**
577   * Detect if a value is a string with any non-whitespace characters.
578   *
579   * @private
580   * @param  {string} str
581   *         The string to check
582   *
583   * @return {boolean}
584   *         Will be `true` if the string is non-blank, `false` otherwise.
585   *
586   */
587
588  function isNonBlankString(str) {
589    return typeof str === 'string' && /\S/.test(str);
590  }
591  /**
592   * Throws an error if the passed string has whitespace. This is used by
593   * class methods to be relatively consistent with the classList API.
594   *
595   * @private
596   * @param  {string} str
597   *         The string to check for whitespace.
598   *
599   * @throws {Error}
600   *         Throws an error if there is whitespace in the string.
601   */
602
603
604  function throwIfWhitespace(str) {
605    if (/\s/.test(str)) {
606      throw new Error('class has illegal whitespace characters');
607    }
608  }
609  /**
610   * Produce a regular expression for matching a className within an elements className.
611   *
612   * @private
613   * @param  {string} className
614   *         The className to generate the RegExp for.
615   *
616   * @return {RegExp}
617   *         The RegExp that will check for a specific `className` in an elements
618   *         className.
619   */
620
621
622  function classRegExp(className) {
623    return new RegExp('(^|\\s)' + className + '($|\\s)');
624  }
625  /**
626   * Whether the current DOM interface appears to be real (i.e. not simulated).
627   *
628   * @return {boolean}
629   *         Will be `true` if the DOM appears to be real, `false` otherwise.
630   */
631
632
633  function isReal() {
634    // Both document and window will never be undefined thanks to `global`.
635    return document === window$1.document;
636  }
637  /**
638   * Determines, via duck typing, whether or not a value is a DOM element.
639   *
640   * @param  {Mixed} value
641   *         The value to check.
642   *
643   * @return {boolean}
644   *         Will be `true` if the value is a DOM element, `false` otherwise.
645   */
646
647  function isEl(value) {
648    return isObject(value) && value.nodeType === 1;
649  }
650  /**
651   * Determines if the current DOM is embedded in an iframe.
652   *
653   * @return {boolean}
654   *         Will be `true` if the DOM is embedded in an iframe, `false`
655   *         otherwise.
656   */
657
658  function isInFrame() {
659    // We need a try/catch here because Safari will throw errors when attempting
660    // to get either `parent` or `self`
661    try {
662      return window$1.parent !== window$1.self;
663    } catch (x) {
664      return true;
665    }
666  }
667  /**
668   * Creates functions to query the DOM using a given method.
669   *
670   * @private
671   * @param   {string} method
672   *          The method to create the query with.
673   *
674   * @return  {Function}
675   *          The query method
676   */
677
678  function createQuerier(method) {
679    return function (selector, context) {
680      if (!isNonBlankString(selector)) {
681        return document[method](null);
682      }
683
684      if (isNonBlankString(context)) {
685        context = document.querySelector(context);
686      }
687
688      var ctx = isEl(context) ? context : document;
689      return ctx[method] && ctx[method](selector);
690    };
691  }
692  /**
693   * Creates an element and applies properties, attributes, and inserts content.
694   *
695   * @param  {string} [tagName='div']
696   *         Name of tag to be created.
697   *
698   * @param  {Object} [properties={}]
699   *         Element properties to be applied.
700   *
701   * @param  {Object} [attributes={}]
702   *         Element attributes to be applied.
703   *
704   * @param {module:dom~ContentDescriptor} content
705   *        A content descriptor object.
706   *
707   * @return {Element}
708   *         The element that was created.
709   */
710
711
712  function createEl(tagName, properties, attributes, content) {
713    if (tagName === void 0) {
714      tagName = 'div';
715    }
716
717    if (properties === void 0) {
718      properties = {};
719    }
720
721    if (attributes === void 0) {
722      attributes = {};
723    }
724
725    var el = document.createElement(tagName);
vendor: 4,990 bytes, lines 726-897
726    Object.getOwnPropertyNames(properties).forEach(function (propName) {
727      var val = properties[propName]; // See #2176
728      // We originally were accepting both properties and attributes in the
729      // same object, but that doesn't work so well.
730
731      if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
732        log.warn(tsml(_templateObject(), propName, val));
733        el.setAttribute(propName, val); // Handle textContent since it's not supported everywhere and we have a
734        // method for it.
735      } else if (propName === 'textContent') {
736        textContent(el, val);
737      } else {
738        el[propName] = val;
739      }
740    });
741    Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
742      el.setAttribute(attrName, attributes[attrName]);
743    });
744
745    if (content) {
746      appendContent(el, content);
747    }
748
749    return el;
750  }
751  /**
752   * Injects text into an element, replacing any existing contents entirely.
753   *
754   * @param  {Element} el
755   *         The element to add text content into
756   *
757   * @param  {string} text
758   *         The text content to add.
759   *
760   * @return {Element}
761   *         The element with added text content.
762   */
763
764  function textContent(el, text) {
765    if (typeof el.textContent === 'undefined') {
766      el.innerText = text;
767    } else {
768      el.textContent = text;
769    }
770
771    return el;
772  }
773  /**
774   * Insert an element as the first child node of another
775   *
776   * @param {Element} child
777   *        Element to insert
778   *
779   * @param {Element} parent
780   *        Element to insert child into
781   */
782
783  function prependTo(child, parent) {
784    if (parent.firstChild) {
785      parent.insertBefore(child, parent.firstChild);
786    } else {
787      parent.appendChild(child);
788    }
789  }
790  /**
791   * Check if an element has a class name.
792   *
793   * @param  {Element} element
794   *         Element to check
795   *
796   * @param  {string} classToCheck
797   *         Class name to check for
798   *
799   * @return {boolean}
800   *         Will be `true` if the element has a class, `false` otherwise.
801   *
802   * @throws {Error}
803   *         Throws an error if `classToCheck` has white space.
804   */
805
806  function hasClass(element, classToCheck) {
807    throwIfWhitespace(classToCheck);
808
809    if (element.classList) {
810      return element.classList.contains(classToCheck);
811    }
812
813    return classRegExp(classToCheck).test(element.className);
814  }
815  /**
816   * Add a class name to an element.
817   *
818   * @param  {Element} element
819   *         Element to add class name to.
820   *
821   * @param  {string} classToAdd
822   *         Class name to add.
823   *
824   * @return {Element}
825   *         The DOM element with the added class name.
826   */
827
828  function addClass(element, classToAdd) {
829    if (element.classList) {
830      element.classList.add(classToAdd); // Don't need to `throwIfWhitespace` here because `hasElClass` will do it
831      // in the case of classList not being supported.
832    } else if (!hasClass(element, classToAdd)) {
833      element.className = (element.className + ' ' + classToAdd).trim();
834    }
835
836    return element;
837  }
838  /**
839   * Remove a class name from an element.
840   *
841   * @param  {Element} element
842   *         Element to remove a class name from.
843   *
844   * @param  {string} classToRemove
845   *         Class name to remove
846   *
847   * @return {Element}
848   *         The DOM element with class name removed.
849   */
850
851  function removeClass(element, classToRemove) {
852    if (element.classList) {
853      element.classList.remove(classToRemove);
854    } else {
855      throwIfWhitespace(classToRemove);
856      element.className = element.className.split(/\s+/).filter(function (c) {
857        return c !== classToRemove;
858      }).join(' ');
859    }
860
861    return element;
862  }
863  /**
864   * The callback definition for toggleClass.
865   *
866   * @callback module:dom~PredicateCallback
867   * @param    {Element} element
868   *           The DOM element of the Component.
869   *
870   * @param    {string} classToToggle
871   *           The `className` that wants to be toggled
872   *
873   * @return   {boolean|undefined}
874   *           If `true` is returned, the `classToToggle` will be added to the
875   *           `element`. If `false`, the `classToToggle` will be removed from
876   *           the `element`. If `undefined`, the callback will be ignored.
877   */
878
879  /**
880   * Adds or removes a class name to/from an element depending on an optional
881   * condition or the presence/absence of the class name.
882   *
883   * @param  {Element} element
884   *         The element to toggle a class name on.
885   *
886   * @param  {string} classToToggle
887   *         The class that should be toggled.
888   *
889   * @param  {boolean|module:dom~PredicateCallback} [predicate]
890   *         See the return value for {@link module:dom~PredicateCallback}
891   *
892   * @return {Element}
893   *         The element with a class that has been toggled.
894   */
895
896  function toggleClass(element, classToToggle, predicate) {
897    // This CANNOT use `classList` internally because IE11 does not support the
vendor: 4,324 bytes, lines 898-1054
898    // second parameter to the `classList.toggle()` method! Which is fine because
899    // `classList` will be used by the add/remove functions.
900    var has = hasClass(element, classToToggle);
901
902    if (typeof predicate === 'function') {
903      predicate = predicate(element, classToToggle);
904    }
905
906    if (typeof predicate !== 'boolean') {
907      predicate = !has;
908    } // If the necessary class operation matches the current state of the
909    // element, no action is required.
910
911
912    if (predicate === has) {
913      return;
914    }
915
916    if (predicate) {
917      addClass(element, classToToggle);
918    } else {
919      removeClass(element, classToToggle);
920    }
921
922    return element;
923  }
924  /**
925   * Apply attributes to an HTML element.
926   *
927   * @param {Element} el
928   *        Element to add attributes to.
929   *
930   * @param {Object} [attributes]
931   *        Attributes to be applied.
932   */
933
934  function setAttributes(el, attributes) {
935    Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
936      var attrValue = attributes[attrName];
937
938      if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
939        el.removeAttribute(attrName);
940      } else {
941        el.setAttribute(attrName, attrValue === true ? '' : attrValue);
942      }
943    });
944  }
945  /**
946   * Get an element's attribute values, as defined on the HTML tag.
947   *
948   * Attributes are not the same as properties. They're defined on the tag
949   * or with setAttribute.
950   *
951   * @param  {Element} tag
952   *         Element from which to get tag attributes.
953   *
954   * @return {Object}
955   *         All attributes of the element. Boolean attributes will be `true` or
956   *         `false`, others will be strings.
957   */
958
959  function getAttributes(tag) {
960    var obj = {}; // known boolean attributes
961    // we can check for matching boolean properties, but not all browsers
962    // and not all tags know about these attributes, so, we still want to check them manually
963
964    var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ',';
965
966    if (tag && tag.attributes && tag.attributes.length > 0) {
967      var attrs = tag.attributes;
968
969      for (var i = attrs.length - 1; i >= 0; i--) {
970        var attrName = attrs[i].name;
971        var attrVal = attrs[i].value; // check for known booleans
972        // the matching element property will return a value for typeof
973
974        if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
975          // the value of an included boolean attribute is typically an empty
976          // string ('') which would equal false if we just check for a false value.
977          // we also don't want support bad code like autoplay='false'
978          attrVal = attrVal !== null ? true : false;
979        }
980
981        obj[attrName] = attrVal;
982      }
983    }
984
985    return obj;
986  }
987  /**
988   * Get the value of an element's attribute.
989   *
990   * @param {Element} el
991   *        A DOM element.
992   *
993   * @param {string} attribute
994   *        Attribute to get the value of.
995   *
996   * @return {string}
997   *         The value of the attribute.
998   */
999
1000  function getAttribute(el, attribute) {
1001    return el.getAttribute(attribute);
1002  }
1003  /**
1004   * Set the value of an element's attribute.
1005   *
1006   * @param {Element} el
1007   *        A DOM element.
1008   *
1009   * @param {string} attribute
1010   *        Attribute to set.
1011   *
1012   * @param {string} value
1013   *        Value to set the attribute to.
1014   */
1015
1016  function setAttribute(el, attribute, value) {
1017    el.setAttribute(attribute, value);
1018  }
1019  /**
1020   * Remove an element's attribute.
1021   *
1022   * @param {Element} el
1023   *        A DOM element.
1024   *
1025   * @param {string} attribute
1026   *        Attribute to remove.
1027   */
1028
1029  function removeAttribute(el, attribute) {
1030    el.removeAttribute(attribute);
1031  }
1032  /**
1033   * Attempt to block the ability to select text.
1034   */
1035
1036  function blockTextSelection() {
1037    document.body.focus();
1038
1039    document.onselectstart = function () {
1040      return false;
1041    };
1042  }
1043  /**
1044   * Turn off text selection blocking.
1045   */
1046
1047  function unblockTextSelection() {
1048    document.onselectstart = function () {
1049      return true;
1050    };
1051  }
1052  /**
1053   * Identical to the native `getBoundingClientRect` function, but ensures that
1054   * the method is supported at all (it is in all browsers we claim to supp
1054ort)
1055   * and that the element is in the DOM before continuing.
1056   *
1057   * This wrapper function also shims properties which are not provided by some
1058   * older browsers (namely, IE8).
1059   *
1060   * Additionally, some browsers do not support adding properties to a
1061   * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
1062   * properties (except `x` and `y` which are not widely supported). This helps
1063   * avoid implementations where keys are non-enumerable.
1064   *
1065   * @param  {Element} el
1066   *         Element whose `ClientRect` we want to calculate.
1067   *
1068   * @return {Object|undefined}
1069   *         Always returns a plain object - or `undefined` if it cannot.
1070   */
1071
1072  function getBoundingClientRect(el) {
1073    if (el && el.getBoundingClientRect && el.parentNode) {
1074      var rect = el.getBoundingClientRect();
1075      var result = {};
1076      ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) {
1077        if (rect[k] !== undefined) {
1078          result[k] = rect[k];
1079        }
1080      });
1081
1082      if (!result.height) {
1083        result.height = parseFloat(computedStyle(el, 'height'));
1084      }
1085
1086      if (!result.width) {
1087        result.width = parseFloat(computedStyle(el, 'width'));
1088      }
1089
1090      return result;
1091    }
1092  }
1093  /**
1094   * Represents the position of a DOM element on the page.
1095   *
1096   * @typedef  {Object} module:dom~Position
1097   *
1098   * @property {number} left
1099   *           Pixels to the left.
1100   *
1101   * @property {number} top
1102   *           Pixels from the top.
1103   */
1104
1105  /**
1106   * Get the position of an element in the DOM.
1107   *
1108   * Uses `getBoundingClientRect` technique from John Resig.
1109   *
1110   * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
1111   *
1112   * @param  {Element} el
1113   *         Element from which to get offset.
1114   *
1115   * @return {module:dom~Position}
1116   *         The position of the element that was passed in.
1117   */
1118
1119  function findPosition(el) {
1120    var box;
1121
1122    if (el.getBoundingClientRect && el.parentNode) {
1123      box = el.getBoundingClientRect();
1124    }
1125
1126    if (!box) {
1127      return {
1128        left: 0,
1129        top: 0
1130      };
1131    }
1132
1133    var docEl = document.documentElement;
1134    var body = document.body;
1135    var clientLeft = docEl.clientLeft || body.clientLeft || 0;
1136    var scrollLeft = window$1.pageXOffset || body.scrollLeft;
1137    var left = box.left + scrollLeft - clientLeft;
1138    var clientTop = docEl.clientTop || body.clientTop || 0;
1139    var scrollTop = window$1.pageYOffset || body.scrollTop;
1140    var top = box.top + scrollTop - clientTop; // Android sometimes returns slightly off decimal values, so need to round
1141
1142    return {
1143      left: Math.round(left),
1144      top: Math.round(top)
1145    };
1146  }
1147  /**
1148   * Represents x and y coordinates for a DOM element or mouse pointer.
1149   *
1150   * @typedef  {Object} module:dom~Coordinates
1151   *
1152   * @property {number} x
1153   *           x coordinate in pixels
1154   *
1155   * @property {number} y
1156   *           y coordinate in pixels
1157   */
1158
1159  /**
1160   * Get the pointer position within an element.
1161   *
1162   * The base on the coordinates are the bottom left of the element.
1163   *
1164   * @param  {Element} el
1165   *         Element on which to get the pointer position on.
1166   *
1167   * @param  {EventTarget~Event} event
1168   *         Event object.
1169   *
1170   * @return {module:dom~Coordinates}
1171   *         A coordinates object corresponding to the mouse position.
1172   *
1173   */
1174
1175  function getPointerPosition(el, event) {
1176    var position = {};
1177    var box = findPosition(el);
1178    var boxW = el.offsetWidth;
1179    var boxH = el.offsetHeight;
1180    var boxY = box.top;
1181    var boxX = box.left;
1182    var pageY = event.pageY;
1183    var pageX = event.pageX;
1184
1185    if (event.changedTouches) {
1186      pageX = event.changedTouches[0].pageX;
1187      pageY = event.changedTouches[0].pageY;
1188    }
1189
1190    position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH));
1191    position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
1192    return position;
1193  }
1194  /**
1195   * Determines, via duck typing, whether or not a value is a text node.
1196   *
1197   * @param  {Mixed} value
1198   *         Check if this value is a text node.
1199   *
1200   * @return {boolean}
1201   *         Will be `true` if the value is a text node, `false` otherwise.
1202   */
1203
1204  function isTextNode(value) {
1205    return isObject(value) && value.nodeType === 3;
1206  }
1207  /**
1208   * Empties the contents of an element.
1209   *
1210   * @param  {Element} el
1211   *         The element to empty children from
1212   *
1213   * @return {Element}
1214   *         The element with no children
1215   */
1216
1217  function emptyEl(el) {
1218    while (el.firstChild) {
1219      el.removeChild(el.firstChild);
1220    }
1221
1222    return el;
1223  }
1224  /**
1225   * This is a mixed value that describes content to be injected into the DOM
1226   * via some method. It can be of the following types:
1227   *
1228   * Type       | Description
1229   * -----------|-------------
1230   * `string`   | The value will be normalized into a text node.
1231   * `Element`  | The value will be accepted as-is.
1232   * `TextNode` | The value will be accepted as-is.
1233   * `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).
1234   * `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.
1235   *
1236   * @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor
1237   */
1238
1239  /**
1240   * Normalizes content for eventual insertion into the DOM.
1241   *
1242   * This allows a wide range of content definition methods, but helps protect
1243   * from falling into the trap of simply writing to `innerHTML`, which could
1244   * be an XSS concern.
1245   *
1246   * The content for an element can be passed in multiple types and
1247   * combinations, whose behavior is as follows:
1248   *
1249   * @param {module:dom~ContentDescriptor} content
1250   *        A content descriptor value.
1251   *
1252   * @return {Array}
1253   *         All of the content that was passed in, normalized to an array of
1254   *         elements or text nodes.
1255   */
1256
1257  function normalizeContent(content) {
1258    // First, invoke content if it is a function. If it produces an array,
1259    // that needs to happen before normalization.
1260    if (typeof content === 'function') {
1261      content = content();
1262    } // Next up, normalize to an array, so one or many items can be normalized,
1263    // filtered, and returned.
1264
1265
1266    return (Array.isArray(content) ? content : [content]).map(function (value) {
1267      // First, invoke value if it is a function to produce a new value,
1268      // which will be subsequently normalized to a Node of some kind.
1269      if (typeof value === 'function') {
1270        value = value();
1271      }
1272
1273      if (isEl(value) || isTextNode(value)) {
1274        return value;
1275      }
1276
1277      if (typeof value === 'string' && /\S/.test(value)) {
1278        return document.createTextNode(value);
1279      }
1280    }).filter(function (value) {
1281      return value;
1282    });
1283  }
1284  /**
1285   * Normalizes and appends content to an element.
1286   *
1287   * @param  {Element} el
1288   *         Element to append normalized content to.
1289   *
1290   * @param {module:dom~ContentDescriptor} content
1291   *        A content descriptor value.
1292   *
1293   * @return {Element}
1294   *         The element with appended normalized content.
1295   */
1296
1297  function appendContent(el, content) {
1298    normalizeContent(content).forEach(function (node) {
1299      return el.appendChild(node);
1300    });
1301    return el;
1302  }
1303  /**
1304   * Normalizes and inserts content into an element; this is identical to
1305   * `appendContent()`, except it empties the element first.
1306   *
1307   * @param {Element} el
1308   *        Element to insert normalized content into.
1309   *
1310   * @param {module:dom~ContentDescriptor} content
1311   *        A content descriptor value.
1312   *
1313   * @return {Element}
1314   *         The element with inserted normalized content.
1315   */
1316
1317  function insertContent(el, content) {
1318    return appendContent(emptyEl(el), content);
1319  }
1320  /**
1321   * Check if an event was a single left click.
1322   *
1323   * @param  {EventTarget~Event} event
1324   *         Event object.
1325   *
1326   * @return {boolean}
1327   *         Will be `true` if a single left click, `false` otherwise.
1328   */
1329
1330  function isSingleLeftClick(event) {
1331    // Note: if you create something draggable, be sure to
1332    // call it on both `mousedown` and `mousemove` event,
1333    // otherwise `mousedown` should be enough for a button
1334    if (event.button === undefined && event.buttons === undefined) {
1335      // Why do we need `buttons` ?
1336      // Because, middle mouse sometimes have this:
1337      // e.button === 0 and e.buttons === 4
1338      // Furthermore, we want to prevent combination click, something like
1339      // HOLD middlemouse then left click, that would be
1340      // e.button === 0, e.buttons === 5
1341      // just `button` is not gonna work
1342      // Alright, then what this block does ?
1343      // this is for chrome `simulate mobile devices`
vendor: 8,747 bytes, lines 1344-1655
1344      // I want to support this as well
1345      return true;
1346    }
1347
1348    if (event.button === 0 && event.buttons === undefined) {
1349      // Touch screen, sometimes on some specific device, `buttons`
1350      // doesn't have anything (safari on ios, blackberry...)
1351      return true;
1352    }
1353
1354    if (event.button !== 0 || event.buttons !== 1) {
1355      // This is the reason we have those if else block above
1356      // if any special case we can catch and let it slide
1357      // we do it above, when get to here, this definitely
1358      // is-not-left-click
1359      return false;
1360    }
1361
1362    return true;
1363  }
1364  /**
1365   * Finds a single DOM element matching `selector` within the optional
1366   * `context` of another DOM element (defaulting to `document`).
1367   *
1368   * @param  {string} selector
1369   *         A valid CSS selector, which will be passed to `querySelector`.
1370   *
1371   * @param  {Element|String} [context=document]
1372   *         A DOM element within which to query. Can also be a selector
1373   *         string in which case the first matching element will be used
1374   *         as context. If missing (or no element matches selector), falls
1375   *         back to `document`.
1376   *
1377   * @return {Element|null}
1378   *         The element that was found or null.
1379   */
1380
1381  var $ = createQuerier('querySelector');
1382  /**
1383   * Finds a all DOM elements matching `selector` within the optional
1384   * `context` of another DOM element (defaulting to `document`).
1385   *
1386   * @param  {string} selector
1387   *         A valid CSS selector, which will be passed to `querySelectorAll`.
1388   *
1389   * @param  {Element|String} [context=document]
1390   *         A DOM element within which to query. Can also be a selector
1391   *         string in which case the first matching element will be used
1392   *         as context. If missing (or no element matches selector), falls
1393   *         back to `document`.
1394   *
1395   * @return {NodeList}
1396   *         A element list of elements that were found. Will be empty if none
1397   *         were found.
1398   *
1399   */
1400
1401  var $$ = createQuerier('querySelectorAll');
1402
1403  var Dom = /*#__PURE__*/Object.freeze({
1404    isReal: isReal,
1405    isEl: isEl,
1406    isInFrame: isInFrame,
1407    createEl: createEl,
1408    textContent: textContent,
1409    prependTo: prependTo,
1410    hasClass: hasClass,
1411    addClass: addClass,
1412    removeClass: removeClass,
1413    toggleClass: toggleClass,
1414    setAttributes: setAttributes,
1415    getAttributes: getAttributes,
1416    getAttribute: getAttribute,
1417    setAttribute: setAttribute,
1418    removeAttribute: removeAttribute,
1419    blockTextSelection: blockTextSelection,
1420    unblockTextSelection: unblockTextSelection,
1421    getBoundingClientRect: getBoundingClientRect,
1422    findPosition: findPosition,
1423    getPointerPosition: getPointerPosition,
1424    isTextNode: isTextNode,
1425    emptyEl: emptyEl,
1426    normalizeContent: normalizeContent,
1427    appendContent: appendContent,
1428    insertContent: insertContent,
1429    isSingleLeftClick: isSingleLeftClick,
1430    $: $,
1431    $$: $$
1432  });
1433
1434  /**
1435   * @file guid.js
1436   * @module guid
1437   */
1438
1439  /**
1440   * Unique ID for an element or function
1441   * @type {Number}
1442   */
1443  var _guid = 1;
1444  /**
1445   * Get a unique auto-incrementing ID by number that has not been returned before.
1446   *
1447   * @return {number}
1448   *         A new unique ID.
1449   */
1450
1451  function newGUID() {
1452    return _guid++;
1453  }
1454
1455  /**
1456   * @file dom-data.js
1457   * @module dom-data
1458   */
1459  /**
1460   * Element Data Store.
1461   *
1462   * Allows for binding data to an element without putting it directly on the
1463   * element. Ex. Event listeners are stored here.
1464   * (also from jsninja.com, slightly modified and updated for closure compiler)
1465   *
1466   * @type {Object}
1467   * @private
1468   */
1469
1470  var elData = {};
1471  /*
1472   * Unique attribute name to store an element's guid in
1473   *
1474   * @type {String}
1475   * @constant
1476   * @private
1477   */
1478
1479  var elIdAttr = 'vdata' + new Date().getTime();
1480  /**
1481   * Returns the cache object where data for an element is stored
1482   *
1483   * @param {Element} el
1484   *        Element to store data for.
1485   *
1486   * @return {Object}
1487   *         The cache object for that el that was passed in.
1488   */
1489
1490  function getData(el) {
1491    var id = el[elIdAttr];
1492
1493    if (!id) {
1494      id = el[elIdAttr] = newGUID();
1495    }
1496
1497    if (!elData[id]) {
1498      elData[id] = {};
1499    }
1500
1501    return elData[id];
1502  }
1503  /**
1504   * Returns whether or not an element has cached data
1505   *
1506   * @param {Element} el
1507   *        Check if this element has cached data.
1508   *
1509   * @return {boolean}
1510   *         - True if the DOM element has cached data.
1511   *         - False otherwise.
1512   */
1513
1514  function hasData(el) {
1515    var id = el[elIdAttr];
1516
1517    if (!id) {
1518      return false;
1519    }
1520
1521    return !!Object.getOwnPropertyNames(elData[id]).length;
1522  }
1523  /**
1524   * Delete data for the element from the cache and the guid attr from getElementById
1525   *
1526   * @param {Element} el
1527   *        Remove cached data for this element.
1528   */
1529
1530  function removeData(el) {
1531    var id = el[elIdAttr];
1532
1533    if (!id) {
1534      return;
1535    } // Remove all stored data
1536
1537
1538    delete elData[id]; // Remove the elIdAttr property from the DOM node
1539
1540    try {
1541      delete el[elIdAttr];
1542    } catch (e) {
1543      if (el.removeAttribute) {
1544        el.removeAttribute(elIdAttr);
1545      } else {
1546        // IE doesn't appear to support removeAttribute on the document element
1547        el[elIdAttr] = null;
1548      }
1549    }
1550  }
1551
1552  /**
1553   * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
1554   * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
1555   * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
1556   * robust as jquery's, so there's probably some differences.
1557   *
1558   * @file events.js
1559   * @module events
1560   */
1561  /**
1562   * Clean up the listener cache and dispatchers
1563   *
1564   * @param {Element|Object} elem
1565   *        Element to clean up
1566   *
1567   * @param {string} type
1568   *        Type of event to clean up
1569   */
1570
1571  function _cleanUpEvents(elem, type) {
1572    var data = getData(elem); // Remove the events of a particular type if there are none left
1573
1574    if (data.handlers[type].length === 0) {
1575      delete data.handlers[type]; // data.handlers[type] = null;
1576      // Setting to null was causing an error with data.handlers
1577      // Remove the meta-handler from the element
1578
1579      if (elem.removeEventListener) {
1580        elem.removeEventListener(type, data.dispatcher, false);
1581      } else if (elem.detachEvent) {
1582        elem.detachEvent('on' + type, data.dispatcher);
1583      }
1584    } // Remove the events object if there are no types left
1585
1586
1587    if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
1588      delete data.handlers;
1589      delete data.dispatcher;
1590      delete data.disabled;
1591    } // Finally remove the element data if there is no data left
1592
1593
1594    if (Object.getOwnPropertyNames(data).length === 0) {
1595      removeData(elem);
1596    }
1597  }
1598  /**
1599   * Loops through an array of event types and calls the requested method for each type.
1600   *
1601   * @param {Function} fn
1602   *        The event method we want to use.
1603   *
1604   * @param {Element|Object} elem
1605   *        Element or object to bind listeners to
1606   *
1607   * @param {string} type
1608   *        Type of event to bind to.
1609   *
1610   * @param {EventTarget~EventListener} callback
1611   *        Event listener.
1612   */
1613
1614
1615  function _handleMultipleEvents(fn, elem, types, callback) {
1616    types.forEach(function (type) {
1617      // Call the event method for each one of the types
1618      fn(elem, type, callback);
1619    });
1620  }
1621  /**
1622   * Fix a native event to have standard property values
1623   *
1624   * @param {Object} event
1625   *        Event object to fix.
1626   *
1627   * @return {Object}
1628   *         Fixed event object.
1629   */
1630
1631
1632  function fixEvent(event) {
1633    function returnTrue() {
1634      return true;
1635    }
1636
1637    function returnFalse() {
1638      return false;
1639    } // Test if fixing up is needed
1640    // Used to check if !event.stopPropagation instead of isPropagationStopped
1641    // But native events return true for stopPropagation, but don't have
1642    // other expected methods like isPropagationStopped. Seems to be a problem
1643    // with the Javascript Ninja code. So we're just overriding all events now.
1644
1645
1646    if (!event || !event.isPropagationStopped) {
1647      var old = event || window$1.event;
1648      event = {}; // Clone the old object so that we can modify the values event = {};
1649      // IE8 Doesn't like when you mess with native event properties
1650      // Firefox returns false for event.hasOwnProperty('type') and other props
1651      //  which makes copying more difficult.
1652      // TODO: Probably best to create a whitelist of event props
1653
1654      for (var key in old) {
1655        // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
vendor: 4,373 bytes, lines 1656-1799
1656        // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
1657        // and webkitMovementX/Y
1658        if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') {
1659          // Chrome 32+ warns if you try to copy deprecated returnValue, but
1660          // we still want to if preventDefault isn't supported (IE8).
1661          if (!(key === 'returnValue' && old.preventDefault)) {
1662            event[key] = old[key];
1663          }
1664        }
1665      } // The event occurred on this element
1666
1667
1668      if (!event.target) {
1669        event.target = event.srcElement || document;
1670      } // Handle which other element the event is related to
1671
1672
1673      if (!event.relatedTarget) {
1674        event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
1675      } // Stop the default browser action
1676
1677
1678      event.preventDefault = function () {
1679        if (old.preventDefault) {
1680          old.preventDefault();
1681        }
1682
1683        event.returnValue = false;
1684        old.returnValue = false;
1685        event.defaultPrevented = true;
1686      };
1687
1688      event.defaultPrevented = false; // Stop the event from bubbling
1689
1690      event.stopPropagation = function () {
1691        if (old.stopPropagation) {
1692          old.stopPropagation();
1693        }
1694
1695        event.cancelBubble = true;
1696        old.cancelBubble = true;
1697        event.isPropagationStopped = returnTrue;
1698      };
1699
1700      event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers
1701
1702      event.stopImmediatePropagation = function () {
1703        if (old.stopImmediatePropagation) {
1704          old.stopImmediatePropagation();
1705        }
1706
1707        event.isImmediatePropagationStopped = returnTrue;
1708        event.stopPropagation();
1709      };
1710
1711      event.isImmediatePropagationStopped = returnFalse; // Handle mouse position
1712
1713      if (event.clientX !== null && event.clientX !== undefined) {
1714        var doc = document.documentElement;
1715        var body = document.body;
1716        event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
1717        event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
1718      } // Handle key presses
1719
1720
1721      event.which = event.charCode || event.keyCode; // Fix button for mouse clicks:
1722      // 0 == left; 1 == middle; 2 == right
1723
1724      if (event.button !== null && event.button !== undefined) {
1725        // The following is disabled because it does not pass videojs-standard
1726        // and... yikes.
1727
1728        /* eslint-disable */
1729        event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
1730        /* eslint-enable */
1731      }
1732    } // Returns fixed-up instance
1733
1734
1735    return event;
1736  }
1737  /**
1738   * Whether passive event listeners are supported
1739   */
1740
1741  var _supportsPassive = false;
1742
1743  (function () {
1744    try {
1745      var opts = Object.defineProperty({}, 'passive', {
1746        get: function get() {
1747          _supportsPassive = true;
1748        }
1749      });
1750      window$1.addEventListener('test', null, opts);
1751      window$1.removeEventListener('test', null, opts);
1752    } catch (e) {// disregard
1753    }
1754  })();
1755  /**
1756   * Touch events Chrome expects to be passive
1757   */
1758
1759
1760  var passiveEvents = ['touchstart', 'touchmove'];
1761  /**
1762   * Add an event listener to element
1763   * It stores the handler function in a separate cache object
1764   * and adds a generic handler to the element's event,
1765   * along with a unique id (guid) to the element.
1766   *
1767   * @param {Element|Object} elem
1768   *        Element or object to bind listeners to
1769   *
1770   * @param {string|string[]} type
1771   *        Type of event to bind to.
1772   *
1773   * @param {EventTarget~EventListener} fn
1774   *        Event listener.
1775   */
1776
1777  function on(elem, type, fn) {
1778    if (Array.isArray(type)) {
1779      return _handleMultipleEvents(on, elem, type, fn);
1780    }
1781
1782    var data = getData(elem); // We need a place to store all our handler data
1783
1784    if (!data.handlers) {
1785      data.handlers = {};
1786    }
1787
1788    if (!data.handlers[type]) {
1789      data.handlers[type] = [];
1790    }
1791
1792    if (!fn.guid) {
1793      fn.guid = newGUID();
1794    }
1795
1796    data.handlers[type].push(fn);
1797
1798    if (!data.dispatcher) {
1799      data.disabled = false;
vendor: 9,558 bytes, lines 1800-2139
1800
1801      data.dispatcher = function (event, hash) {
1802        if (data.disabled) {
1803          return;
1804        }
1805
1806        event = fixEvent(event);
1807        var handlers = data.handlers[event.type];
1808
1809        if (handlers) {
1810          // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
1811          var handlersCopy = handlers.slice(0);
1812
1813          for (var m = 0, n = handlersCopy.length; m < n; m++) {
1814            if (event.isImmediatePropagationStopped()) {
1815              break;
1816            } else {
1817              try {
1818                handlersCopy[m].call(elem, event, hash);
1819              } catch (e) {
1820                log.error(e);
1821              }
1822            }
1823          }
1824        }
1825      };
1826    }
1827
1828    if (data.handlers[type].length === 1) {
1829      if (elem.addEventListener) {
1830        var options = false;
1831
1832        if (_supportsPassive && passiveEvents.indexOf(type) > -1) {
1833          options = {
1834            passive: true
1835          };
1836        }
1837
1838        elem.addEventListener(type, data.dispatcher, options);
1839      } else if (elem.attachEvent) {
1840        elem.attachEvent('on' + type, data.dispatcher);
1841      }
1842    }
1843  }
1844  /**
1845   * Removes event listeners from an element
1846   *
1847   * @param {Element|Object} elem
1848   *        Object to remove listeners from.
1849   *
1850   * @param {string|string[]} [type]
1851   *        Type of listener to remove. Don't include to remove all events from element.
1852   *
1853   * @param {EventTarget~EventListener} [fn]
1854   *        Specific listener to remove. Don't include to remove listeners for an event
1855   *        type.
1856   */
1857
1858  function off(elem, type, fn) {
1859    // Don't want to add a cache object through getElData if not needed
1860    if (!hasData(elem)) {
1861      return;
1862    }
1863
1864    var data = getData(elem); // If no events exist, nothing to unbind
1865
1866    if (!data.handlers) {
1867      return;
1868    }
1869
1870    if (Array.isArray(type)) {
1871      return _handleMultipleEvents(off, elem, type, fn);
1872    } // Utility function
1873
1874
1875    var removeType = function removeType(el, t) {
1876      data.handlers[t] = [];
1877
1878      _cleanUpEvents(el, t);
1879    }; // Are we removing all bound events?
1880
1881
1882    if (type === undefined) {
1883      for (var t in data.handlers) {
1884        if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
1885          removeType(elem, t);
1886        }
1887      }
1888
1889      return;
1890    }
1891
1892    var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind
1893
1894    if (!handlers) {
1895      return;
1896    } // If no listener was provided, remove all listeners for type
1897
1898
1899    if (!fn) {
1900      removeType(elem, type);
1901      return;
1902    } // We're only removing a single handler
1903
1904
1905    if (fn.guid) {
1906      for (var n = 0; n < handlers.length; n++) {
1907        if (handlers[n].guid === fn.guid) {
1908          handlers.splice(n--, 1);
1909        }
1910      }
1911    }
1912
1913    _cleanUpEvents(elem, type);
1914  }
1915  /**
1916   * Trigger an event for an element
1917   *
1918   * @param {Element|Object} elem
1919   *        Element to trigger an event on
1920   *
1921   * @param {EventTarget~Event|string} event
1922   *        A string (the type) or an event object with a type attribute
1923   *
1924   * @param {Object} [hash]
1925   *        data hash to pass along with the event
1926   *
1927   * @return {boolean|undefined}
1928   *         Returns the opposite of `defaultPrevented` if default was
1929   *         prevented. Otherwise, returns `undefined`
1930   */
1931
1932  function trigger(elem, event, hash) {
1933    // Fetches element data and a reference to the parent (for bubbling).
1934    // Don't want to add a data object to cache for every parent,
1935    // so checking hasElData first.
1936    var elemData = hasData(elem) ? getData(elem) : {};
1937    var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event,
1938    // handler;
1939    // If an event name was passed as a string, creates an event out of it
1940
1941    if (typeof event === 'string') {
1942      event = {
1943        type: event,
1944        target: elem
1945      };
1946    } else if (!event.target) {
1947      event.target = elem;
1948    } // Normalizes the event properties.
1949
1950
1951    event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers.
1952
1953    if (elemData.dispatcher) {
1954      elemData.dispatcher.call(elem, event, hash);
1955    } // Unless explicitly stopped or the event does not bubble (e.g. media events)
1956    // recursively calls this function to bubble the event up the DOM.
1957
1958
1959    if (parent && !event.isPropagationStopped() && event.bubbles === true) {
1960      trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled.
1961    } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
1962      var targetData = getData(event.target); // Checks if the target has a default action for this event.
1963
1964      if (event.target[event.type]) {
1965        // Temporarily disables event dispatching on the target as we have already executed the handler.
1966        targetData.disabled = true; // Executes the default action.
1967
1968        if (typeof event.target[event.type] === 'function') {
1969          event.target[event.type]();
1970        } // Re-enables event dispatching.
1971
1972
1973        targetData.disabled = false;
1974      }
1975    } // Inform the triggerer if the default was prevented by returning false
1976
1977
1978    return !event.defaultPrevented;
1979  }
1980  /**
1981   * Trigger a listener only once for an event.
1982   *
1983   * @param {Element|Object} elem
1984   *        Element or object to bind to.
1985   *
1986   * @param {string|string[]} type
1987   *        Name/type of event
1988   *
1989   * @param {Event~EventListener} fn
1990   *        Event listener function
1991   */
1992
1993  function one(elem, type, fn) {
1994    if (Array.isArray(type)) {
1995      return _handleMultipleEvents(one, elem, type, fn);
1996    }
1997
1998    var func = function func() {
1999      off(elem, type, func);
2000      fn.apply(this, arguments);
2001    }; // copy the guid to the new function so it can removed using the original function's ID
2002
2003
2004    func.guid = fn.guid = fn.guid || newGUID();
2005    on(elem, type, func);
2006  }
2007
2008  var Events = /*#__PURE__*/Object.freeze({
2009    fixEvent: fixEvent,
2010    on: on,
2011    off: off,
2012    trigger: trigger,
2013    one: one
2014  });
2015
2016  /**
2017   * @file setup.js - Functions for setting up a player without
2018   * user interaction based on the data-setup `attribute` of the video tag.
2019   *
2020   * @module setup
2021   */
2022  var _windowLoaded = false;
2023  var videojs;
2024  /**
2025   * Set up any tags that have a data-setup `attribute` when the player is started.
2026   */
2027
2028  var autoSetup = function autoSetup() {
2029    // Protect against breakage in non-browser environments and check global autoSetup option.
2030    if (!isReal() || videojs.options.autoSetup === false) {
2031      return;
2032    }
2033
2034    var vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
2035    var audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
2036    var divs = Array.prototype.slice.call(document.getElementsByTagName('video-js'));
2037    var mediaEls = vids.concat(audios, divs); // Check if any media elements exist
2038
2039    if (mediaEls && mediaEls.length > 0) {
2040      for (var i = 0, e = mediaEls.length; i < e; i++) {
2041        var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func.
2042
2043        if (mediaEl && mediaEl.getAttribute) {
2044          // Make sure this player hasn't already been set up.
2045          if (mediaEl.player === undefined) {
2046            var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists.
2047            // We only auto-setup if they've added the data-setup attr.
2048
2049            if (options !== null) {
2050              // Create new video.js instance.
2051              videojs(mediaEl);
2052            }
2053          } // If getAttribute isn't defined, we need to wait for the DOM.
2054
2055        } else {
2056          autoSetupTimeout(1);
2057          break;
2058        }
2059      } // No videos were found, so keep looping unless page is finished loading.
2060
2061    } else if (!_windowLoaded) {
2062      autoSetupTimeout(1);
2063    }
2064  };
2065  /**
2066   * Wait until the page is loaded before running autoSetup. This will be called in
2067   * autoSetup if `hasLoaded` returns false.
2068   *
2069   * @param {number} wait
2070   *        How long to wait in ms
2071   *
2072   * @param {module:videojs} [vjs]
2073   *        The videojs library function
2074   */
2075
2076
2077  function autoSetupTimeout(wait, vjs) {
2078    if (vjs) {
2079      videojs = vjs;
2080    }
2081
2082    window$1.setTimeout(autoSetup, wait);
2083  }
2084
2085  if (isReal() && document.readyState === 'complete') {
2086    _windowLoaded = true;
2087  } else {
2088    /**
2089     * Listen for the load event on window, and set _windowLoaded to true.
2090     *
2091     * @listens load
2092     */
2093    one(window$1, 'load', function () {
2094      _windowLoaded = true;
2095    });
2096  }
2097
2098  /**
2099   * @file stylesheet.js
2100   * @module stylesheet
2101   */
2102  /**
2103   * Create a DOM syle element given a className for it.
2104   *
2105   * @param {string} className
2106   *        The className to add to the created style element.
2107   *
2108   * @return {Element}
2109   *         The element that was created.
2110   */
2111
2112  var createStyleElement = function createStyleElement(className) {
2113    var style = document.createElement('style');
2114    style.className = className;
2115    return style;
2116  };
2117  /**
2118   * Add text to a DOM element.
2119   *
2120   * @param {Element} el
2121   *        The Element to add text content to.
2122   *
2123   * @param {string} content
2124   *        The text to add to the element.
2125   */
2126
2127  var setTextContent = function setTextContent(el, content) {
2128    if (el.styleSheet) {
2129      el.styleSheet.cssText = content;
2130    } else {
2131      el.textContent = content;
2132    }
2133  };
2134
2135  /**
2136   * @file fn.js
2137   * @module fn
2138   */
2139  /**
2140   * Bind (a.k.a proxy or context). A simple method for changing the context of
2141   * a function.
2142   *
2143   * It also stores a unique id on the function so it can be easily removed from
2144   * events.
2145   *
2146   * @function
2147   * @param    {Mixed} context
2148   *           The object to bind as scope.
2149   *
2150   * @param    {Function} fn
2151   *           The function to be bound to a scope.
2152   *
2153   * @param    {number} [uid]
2154   *           An optional unique ID for the function to be set
2155   *
2156   * @return   {Function}
2157   *           The new function that will be bound into the context given
2158   */
2159
2160  var bind = function bind(context, fn, uid) {
2161    // Make sure the function has a unique ID
2162    if (!fn.guid) {
2163      fn.guid = newGUID();
2164    } // Create the new function that changes the context
2165
2166
2167    var bound = function bound() {
2168      return fn.apply(context, arguments);
2169    }; // Allow for the ability to individualize this function
2170    // Needed in the case where multiple objects might share the same prototype
2171    // IF both items add an event listener with the same function, then you try to remove just one
2172    // it will remove both because they both have the same guid.
2173    // when using this, you need to use the bind method when you remove the listener as well.
2174    // currently used in text tracks
2175
2176
2177    bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
2178    return bound;
2179  };
2180  /**
2181   * Wraps the given function, `fn`, with a new function that only invokes `fn`
2182   * at most once per every `wait` milliseconds.
2183   *
2184   * @function
2185   * @param    {Function} fn
2186   *           The function to be throttled.
2187   *
2188   * @param    {number}   wait
2189   *           The number of milliseconds by which to throttle.
2190   *
2191   * @return   {Function}
2192   */
2193
2194  var throttle = function throttle(fn, wait) {
2195    var last = Date.now();
2196
2197    var throttled = function throttled() {
2198      var now = Date.now();
2199
2200      if (now - last >= wait) {
2201        fn.apply(void 0, arguments);
2202        last = now;
2203      }
2204    };
2205
2206    return throttled;
2207  };
2208  /**
2209   * Creates a debounced function that delays invoking `func` until after `wait`
2210   * milliseconds have elapsed since the last time the debounced function was
2211   * invoked.
2212   *
2213   * Inspired by lodash and underscore implementations.
2214   *
2215   * @function
2216   * @param    {Function} func
2217   *           The function to wrap with debounce behavior.
2218   *
2219   * @param    {number} wait
2220   *           The number of milliseconds to wait after the last invocation.
2221   *
2222   * @param    {boolean} [immediate]
2223   *           Whether or not to invoke the function immediately upon creation.
2224   *
2225   * @param    {Object} [context=window]
2226   *           The "context" in which the debounced function should debounce. For
2227   *           example, if this function should be tied to a Video.js player,
2228   *           the player can be passed here. Alternatively, defaults to the
2229   *           global `window` object.
2230   *
2231   * @return   {Function}
2232   *           A debounced function.
2233   */
2234
2235  var debounce = function debounce(func, wait, immediate, context) {
2236    if (context === void 0) {
2237      context = window$1;
2238    }
2239
2240    var timeout;
2241
2242    var cancel = function cancel() {
2243      context.clearTimeout(timeout);
2244      timeout = null;
2245    };
2246    /* eslint-disable consistent-this */
2247
2248
2249    var debounced = function debounced() {
2250      var self = this;
2251      var args = arguments;
2252
2253      var _later = function later() {
2254        timeout = null;
2255        _later = null;
2256
2257        if (!immediate) {
2258          func.apply(self, args);
2259        }
2260      };
2261
2262      if (!timeout && immediate) {
2263        func.apply(self, args);
2264      }
2265
2266      context.clearTimeout(timeout);
2267      timeout = context.setTimeout(_later, wait);
2268    };
2269    /* eslint-enable consistent-this */
2270
2271
2272    debounced.cancel = cancel;
2273    return debounced;
2274  };
2275
2276  /**
2277   * @file src/js/event-target.js
2278   */
2279  /**
2280   * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
2281   * adds shorthand functions that wrap around lengthy functions. For example:
2282   * the `on` function is a wrapper around `addEventListener`.
2283   *
2284   * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
2285   * @class EventTarget
2286   */
2287
2288  var EventTarget = function EventTarget() {};
2289  /**
vendor: 7,375 bytes, lines 2290-2550
2290   * A Custom DOM event.
2291   *
2292   * @typedef {Object} EventTarget~Event
2293   * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
2294   */
2295
2296  /**
2297   * All event listeners should follow the following format.
2298   *
2299   * @callback EventTarget~EventListener
2300   * @this {EventTarget}
2301   *
2302   * @param {EventTarget~Event} event
2303   *        the event that triggered this function
2304   *
2305   * @param {Object} [hash]
2306   *        hash of data sent during the event
2307   */
2308
2309  /**
2310   * An object containing event names as keys and booleans as values.
2311   *
2312   * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
2313   *         will have extra functionality. See that function for more information.
2314   *
2315   * @property EventTarget.prototype.allowedEvents_
2316   * @private
2317   */
2318
2319
2320  EventTarget.prototype.allowedEvents_ = {};
2321  /**
2322   * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
2323   * function that will get called when an event with a certain name gets triggered.
2324   *
2325   * @param {string|string[]} type
2326   *        An event name or an array of event names.
2327   *
2328   * @param {EventTarget~EventListener} fn
2329   *        The function to call with `EventTarget`s
2330   */
2331
2332  EventTarget.prototype.on = function (type, fn) {
2333    // Remove the addEventListener alias before calling Events.on
2334    // so we don't get into an infinite type loop
2335    var ael = this.addEventListener;
2336
2337    this.addEventListener = function () {};
2338
2339    on(this, type, fn);
2340    this.addEventListener = ael;
2341  };
2342  /**
2343   * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
2344   * the standard DOM API.
2345   *
2346   * @function
2347   * @see {@link EventTarget#on}
2348   */
2349
2350
2351  EventTarget.prototype.addEventListener = EventTarget.prototype.on;
2352  /**
2353   * Removes an `event listener` for a specific event from an instance of `EventTarget`.
2354   * This makes it so that the `event listener` will no longer get called when the
2355   * named event happens.
2356   *
2357   * @param {string|string[]} type
2358   *        An event name or an array of event names.
2359   *
2360   * @param {EventTarget~EventListener} fn
2361   *        The function to remove.
2362   */
2363
2364  EventTarget.prototype.off = function (type, fn) {
2365    off(this, type, fn);
2366  };
2367  /**
2368   * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
2369   * the standard DOM API.
2370   *
2371   * @function
2372   * @see {@link EventTarget#off}
2373   */
2374
2375
2376  EventTarget.prototype.removeEventListener = EventTarget.prototype.off;
2377  /**
2378   * This function will add an `event listener` that gets triggered only once. After the
2379   * first trigger it will get removed. This is like adding an `event listener`
2380   * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
2381   *
2382   * @param {string|string[]} type
2383   *        An event name or an array of event names.
2384   *
2385   * @param {EventTarget~EventListener} fn
2386   *        The function to be called once for each event name.
2387   */
2388
2389  EventTarget.prototype.one = function (type, fn) {
2390    // Remove the addEventListener alialing Events.on
2391    // so we don't get into an infinite type loop
2392    var ael = this.addEventListener;
2393
2394    this.addEventListener = function () {};
2395
2396    one(this, type, fn);
2397    this.addEventListener = ael;
2398  };
2399  /**
2400   * This function causes an event to happen. This will then cause any `event listeners`
2401   * that are waiting for that event, to get called. If there are no `event listeners`
2402   * for an event then nothing will happen.
2403   *
2404   * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
2405   * Trigger will also call the `on` + `uppercaseEventName` function.
2406   *
2407   * Example:
2408   * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
2409   * `onClick` if it exists.
2410   *
2411   * @param {string|EventTarget~Event|Object} event
2412   *        The name of the event, an `Event`, or an object with a key of type set to
2413   *        an event name.
2414   */
2415
2416
2417  EventTarget.prototype.trigger = function (event) {
2418    var type = event.type || event; // deprecation
2419    // In a future version we should default target to `this`
2420    // similar to how we default the target to `elem` in
2421    // `Events.trigger`. Right now the default `target` will be
2422    // `document` due to the `Event.fixEvent` call.
2423
2424    if (typeof event === 'string') {
2425      event = {
2426        type: type
2427      };
2428    }
2429
2430    event = fixEvent(event);
2431
2432    if (this.allowedEvents_[type] && this['on' + type]) {
2433      this['on' + type](event);
2434    }
2435
2436    trigger(this, event);
2437  };
2438  /**
2439   * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
2440   * the standard DOM API.
2441   *
2442   * @function
2443   * @see {@link EventTarget#trigger}
2444   */
2445
2446
2447  EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger;
2448  var EVENT_MAP;
2449
2450  EventTarget.prototype.queueTrigger = function (event) {
2451    var _this = this;
2452
2453    // only set up EVENT_MAP if it'll be used
2454    if (!EVENT_MAP) {
2455      EVENT_MAP = new Map();
2456    }
2457
2458    var type = event.type || event;
2459    var map = EVENT_MAP.get(this);
2460
2461    if (!map) {
2462      map = new Map();
2463      EVENT_MAP.set(this, map);
2464    }
2465
2466    var oldTimeout = map.get(type);
2467    map.delete(type);
2468    window$1.clearTimeout(oldTimeout);
2469    var timeout = window$1.setTimeout(function () {
2470      // if we cleared out all timeouts for the current target, delete its map
2471      if (map.size === 0) {
2472        map = null;
2473        EVENT_MAP.delete(_this);
2474      }
2475
2476      _this.trigger(event);
2477    }, 0);
2478    map.set(type, timeout);
2479  };
2480
2481  /**
2482   * @file mixins/evented.js
2483   * @module evented
2484   */
2485  /**
2486   * Returns whether or not an object has had the evented mixin applied.
2487   *
2488   * @param  {Object} object
2489   *         An object to test.
2490   *
2491   * @return {boolean}
2492   *         Whether or not the object appears to be evented.
2493   */
2494
2495  var isEvented = function isEvented(object) {
2496    return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) {
2497      return typeof object[k] === 'function';
2498    });
2499  };
2500  /**
2501   * Adds a callback to run after the evented mixin applied.
2502   *
2503   * @param  {Object} object
2504   *         An object to Add
2505   * @param  {Function} callback
2506   *         The callback to run.
2507   */
2508
2509
2510  var addEventedCallback = function addEventedCallback(target, callback) {
2511    if (isEvented(target)) {
2512      callback();
2513    } else {
2514      if (!target.eventedCallbacks) {
2515        target.eventedCallbacks = [];
2516      }
2517
2518      target.eventedCallbacks.push(callback);
2519    }
2520  };
2521  /**
2522   * Whether a value is a valid event type - non-empty string or array.
2523   *
2524   * @private
2525   * @param  {string|Array} type
2526   *         The type value to test.
2527   *
2528   * @return {boolean}
2529   *         Whether or not the type is a valid event type.
2530   */
2531
2532
2533  var isValidEventType = function isValidEventType(type) {
2534    return (// The regex here verifies that the `type` contains at least one non-
2535      // whitespace character.
2536      typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length
2537    );
2538  };
2539  /**
2540   * Validates a value to determine if it is a valid event target. Throws if not.
2541   *
2542   * @private
2543   * @throws {Error}
2544   *         If the target does not appear to be a valid event target.
2545   *
2546   * @param  {Object} target
2547   *         The object to test.
2548   */
2549
2550
vendor: 6,293 bytes, lines 2551-2744
2551  var validateTarget = function validateTarget(target) {
2552    if (!target.nodeName && !isEvented(target)) {
2553      throw new Error('Invalid target; must be a DOM node or evented object.');
2554    }
2555  };
2556  /**
2557   * Validates a value to determine if it is a valid event target. Throws if not.
2558   *
2559   * @private
2560   * @throws {Error}
2561   *         If the type does not appear to be a valid event type.
2562   *
2563   * @param  {string|Array} type
2564   *         The type to test.
2565   */
2566
2567
2568  var validateEventType = function validateEventType(type) {
2569    if (!isValidEventType(type)) {
2570      throw new Error('Invalid event type; must be a non-empty string or array.');
2571    }
2572  };
2573  /**
2574   * Validates a value to determine if it is a valid listener. Throws if not.
2575   *
2576   * @private
2577   * @throws {Error}
2578   *         If the listener is not a function.
2579   *
2580   * @param  {Function} listener
2581   *         The listener to test.
2582   */
2583
2584
2585  var validateListener = function validateListener(listener) {
2586    if (typeof listener !== 'function') {
2587      throw new Error('Invalid listener; must be a function.');
2588    }
2589  };
2590  /**
2591   * Takes an array of arguments given to `on()` or `one()`, validates them, and
2592   * normalizes them into an object.
2593   *
2594   * @private
2595   * @param  {Object} self
2596   *         The evented object on which `on()` or `one()` was called. This
2597   *         object will be bound as the `this` value for the listener.
2598   *
2599   * @param  {Array} args
2600   *         An array of arguments passed to `on()` or `one()`.
2601   *
2602   * @return {Object}
2603   *         An object containing useful values for `on()` or `one()` calls.
2604   */
2605
2606
2607  var normalizeListenArgs = function normalizeListenArgs(self, args) {
2608    // If the number of arguments is less than 3, the target is always the
2609    // evented object itself.
2610    var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
2611    var target;
2612    var type;
2613    var listener;
2614
2615    if (isTargetingSelf) {
2616      target = self.eventBusEl_; // Deal with cases where we got 3 arguments, but we are still listening to
2617      // the evented object itself.
2618
2619      if (args.length >= 3) {
2620        args.shift();
2621      }
2622
2623      type = args[0];
2624      listener = args[1];
2625    } else {
2626      target = args[0];
2627      type = args[1];
2628      listener = args[2];
2629    }
2630
2631    validateTarget(target);
2632    validateEventType(type);
2633    validateListener(listener);
2634    listener = bind(self, listener);
2635    return {
2636      isTargetingSelf: isTargetingSelf,
2637      target: target,
2638      type: type,
2639      listener: listener
2640    };
2641  };
2642  /**
2643   * Adds the listener to the event type(s) on the target, normalizing for
2644   * the type of target.
2645   *
2646   * @private
2647   * @param  {Element|Object} target
2648   *         A DOM node or evented object.
2649   *
2650   * @param  {string} method
2651   *         The event binding method to use ("on" or "one").
2652   *
2653   * @param  {string|Array} type
2654   *         One or more event type(s).
2655   *
2656   * @param  {Function} listener
2657   *         A listener function.
2658   */
2659
2660
2661  var listen = function listen(target, method, type, listener) {
2662    validateTarget(target);
2663
2664    if (target.nodeName) {
2665      Events[method](target, type, listener);
2666    } else {
2667      target[method](type, listener);
2668    }
2669  };
2670  /**
2671   * Contains methods that provide event capabilities to an object which is passed
2672   * to {@link module:evented|evented}.
2673   *
2674   * @mixin EventedMixin
2675   */
2676
2677
2678  var EventedMixin = {
2679    /**
2680     * Add a listener to an event (or events) on this object or another evented
2681     * object.
2682     *
2683     * @param  {string|Array|Element|Object} targetOrType
2684     *         If this is a string or array, it represents the event type(s)
2685     *         that will trigger the listener.
2686     *
2687     *         Another evented object can be passed here instead, which will
2688     *         cause the listener to listen for events on _that_ object.
2689     *
2690     *         In either case, the listener's `this` value will be bound to
2691     *         this object.
2692     *
2693     * @param  {string|Array|Function} typeOrListener
2694     *         If the first argument was a string or array, this should be the
2695     *         listener function. Otherwise, this is a string or array of event
2696     *         type(s).
2697     *
2698     * @param  {Function} [listener]
2699     *         If the first argument was another evented object, this will be
2700     *         the listener function.
2701     */
2702    on: function on$$1() {
2703      var _this = this;
2704
2705      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
2706        args[_key] = arguments[_key];
2707      }
2708
2709      var _normalizeListenArgs = normalizeListenArgs(this, args),
2710          isTargetingSelf = _normalizeListenArgs.isTargetingSelf,
2711          target = _normalizeListenArgs.target,
2712          type = _normalizeListenArgs.type,
2713          listener = _normalizeListenArgs.listener;
2714
2715      listen(target, 'on', type, listener); // If this object is listening to another evented object.
2716
2717      if (!isTargetingSelf) {
2718        // If this object is disposed, remove the listener.
2719        var removeListenerOnDispose = function removeListenerOnDispose() {
2720          return _this.off(target, type, listener);
2721        }; // Use the same function ID as the listener so we can remove it later it
2722        // using the ID of the original listener.
2723
2724
2725        removeListenerOnDispose.guid = listener.guid; // Add a listener to the target's dispose event as well. This ensures
2726        // that if the target is disposed BEFORE this object, we remove the
2727        // removal listener that was just added. Otherwise, we create a memory leak.
2728
2729        var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() {
2730          return _this.off('dispose', removeListenerOnDispose);
2731        }; // Use the same function ID as the listener so we can remove it later
2732        // it using the ID of the original listener.
2733
2734
2735        removeRemoverOnTargetDispose.guid = listener.guid;
2736        listen(this, 'on', 'dispose', removeListenerOnDispose);
2737        listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
2738      }
2739    },
2740
2741    /**
2742     * Add a listener to an event (or events) on this object or another evented
2743     * object. The listener will only be called once and then removed.
2744     *
2745     * @param  {string|Array|Element|Object} targetOrType
2746     *         If this is a string or array, it represents the event type(s)
2747     *         that will trigger the listener.
2748     *
2749     *         Another evented object can be passed here instead, which will
2750     *         cause the listener to listen for events on _that_ object.
2751     *
2752     *         In either case, the listener's `this` value will be bound to
2753     *         this object.
2754     *
2755     * @param  {string|Array|Function} typeOrListener
2756     *         If the first argument was a string or array, this should be the
2757     *         listener function. Otherwise, this is a string or array of event
2758     *         type(s).
2759     *
2760     * @param  {Function} [listener]
2761     *         If the first argument was another evented object, this will be
2762     *         the listener function.
2763     */
2764    one: function one$$1() {
2765      var _this2 = this;
2766
2767      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
2768        args[_key2] = arguments[_key2];
2769      }
2770
2771      var _normalizeListenArgs2 = normalizeListenArgs(this, args),
2772          isTargetingSelf = _normalizeListenArgs2.isTargetingSelf,
2773          target = _normalizeListenArgs2.target,
2774          type = _normalizeListenArgs2.type,
2775          listener = _normalizeListenArgs2.listener; // Targeting this evented object.
2776
2777
2778      if (isTargetingSelf) {
2779        listen(target, 'one', type, listener); // Targeting another evented object.
2780      } else {
2781        var wrapper = function wrapper() {
2782          _this2.off(target, type, wrapper);
2783
2784          for (var _len3 = arguments.length, largs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
2785            largs[_key3] = arguments[_key3];
2786          }
2787
2788          listener.apply(null, largs);
2789        }; // Use the same function ID as the listener so we can remove it later
2790        // it using the ID of the original listener.
2791
2792
2793        wrapper.guid = listener.guid;
2794        listen(target, 'one', type, wrapper);
2795      }
2796    },
2797
2798    /**
2799     * Removes listener(s) from event(s) on an evented object.
2800     *
2801     * @param  {string|Array|Element|Object} [targetOrType]
2802     *         If this is a string or array, it represents the event type(s).
2803     *
2804     *         Another evented object can be passed here instead, in which case
2805     *         ALL 3 arguments are _required_.
2806     *
2807     * @param  {string|Array|Function} [typeOrListener]
2808     *         If the first argument was a string or array, this may be the
2809     *         listener function. Otherwise, this is a string or array of event
2810     *         type(s).
2811     *
2812     * @param  {Function} [listener]
2813     *         If the first argument was another evented object, this will be
2814     *         the listener function; otherwise, _all_ listeners bound to the
2815     *         event type(s) will be removed.
2816     */
2817    off: function off$$1(targetOrType, typeOrListener, listener) {
2818      // Targeting this evented object.
2819      if (!targetOrType || isValidEventType(targetOrType)) {
2820        off(this.eventBusEl_, targetOrType, typeOrListener); // Targeting another evented object.
2821      } else {
2822        var target = targetOrType;
2823        var type = typeOrListener; // Fail fast and in a meaningful way!
2824
2825        validateTarget(target);
2826        validateEventType(type);
2827        validateListener(listener); // Ensure there's at least a guid, even if the function hasn't been used
2828
2829        listener = bind(this, listener); // Remove the dispose listener on this evented object, which was given
2830        // the same guid as the event listener in on().
2831
2832        this.off('dispose', listener);
2833
2834        if (target.nodeName) {
2835          off(target, type, listener);
2836          off(target, 'dispose', listener);
2837        } else if (isEvented(target)) {
2838          target.off(type, listener);
2839          target.off('dispose', listener);
2840        }
2841      }
2842    },
2843
2844    /**
2845     * Fire an event on this evented object, causing its listeners to be called.
2846     *
2847     * @param   {string|Object} event
2848     *          An event type or an object with a type property.
2849     *
2850     * @param   {Object} [hash]
2851     *          An additional object to pass along to listeners.
2852     *
2853     * @return {boolean}
2854     *          Whether or not the default behavior was prevented.
2855     */
2856    trigger: function trigger$$1(event, hash) {
2857      return trigger(this.eventBusEl_, event, hash);
2858    }
2859  };
2860  /**
2861   * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
2862   *
2863   * @param  {Object} target
2864   *         The object to which to add event methods.
2865   *
2866   * @param  {Object} [options={}]
2867   *         Options for customizing the mixin behavior.
2868   *
2869   * @param  {string} [options.eventBusKey]
2870   *         By default, adds a `eventBusEl_` DOM element to the target object,
2871   *         which is used as an event bus. If the target object already has a
2872   *         DOM element that should be used, pass its key here.
2873   *
2874   * @return {Object}
2875   *         The target object.
2876   */
2877
2878  function evented(target, options) {
2879    if (options === void 0) {
2880      options = {};
2881    }
2882
2883    var _options = options,
2884        eventBusKey = _options.eventBusKey; // Set or create the eventBusEl_.
2885
vendor: 5,588 bytes, lines 2886-3080
2886    if (eventBusKey) {
2887      if (!target[eventBusKey].nodeName) {
2888        throw new Error("The eventBusKey \"" + eventBusKey + "\" does not refer to an element.");
2889      }
2890
2891      target.eventBusEl_ = target[eventBusKey];
2892    } else {
2893      target.eventBusEl_ = createEl('span', {
2894        className: 'vjs-event-bus'
2895      });
2896    }
2897
2898    assign(target, EventedMixin);
2899
2900    if (target.eventedCallbacks) {
2901      target.eventedCallbacks.forEach(function (callback) {
2902        callback();
2903      });
2904    } // When any evented object is disposed, it removes all its listeners.
2905
2906
2907    target.on('dispose', function () {
2908      target.off();
2909      window$1.setTimeout(function () {
2910        target.eventBusEl_ = null;
2911      }, 0);
2912    });
2913    return target;
2914  }
2915
2916  /**
2917   * @file mixins/stateful.js
2918   * @module stateful
2919   */
2920  /**
2921   * Contains methods that provide statefulness to an object which is passed
2922   * to {@link module:stateful}.
2923   *
2924   * @mixin StatefulMixin
2925   */
2926
2927  var StatefulMixin = {
2928    /**
2929     * A hash containing arbitrary keys and values representing the state of
2930     * the object.
2931     *
2932     * @type {Object}
2933     */
2934    state: {},
2935
2936    /**
2937     * Set the state of an object by mutating its
2938     * {@link module:stateful~StatefulMixin.state|state} object in place.
2939     *
2940     * @fires   module:stateful~StatefulMixin#statechanged
2941     * @param   {Object|Function} stateUpdates
2942     *          A new set of properties to shallow-merge into the plugin state.
2943     *          Can be a plain object or a function returning a plain object.
2944     *
2945     * @return {Object|undefined}
2946     *          An object containing changes that occurred. If no changes
2947     *          occurred, returns `undefined`.
2948     */
2949    setState: function setState(stateUpdates) {
2950      var _this = this;
2951
2952      // Support providing the `stateUpdates` state as a function.
2953      if (typeof stateUpdates === 'function') {
2954        stateUpdates = stateUpdates();
2955      }
2956
2957      var changes;
2958      each(stateUpdates, function (value, key) {
2959        // Record the change if the value is different from what's in the
2960        // current state.
2961        if (_this.state[key] !== value) {
2962          changes = changes || {};
2963          changes[key] = {
2964            from: _this.state[key],
2965            to: value
2966          };
2967        }
2968
2969        _this.state[key] = value;
2970      }); // Only trigger "statechange" if there were changes AND we have a trigger
2971      // function. This allows us to not require that the target object be an
2972      // evented object.
2973
2974      if (changes && isEvented(this)) {
2975        /**
2976         * An event triggered on an object that is both
2977         * {@link module:stateful|stateful} and {@link module:evented|evented}
2978         * indicating that its state has changed.
2979         *
2980         * @event    module:stateful~StatefulMixin#statechanged
2981         * @type     {Object}
2982         * @property {Object} changes
2983         *           A hash containing the properties that were changed and
2984         *           the values they were changed `from` and `to`.
2985         */
2986        this.trigger({
2987          changes: changes,
2988          type: 'statechanged'
2989        });
2990      }
2991
2992      return changes;
2993    }
2994  };
2995  /**
2996   * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
2997   * object.
2998   *
2999   * If the target object is {@link module:evented|evented} and has a
3000   * `handleStateChanged` method, that method will be automatically bound to the
3001   * `statechanged` event on itself.
3002   *
3003   * @param   {Object} target
3004   *          The object to be made stateful.
3005   *
3006   * @param   {Object} [defaultState]
3007   *          A default set of properties to populate the newly-stateful object's
3008   *          `state` property.
3009   *
3010   * @return {Object}
3011   *          Returns the `target`.
3012   */
3013
3014  function stateful(target, defaultState) {
3015    assign(target, StatefulMixin); // This happens after the mixing-in because we need to replace the `state`
3016    // added in that step.
3017
3018    target.state = assign({}, target.state, defaultState); // Auto-bind the `handleStateChanged` method of the target object if it exists.
3019
3020    if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
3021      target.on('statechanged', target.handleStateChanged);
3022    }
3023
3024    return target;
3025  }
3026
3027  /**
3028   * @file to-title-case.js
3029   * @module to-title-case
3030   */
3031
3032  /**
3033   * Uppercase the first letter of a string.
3034   *
3035   * @param {string} string
3036   *        String to be uppercased
3037   *
3038   * @return {string}
3039   *         The string with an uppercased first letter
3040   */
3041  function toTitleCase(string) {
3042    if (typeof string !== 'string') {
3043      return string;
3044    }
3045
3046    return string.charAt(0).toUpperCase() + string.slice(1);
3047  }
3048  /**
3049   * Compares the TitleCase versions of the two strings for equality.
3050   *
3051   * @param {string} str1
3052   *        The first string to compare
3053   *
3054   * @param {string} str2
3055   *        The second string to compare
3056   *
3057   * @return {boolean}
3058   *         Whether the TitleCase versions of the strings are equal
3059   */
3060
3061  function titleCaseEquals(str1, str2) {
3062    return toTitleCase(str1) === toTitleCase(str2);
3063  }
3064
3065  /**
3066   * @file merge-options.js
3067   * @module merge-options
3068   */
3069  /**
3070   * Merge two objects recursively.
3071   *
3072   * Performs a deep merge like
3073   * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges
3074   * plain objects (not arrays, elements, or anything else).
3075   *
3076   * Non-plain object values will be copied directly from the right-most
3077   * argument.
3078   *
3079   * @static
3080   * @param   {Object[]} sources
vendor: 10,100 bytes, lines 3081-3406
3081   *          One or more objects to merge into a new object.
3082   *
3083   * @return {Object}
3084   *          A new object that is the merged result of all sources.
3085   */
3086
3087  function mergeOptions() {
3088    var result = {};
3089
3090    for (var _len = arguments.length, sources = new Array(_len), _key = 0; _key < _len; _key++) {
3091      sources[_key] = arguments[_key];
3092    }
3093
3094    sources.forEach(function (source) {
3095      if (!source) {
3096        return;
3097      }
3098
3099      each(source, function (value, key) {
3100        if (!isPlain(value)) {
3101          result[key] = value;
3102          return;
3103        }
3104
3105        if (!isPlain(result[key])) {
3106          result[key] = {};
3107        }
3108
3109        result[key] = mergeOptions(result[key], value);
3110      });
3111    });
3112    return result;
3113  }
3114
3115  /**
3116   * Player Component - Base class for all UI objects
3117   *
3118   * @file component.js
3119   */
3120  /**
3121   * Base class for all UI Components.
3122   * Components are UI objects which represent both a javascript object and an element
3123   * in the DOM. They can be children of other components, and can have
3124   * children themselves.
3125   *
3126   * Components can also use methods from {@link EventTarget}
3127   */
3128
3129  var Component =
3130  /*#__PURE__*/
3131  function () {
3132    /**
3133     * A callback that is called when a component is ready. Does not have any
3134     * paramters and any callback value will be ignored.
3135     *
3136     * @callback Component~ReadyCallback
3137     * @this Component
3138     */
3139
3140    /**
3141     * Creates an instance of this class.
3142     *
3143     * @param {Player} player
3144     *        The `Player` that this class should be attached to.
3145     *
3146     * @param {Object} [options]
3147     *        The key/value store of player options.
3148     *
3149     * @param {Object[]} [options.children]
3150     *        An array of children objects to intialize this component with. Children objects have
3151     *        a name property that will be used if more than one component of the same type needs to be
3152     *        added.
3153     *
3154     * @param {Component~ReadyCallback} [ready]
3155     *        Function that gets called when the `Component` is ready.
3156     */
3157    function Component(player, options, ready) {
3158      // The component might be the player itself and we can't pass `this` to super
3159      if (!player && this.play) {
3160        this.player_ = player = this; // eslint-disable-line
3161      } else {
3162        this.player_ = player;
3163      } // Hold the reference to the parent component via `addChild` method
3164
3165
3166      this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults
3167
3168      this.options_ = mergeOptions({}, this.options_); // Updated options with supplied options
3169
3170      options = this.options_ = mergeOptions(this.options_, options); // Get ID from options or options element if one is supplied
3171
3172      this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one
3173
3174      if (!this.id_) {
3175        // Don't require the player ID function in the case of mock players
3176        var id = player && player.id && player.id() || 'no_player';
3177        this.id_ = id + "_component_" + newGUID();
3178      }
3179
3180      this.name_ = options.name || null; // Create element if one wasn't provided in options
3181
3182      if (options.el) {
3183        this.el_ = options.el;
3184      } else if (options.createEl !== false) {
3185        this.el_ = this.createEl();
3186      } // if evented is anything except false, we want to mixin in evented
3187
3188
3189      if (options.evented !== false) {
3190        // Make this an evented object and use `el_`, if available, as its event bus
3191        evented(this, {
3192          eventBusKey: this.el_ ? 'el_' : null
3193        });
3194      }
3195
3196      stateful(this, this.constructor.defaultState);
3197      this.children_ = [];
3198      this.childIndex_ = {};
3199      this.childNameIndex_ = {}; // Add any child components in options
3200
3201      if (options.initChildren !== false) {
3202        this.initChildren();
3203      }
3204
3205      this.ready(ready); // Don't want to trigger ready here or it will before init is actually
3206      // finished for all children that run this constructor
3207
3208      if (options.reportTouchActivity !== false) {
3209        this.enableTouchActivity();
3210      }
3211    }
3212    /**
3213     * Dispose of the `Component` and all child components.
3214     *
3215     * @fires Component#dispose
3216     */
3217
3218
3219    var _proto = Component.prototype;
3220
3221    _proto.dispose = function dispose() {
3222      /**
3223       * Triggered when a `Component` is disposed.
3224       *
3225       * @event Component#dispose
3226       * @type {EventTarget~Event}
3227       *
3228       * @property {boolean} [bubbles=false]
3229       *           set to false so that the close event does not
3230       *           bubble up
3231       */
3232      this.trigger({
3233        type: 'dispose',
3234        bubbles: false
3235      }); // Dispose all children.
3236
3237      if (this.children_) {
3238        for (var i = this.children_.length - 1; i >= 0; i--) {
3239          if (this.children_[i].dispose) {
3240            this.children_[i].dispose();
3241          }
3242        }
3243      } // Delete child references
3244
3245
3246      this.children_ = null;
3247      this.childIndex_ = null;
3248      this.childNameIndex_ = null;
3249      this.parentComponent_ = null;
3250
3251      if (this.el_) {
3252        // Remove element from DOM
3253        if (this.el_.parentNode) {
3254          this.el_.parentNode.removeChild(this.el_);
3255        }
3256
3257        removeData(this.el_);
3258        this.el_ = null;
3259      } // remove reference to the player after disposing of the element
3260
3261
3262      this.player_ = null;
3263    }
3264    /**
3265     * Return the {@link Player} that the `Component` has attached to.
3266     *
3267     * @return {Player}
3268     *         The player that this `Component` has attached to.
3269     */
3270    ;
3271
3272    _proto.player = function player() {
3273      return this.player_;
3274    }
3275    /**
3276     * Deep merge of options objects with new options.
3277     * > Note: When both `obj` and `options` contain properties whose values are objects.
3278     *         The two properties get merged using {@link module:mergeOptions}
3279     *
3280     * @param {Object} obj
3281     *        The object that contains new options.
3282     *
3283     * @return {Object}
3284     *         A new object of `this.options_` and `obj` merged together.
3285     *
3286     * @deprecated since version 5
3287     */
3288    ;
3289
3290    _proto.options = function options(obj) {
3291      log.warn('this.options() has been deprecated and will be moved to the constructor in 6.0');
3292
3293      if (!obj) {
3294        return this.options_;
3295      }
3296
3297      this.options_ = mergeOptions(this.options_, obj);
3298      return this.options_;
3299    }
3300    /**
3301     * Get the `Component`s DOM element
3302     *
3303     * @return {Element}
3304     *         The DOM element for this `Component`.
3305     */
3306    ;
3307
3308    _proto.el = function el() {
3309      return this.el_;
3310    }
3311    /**
3312     * Create the `Component`s DOM element.
3313     *
3314     * @param {string} [tagName]
3315     *        Element's DOM node type. e.g. 'div'
3316     *
3317     * @param {Object} [properties]
3318     *        An object of properties that should be set.
3319     *
3320     * @param {Object} [attributes]
3321     *        An object of attributes that should be set.
3322     *
3323     * @return {Element}
3324     *         The element that gets created.
3325     */
3326    ;
3327
3328    _proto.createEl = function createEl$$1(tagName, properties, attributes) {
3329      return createEl(tagName, properties, attributes);
3330    }
3331    /**
3332     * Localize a string given the string in english.
3333     *
3334     * If tokens are provided, it'll try and run a simple token replacement on the provided string.
3335     * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
3336     *
3337     * If a `defaultValue` is provided, it'll use that over `string`,
3338     * if a value isn't found in provided language files.
3339     * This is useful if you want to have a descriptive key for token replacement
3340     * but have a succinct localized string and not require `en.json` to be included.
3341     *
3342     * Currently, it is used for the progress bar timing.
3343     * ```js
3344     * {
3345     *   "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
3346     * }
3347     * ```
3348     * It is then used like so:
3349     * ```js
3350     * this.localize('progress bar timing: currentTime={1} duration{2}',
3351     *               [this.player_.currentTime(), this.player_.duration()],
3352     *               '{1} of {2}');
3353     * ```
3354     *
3355     * Which outputs something like: `01:23 of 24:56`.
3356     *
3357     *
3358     * @param {string} string
3359     *        The string to localize and the key to lookup in the language files.
3360     * @param {string[]} [tokens]
3361     *        If the current item has token replacements, provide the tokens here.
3362     * @param {string} [defaultValue]
3363     *        Defaults to `string`. Can be a default value to use for token replacement
3364     *        if the lookup key is needed to be separate.
3365     *
3366     * @return {string}
3367     *         The localized string or if no localization exists the english string.
3368     */
3369    ;
3370
3371    _proto.localize = function localize(string, tokens, defaultValue) {
3372      if (defaultValue === void 0) {
3373        defaultValue = string;
3374      }
3375
3376      var code = this.player_.language && this.player_.language();
3377      var languages = this.player_.languages && this.player_.languages();
3378      var language = languages && languages[code];
3379      var primaryCode = code && code.split('-')[0];
3380      var primaryLang = languages && languages[primaryCode];
3381      var localizedString = defaultValue;
3382
3383      if (language && language[string]) {
3384        localizedString = language[string];
3385      } else if (primaryLang && primaryLang[string]) {
3386        localizedString = primaryLang[string];
3387      }
3388
3389      if (tokens) {
3390        localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
3391          var value = tokens[index - 1];
3392          var ret = value;
3393
3394          if (typeof value === 'undefined') {
3395            ret = match;
3396          }
3397
3398          return ret;
3399        });
3400      }
3401
3402      return localizedString;
3403    }
3404    /**
3405     * Return the `Component`s DOM element. This is where children get inserted.
3406     * This will usually be the the same as the element returned in {@link Component#el}.
vendor: 19,707 bytes, lines 3407-4038
3407     *
3408     * @return {Element}
3409     *         The content element for this `Component`.
3410     */
3411    ;
3412
3413    _proto.contentEl = function contentEl() {
3414      return this.contentEl_ || this.el_;
3415    }
3416    /**
3417     * Get this `Component`s ID
3418     *
3419     * @return {string}
3420     *         The id of this `Component`
3421     */
3422    ;
3423
3424    _proto.id = function id() {
3425      return this.id_;
3426    }
3427    /**
3428     * Get the `Component`s name. The name gets used to reference the `Component`
3429     * and is set during registration.
3430     *
3431     * @return {string}
3432     *         The name of this `Component`.
3433     */
3434    ;
3435
3436    _proto.name = function name() {
3437      return this.name_;
3438    }
3439    /**
3440     * Get an array of all child components
3441     *
3442     * @return {Array}
3443     *         The children
3444     */
3445    ;
3446
3447    _proto.children = function children() {
3448      return this.children_;
3449    }
3450    /**
3451     * Returns the child `Component` with the given `id`.
3452     *
3453     * @param {string} id
3454     *        The id of the child `Component` to get.
3455     *
3456     * @return {Component|undefined}
3457     *         The child `Component` with the given `id` or undefined.
3458     */
3459    ;
3460
3461    _proto.getChildById = function getChildById(id) {
3462      return this.childIndex_[id];
3463    }
3464    /**
3465     * Returns the child `Component` with the given `name`.
3466     *
3467     * @param {string} name
3468     *        The name of the child `Component` to get.
3469     *
3470     * @return {Component|undefined}
3471     *         The child `Component` with the given `name` or undefined.
3472     */
3473    ;
3474
3475    _proto.getChild = function getChild(name) {
3476      if (!name) {
3477        return;
3478      }
3479
3480      name = toTitleCase(name);
3481      return this.childNameIndex_[name];
3482    }
3483    /**
3484     * Add a child `Component` inside the current `Component`.
3485     *
3486     *
3487     * @param {string|Component} child
3488     *        The name or instance of a child to add.
3489     *
3490     * @param {Object} [options={}]
3491     *        The key/value store of options that will get passed to children of
3492     *        the child.
3493     *
3494     * @param {number} [index=this.children_.length]
3495     *        The index to attempt to add a child into.
3496     *
3497     * @return {Component}
3498     *         The `Component` that gets added as a child. When using a string the
3499     *         `Component` will get created by this process.
3500     */
3501    ;
3502
3503    _proto.addChild = function addChild(child, options, index) {
3504      if (options === void 0) {
3505        options = {};
3506      }
3507
3508      if (index === void 0) {
3509        index = this.children_.length;
3510      }
3511
3512      var component;
3513      var componentName; // If child is a string, create component with options
3514
3515      if (typeof child === 'string') {
3516        componentName = toTitleCase(child);
3517        var componentClassName = options.componentClass || componentName; // Set name through options
3518
3519        options.name = componentName; // Create a new object & element for this controls set
3520        // If there's no .player_, this is a player
3521
3522        var ComponentClass = Component.getComponent(componentClassName);
3523
3524        if (!ComponentClass) {
3525          throw new Error("Component " + componentClassName + " does not exist");
3526        } // data stored directly on the videojs object may be
3527        // misidentified as a component to retain
3528        // backwards-compatibility with 4.x. check to make sure the
3529        // component class can be instantiated.
3530
3531
3532        if (typeof ComponentClass !== 'function') {
3533          return null;
3534        }
3535
3536        component = new ComponentClass(this.player_ || this, options); // child is a component instance
3537      } else {
3538        component = child;
3539      }
3540
3541      if (component.parentComponent_) {
3542        component.parentComponent_.removeChild(component);
3543      }
3544
3545      this.children_.splice(index, 0, component);
3546      component.parentComponent_ = this;
3547
3548      if (typeof component.id === 'function') {
3549        this.childIndex_[component.id()] = component;
3550      } // If a name wasn't used to create the component, check if we can use the
3551      // name function of the component
3552
3553
3554      componentName = componentName || component.name && toTitleCase(component.name());
3555
3556      if (componentName) {
3557        this.childNameIndex_[componentName] = component;
3558      } // Add the UI object's element to the container div (box)
3559      // Having an element is not required
3560
3561
3562      if (typeof component.el === 'function' && component.el()) {
3563        var childNodes = this.contentEl().children;
3564        var refNode = childNodes[index] || null;
3565        this.contentEl().insertBefore(component.el(), refNode);
3566      } // Return so it can stored on parent object if desired.
3567
3568
3569      return component;
3570    }
3571    /**
3572     * Remove a child `Component` from this `Component`s list of children. Also removes
3573     * the child `Component`s element from this `Component`s element.
3574     *
3575     * @param {Component} component
3576     *        The child `Component` to remove.
3577     */
3578    ;
3579
3580    _proto.removeChild = function removeChild(component) {
3581      if (typeof component === 'string') {
3582        component = this.getChild(component);
3583      }
3584
3585      if (!component || !this.children_) {
3586        return;
3587      }
3588
3589      var childFound = false;
3590
3591      for (var i = this.children_.length - 1; i >= 0; i--) {
3592        if (this.children_[i] === component) {
3593          childFound = true;
3594          this.children_.splice(i, 1);
3595          break;
3596        }
3597      }
3598
3599      if (!childFound) {
3600        return;
3601      }
3602
3603      component.parentComponent_ = null;
3604      this.childIndex_[component.id()] = null;
3605      this.childNameIndex_[component.name()] = null;
3606      var compEl = component.el();
3607
3608      if (compEl && compEl.parentNode === this.contentEl()) {
3609        this.contentEl().removeChild(component.el());
3610      }
3611    }
3612    /**
3613     * Add and initialize default child `Component`s based upon options.
3614     */
3615    ;
3616
3617    _proto.initChildren = function initChildren() {
3618      var _this = this;
3619
3620      var children = this.options_.children;
3621
3622      if (children) {
3623        // `this` is `parent`
3624        var parentOptions = this.options_;
3625
3626        var handleAdd = function handleAdd(child) {
3627          var name = child.name;
3628          var opts = child.opts; // Allow options for children to be set at the parent options
3629          // e.g. videojs(id, { controlBar: false });
3630          // instead of videojs(id, { children: { controlBar: false });
3631
3632          if (parentOptions[name] !== undefined) {
3633            opts = parentOptions[name];
3634          } // Allow for disabling default components
3635          // e.g. options['children']['posterImage'] = false
3636
3637
3638          if (opts === false) {
3639            return;
3640          } // Allow options to be passed as a simple boolean if no configuration
3641          // is necessary.
3642
3643
3644          if (opts === true) {
3645            opts = {};
3646          } // We also want to pass the original player options
3647          // to each component as well so they don't need to
3648          // reach back into the player for options later.
3649
3650
3651          opts.playerOptions = _this.options_.playerOptions; // Create and add the child component.
3652          // Add a direct reference to the child by name on the parent instance.
3653          // If two of the same component are used, different names should be supplied
3654          // for each
3655
3656          var newChild = _this.addChild(name, opts);
3657
3658          if (newChild) {
3659            _this[name] = newChild;
3660          }
3661        }; // Allow for an array of children details to passed in the options
3662
3663
3664        var workingChildren;
3665        var Tech = Component.getComponent('Tech');
3666
3667        if (Array.isArray(children)) {
3668          workingChildren = children;
3669        } else {
3670          workingChildren = Object.keys(children);
3671        }
3672
3673        workingChildren // children that are in this.options_ but also in workingChildren  would
3674        // give us extra children we do not want. So, we want to filter them out.
3675        .concat(Object.keys(this.options_).filter(function (child) {
3676          return !workingChildren.some(function (wchild) {
3677            if (typeof wchild === 'string') {
3678              return child === wchild;
3679            }
3680
3681            return child === wchild.name;
3682          });
3683        })).map(function (child) {
3684          var name;
3685          var opts;
3686
3687          if (typeof child === 'string') {
3688            name = child;
3689            opts = children[name] || _this.options_[name] || {};
3690          } else {
3691            name = child.name;
3692            opts = child;
3693          }
3694
3695          return {
3696            name: name,
3697            opts: opts
3698          };
3699        }).filter(function (child) {
3700          // we have to make sure that child.name isn't in the techOrder since
3701          // techs are registerd as Components but can't aren't compatible
3702          // See https://github.com/videojs/video.js/issues/2772
3703          var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name));
3704          return c && !Tech.isTech(c);
3705        }).forEach(handleAdd);
3706      }
3707    }
3708    /**
3709     * Builds the default DOM class name. Should be overriden by sub-components.
3710     *
3711     * @return {string}
3712     *         The DOM class name for this object.
3713     *
3714     * @abstract
3715     */
3716    ;
3717
3718    _proto.buildCSSClass = function buildCSSClass() {
3719      // Child classes can include a function that does:
3720      // return 'CLASS NAME' + this._super();
3721      return '';
3722    }
3723    /**
3724     * Bind a listener to the component's ready state.
3725     * Different from event listeners in that if the ready event has already happened
3726     * it will trigger the function immediately.
3727     *
3728     * @return {Component}
3729     *         Returns itself; method can be chained.
3730     */
3731    ;
3732
3733    _proto.ready = function ready(fn, sync) {
3734      if (sync === void 0) {
3735        sync = false;
3736      }
3737
3738      if (!fn) {
3739        return;
3740      }
3741
3742      if (!this.isReady_) {
3743        this.readyQueue_ = this.readyQueue_ || [];
3744        this.readyQueue_.push(fn);
3745        return;
3746      }
3747
3748      if (sync) {
3749        fn.call(this);
3750      } else {
3751        // Call the function asynchronously by default for consistency
3752        this.setTimeout(fn, 1);
3753      }
3754    }
3755    /**
3756     * Trigger all the ready listeners for this `Component`.
3757     *
3758     * @fires Component#ready
3759     */
3760    ;
3761
3762    _proto.triggerReady = function triggerReady() {
3763      this.isReady_ = true; // Ensure ready is triggered asynchronously
3764
3765      this.setTimeout(function () {
3766        var readyQueue = this.readyQueue_; // Reset Ready Queue
3767
3768        this.readyQueue_ = [];
3769
3770        if (readyQueue && readyQueue.length > 0) {
3771          readyQueue.forEach(function (fn) {
3772            fn.call(this);
3773          }, this);
3774        } // Allow for using event listeners also
3775
3776        /**
3777         * Triggered when a `Component` is ready.
3778         *
3779         * @event Component#ready
3780         * @type {EventTarget~Event}
3781         */
3782
3783
3784        this.trigger('ready');
3785      }, 1);
3786    }
3787    /**
3788     * Find a single DOM element matching a `selector`. This can be within the `Component`s
3789     * `contentEl()` or another custom context.
3790     *
3791     * @param {string} selector
3792     *        A valid CSS selector, which will be passed to `querySelector`.
3793     *
3794     * @param {Element|string} [context=this.contentEl()]
3795     *        A DOM element within which to query. Can also be a selector string in
3796     *        which case the first matching element will get used as context. If
3797     *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
3798     *        nothing it falls back to `document`.
3799     *
3800     * @return {Element|null}
3801     *         the dom element that was found, or null
3802     *
3803     * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
3804     */
3805    ;
3806
3807    _proto.$ = function $$$1(selector, context) {
3808      return $(selector, context || this.contentEl());
3809    }
3810    /**
3811     * Finds all DOM element matching a `selector`. This can be within the `Component`s
3812     * `contentEl()` or another custom context.
3813     *
3814     * @param {string} selector
3815     *        A valid CSS selector, which will be passed to `querySelectorAll`.
3816     *
3817     * @param {Element|string} [context=this.contentEl()]
3818     *        A DOM element within which to query. Can also be a selector string in
3819     *        which case the first matching element will get used as context. If
3820     *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
3821     *        nothing it falls back to `document`.
3822     *
3823     * @return {NodeList}
3824     *         a list of dom elements that were found
3825     *
3826     * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
3827     */
3828    ;
3829
3830    _proto.$$ = function $$$$1(selector, context) {
3831      return $$(selector, context || this.contentEl());
3832    }
3833    /**
3834     * Check if a component's element has a CSS class name.
3835     *
3836     * @param {string} classToCheck
3837     *        CSS class name to check.
3838     *
3839     * @return {boolean}
3840     *         - True if the `Component` has the class.
3841     *         - False if the `Component` does not have the class`
3842     */
3843    ;
3844
3845    _proto.hasClass = function hasClass$$1(classToCheck) {
3846      return hasClass(this.el_, classToCheck);
3847    }
3848    /**
3849     * Add a CSS class name to the `Component`s element.
3850     *
3851     * @param {string} classToAdd
3852     *        CSS class name to add
3853     */
3854    ;
3855
3856    _proto.addClass = function addClass$$1(classToAdd) {
3857      addClass(this.el_, classToAdd);
3858    }
3859    /**
3860     * Remove a CSS class name from the `Component`s element.
3861     *
3862     * @param {string} classToRemove
3863     *        CSS class name to remove
3864     */
3865    ;
3866
3867    _proto.removeClass = function removeClass$$1(classToRemove) {
3868      removeClass(this.el_, classToRemove);
3869    }
3870    /**
3871     * Add or remove a CSS class name from the component's element.
3872     * - `classToToggle` gets added when {@link Component#hasClass} would return false.
3873     * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
3874     *
3875     * @param  {string} classToToggle
3876     *         The class to add or remove based on (@link Component#hasClass}
3877     *
3878     * @param  {boolean|Dom~predicate} [predicate]
3879     *         An {@link Dom~predicate} function or a boolean
3880     */
3881    ;
3882
3883    _proto.toggleClass = function toggleClass$$1(classToToggle, predicate) {
3884      toggleClass(this.el_, classToToggle, predicate);
3885    }
3886    /**
3887     * Show the `Component`s element if it is hidden by removing the
3888     * 'vjs-hidden' class name from it.
3889     */
3890    ;
3891
3892    _proto.show = function show() {
3893      this.removeClass('vjs-hidden');
3894    }
3895    /**
3896     * Hide the `Component`s element if it is currently showing by adding the
3897     * 'vjs-hidden` class name to it.
3898     */
3899    ;
3900
3901    _proto.hide = function hide() {
3902      this.addClass('vjs-hidden');
3903    }
3904    /**
3905     * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
3906     * class name to it. Used during fadeIn/fadeOut.
3907     *
3908     * @private
3909     */
3910    ;
3911
3912    _proto.lockShowing = function lockShowing() {
3913      this.addClass('vjs-lock-showing');
3914    }
3915    /**
3916     * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
3917     * class name from it. Used during fadeIn/fadeOut.
3918     *
3919     * @private
3920     */
3921    ;
3922
3923    _proto.unlockShowing = function unlockShowing() {
3924      this.removeClass('vjs-lock-showing');
3925    }
3926    /**
3927     * Get the value of an attribute on the `Component`s element.
3928     *
3929     * @param {string} attribute
3930     *        Name of the attribute to get the value from.
3931     *
3932     * @return {string|null}
3933     *         - The value of the attribute that was asked for.
3934     *         - Can be an empty string on some browsers if the attribute does not exist
3935     *           or has no value
3936     *         - Most browsers will return null if the attibute does not exist or has
3937     *           no value.
3938     *
3939     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
3940     */
3941    ;
3942
3943    _proto.getAttribute = function getAttribute$$1(attribute) {
3944      return getAttribute(this.el_, attribute);
3945    }
3946    /**
3947     * Set the value of an attribute on the `Component`'s element
3948     *
3949     * @param {string} attribute
3950     *        Name of the attribute to set.
3951     *
3952     * @param {string} value
3953     *        Value to set the attribute to.
3954     *
3955     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
3956     */
3957    ;
3958
3959    _proto.setAttribute = function setAttribute$$1(attribute, value) {
3960      setAttribute(this.el_, attribute, value);
3961    }
3962    /**
3963     * Remove an attribute from the `Component`s element.
3964     *
3965     * @param {string} attribute
3966     *        Name of the attribute to remove.
3967     *
3968     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
3969     */
3970    ;
3971
3972    _proto.removeAttribute = function removeAttribute$$1(attribute) {
3973      removeAttribute(this.el_, attribute);
3974    }
3975    /**
3976     * Get or set the width of the component based upon the CSS styles.
3977     * See {@link Component#dimension} for more detailed information.
3978     *
3979     * @param {number|string} [num]
3980     *        The width that you want to set postfixed with '%', 'px' or nothing.
3981     *
3982     * @param {boolean} [skipListeners]
3983     *        Skip the componentresize event trigger
3984     *
3985     * @return {number|string}
3986     *         The width when getting, zero if there is no width. Can be a string
3987     *           postpixed with '%' or 'px'.
3988     */
3989    ;
3990
3991    _proto.width = function width(num, skipListeners) {
3992      return this.dimension('width', num, skipListeners);
3993    }
3994    /**
3995     * Get or set the height of the component based upon the CSS styles.
3996     * See {@link Component#dimension} for more detailed information.
3997     *
3998     * @param {number|string} [num]
3999     *        The height that you want to set postfixed with '%', 'px' or nothing.
4000     *
4001     * @param {boolean} [skipListeners]
4002     *        Skip the componentresize event trigger
4003     *
4004     * @return {number|string}
4005     *         The width when getting, zero if there is no width. Can be a string
4006     *         postpixed with '%' or 'px'.
4007     */
4008    ;
4009
4010    _proto.height = function height(num, skipListeners) {
4011      return this.dimension('height', num, skipListeners);
4012    }
4013    /**
4014     * Set both the width and height of the `Component` element at the same time.
4015     *
4016     * @param  {number|string} width
4017     *         Width to set the `Component`s element to.
4018     *
4019     * @param  {number|string} height
4020     *         Height to set the `Component`s element to.
4021     */
4022    ;
4023
4024    _proto.dimensions = function dimensions(width, height) {
4025      // Skip componentresize listeners on width for optimization
4026      this.width(width, true);
4027      this.height(height);
4028    }
4029    /**
4030     * Get or set width or height of the `Component` element. This is the shared code
4031     * for the {@link Component#width} and {@link Component#height}.
4032     *
4033     * Things to know:
4034     * - If the width or height in an number this will return the number postfixed with 'px'.
4035     * - If the width/height is a percent this will return the percent postfixed with '%'
4036     * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
4037     *   defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
4038     *   See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-u
4038sing-width/}
4039     *   for more information
4040     * - If you want the computed style of the component, use {@link Component#currentWidth}
4041     *   and {@link {Component#currentHeight}
4042     *
4043     * @fires Component#componentresize
4044     *
4045     * @param {string} widthOrHeight
4046     8        'width' or 'height'
4047     *
4048     * @param  {number|string} [num]
4049     8         New dimension
4050     *
4051     * @param  {boolean} [skipListeners]
4052     *         Skip componentresize event trigger
4053     *
4054     * @return {number}
4055     *         The dimension when getting or 0 if unset
4056     */
4057    ;
4058
4059    _proto.dimension = function dimension(widthOrHeight, num, skipListeners) {
4060      if (num !== undefined) {
4061        // Set to zero if null or literally NaN (NaN !== NaN)
4062        if (num === null || num !== num) {
4063          num = 0;
4064        } // Check if using css width/height (% or px) and adjust
4065
4066
4067        if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
4068          this.el_.style[widthOrHeight] = num;
4069        } else if (num === 'auto') {
4070          this.el_.style[widthOrHeight] = '';
4071        } else {
4072          this.el_.style[widthOrHeight] = num + 'px';
4073        } // skipListeners allows us to avoid triggering the resize event when setting both width and height
4074
4075
4076        if (!skipListeners) {
4077          /**
4078           * Triggered when a component is resized.
4079           *
4080           * @event Component#componentresize
4081           * @type {EventTarget~Event}
4082           */
4083          this.trigger('componentresize');
4084        }
4085
4086        return;
4087      } // Not setting a value, so getting it
4088      // Make sure element exists
4089
4090
4091      if (!this.el_) {
4092        return 0;
4093      } // Get dimension value from style
4094
4095
4096      var val = this.el_.style[widthOrHeight];
4097      var pxIndex = val.indexOf('px');
4098
4099      if (pxIndex !== -1) {
4100        // Return the pixel value with no 'px'
4101        return parseInt(val.slice(0, pxIndex), 10);
4102      } // No px so using % or no style was set, so falling back to offsetWidth/height
4103      // If component has display:none, offset will return 0
4104      // TODO: handle display:none and no dimension style using px
4105
4106
4107      return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10);
4108    }
4109    /**
4110     * Get the computed width or the height of the component's element.
4111     *
4112     * Uses `window.getComputedStyle`.
4113     *
4114     * @param {string} widthOrHeight
4115     *        A string containing 'width' or 'height'. Whichever one you want to get.
4116     *
4117     * @return {number}
4118     *         The dimension that gets asked for or 0 if nothing was set
4119     *         for that dimension.
4120     */
4121    ;
4122
4123    _proto.currentDimension = function currentDimension(widthOrHeight) {
4124      var computedWidthOrHeight = 0;
4125
4126      if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
4127        throw new Error('currentDimension only accepts width or height value');
4128      }
4129
4130      if (typeof window$1.getComputedStyle === 'function') {
4131        var computedStyle = window$1.getComputedStyle(this.el_);
4132        computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight];
4133      } // remove 'px' from variable and parse as integer
4134
4135
4136      computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying
4137      // and we want to check the offset values.
4138      // This code also runs wherever getComputedStyle doesn't exist.
4139
4140      if (computedWidthOrHeight === 0) {
4141        var rule = "offset" + toTitleCase(widthOrHeight);
4142        computedWidthOrHeight = this.el_[rule];
4143      }
4144
4145      return computedWidthOrHeight;
4146    }
4147    /**
4148     * An object that contains width and height values of the `Component`s
4149     * computed style. Uses `window.getComputedStyle`.
4150     *
4151     * @typedef {Object} Component~DimensionObject
4152     *
4153     * @property {number} width
4154     *           The width of the `Component`s computed style.
4155     *
4156     * @property {number} height
4157     *           The height of the `Component`s computed style.
4158     */
4159
4160    /**
4161     * Get an object that contains computed width and height values of the
4162     * component's element.
4163     *
4164     * Uses `window.getComputedStyle`.
4165     *
4166     * @return {Component~DimensionObject}
4167     *         The computed dimensions of the component's element.
4168     */
4169    ;
4170
4171    _proto.currentDimensions = function currentDimensions() {
4172      return {
4173        width: this.currentDimension('width'),
4174        height: this.currentDimension('height')
4175      };
4176    }
4177    /**
4178     * Get the computed width of the component's element.
4179     *
4180     * Uses `window.getComputedStyle`.
4181     *
4182     * @return {number}
4183     *         The computed width of the component's element.
4184     */
4185    ;
4186
4187    _proto.currentWidth = function currentWidth() {
4188      return this.currentDimension('width');
4189    }
4190    /**
4191     * Get the computed height of the component's element.
4192     *
4193     * Uses `window.getComputedStyle`.
4194     *
4195     * @return {number}
4196     *         The computed height of the component's element.
4197     */
4198    ;
4199
4200    _proto.currentHeight = function currentHeight() {
4201      return this.currentDimension('height');
4202    }
4203    /**
4204     * Set the focus to this component
4205     */
4206    ;
4207
4208    _proto.focus = function focus() {
4209      this.el_.focus();
4210    }
4211    /**
4212     * Remove the focus from this component
4213     */
4214    ;
4215
4216    _proto.blur = function blur() {
4217      this.el_.blur();
4218    }
4219    /**
4220     * When this Component receives a `keydown` event which it does not process,
4221     *  it passes the event to the Player for handling.
4222     *
4223     * @param {EventTarget~Event} event
vendor: 10,582 bytes, lines 4224-4489
4224     *        The `keydown` event that caused this function to be called.
4225     */
4226    ;
4227
4228    _proto.handleKeyDown = function handleKeyDown(event) {
4229      if (this.player_) {
4230        // We only stop propagation here because we want unhandled events to fall
4231        // back to the browser.
4232        event.stopPropagation();
4233        this.player_.handleKeyDown(event);
4234      }
4235    }
4236    /**
4237     * Many components used to have a `handleKeyPress` method, which was poorly
4238     * named because it listened to a `keydown` event. This method name now
4239     * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress`
4240     * will not see their method calls stop working.
4241     *
4242     * @param {EventTarget~Event} event
4243     *        The event that caused this function to be called.
4244     */
4245    ;
4246
4247    _proto.handleKeyPress = function handleKeyPress(event) {
4248      this.handleKeyDown(event);
4249    }
4250    /**
4251     * Emit a 'tap' events when touch event support gets detected. This gets used to
4252     * support toggling the controls through a tap on the video. They get enabled
4253     * because every sub-component would have extra overhead otherwise.
4254     *
4255     * @private
4256     * @fires Component#tap
4257     * @listens Component#touchstart
4258     * @listens Component#touchmove
4259     * @listens Component#touchleave
4260     * @listens Component#touchcancel
4261     * @listens Component#touchend
4262      */
4263    ;
4264
4265    _proto.emitTapEvents = function emitTapEvents() {
4266      // Track the start time so we can determine how long the touch lasted
4267      var touchStart = 0;
4268      var firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap
4269      // Other popular libs use anywhere from 2 (hammer.js) to 15,
4270      // so 10 seems like a nice, round number.
4271
4272      var tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap
4273
4274      var touchTimeThreshold = 200;
4275      var couldBeTap;
4276      this.on('touchstart', function (event) {
4277        // If more than one finger, don't consider treating this as a click
4278        if (event.touches.length === 1) {
4279          // Copy pageX/pageY from the object
4280          firstTouch = {
4281            pageX: event.touches[0].pageX,
4282            pageY: event.touches[0].pageY
4283          }; // Record start time so we can detect a tap vs. "touch and hold"
4284
4285          touchStart = new Date().getTime(); // Reset couldBeTap tracking
4286
4287          couldBeTap = true;
4288        }
4289      });
4290      this.on('touchmove', function (event) {
4291        // If more than one finger, don't consider treating this as a click
4292        if (event.touches.length > 1) {
4293          couldBeTap = false;
4294        } else if (firstTouch) {
4295          // Some devices will throw touchmoves for all but the slightest of taps.
4296          // So, if we moved only a small distance, this could still be a tap
4297          var xdiff = event.touches[0].pageX - firstTouch.pageX;
4298          var ydiff = event.touches[0].pageY - firstTouch.pageY;
4299          var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
4300
4301          if (touchDistance > tapMovementThreshold) {
4302            couldBeTap = false;
4303          }
4304        }
4305      });
4306
4307      var noTap = function noTap() {
4308        couldBeTap = false;
4309      }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
4310
4311
4312      this.on('touchleave', noTap);
4313      this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate
4314      // event
4315
4316      this.on('touchend', function (event) {
4317        firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen
4318
4319        if (couldBeTap === true) {
4320          // Measure how long the touch lasted
4321          var touchTime = new Date().getTime() - touchStart; // Make sure the touch was less than the threshold to be considered a tap
4322
4323          if (touchTime < touchTimeThreshold) {
4324            // Don't let browser turn this into a click
4325            event.preventDefault();
4326            /**
4327             * Triggered when a `Component` is tapped.
4328             *
4329             * @event Component#tap
4330             * @type {EventTarget~Event}
4331             */
4332
4333            this.trigger('tap'); // It may be good to copy the touchend event object and change the
4334            // type to tap, if the other event properties aren't exact after
4335            // Events.fixEvent runs (e.g. event.target)
4336          }
4337        }
4338      });
4339    }
4340    /**
4341     * This function reports user activity whenever touch events happen. This can get
4342     * turned off by any sub-components that wants touch events to act another way.
4343     *
4344     * Report user touch activity when touch events occur. User activity gets used to
4345     * determine when controls should show/hide. It is simple when it comes to mouse
4346     * events, because any mouse event should show the controls. So we capture mouse
4347     * events that bubble up to the player and report activity when that happens.
4348     * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
4349     * controls. So touch events can't help us at the player level either.
4350     *
4351     * User activity gets checked asynchronously. So what could happen is a tap event
4352     * on the video turns the controls off. Then the `touchend` event bubbles up to
4353     * the player. Which, if it reported user activity, would turn the controls right
4354     * back on. We also don't want to completely block touch events from bubbling up.
4355     * Furthermore a `touchmove` event and anything other than a tap, should not turn
4356     * controls back on.
4357     *
4358     * @listens Component#touchstart
4359     * @listens Component#touchmove
4360     * @listens Component#touchend
4361     * @listens Component#touchcancel
4362     */
4363    ;
4364
4365    _proto.enableTouchActivity = function enableTouchActivity() {
4366      // Don't continue if the root player doesn't support reporting user activity
4367      if (!this.player() || !this.player().reportUserActivity) {
4368        return;
4369      } // listener for reporting that the user is active
4370
4371
4372      var report = bind(this.player(), this.player().reportUserActivity);
4373      var touchHolding;
4374      this.on('touchstart', function () {
4375        report(); // For as long as the they are touching the device or have their mouse down,
4376        // we consider them active even if they're not moving their finger or mouse.
4377        // So we want to continue to update that they are active
4378
4379        this.clearInterval(touchHolding); // report at the same interval as activityCheck
4380
4381        touchHolding = this.setInterval(report, 250);
4382      });
4383
4384      var touchEnd = function touchEnd(event) {
4385        report(); // stop the interval that maintains activity if the touch is holding
4386
4387        this.clearInterval(touchHolding);
4388      };
4389
4390      this.on('touchmove', report);
4391      this.on('touchend', touchEnd);
4392      this.on('touchcancel', touchEnd);
4393    }
4394    /**
4395     * A callback that has no parameters and is bound into `Component`s context.
4396     *
4397     * @callback Component~GenericCallback
4398     * @this Component
4399     */
4400
4401    /**
4402     * Creates a function that runs after an `x` millisecond timeout. This function is a
4403     * wrapper around `window.setTimeout`. There are a few reasons to use this one
4404     * instead though:
4405     * 1. It gets cleared via  {@link Component#clearTimeout} when
4406     *    {@link Component#dispose} gets called.
4407     * 2. The function callback will gets turned into a {@link Component~GenericCallback}
4408     *
4409     * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
4410     *         will cause its dispose listener not to get cleaned up! Please use
4411     *         {@link Component#clearTimeout} or {@link Component#dispose} instead.
4412     *
4413     * @param {Component~GenericCallback} fn
4414     *        The function that will be run after `timeout`.
4415     *
4416     * @param {number} timeout
4417     *        Timeout in milliseconds to delay before executing the specified function.
4418     *
4419     * @return {number}
4420     *         Returns a timeout ID that gets used to identify the timeout. It can also
4421     *         get used in {@link Component#clearTimeout} to clear the timeout that
4422     *         was set.
4423     *
4424     * @listens Component#dispose
4425     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
4426     */
4427    ;
4428
4429    _proto.setTimeout = function setTimeout(fn, timeout) {
4430      var _this2 = this;
4431
4432      // declare as variables so they are properly available in timeout function
4433      // eslint-disable-next-line
4434      var timeoutId, disposeFn;
4435      fn = bind(this, fn);
4436      timeoutId = window$1.setTimeout(function () {
4437        _this2.off('dispose', disposeFn);
4438
4439        fn();
4440      }, timeout);
4441
4442      disposeFn = function disposeFn() {
4443        return _this2.clearTimeout(timeoutId);
4444      };
4445
4446      disposeFn.guid = "vjs-timeout-" + timeoutId;
4447      this.on('dispose', disposeFn);
4448      return timeoutId;
4449    }
4450    /**
4451     * Clears a timeout that gets created via `window.setTimeout` or
4452     * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
4453     * use this function instead of `window.clearTimout`. If you don't your dispose
4454     * listener will not get cleaned up until {@link Component#dispose}!
4455     *
4456     * @param {number} timeoutId
4457     *        The id of the timeout to clear. The return value of
4458     *        {@link Component#setTimeout} or `window.setTimeout`.
4459     *
4460     * @return {number}
4461     *         Returns the timeout id that was cleared.
4462     *
4463     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
4464     */
4465    ;
4466
4467    _proto.clearTimeout = function clearTimeout(timeoutId) {
4468      window$1.clearTimeout(timeoutId);
4469
4470      var disposeFn = function disposeFn() {};
4471
4472      disposeFn.guid = "vjs-timeout-" + timeoutId;
4473      this.off('dispose', disposeFn);
4474      return timeoutId;
4475    }
4476    /**
4477     * Creates a function that gets run every `x` milliseconds. This function is a wrapper
4478     * around `window.setInterval`. There are a few reasons to use this one instead though.
4479     * 1. It gets cleared via  {@link Component#clearInterval} when
4480     *    {@link Component#dispose} gets called.
4481     * 2. The function callback will be a {@link Component~GenericCallback}
4482     *
4483     * @param {Component~GenericCallback} fn
4484     *        The function to run every `x` seconds.
4485     *
4486     * @param {number} interval
4487     *        Execute the specified function every `x` milliseconds.
4488     *
4489     * @return {number}
4490     *         Returns an id that can be used to identify the interval. It can also be be used in
4491     *         {@link Component#clearInterval} to clear the interval.
4492     *
4493     * @listens Component#dispose
4494     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
4495     */
4496    ;
4497
4498    _proto.setInterval = function setInterval(fn, interval) {
4499      var _this3 = this;
4500
4501      fn = bind(this, fn);
4502      var intervalId = window$1.setInterval(fn, interval);
4503
4504      var disposeFn = function disposeFn() {
4505        return _this3.clearInterval(intervalId);
4506      };
4507
4508      disposeFn.guid = "vjs-interval-" + intervalId;
4509      this.on('dispose', disposeFn);
4510      return intervalId;
4511    }
4512    /**
4513     * Clears an interval that gets created via `window.setInterval` or
4514     * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval}
4515     * use this function instead of `window.clearInterval`. If you don't your dispose
4516     * listener will not get cleaned up until {@link Component#dispose}!
4517     *
4518     * @param {number} intervalId
4519     *        The id of the interval to clear. The return value of
4520     *        {@link Component#setInterval} or `window.setInterval`.
4521     *
4522     * @return {number}
4523     *         Returns the interval id that was cleared.
4524     *
4525     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
4526     */
4527    ;
4528
4529    _proto.clearInterval = function clearInterval(intervalId) {
4530      window$1.clearInterval(intervalId);
4531
4532      var disposeFn = function disposeFn() {};
4533
4534      disposeFn.guid = "vjs-interval-" + intervalId;
4535      this.off('dispose', disposeFn);
4536      return intervalId;
4537    }
4538    /**
4539     * Queues up a callback to be passed to requestAnimationFrame (rAF), but
4540     * with a few extra bonuses:
4541     *
4542     * - Supports browsers that do not support rAF by falling back to
4543     *   {@link Component#setTimeout}.
4544     *
4545     * - The callback is turned into a {@link Component~GenericCallback} (i.e.
4546     *   bound to the component).
4547     *
4548     * - Automatic cancellation of the rAF callback is handled if the component
4549     *   is disposed before it is called.
4550     *
4551     * @param  {Component~GenericCallback} fn
4552     *         A function that will be bound to this component and executed just
4553     *         before the browser's next repaint.
4554     *
4555     * @return {number}
4556     *         Returns an rAF ID that gets used to identify the timeout. It can
4557     *         also be used in {@link Component#cancelAnimationFrame} to cancel
4558     *         the animation frame callback.
4559     *
4560     * @listens Component#dispose
4561     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
4562     */
4563    ;
4564
4565    _proto.requestAnimationFrame = function requestAnimationFrame(fn) {
4566      var _this4 = this;
4567
4568      // declare as variables so they are properly available in rAF function
4569      // eslint-disable-next-line
4570      var id, disposeFn;
4571
4572      if (this.supportsRaf_) {
4573        fn = bind(this, fn);
4574        id = window$1.requestAnimationFrame(function () {
4575          _this4.off('dispose', disposeFn);
4576
4577          fn();
4578        });
4579
4580        disposeFn = function disposeFn() {
4581          return _this4.cancelAnimationFrame(id);
4582        };
4583
4584        disposeFn.guid = "vjs-raf-" + id;
4585        this.on('dispose', disposeFn);
4586        return id;
4587      } // Fall back to using a timer.
4588
4589
4590      return this.setTimeout(fn, 1000 / 60);
4591    }
4592    /**
4593     * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
4594     * (rAF).
4595     *
4596     * If you queue an rAF callback via {@link Component#requestAnimationFrame},
4597     * use this function instead of `window.cancelAnimationFrame`. If you don't,
4598     * your dispose listener will not get cleaned up until {@link Component#dispose}!
4599     *
4600     * @param {number} id
4601     *        The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
4602     *
4603     * @return {number}
4604     *         Returns the rAF ID that was cleared.
4605     *
4606     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
4607     */
4608    ;
4609
4610    _proto.cancelAnimationFrame = function cancelAnimationFrame(id) {
4611      if (this.supportsRaf_) {
4612        window$1.cancelAnimationFrame(id);
4613
4614        var disposeFn = function disposeFn() {};
4615
4616        disposeFn.guid = "vjs-raf-" + id;
4617        this.off('dispose', disposeFn);
4618        return id;
4619      } // Fall back to using a timer.
4620
4621
4622      return this.clearTimeout(id);
4623    }
4624    /**
4625     * Register a `Component` with `videojs` given the name and the component.
4626     *
4627     * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
4628     *         should be registered using {@link Tech.registerTech} or
4629     *         {@link videojs:videojs.registerTech}.
4630     *
4631     * > NOTE: This function can also be seen on videojs as
4632     *         {@link videojs:videojs.registerComponent}.
4633     *
4634     * @param {string} name
4635     *        The name of the `Component` to register.
4636     *
4637     * @param {Component} ComponentToRegister
4638     *        The `Component` class to register.
4639     *
4640     * @return {Component}
4641     *         The `Component` that was registered.
4642     */
4643    ;
4644
4645    Component.registerComponent = function registerComponent(name, ComponentToRegister) {
4646      if (typeof name !== 'string' || !name) {
4647        throw new Error("Illegal component name, \"" + name + "\"; must be a non-empty string.");
4648      }
4649
4650      var Tech = Component.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered.
4651
4652      var isTech = Tech && Tech.isTech(ComponentToRegister);
4653      var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype);
4654
4655      if (isTech || !isComp) {
4656        var reason;
4657
4658        if (isTech) {
4659          reason = 'techs must be registered using Tech.registerTech()';
4660        } else {
4661          reason = 'must be a Component subclass';
4662        }
4663
4664        throw new Error("Illegal component, \"" + name + "\"; " + reason + ".");
4665      }
4666
4667      name = toTitleCase(name);
4668
4669      if (!Component.components_) {
4670        Component.components_ = {};
4671      }
4672
4673      var Player = Component.getComponent('Player');
4674
4675      if (name === 'Player' && Player && Player.players) {
4676        var players = Player.players;
4677        var playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be
4678        // in Players.players. So, we must loop through and verify that the value
4679        // for each item is not null. This allows registration of the Player component
4680        // after all players have been disposed or before any were created.
4681
4682        if (players && playerNames.length > 0 && playerNames.map(function (pname) {
4683          return players[pname];
4684        }).every(Boolean)) {
4685          throw new Error('Can not register Player component after player has been created.');
4686        }
4687      }
4688
4689      Component.components_[name] = ComponentToRegister;
4690      return ComponentToRegister;
4691    }
4692    /**
4693     * Get a `Component` based on the name it was registered with.
4694     *
4695     * @param {string} name
4696     *        The Name of the component to get.
4697     *
4698     * @return {Component}
4699     *         The `Component` that got registered under the given name.
4700     *
4701     * @deprecated In `videojs` 6 this will not return `Component`s that were not
4702     *             registered using {@link Component.registerComponent}. Currently we
4703     *             check the global `videojs` object for a `Component` name and
4704     *             return that if it exists.
4705     */
4706    ;
4707
4708    Component.getComponent = function getComponent(name) {
4709      if (!name) {
4710        return;
4711      }
4712
4713      name = toTitleCase(name);
4714
4715      if (Component.components_ && Component.components_[name]) {
4716        return Component.components_[name];
4717      }
4718    };
4719
4720    return Component;
4721  }();
4722  /**
4723   * Whether or not this component supports `requestAnimationFrame`.
4724   *
4725   * This is exposed primarily for testing purposes.
4726   *
4727   * @private
4728   * @type {Boolean}
4729   */
4730
4731
4732  Component.prototype.supportsRaf_ = typeof window$1.requestAnimationFrame === 'function' && typeof window$1.cancelAnimationFrame === 'function';
4733  Component.registerComponent('Component', Component);
4734
4735  /**
4736   * @file browser.js
4737   * @module browser
4738   */
4739  var USER_AGENT = window$1.navigator && window$1.navigator.userAgent || '';
4740  var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT);
4741  var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;
4742  /**
4743   * Whether or not this device is an iPad.
4744   *
4745   * @static
4746   * @const
4747   * @type {Boolean}
4748   */
4749
4750  var IS_IPAD = /iPad/i.test(USER_AGENT);
4751  /**
4752   * Whether or not this device is an iPhone.
4753   *
4754   * @static
4755   * @const
4756   * @type {Boolean}
4757   */
4758  // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
4759  // to identify iPhones, we need to exclude iPads.
4760  // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
4761
4762  var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
4763  /**
4764   * Whether or not this device is an iPod.
4765   *
4766   * @static
4767   * @const
4768   * @type {Boolean}
4769   */
4770
4771  var IS_IPOD = /iPod/i.test(USER_AGENT);
4772  /**
4773   * Whether or not this is an iOS device.
4774   *
4775   * @static
4776   * @const
4777   * @type {Boolean}
4778   */
4779
4780  var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
4781  /**
4782   * The detected iOS version - or `null`.
4783   *
4784   * @static
4785   * @const
4786   * @type {string|null}
4787   */
4788
4789  var IOS_VERSION = function () {
4790    var match = USER_AGENT.match(/OS (\d+)_/i);
4791
4792    if (match && match[1]) {
4793      return match[1];
4794    }
4795
4796    return null;
4797  }();
4798  /**
4799   * Whether or not this is an Android device.
4800   *
4801   * @static
4802   * @const
4803   * @type {Boolean}
4804   */
4805
4806  var IS_ANDROID = /Android/i.test(USER_AGENT);
4807  /**
4808   * The detected Android version - or `null`.
4809   *
4810   * @static
4811   * @const
4812   * @type {number|string|null}
4813   */
4814
4815  var ANDROID_VERSION = function () {
4816    // This matches Android Major.Minor.Patch versions
vendor: 10,112 bytes, lines 4817-5184
4817    // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
4818    var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
4819
4820    if (!match) {
4821      return null;
4822    }
4823
4824    var major = match[1] && parseFloat(match[1]);
4825    var minor = match[2] && parseFloat(match[2]);
4826
4827    if (major && minor) {
4828      return parseFloat(match[1] + '.' + match[2]);
4829    } else if (major) {
4830      return major;
4831    }
4832
4833    return null;
4834  }();
4835  /**
4836   * Whether or not this is a native Android browser.
4837   *
4838   * @static
4839   * @const
4840   * @type {Boolean}
4841   */
4842
4843  var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;
4844  /**
4845   * Whether or not this is Mozilla Firefox.
4846   *
4847   * @static
4848   * @const
4849   * @type {Boolean}
4850   */
4851
4852  var IS_FIREFOX = /Firefox/i.test(USER_AGENT);
4853  /**
4854   * Whether or not this is Microsoft Edge.
4855   *
4856   * @static
4857   * @const
4858   * @type {Boolean}
4859   */
4860
4861  var IS_EDGE = /Edge/i.test(USER_AGENT);
4862  /**
4863   * Whether or not this is Google Chrome.
4864   *
4865   * This will also be `true` for Chrome on iOS, which will have different support
4866   * as it is actually Safari under the hood.
4867   *
4868   * @static
4869   * @const
4870   * @type {Boolean}
4871   */
4872
4873  var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT));
4874  /**
4875   * The detected Google Chrome version - or `null`.
4876   *
4877   * @static
4878   * @const
4879   * @type {number|null}
4880   */
4881
4882  var CHROME_VERSION = function () {
4883    var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
4884
4885    if (match && match[2]) {
4886      return parseFloat(match[2]);
4887    }
4888
4889    return null;
4890  }();
4891  /**
4892   * The detected Internet Explorer version - or `null`.
4893   *
4894   * @static
4895   * @const
4896   * @type {number|null}
4897   */
4898
4899  var IE_VERSION = function () {
4900    var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
4901    var version = result && parseFloat(result[1]);
4902
4903    if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
4904      // IE 11 has a different user agent string than other IE versions
4905      version = 11.0;
4906    }
4907
4908    return version;
4909  }();
4910  /**
4911   * Whether or not this is desktop Safari.
4912   *
4913   * @static
4914   * @const
4915   * @type {Boolean}
4916   */
4917
4918  var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
4919  /**
4920   * Whether or not this is any flavor of Safari - including iOS.
4921   *
4922   * @static
4923   * @const
4924   * @type {Boolean}
4925   */
4926
4927  var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
4928  /**
4929   * Whether or not this device is touch-enabled.
4930   *
4931   * @static
4932   * @const
4933   * @type {Boolean}
4934   */
4935
4936  var TOUCH_ENABLED = isReal() && ('ontouchstart' in window$1 || window$1.navigator.maxTouchPoints || window$1.DocumentTouch && window$1.document instanceof window$1.DocumentTouch);
4937
4938  var browser = /*#__PURE__*/Object.freeze({
4939    IS_IPAD: IS_IPAD,
4940    IS_IPHONE: IS_IPHONE,
4941    IS_IPOD: IS_IPOD,
4942    IS_IOS: IS_IOS,
4943    IOS_VERSION: IOS_VERSION,
4944    IS_ANDROID: IS_ANDROID,
4945    ANDROID_VERSION: ANDROID_VERSION,
4946    IS_NATIVE_ANDROID: IS_NATIVE_ANDROID,
4947    IS_FIREFOX: IS_FIREFOX,
4948    IS_EDGE: IS_EDGE,
4949    IS_CHROME: IS_CHROME,
4950    CHROME_VERSION: CHROME_VERSION,
4951    IE_VERSION: IE_VERSION,
4952    IS_SAFARI: IS_SAFARI,
4953    IS_ANY_SAFARI: IS_ANY_SAFARI,
4954    TOUCH_ENABLED: TOUCH_ENABLED
4955  });
4956
4957  /**
4958   * @file time-ranges.js
4959   * @module time-ranges
4960   */
4961
4962  /**
4963   * Returns the time for the specified index at the start or end
4964   * of a TimeRange object.
4965   *
4966   * @typedef    {Function} TimeRangeIndex
4967   *
4968   * @param      {number} [index=0]
4969   *             The range number to return the time for.
4970   *
4971   * @return     {number}
4972   *             The time offset at the specified index.
4973   *
4974   * @deprecated The index argument must be provided.
4975   *             In the future, leaving it out will throw an error.
4976   */
4977
4978  /**
4979   * An object that contains ranges of time.
4980   *
4981   * @typedef  {Object} TimeRange
4982   *
4983   * @property {number} length
4984   *           The number of time ranges represented by this object.
4985   *
4986   * @property {module:time-ranges~TimeRangeIndex} start
4987   *           Returns the time offset at which a specified time range begins.
4988   *
4989   * @property {module:time-ranges~TimeRangeIndex} end
4990   *           Returns the time offset at which a specified time range ends.
4991   *
4992   * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
4993   */
4994
4995  /**
4996   * Check if any of the time ranges are over the maximum index.
4997   *
4998   * @private
4999   * @param   {string} fnName
5000   *          The function name to use for logging
5001   *
5002   * @param   {number} index
5003   *          The index to check
5004   *
5005   * @param   {number} maxIndex
5006   *          The maximum possible index
5007   *
5008   * @throws  {Error} if the timeRanges provided are over the maxIndex
5009   */
5010  function rangeCheck(fnName, index, maxIndex) {
5011    if (typeof index !== 'number' || index < 0 || index > maxIndex) {
5012      throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ").");
5013    }
5014  }
5015  /**
5016   * Get the time for the specified index at the start or end
5017   * of a TimeRange object.
5018   *
5019   * @private
5020   * @param      {string} fnName
5021   *             The function name to use for logging
5022   *
5023   * @param      {string} valueIndex
5024   *             The property that should be used to get the time. should be
5025   *             'start' or 'end'
5026   *
5027   * @param      {Array} ranges
5028   *             An array of time ranges
5029   *
5030   * @param      {Array} [rangeIndex=0]
5031   *             The index to start the search at
5032   *
5033   * @return     {number}
5034   *             The time that offset at the specified index.
5035   *
5036   * @deprecated rangeIndex must be set to a value, in the future this will throw an error.
5037   * @throws     {Error} if rangeIndex is more than the length of ranges
5038   */
5039
5040
5041  function getRange(fnName, valueIndex, ranges, rangeIndex) {
5042    rangeCheck(fnName, rangeIndex, ranges.length - 1);
5043    return ranges[rangeIndex][valueIndex];
5044  }
5045  /**
5046   * Create a time range object given ranges of time.
5047   *
5048   * @private
5049   * @param   {Array} [ranges]
5050   *          An array of time ranges.
5051   */
5052
5053
5054  function createTimeRangesObj(ranges) {
5055    if (ranges === undefined || ranges.length === 0) {
5056      return {
5057        length: 0,
5058        start: function start() {
5059          throw new Error('This TimeRanges object is empty');
5060        },
5061        end: function end() {
5062          throw new Error('This TimeRanges object is empty');
5063        }
5064      };
5065    }
5066
5067    return {
5068      length: ranges.length,
5069      start: getRange.bind(null, 'start', 0, ranges),
5070      end: getRange.bind(null, 'end', 1, ranges)
5071    };
5072  }
5073  /**
5074   * Create a `TimeRange` object which mimics an
5075   * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}.
5076   *
5077   * @param {number|Array[]} start
5078   *        The start of a single range (a number) or an array of ranges (an
5079   *        array of arrays of two numbers each).
5080   *
5081   * @param {number} end
5082   *        The end of a single range. Cannot be used with the array form of
5083   *        the `start` argument.
5084   */
5085
5086
5087  function createTimeRanges(start, end) {
5088    if (Array.isArray(start)) {
5089      return createTimeRangesObj(start);
5090    } else if (start === undefined || end === undefined) {
5091      return createTimeRangesObj();
5092    }
5093
5094    return createTimeRangesObj([[start, end]]);
5095  }
5096
5097  /**
5098   * @file buffer.js
5099   * @module buffer
5100   */
5101  /**
5102   * Compute the percentage of the media that has been buffered.
5103   *
5104   * @param {TimeRange} buffered
5105   *        The current `TimeRange` object representing buffered time ranges
5106   *
5107   * @param {number} duration
5108   *        Total duration of the media
5109   *
5110   * @return {number}
5111   *         Percent buffered of the total duration in decimal form.
5112   */
5113
5114  function bufferedPercent(buffered, duration) {
5115    var bufferedDuration = 0;
5116    var start;
5117    var end;
5118
5119    if (!duration) {
5120      return 0;
5121    }
5122
5123    if (!buffered || !buffered.length) {
5124      buffered = createTimeRanges(0, 0);
5125    }
5126
5127    for (var i = 0; i < buffered.length; i++) {
5128      start = buffered.start(i);
5129      end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction
5130
5131      if (end > duration) {
5132        end = duration;
5133      }
5134
5135      bufferedDuration += end - start;
5136    }
5137
5138    return bufferedDuration / duration;
5139  }
5140
5141  /**
5142   * @file fullscreen-api.js
5143   * @module fullscreen-api
5144   * @private
5145   */
5146  /**
5147   * Store the browser-specific methods for the fullscreen API.
5148   *
5149   * @type {Object}
5150   * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
5151   * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
5152   */
5153
5154  var FullscreenApi = {}; // browser API methods
5155
5156  var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit
5157  ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla
5158  ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft
5159  ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']];
5160  var specApi = apiMap[0];
5161  var browserApi;
5162  var prefixedAPI = false; // determine the supported set of functions
5163
5164  for (var i = 0; i < apiMap.length; i++) {
5165    // check for exitFullscreen function
5166    if (apiMap[i][1] in document) {
5167      browserApi = apiMap[i];
5168      break;
5169    }
5170  } // map the browser API names to the spec API names
5171
5172
5173  if (browserApi) {
5174    for (var _i = 0; _i < browserApi.length; _i++) {
5175      FullscreenApi[specApi[_i]] = browserApi[_i];
5176    }
5177
5178    prefixedAPI = browserApi[0] === specApi[0];
5179  }
5180
5181  /**
5182   * @file media-error.js
5183   */
5184  /**
5185   * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
5186   *
5187   * @param {number|string|Object|MediaError} value
5188   *        This can be of multiple types:
5189   *        - number: should be a standard error code
5190   *        - string: an error message (the code will be 0)
5191   *        - Object: arbitrary properties
5192   *        - `MediaError` (native): used to populate a video.js `MediaError` object
5193   *        - `MediaError` (video.js): will return itself if it's already a
5194   *          video.js `MediaError` object.
5195   *
5196   * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
5197   * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
5198   *
5199   * @class MediaError
5200   */
5201
5202  function MediaError(value) {
5203    // Allow redundant calls to this constructor to avoid having `instanceof`
5204    // checks peppered around the code.
5205    if (value instanceof MediaError) {
5206      return value;
5207    }
5208
5209    if (typeof value === 'number') {
5210      this.code = value;
5211    } else if (typeof value === 'string') {
vendor: 4,827 bytes, lines 5212-5360
5212      // default code is zero, so this is a custom error
5213      this.message = value;
5214    } else if (isObject(value)) {
5215      // We assign the `code` property manually because native `MediaError` objects
5216      // do not expose it as an own/enumerable property of the object.
5217      if (typeof value.code === 'number') {
5218        this.code = value.code;
5219      }
5220
5221      assign(this, value);
5222    }
5223
5224    if (!this.message) {
5225      this.message = MediaError.defaultMessages[this.code] || '';
5226    }
5227  }
5228  /**
5229   * The error code that refers two one of the defined `MediaError` types
5230   *
5231   * @type {Number}
5232   */
5233
5234
5235  MediaError.prototype.code = 0;
5236  /**
5237   * An optional message that to show with the error. Message is not part of the HTML5
5238   * video spec but allows for more informative custom errors.
5239   *
5240   * @type {String}
5241   */
5242
5243  MediaError.prototype.message = '';
5244  /**
5245   * An optional status code that can be set by plugins to allow even more detail about
5246   * the error. For example a plugin might provide a specific HTTP status code and an
5247   * error message for that code. Then when the plugin gets that error this class will
5248   * know how to display an error message for it. This allows a custom message to show
5249   * up on the `Player` error overlay.
5250   *
5251   * @type {Array}
5252   */
5253
5254  MediaError.prototype.status = null;
5255  /**
5256   * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
5257   * specification listed under {@link MediaError} for more information.
5258   *
5259   * @enum {array}
5260   * @readonly
5261   * @property {string} 0 - MEDIA_ERR_CUSTOM
5262   * @property {string} 1 - MEDIA_ERR_ABORTED
5263   * @property {string} 2 - MEDIA_ERR_NETWORK
5264   * @property {string} 3 - MEDIA_ERR_DECODE
5265   * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
5266   * @property {string} 5 - MEDIA_ERR_ENCRYPTED
5267   */
5268
5269  MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
5270  /**
5271   * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
5272   *
5273   * @type {Array}
5274   * @constant
5275   */
5276
5277  MediaError.defaultMessages = {
5278    1: 'You aborted the media playback',
5279    2: 'A network error caused the media download to fail part-way.',
5280    3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
5281    4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
5282    5: 'The media is encrypted and we do not have the keys to decrypt it.'
5283  }; // Add types as properties on MediaError
5284  // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
5285
5286  for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
5287    MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance
5288
5289    MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
5290  } // jsdocs for instance/static members added above
5291
5292  var tuple = SafeParseTuple;
5293
5294  function SafeParseTuple(obj, reviver) {
5295    var json;
5296    var error = null;
5297
5298    try {
5299      json = JSON.parse(obj, reviver);
5300    } catch (err) {
5301      error = err;
5302    }
5303
5304    return [error, json];
5305  }
5306
5307  /**
5308   * Returns whether an object is `Promise`-like (i.e. has a `then` method).
5309   *
5310   * @param  {Object}  value
5311   *         An object that may or may not be `Promise`-like.
5312   *
5313   * @return {boolean}
5314   *         Whether or not the object is `Promise`-like.
5315   */
5316  function isPromise(value) {
5317    return value !== undefined && value !== null && typeof value.then === 'function';
5318  }
5319  /**
5320   * Silence a Promise-like object.
5321   *
5322   * This is useful for avoiding non-harmful, but potentially confusing "uncaught
5323   * play promise" rejection error messages.
5324   *
5325   * @param  {Object} value
5326   *         An object that may or may not be `Promise`-like.
5327   */
5328
5329  function silencePromise(value) {
5330    if (isPromise(value)) {
5331      value.then(null, function (e) {});
5332    }
5333  }
5334
5335  /**
5336   * @file text-track-list-converter.js Utilities for capturing text track state and
5337   * re-creating tracks based on a capture.
5338   *
5339   * @module text-track-list-converter
5340   */
5341
5342  /**
5343   * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
5344   * represents the {@link TextTrack}'s state.
5345   *
5346   * @param {TextTrack} track
5347   *        The text track to query.
5348   *
5349   * @return {Object}
5350   *         A serializable javascript representation of the TextTrack.
5351   * @private
5352   */
5353  var trackToJson_ = function trackToJson_(track) {
5354    var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) {
5355      if (track[prop]) {
5356        acc[prop] = track[prop];
5357      }
5358
5359      return acc;
5360    }, {
5361      cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
5362        return {
5363          startTime: cue.startTime,
5364          endTime: cue.endTime,
5365          text: cue.text,
5366          id: cue.id
5367        };
5368      })
5369    });
5370    return ret;
5371  };
5372  /**
5373   * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
5374   * state of all {@link TextTrack}s currently configured. The return array is compatible with
5375   * {@link text-track-list-converter:jsonToTextTracks}.
5376   *
5377   * @param {Tech} tech
5378   *        The tech object to query
5379   *
5380   * @return {Array}
5381   *         A serializable javascript representation of the {@link Tech}s
5382   *         {@link TextTrackList}.
5383   */
5384
5385
5386  var textTracksToJson = function textTracksToJson(tech) {
5387    var trackEls = tech.$$('track');
5388    var trackObjs = Array.prototype.map.call(trackEls, function (t) {
5389      return t.track;
5390    });
5391    var tracks = Array.prototype.map.call(trackEls, function (trackEl) {
5392      var json = trackToJson_(trackEl.track);
5393
5394      if (trackEl.src) {
5395        json.src = trackEl.src;
5396      }
5397
5398      return json;
5399    });
5400    return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
5401      return trackObjs.indexOf(track) === -1;
5402    }).map(trackToJson_));
5403  };
5404  /**
5405   * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
5406   * object {@link TextTrack} representations.
5407   *
5408   * @param {Array} json
5409   *        An array of `TextTrack` representation objects, like those that would be
5410   *        produced by `textTracksToJson`.
5411   *
5412   * @param {Tech} tech
5413   *        The `Tech` to create the `TextTrack`s on.
5414   */
5415
5416
5417  var jsonToTextTracks = function jsonToTextTracks(json, tech) {
5418    json.forEach(function (track) {
5419      var addedTrack = tech.addRemoteTextTrack(track).track;
5420
5421      if (!track.src && track.cues) {
5422        track.cues.forEach(function (cue) {
5423          return addedTrack.addCue(cue);
5424        });
5425      }
5426    });
5427    return tech.textTracks();
5428  };
5429
5430  var textTrackConverter = {
5431    textTracksToJson: textTracksToJson,
5432    jsonToTextTracks: jsonToTextTracks,
5433    trackToJson_: trackToJson_
5434  };
5435
5436  var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
5437
5438  function createCommonjsModule(fn, module) {
5439  	return module = { exports: {} }, fn(module, module.exports), module.exports;
5440  }
5441
5442  var keycode = createCommonjsModule(function (module, exports) {
5443    // Source: http://jsfiddle.net/vWx8V/
5444    // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes
5445
5446    /**
5447     * Conenience method returns corresponding value for given keyName or keyCode.
5448     *
5449     * @param {Mixed} keyCode {Number} or keyName {String}
5450     * @return {Mixed}
5451     * @api public
5452     */
5453    function keyCode(searchInput) {
5454      // Keyboard Events
5455      if (searchInput && 'object' === typeof searchInput) {
5456        var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode;
5457        if (hasKeyCode) searchInput = hasKeyCode;
5458      } // Numbers
5459
5460
5461      if ('number' === typeof searchInput) return names[searchInput]; // Everything else (cast to string)
5462
5463      var search = String(searchInput); // check codes
5464
5465      var foundNamedKey = codes[search.toLowerCase()];
5466      if (foundNamedKey) return foundNamedKey; // check aliases
5467
5468      var foundNamedKey = aliases[search.toLowerCase()];
5469      if (foundNamedKey) return foundNamedKey; // weird character?
5470
5471      if (search.length === 1) return search.charCodeAt(0);
5472      return undefined;
5473    }
5474    /**
5475     * Compares a keyboard event with a given keyCode or keyName.
5476     *
5477     * @param {Event} event Keyboard event that should be tested
5478     * @param {Mixed} keyCode {Number} or keyName {String}
5479     * @return {Boolean}
5480     * @api public
5481     */
5482
5483
5484    keyCode.isEventKey = function isEventKey(event, nameOrCode) {
5485      if (event && 'object' === typeof event) {
5486        var keyCode = event.which || event.keyCode || event.charCode;
5487
5488        if (keyCode === null || keyCode === undefined) {
5489          return false;
5490        }
5491
5492        if (typeof nameOrCode === 'string') {
5493          // check codes
5494          var foundNamedKey = codes[nameOrCode.toLowerCase()];
5495
5496          if (foundNamedKey) {
5497            return foundNamedKey === keyCode;
5498          } // check aliases
5499
5500
5501          var foundNamedKey = aliases[nameOrCode.toLowerCase()];
5502
5503          if (foundNamedKey) {
5504            return foundNamedKey === keyCode;
5505          }
5506        } else if (typeof nameOrCode === 'number') {
5507          return nameOrCode === keyCode;
5508        }
5509
5510        return false;
5511      }
5512    };
5513
5514    exports = module.exports = keyCode;
5515    /**
5516     * Get by name
5517     *
5518     *   exports.code['enter'] // => 13
5519     */
5520
5521    var codes = exports.code = exports.codes = {
5522      'backspace': 8,
5523      'tab': 9,
5524      'enter': 13,
5525      'shift': 16,
5526      'ctrl': 17,
5527      'alt': 18,
5528      'pause/break': 19,
5529      'caps lock': 20,
5530      'esc': 27,
5531      'space': 32,
5532      'page up': 33,
5533      'page down': 34,
5534      'end': 35,
5535      'home': 36,
5536      'left': 37,
5537      'up': 38,
5538      'right': 39,
5539      'down': 40,
5540      'insert': 45,
5541      'delete': 46,
5542      'command': 91,
5543      'left command': 91,
5544      'right command': 93,
5545      'numpad *': 106,
5546      'numpad +': 107,
5547      'numpad -': 109,
5548      'numpad .': 110,
5549      'numpad /': 111,
5550      'num lock': 144,
5551      'scroll lock': 145,
5552      'my computer': 182,
5553      'my calculator': 183,
5554      ';': 186,
5555      '=': 187,
5556      ',': 188,
5557      '-': 189,
5558      '.': 190,
5559      '/': 191,
5560      '`': 192,
5561      '[': 219,
5562      '\\': 220,
5563      ']': 221,
5564      "'": 222 // Helper aliases
5565
5566    };
5567    var aliases = exports.aliases = {
5568      'windows': 91,
5569      '⇧': 16,
5570      '⌥': 18,
5571      '⌃': 17,
5572      '⌘': 91,
5573      'ctl': 17,
5574      'control': 17,
5575      'option': 18,
5576      'pause': 19,
5577      'break': 19,
5578      'caps': 20,
5579      'return': 13,
5580      'escape': 27,
5581      'spc': 32,
5582      'spacebar': 32,
5583      'pgup': 33,
5584      'pgdn': 34,
5585      'ins': 45,
5586      'del': 46,
5587      'cmd': 91
5588      /*!
5589       * Programatically add the following
5590       */
5591      // lower case chars
5592
5593    };
5594
5595    for (i = 97; i < 123; i++) {
5596      codes[String.fromCharCode(i)] = i - 32;
5597    } // numbers
5598
5599
5600    for (var i = 48; i < 58; i++) {
5601      codes[i - 48] = i;
5602    } // function keys
5603
5604
5605    for (i = 1; i < 13; i++) {
5606      codes['f' + i] = i + 111;
5607    } // numpad keys
5608
5609
5610    for (i = 0; i < 10; i++) {
5611      codes['numpad ' + i] = i + 96;
5612    }
5613    /**
5614     * Get by code
5615     *
5616     *   exports.name[13] // => 'Enter'
5617     */
5618
5619
5620    var names = exports.names = exports.title = {}; // title for backward compat
5621    // Create reverse mapping
5622
5623    for (i in codes) {
5624      names[codes[i]] = i;
5625    } // Add aliases
5626
5627
5628    for (var alias in aliases) {
5629      codes[alias] = aliases[alias];
5630    }
5631  });
5632  var keycode_1 = keycode.code;
5633  var keycode_2 = keycode.codes;
5634  var keycode_3 = keycode.aliases;
5635  var keycode_4 = keycode.names;
5636  var keycode_5 = keycode.title;
5637
5638  var MODAL_CLASS_NAME = 'vjs-modal-dialog';
5639  /**
5640   * The `ModalDialog` displays over the video and its controls, which blocks
5641   * interaction with the player until it is closed.
5642   *
5643   * Modal dialogs include a "Close" button and will close when that button
5644   * is activated - or when ESC is pressed anywhere.
5645   *
5646   * @extends Component
5647   */
5648
5649  var ModalDialog =
5650  /*#__PURE__*/
5651  function (_Component) {
5652    _inheritsLoose(ModalDialog, _Component);
5653
5654    /**
5655     * Create an instance of this class.
5656     *
5657     * @param {Player} player
5658     *        The `Player` that this class should be attached to.
5659     *
5660     * @param {Object} [options]
5661     *        The key/value store of player options.
5662     *
5663     * @param {Mixed} [options.content=undefined]
5664     *        Provide customized content for this modal.
5665     *
5666     * @param {string} [options.description]
5667     *        A text description for the modal, primarily for accessibility.
5668     *
5669     * @param {boolean} [options.fillAlways=false]
5670     *        Normally, modals are automatically filled only the first time
5671     *        they open. This tells the modal to refresh its content
5672     *        every time it opens.
5673     *
5674     * @param {string} [options.label]
5675     *        A text label for the modal, primarily for accessibility.
5676     *
5677     * @param {boolean}
5677 [options.pauseOnOpen=true]
5678     *        If `true`, playback will will be paused if playing when
5679     *        the modal opens, and resumed when it closes.
5680     *
5681     * @param {boolean} [options.temporary=true]
5682     *        If `true`, the modal can only be opened once; it will be
5683     *        disposed as soon as it's closed.
5684     *
5685     * @param {boolean} [options.uncloseable=false]
5686     *        If `true`, the user will not be able to close the modal
5687     *        through the UI in the normal ways. Programmatic closing is
5688     *        still possible.
5689     */
5690    function ModalDialog(player, options) {
5691      var _this;
5692
5693      _this = _Component.call(this, player, options) || this;
5694      _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false;
5695
5696      _this.closeable(!_this.options_.uncloseable);
5697
5698      _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized
5699      // because we only want the contents of the modal in the contentEl
5700      // (not the UI elements like the close button).
5701
5702
5703      _this.contentEl_ = createEl('div', {
5704        className: MODAL_CLASS_NAME + "-content"
5705      }, {
5706        role: 'document'
5707      });
5708      _this.descEl_ = createEl('p', {
5709        className: MODAL_CLASS_NAME + "-description vjs-control-text",
5710        id: _this.el().getAttribute('aria-describedby')
5711      });
5712      textContent(_this.descEl_, _this.description());
5713
5714      _this.el_.appendChild(_this.descEl_);
5715
5716      _this.el_.appendChild(_this.contentEl_);
5717
5718      return _this;
5719    }
5720    /**
5721     * Create the `ModalDialog`'s DOM element
5722     *
5723     * @return {Element}
5724     *         The DOM element that gets created.
5725     */
5726
5727
5728    var _proto = ModalDialog.prototype;
5729
5730    _proto.createEl = function createEl$$1() {
5731      return _Component.prototype.createEl.call(this, 'div', {
5732        className: this.buildCSSClass(),
5733        tabIndex: -1
5734      }, {
5735        'aria-describedby': this.id() + "_description",
5736        'aria-hidden': 'true',
5737        'aria-label': this.label(),
5738        'role': 'dialog'
5739      });
5740    };
5741
5742    _proto.dispose = function dispose() {
5743      this.contentEl_ = null;
5744      this.descEl_ = null;
5745      this.previouslyActiveEl_ = null;
5746
5747      _Component.prototype.dispose.call(this);
5748    }
5749    /**
5750     * Builds the default DOM `className`.
5751     *
5752     * @return {string}
5753     *         The DOM `className` for this object.
5754     */
5755    ;
5756
5757    _proto.buildCSSClass = function buildCSSClass() {
5758      return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this);
5759    }
5760    /**
5761     * Returns the label string for this modal. Primarily used for accessibility.
5762     *
5763     * @return {string}
5764     *         the localized or raw label of this modal.
5765     */
5766    ;
5767
5768    _proto.label = function label() {
5769      return this.localize(this.options_.label || 'Modal Window');
5770    }
5771    /**
5772     * Returns the description string for this modal. Primarily used for
5773     * accessibility.
5774     *
5775     * @return {string}
5776     *         The localized or raw description of this modal.
5777     */
5778    ;
5779
5780    _proto.description = function description() {
5781      var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable.
5782
5783      if (this.closeable()) {
5784        desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
5785      }
5786
5787      return desc;
5788    }
5789    /**
5790     * Opens the modal.
5791     *
5792     * @fires ModalDialog#beforemodalopen
5793     * @fires ModalDialog#modalopen
5794     */
5795    ;
5796
5797    _proto.open = function open() {
5798      if (!this.opened_) {
5799        var player = this.player();
5800        /**
5801          * Fired just before a `ModalDialog` is opened.
5802          *
5803          * @event ModalDialog#beforemodalopen
5804          * @type {EventTarget~Event}
5805          */
5806
5807        this.trigger('beforemodalopen');
5808        this.opened_ = true; // Fill content if the modal has never opened before and
5809        // never been filled.
5810
5811        if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
5812          this.fill();
5813        } // If the player was playing, pause it and take note of its previously
5814        // playing state.
5815
5816
5817        this.wasPlaying_ = !player.paused();
5818
vendor: 33,984 bytes, lines 5819-7139
5819        if (this.options_.pauseOnOpen && this.wasPlaying_) {
5820          player.pause();
5821        }
5822
5823        this.on('keydown', this.handleKeyDown); // Hide controls and note if they were enabled.
5824
5825        this.hadControls_ = player.controls();
5826        player.controls(false);
5827        this.show();
5828        this.conditionalFocus_();
5829        this.el().setAttribute('aria-hidden', 'false');
5830        /**
5831          * Fired just after a `ModalDialog` is opened.
5832          *
5833          * @event ModalDialog#modalopen
5834          * @type {EventTarget~Event}
5835          */
5836
5837        this.trigger('modalopen');
5838        this.hasBeenOpened_ = true;
5839      }
5840    }
5841    /**
5842     * If the `ModalDialog` is currently open or closed.
5843     *
5844     * @param  {boolean} [value]
5845     *         If given, it will open (`true`) or close (`false`) the modal.
5846     *
5847     * @return {boolean}
5848     *         the current open state of the modaldialog
5849     */
5850    ;
5851
5852    _proto.opened = function opened(value) {
5853      if (typeof value === 'boolean') {
5854        this[value ? 'open' : 'close']();
5855      }
5856
5857      return this.opened_;
5858    }
5859    /**
5860     * Closes the modal, does nothing if the `ModalDialog` is
5861     * not open.
5862     *
5863     * @fires ModalDialog#beforemodalclose
5864     * @fires ModalDialog#modalclose
5865     */
5866    ;
5867
5868    _proto.close = function close() {
5869      if (!this.opened_) {
5870        return;
5871      }
5872
5873      var player = this.player();
5874      /**
5875        * Fired just before a `ModalDialog` is closed.
5876        *
5877        * @event ModalDialog#beforemodalclose
5878        * @type {EventTarget~Event}
5879        */
5880
5881      this.trigger('beforemodalclose');
5882      this.opened_ = false;
5883
5884      if (this.wasPlaying_ && this.options_.pauseOnOpen) {
5885        player.play();
5886      }
5887
5888      this.off('keydown', this.handleKeyDown);
5889
5890      if (this.hadControls_) {
5891        player.controls(true);
5892      }
5893
5894      this.hide();
5895      this.el().setAttribute('aria-hidden', 'true');
5896      /**
5897        * Fired just after a `ModalDialog` is closed.
5898        *
5899        * @event ModalDialog#modalclose
5900        * @type {EventTarget~Event}
5901        */
5902
5903      this.trigger('modalclose');
5904      this.conditionalBlur_();
5905
5906      if (this.options_.temporary) {
5907        this.dispose();
5908      }
5909    }
5910    /**
5911     * Check to see if the `ModalDialog` is closeable via the UI.
5912     *
5913     * @param  {boolean} [value]
5914     *         If given as a boolean, it will set the `closeable` option.
5915     *
5916     * @return {boolean}
5917     *         Returns the final value of the closable option.
5918     */
5919    ;
5920
5921    _proto.closeable = function closeable(value) {
5922      if (typeof value === 'boolean') {
5923        var closeable = this.closeable_ = !!value;
5924        var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one.
5925
5926        if (closeable && !close) {
5927          // The close button should be a child of the modal - not its
5928          // content element, so temporarily change the content element.
5929          var temp = this.contentEl_;
5930          this.contentEl_ = this.el_;
5931          close = this.addChild('closeButton', {
5932            controlText: 'Close Modal Dialog'
5933          });
5934          this.contentEl_ = temp;
5935          this.on(close, 'close', this.close);
5936        } // If this is being made uncloseable and has a close button, remove it.
5937
5938
5939        if (!closeable && close) {
5940          this.off(close, 'close', this.close);
5941          this.removeChild(close);
5942          close.dispose();
5943        }
5944      }
5945
5946      return this.closeable_;
5947    }
5948    /**
5949     * Fill the modal's content element with the modal's "content" option.
5950     * The content element will be emptied before this change takes place.
5951     */
5952    ;
5953
5954    _proto.fill = function fill() {
5955      this.fillWith(this.content());
5956    }
5957    /**
5958     * Fill the modal's content element with arbitrary content.
5959     * The content element will be emptied before this change takes place.
5960     *
5961     * @fires ModalDialog#beforemodalfill
5962     * @fires ModalDialog#modalfill
5963     *
5964     * @param {Mixed} [content]
5965     *        The same rules apply to this as apply to the `content` option.
5966     */
5967    ;
5968
5969    _proto.fillWith = function fillWith(content) {
5970      var contentEl = this.contentEl();
5971      var parentEl = contentEl.parentNode;
5972      var nextSiblingEl = contentEl.nextSibling;
5973      /**
5974        * Fired just before a `ModalDialog` is filled with content.
5975        *
5976        * @event ModalDialog#beforemodalfill
5977        * @type {EventTarget~Event}
5978        */
5979
5980      this.trigger('beforemodalfill');
5981      this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing
5982      // manipulation to avoid modifying the live DOM multiple times.
5983
5984      parentEl.removeChild(contentEl);
5985      this.empty();
5986      insertContent(contentEl, content);
5987      /**
5988       * Fired just after a `ModalDialog` is filled with content.
5989       *
5990       * @event ModalDialog#modalfill
5991       * @type {EventTarget~Event}
5992       */
5993
5994      this.trigger('modalfill'); // Re-inject the re-filled content element.
5995
5996      if (nextSiblingEl) {
5997        parentEl.insertBefore(contentEl, nextSiblingEl);
5998      } else {
5999        parentEl.appendChild(contentEl);
6000      } // make sure that the close button is last in the dialog DOM
6001
6002
6003      var closeButton = this.getChild('closeButton');
6004
6005      if (closeButton) {
6006        parentEl.appendChild(closeButton.el_);
6007      }
6008    }
6009    /**
6010     * Empties the content element. This happens anytime the modal is filled.
6011     *
6012     * @fires ModalDialog#beforemodalempty
6013     * @fires ModalDialog#modalempty
6014     */
6015    ;
6016
6017    _proto.empty = function empty() {
6018      /**
6019      * Fired just before a `ModalDialog` is emptied.
6020      *
6021      * @event ModalDialog#beforemodalempty
6022      * @type {EventTarget~Event}
6023      */
6024      this.trigger('beforemodalempty');
6025      emptyEl(this.contentEl());
6026      /**
6027      * Fired just after a `ModalDialog` is emptied.
6028      *
6029      * @event ModalDialog#modalempty
6030      * @type {EventTarget~Event}
6031      */
6032
6033      this.trigger('modalempty');
6034    }
6035    /**
6036     * Gets or sets the modal content, which gets normalized before being
6037     * rendered into the DOM.
6038     *
6039     * This does not update the DOM or fill the modal, but it is called during
6040     * that process.
6041     *
6042     * @param  {Mixed} [value]
6043     *         If defined, sets the internal content value to be used on the
6044     *         next call(s) to `fill`. This value is normalized before being
6045     *         inserted. To "clear" the internal content value, pass `null`.
6046     *
6047     * @return {Mixed}
6048     *         The current content of the modal dialog
6049     */
6050    ;
6051
6052    _proto.content = function content(value) {
6053      if (typeof value !== 'undefined') {
6054        this.content_ = value;
6055      }
6056
6057      return this.content_;
6058    }
6059    /**
6060     * conditionally focus the modal dialog if focus was previously on the player.
6061     *
6062     * @private
6063     */
6064    ;
6065
6066    _proto.conditionalFocus_ = function conditionalFocus_() {
6067      var activeEl = document.activeElement;
6068      var playerEl = this.player_.el_;
6069      this.previouslyActiveEl_ = null;
6070
6071      if (playerEl.contains(activeEl) || playerEl === activeEl) {
6072        this.previouslyActiveEl_ = activeEl;
6073        this.focus();
6074      }
6075    }
6076    /**
6077     * conditionally blur the element and refocus the last focused element
6078     *
6079     * @private
6080     */
6081    ;
6082
6083    _proto.conditionalBlur_ = function conditionalBlur_() {
6084      if (this.previouslyActiveEl_) {
6085        this.previouslyActiveEl_.focus();
6086        this.previouslyActiveEl_ = null;
6087      }
6088    }
6089    /**
6090     * Keydown handler. Attached when modal is focused.
6091     *
6092     * @listens keydown
6093     */
6094    ;
6095
6096    _proto.handleKeyDown = function handleKeyDown(event) {
6097      // Do not allow keydowns to reach out of the modal dialog.
6098      event.stopPropagation();
6099
6100      if (keycode.isEventKey(event, 'Escape') && this.closeable()) {
6101        event.preventDefault();
6102        this.close();
6103        return;
6104      } // exit early if it isn't a tab key
6105
6106
6107      if (!keycode.isEventKey(event, 'Tab')) {
6108        return;
6109      }
6110
6111      var focusableEls = this.focusableEls_();
6112      var activeEl = this.el_.querySelector(':focus');
6113      var focusIndex;
6114
6115      for (var i = 0; i < focusableEls.length; i++) {
6116        if (activeEl === focusableEls[i]) {
6117          focusIndex = i;
6118          break;
6119        }
6120      }
6121
6122      if (document.activeElement === this.el_) {
6123        focusIndex = 0;
6124      }
6125
6126      if (event.shiftKey && focusIndex === 0) {
6127        focusableEls[focusableEls.length - 1].focus();
6128        event.preventDefault();
6129      } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
6130        focusableEls[0].focus();
6131        event.preventDefault();
6132      }
6133    }
6134    /**
6135     * get all focusable elements
6136     *
6137     * @private
6138     */
6139    ;
6140
6141    _proto.focusableEls_ = function focusableEls_() {
6142      var allChildren = this.el_.querySelectorAll('*');
6143      return Array.prototype.filter.call(allChildren, function (child) {
6144        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');
6145      });
6146    };
6147
6148    return ModalDialog;
6149  }(Component);
6150  /**
6151   * Default options for `ModalDialog` default options.
6152   *
6153   * @type {Object}
6154   * @private
6155   */
6156
6157
6158  ModalDialog.prototype.options_ = {
6159    pauseOnOpen: true,
6160    temporary: true
6161  };
6162  Component.registerComponent('ModalDialog', ModalDialog);
6163
6164  /**
6165   * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
6166   * {@link VideoTrackList}
6167   *
6168   * @extends EventTarget
6169   */
6170
6171  var TrackList =
6172  /*#__PURE__*/
6173  function (_EventTarget) {
6174    _inheritsLoose(TrackList, _EventTarget);
6175
6176    /**
6177     * Create an instance of this class
6178     *
6179     * @param {Track[]} tracks
6180     *        A list of tracks to initialize the list with.
6181     *
6182     * @abstract
6183     */
6184    function TrackList(tracks) {
6185      var _this;
6186
6187      if (tracks === void 0) {
6188        tracks = [];
6189      }
6190
6191      _this = _EventTarget.call(this) || this;
6192      _this.tracks_ = [];
6193      /**
6194       * @memberof TrackList
6195       * @member {number} length
6196       *         The current number of `Track`s in the this Trackist.
6197       * @instance
6198       */
6199
6200      Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'length', {
6201        get: function get() {
6202          return this.tracks_.length;
6203        }
6204      });
6205
6206      for (var i = 0; i < tracks.length; i++) {
6207        _this.addTrack(tracks[i]);
6208      }
6209
6210      return _this;
6211    }
6212    /**
6213     * Add a {@link Track} to the `TrackList`
6214     *
6215     * @param {Track} track
6216     *        The audio, video, or text track to add to the list.
6217     *
6218     * @fires TrackList#addtrack
6219     */
6220
6221
6222    var _proto = TrackList.prototype;
6223
6224    _proto.addTrack = function addTrack(track) {
6225      var index = this.tracks_.length;
6226
6227      if (!('' + index in this)) {
6228        Object.defineProperty(this, index, {
6229          get: function get() {
6230            return this.tracks_[index];
6231          }
6232        });
6233      } // Do not add duplicate tracks
6234
6235
6236      if (this.tracks_.indexOf(track) === -1) {
6237        this.tracks_.push(track);
6238        /**
6239         * Triggered when a track is added to a track list.
6240         *
6241         * @event TrackList#addtrack
6242         * @type {EventTarget~Event}
6243         * @property {Track} track
6244         *           A reference to track that was added.
6245         */
6246
6247        this.trigger({
6248          track: track,
6249          type: 'addtrack',
6250          target: this
6251        });
6252      }
6253    }
6254    /**
6255     * Remove a {@link Track} from the `TrackList`
6256     *
6257     * @param {Track} rtrack
6258     *        The audio, video, or text track to remove from the list.
6259     *
6260     * @fires TrackList#removetrack
6261     */
6262    ;
6263
6264    _proto.removeTrack = function removeTrack(rtrack) {
6265      var track;
6266
6267      for (var i = 0, l = this.length; i < l; i++) {
6268        if (this[i] === rtrack) {
6269          track = this[i];
6270
6271          if (track.off) {
6272            track.off();
6273          }
6274
6275          this.tracks_.splice(i, 1);
6276          break;
6277        }
6278      }
6279
6280      if (!track) {
6281        return;
6282      }
6283      /**
6284       * Triggered when a track is removed from track list.
6285       *
6286       * @event TrackList#removetrack
6287       * @type {EventTarget~Event}
6288       * @property {Track} track
6289       *           A reference to track that was removed.
6290       */
6291
6292
6293      this.trigger({
6294        track: track,
6295        type: 'removetrack',
6296        target: this
6297      });
6298    }
6299    /**
6300     * Get a Track from the TrackList by a tracks id
6301     *
6302     * @param {string} id - the id of the track to get
6303     * @method getTrackById
6304     * @return {Track}
6305     * @private
6306     */
6307    ;
6308
6309    _proto.getTrackById = function getTrackById(id) {
6310      var result = null;
6311
6312      for (var i = 0, l = this.length; i < l; i++) {
6313        var track = this[i];
6314
6315        if (track.id === id) {
6316          result = track;
6317          break;
6318        }
6319      }
6320
6321      return result;
6322    };
6323
6324    return TrackList;
6325  }(EventTarget);
6326  /**
6327   * Triggered when a different track is selected/enabled.
6328   *
6329   * @event TrackList#change
6330   * @type {EventTarget~Event}
6331   */
6332
6333  /**
6334   * Events that can be called with on + eventName. See {@link EventHandler}.
6335   *
6336   * @property {Object} TrackList#allowedEvents_
6337   * @private
6338   */
6339
6340
6341  TrackList.prototype.allowedEvents_ = {
6342    change: 'change',
6343    addtrack: 'addtrack',
6344    removetrack: 'removetrack'
6345  }; // emulate attribute EventHandler support to allow for feature detection
6346
6347  for (var event in TrackList.prototype.allowedEvents_) {
6348    TrackList.prototype['on' + event] = null;
6349  }
6350
6351  /**
6352   * Anywhere we call this function we diverge from the spec
6353   * as we only support one enabled audiotrack at a time
6354   *
6355   * @param {AudioTrackList} list
6356   *        list to work on
6357   *
6358   * @param {AudioTrack} track
6359   *        The track to skip
6360   *
6361   * @private
6362   */
6363
6364  var disableOthers = function disableOthers(list, track) {
6365    for (var i = 0; i < list.length; i++) {
6366      if (!Object.keys(list[i]).length || track.id === list[i].id) {
6367        continue;
6368      } // another audio track is enabled, disable it
6369
6370
6371      list[i].enabled = false;
6372    }
6373  };
6374  /**
6375   * The current list of {@link AudioTrack} for a media file.
6376   *
6377   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
6378   * @extends TrackList
6379   */
6380
6381
6382  var AudioTrackList =
6383  /*#__PURE__*/
6384  function (_TrackList) {
6385    _inheritsLoose(AudioTrackList, _TrackList);
6386
6387    /**
6388     * Create an instance of this class.
6389     *
6390     * @param {AudioTrack[]} [tracks=[]]
6391     *        A list of `AudioTrack` to instantiate the list with.
6392     */
6393    function AudioTrackList(tracks) {
6394      var _this;
6395
6396      if (tracks === void 0) {
6397        tracks = [];
6398      }
6399
6400      // make sure only 1 track is enabled
6401      // sorted from last index to first index
6402      for (var i = tracks.length - 1; i >= 0; i--) {
6403        if (tracks[i].enabled) {
6404          disableOthers(tracks, tracks[i]);
6405          break;
6406        }
6407      }
6408
6409      _this = _TrackList.call(this, tracks) || this;
6410      _this.changing_ = false;
6411      return _this;
6412    }
6413    /**
6414     * Add an {@link AudioTrack} to the `AudioTrackList`.
6415     *
6416     * @param {AudioTrack} track
6417     *        The AudioTrack to add to the list
6418     *
6419     * @fires TrackList#addtrack
6420     */
6421
6422
6423    var _proto = AudioTrackList.prototype;
6424
6425    _proto.addTrack = function addTrack(track) {
6426      var _this2 = this;
6427
6428      if (track.enabled) {
6429        disableOthers(this, track);
6430      }
6431
6432      _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
6433
6434
6435      if (!track.addEventListener) {
6436        return;
6437      }
6438
6439      track.enabledChange_ = function () {
6440        // when we are disabling other tracks (since we don't support
6441        // more than one track at a time) we will set changing_
6442        // to true so that we don't trigger additional change events
6443        if (_this2.changing_) {
6444          return;
6445        }
6446
6447        _this2.changing_ = true;
6448        disableOthers(_this2, track);
6449        _this2.changing_ = false;
6450
6451        _this2.trigger('change');
6452      };
6453      /**
6454       * @listens AudioTrack#enabledchange
6455       * @fires TrackList#change
6456       */
6457
6458
6459      track.addEventListener('enabledchange', track.enabledChange_);
6460    };
6461
6462    _proto.removeTrack = function removeTrack(rtrack) {
6463      _TrackList.prototype.removeTrack.call(this, rtrack);
6464
6465      if (rtrack.removeEventListener && rtrack.enabledChange_) {
6466        rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
6467        rtrack.enabledChange_ = null;
6468      }
6469    };
6470
6471    return AudioTrackList;
6472  }(TrackList);
6473
6474  /**
6475   * Un-select all other {@link VideoTrack}s that are selected.
6476   *
6477   * @param {VideoTrackList} list
6478   *        list to work on
6479   *
6480   * @param {VideoTrack} track
6481   *        The track to skip
6482   *
6483   * @private
6484   */
6485
6486  var disableOthers$1 = function disableOthers(list, track) {
6487    for (var i = 0; i < list.length; i++) {
6488      if (!Object.keys(list[i]).length || track.id === list[i].id) {
6489        continue;
6490      } // another video track is enabled, disable it
6491
6492
6493      list[i].selected = false;
6494    }
6495  };
6496  /**
6497   * The current list of {@link VideoTrack} for a video.
6498   *
6499   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
6500   * @extends TrackList
6501   */
6502
6503
6504  var VideoTrackList =
6505  /*#__PURE__*/
6506  function (_TrackList) {
6507    _inheritsLoose(VideoTrackList, _TrackList);
6508
6509    /**
6510     * Create an instance of this class.
6511     *
6512     * @param {VideoTrack[]} [tracks=[]]
6513     *        A list of `VideoTrack` to instantiate the list with.
6514     */
6515    function VideoTrackList(tracks) {
6516      var _this;
6517
6518      if (tracks === void 0) {
6519        tracks = [];
6520      }
6521
6522      // make sure only 1 track is enabled
6523      // sorted from last index to first index
6524      for (var i = tracks.length - 1; i >= 0; i--) {
6525        if (tracks[i].selected) {
6526          disableOthers$1(tracks, tracks[i]);
6527          break;
6528        }
6529      }
6530
6531      _this = _TrackList.call(this, tracks) || this;
6532      _this.changing_ = false;
6533      /**
6534       * @member {number} VideoTrackList#selectedIndex
6535       *         The current index of the selected {@link VideoTrack`}.
6536       */
6537
6538      Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'selectedIndex', {
6539        get: function get() {
6540          for (var _i = 0; _i < this.length; _i++) {
6541            if (this[_i].selected) {
6542              return _i;
6543            }
6544          }
6545
6546          return -1;
6547        },
6548        set: function set() {}
6549      });
6550      return _this;
6551    }
6552    /**
6553     * Add a {@link VideoTrack} to the `VideoTrackList`.
6554     *
6555     * @param {VideoTrack} track
6556     *        The VideoTrack to add to the list
6557     *
6558     * @fires TrackList#addtrack
6559     */
6560
6561
6562    var _proto = VideoTrackList.prototype;
6563
6564    _proto.addTrack = function addTrack(track) {
6565      var _this2 = this;
6566
6567      if (track.selected) {
6568        disableOthers$1(this, track);
6569      }
6570
6571      _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this
6572
6573
6574      if (!track.addEventListener) {
6575        return;
6576      }
6577
6578      track.selectedChange_ = function () {
6579        if (_this2.changing_) {
6580          return;
6581        }
6582
6583        _this2.changing_ = true;
6584        disableOthers$1(_this2, track);
6585        _this2.changing_ = false;
6586
6587        _this2.trigger('change');
6588      };
6589      /**
6590       * @listens VideoTrack#selectedchange
6591       * @fires TrackList#change
6592       */
6593
6594
6595      track.addEventListener('selectedchange', track.selectedChange_);
6596    };
6597
6598    _proto.removeTrack = function removeTrack(rtrack) {
6599      _TrackList.prototype.removeTrack.call(this, rtrack);
6600
6601      if (rtrack.removeEventListener && rtrack.selectedChange_) {
6602        rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
6603        rtrack.selectedChange_ = null;
6604      }
6605    };
6606
6607    return VideoTrackList;
6608  }(TrackList);
6609
6610  /**
6611   * The current list of {@link TextTrack} for a media file.
6612   *
6613   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
6614   * @extends TrackList
6615   */
6616
6617  var TextTrackList =
6618  /*#__PURE__*/
6619  function (_TrackList) {
6620    _inheritsLoose(TextTrackList, _TrackList);
6621
6622    function TextTrackList() {
6623      return _TrackList.apply(this, arguments) || this;
6624    }
6625
6626    var _proto = TextTrackList.prototype;
6627
6628    /**
6629     * Add a {@link TextTrack} to the `TextTrackList`
6630     *
6631     * @param {TextTrack} track
6632     *        The text track to add to the list.
6633     *
6634     * @fires TrackList#addtrack
6635     */
6636    _proto.addTrack = function addTrack(track) {
6637      var _this = this;
6638
6639      _TrackList.prototype.addTrack.call(this, track);
6640
6641      if (!this.queueChange_) {
6642        this.queueChange_ = function () {
6643          return _this.queueTrigger('change');
6644        };
6645      }
6646
6647      if (!this.triggerSelectedlanguagechange) {
6648        this.triggerSelectedlanguagechange_ = function () {
6649          return _this.trigger('selectedlanguagechange');
6650        };
6651      }
6652      /**
6653       * @listens TextTrack#modechange
6654       * @fires TrackList#change
6655       */
6656
6657
6658      track.addEventListener('modechange', this.queueChange_);
6659      var nonLanguageTextTrackKind = ['metadata', 'chapters'];
6660
6661      if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
6662        track.addEventListener('modechange', this.triggerSelectedlanguagechange_);
6663      }
6664    };
6665
6666    _proto.removeTrack = function removeTrack(rtrack) {
6667      _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added
6668
6669
6670      if (rtrack.removeEventListener) {
6671        if (this.queueChange_) {
6672          rtrack.removeEventListener('modechange', this.queueChange_);
6673        }
6674
6675        if (this.selectedlanguagechange_) {
6676          rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_);
6677        }
6678      }
6679    };
6680
6681    return TextTrackList;
6682  }(TrackList);
6683
6684  /**
6685   * @file html-track-element-list.js
6686   */
6687
6688  /**
6689   * The current list of {@link HtmlTrackElement}s.
6690   */
6691  var HtmlTrackElementList =
6692  /*#__PURE__*/
6693  function () {
6694    /**
6695     * Create an instance of this class.
6696     *
6697     * @param {HtmlTrackElement[]} [tracks=[]]
6698     *        A list of `HtmlTrackElement` to instantiate the list with.
6699     */
6700    function HtmlTrackElementList(trackElements) {
6701      if (trackElements === void 0) {
6702        trackElements = [];
6703      }
6704
6705      this.trackElements_ = [];
6706      /**
6707       * @memberof HtmlTrackElementList
6708       * @member {number} length
6709       *         The current number of `Track`s in the this Trackist.
6710       * @instance
6711       */
6712
6713      Object.defineProperty(this, 'length', {
6714        get: function get() {
6715          return this.trackElements_.length;
6716        }
6717      });
6718
6719      for (var i = 0, length = trackElements.length; i < length; i++) {
6720        this.addTrackElement_(trackElements[i]);
6721      }
6722    }
6723    /**
6724     * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
6725     *
6726     * @param {HtmlTrackElement} trackElement
6727     *        The track element to add to the list.
6728     *
6729     * @private
6730     */
6731
6732
6733    var _proto = HtmlTrackElementList.prototype;
6734
6735    _proto.addTrackElement_ = function addTrackElement_(trackElement) {
6736      var index = this.trackElements_.length;
6737
6738      if (!('' + index in this)) {
6739        Object.defineProperty(this, index, {
6740          get: function get() {
6741            return this.trackElements_[index];
6742          }
6743        });
6744      } // Do not add duplicate elements
6745
6746
6747      if (this.trackElements_.indexOf(trackElement) === -1) {
6748        this.trackElements_.push(trackElement);
6749      }
6750    }
6751    /**
6752     * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
6753     * {@link TextTrack}.
6754     *
6755     * @param {TextTrack} track
6756     *        The track associated with a track element.
6757     *
6758     * @return {HtmlTrackElement|undefined}
6759     *         The track element that was found or undefined.
6760     *
6761     * @private
6762     */
6763    ;
6764
6765    _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) {
6766      var trackElement_;
6767
6768      for (var i = 0, length = this.trackElements_.length; i < length; i++) {
6769        if (track === this.trackElements_[i].track) {
6770          trackElement_ = this.trackElements_[i];
6771          break;
6772        }
6773      }
6774
6775      return trackElement_;
6776    }
6777    /**
6778     * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
6779     *
6780     * @param {HtmlTrackElement} trackElement
6781     *        The track element to remove from the list.
6782     *
6783     * @private
6784     */
6785    ;
6786
6787    _proto.removeTrackElement_ = function removeTrackElement_(trackElement) {
6788      for (var i = 0, length = this.trackElements_.length; i < length; i++) {
6789        if (trackElement === this.trackElements_[i]) {
6790          if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') {
6791            this.trackElements_[i].track.off();
6792          }
6793
6794          if (typeof this.trackElements_[i].off === 'function') {
6795            this.trackElements_[i].off();
6796          }
6797
6798          this.trackElements_.splice(i, 1);
6799          break;
6800        }
6801      }
6802    };
6803
6804    return HtmlTrackElementList;
6805  }();
6806
6807  /**
6808   * @file text-track-cue-list.js
6809   */
6810
6811  /**
6812   * @typedef {Object} TextTrackCueList~TextTrackCue
6813   *
6814   * @property {string} id
6815   *           The unique id for this text track cue
6816   *
6817   * @property {number} startTime
6818   *           The start time for this text track cue
6819   *
6820   * @property {number} endTime
6821   *           The end time for this text track cue
6822   *
6823   * @property {boolean} pauseOnExit
6824   *           Pause when the end time is reached if true.
6825   *
6826   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
6827   */
6828
6829  /**
6830   * A List of TextTrackCues.
6831   *
6832   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
6833   */
6834  var TextTrackCueList =
6835  /*#__PURE__*/
6836  function () {
6837    /**
6838     * Create an instance of this class..
6839     *
6840     * @param {Array} cues
6841     *        A list of cues to be initialized with
6842     */
6843    function TextTrackCueList(cues) {
6844      TextTrackCueList.prototype.setCues_.call(this, cues);
6845      /**
6846       * @memberof TextTrackCueList
6847       * @member {number} length
6848       *         The current number of `TextTrackCue`s in the TextTrackCueList.
6849       * @instance
6850       */
6851
6852      Object.defineProperty(this, 'length', {
6853        get: function get() {
6854          return this.length_;
6855        }
6856      });
6857    }
6858    /**
6859     * A setter for cues in this list. Creates getters
6860     * an an index for the cues.
6861     *
6862     * @param {Array} cues
6863     *        An array of cues to set
6864     *
6865     * @private
6866     */
6867
6868
6869    var _proto = TextTrackCueList.prototype;
6870
6871    _proto.setCues_ = function setCues_(cues) {
6872      var oldLength = this.length || 0;
6873      var i = 0;
6874      var l = cues.length;
6875      this.cues_ = cues;
6876      this.length_ = cues.length;
6877
6878      var defineProp = function defineProp(index) {
6879        if (!('' + index in this)) {
6880          Object.defineProperty(this, '' + index, {
6881            get: function get() {
6882              return this.cues_[index];
6883            }
6884          });
6885        }
6886      };
6887
6888      if (oldLength < l) {
6889        i = oldLength;
6890
6891        for (; i < l; i++) {
6892          defineProp.call(this, i);
6893        }
6894      }
6895    }
6896    /**
6897     * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
6898     *
6899     * @param {string} id
6900     *        The id of the cue that should be searched for.
6901     *
6902     * @return {TextTrackCueList~TextTrackCue|null}
6903     *         A single cue or null if none was found.
6904     */
6905    ;
6906
6907    _proto.getCueById = function getCueById(id) {
6908      var result = null;
6909
6910      for (var i = 0, l = this.length; i < l; i++) {
6911        var cue = this[i];
6912
6913        if (cue.id === id) {
6914          result = cue;
6915          break;
6916        }
6917      }
6918
6919      return result;
6920    };
6921
6922    return TextTrackCueList;
6923  }();
6924
6925  /**
6926   * @file track-kinds.js
6927   */
6928
6929  /**
6930   * All possible `VideoTrackKind`s
6931   *
6932   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
6933   * @typedef VideoTrack~Kind
6934   * @enum
6935   */
6936  var VideoTrackKind = {
6937    alternative: 'alternative',
6938    captions: 'captions',
6939    main: 'main',
6940    sign: 'sign',
6941    subtitles: 'subtitles',
6942    commentary: 'commentary'
6943  };
6944  /**
6945   * All possible `AudioTrackKind`s
6946   *
6947   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
6948   * @typedef AudioTrack~Kind
6949   * @enum
6950   */
6951
6952  var AudioTrackKind = {
6953    'alternative': 'alternative',
6954    'descriptions': 'descriptions',
6955    'main': 'main',
6956    'main-desc': 'main-desc',
6957    'translation': 'translation',
6958    'commentary': 'commentary'
6959  };
6960  /**
6961   * All possible `TextTrackKind`s
6962   *
6963   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
6964   * @typedef TextTrack~Kind
6965   * @enum
6966   */
6967
6968  var TextTrackKind = {
6969    subtitles: 'subtitles',
6970    captions: 'captions',
6971    descriptions: 'descriptions',
6972    chapters: 'chapters',
6973    metadata: 'metadata'
6974  };
6975  /**
6976   * All possible `TextTrackMode`s
6977   *
6978   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
6979   * @typedef TextTrack~Mode
6980   * @enum
6981   */
6982
6983  var TextTrackMode = {
6984    disabled: 'disabled',
6985    hidden: 'hidden',
6986    showing: 'showing'
6987  };
6988
6989  /**
6990   * A Track class that contains all of the common functionality for {@link AudioTrack},
6991   * {@link VideoTrack}, and {@link TextTrack}.
6992   *
6993   * > Note: This class should not be used directly
6994   *
6995   * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
6996   * @extends EventTarget
6997   * @abstract
6998   */
6999
7000  var Track =
7001  /*#__PURE__*/
7002  function (_EventTarget) {
7003    _inheritsLoose(Track, _EventTarget);
7004
7005    /**
7006     * Create an instance of this class.
7007     *
7008     * @param {Object} [options={}]
7009     *        Object of option names and values
7010     *
7011     * @param {string} [options.kind='']
7012     *        A valid kind for the track type you are creating.
7013     *
7014     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7015     *        A unique id for this AudioTrack.
7016     *
7017     * @param {string} [options.label='']
7018     *        The menu label for this track.
7019     *
7020     * @param {string} [options.language='']
7021     *        A valid two character language code.
7022     *
7023     * @abstract
7024     */
7025    function Track(options) {
7026      var _this;
7027
7028      if (options === void 0) {
7029        options = {};
7030      }
7031
7032      _this = _EventTarget.call(this) || this;
7033      var trackProps = {
7034        id: options.id || 'vjs_track_' + newGUID(),
7035        kind: options.kind || '',
7036        label: options.label || '',
7037        language: options.language || ''
7038      };
7039      /**
7040       * @memberof Track
7041       * @member {string} id
7042       *         The id of this track. Cannot be changed after creation.
7043       * @instance
7044       *
7045       * @readonly
7046       */
7047
7048      /**
7049       * @memberof Track
7050       * @member {string} kind
7051       *         The kind of track that this is. Cannot be changed after creation.
7052       * @instance
7053       *
7054       * @readonly
7055       */
7056
7057      /**
7058       * @memberof Track
7059       * @member {string} label
7060       *         The label of this track. Cannot be changed after creation.
7061       * @instance
7062       *
7063       * @readonly
7064       */
7065
7066      /**
7067       * @memberof Track
7068       * @member {string} language
7069       *         The two letter language code for this track. Cannot be changed after
7070       *         creation.
7071       * @instance
7072       *
7073       * @readonly
7074       */
7075
7076      var _loop = function _loop(key) {
7077        Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), key, {
7078          get: function get() {
7079            return trackProps[key];
7080          },
7081          set: function set() {}
7082        });
7083      };
7084
7085      for (var key in trackProps) {
7086        _loop(key);
7087      }
7088
7089      return _this;
7090    }
7091
7092    return Track;
7093  }(EventTarget);
7094
7095  /**
7096   * @file url.js
7097   * @module url
7098   */
7099  /**
7100   * @typedef {Object} url:URLObject
7101   *
7102   * @property {string} protocol
7103   *           The protocol of the url that was parsed.
7104   *
7105   * @property {string} hostname
7106   *           The hostname of the url that was parsed.
7107   *
7108   * @property {string} port
7109   *           The port of the url that was parsed.
7110   *
7111   * @property {string} pathname
7112   *           The pathname of the url that was parsed.
7113   *
7114   * @property {string} search
7115   *           The search query of the url that was parsed.
7116   *
7117   * @property {string} hash
7118   *           The hash of the url that was parsed.
7119   *
7120   * @property {string} host
7121   *           The host of the url that was parsed.
7122   */
7123
7124  /**
7125   * Resolve and parse the elements of a URL.
7126   *
7127   * @function
7128   * @param    {String} url
7129   *           The url to parse
7130   *
7131   * @return   {url:URLObject}
7132   *           An object of url details
7133   */
7134
7135  var parseUrl = function parseUrl(url) {
7136    var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL
7137
7138    var a = document.createElement('a');
7139    a.href = url; // IE8 (and 9?) Fix
vendor: 4,092 bytes, lines 7140-7270
7140    // ie8 doesn't parse the URL correctly until the anchor is actually
7141    // added to the body, and an innerHTML is needed to trigger the parsing
7142
7143    var addToBody = a.host === '' && a.protocol !== 'file:';
7144    var div;
7145
7146    if (addToBody) {
7147      div = document.createElement('div');
7148      div.innerHTML = "<a href=\"" + url + "\"></a>";
7149      a = div.firstChild; // prevent the div from affecting layout
7150
7151      div.setAttribute('style', 'display:none; position:absolute;');
7152      document.body.appendChild(div);
7153    } // Copy the specific URL properties to a new object
7154    // This is also needed for IE8 because the anchor loses its
7155    // properties when it's removed from the dom
7156
7157
7158    var details = {};
7159
7160    for (var i = 0; i < props.length; i++) {
7161      details[props[i]] = a[props[i]];
7162    } // IE9 adds the port to the host property unlike everyone else. If
7163    // a port identifier is added for standard ports, strip it.
7164
7165
7166    if (details.protocol === 'http:') {
7167      details.host = details.host.replace(/:80$/, '');
7168    }
7169
7170    if (details.protocol === 'https:') {
7171      details.host = details.host.replace(/:443$/, '');
7172    }
7173
7174    if (!details.protocol) {
7175      details.protocol = window$1.location.protocol;
7176    }
7177
7178    if (addToBody) {
7179      document.body.removeChild(div);
7180    }
7181
7182    return details;
7183  };
7184  /**
7185   * Get absolute version of relative URL. Used to tell Flash the correct URL.
7186   *
7187   * @function
7188   * @param    {string} url
7189   *           URL to make absolute
7190   *
7191   * @return   {string}
7192   *           Absolute URL
7193   *
7194   * @see      http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
7195   */
7196
7197  var getAbsoluteURL = function getAbsoluteURL(url) {
7198    // Check if absolute URL
7199    if (!url.match(/^https?:\/\//)) {
7200      // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
7201      var div = document.createElement('div');
7202      div.innerHTML = "<a href=\"" + url + "\">x</a>";
7203      url = div.firstChild.href;
7204    }
7205
7206    return url;
7207  };
7208  /**
7209   * Returns the extension of the passed file name. It will return an empty string
7210   * if passed an invalid path.
7211   *
7212   * @function
7213   * @param    {string} path
7214   *           The fileName path like '/path/to/file.mp4'
7215   *
7216   * @return  {string}
7217   *           The extension in lower case or an empty string if no
7218   *           extension could be found.
7219   */
7220
7221  var getFileExtension = function getFileExtension(path) {
7222    if (typeof path === 'string') {
7223      var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
7224      var pathParts = splitPathRe.exec(path);
7225
7226      if (pathParts) {
7227        return pathParts.pop().toLowerCase();
7228      }
7229    }
7230
7231    return '';
7232  };
7233  /**
7234   * Returns whether the url passed is a cross domain request or not.
7235   *
7236   * @function
7237   * @param    {string} url
7238   *           The url to check.
7239   *
7240   * @return   {boolean}
7241   *           Whether it is a cross domain request or not.
7242   */
7243
7244  var isCrossOrigin = function isCrossOrigin(url) {
7245    var winLoc = window$1.location;
7246    var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol
7247
7248    var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin
7249    // IE8 doesn't know location.origin, so we won't rely on it here
7250
7251    var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
7252    return crossOrigin;
7253  };
7254
7255  var Url = /*#__PURE__*/Object.freeze({
7256    parseUrl: parseUrl,
7257    getAbsoluteURL: getAbsoluteURL,
7258    getFileExtension: getFileExtension,
7259    isCrossOrigin: isCrossOrigin
7260  });
7261
7262  var isFunction_1 = isFunction;
7263  var toString$1 = Object.prototype.toString;
7264
7265  function isFunction(fn) {
7266    var string = toString$1.call(fn);
7267    return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && ( // IE8 and below
7268    fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt);
7269  }
7270
7271  var trim_1 = createCommonjsModule(function (module, exports) {
7272    exports = module.exports = trim;
7273
7274    function trim(str) {
7275      return str.replace(/^\s*|\s*$/g, '');
7276    }
7277
7278    exports.left = function (str) {
7279      return str.replace(/^\s*/, '');
7280    };
7281
7282    exports.right = function (str) {
7283      return str.replace(/\s*$/, '');
7284    };
7285  });
7286  var trim_2 = trim_1.left;
7287  var trim_3 = trim_1.right;
7288
7289  var fnToStr = Function.prototype.toString;
7290  var constructorRegex = /^\s*class\b/;
7291
7292  var isES6ClassFn = function isES6ClassFunction(value) {
7293    try {
7294      var fnStr = fnToStr.call(value);
7295      return constructorRegex.test(fnStr);
7296    } catch (e) {
7297      return false; // not a function
7298    }
7299  };
7300
7301  var tryFunctionObject = function tryFunctionToStr(value) {
7302    try {
7303      if (isES6ClassFn(value)) {
7304        return false;
7305      }
7306
7307      fnToStr.call(value);
7308      return true;
7309    } catch (e) {
7310      return false;
7311    }
7312  };
7313
7314  var toStr = Object.prototype.toString;
7315  var fnClass = '[object Function]';
7316  var genClass = '[object GeneratorFunction]';
7317  var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
7318
7319  var isCallable = function isCallable(value) {
7320    if (!value) {
7321      return false;
7322    }
7323
7324    if (typeof value !== 'function' && typeof value !== 'object') {
7325      return false;
7326    }
7327
7328    if (typeof value === 'function' && !value.prototype) {
7329      return true;
7330    }
7331
7332    if (hasToStringTag) {
7333      return tryFunctionObject(value);
7334    }
7335
7336    if (isES6ClassFn(value)) {
7337      return false;
7338    }
7339
7340    var strClass = toStr.call(value);
7341    return strClass === fnClass || strClass === genClass;
7342  };
7343
7344  var toStr$1 = Object.prototype.toString;
7345  var hasOwnProperty = Object.prototype.hasOwnProperty;
7346
7347  var forEachArray = function forEachArray(array, iterator, receiver) {
7348    for (var i = 0, len = array.length; i < len; i++) {
7349      if (hasOwnProperty.call(array, i)) {
7350        if (receiver == null) {
7351          iterator(array[i], i, array);
7352        } else {
7353          iterator.call(receiver, array[i], i, array);
7354        }
7355      }
7356    }
7357  };
7358
7359  var forEachString = function forEachString(string, iterator, receiver) {
7360    for (var i = 0, len = string.length; i < len; i++) {
7361      // no such thing as a sparse string.
7362      if (receiver == null) {
7363        iterator(string.charAt(i), i, string);
7364      } else {
7365        iterator.call(receiver, string.charAt(i), i, string);
7366      }
7367    }
7368  };
7369
7370  var forEachObject = function forEachObject(object, iterator, receiver) {
7371    for (var k in object) {
7372      if (hasOwnProperty.call(object, k)) {
7373        if (receiver == null) {
7374          iterator(object[k], k, object);
7375        } else {
7376          iterator.call(receiver, object[k], k, object);
7377        }
7378      }
7379    }
7380  };
7381
7382  var forEach = function forEach(list, iterator, thisArg) {
7383    if (!isCallable(iterator)) {
7384      throw new TypeError('iterator must be a function');
7385    }
7386
7387    var receiver;
7388
7389    if (arguments.length >= 3) {
7390      receiver = thisArg;
7391    }
7392
7393    if (toStr$1.call(list) === '[object Array]') {
7394      forEachArray(list, iterator, receiver);
7395    } else if (typeof list === 'string') {
7396      forEachString(list, iterator, receiver);
7397    } else {
7398      forEachObject(list, iterator, receiver);
7399    }
7400  };
7401
7402  var forEach_1 = forEach;
7403
7404  var isArray = function isArray(arg) {
7405    return Object.prototype.toString.call(arg) === '[object Array]';
7406  };
7407
7408  var parseHeaders = function parseHeaders(headers) {
7409    if (!headers) return {};
7410    var result = {};
7411    forEach_1(trim_1(headers).split('\n'), function (row) {
7412      var index = row.indexOf(':'),
7413          key = trim_1(row.slice(0, index)).toLowerCase(),
7414          value = trim_1(row.slice(index + 1));
7415
7416      if (typeof result[key] === 'undefined') {
7417        result[key] = value;
7418      } else if (isArray(result[key])) {
7419        result[key].push(value);
7420      } else {
7421        result[key] = [result[key], value];
7422      }
7423    });
7424    return result;
7425  };
7426
7427  var immutable = extend;
7428  var hasOwnProperty$1 = Object.prototype.hasOwnProperty;
7429
7430  function extend() {
7431    var target = {};
7432
7433    for (var i = 0; i < arguments.length; i++) {
7434      var source = arguments[i];
7435
7436      for (var key in source) {
7437        if (hasOwnProperty$1.call(source, key)) {
7438          target[key] = source[key];
7439        }
7440      }
7441    }
7442
7443    return target;
7444  }
7445
7446  var xhr = createXHR;
7447  createXHR.XMLHttpRequest = window$1.XMLHttpRequest || noop;
7448  createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window$1.XDomainRequest;
7449  forEachArray$1(["get", "put", "post", "patch", "head", "delete"], function (method) {
7450    createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
7451      options = initParams(uri, options, callback);
7452      options.method = method.toUpperCase();
7453      return _createXHR(options);
7454    };
7455  });
7456
7457  function forEachArray$1(array, iterator) {
7458    for (var i = 0; i < array.length; i++) {
7459      iterator(array[i]);
7460    }
7461  }
7462
7463  function isEmpty(obj) {
7464    for (var i in obj) {
7465      if (obj.hasOwnProperty(i)) return false;
vendor: 5,374 bytes, lines 7466-7669
7466    }
7467
7468    return true;
7469  }
7470
7471  function initParams(uri, options, callback) {
7472    var params = uri;
7473
7474    if (isFunction_1(options)) {
7475      callback = options;
7476
7477      if (typeof uri === "string") {
7478        params = {
7479          uri: uri
7480        };
7481      }
7482    } else {
7483      params = immutable(options, {
7484        uri: uri
7485      });
7486    }
7487
7488    params.callback = callback;
7489    return params;
7490  }
7491
7492  function createXHR(uri, options, callback) {
7493    options = initParams(uri, options, callback);
7494    return _createXHR(options);
7495  }
7496
7497  function _createXHR(options) {
7498    if (typeof options.callback === "undefined") {
7499      throw new Error("callback argument missing");
7500    }
7501
7502    var called = false;
7503
7504    var callback = function cbOnce(err, response, body) {
7505      if (!called) {
7506        called = true;
7507        options.callback(err, response, body);
7508      }
7509    };
7510
7511    function readystatechange() {
7512      if (xhr.readyState === 4) {
7513        setTimeout(loadFunc, 0);
7514      }
7515    }
7516
7517    function getBody() {
7518      // Chrome with requestType=blob throws errors arround when even testing access to responseText
7519      var body = undefined;
7520
7521      if (xhr.response) {
7522        body = xhr.response;
7523      } else {
7524        body = xhr.responseText || getXml(xhr);
7525      }
7526
7527      if (isJson) {
7528        try {
7529          body = JSON.parse(body);
7530        } catch (e) {}
7531      }
7532
7533      return body;
7534    }
7535
7536    function errorFunc(evt) {
7537      clearTimeout(timeoutTimer);
7538
7539      if (!(evt instanceof Error)) {
7540        evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
7541      }
7542
7543      evt.statusCode = 0;
7544      return callback(evt, failureResponse);
7545    } // will load the data & process the response in a special response object
7546
7547
7548    function loadFunc() {
7549      if (aborted) return;
7550      var status;
7551      clearTimeout(timeoutTimer);
7552
7553      if (options.useXDR && xhr.status === undefined) {
7554        //IE8 CORS GET successful response doesn't have a status field, but body is fine
7555        status = 200;
7556      } else {
7557        status = xhr.status === 1223 ? 204 : xhr.status;
7558      }
7559
7560      var response = failureResponse;
7561      var err = null;
7562
7563      if (status !== 0) {
7564        response = {
7565          body: getBody(),
7566          statusCode: status,
7567          method: method,
7568          headers: {},
7569          url: uri,
7570          rawRequest: xhr
7571        };
7572
7573        if (xhr.getAllResponseHeaders) {
7574          //remember xhr can in fact be XDR for CORS in IE
7575          response.headers = parseHeaders(xhr.getAllResponseHeaders());
7576        }
7577      } else {
7578        err = new Error("Internal XMLHttpRequest Error");
7579      }
7580
7581      return callback(err, response, response.body);
7582    }
7583
7584    var xhr = options.xhr || null;
7585
7586    if (!xhr) {
7587      if (options.cors || options.useXDR) {
7588        xhr = new createXHR.XDomainRequest();
7589      } else {
7590        xhr = new createXHR.XMLHttpRequest();
7591      }
7592    }
7593
7594    var key;
7595    var aborted;
7596    var uri = xhr.url = options.uri || options.url;
7597    var method = xhr.method = options.method || "GET";
7598    var body = options.body || options.data;
7599    var headers = xhr.headers = options.headers || {};
7600    var sync = !!options.sync;
7601    var isJson = false;
7602    var timeoutTimer;
7603    var failureResponse = {
7604      body: undefined,
7605      headers: {},
7606      statusCode: 0,
7607      method: method,
7608      url: uri,
7609      rawRequest: xhr
7610    };
7611
7612    if ("json" in options && options.json !== false) {
7613      isJson = true;
7614      headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
7615
7616      if (method !== "GET" && method !== "HEAD") {
7617        headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
7618
7619        body = JSON.stringify(options.json === true ? body : options.json);
7620      }
7621    }
7622
7623    xhr.onreadystatechange = readystatechange;
7624    xhr.onload = loadFunc;
7625    xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function.
7626
7627    xhr.onprogress = function () {// IE must die
7628    };
7629
7630    xhr.onabort = function () {
7631      aborted = true;
7632    };
7633
7634    xhr.ontimeout = errorFunc;
7635    xhr.open(method, uri, !sync, options.username, options.password); //has to be after open
7636
7637    if (!sync) {
7638      xhr.withCredentials = !!options.withCredentials;
7639    } // Cannot set timeout with sync request
7640    // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
7641    // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
7642
7643
7644    if (!sync && options.timeout > 0) {
7645      timeoutTimer = setTimeout(function () {
7646        if (aborted) return;
7647        aborted = true; //IE9 may still call readystatechange
7648
7649        xhr.abort("timeout");
7650        var e = new Error("XMLHttpRequest timeout");
7651        e.code = "ETIMEDOUT";
7652        errorFunc(e);
7653      }, options.timeout);
7654    }
7655
7656    if (xhr.setRequestHeader) {
7657      for (key in headers) {
7658        if (headers.hasOwnProperty(key)) {
7659          xhr.setRequestHeader(key, headers[key]);
7660        }
7661      }
7662    } else if (options.headers && !isEmpty(options.headers)) {
7663      throw new Error("Headers cannot be set on an XDomainRequest object");
7664    }
7665
7666    if ("responseType" in options) {
7667      xhr.responseType = options.responseType;
7668    }
7669
vendor: 5,015 bytes, lines 7670-7855
7670    if ("beforeSend" in options && typeof options.beforeSend === "function") {
7671      options.beforeSend(xhr);
7672    } // Microsoft Edge browser sends "undefined" when send is called with undefined value.
7673    // XMLHttpRequest spec says to pass null as body to indicate no body
7674    // See https://github.com/naugtur/xhr/issues/100.
7675
7676
7677    xhr.send(body || null);
7678    return xhr;
7679  }
7680
7681  function getXml(xhr) {
7682    if (xhr.responseType === "document") {
7683      return xhr.responseXML;
7684    }
7685
7686    var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
7687
7688    if (xhr.responseType === "" && !firefoxBugTakenEffect) {
7689      return xhr.responseXML;
7690    }
7691
7692    return null;
7693  }
7694
7695  function noop() {}
7696
7697  /**
7698   * Takes a webvtt file contents and parses it into cues
7699   *
7700   * @param {string} srcContent
7701   *        webVTT file contents
7702   *
7703   * @param {TextTrack} track
7704   *        TextTrack to add cues to. Cues come from the srcContent.
7705   *
7706   * @private
7707   */
7708
7709  var parseCues = function parseCues(srcContent, track) {
7710    var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, window$1.WebVTT.StringDecoder());
7711    var errors = [];
7712
7713    parser.oncue = function (cue) {
7714      track.addCue(cue);
7715    };
7716
7717    parser.onparsingerror = function (error) {
7718      errors.push(error);
7719    };
7720
7721    parser.onflush = function () {
7722      track.trigger({
7723        type: 'loadeddata',
7724        target: track
7725      });
7726    };
7727
7728    parser.parse(srcContent);
7729
7730    if (errors.length > 0) {
7731      if (window$1.console && window$1.console.groupCollapsed) {
7732        window$1.console.groupCollapsed("Text Track parsing errors for " + track.src);
7733      }
7734
7735      errors.forEach(function (error) {
7736        return log.error(error);
7737      });
7738
7739      if (window$1.console && window$1.console.groupEnd) {
7740        window$1.console.groupEnd();
7741      }
7742    }
7743
7744    parser.flush();
7745  };
7746  /**
7747   * Load a `TextTrack` from a specified url.
7748   *
7749   * @param {string} src
7750   *        Url to load track from.
7751   *
7752   * @param {TextTrack} track
7753   *        Track to add cues to. Comes from the content at the end of `url`.
7754   *
7755   * @private
7756   */
7757
7758
7759  var loadTrack = function loadTrack(src, track) {
7760    var opts = {
7761      uri: src
7762    };
7763    var crossOrigin = isCrossOrigin(src);
7764
7765    if (crossOrigin) {
7766      opts.cors = crossOrigin;
7767    }
7768
7769    xhr(opts, bind(this, function (err, response, responseBody) {
7770      if (err) {
7771        return log.error(err, response);
7772      }
7773
7774      track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
7775      // NOTE: this is only used for the alt/video.novtt.js build
7776
7777      if (typeof window$1.WebVTT !== 'function') {
7778        if (track.tech_) {
7779          // to prevent use before define eslint error, we define loadHandler
7780          // as a let here
7781          var loadHandler;
7782
7783          var errorHandler = function errorHandler() {
7784            log.error("vttjs failed to load, stopping trying to process " + track.src);
7785            track.tech_.off('vttjsloaded', loadHandler);
7786          };
7787
7788          loadHandler = function loadHandler() {
7789            track.tech_.off('vttjserror', errorHandler);
7790            return parseCues(responseBody, track);
7791          };
7792
7793          track.tech_.one('vttjsloaded', loadHandler);
7794          track.tech_.one('vttjserror', errorHandler);
7795        }
7796      } else {
7797        parseCues(responseBody, track);
7798      }
7799    }));
7800  };
7801  /**
7802   * A representation of a single `TextTrack`.
7803   *
7804   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
7805   * @extends Track
7806   */
7807
7808
7809  var TextTrack =
7810  /*#__PURE__*/
7811  function (_Track) {
7812    _inheritsLoose(TextTrack, _Track);
7813
7814    /**
7815     * Create an instance of this class.
7816     *
7817     * @param {Object} options={}
7818     *        Object of option names and values
7819     *
7820     * @param {Tech} options.tech
7821     *        A reference to the tech that owns this TextTrack.
7822     *
7823     * @param {TextTrack~Kind} [options.kind='subtitles']
7824     *        A valid text track kind.
7825     *
7826     * @param {TextTrack~Mode} [options.mode='disabled']
7827     *        A valid text track mode.
7828     *
7829     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7830     *        A unique id for this TextTrack.
7831     *
7832     * @param {string} [options.label='']
7833     *        The menu label for this track.
7834     *
7835     * @param {string} [options.language='']
7836     *        A valid two character language code.
7837     *
7838     * @param {string} [options.srclang='']
7839     *        A valid two character language code. An alternative, but deprioritized
7840     *        version of `options.language`
7841     *
7842     * @param {string} [options.src]
7843     *        A url to TextTrack cues.
7844     *
7845     * @param {boolean} [options.default]
7846     *        If this track should default to on or off.
7847     */
7848    function TextTrack(options) {
7849      var _this;
7850
7851      if (options === void 0) {
7852        options = {};
7853      }
7854
7855      if (!options.tech) {
vendor: 4,481 bytes, lines 7856-8004
7856        throw new Error('A tech was not provided.');
7857      }
7858
7859      var settings = mergeOptions(options, {
7860        kind: TextTrackKind[options.kind] || 'subtitles',
7861        language: options.language || options.srclang || ''
7862      });
7863      var mode = TextTrackMode[settings.mode] || 'disabled';
7864      var default_ = settings.default;
7865
7866      if (settings.kind === 'metadata' || settings.kind === 'chapters') {
7867        mode = 'hidden';
7868      }
7869
7870      _this = _Track.call(this, settings) || this;
7871      _this.tech_ = settings.tech;
7872      _this.cues_ = [];
7873      _this.activeCues_ = [];
7874      var cues = new TextTrackCueList(_this.cues_);
7875      var activeCues = new TextTrackCueList(_this.activeCues_);
7876      var changed = false;
7877      var timeupdateHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), function () {
7878        // Accessing this.activeCues for the side-effects of updating itself
7879        // due to its nature as a getter function. Do not remove or cues will
7880        // stop updating!
7881        // Use the setter to prevent deletion from uglify (pure_getters rule)
7882        this.activeCues = this.activeCues;
7883
7884        if (changed) {
7885          this.trigger('cuechange');
7886          changed = false;
7887        }
7888      });
7889
7890      if (mode !== 'disabled') {
7891        _this.tech_.ready(function () {
7892          _this.tech_.on('timeupdate', timeupdateHandler);
7893        }, true);
7894      }
7895
7896      Object.defineProperties(_assertThisInitialized(_assertThisInitialized(_this)), {
7897        /**
7898         * @memberof TextTrack
7899         * @member {boolean} default
7900         *         If this track was set to be on or off by default. Cannot be changed after
7901         *         creation.
7902         * @instance
7903         *
7904         * @readonly
7905         */
7906        default: {
7907          get: function get() {
7908            return default_;
7909          },
7910          set: function set() {}
7911        },
7912
7913        /**
7914         * @memberof TextTrack
7915         * @member {string} mode
7916         *         Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
7917         *         not be set if setting to an invalid mode.
7918         * @instance
7919         *
7920         * @fires TextTrack#modechange
7921         */
7922        mode: {
7923          get: function get() {
7924            return mode;
7925          },
7926          set: function set(newMode) {
7927            var _this2 = this;
7928
7929            if (!TextTrackMode[newMode]) {
7930              return;
7931            }
7932
7933            mode = newMode;
7934
7935            if (mode !== 'disabled') {
7936              this.tech_.ready(function () {
7937                _this2.tech_.on('timeupdate', timeupdateHandler);
7938              }, true);
7939            } else {
7940              this.tech_.off('timeupdate', timeupdateHandler);
7941            }
7942            /**
7943             * An event that fires when mode changes on this track. This allows
7944             * the TextTrackList that holds this track to act accordingly.
7945             *
7946             * > Note: This is not part of the spec!
7947             *
7948             * @event TextTrack#modechange
7949             * @type {EventTarget~Event}
7950             */
7951
7952
7953            this.trigger('modechange');
7954          }
7955        },
7956
7957        /**
7958         * @memberof TextTrack
7959         * @member {TextTrackCueList} cues
7960         *         The text track cue list for this TextTrack.
7961         * @instance
7962         */
7963        cues: {
7964          get: function get() {
7965            if (!this.loaded_) {
7966              return null;
7967            }
7968
7969            return cues;
7970          },
7971          set: function set() {}
7972        },
7973
7974        /**
7975         * @memberof TextTrack
7976         * @member {TextTrackCueList} activeCues
7977         *         The list text track cues that are currently active for this TextTrack.
7978         * @instance
7979         */
7980        activeCues: {
7981          get: function get() {
7982            if (!this.loaded_) {
7983              return null;
7984            } // nothing to do
7985
7986
7987            if (this.cues.length === 0) {
7988              return activeCues;
7989            }
7990
7991            var ct = this.tech_.currentTime();
7992            var active = [];
7993
7994            for (var i = 0, l = this.cues.length; i < l; i++) {
7995              var cue = this.cues[i];
7996
7997              if (cue.startTime <= ct && cue.endTime >= ct) {
7998                active.push(cue);
7999              } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) {
8000                active.push(cue);
8001              }
8002            }
8003
8004            changed = false;
vendor: 12,090 bytes, lines 8005-8440
8005
8006            if (active.length !== this.activeCues_.length) {
8007              changed = true;
8008            } else {
8009              for (var _i = 0; _i < active.length; _i++) {
8010                if (this.activeCues_.indexOf(active[_i]) === -1) {
8011                  changed = true;
8012                }
8013              }
8014            }
8015
8016            this.activeCues_ = active;
8017            activeCues.setCues_(this.activeCues_);
8018            return activeCues;
8019          },
8020          // /!\ Keep this setter empty (see the timeupdate handler above)
8021          set: function set() {}
8022        }
8023      });
8024
8025      if (settings.src) {
8026        _this.src = settings.src;
8027        loadTrack(settings.src, _assertThisInitialized(_assertThisInitialized(_this)));
8028      } else {
8029        _this.loaded_ = true;
8030      }
8031
8032      return _this;
8033    }
8034    /**
8035     * Add a cue to the internal list of cues.
8036     *
8037     * @param {TextTrack~Cue} cue
8038     *        The cue to add to our internal list
8039     */
8040
8041
8042    var _proto = TextTrack.prototype;
8043
8044    _proto.addCue = function addCue(originalCue) {
8045      var cue = originalCue;
8046
8047      if (window$1.vttjs && !(originalCue instanceof window$1.vttjs.VTTCue)) {
8048        cue = new window$1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
8049
8050        for (var prop in originalCue) {
8051          if (!(prop in cue)) {
8052            cue[prop] = originalCue[prop];
8053          }
8054        } // make sure that `id` is copied over
8055
8056
8057        cue.id = originalCue.id;
8058        cue.originalCue_ = originalCue;
8059      }
8060
8061      var tracks = this.tech_.textTracks();
8062
8063      for (var i = 0; i < tracks.length; i++) {
8064        if (tracks[i] !== this) {
8065          tracks[i].removeCue(cue);
8066        }
8067      }
8068
8069      this.cues_.push(cue);
8070      this.cues.setCues_(this.cues_);
8071    }
8072    /**
8073     * Remove a cue from our internal list
8074     *
8075     * @param {TextTrack~Cue} removeCue
8076     *        The cue to remove from our internal list
8077     */
8078    ;
8079
8080    _proto.removeCue = function removeCue(_removeCue) {
8081      var i = this.cues_.length;
8082
8083      while (i--) {
8084        var cue = this.cues_[i];
8085
8086        if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) {
8087          this.cues_.splice(i, 1);
8088          this.cues.setCues_(this.cues_);
8089          break;
8090        }
8091      }
8092    };
8093
8094    return TextTrack;
8095  }(Track);
8096  /**
8097   * cuechange - One or more cues in the track have become active or stopped being active.
8098   */
8099
8100
8101  TextTrack.prototype.allowedEvents_ = {
8102    cuechange: 'cuechange'
8103  };
8104
8105  /**
8106   * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
8107   * only one `AudioTrack` in the list will be enabled at a time.
8108   *
8109   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
8110   * @extends Track
8111   */
8112
8113  var AudioTrack =
8114  /*#__PURE__*/
8115  function (_Track) {
8116    _inheritsLoose(AudioTrack, _Track);
8117
8118    /**
8119     * Create an instance of this class.
8120     *
8121     * @param {Object} [options={}]
8122     *        Object of option names and values
8123     *
8124     * @param {AudioTrack~Kind} [options.kind='']
8125     *        A valid audio track kind
8126     *
8127     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8128     *        A unique id for this AudioTrack.
8129     *
8130     * @param {string} [options.label='']
8131     *        The menu label for this track.
8132     *
8133     * @param {string} [options.language='']
8134     *        A valid two character language code.
8135     *
8136     * @param {boolean} [options.enabled]
8137     *        If this track is the one that is currently playing. If this track is part of
8138     *        an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
8139     */
8140    function AudioTrack(options) {
8141      var _this;
8142
8143      if (options === void 0) {
8144        options = {};
8145      }
8146
8147      var settings = mergeOptions(options, {
8148        kind: AudioTrackKind[options.kind] || ''
8149      });
8150      _this = _Track.call(this, settings) || this;
8151      var enabled = false;
8152      /**
8153       * @memberof AudioTrack
8154       * @member {boolean} enabled
8155       *         If this `AudioTrack` is enabled or not. When setting this will
8156       *         fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
8157       * @instance
8158       *
8159       * @fires VideoTrack#selectedchange
8160       */
8161
8162      Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'enabled', {
8163        get: function get() {
8164          return enabled;
8165        },
8166        set: function set(newEnabled) {
8167          // an invalid or unchanged value
8168          if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
8169            return;
8170          }
8171
8172          enabled = newEnabled;
8173          /**
8174           * An event that fires when enabled changes on this track. This allows
8175           * the AudioTrackList that holds this track to act accordingly.
8176           *
8177           * > Note: This is not part of the spec! Native tracks will do
8178           *         this internally without an event.
8179           *
8180           * @event AudioTrack#enabledchange
8181           * @type {EventTarget~Event}
8182           */
8183
8184          this.trigger('enabledchange');
8185        }
8186      }); // if the user sets this track to selected then
8187      // set selected to that true value otherwise
8188      // we keep it false
8189
8190      if (settings.enabled) {
8191        _this.enabled = settings.enabled;
8192      }
8193
8194      _this.loaded_ = true;
8195      return _this;
8196    }
8197
8198    return AudioTrack;
8199  }(Track);
8200
8201  /**
8202   * A representation of a single `VideoTrack`.
8203   *
8204   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
8205   * @extends Track
8206   */
8207
8208  var VideoTrack =
8209  /*#__PURE__*/
8210  function (_Track) {
8211    _inheritsLoose(VideoTrack, _Track);
8212
8213    /**
8214     * Create an instance of this class.
8215     *
8216     * @param {Object} [options={}]
8217     *        Object of option names and values
8218     *
8219     * @param {string} [options.kind='']
8220     *        A valid {@link VideoTrack~Kind}
8221     *
8222     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8223     *        A unique id for this AudioTrack.
8224     *
8225     * @param {string} [options.label='']
8226     *        The menu label for this track.
8227     *
8228     * @param {string} [options.language='']
8229     *        A valid two character language code.
8230     *
8231     * @param {boolean} [options.selected]
8232     *        If this track is the one that is currently playing.
8233     */
8234    function VideoTrack(options) {
8235      var _this;
8236
8237      if (options === void 0) {
8238        options = {};
8239      }
8240
8241      var settings = mergeOptions(options, {
8242        kind: VideoTrackKind[options.kind] || ''
8243      });
8244      _this = _Track.call(this, settings) || this;
8245      var selected = false;
8246      /**
8247       * @memberof VideoTrack
8248       * @member {boolean} selected
8249       *         If this `VideoTrack` is selected or not. When setting this will
8250       *         fire {@link VideoTrack#selectedchange} if the state of selected changed.
8251       * @instance
8252       *
8253       * @fires VideoTrack#selectedchange
8254       */
8255
8256      Object.defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), 'selected', {
8257        get: function get() {
8258          return selected;
8259        },
8260        set: function set(newSelected) {
8261          // an invalid or unchanged value
8262          if (typeof newSelected !== 'boolean' || newSelected === selected) {
8263            return;
8264          }
8265
8266          selected = newSelected;
8267          /**
8268           * An event that fires when selected changes on this track. This allows
8269           * the VideoTrackList that holds this track to act accordingly.
8270           *
8271           * > Note: This is not part of the spec! Native tracks will do
8272           *         this internally without an event.
8273           *
8274           * @event VideoTrack#selectedchange
8275           * @type {EventTarget~Event}
8276           */
8277
8278          this.trigger('selectedchange');
8279        }
8280      }); // if the user sets this track to selected then
8281      // set selected to that true value otherwise
8282      // we keep it false
8283
8284      if (settings.selected) {
8285        _this.selected = settings.selected;
8286      }
8287
8288      return _this;
8289    }
8290
8291    return VideoTrack;
8292  }(Track);
8293
8294  /**
8295   * @memberof HTMLTrackElement
8296   * @typedef {HTMLTrackElement~ReadyState}
8297   * @enum {number}
8298   */
8299
8300  var NONE = 0;
8301  var LOADING = 1;
8302  var LOADED = 2;
8303  var ERROR = 3;
8304  /**
8305   * A single track represented in the DOM.
8306   *
8307   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
8308   * @extends EventTarget
8309   */
8310
8311  var HTMLTrackElement =
8312  /*#__PURE__*/
8313  function (_EventTarget) {
8314    _inheritsLoose(HTMLTrackElement, _EventTarget);
8315
8316    /**
8317     * Create an instance of this class.
8318     *
8319     * @param {Object} options={}
8320     *        Object of option names and values
8321     *
8322     * @param {Tech} options.tech
8323     *        A reference to the tech that owns this HTMLTrackElement.
8324     *
8325     * @param {TextTrack~Kind} [options.kind='subtitles']
8326     *        A valid text track kind.
8327     *
8328     * @param {TextTrack~Mode} [options.mode='disabled']
8329     *        A valid text track mode.
8330     *
8331     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8332     *        A unique id for this TextTrack.
8333     *
8334     * @param {string} [options.label='']
8335     *        The menu label for this track.
8336     *
8337     * @param {string} [options.language='']
8338     *        A valid two character language code.
8339     *
8340     * @param {string} [options.srclang='']
8341     *        A valid two character language code. An alternative, but deprioritized
8342     *        vesion of `options.language`
8343     *
8344     * @param {string} [options.src]
8345     *        A url to TextTrack cues.
8346     *
8347     * @param {boolean} [options.default]
8348     *        If this track should default to on or off.
8349     */
8350    function HTMLTrackElement(options) {
8351      var _this;
8352
8353      if (options === void 0) {
8354        options = {};
8355      }
8356
8357      _this = _EventTarget.call(this) || this;
8358      var readyState;
8359      var track = new TextTrack(options);
8360      _this.kind = track.kind;
8361      _this.src = track.src;
8362      _this.srclang = track.language;
8363      _this.label = track.label;
8364      _this.default = track.default;
8365      Object.defineProperties(_assertThisInitialized(_assertThisInitialized(_this)), {
8366        /**
8367         * @memberof HTMLTrackElement
8368         * @member {HTMLTrackElement~ReadyState} readyState
8369         *         The current ready state of the track element.
8370         * @instance
8371         */
8372        readyState: {
8373          get: function get() {
8374            return readyState;
8375          }
8376        },
8377
8378        /**
8379         * @memberof HTMLTrackElement
8380         * @member {TextTrack} track
8381         *         The underlying TextTrack object.
8382         * @instance
8383         *
8384         */
8385        track: {
8386          get: function get() {
8387            return track;
8388          }
8389        }
8390      });
8391      readyState = NONE;
8392      /**
8393       * @listens TextTrack#loadeddata
8394       * @fires HTMLTrackElement#load
8395       */
8396
8397      track.addEventListener('loadeddata', function () {
8398        readyState = LOADED;
8399
8400        _this.trigger({
8401          type: 'load',
8402          target: _assertThisInitialized(_assertThisInitialized(_this))
8403        });
8404      });
8405      return _this;
8406    }
8407
8408    return HTMLTrackElement;
8409  }(EventTarget);
8410
8411  HTMLTrackElement.prototype.allowedEvents_ = {
8412    load: 'load'
8413  };
8414  HTMLTrackElement.NONE = NONE;
8415  HTMLTrackElement.LOADING = LOADING;
8416  HTMLTrackElement.LOADED = LOADED;
8417  HTMLTrackElement.ERROR = ERROR;
8418
8419  /*
8420   * This file contains all track properties that are used in
8421   * player.js, tech.js, html5.js and possibly other techs in the future.
8422   */
8423
8424  var NORMAL = {
8425    audio: {
8426      ListClass: AudioTrackList,
8427      TrackClass: AudioTrack,
8428      capitalName: 'Audio'
8429    },
8430    video: {
8431      ListClass: VideoTrackList,
8432      TrackClass: VideoTrack,
8433      capitalName: 'Video'
8434    },
8435    text: {
8436      ListClass: TextTrackList,
8437      TrackClass: TextTrack,
8438      capitalName: 'Text'
8439    }
8440  };
vendor: 14,149 bytes, lines 8441-8898
8441  Object.keys(NORMAL).forEach(function (type) {
8442    NORMAL[type].getterName = type + "Tracks";
8443    NORMAL[type].privateName = type + "Tracks_";
8444  });
8445  var REMOTE = {
8446    remoteText: {
8447      ListClass: TextTrackList,
8448      TrackClass: TextTrack,
8449      capitalName: 'RemoteText',
8450      getterName: 'remoteTextTracks',
8451      privateName: 'remoteTextTracks_'
8452    },
8453    remoteTextEl: {
8454      ListClass: HtmlTrackElementList,
8455      TrackClass: HTMLTrackElement,
8456      capitalName: 'RemoteTextTrackEls',
8457      getterName: 'remoteTextTrackEls',
8458      privateName: 'remoteTextTrackEls_'
8459    }
8460  };
8461  var ALL = mergeOptions(NORMAL, REMOTE);
8462  REMOTE.names = Object.keys(REMOTE);
8463  NORMAL.names = Object.keys(NORMAL);
8464  ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
8465
8466  /**
8467   * Copyright 2013 vtt.js Contributors
8468   *
8469   * Licensed under the Apache License, Version 2.0 (the "License");
8470   * you may not use this file except in compliance with the License.
8471   * You may obtain a copy of the License at
8472   *
8473   *   http://www.apache.org/licenses/LICENSE-2.0
8474   *
8475   * Unless required by applicable law or agreed to in writing, software
8476   * distributed under the License is distributed on an "AS IS" BASIS,
8477   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8478   * See the License for the specific language governing permissions and
8479   * limitations under the License.
8480   */
8481
8482  /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
8483
8484  /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
8485  var _objCreate = Object.create || function () {
8486    function F() {}
8487
8488    return function (o) {
8489      if (arguments.length !== 1) {
8490        throw new Error('Object.create shim only accepts one parameter.');
8491      }
8492
8493      F.prototype = o;
8494      return new F();
8495    };
8496  }(); // Creates a new ParserError object from an errorData object. The errorData
8497  // object should have default code and message properties. The default message
8498  // property can be overriden by passing in a message parameter.
8499  // See ParsingError.Errors below for acceptable errors.
8500
8501
8502  function ParsingError(errorData, message) {
8503    this.name = "ParsingError";
8504    this.code = errorData.code;
8505    this.message = message || errorData.message;
8506  }
8507
8508  ParsingError.prototype = _objCreate(Error.prototype);
8509  ParsingError.prototype.constructor = ParsingError; // ParsingError metadata for acceptable ParsingErrors.
8510
8511  ParsingError.Errors = {
8512    BadSignature: {
8513      code: 0,
8514      message: "Malformed WebVTT signature."
8515    },
8516    BadTimeStamp: {
8517      code: 1,
8518      message: "Malformed time stamp."
8519    }
8520  }; // Try to parse input as a time stamp.
8521
8522  function parseTimeStamp(input) {
8523    function computeSeconds(h, m, s, f) {
8524      return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
8525    }
8526
8527    var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/);
8528
8529    if (!m) {
8530      return null;
8531    }
8532
8533    if (m[3]) {
8534      // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
8535      return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
8536    } else if (m[1] > 59) {
8537      // Timestamp takes the form of [hours]:[minutes].[milliseconds]
8538      // First position is hours as it's over 59.
8539      return computeSeconds(m[1], m[2], 0, m[4]);
8540    } else {
8541      // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
8542      return computeSeconds(0, m[1], m[2], m[4]);
8543    }
8544  } // A settings object holds key/value pairs and will ignore anything but the first
8545  // assignment to a specific key.
8546
8547
8548  function Settings() {
8549    this.values = _objCreate(null);
8550  }
8551
8552  Settings.prototype = {
8553    // Only accept the first assignment to any key.
8554    set: function set(k, v) {
8555      if (!this.get(k) && v !== "") {
8556        this.values[k] = v;
8557      }
8558    },
8559    // Return the value for a key, or a default value.
8560    // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
8561    // a number of possible default values as properties where 'defaultKey' is
8562    // the key of the property that will be chosen; otherwise it's assumed to be
8563    // a single value.
8564    get: function get(k, dflt, defaultKey) {
8565      if (defaultKey) {
8566        return this.has(k) ? this.values[k] : dflt[defaultKey];
8567      }
8568
8569      return this.has(k) ? this.values[k] : dflt;
8570    },
8571    // Check whether we have a value for a key.
8572    has: function has(k) {
8573      return k in this.values;
8574    },
8575    // Accept a setting if its one of the given alternatives.
8576    alt: function alt(k, v, a) {
8577      for (var n = 0; n < a.length; ++n) {
8578        if (v === a[n]) {
8579          this.set(k, v);
8580          break;
8581        }
8582      }
8583    },
8584    // Accept a setting if its a valid (signed) integer.
8585    integer: function integer(k, v) {
8586      if (/^-?\d+$/.test(v)) {
8587        // integer
8588        this.set(k, parseInt(v, 10));
8589      }
8590    },
8591    // Accept a setting if its a valid percentage.
8592    percent: function percent(k, v) {
8593      var m;
8594
8595      if (m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) {
8596        v = parseFloat(v);
8597
8598        if (v >= 0 && v <= 100) {
8599          this.set(k, v);
8600          return true;
8601        }
8602      }
8603
8604      return false;
8605    }
8606  }; // Helper function to parse input into groups separated by 'groupDelim', and
8607  // interprete each group as a key/value pair separated by 'keyValueDelim'.
8608
8609  function parseOptions(input, callback, keyValueDelim, groupDelim) {
8610    var groups = groupDelim ? input.split(groupDelim) : [input];
8611
8612    for (var i in groups) {
8613      if (typeof groups[i] !== "string") {
8614        continue;
8615      }
8616
8617      var kv = groups[i].split(keyValueDelim);
8618
8619      if (kv.length !== 2) {
8620        continue;
8621      }
8622
8623      var k = kv[0];
8624      var v = kv[1];
8625      callback(k, v);
8626    }
8627  }
8628
8629  function parseCue(input, cue, regionList) {
8630    // Remember the original input if we need to throw an error.
8631    var oInput = input; // 4.1 WebVTT timestamp
8632
8633    function consumeTimeStamp() {
8634      var ts = parseTimeStamp(input);
8635
8636      if (ts === null) {
8637        throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput);
8638      } // Remove time stamp from input.
8639
8640
8641      input = input.replace(/^[^\sa-zA-Z-]+/, "");
8642      return ts;
8643    } // 4.4.2 WebVTT cue settings
8644
8645
8646    function consumeCueSettings(input, cue) {
8647      var settings = new Settings();
8648      parseOptions(input, function (k, v) {
8649        switch (k) {
8650          case "region":
8651            // Find the last region we parsed with the same region id.
8652            for (var i = regionList.length - 1; i >= 0; i--) {
8653              if (regionList[i].id === v) {
8654                settings.set(k, regionList[i].region);
8655                break;
8656              }
8657            }
8658
8659            break;
8660
8661          case "vertical":
8662            settings.alt(k, v, ["rl", "lr"]);
8663            break;
8664
8665          case "line":
8666            var vals = v.split(","),
8667                vals0 = vals[0];
8668            settings.integer(k, vals0);
8669            settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
8670            settings.alt(k, vals0, ["auto"]);
8671
8672            if (vals.length === 2) {
8673              settings.alt("lineAlign", vals[1], ["start", "middle", "end"]);
8674            }
8675
8676            break;
8677
8678          case "position":
8679            vals = v.split(",");
8680            settings.percent(k, vals[0]);
8681
8682            if (vals.length === 2) {
8683              settings.alt("positionAlign", vals[1], ["start", "middle", "end"]);
8684            }
8685
8686            break;
8687
8688          case "size":
8689            settings.percent(k, v);
8690            break;
8691
8692          case "align":
8693            settings.alt(k, v, ["start", "middle", "end", "left", "right"]);
8694            break;
8695        }
8696      }, /:/, /\s/); // Apply default values for any missing fields.
8697
8698      cue.region = settings.get("region", null);
8699      cue.vertical = settings.get("vertical", "");
8700      cue.line = settings.get("line", "auto");
8701      cue.lineAlign = settings.get("lineAlign", "start");
8702      cue.snapToLines = settings.get("snapToLines", true);
8703      cue.size = settings.get("size", 100);
8704      cue.align = settings.get("align", "middle");
8705      cue.position = settings.get("position", {
8706        start: 0,
8707        left: 0,
8708        middle: 50,
8709        end: 100,
8710        right: 100
8711      }, cue.align);
8712      cue.positionAlign = settings.get("positionAlign", {
8713        start: "start",
8714        left: "start",
8715        middle: "middle",
8716        end: "end",
8717        right: "end"
8718      }, cue.align);
8719    }
8720
8721    function skipWhitespace() {
8722      input = input.replace(/^\s+/, "");
8723    } // 4.1 WebVTT cue timings.
8724
8725
8726    skipWhitespace();
8727    cue.startTime = consumeTimeStamp(); // (1) collect cue start time
8728
8729    skipWhitespace();
8730
8731    if (input.substr(0, 3) !== "-->") {
8732      // (3) next characters must match "-->"
8733      throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput);
8734    }
8735
8736    input = input.substr(3);
8737    skipWhitespace();
8738    cue.endTime = consumeTimeStamp(); // (5) collect cue end time
8739    // 4.1 WebVTT cue settings list.
8740
8741    skipWhitespace();
8742    consumeCueSettings(input, cue);
8743  }
8744
8745  var ESCAPE = {
8746    "&amp;": "&",
8747    "&lt;": "<",
8748    "&gt;": ">",
8749    "&lrm;": "\u200E",
8750    "&rlm;": "\u200F",
8751    "&nbsp;": "\xA0"
8752  };
8753  var TAG_NAME = {
8754    c: "span",
8755    i: "i",
8756    b: "b",
8757    u: "u",
8758    ruby: "ruby",
8759    rt: "rt",
8760    v: "span",
8761    lang: "span"
8762  };
8763  var TAG_ANNOTATION = {
8764    v: "title",
8765    lang: "lang"
8766  };
8767  var NEEDS_PARENT = {
8768    rt: "ruby"
8769  }; // Parse content into a document fragment.
8770
8771  function parseContent(window, input) {
8772    function nextToken() {
8773      // Check for end-of-string.
8774      if (!input) {
8775        return null;
8776      } // Consume 'n' characters from the input.
8777
8778
8779      function consume(result) {
8780        input = input.substr(result.length);
8781        return result;
8782      }
8783
8784      var m = input.match(/^([^<]*)(<[^>]*>?)?/); // If there is some text before the next tag, return it, otherwise return
8785      // the tag.
8786
8787      return consume(m[1] ? m[1] : m[2]);
8788    } // Unescape a string 's'.
8789
8790
8791    function unescape1(e) {
8792      return ESCAPE[e];
8793    }
8794
8795    function unescape(s) {
8796      while (m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/)) {
8797        s = s.replace(m[0], unescape1);
8798      }
8799
8800      return s;
8801    }
8802
8803    function shouldAdd(current, element) {
8804      return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName;
8805    } // Create an element for this tag.
8806
8807
8808    function createElement(type, annotation) {
8809      var tagName = TAG_NAME[type];
8810
8811      if (!tagName) {
8812        return null;
8813      }
8814
8815      var element = window.document.createElement(tagName);
8816      element.localName = tagName;
8817      var name = TAG_ANNOTATION[type];
8818
8819      if (name && annotation) {
8820        element[name] = annotation.trim();
8821      }
8822
8823      return element;
8824    }
8825
8826    var rootDiv = window.document.createElement("div"),
8827        current = rootDiv,
8828        t,
8829        tagStack = [];
8830
8831    while ((t = nextToken()) !== null) {
8832      if (t[0] === '<') {
8833        if (t[1] === "/") {
8834          // If the closing tag matches, move back up to the parent node.
8835          if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
8836            tagStack.pop();
8837            current = current.parentNode;
8838          } // Otherwise just ignore the end tag.
8839
8840
8841          continue;
8842        }
8843
8844        var ts = parseTimeStamp(t.substr(1, t.length - 2));
8845        var node;
8846
8847        if (ts) {
8848          // Timestamps are lead nodes as well.
8849          node = window.document.createProcessingInstruction("timestamp", ts);
8850          current.appendChild(node);
8851          continue;
8852        }
8853
8854        var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); // If we can't parse the tag, skip to the next tag.
8855
8856        if (!m) {
8857          continue;
8858        } // Try to construct an element, and ignore the tag if we couldn't.
8859
8860
8861        node = createElement(m[1], m[3]);
8862
8863        if (!node) {
8864          continue;
8865        } // Determine if the tag should be added based on the context of where it
8866        // is placed in the cuetext.
8867
8868
8869        if (!shouldAdd(current, node)) {
8870          continue;
8871        } // Set the class list (as a list of classes, separated by space).
8872
8873
8874        if (m[2]) {
8875          node.className = m[2].substr(1).replace('.', ' ');
8876        } // Append the node to the current node, and enter the scope of the new
8877        // node.
8878
8879
8880        tagStack.push(m[1]);
8881        current.appendChild(node);
8882        current = node;
8883        continue;
8884      } // Text nodes are leaf nodes.
8885
8886
8887      current.appendChild(window.document.createTextNode(unescape(t)));
8888    }
8889
8890    return rootDiv;
8891  } // This is a list of all the Unicode characters that have a strong
8892  // right-to-left category. What this means is that these characters are
8893  // written right-to-left for sure. It was generated by pulling all the strong
8894  // right-to-left characters out of the Unicode data table. That table can
8895  // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
8896
8897
8898  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, 
vendor: 18,632 bytes, lines 8898-9439
88980x1093f], [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]];
8899
8900  function isStrongRTLChar(charCode) {
8901    for (var i = 0; i < strongRTLRanges.length; i++) {
8902      var currentRange = strongRTLRanges[i];
8903
8904      if (charCode >= currentRange[0] && charCode <= currentRange[1]) {
8905        return true;
8906      }
8907    }
8908
8909    return false;
8910  }
8911
8912  function determineBidi(cueDiv) {
8913    var nodeStack = [],
8914        text = "",
8915        charCode;
8916
8917    if (!cueDiv || !cueDiv.childNodes) {
8918      return "ltr";
8919    }
8920
8921    function pushNodes(nodeStack, node) {
8922      for (var i = node.childNodes.length - 1; i >= 0; i--) {
8923        nodeStack.push(node.childNodes[i]);
8924      }
8925    }
8926
8927    function nextTextNode(nodeStack) {
8928      if (!nodeStack || !nodeStack.length) {
8929        return null;
8930      }
8931
8932      var node = nodeStack.pop(),
8933          text = node.textContent || node.innerText;
8934
8935      if (text) {
8936        // TODO: This should match all unicode type B characters (paragraph
8937        // separator characters). See issue #115.
8938        var m = text.match(/^.*(\n|\r)/);
8939
8940        if (m) {
8941          nodeStack.length = 0;
8942          return m[0];
8943        }
8944
8945        return text;
8946      }
8947
8948      if (node.tagName === "ruby") {
8949        return nextTextNode(nodeStack);
8950      }
8951
8952      if (node.childNodes) {
8953        pushNodes(nodeStack, node);
8954        return nextTextNode(nodeStack);
8955      }
8956    }
8957
8958    pushNodes(nodeStack, cueDiv);
8959
8960    while (text = nextTextNode(nodeStack)) {
8961      for (var i = 0; i < text.length; i++) {
8962        charCode = text.charCodeAt(i);
8963
8964        if (isStrongRTLChar(charCode)) {
8965          return "rtl";
8966        }
8967      }
8968    }
8969
8970    return "ltr";
8971  }
8972
8973  function computeLinePos(cue) {
8974    if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) {
8975      return cue.line;
8976    }
8977
8978    if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) {
8979      return -1;
8980    }
8981
8982    var track = cue.track,
8983        trackList = track.textTrackList,
8984        count = 0;
8985
8986    for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
8987      if (trackList[i].mode === "showing") {
8988        count++;
8989      }
8990    }
8991
8992    return ++count * -1;
8993  }
8994
8995  function StyleBox() {} // Apply styles to a div. If there is no div passed then it defaults to the
8996  // div on 'this'.
8997
8998
8999  StyleBox.prototype.applyStyles = function (styles, div) {
9000    div = div || this.div;
9001
9002    for (var prop in styles) {
9003      if (styles.hasOwnProperty(prop)) {
9004        div.style[prop] = styles[prop];
9005      }
9006    }
9007  };
9008
9009  StyleBox.prototype.formatStyle = function (val, unit) {
9010    return val === 0 ? 0 : val + unit;
9011  }; // Constructs the computed display state of the cue (a div). Places the div
9012  // into the overlay which should be a block level element (usually a div).
9013
9014
9015  function CueStyleBox(window, cue, styleOptions) {
9016    StyleBox.call(this);
9017    this.cue = cue; // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
9018    // have inline positioning and will function as the cue background box.
9019
9020    this.cueDiv = parseContent(window, cue.text);
9021    var styles = {
9022      color: "rgba(255, 255, 255, 1)",
9023      backgroundColor: "rgba(0, 0, 0, 0.8)",
9024      position: "relative",
9025      left: 0,
9026      right: 0,
9027      top: 0,
9028      bottom: 0,
9029      display: "inline",
9030      writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
9031      unicodeBidi: "plaintext"
9032    };
9033    this.applyStyles(styles, this.cueDiv); // Create an absolutely positioned div that will be used to position the cue
9034    // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
9035    // mirrors of them except "middle" which is "center" in CSS.
9036
9037    this.div = window.document.createElement("div");
9038    styles = {
9039      direction: determineBidi(this.cueDiv),
9040      writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
9041      unicodeBidi: "plaintext",
9042      textAlign: cue.align === "middle" ? "center" : cue.align,
9043      font: styleOptions.font,
9044      whiteSpace: "pre-line",
9045      position: "absolute"
9046    };
9047    this.applyStyles(styles);
9048    this.div.appendChild(this.cueDiv); // Calculate the distance from the reference edge of the viewport to the text
9049    // position of the cue box. The reference edge will be resolved later when
9050    // the box orientation styles are applied.
9051
9052    var textPos = 0;
9053
9054    switch (cue.positionAlign) {
9055      case "start":
9056        textPos = cue.position;
9057        break;
9058
9059      case "middle":
9060        textPos = cue.position - cue.size / 2;
9061        break;
9062
9063      case "end":
9064        textPos = cue.position - cue.size;
9065        break;
9066    } // Horizontal box orientation; textPos is the distance from the left edge of the
9067    // area to the left edge of the box and cue.size is the distance extending to
9068    // the right from there.
9069
9070
9071    if (cue.vertical === "") {
9072      this.applyStyles({
9073        left: this.formatStyle(textPos, "%"),
9074        width: this.formatStyle(cue.size, "%")
9075      }); // Vertical box orientation; textPos is the distance from the top edge of the
9076      // area to the top edge of the box and cue.size is the height extending
9077      // downwards from there.
9078    } else {
9079      this.applyStyles({
9080        top: this.formatStyle(textPos, "%"),
9081        height: this.formatStyle(cue.size, "%")
9082      });
9083    }
9084
9085    this.move = function (box) {
9086      this.applyStyles({
9087        top: this.formatStyle(box.top, "px"),
9088        bottom: this.formatStyle(box.bottom, "px"),
9089        left: this.formatStyle(box.left, "px"),
9090        right: this.formatStyle(box.right, "px"),
9091        height: this.formatStyle(box.height, "px"),
9092        width: this.formatStyle(box.width, "px")
9093      });
9094    };
9095  }
9096
9097  CueStyleBox.prototype = _objCreate(StyleBox.prototype);
9098  CueStyleBox.prototype.constructor = CueStyleBox; // Represents the co-ordinates of an Element in a way that we can easily
9099  // compute things with such as if it overlaps or intersects with another Element.
9100  // Can initialize it with either a StyleBox or another BoxPosition.
9101
9102  function BoxPosition(obj) {
9103    // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
9104    // was passed in and we need to copy the results of 'getBoundingClientRect'
9105    // as the object returned is readonly. All co-ordinate values are in reference
9106    // to the viewport origin (top left).
9107    var lh, height, width, top;
9108
9109    if (obj.div) {
9110      height = obj.div.offsetHeight;
9111      width = obj.div.offsetWidth;
9112      top = obj.div.offsetTop;
9113      var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects();
9114      obj = obj.div.getBoundingClientRect(); // In certain cases the outter div will be slightly larger then the sum of
9115      // the inner div's lines. This could be due to bold text, etc, on some platforms.
9116      // In this case we should get the average line height and use that. This will
9117      // result in the desired behaviour.
9118
9119      lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0;
9120    }
9121
9122    this.left = obj.left;
9123    this.right = obj.right;
9124    this.top = obj.top || top;
9125    this.height = obj.height || height;
9126    this.bottom = obj.bottom || top + (obj.height || height);
9127    this.width = obj.width || width;
9128    this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
9129  } // Move the box along a particular axis. Optionally pass in an amount to move
9130  // the box. If no amount is passed then the default is the line height of the
9131  // box.
9132
9133
9134  BoxPosition.prototype.move = function (axis, toMove) {
9135    toMove = toMove !== undefined ? toMove : this.lineHeight;
9136
9137    switch (axis) {
9138      case "+x":
9139        this.left += toMove;
9140        this.right += toMove;
9141        break;
9142
9143      case "-x":
9144        this.left -= toMove;
9145        this.right -= toMove;
9146        break;
9147
9148      case "+y":
9149        this.top += toMove;
9150        this.bottom += toMove;
9151        break;
9152
9153      case "-y":
9154        this.top -= toMove;
9155        this.bottom -= toMove;
9156        break;
9157    }
9158  }; // Check if this box overlaps another box, b2.
9159
9160
9161  BoxPosition.prototype.overlaps = function (b2) {
9162    return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top;
9163  }; // Check if this box overlaps any other boxes in boxes.
9164
9165
9166  BoxPosition.prototype.overlapsAny = function (boxes) {
9167    for (var i = 0; i < boxes.length; i++) {
9168      if (this.overlaps(boxes[i])) {
9169        return true;
9170      }
9171    }
9172
9173    return false;
9174  }; // Check if this box is within another box.
9175
9176
9177  BoxPosition.prototype.within = function (container) {
9178    return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right;
9179  }; // Check if this box is entirely within the container or it is overlapping
9180  // on the edge opposite of the axis direction passed. For example, if "+x" is
9181  // passed and the box is overlapping on the left edge of the container, then
9182  // return true.
9183
9184
9185  BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) {
9186    switch (axis) {
9187      case "+x":
9188        return this.left < container.left;
9189
9190      case "-x":
9191        return this.right > container.right;
9192
9193      case "+y":
9194        return this.top < container.top;
9195
9196      case "-y":
9197        return this.bottom > container.bottom;
9198    }
9199  }; // Find the percentage of the area that this box is overlapping with another
9200  // box.
9201
9202
9203  BoxPosition.prototype.intersectPercentage = function (b2) {
9204    var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
9205        y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
9206        intersectArea = x * y;
9207    return intersectArea / (this.height * this.width);
9208  }; // Convert the positions from this box to CSS compatible positions using
9209  // the reference container's positions. This has to be done because this
9210  // box's positions are in reference to the viewport origin, whereas, CSS
9211  // values are in referecne to their respective edges.
9212
9213
9214  BoxPosition.prototype.toCSSCompatValues = function (reference) {
9215    return {
9216      top: this.top - reference.top,
9217      bottom: reference.bottom - this.bottom,
9218      left: this.left - reference.left,
9219      right: reference.right - this.right,
9220      height: this.height,
9221      width: this.width
9222    };
9223  }; // Get an object that represents the box's position without anything extra.
9224  // Can pass a StyleBox, HTMLElement, or another BoxPositon.
9225
9226
9227  BoxPosition.getSimpleBoxPosition = function (obj) {
9228    var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
9229    var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
9230    var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;
9231    obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj;
9232    var ret = {
9233      left: obj.left,
9234      right: obj.right,
9235      top: obj.top || top,
9236      height: obj.height || height,
9237      bottom: obj.bottom || top + (obj.height || height),
9238      width: obj.width || width
9239    };
9240    return ret;
9241  }; // Move a StyleBox to its specified, or next best, position. The containerBox
9242  // is the box that contains the StyleBox, such as a div. boxPositions are
9243  // a list of other boxes that the styleBox can't overlap with.
9244
9245
9246  function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
9247    // Find the best position for a cue box, b, on the video. The axis parameter
9248    // is a list of axis, the order of which, it will move the box along. For example:
9249    // Passing ["+x", "-x"] will move the box first along the x axis in the positive
9250    // direction. If it doesn't find a good position for it there it will then move
9251    // it along the x axis in the negative direction.
9252    function findBestPosition(b, axis) {
9253      var bestPosition,
9254          specifiedPosition = new BoxPosition(b),
9255          percentage = 1; // Highest possible so the first thing we get is better.
9256
9257      for (var i = 0; i < axis.length; i++) {
9258        while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) {
9259          b.move(axis[i]);
9260        } // We found a spot where we aren't overlapping anything. This is our
9261        // best position.
9262
9263
9264        if (b.within(containerBox)) {
9265          return b;
9266        }
9267
9268        var p = b.intersectPercentage(containerBox); // If we're outside the container box less then we were on our last try
9269        // then remember this position as the best position.
9270
9271        if (percentage > p) {
9272          bestPosition = new BoxPosition(b);
9273          percentage = p;
9274        } // Reset the box position to the specified position.
9275
9276
9277        b = new BoxPosition(specifiedPosition);
9278      }
9279
9280      return bestPosition || specifiedPosition;
9281    }
9282
9283    var boxPosition = new BoxPosition(styleBox),
9284        cue = styleBox.cue,
9285        linePos = computeLinePos(cue),
9286        axis = []; // If we have a line number to align the cue to.
9287
9288    if (cue.snapToLines) {
9289      var size;
9290
9291      switch (cue.vertical) {
9292        case "":
9293          axis = ["+y", "-y"];
9294          size = "height";
9295          break;
9296
9297        case "rl":
9298          axis = ["+x", "-x"];
9299          size = "width";
9300          break;
9301
9302        case "lr":
9303          axis = ["-x", "+x"];
9304          size = "width";
9305          break;
9306      }
9307
9308      var step = boxPosition.lineHeight,
9309          position = step * Math.round(linePos),
9310          maxPosition = containerBox[size] + step,
9311          initialAxis = axis[0]; // If the specified intial position is greater then the max position then
9312      // clamp the box to the amount of steps it would take for the box to
9313      // reach the max position.
9314
9315      if (Math.abs(position) > maxPosition) {
9316        position = position < 0 ? -1 : 1;
9317        position *= Math.ceil(maxPosition / step) * step;
9318      } // If computed line position returns negative then line numbers are
9319      // relative to the bottom of the video instead of the top. Therefore, we
9320      // need to increase our initial position by the length or width of the
9321      // video, depending on the writing direction, and reverse our axis directions.
9322
9323
9324      if (linePos < 0) {
9325        position += cue.vertical === "" ? containerBox.height : containerBox.width;
9326        axis = axis.reverse();
9327      } // Move the box to the specified position. This may not be its best
9328      // position.
9329
9330
9331      boxPosition.move(initialAxis, position);
9332    } else {
9333      // If we have a percentage line value for the cue.
9334      var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100;
9335
9336      switch (cue.lineAlign) {
9337        case "middle":
9338          linePos -= calculatedPercentage / 2;
9339          break;
9340
9341        case "end":
9342          linePos -= calculatedPercentage;
9343          break;
9344      } // Apply initial line position to the cue box.
9345
9346
9347      switch (cue.vertical) {
9348        case "":
9349          styleBox.applyStyles({
9350            top: styleBox.formatStyle(linePos, "%")
9351          });
9352          break;
9353
9354        case "rl":
9355          styleBox.applyStyles({
9356            left: styleBox.formatStyle(linePos, "%")
9357          });
9358          break;
9359
9360        case "lr":
9361          styleBox.applyStyles({
9362            right: styleBox.formatStyle(linePos, "%")
9363          });
9364          break;
9365      }
9366
9367      axis = ["+y", "-x", "+x", "-y"]; // Get the box position again after we've applied the specified positioning
9368      // to it.
9369
9370      boxPosition = new BoxPosition(styleBox);
9371    }
9372
9373    var bestPosition = findBestPosition(boxPosition, axis);
9374    styleBox.move(bestPosition.toCSSCompatValues(containerBox));
9375  }
9376
9377  function WebVTT$1() {} // Nothing
9378  // Helper to allow strings to be decoded instead of the default binary utf8 data.
9379
9380
9381  WebVTT$1.StringDecoder = function () {
9382    return {
9383      decode: function decode(data) {
9384        if (!data) {
9385          return "";
9386        }
9387
9388        if (typeof data !== "string") {
9389          throw new Error("Error - expected string data.");
9390        }
9391
9392        return decodeURIComponent(encodeURIComponent(data));
9393      }
9394    };
9395  };
9396
9397  WebVTT$1.convertCueToDOMTree = function (window, cuetext) {
9398    if (!window || !cuetext) {
9399      return null;
9400    }
9401
9402    return parseContent(window, cuetext);
9403  };
9404
9405  var FONT_SIZE_PERCENT = 0.05;
9406  var FONT_STYLE = "sans-serif";
9407  var CUE_BACKGROUND_PADDING = "1.5%"; // Runs the processing model over the cues and regions passed to it.
9408  // @param overlay A block level element (usually a div) that the computed cues
9409  //                and regions will be placed into.
9410
9411  WebVTT$1.processCues = function (window, cues, overlay) {
9412    if (!window || !cues || !overlay) {
9413      return null;
9414    } // Remove all previous children.
9415
9416
9417    while (overlay.firstChild) {
9418      overlay.removeChild(overlay.firstChild);
9419    }
9420
9421    var paddedOverlay = window.document.createElement("div");
9422    paddedOverlay.style.position = "absolute";
9423    paddedOverlay.style.left = "0";
9424    paddedOverlay.style.right = "0";
9425    paddedOverlay.style.top = "0";
9426    paddedOverlay.style.bottom = "0";
9427    paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
9428    overlay.appendChild(paddedOverlay); // Determine if we need to compute the display states of the cues. This could
9429    // be the case if a cue's state has been changed since the last computation or
9430    // if it has not been computed yet.
9431
9432    function shouldCompute(cues) {
9433      for (var i = 0; i < cues.length; i++) {
9434        if (cues[i].hasBeenReset || !cues[i].displayState) {
9435          return true;
9436        }
9437      }
9438
9439      return false;
vendor: 10,534 bytes, lines 9440-9790
9440    } // We don't need to recompute the cues' display states. Just reuse them.
9441
9442
9443    if (!shouldCompute(cues)) {
9444      for (var i = 0; i < cues.length; i++) {
9445        paddedOverlay.appendChild(cues[i].displayState);
9446      }
9447
9448      return;
9449    }
9450
9451    var boxPositions = [],
9452        containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
9453        fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
9454    var styleOptions = {
9455      font: fontSize + "px " + FONT_STYLE
9456    };
9457
9458    (function () {
9459      var styleBox, cue;
9460
9461      for (var i = 0; i < cues.length; i++) {
9462        cue = cues[i]; // Compute the intial position and styles of the cue div.
9463
9464        styleBox = new CueStyleBox(window, cue, styleOptions);
9465        paddedOverlay.appendChild(styleBox.div); // Move the cue div to it's correct line position.
9466
9467        moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); // Remember the computed div so that we don't have to recompute it later
9468        // if we don't have too.
9469
9470        cue.displayState = styleBox.div;
9471        boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
9472      }
9473    })();
9474  };
9475
9476  WebVTT$1.Parser = function (window, vttjs, decoder) {
9477    if (!decoder) {
9478      decoder = vttjs;
9479      vttjs = {};
9480    }
9481
9482    if (!vttjs) {
9483      vttjs = {};
9484    }
9485
9486    this.window = window;
9487    this.vttjs = vttjs;
9488    this.state = "INITIAL";
9489    this.buffer = "";
9490    this.decoder = decoder || new TextDecoder("utf8");
9491    this.regionList = [];
9492  };
9493
9494  WebVTT$1.Parser.prototype = {
9495    // If the error is a ParsingError then report it to the consumer if
9496    // possible. If it's not a ParsingError then throw it like normal.
9497    reportOrThrowError: function reportOrThrowError(e) {
9498      if (e instanceof ParsingError) {
9499        this.onparsingerror && this.onparsingerror(e);
9500      } else {
9501        throw e;
9502      }
9503    },
9504    parse: function parse(data) {
9505      var self = this; // If there is no data then we won't decode it, but will just try to parse
9506      // whatever is in buffer already. This may occur in circumstances, for
9507      // example when flush() is called.
9508
9509      if (data) {
9510        // Try to decode the data that we received.
9511        self.buffer += self.decoder.decode(data, {
9512          stream: true
9513        });
9514      }
9515
9516      function collectNextLine() {
9517        var buffer = self.buffer;
9518        var pos = 0;
9519
9520        while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
9521          ++pos;
9522        }
9523
9524        var line = buffer.substr(0, pos); // Advance the buffer early in case we fail below.
9525
9526        if (buffer[pos] === '\r') {
9527          ++pos;
9528        }
9529
9530        if (buffer[pos] === '\n') {
9531          ++pos;
9532        }
9533
9534        self.buffer = buffer.substr(pos);
9535        return line;
9536      } // 3.4 WebVTT region and WebVTT region settings syntax
9537
9538
9539      function parseRegion(input) {
9540        var settings = new Settings();
9541        parseOptions(input, function (k, v) {
9542          switch (k) {
9543            case "id":
9544              settings.set(k, v);
9545              break;
9546
9547            case "width":
9548              settings.percent(k, v);
9549              break;
9550
9551            case "lines":
9552              settings.integer(k, v);
9553              break;
9554
9555            case "regionanchor":
9556            case "viewportanchor":
9557              var xy = v.split(',');
9558
9559              if (xy.length !== 2) {
9560                break;
9561              } // We have to make sure both x and y parse, so use a temporary
9562              // settings object here.
9563
9564
9565              var anchor = new Settings();
9566              anchor.percent("x", xy[0]);
9567              anchor.percent("y", xy[1]);
9568
9569              if (!anchor.has("x") || !anchor.has("y")) {
9570                break;
9571              }
9572
9573              settings.set(k + "X", anchor.get("x"));
9574              settings.set(k + "Y", anchor.get("y"));
9575              break;
9576
9577            case "scroll":
9578              settings.alt(k, v, ["up"]);
9579              break;
9580          }
9581        }, /=/, /\s/); // Create the region, using default values for any values that were not
9582        // specified.
9583
9584        if (settings.has("id")) {
9585          var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
9586          region.width = settings.get("width", 100);
9587          region.lines = settings.get("lines", 3);
9588          region.regionAnchorX = settings.get("regionanchorX", 0);
9589          region.regionAnchorY = settings.get("regionanchorY", 100);
9590          region.viewportAnchorX = settings.get("viewportanchorX", 0);
9591          region.viewportAnchorY = settings.get("viewportanchorY", 100);
9592          region.scroll = settings.get("scroll", ""); // Register the region.
9593
9594          self.onregion && self.onregion(region); // Remember the VTTRegion for later in case we parse any VTTCues that
9595          // reference it.
9596
9597          self.regionList.push({
9598            id: settings.get("id"),
9599            region: region
9600          });
9601        }
9602      } // draft-pantos-http-live-streaming-20
9603      // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5
9604      // 3.5 WebVTT
9605
9606
9607      function parseTimestampMap(input) {
9608        var settings = new Settings();
9609        parseOptions(input, function (k, v) {
9610          switch (k) {
9611            case "MPEGT":
9612              settings.integer(k + 'S', v);
9613              break;
9614
9615            case "LOCA":
9616              settings.set(k + 'L', parseTimeStamp(v));
9617              break;
9618          }
9619        }, /[^\d]:/, /,/);
9620        self.ontimestampmap && self.ontimestampmap({
9621          "MPEGTS": settings.get("MPEGTS"),
9622          "LOCAL": settings.get("LOCAL")
9623        });
9624      } // 3.2 WebVTT metadata header syntax
9625
9626
9627      function parseHeader(input) {
9628        if (input.match(/X-TIMESTAMP-MAP/)) {
9629          // This line contains HLS X-TIMESTAMP-MAP metadata
9630          parseOptions(input, function (k, v) {
9631            switch (k) {
9632              case "X-TIMESTAMP-MAP":
9633                parseTimestampMap(v);
9634                break;
9635            }
9636          }, /=/);
9637        } else {
9638          parseOptions(input, function (k, v) {
9639            switch (k) {
9640              case "Region":
9641                // 3.3 WebVTT region metadata header syntax
9642                parseRegion(v);
9643                break;
9644            }
9645          }, /:/);
9646        }
9647      } // 5.1 WebVTT file parsing.
9648
9649
9650      try {
9651        var line;
9652
9653        if (self.state === "INITIAL") {
9654          // We can't start parsing until we have the first line.
9655          if (!/\r\n|\n/.test(self.buffer)) {
9656            return this;
9657          }
9658
9659          line = collectNextLine();
9660          var m = line.match(/^WEBVTT([ \t].*)?$/);
9661
9662          if (!m || !m[0]) {
9663            throw new ParsingError(ParsingError.Errors.BadSignature);
9664          }
9665
9666          self.state = "HEADER";
9667        }
9668
9669        var alreadyCollectedLine = false;
9670
9671        while (self.buffer) {
9672          // We can't parse a line until we have the full line.
9673          if (!/\r\n|\n/.test(self.buffer)) {
9674            return this;
9675          }
9676
9677          if (!alreadyCollectedLine) {
9678            line = collectNextLine();
9679          } else {
9680            alreadyCollectedLine = false;
9681          }
9682
9683          switch (self.state) {
9684            case "HEADER":
9685              // 13-18 - Allow a header (metadata) under the WEBVTT line.
9686              if (/:/.test(line)) {
9687                parseHeader(line);
9688              } else if (!line) {
9689                // An empty line terminates the header and starts the body (cues).
9690                self.state = "ID";
9691              }
9692
9693              continue;
9694
9695            case "NOTE":
9696              // Ignore NOTE blocks.
9697              if (!line) {
9698                self.state = "ID";
9699              }
9700
9701              continue;
9702
9703            case "ID":
9704              // Check for the start of NOTE blocks.
9705              if (/^NOTE($|[ \t])/.test(line)) {
9706                self.state = "NOTE";
9707                break;
9708              } // 19-29 - Allow any number of line terminators, then initialize new cue values.
9709
9710
9711              if (!line) {
9712                continue;
9713              }
9714
9715              self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, "");
9716              self.state = "CUE"; // 30-39 - Check if self line contains an optional identifier or timing data.
9717
9718              if (line.indexOf("-->") === -1) {
9719                self.cue.id = line;
9720                continue;
9721              }
9722
9723            // Process line as start of a cue.
9724
9725            /*falls through*/
9726
9727            case "CUE":
9728              // 40 - Collect cue timings and settings.
9729              try {
9730                parseCue(line, self.cue, self.regionList);
9731              } catch (e) {
9732                self.reportOrThrowError(e); // In case of an error ignore rest of the cue.
9733
9734                self.cue = null;
9735                self.state = "BADCUE";
9736                continue;
9737              }
9738
9739              self.state = "CUETEXT";
9740              continue;
9741
9742            case "CUETEXT":
9743              var hasSubstring = line.indexOf("-->") !== -1; // 34 - If we have an empty line then report the cue.
9744              // 35 - If we have the special substring '-->' then report the cue,
9745              // but do not collect the line as we need to process the current
9746              // one as a new cue.
9747
9748              if (!line || hasSubstring && (alreadyCollectedLine = true)) {
9749                // We are done parsing self cue.
9750                self.oncue && self.oncue(self.cue);
9751                self.cue = null;
9752                self.state = "ID";
9753                continue;
9754              }
9755
9756              if (self.cue.text) {
9757                self.cue.text += "\n";
9758              }
9759
9760              self.cue.text += line;
9761              continue;
9762
9763            case "BADCUE":
9764              // BADCUE
9765              // 54-62 - Collect and discard the remaining cue.
9766              if (!line) {
9767                self.state = "ID";
9768              }
9769
9770              continue;
9771          }
9772        }
9773      } catch (e) {
9774        self.reportOrThrowError(e); // If we are currently parsing a cue, report what we have.
9775
9776        if (self.state === "CUETEXT" && self.cue && self.oncue) {
9777          self.oncue(self.cue);
9778        }
9779
9780        self.cue = null; // Enter BADWEBVTT state if header was not parsed correctly otherwise
9781        // another exception occurred so enter BADCUE state.
9782
9783        self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
9784      }
9785
9786      return this;
9787    },
9788    flush: function flush() {
9789      var self = this;
9790
vendor: 5,283 bytes, lines 9791-9989
9791      try {
9792        // Finish decoding the stream.
9793        self.buffer += self.decoder.decode(); // Synthesize the end of the current cue or region.
9794
9795        if (self.cue || self.state === "HEADER") {
9796          self.buffer += "\n\n";
9797          self.parse();
9798        } // If we've flushed, parsed, and we're still on the INITIAL state then
9799        // that means we don't have enough of the stream to parse the first
9800        // line.
9801
9802
9803        if (self.state === "INITIAL") {
9804          throw new ParsingError(ParsingError.Errors.BadSignature);
9805        }
9806      } catch (e) {
9807        self.reportOrThrowError(e);
9808      }
9809
9810      self.onflush && self.onflush();
9811      return this;
9812    }
9813  };
9814  var vtt = WebVTT$1;
9815
9816  /**
9817   * Copyright 2013 vtt.js Contributors
9818   *
9819   * Licensed under the Apache License, Version 2.0 (the "License");
9820   * you may not use this file except in compliance with the License.
9821   * You may obtain a copy of the License at
9822   *
9823   *   http://www.apache.org/licenses/LICENSE-2.0
9824   *
9825   * Unless required by applicable law or agreed to in writing, software
9826   * distributed under the License is distributed on an "AS IS" BASIS,
9827   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9828   * See the License for the specific language governing permissions and
9829   * limitations under the License.
9830   */
9831  var autoKeyword = "auto";
9832  var directionSetting = {
9833    "": 1,
9834    "lr": 1,
9835    "rl": 1
9836  };
9837  var alignSetting = {
9838    "start": 1,
9839    "middle": 1,
9840    "end": 1,
9841    "left": 1,
9842    "right": 1
9843  };
9844
9845  function findDirectionSetting(value) {
9846    if (typeof value !== "string") {
9847      return false;
9848    }
9849
9850    var dir = directionSetting[value.toLowerCase()];
9851    return dir ? value.toLowerCase() : false;
9852  }
9853
9854  function findAlignSetting(value) {
9855    if (typeof value !== "string") {
9856      return false;
9857    }
9858
9859    var align = alignSetting[value.toLowerCase()];
9860    return align ? value.toLowerCase() : false;
9861  }
9862
9863  function VTTCue(startTime, endTime, text) {
9864    /**
9865     * Shim implementation specific properties. These properties are not in
9866     * the spec.
9867     */
9868    // Lets us know when the VTTCue's data has changed in such a way that we need
9869    // to recompute its display state. This lets us compute its display state
9870    // lazily.
9871    this.hasBeenReset = false;
9872    /**
9873     * VTTCue and TextTrackCue properties
9874     * http://dev.w3.org/html5/webvtt/#vttcue-interface
9875     */
9876
9877    var _id = "";
9878    var _pauseOnExit = false;
9879    var _startTime = startTime;
9880    var _endTime = endTime;
9881    var _text = text;
9882    var _region = null;
9883    var _vertical = "";
9884    var _snapToLines = true;
9885    var _line = "auto";
9886    var _lineAlign = "start";
9887    var _position = 50;
9888    var _positionAlign = "middle";
9889    var _size = 50;
9890    var _align = "middle";
9891    Object.defineProperties(this, {
9892      "id": {
9893        enumerable: true,
9894        get: function get() {
9895          return _id;
9896        },
9897        set: function set(value) {
9898          _id = "" + value;
9899        }
9900      },
9901      "pauseOnExit": {
9902        enumerable: true,
9903        get: function get() {
9904          return _pauseOnExit;
9905        },
9906        set: function set(value) {
9907          _pauseOnExit = !!value;
9908        }
9909      },
9910      "startTime": {
9911        enumerable: true,
9912        get: function get() {
9913          return _startTime;
9914        },
9915        set: function set(value) {
9916          if (typeof value !== "number") {
9917            throw new TypeError("Start time must be set to a number.");
9918          }
9919
9920          _startTime = value;
9921          this.hasBeenReset = true;
9922        }
9923      },
9924      "endTime": {
9925        enumerable: true,
9926        get: function get() {
9927          return _endTime;
9928        },
9929        set: function set(value) {
9930          if (typeof value !== "number") {
9931            throw new TypeError("End time must be set to a number.");
9932          }
9933
9934          _endTime = value;
9935          this.hasBeenReset = true;
9936        }
9937      },
9938      "text": {
9939        enumerable: true,
9940        get: function get() {
9941          return _text;
9942        },
9943        set: function set(value) {
9944          _text = "" + value;
9945          this.hasBeenReset = true;
9946        }
9947      },
9948      "region": {
9949        enumerable: true,
9950        get: function get() {
9951          return _region;
9952        },
9953        set: function set(value) {
9954          _region = value;
9955          this.hasBeenReset = true;
9956        }
9957      },
9958      "vertical": {
9959        enumerable: true,
9960        get: function get() {
9961          return _vertical;
9962        },
9963        set: function set(value) {
9964          var setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string.
9965
9966          if (setting === false) {
9967            throw new SyntaxError("An invalid or illegal string was specified.");
9968          }
9969
9970          _vertical = setting;
9971          this.hasBeenReset = true;
9972        }
9973      },
9974      "snapToLines": {
9975        enumerable: true,
9976        get: function get() {
9977          return _snapToLines;
9978        },
9979        set: function set(value) {
9980          _snapToLines = !!value;
9981          this.hasBeenReset = true;
9982        }
9983      },
9984      "line": {
9985        enumerable: true,
9986        get: function get() {
9987          return _line;
9988        },
9989        set: function set(value) {
vendor: 4,419 bytes, lines 9990-10155
9990          if (typeof value !== "number" && value !== autoKeyword) {
9991            throw new SyntaxError("An invalid number or illegal string was specified.");
9992          }
9993
9994          _line = value;
9995          this.hasBeenReset = true;
9996        }
9997      },
9998      "lineAlign": {
9999        enumerable: true,
10000        get: function get() {
10001          return _lineAlign;
10002        },
10003        set: function set(value) {
10004          var setting = findAlignSetting(value);
10005
10006          if (!setting) {
10007            throw new SyntaxError("An invalid or illegal string was specified.");
10008          }
10009
10010          _lineAlign = setting;
10011          this.hasBeenReset = true;
10012        }
10013      },
10014      "position": {
10015        enumerable: true,
10016        get: function get() {
10017          return _position;
10018        },
10019        set: function set(value) {
10020          if (value < 0 || value > 100) {
10021            throw new Error("Position must be between 0 and 100.");
10022          }
10023
10024          _position = value;
10025          this.hasBeenReset = true;
10026        }
10027      },
10028      "positionAlign": {
10029        enumerable: true,
10030        get: function get() {
10031          return _positionAlign;
10032        },
10033        set: function set(value) {
10034          var setting = findAlignSetting(value);
10035
10036          if (!setting) {
10037            throw new SyntaxError("An invalid or illegal string was specified.");
10038          }
10039
10040          _positionAlign = setting;
10041          this.hasBeenReset = true;
10042        }
10043      },
10044      "size": {
10045        enumerable: true,
10046        get: function get() {
10047          return _size;
10048        },
10049        set: function set(value) {
10050          if (value < 0 || value > 100) {
10051            throw new Error("Size must be between 0 and 100.");
10052          }
10053
10054          _size = value;
10055          this.hasBeenReset = true;
10056        }
10057      },
10058      "align": {
10059        enumerable: true,
10060        get: function get() {
10061          return _align;
10062        },
10063        set: function set(value) {
10064          var setting = findAlignSetting(value);
10065
10066          if (!setting) {
10067            throw new SyntaxError("An invalid or illegal string was specified.");
10068          }
10069
10070          _align = setting;
10071          this.hasBeenReset = true;
10072        }
10073      }
10074    });
10075    /**
10076     * Other <track> spec defined properties
10077     */
10078    // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
10079
10080    this.displayState = undefined;
10081  }
10082  /**
10083   * VTTCue methods
10084   */
10085
10086
10087  VTTCue.prototype.getCueAsHTML = function () {
10088    // Assume WebVTT.convertCueToDOMTree is on the global.
10089    return WebVTT.convertCueToDOMTree(window, this.text);
10090  };
10091
10092  var vttcue = VTTCue;
10093
10094  /**
10095   * Copyright 2013 vtt.js Contributors
10096   *
10097   * Licensed under the Apache License, Version 2.0 (the "License");
10098   * you may not use this file except in compliance with the License.
10099   * You may obtain a copy of the License at
10100   *
10101   *   http://www.apache.org/licenses/LICENSE-2.0
10102   *
10103   * Unless required by applicable law or agreed to in writing, software
10104   * distributed under the License is distributed on an "AS IS" BASIS,
10105   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10106   * See the License for the specific language governing permissions and
10107   * limitations under the License.
10108   */
10109  var scrollSetting = {
10110    "": true,
10111    "up": true
10112  };
10113
10114  function findScrollSetting(value) {
10115    if (typeof value !== "string") {
10116      return false;
10117    }
10118
10119    var scroll = scrollSetting[value.toLowerCase()];
10120    return scroll ? value.toLowerCase() : false;
10121  }
10122
10123  function isValidPercentValue(value) {
10124    return typeof value === "number" && value >= 0 && value <= 100;
10125  } // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface
10126
10127
10128  function VTTRegion() {
10129    var _width = 100;
10130    var _lines = 3;
10131    var _regionAnchorX = 0;
10132    var _regionAnchorY = 100;
10133    var _viewportAnchorX = 0;
10134    var _viewportAnchorY = 100;
10135    var _scroll = "";
10136    Object.defineProperties(this, {
10137      "width": {
10138        enumerable: true,
10139        get: function get() {
10140          return _width;
10141        },
10142        set: function set(value) {
10143          if (!isValidPercentValue(value)) {
10144            throw new Error("Width must be between 0 and 100.");
10145          }
10146
10147          _width = value;
10148        }
10149      },
10150      "lines": {
10151        enumerable: true,
10152        get: function get() {
10153          return _lines;
10154        },
10155        set: function set(value) {
10156          if (typeof value !== "number") {
10157            throw new TypeError("Lines must be set to a number.");
10158          }
10159
10160          _lines = value;
10161        }
10162      },
10163      "regionAnchorY": {
10164        enumerable: true,
10165        get: function get() {
10166          return _regionAnchorY;
10167        },
10168        set: function set(value) {
10169          if (!isValidPercentValue(value)) {
10170            throw new Error("RegionAnchorX must be between 0 and 100.");
10171          }
10172
10173          _regionAnchorY = value;
10174        }
10175      },
10176      "regionAnchorX": {
10177        enumerable: true,
10178        get: function get() {
10179          return _regionAnchorX;
10180        },
10181        set: function set(value) {
10182          if (!isValidPercentValue(value)) {
10183            throw new Error("RegionAnchorY must be between 0 and 100.");
10184          }
10185
10186          _regionAnchorX = value;
10187        }
10188      },
10189      "viewportAnchorY": {
10190        enumerable: true,
10191        get: function get() {
10192          return _viewportAnchorY;
10193        },
10194        set: function set(value) {
10195          if (!isValidPercentValue(value)) {
10196            throw new Error("ViewportAnchorY must be between 0 and 100.");
10197          }
10198
10199          _viewportAnchorY = value;
10200        }
10201      },
10202      "viewportAnchorX": {
10203        enumerable: true,
10204        get: function get() {
10205          return _viewportAnchorX;
10206        },
10207        set: function set(value) {
10208          if (!isValidPercentValue(value)) {
10209            throw new Error("ViewportAnchorX must be between 0 and 100.");
10210          }
10211
10212          _viewportAnchorX = value;
10213        }
10214      },
10215      "scroll": {
10216        enumerable: true,
10217        get: function get() {
10218          return _scroll;
10219        },
10220        set: function set(value) {
10221          var setting = findScrollSetting(value); // Have to check for false as an empty string is a legal value.
10222
10223          if (setting === false) {
10224            throw new SyntaxError("An invalid or illegal string was specified.");
10225          }
10226
10227          _scroll = setting;
10228        }
10229      }
10230    });
10231  }
10232
10233  var vttregion = VTTRegion;
10234
10235  var browserIndex = createCommonjsModule(function (module) {
10236    /**
10237     * Copyright 2013 vtt.js Contributors
10238     *
10239     * Licensed under the Apache License, Version 2.0 (the "License");
10240     * you may not use this file except in compliance with the License.
10241     * You may obtain a copy of the License at
10242     *
10243     *   http://www.apache.org/licenses/LICENSE-2.0
10244     *
10245     * Unless required by applicable law or agreed to in writing, software
10246     * distributed under the License is distributed on an "AS IS" BASIS,
10247     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10248     * See the License for the specific language governing permissions and
10249     * limitations under the License.
10250     */
10251    // Default exports for Node. Export the extended versions of VTTCue and
10252    // VTTRegion in Node since we likely want the capability to convert back and
10253    // forth between JSON. If we don't then it's not that big of a deal since we're
10254    // off browser.
10255    var vttjs = module.exports = {
10256      WebVTT: vtt,
10257      VTTCue: vttcue,
10258      VTTRegion: vttregion
10259    };
10260    window$1.vttjs = vttjs;
10261    window$1.WebVTT = vttjs.WebVTT;
10262    var cueShim = vttjs.VTTCue;
10263    var regionShim = vttjs.VTTRegion;
10264    var nativeVTTCue = window$1.VTTCue;
10265    var nativeVTTRegion = window$1.VTTRegion;
10266
10267    vttjs.shim = function () {
10268      window$1.VTTCue = cueShim;
10269      window$1.VTTRegion = regionShim;
10270    };
10271
10272    vttjs.restore = function () {
10273      window$1.VTTCue = nativeVTTCue;
10274      window$1.VTTRegion = nativeVTTRegion;
10275    };
10276
10277    if (!window$1.VTTCue) {
10278      vttjs.shim();
10279    }
10280  });
10281  var browserIndex_1 = browserIndex.WebVTT;
10282  var browserIndex_2 = browserIndex.VTTCue;
10283  var browserIndex_3 = browserIndex.VTTRegion;
10284
10285  /**
10286   * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
10287   * that just contains the src url alone.
10288   * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
10289     * `var SourceString = 'http://example.com/some-video.mp4';`
10290   *
10291   * @typedef {Object|string} Tech~SourceObject
10292   *
10293   * @property {string} src
10294   *           The url to the source
10295   *
10296   * @property {string} type
10297   *           The mime type of the source
10298   */
10299
10300  /**
10301   * A function used by {@link Tech} to create a new {@link TextTrack}.
10302   *
10303   * @private
10304   *
10305   * @param {Tech} self
10306   *        An instance of the Tech class.
10307   *
10308   * @param {string} kind
10309   *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
10310   *
10311   * @param {string} [label]
10312   *        Label to identify the text track
10313   *
10314   * @param {string} [language]
10315   *        Two letter language abbreviation
10316   *
10317   * @param {Object} [options={}]
10318   *        An object with additional text track options
10319   *
10320   * @return {TextTrack}
10321   *          The text track that was created.
10322   */
10323
10324  function createTrackHelper(self, kind, label, language, options) {
10325    if (options === void 0) {
10326      options = {};
10327    }
10328
10329    var tracks = self.textTracks();
10330    options.kind = kind;
10331
10332    if (label) {
10333      options.label = label;
10334    }
10335
10336    if (language) {
10337      options.language = language;
10338    }
10339
10340    options.tech = self;
10341    var track = new ALL.text.TrackClass(options);
10342    tracks.addTrack(track);
10343    return track;
10344  }
10345  /**
10346   * This is the base class for media playback technology controllers, such as
10347   * {@link Flash} and {@link HTML5}
10348   *
10349   * @extends Component
10350   */
10351
10352
10353  var Tech =
10354  /*#__PURE__*/
10355  function (_Component) {
10356    _inheritsLoose(Tech, _Component);
10357
10358    /**
10359    * Create an instance of this Tech.
10360    *
10361    * @param {Object} [options]
10362    *        The key/value store of player options.
10363    *
10364    * @param {Component~ReadyCallback} ready
10365    *        Callback function to call when the `HTML5` Tech is ready.
10366    */
10367    function Tech(options, ready) {
10368      var _this;
10369
10370      if (options === void 0) {
10371        options = {};
10372      }
10373
10374      if (ready === void 0) {
10375        ready = function ready() {};
10376      }
10377
10378      // we don't want the tech to report user activity automatically.
vendor: 36,792 bytes, lines 10379-11651
10379      // This is done manually in addControlsListeners
10380      options.reportTouchActivity = false;
10381      _this = _Component.call(this, null, options, ready) || this; // keep track of whether the current source has played at all to
10382      // implement a very limited played()
10383
10384      _this.hasStarted_ = false;
10385
10386      _this.on('playing', function () {
10387        this.hasStarted_ = true;
10388      });
10389
10390      _this.on('loadstart', function () {
10391        this.hasStarted_ = false;
10392      });
10393
10394      ALL.names.forEach(function (name) {
10395        var props = ALL[name];
10396
10397        if (options && options[props.getterName]) {
10398          _this[props.privateName] = options[props.getterName];
10399        }
10400      }); // Manually track progress in cases where the browser/flash player doesn't report it.
10401
10402      if (!_this.featuresProgressEvents) {
10403        _this.manualProgressOn();
10404      } // Manually track timeupdates in cases where the browser/flash player doesn't report it.
10405
10406
10407      if (!_this.featuresTimeupdateEvents) {
10408        _this.manualTimeUpdatesOn();
10409      }
10410
10411      ['Text', 'Audio', 'Video'].forEach(function (track) {
10412        if (options["native" + track + "Tracks"] === false) {
10413          _this["featuresNative" + track + "Tracks"] = false;
10414        }
10415      });
10416
10417      if (options.nativeCaptions === false || options.nativeTextTracks === false) {
10418        _this.featuresNativeTextTracks = false;
10419      } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
10420        _this.featuresNativeTextTracks = true;
10421      }
10422
10423      if (!_this.featuresNativeTextTracks) {
10424        _this.emulateTextTracks();
10425      }
10426
10427      _this.autoRemoteTextTracks_ = new ALL.text.ListClass();
10428
10429      _this.initTrackListeners(); // Turn on component tap events only if not using native controls
10430
10431
10432      if (!options.nativeControlsForTouch) {
10433        _this.emitTapEvents();
10434      }
10435
10436      if (_this.constructor) {
10437        _this.name_ = _this.constructor.name || 'Unknown Tech';
10438      }
10439
10440      return _this;
10441    }
10442    /**
10443     * A special function to trigger source set in a way that will allow player
10444     * to re-trigger if the player or tech are not ready yet.
10445     *
10446     * @fires Tech#sourceset
10447     * @param {string} src The source string at the time of the source changing.
10448     */
10449
10450
10451    var _proto = Tech.prototype;
10452
10453    _proto.triggerSourceset = function triggerSourceset(src) {
10454      var _this2 = this;
10455
10456      if (!this.isReady_) {
10457        // on initial ready we have to trigger source set
10458        // 1ms after ready so that player can watch for it.
10459        this.one('ready', function () {
10460          return _this2.setTimeout(function () {
10461            return _this2.triggerSourceset(src);
10462          }, 1);
10463        });
10464      }
10465      /**
10466       * Fired when the source is set on the tech causing the media element
10467       * to reload.
10468       *
10469       * @see {@link Player#event:sourceset}
10470       * @event Tech#sourceset
10471       * @type {EventTarget~Event}
10472       */
10473
10474
10475      this.trigger({
10476        src: src,
10477        type: 'sourceset'
10478      });
10479    }
10480    /* Fallbacks for unsupported event types
10481    ================================================================================ */
10482
10483    /**
10484     * Polyfill the `progress` event for browsers that don't support it natively.
10485     *
10486     * @see {@link Tech#trackProgress}
10487     */
10488    ;
10489
10490    _proto.manualProgressOn = function manualProgressOn() {
10491      this.on('durationchange', this.onDurationChange);
10492      this.manualProgress = true; // Trigger progress watching when a source begins loading
10493
10494      this.one('ready', this.trackProgress);
10495    }
10496    /**
10497     * Turn off the polyfill for `progress` events that was created in
10498     * {@link Tech#manualProgressOn}
10499     */
10500    ;
10501
10502    _proto.manualProgressOff = function manualProgressOff() {
10503      this.manualProgress = false;
10504      this.stopTrackingProgress();
10505      this.off('durationchange', this.onDurationChange);
10506    }
10507    /**
10508     * This is used to trigger a `progress` event when the buffered percent changes. It
10509     * sets an interval function that will be called every 500 milliseconds to check if the
10510     * buffer end percent has changed.
10511     *
10512     * > This function is called by {@link Tech#manualProgressOn}
10513     *
10514     * @param {EventTarget~Event} event
10515     *        The `ready` event that caused this to run.
10516     *
10517     * @listens Tech#ready
10518     * @fires Tech#progress
10519     */
10520    ;
10521
10522    _proto.trackProgress = function trackProgress(event) {
10523      this.stopTrackingProgress();
10524      this.progressInterval = this.setInterval(bind(this, function () {
10525        // Don't trigger unless buffered amount is greater than last time
10526        var numBufferedPercent = this.bufferedPercent();
10527
10528        if (this.bufferedPercent_ !== numBufferedPercent) {
10529          /**
10530           * See {@link Player#progress}
10531           *
10532           * @event Tech#progress
10533           * @type {EventTarget~Event}
10534           */
10535          this.trigger('progress');
10536        }
10537
10538        this.bufferedPercent_ = numBufferedPercent;
10539
10540        if (numBufferedPercent === 1) {
10541          this.stopTrackingProgress();
10542        }
10543      }), 500);
10544    }
10545    /**
10546     * Update our internal duration on a `durationchange` event by calling
10547     * {@link Tech#duration}.
10548     *
10549     * @param {EventTarget~Event} event
10550     *        The `durationchange` event that caused this to run.
10551     *
10552     * @listens Tech#durationchange
10553     */
10554    ;
10555
10556    _proto.onDurationChange = function onDurationChange(event) {
10557      this.duration_ = this.duration();
10558    }
10559    /**
10560     * Get and create a `TimeRange` object for buffering.
10561     *
10562     * @return {TimeRange}
10563     *         The time range object that was created.
10564     */
10565    ;
10566
10567    _proto.buffered = function buffered() {
10568      return createTimeRanges(0, 0);
10569    }
10570    /**
10571     * Get the percentage of the current video that is currently buffered.
10572     *
10573     * @return {number}
10574     *         A number from 0 to 1 that represents the decimal percentage of the
10575     *         video that is buffered.
10576     *
10577     */
10578    ;
10579
10580    _proto.bufferedPercent = function bufferedPercent$$1() {
10581      return bufferedPercent(this.buffered(), this.duration_);
10582    }
10583    /**
10584     * Turn off the polyfill for `progress` events that was created in
10585     * {@link Tech#manualProgressOn}
10586     * Stop manually tracking progress events by clearing the interval that was set in
10587     * {@link Tech#trackProgress}.
10588     */
10589    ;
10590
10591    _proto.stopTrackingProgress = function stopTrackingProgress() {
10592      this.clearInterval(this.progressInterval);
10593    }
10594    /**
10595     * Polyfill the `timeupdate` event for browsers that don't support it.
10596     *
10597     * @see {@link Tech#trackCurrentTime}
10598     */
10599    ;
10600
10601    _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
10602      this.manualTimeUpdates = true;
10603      this.on('play', this.trackCurrentTime);
10604      this.on('pause', this.stopTrackingCurrentTime);
10605    }
10606    /**
10607     * Turn off the polyfill for `timeupdate` events that was created in
10608     * {@link Tech#manualTimeUpdatesOn}
10609     */
10610    ;
10611
10612    _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
10613      this.manualTimeUpdates = false;
10614      this.stopTrackingCurrentTime();
10615      this.off('play', this.trackCurrentTime);
10616      this.off('pause', this.stopTrackingCurrentTime);
10617    }
10618    /**
10619     * Sets up an interval function to track current time and trigger `timeupdate` every
10620     * 250 milliseconds.
10621     *
10622     * @listens Tech#play
10623     * @triggers Tech#timeupdate
10624     */
10625    ;
10626
10627    _proto.trackCurrentTime = function trackCurrentTime() {
10628      if (this.currentTimeInterval) {
10629        this.stopTrackingCurrentTime();
10630      }
10631
10632      this.currentTimeInterval = this.setInterval(function () {
10633        /**
10634         * Triggered at an interval of 250ms to indicated that time is passing in the video.
10635         *
10636         * @event Tech#timeupdate
10637         * @type {EventTarget~Event}
10638         */
10639        this.trigger({
10640          type: 'timeupdate',
10641          target: this,
10642          manuallyTriggered: true
10643        }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
10644      }, 250);
10645    }
10646    /**
10647     * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
10648     * `timeupdate` event is no longer triggered.
10649     *
10650     * @listens {Tech#pause}
10651     */
10652    ;
10653
10654    _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
10655      this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen,
10656      // the progress bar won't make it all the way to the end
10657
10658      this.trigger({
10659        type: 'timeupdate',
10660        target: this,
10661        manuallyTriggered: true
10662      });
10663    }
10664    /**
10665     * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
10666     * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
10667     *
10668     * @fires Component#dispose
10669     */
10670    ;
10671
10672    _proto.dispose = function dispose() {
10673      // clear out all tracks because we can't reuse them between techs
10674      this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking
10675
10676      if (this.manualProgress) {
10677        this.manualProgressOff();
10678      }
10679
10680      if (this.manualTimeUpdates) {
10681        this.manualTimeUpdatesOff();
10682      }
10683
10684      _Component.prototype.dispose.call(this);
10685    }
10686    /**
10687     * Clear out a single `TrackList` or an array of `TrackLists` given their names.
10688     *
10689     * > Note: Techs without source handlers should call this between sources for `video`
10690     *         & `audio` tracks. You don't want to use them between tracks!
10691     *
10692     * @param {string[]|string} types
10693     *        TrackList names to clear, valid names are `video`, `audio`, and
10694     *        `text`.
10695     */
10696    ;
10697
10698    _proto.clearTracks = function clearTracks(types) {
10699      var _this3 = this;
10700
10701      types = [].concat(types); // clear out all tracks because we can't reuse them between techs
10702
10703      types.forEach(function (type) {
10704        var list = _this3[type + "Tracks"]() || [];
10705        var i = list.length;
10706
10707        while (i--) {
10708          var track = list[i];
10709
10710          if (type === 'text') {
10711            _this3.removeRemoteTextTrack(track);
10712          }
10713
10714          list.removeTrack(track);
10715        }
10716      });
10717    }
10718    /**
10719     * Remove any TextTracks added via addRemoteTextTrack that are
10720     * flagged for automatic garbage collection
10721     */
10722    ;
10723
10724    _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() {
10725      var list = this.autoRemoteTextTracks_ || [];
10726      var i = list.length;
10727
10728      while (i--) {
10729        var track = list[i];
10730        this.removeRemoteTextTrack(track);
10731      }
10732    }
10733    /**
10734     * Reset the tech, which will removes all sources and reset the internal readyState.
10735     *
10736     * @abstract
10737     */
10738    ;
10739
10740    _proto.reset = function reset() {}
10741    /**
10742     * Get or set an error on the Tech.
10743     *
10744     * @param {MediaError} [err]
10745     *        Error to set on the Tech
10746     *
10747     * @return {MediaError|null}
10748     *         The current error object on the tech, or null if there isn't one.
10749     */
10750    ;
10751
10752    _proto.error = function error(err) {
10753      if (err !== undefined) {
10754        this.error_ = new MediaError(err);
10755        this.trigger('error');
10756      }
10757
10758      return this.error_;
10759    }
10760    /**
10761     * Returns the `TimeRange`s that have been played through for the current source.
10762     *
10763     * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
10764     *         It only checks whether the source has played at all or not.
10765     *
10766     * @return {TimeRange}
10767     *         - A single time range if this video has played
10768     *         - An empty set of ranges if not.
10769     */
10770    ;
10771
10772    _proto.played = function played() {
10773      if (this.hasStarted_) {
10774        return createTimeRanges(0, 0);
10775      }
10776
10777      return createTimeRanges();
10778    }
10779    /**
10780     * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
10781     * previously called.
10782     *
10783     * @fires Tech#timeupdate
10784     */
10785    ;
10786
10787    _proto.setCurrentTime = function setCurrentTime() {
10788      // improve the accuracy of manual timeupdates
10789      if (this.manualTimeUpdates) {
10790        /**
10791         * A manual `timeupdate` event.
10792         *
10793         * @event Tech#timeupdate
10794         * @type {EventTarget~Event}
10795         */
10796        this.trigger({
10797          type: 'timeupdate',
10798          target: this,
10799          manuallyTriggered: true
10800        });
10801      }
10802    }
10803    /**
10804     * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
10805     * {@link TextTrackList} events.
10806     *
10807     * This adds {@link EventTarget~EventListeners} for `addtrack`, and  `removetrack`.
10808     *
10809     * @fires Tech#audiotrackchange
10810     * @fires Tech#videotrackchange
10811     * @fires Tech#texttrackchange
10812     */
10813    ;
10814
10815    _proto.initTrackListeners = function initTrackListeners() {
10816      var _this4 = this;
10817
10818      /**
10819        * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
10820        *
10821        * @event Tech#audiotrackchange
10822        * @type {EventTarget~Event}
10823        */
10824
10825      /**
10826        * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
10827        *
10828        * @event Tech#videotrackchange
10829        * @type {EventTarget~Event}
10830        */
10831
10832      /**
10833        * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
10834        *
10835        * @event Tech#texttrackchange
10836        * @type {EventTarget~Event}
10837        */
10838      NORMAL.names.forEach(function (name) {
10839        var props = NORMAL[name];
10840
10841        var trackListChanges = function trackListChanges() {
10842          _this4.trigger(name + "trackchange");
10843        };
10844
10845        var tracks = _this4[props.getterName]();
10846
10847        tracks.addEventListener('removetrack', trackListChanges);
10848        tracks.addEventListener('addtrack', trackListChanges);
10849
10850        _this4.on('dispose', function () {
10851          tracks.removeEventListener('removetrack', trackListChanges);
10852          tracks.removeEventListener('addtrack', trackListChanges);
10853        });
10854      });
10855    }
10856    /**
10857     * Emulate TextTracks using vtt.js if necessary
10858     *
10859     * @fires Tech#vttjsloaded
10860     * @fires Tech#vttjserror
10861     */
10862    ;
10863
10864    _proto.addWebVttScript_ = function addWebVttScript_() {
10865      var _this5 = this;
10866
10867      if (window$1.WebVTT) {
10868        return;
10869      } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
10870      // signals that the Tech is ready at which point Tech.el_ is part of the DOM
10871      // before inserting the WebVTT script
10872
10873
10874      if (document.body.contains(this.el())) {
10875        // load via require if available and vtt.js script location was not passed in
10876        // as an option. novtt builds will turn the above require call into an empty object
10877        // which will cause this if check to always fail.
10878        if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) {
10879          this.trigger('vttjsloaded');
10880          return;
10881        } // load vtt.js via the script location option or the cdn of no location was
10882        // passed in
10883
10884
10885        var script = document.createElement('script');
10886        script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
10887
10888        script.onload = function () {
10889          /**
10890           * Fired when vtt.js is loaded.
10891           *
10892           * @event Tech#vttjsloaded
10893           * @type {EventTarget~Event}
10894           */
10895          _this5.trigger('vttjsloaded');
10896        };
10897
10898        script.onerror = function () {
10899          /**
10900           * Fired when vtt.js was not loaded due to an error
10901           *
10902           * @event Tech#vttjsloaded
10903           * @type {EventTarget~Event}
10904           */
10905          _this5.trigger('vttjserror');
10906        };
10907
10908        this.on('dispose', function () {
10909          script.onload = null;
10910          script.onerror = null;
10911        }); // but have not loaded yet and we set it to true before the inject so that
10912        // we don't overwrite the injected window.WebVTT if it loads right away
10913
10914        window$1.WebVTT = true;
10915        this.el().parentNode.appendChild(script);
10916      } else {
10917        this.ready(this.addWebVttScript_);
10918      }
10919    }
10920    /**
10921     * Emulate texttracks
10922     *
10923     */
10924    ;
10925
10926    _proto.emulateTextTracks = function emulateTextTracks() {
10927      var _this6 = this;
10928
10929      var tracks = this.textTracks();
10930      var remoteTracks = this.remoteTextTracks();
10931
10932      var handleAddTrack = function handleAddTrack(e) {
10933        return tracks.addTrack(e.track);
10934      };
10935
10936      var handleRemoveTrack = function handleRemoveTrack(e) {
10937        return tracks.removeTrack(e.track);
10938      };
10939
10940      remoteTracks.on('addtrack', handleAddTrack);
10941      remoteTracks.on('removetrack', handleRemoveTrack);
10942      this.addWebVttScript_();
10943
10944      var updateDisplay = function updateDisplay() {
10945        return _this6.trigger('texttrackchange');
10946      };
10947
10948      var textTracksChanges = function textTracksChanges() {
10949        updateDisplay();
10950
10951        for (var i = 0; i < tracks.length; i++) {
10952          var track = tracks[i];
10953          track.removeEventListener('cuechange', updateDisplay);
10954
10955          if (track.mode === 'showing') {
10956            track.addEventListener('cuechange', updateDisplay);
10957          }
10958        }
10959      };
10960
10961      textTracksChanges();
10962      tracks.addEventListener('change', textTracksChanges);
10963      tracks.addEventListener('addtrack', textTracksChanges);
10964      tracks.addEventListener('removetrack', textTracksChanges);
10965      this.on('dispose', function () {
10966        remoteTracks.off('addtrack', handleAddTrack);
10967        remoteTracks.off('removetrack', handleRemoveTrack);
10968        tracks.removeEventListener('change', textTracksChanges);
10969        tracks.removeEventListener('addtrack', textTracksChanges);
10970        tracks.removeEventListener('removetrack', textTracksChanges);
10971
10972        for (var i = 0; i < tracks.length; i++) {
10973          var track = tracks[i];
10974          track.removeEventListener('cuechange', updateDisplay);
10975        }
10976      });
10977    }
10978    /**
10979     * Create and returns a remote {@link TextTrack} object.
10980     *
10981     * @param {string} kind
10982     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
10983     *
10984     * @param {string} [label]
10985     *        Label to identify the text track
10986     *
10987     * @param {string} [language]
10988     *        Two letter language abbreviation
10989     *
10990     * @return {TextTrack}
10991     *         The TextTrack that gets created.
10992     */
10993    ;
10994
10995    _proto.addTextTrack = function addTextTrack(kind, label, language) {
10996      if (!kind) {
10997        throw new Error('TextTrack kind is required but was not provided');
10998      }
10999
11000      return createTrackHelper(this, kind, label, language);
11001    }
11002    /**
11003     * Create an emulated TextTrack for use by addRemoteTextTrack
11004     *
11005     * This is intended to be overridden by classes that inherit from
11006     * Tech in order to create native or custom TextTracks.
11007     *
11008     * @param {Object} options
11009     *        The object should contain the options to initialize the TextTrack with.
11010     *
11011     * @param {string} [options.kind]
11012     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
11013     *
11014     * @param {string} [options.label].
11015     *        Label to identify the text track
11016     *
11017     * @param {string} [options.language]
11018     *        Two letter language abbreviation.
11019     *
11020     * @return {HTMLTrackElement}
11021     *         The track element that gets created.
11022     */
11023    ;
11024
11025    _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
11026      var track = mergeOptions(options, {
11027        tech: this
11028      });
11029      return new REMOTE.remoteTextEl.TrackClass(track);
11030    }
11031    /**
11032     * Creates a remote text track object and returns an html track element.
11033     *
11034     * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
11035     *
11036     * @param {Object} options
11037     *        See {@link Tech#createRemoteTextTrack} for more detailed properties.
11038     *
11039     * @param {boolean} [manualCleanup=true]
11040     *        - When false: the TextTrack will be automatically removed from the video
11041     *          element whenever the source changes
11042     *        - When True: The TextTrack will have to be cleaned up manually
11043     *
11044     * @return {HTMLTrackElement}
11045     *         An Html Track Element.
11046     *
11047     * @deprecated The default functionality for this function will be equivalent
11048     *             to "manualCleanup=false" in the future. The manualCleanup parameter will
11049     *             also be removed.
11050     */
11051    ;
11052
11053    _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
11054      var _this7 = this;
11055
11056      if (options === void 0) {
11057        options = {};
11058      }
11059
11060      var htmlTrackElement = this.createRemoteTextTrack(options);
11061
11062      if (manualCleanup !== true && manualCleanup !== false) {
11063        // deprecation warning
11064        log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js');
11065        manualCleanup = true;
11066      } // store HTMLTrackElement and TextTrack to remote list
11067
11068
11069      this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
11070      this.remoteTextTracks().addTrack(htmlTrackElement.track);
11071
11072      if (manualCleanup !== true) {
11073        // create the TextTrackList if it doesn't exist
11074        this.ready(function () {
11075          return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track);
11076        });
11077      }
11078
11079      return htmlTrackElement;
11080    }
11081    /**
11082     * Remove a remote text track from the remote `TextTrackList`.
11083     *
11084     * @param {TextTrack} track
11085     *        `TextTrack` to remove from the `TextTrackList`
11086     */
11087    ;
11088
11089    _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
11090      var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list
11091
11092      this.remoteTextTrackEls().removeTrackElement_(trackElement);
11093      this.remoteTextTracks().removeTrack(track);
11094      this.autoRemoteTextTracks_.removeTrack(track);
11095    }
11096    /**
11097     * Gets available media playback quality metrics as specified by the W3C's Media
11098     * Playback Quality API.
11099     *
11100     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
11101     *
11102     * @return {Object}
11103     *         An object with supported media playback quality metrics
11104     *
11105     * @abstract
11106     */
11107    ;
11108
11109    _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
11110      return {};
11111    }
11112    /**
11113     * A method to set a poster from a `Tech`.
11114     *
11115     * @abstract
11116     */
11117    ;
11118
11119    _proto.setPoster = function setPoster() {}
11120    /**
11121     * A method to check for the presence of the 'playsinline' <video> attribute.
11122     *
11123     * @abstract
11124     */
11125    ;
11126
11127    _proto.playsinline = function playsinline() {}
11128    /**
11129     * A method to set or unset the 'playsinline' <video> attribute.
11130     *
11131     * @abstract
11132     */
11133    ;
11134
11135    _proto.setPlaysinline = function setPlaysinline() {}
11136    /**
11137     * Attempt to force override of native audio tracks.
11138     *
11139     * @param {boolean} override - If set to true native audio will be overridden,
11140     * otherwise native audio will potentially be used.
11141     *
11142     * @abstract
11143     */
11144    ;
11145
11146    _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks() {}
11147    /**
11148     * Attempt to force override of native video tracks.
11149     *
11150     * @param {boolean} override - If set to true native video will be overridden,
11151     * otherwise native video will potentially be used.
11152     *
11153     * @abstract
11154     */
11155    ;
11156
11157    _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks() {}
11158    /*
11159     * Check if the tech can support the given mime-type.
11160     *
11161     * The base tech does not support any type, but source handlers might
11162     * overwrite this.
11163     *
11164     * @param  {string} type
11165     *         The mimetype to check for support
11166     *
11167     * @return {string}
11168     *         'probably', 'maybe', or empty string
11169     *
11170     * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
11171     *
11172     * @abstract
11173     */
11174    ;
11175
11176    _proto.canPlayType = function canPlayType() {
11177      return '';
11178    }
11179    /**
11180     * Check if the type is supported by this tech.
11181     *
11182     * The base tech does not support any type, but source handlers might
11183     * overwrite this.
11184     *
11185     * @param {string} type
11186     *        The media type to check
11187     * @return {string} Returns the native video element's response
11188     */
11189    ;
11190
11191    Tech.canPlayType = function canPlayType() {
11192      return '';
11193    }
11194    /**
11195     * Check if the tech can support the given source
11196     *
11197     * @param {Object} srcObj
11198     *        The source object
11199     * @param {Object} options
11200     *        The options passed to the tech
11201     * @return {string} 'probably', 'maybe', or '' (empty string)
11202     */
11203    ;
11204
11205    Tech.canPlaySource = function canPlaySource(srcObj, options) {
11206      return Tech.canPlayType(srcObj.type);
11207    }
11208    /*
11209     * Return whether the argument is a Tech or not.
11210     * Can be passed either a Class like `Html5` or a instance like `player.tech_`
11211     *
11212     * @param {Object} component
11213     *        The item to check
11214     *
11215     * @return {boolean}
11216     *         Whether it is a tech or not
11217     *         - True if it is a tech
11218     *         - False if it is not
11219     */
11220    ;
11221
11222    Tech.isTech = function isTech(component) {
11223      return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
11224    }
11225    /**
11226     * Registers a `Tech` into a shared list for videojs.
11227     *
11228     * @param {string} name
11229     *        Name of the `Tech` to register.
11230     *
11231     * @param {Object} tech
11232     *        The `Tech` class to register.
11233     */
11234    ;
11235
11236    Tech.registerTech = function registerTech(name, tech) {
11237      if (!Tech.techs_) {
11238        Tech.techs_ = {};
11239      }
11240
11241      if (!Tech.isTech(tech)) {
11242        throw new Error("Tech " + name + " must be a Tech");
11243      }
11244
11245      if (!Tech.canPlayType) {
11246        throw new Error('Techs must have a static canPlayType method on them');
11247      }
11248
11249      if (!Tech.canPlaySource) {
11250        throw new Error('Techs must have a static canPlaySource method on them');
11251      }
11252
11253      name = toTitleCase(name);
11254      Tech.techs_[name] = tech;
11255
11256      if (name !== 'Tech') {
11257        // camel case the techName for use in techOrder
11258        Tech.defaultTechOrder_.push(name);
11259      }
11260
11261      return tech;
11262    }
11263    /**
11264     * Get a `Tech` from the shared list by name.
11265     *
11266     * @param {string} name
11267     *        `camelCase` or `TitleCase` name of the Tech to get
11268     *
11269     * @return {Tech|undefined}
11270     *         The `Tech` or undefined if there was no tech with the name requested.
11271     */
11272    ;
11273
11274    Tech.getTech = function getTech(name) {
11275      if (!name) {
11276        return;
11277      }
11278
11279      name = toTitleCase(name);
11280
11281      if (Tech.techs_ && Tech.techs_[name]) {
11282        return Tech.techs_[name];
11283      }
11284
11285      if (window$1 && window$1.videojs && window$1.videojs[name]) {
11286        log.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)");
11287        return window$1.videojs[name];
11288      }
11289    };
11290
11291    return Tech;
11292  }(Component);
11293  /**
11294   * Get the {@link VideoTrackList}
11295   *
11296   * @returns {VideoTrackList}
11297   * @method Tech.prototype.videoTracks
11298   */
11299
11300  /**
11301   * Get the {@link AudioTrackList}
11302   *
11303   * @returns {AudioTrackList}
11304   * @method Tech.prototype.audioTracks
11305   */
11306
11307  /**
11308   * Get the {@link TextTrackList}
11309   *
11310   * @returns {TextTrackList}
11311   * @method Tech.prototype.textTracks
11312   */
11313
11314  /**
11315   * Get the remote element {@link TextTrackList}
11316   *
11317   * @returns {TextTrackList}
11318   * @method Tech.prototype.remoteTextTracks
11319   */
11320
11321  /**
11322   * Get the remote element {@link HtmlTrackElementList}
11323   *
11324   * @returns {HtmlTrackElementList}
11325   * @method Tech.prototype.remoteTextTrackEls
11326   */
11327
11328
11329  ALL.names.forEach(function (name) {
11330    var props = ALL[name];
11331
11332    Tech.prototype[props.getterName] = function () {
11333      this[props.privateName] = this[props.privateName] || new props.ListClass();
11334      return this[props.privateName];
11335    };
11336  });
11337  /**
11338   * List of associated text tracks
11339   *
11340   * @type {TextTrackList}
11341   * @private
11342   * @property Tech#textTracks_
11343   */
11344
11345  /**
11346   * List of associated audio tracks.
11347   *
11348   * @type {AudioTrackList}
11349   * @private
11350   * @property Tech#audioTracks_
11351   */
11352
11353  /**
11354   * List of associated video tracks.
11355   *
11356   * @type {VideoTrackList}
11357   * @private
11358   * @property Tech#videoTracks_
11359   */
11360
11361  /**
11362   * Boolean indicating whether the `Tech` supports volume control.
11363   *
11364   * @type {boolean}
11365   * @default
11366   */
11367
11368  Tech.prototype.featuresVolumeControl = true;
11369  /**
11370   * Boolean indicating whether the `Tech` supports muting volume.
11371   *
11372   * @type {bolean}
11373   * @default
11374   */
11375
11376  Tech.prototype.featuresMuteControl = true;
11377  /**
11378   * Boolean indicating whether the `Tech` supports fullscreen resize control.
11379   * Resizing plugins using request fullscreen reloads the plugin
11380   *
11381   * @type {boolean}
11382   * @default
11383   */
11384
11385  Tech.prototype.featuresFullscreenResize = false;
11386  /**
11387   * Boolean indicating whether the `Tech` supports changing the speed at which the video
11388   * plays. Examples:
11389   *   - Set player to play 2x (twice) as fast
11390   *   - Set player to play 0.5x (half) as fast
11391   *
11392   * @type {boolean}
11393   * @default
11394   */
11395
11396  Tech.prototype.featuresPlaybackRate = false;
11397  /**
11398   * Boolean indicating whether the `Tech` supports the `progress` event. This is currently
11399   * not triggered by video-js-swf. This will be used to determine if
11400   * {@link Tech#manualProgressOn} should be called.
11401   *
11402   * @type {boolean}
11403   * @default
11404   */
11405
11406  Tech.prototype.featuresProgressEvents = false;
11407  /**
11408   * Boolean indicating whether the `Tech` supports the `sourceset` event.
11409   *
11410   * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
11411   * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
11412   * a new source.
11413   *
11414   * @type {boolean}
11415   * @default
11416   */
11417
11418  Tech.prototype.featuresSourceset = false;
11419  /**
11420   * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently
11421   * not triggered by video-js-swf. This will be used to determine if
11422   * {@link Tech#manualTimeUpdates} should be called.
11423   *
11424   * @type {boolean}
11425   * @default
11426   */
11427
11428  Tech.prototype.featuresTimeupdateEvents = false;
11429  /**
11430   * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
11431   * This will help us integrate with native `TextTrack`s if the browser supports them.
11432   *
11433   * @type {boolean}
11434   * @default
11435   */
11436
11437  Tech.prototype.featuresNativeTextTracks = false;
11438  /**
11439   * A functional mixin for techs that want to use the Source Handler pattern.
11440   * Source handlers are scripts for handling specific formats.
11441   * The source handler pattern is used for adaptive formats (HLS, DASH) that
11442   * manually load video data and feed it into a Source Buffer (Media Source Extensions)
11443   * Example: `Tech.withSourceHandlers.call(MyTech);`
11444   *
11445   * @param {Tech} _Tech
11446   *        The tech to add source handler functions to.
11447   *
11448   * @mixes Tech~SourceHandlerAdditions
11449   */
11450
11451  Tech.withSourceHandlers = function (_Tech) {
11452    /**
11453     * Register a source handler
11454     *
11455     * @param {Function} handler
11456     *        The source handler class
11457     *
11458     * @param {number} [index]
11459     *        Register it at the following index
11460     */
11461    _Tech.registerSourceHandler = function (handler, index) {
11462      var handlers = _Tech.sourceHandlers;
11463
11464      if (!handlers) {
11465        handlers = _Tech.sourceHandlers = [];
11466      }
11467
11468      if (index === undefined) {
11469        // add to the end of the list
11470        index = handlers.length;
11471      }
11472
11473      handlers.splice(index, 0, handler);
11474    };
11475    /**
11476     * Check if the tech can support the given type. Also checks the
11477     * Techs sourceHandlers.
11478     *
11479     * @param {string} type
11480     *         The mimetype to check.
11481     *
11482     * @return {string}
11483     *         'probably', 'maybe', or '' (empty string)
11484     */
11485
11486
11487    _Tech.canPlayType = function (type) {
11488      var handlers = _Tech.sourceHandlers || [];
11489      var can;
11490
11491      for (var i = 0; i < handlers.length; i++) {
11492        can = handlers[i].canPlayType(type);
11493
11494        if (can) {
11495          return can;
11496        }
11497      }
11498
11499      return '';
11500    };
11501    /**
11502     * Returns the first source handler that supports the source.
11503     *
11504     * TODO: Answer question: should 'probably' be prioritized over 'maybe'
11505     *
11506     * @param {Tech~SourceObject} source
11507     *        The source object
11508     *
11509     * @param {Object} options
11510     *        The options passed to the tech
11511     *
11512     * @return {SourceHandler|null}
11513     *          The first source handler that supports the source or null if
11514     *          no SourceHandler supports the source
11515     */
11516
11517
11518    _Tech.selectSourceHandler = function (source, options) {
11519      var handlers = _Tech.sourceHandlers || [];
11520      var can;
11521
11522      for (var i = 0; i < handlers.length; i++) {
11523        can = handlers[i].canHandleSource(source, options);
11524
11525        if (can) {
11526          return handlers[i];
11527        }
11528      }
11529
11530      return null;
11531    };
11532    /**
11533     * Check if the tech can support the given source.
11534     *
11535     * @param {Tech~SourceObject} srcObj
11536     *        The source object
11537     *
11538     * @param {Object} options
11539     *        The options passed to the tech
11540     *
11541     * @return {string}
11542     *         'probably', 'maybe', or '' (empty string)
11543     */
11544
11545
11546    _Tech.canPlaySource = function (srcObj, options) {
11547      var sh = _Tech.selectSourceHandler(srcObj, options);
11548
11549      if (sh) {
11550        return sh.canHandleSource(srcObj, options);
11551      }
11552
11553      return '';
11554    };
11555    /**
11556     * When using a source handler, prefer its implementation of
11557     * any function normally provided by the tech.
11558     */
11559
11560
11561    var deferrable = ['seekable', 'seeking', 'duration'];
11562    /**
11563     * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
11564     * function if it exists, with a fallback to the Techs seekable function.
11565     *
11566     * @method _Tech.seekable
11567     */
11568
11569    /**
11570     * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
11571     * function if it exists, otherwise it will fallback to the techs duration function.
11572     *
11573     * @method _Tech.duration
11574     */
11575
11576    deferrable.forEach(function (fnName) {
11577      var originalFn = this[fnName];
11578
11579      if (typeof originalFn !== 'function') {
11580        return;
11581      }
11582
11583      this[fnName] = function () {
11584        if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
11585          return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
11586        }
11587
11588        return originalFn.apply(this, arguments);
11589      };
11590    }, _Tech.prototype);
11591    /**
11592     * Create a function for setting the source using a source object
11593     * and source handlers.
11594     * Should never be called unless a source handler was found.
11595     *
11596     * @param {Tech~SourceObject} source
11597     *        A source object with src and type keys
11598     */
11599
11600    _Tech.prototype.setSource = function (source) {
11601      var sh = _Tech.selectSourceHandler(source, this.options_);
11602
11603      if (!sh) {
11604        // Fall back to a native source hander when unsupported sources are
11605        // deliberately set
11606        if (_Tech.nativeSourceHandler) {
11607          sh = _Tech.nativeSourceHandler;
11608        } else {
11609          log.error('No source handler found for the current source.');
11610        }
11611      } // Dispose any existing source handler
11612
11613
11614      this.disposeSourceHandler();
11615      this.off('dispose', this.disposeSourceHandler);
11616
11617      if (sh !== _Tech.nativeSourceHandler) {
11618        this.currentSource_ = source;
11619      }
11620
11621      this.sourceHandler_ = sh.handleSource(source, this, this.options_);
11622      this.one('dispose', this.disposeSourceHandler);
11623    };
11624    /**
11625     * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
11626     *
11627     * @listens Tech#dispose
11628     */
11629
11630
11631    _Tech.prototype.disposeSourceHandler = function () {
11632      // if we have a source and get another one
11633      // then we are loading something new
11634      // than clear all of our current tracks
11635      if (this.currentSource_) {
11636        this.clearTracks(['audio', 'video']);
11637        this.currentSource_ = null;
11638      } // always clean up auto-text tracks
11639
11640
11641      this.cleanupAutoTextTracks();
11642
11643      if (this.sourceHandler_) {
11644        if (this.sourceHandler_.dispose) {
11645          this.sourceHandler_.dispose();
11646        }
11647
11648        this.sourceHandler_ = null;
11649      }
11650    };
11651  };
vendor: 4,342 bytes, lines 11651-11801
11651 // The base Tech class needs to be registered as a Component. It is the only
11652  // Tech that can be registered as a Component.
11653
11654
11655  Component.registerComponent('Tech', Tech);
11656  Tech.registerTech('Tech', Tech);
11657  /**
11658   * A list of techs that should be added to techOrder on Players
11659   *
11660   * @private
11661   */
11662
11663  Tech.defaultTechOrder_ = [];
11664
11665  /**
11666   * @file middleware.js
11667   * @module middleware
11668   */
11669  var middlewares = {};
11670  var middlewareInstances = {};
11671  var TERMINATOR = {};
11672  /**
11673   * A middleware object is a plain JavaScript object that has methods that
11674   * match the {@link Tech} methods found in the lists of allowed
11675   * {@link module:middleware.allowedGetters|getters},
11676   * {@link module:middleware.allowedSetters|setters}, and
11677   * {@link module:middleware.allowedMediators|mediators}.
11678   *
11679   * @typedef {Object} MiddlewareObject
11680   */
11681
11682  /**
11683   * A middleware factory function that should return a
11684   * {@link module:middleware~MiddlewareObject|MiddlewareObject}.
11685   *
11686   * This factory will be called for each player when needed, with the player
11687   * passed in as an argument.
11688   *
11689   * @callback MiddlewareFactory
11690   * @param {Player} player
11691   *        A Video.js player.
11692   */
11693
11694  /**
11695   * Define a middleware that the player should use by way of a factory function
11696   * that returns a middleware object.
11697   *
11698   * @param  {string} type
11699   *         The MIME type to match or `"*"` for all MIME types.
11700   *
11701   * @param  {MiddlewareFactory} middleware
11702   *         A middleware factory function that will be executed for
11703   *         matching types.
11704   */
11705
11706  function use(type, middleware) {
11707    middlewares[type] = middlewares[type] || [];
11708    middlewares[type].push(middleware);
11709  }
11710  /**
11711   * Asynchronously sets a source using middleware by recursing through any
11712   * matching middlewares and calling `setSource` on each, passing along the
11713   * previous returned value each time.
11714   *
11715   * @param  {Player} player
11716   *         A {@link Player} instance.
11717   *
11718   * @param  {Tech~SourceObject} src
11719   *         A source object.
11720   *
11721   * @param  {Function}
11722   *         The next middleware to run.
11723   */
11724
11725  function setSource(player, src, next) {
11726    player.setTimeout(function () {
11727      return setSourceHelper(src, middlewares[src.type], next, player);
11728    }, 1);
11729  }
11730  /**
11731   * When the tech is set, passes the tech to each middleware's `setTech` method.
11732   *
11733   * @param {Object[]} middleware
11734   *        An array of middleware instances.
11735   *
11736   * @param {Tech} tech
11737   *        A Video.js tech.
11738   */
11739
11740  function setTech(middleware, tech) {
11741    middleware.forEach(function (mw) {
11742      return mw.setTech && mw.setTech(tech);
11743    });
11744  }
11745  /**
11746   * Calls a getter on the tech first, through each middleware
11747   * from right to left to the player.
11748   *
11749   * @param  {Object[]} middleware
11750   *         An array of middleware instances.
11751   *
11752   * @param  {Tech} tech
11753   *         The current tech.
11754   *
11755   * @param  {string} method
11756   *         A method name.
11757   *
11758   * @return {Mixed}
11759   *         The final value from the tech after middleware has intercepted it.
11760   */
11761
11762  function get(middleware, tech, method) {
11763    return middleware.reduceRight(middlewareIterator(method), tech[method]());
11764  }
11765  /**
11766   * Takes the argument given to the player and calls the setter method on each
11767   * middleware from left to right to the tech.
11768   *
11769   * @param  {Object[]} middleware
11770   *         An array of middleware instances.
11771   *
11772   * @param  {Tech} tech
11773   *         The current tech.
11774   *
11775   * @param  {string} method
11776   *         A method name.
11777   *
11778   * @param  {Mixed} arg
11779   *         The value to set on the tech.
11780   *
11781   * @return {Mixed}
11782   *         The return value of the `method` of the `tech`.
11783   */
11784
11785  function set$1(middleware, tech, method, arg) {
11786    return tech[method](middleware.reduce(middlewareIterator(method), arg));
11787  }
11788  /**
11789   * Takes the argument given to the player and calls the `call` version of the
11790   * method on each middleware from left to right.
11791   *
11792   * Then, call the passed in method on the tech and return the result unchanged
11793   * back to the player, through middleware, this time from right to left.
11794   *
11795   * @param  {Object[]} middleware
11796   *         An array of middleware instances.
11797   *
11798   * @param  {Tech} tech
11799   *         The current tech.
11800   *
11801   * @param  {string} method
vendor: 44,299 bytes, lines 11802-13373
11802   *         A method name.
11803   *
11804   * @param  {Mixed} arg
11805   *         The value to set on the tech.
11806   *
11807   * @return {Mixed}
11808   *         The return value of the `method` of the `tech`, regardless of the
11809   *         return values of middlewares.
11810   */
11811
11812  function mediate(middleware, tech, method, arg) {
11813    if (arg === void 0) {
11814      arg = null;
11815    }
11816
11817    var callMethod = 'call' + toTitleCase(method);
11818    var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
11819    var terminated = middlewareValue === TERMINATOR; // deprecated. The `null` return value should instead return TERMINATOR to
11820    // prevent confusion if a techs method actually returns null.
11821
11822    var returnValue = terminated ? null : tech[method](middlewareValue);
11823    executeRight(middleware, method, returnValue, terminated);
11824    return returnValue;
11825  }
11826  /**
11827   * Enumeration of allowed getters where the keys are method names.
11828   *
11829   * @type {Object}
11830   */
11831
11832  var allowedGetters = {
11833    buffered: 1,
11834    currentTime: 1,
11835    duration: 1,
11836    seekable: 1,
11837    played: 1,
11838    paused: 1
11839  };
11840  /**
11841   * Enumeration of allowed setters where the keys are method names.
11842   *
11843   * @type {Object}
11844   */
11845
11846  var allowedSetters = {
11847    setCurrentTime: 1
11848  };
11849  /**
11850   * Enumeration of allowed mediators where the keys are method names.
11851   *
11852   * @type {Object}
11853   */
11854
11855  var allowedMediators = {
11856    play: 1,
11857    pause: 1
11858  };
11859
11860  function middlewareIterator(method) {
11861    return function (value, mw) {
11862      // if the previous middleware terminated, pass along the termination
11863      if (value === TERMINATOR) {
11864        return TERMINATOR;
11865      }
11866
11867      if (mw[method]) {
11868        return mw[method](value);
11869      }
11870
11871      return value;
11872    };
11873  }
11874
11875  function executeRight(mws, method, value, terminated) {
11876    for (var i = mws.length - 1; i >= 0; i--) {
11877      var mw = mws[i];
11878
11879      if (mw[method]) {
11880        mw[method](terminated, value);
11881      }
11882    }
11883  }
11884  /**
11885   * Clear the middleware cache for a player.
11886   *
11887   * @param  {Player} player
11888   *         A {@link Player} instance.
11889   */
11890
11891
11892  function clearCacheForPlayer(player) {
11893    middlewareInstances[player.id()] = null;
11894  }
11895  /**
11896   * {
11897   *  [playerId]: [[mwFactory, mwInstance], ...]
11898   * }
11899   *
11900   * @private
11901   */
11902
11903  function getOrCreateFactory(player, mwFactory) {
11904    var mws = middlewareInstances[player.id()];
11905    var mw = null;
11906
11907    if (mws === undefined || mws === null) {
11908      mw = mwFactory(player);
11909      middlewareInstances[player.id()] = [[mwFactory, mw]];
11910      return mw;
11911    }
11912
11913    for (var i = 0; i < mws.length; i++) {
11914      var _mws$i = mws[i],
11915          mwf = _mws$i[0],
11916          mwi = _mws$i[1];
11917
11918      if (mwf !== mwFactory) {
11919        continue;
11920      }
11921
11922      mw = mwi;
11923    }
11924
11925    if (mw === null) {
11926      mw = mwFactory(player);
11927      mws.push([mwFactory, mw]);
11928    }
11929
11930    return mw;
11931  }
11932
11933  function setSourceHelper(src, middleware, next, player, acc, lastRun) {
11934    if (src === void 0) {
11935      src = {};
11936    }
11937
11938    if (middleware === void 0) {
11939      middleware = [];
11940    }
11941
11942    if (acc === void 0) {
11943      acc = [];
11944    }
11945
11946    if (lastRun === void 0) {
11947      lastRun = false;
11948    }
11949
11950    var _middleware = middleware,
11951        mwFactory = _middleware[0],
11952        mwrest = _middleware.slice(1); // if mwFactory is a string, then we're at a fork in the road
11953
11954
11955    if (typeof mwFactory === 'string') {
11956      setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); // if we have an mwFactory, call it with the player to get the mw,
11957      // then call the mw's setSource method
11958    } else if (mwFactory) {
11959      var mw = getOrCreateFactory(player, mwFactory); // if setSource isn't present, implicitly select this middleware
11960
11961      if (!mw.setSource) {
11962        acc.push(mw);
11963        return setSourceHelper(src, mwrest, next, player, acc, lastRun);
11964      }
11965
11966      mw.setSource(assign({}, src), function (err, _src) {
11967        // something happened, try the next middleware on the current level
11968        // make sure to use the old src
11969        if (err) {
11970          return setSourceHelper(src, mwrest, next, player, acc, lastRun);
11971        } // we've succeeded, now we need to go deeper
11972
11973
11974        acc.push(mw); // if it's the same type, continue down the current chain
11975        // otherwise, we want to go down the new chain
11976
11977        setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
11978      });
11979    } else if (mwrest.length) {
11980      setSourceHelper(src, mwrest, next, player, acc, lastRun);
11981    } else if (lastRun) {
11982      next(src, acc);
11983    } else {
11984      setSourceHelper(src, middlewares['*'], next, player, acc, true);
11985    }
11986  }
11987
11988  /**
11989   * Mimetypes
11990   *
11991   * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm
11992   * @typedef Mimetypes~Kind
11993   * @enum
11994   */
11995
11996  var MimetypesKind = {
11997    opus: 'video/ogg',
11998    ogv: 'video/ogg',
11999    mp4: 'video/mp4',
12000    mov: 'video/mp4',
12001    m4v: 'video/mp4',
12002    mkv: 'video/x-matroska',
12003    mp3: 'audio/mpeg',
12004    aac: 'audio/aac',
12005    oga: 'audio/ogg',
12006    m3u8: 'application/x-mpegURL',
12007    jpg: 'image/jpeg',
12008    jpeg: 'image/jpeg',
12009    gif: 'image/gif',
12010    png: 'image/png',
12011    svg: 'image/svg+xml',
12012    webp: 'image/webp'
12013  };
12014  /**
12015   * Get the mimetype of a given src url if possible
12016   *
12017   * @param {string} src
12018   *        The url to the src
12019   *
12020   * @return {string}
12021   *         return the mimetype if it was known or empty string otherwise
12022   */
12023
12024  var getMimetype = function getMimetype(src) {
12025    if (src === void 0) {
12026      src = '';
12027    }
12028
12029    var ext = getFileExtension(src);
12030    var mimetype = MimetypesKind[ext.toLowerCase()];
12031    return mimetype || '';
12032  };
12033  /**
12034   * Find the mime type of a given source string if possible. Uses the player
12035   * source cache.
12036   *
12037   * @param {Player} player
12038   *        The player object
12039   *
12040   * @param {string} src
12041   *        The source string
12042   *
12043   * @return {string}
12044   *         The type that was found
12045   */
12046
12047  var findMimetype = function findMimetype(player, src) {
12048    if (!src) {
12049      return '';
12050    } // 1. check for the type in the `source` cache
12051
12052
12053    if (player.cache_.source.src === src && player.cache_.source.type) {
12054      return player.cache_.source.type;
12055    } // 2. see if we have this source in our `currentSources` cache
12056
12057
12058    var matchingSources = player.cache_.sources.filter(function (s) {
12059      return s.src === src;
12060    });
12061
12062    if (matchingSources.length) {
12063      return matchingSources[0].type;
12064    } // 3. look for the src url in source elements and use the type there
12065
12066
12067    var sources = player.$$('source');
12068
12069    for (var i = 0; i < sources.length; i++) {
12070      var s = sources[i];
12071
12072      if (s.type && s.src && s.src === src) {
12073        return s.type;
12074      }
12075    } // 4. finally fallback to our list of mime types based on src url extension
12076
12077
12078    return getMimetype(src);
12079  };
12080
12081  /**
12082   * @module filter-source
12083   */
12084  /**
12085   * Filter out single bad source objects or multiple source objects in an
12086   * array. Also flattens nested source object arrays into a 1 dimensional
12087   * array of source objects.
12088   *
12089   * @param {Tech~SourceObject|Tech~SourceObject[]} src
12090   *        The src object to filter
12091   *
12092   * @return {Tech~SourceObject[]}
12093   *         An array of sourceobjects containing only valid sources
12094   *
12095   * @private
12096   */
12097
12098  var filterSource = function filterSource(src) {
12099    // traverse array
12100    if (Array.isArray(src)) {
12101      var newsrc = [];
12102      src.forEach(function (srcobj) {
12103        srcobj = filterSource(srcobj);
12104
12105        if (Array.isArray(srcobj)) {
12106          newsrc = newsrc.concat(srcobj);
12107        } else if (isObject(srcobj)) {
12108          newsrc.push(srcobj);
12109        }
12110      });
12111      src = newsrc;
12112    } else if (typeof src === 'string' && src.trim()) {
12113      // convert string into object
12114      src = [fixSource({
12115        src: src
12116      })];
12117    } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
12118      // src is already valid
12119      src = [fixSource(src)];
12120    } else {
12121      // invalid source, turn it into an empty array
12122      src = [];
12123    }
12124
12125    return src;
12126  };
12127  /**
12128   * Checks src mimetype, adding it when possible
12129   *
12130   * @param {Tech~SourceObject} src
12131   *        The src object to check
12132   * @return {Tech~SourceObject}
12133   *        src Object with known type
12134   */
12135
12136
12137  function fixSource(src) {
12138    var mimetype = getMimetype(src.src);
12139
12140    if (!src.type && mimetype) {
12141      src.type = mimetype;
12142    }
12143
12144    return src;
12145  }
12146
12147  /**
12148   * The `MediaLoader` is the `Component` that decides which playback technology to load
12149   * when a player is initialized.
12150   *
12151   * @extends Component
12152   */
12153
12154  var MediaLoader =
12155  /*#__PURE__*/
12156  function (_Component) {
12157    _inheritsLoose(MediaLoader, _Component);
12158
12159    /**
12160     * Create an instance of this class.
12161     *
12162     * @param {Player} player
12163     *        The `Player` that this class should attach to.
12164     *
12165     * @param {Object} [options]
12166     *        The key/value store of player options.
12167     *
12168     * @param {Component~ReadyCallback} [ready]
12169     *        The function that is run when this component is ready.
12170     */
12171    function MediaLoader(player, options, ready) {
12172      var _this;
12173
12174      // MediaLoader has no element
12175      var options_ = mergeOptions({
12176        createEl: false
12177      }, options);
12178      _this = _Component.call(this, player, options_, ready) || this; // If there are no sources when the player is initialized,
12179      // load the first supported playback technology.
12180
12181      if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
12182        for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
12183          var techName = toTitleCase(j[i]);
12184          var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
12185          // Remove once that deprecated behavior is removed.
12186
12187          if (!techName) {
12188            tech = Component.getComponent(techName);
12189          } // Check if the browser supports this technology
12190
12191
12192          if (tech && tech.isSupported()) {
12193            player.loadTech_(techName);
12194            break;
12195          }
12196        }
12197      } else {
12198        // Loop through playback technologies (HTML5, Flash) and check for support.
12199        // Then load the best source.
12200        // A few assumptions here:
12201        //   All playback technologies respect preload false.
12202        player.src(options.playerOptions.sources);
12203      }
12204
12205      return _this;
12206    }
12207
12208    return MediaLoader;
12209  }(Component);
12210
12211  Component.registerComponent('MediaLoader', MediaLoader);
12212
12213  /**
12214   * Component which is clickable or keyboard actionable, but is not a
12215   * native HTML button.
12216   *
12217   * @extends Component
12218   */
12219
12220  var ClickableComponent =
12221  /*#__PURE__*/
12222  function (_Component) {
12223    _inheritsLoose(ClickableComponent, _Component);
12224
12225    /**
12226     * Creates an instance of this class.
12227     *
12228     * @param  {Player} player
12229     *         The `Player` that this class should be attached to.
12230     *
12231     * @param  {Object} [options]
12232     *         The key/value store of player options.
12233     */
12234    function ClickableComponent(player, options) {
12235      var _this;
12236
12237      _this = _Component.call(this, player, options) || this;
12238
12239      _this.emitTapEvents();
12240
12241      _this.enable();
12242
12243      return _this;
12244    }
12245    /**
12246     * Create the `ClickableComponent`s DOM element.
12247     *
12248     * @param {string} [tag=div]
12249     *        The element's node type.
12250     *
12251     * @param {Object} [props={}]
12252     *        An object of properties that should be set on the element.
12253     *
12254     * @param {Object} [attributes={}]
12255     *        An object of attributes that should be set on the element.
12256     *
12257     * @return {Element}
12258     *         The element that gets created.
12259     */
12260
12261
12262    var _proto = ClickableComponent.prototype;
12263
12264    _proto.createEl = function createEl$$1(tag, props, attributes) {
12265      if (tag === void 0) {
12266        tag = 'div';
12267      }
12268
12269      if (props === void 0) {
12270        props = {};
12271      }
12272
12273      if (attributes === void 0) {
12274        attributes = {};
12275      }
12276
12277      props = assign({
12278        innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
12279        className: this.buildCSSClass(),
12280        tabIndex: 0
12281      }, props);
12282
12283      if (tag === 'button') {
12284        log.error("Creating a ClickableComponent with an HTML element of " + tag + " is not supported; use a Button instead.");
12285      } // Add ARIA attributes for clickable element which is not a native HTML button
12286
12287
12288      attributes = assign({
12289        role: 'button'
12290      }, attributes);
12291      this.tabIndex_ = props.tabIndex;
12292
12293      var el = _Component.prototype.createEl.call(this, tag, props, attributes);
12294
12295      this.createControlTextEl(el);
12296      return el;
12297    };
12298
12299    _proto.dispose = function dispose() {
12300      // remove controlTextEl_ on dispose
12301      this.controlTextEl_ = null;
12302
12303      _Component.prototype.dispose.call(this);
12304    }
12305    /**
12306     * Create a control text element on this `ClickableComponent`
12307     *
12308     * @param {Element} [el]
12309     *        Parent element for the control text.
12310     *
12311     * @return {Element}
12312     *         The control text element that gets created.
12313     */
12314    ;
12315
12316    _proto.createControlTextEl = function createControlTextEl(el) {
12317      this.controlTextEl_ = createEl('span', {
12318        className: 'vjs-control-text'
12319      }, {
12320        // let the screen reader user know that the text of the element may change
12321        'aria-live': 'polite'
12322      });
12323
12324      if (el) {
12325        el.appendChild(this.controlTextEl_);
12326      }
12327
12328      this.controlText(this.controlText_, el);
12329      return this.controlTextEl_;
12330    }
12331    /**
12332     * Get or set the localize text to use for the controls on the `ClickableComponent`.
12333     *
12334     * @param {string} [text]
12335     *        Control text for element.
12336     *
12337     * @param {Element} [el=this.el()]
12338     *        Element to set the title on.
12339     *
12340     * @return {string}
12341     *         - The control text when getting
12342     */
12343    ;
12344
12345    _proto.controlText = function controlText(text, el) {
12346      if (el === void 0) {
12347        el = this.el();
12348      }
12349
12350      if (text === undefined) {
12351        return this.controlText_ || 'Need Text';
12352      }
12353
12354      var localizedText = this.localize(text);
12355      this.controlText_ = text;
12356      textContent(this.controlTextEl_, localizedText);
12357
12358      if (!this.nonIconControl) {
12359        // Set title attribute if only an icon is shown
12360        el.setAttribute('title', localizedText);
12361      }
12362    }
12363    /**
12364     * Builds the default DOM `className`.
12365     *
12366     * @return {string}
12367     *         The DOM `className` for this object.
12368     */
12369    ;
12370
12371    _proto.buildCSSClass = function buildCSSClass() {
12372      return "vjs-control vjs-button " + _Component.prototype.buildCSSClass.call(this);
12373    }
12374    /**
12375     * Enable this `ClickableComponent`
12376     */
12377    ;
12378
12379    _proto.enable = function enable() {
12380      if (!this.enabled_) {
12381        this.enabled_ = true;
12382        this.removeClass('vjs-disabled');
12383        this.el_.setAttribute('aria-disabled', 'false');
12384
12385        if (typeof this.tabIndex_ !== 'undefined') {
12386          this.el_.setAttribute('tabIndex', this.tabIndex_);
12387        }
12388
12389        this.on(['tap', 'click'], this.handleClick);
12390        this.on('keydown', this.handleKeyDown);
12391      }
12392    }
12393    /**
12394     * Disable this `ClickableComponent`
12395     */
12396    ;
12397
12398    _proto.disable = function disable() {
12399      this.enabled_ = false;
12400      this.addClass('vjs-disabled');
12401      this.el_.setAttribute('aria-disabled', 'true');
12402
12403      if (typeof this.tabIndex_ !== 'undefined') {
12404        this.el_.removeAttribute('tabIndex');
12405      }
12406
12407      this.off(['tap', 'click'], this.handleClick);
12408      this.off('keydown', this.handleKeyDown);
12409    }
12410    /**
12411     * Event handler that is called when a `ClickableComponent` receives a
12412     * `click` or `tap` event.
12413     *
12414     * @param {EventTarget~Event} event
12415     *        The `tap` or `click` event that caused this function to be called.
12416     *
12417     * @listens tap
12418     * @listens click
12419     * @abstract
12420     */
12421    ;
12422
12423    _proto.handleClick = function handleClick(event) {}
12424    /**
12425     * Event handler that is called when a `ClickableComponent` receives a
12426     * `keydown` event.
12427     *
12428     * By default, if the key is Space or Enter, it will trigger a `click` event.
12429     *
12430     * @param {EventTarget~Event} event
12431     *        The `keydown` event that caused this function to be called.
12432     *
12433     * @listens keydown
12434     */
12435    ;
12436
12437    _proto.handleKeyDown = function handleKeyDown(event) {
12438      // Support Space or Enter key operation to fire a click event. Also,
12439      // prevent the event from propagating through the DOM and triggering
12440      // Player hotkeys.
12441      if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
12442        event.preventDefault();
12443        event.stopPropagation();
12444        this.trigger('click');
12445      } else {
12446        // Pass keypress handling up for unsupported keys
12447        _Component.prototype.handleKeyDown.call(this, event);
12448      }
12449    };
12450
12451    return ClickableComponent;
12452  }(Component);
12453
12454  Component.registerComponent('ClickableComponent', ClickableComponent);
12455
12456  /**
12457   * A `ClickableComponent` that handles showing the poster image for the player.
12458   *
12459   * @extends ClickableComponent
12460   */
12461
12462  var PosterImage =
12463  /*#__PURE__*/
12464  function (_ClickableComponent) {
12465    _inheritsLoose(PosterImage, _ClickableComponent);
12466
12467    /**
12468     * Create an instance of this class.
12469     *
12470     * @param {Player} player
12471     *        The `Player` that this class should attach to.
12472     *
12473     * @param {Object} [options]
12474     *        The key/value store of player options.
12475     */
12476    function PosterImage(player, options) {
12477      var _this;
12478
12479      _this = _ClickableComponent.call(this, player, options) || this;
12480
12481      _this.update();
12482
12483      player.on('posterchange', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update));
12484      return _this;
12485    }
12486    /**
12487     * Clean up and dispose of the `PosterImage`.
12488     */
12489
12490
12491    var _proto = PosterImage.prototype;
12492
12493    _proto.dispose = function dispose() {
12494      this.player().off('posterchange', this.update);
12495
12496      _ClickableComponent.prototype.dispose.call(this);
12497    }
12498    /**
12499     * Create the `PosterImage`s DOM element.
12500     *
12501     * @return {Element}
12502     *         The element that gets created.
12503     */
12504    ;
12505
12506    _proto.createEl = function createEl$$1() {
12507      var el = createEl('div', {
12508        className: 'vjs-poster',
12509        // Don't want poster to be tabbable.
12510        tabIndex: -1
12511      });
12512      return el;
12513    }
12514    /**
12515     * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
12516     *
12517     * @listens Player#posterchange
12518     *
12519     * @param {EventTarget~Event} [event]
12520     *        The `Player#posterchange` event that triggered this function.
12521     */
12522    ;
12523
12524    _proto.update = function update(event) {
12525      var url = this.player().poster();
12526      this.setSrc(url); // If there's no poster source we should display:none on this component
12527      // so it's not still clickable or right-clickable
12528
12529      if (url) {
12530        this.show();
12531      } else {
12532        this.hide();
12533      }
12534    }
12535    /**
12536     * Set the source of the `PosterImage` depending on the display method.
12537     *
12538     * @param {string} url
12539     *        The URL to the source for the `PosterImage`.
12540     */
12541    ;
12542
12543    _proto.setSrc = function setSrc(url) {
12544      var backgroundImage = ''; // Any falsy value should stay as an empty string, otherwise
12545      // this will throw an extra error
12546
12547      if (url) {
12548        backgroundImage = "url(\"" + url + "\")";
12549      }
12550
12551      this.el_.style.backgroundImage = backgroundImage;
12552    }
12553    /**
12554     * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
12555     * {@link ClickableComponent#handleClick} for instances where this will be triggered.
12556     *
12557     * @listens tap
12558     * @listens click
12559     * @listens keydown
12560     *
12561     * @param {EventTarget~Event} event
12562     +        The `click`, `tap` or `keydown` event that caused this function to be called.
12563     */
12564    ;
12565
12566    _proto.handleClick = function handleClick(event) {
12567      // We don't want a click to trigger playback when controls are disabled
12568      if (!this.player_.controls()) {
12569        return;
12570      }
12571
12572      this.player_.tech(true).focus();
12573
12574      if (this.player_.paused()) {
12575        silencePromise(this.player_.play());
12576      } else {
12577        this.player_.pause();
12578      }
12579    };
12580
12581    return PosterImage;
12582  }(ClickableComponent);
12583
12584  Component.registerComponent('PosterImage', PosterImage);
12585
12586  var darkGray = '#222';
12587  var lightGray = '#ccc';
12588  var fontMap = {
12589    monospace: 'monospace',
12590    sansSerif: 'sans-serif',
12591    serif: 'serif',
12592    monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
12593    monospaceSerif: '"Courier New", monospace',
12594    proportionalSansSerif: 'sans-serif',
12595    proportionalSerif: 'serif',
12596    casual: '"Comic Sans MS", Impact, fantasy',
12597    script: '"Monotype Corsiva", cursive',
12598    smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
12599  };
12600  /**
12601   * Construct an rgba color from a given hex color code.
12602   *
12603   * @param {number} color
12604   *        Hex number for color, like #f0e or #f604e2.
12605   *
12606   * @param {number} opacity
12607   *        Value for opacity, 0.0 - 1.0.
12608   *
12609   * @return {string}
12610   *         The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
12611   */
12612
12613  function constructColor(color, opacity) {
12614    var hex;
12615
12616    if (color.length === 4) {
12617      // color looks like "#f0e"
12618      hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
12619    } else if (color.length === 7) {
12620      // color looks like "#f604e2"
12621      hex = color.slice(1);
12622    } else {
12623      throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
12624    }
12625
12626    return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
12627  }
12628  /**
12629   * Try to update the style of a DOM element. Some style changes will throw an error,
12630   * particularly in IE8. Those should be noops.
12631   *
12632   * @param {Element} el
12633   *        The DOM element to be styled.
12634   *
12635   * @param {string} style
12636   *        The CSS property on the element that should be styled.
12637   *
12638   * @param {string} rule
12639   *        The style rule that should be applied to the property.
12640   *
12641   * @private
12642   */
12643
12644  function tryUpdateStyle(el, style, rule) {
12645    try {
12646      el.style[style] = rule;
12647    } catch (e) {
12648      // Satisfies linter.
12649      return;
12650    }
12651  }
12652  /**
12653   * The component for displaying text track cues.
12654   *
12655   * @extends Component
12656   */
12657
12658
12659  var TextTrackDisplay =
12660  /*#__PURE__*/
12661  function (_Component) {
12662    _inheritsLoose(TextTrackDisplay, _Component);
12663
12664    /**
12665     * Creates an instance of this class.
12666     *
12667     * @param {Player} player
12668     *        The `Player` that this class should be attached to.
12669     *
12670     * @param {Object} [options]
12671     *        The key/value store of player options.
12672     *
12673     * @param {Component~ReadyCallback} [ready]
12674     *        The function to call when `TextTrackDisplay` is ready.
12675     */
12676    function TextTrackDisplay(player, options, ready) {
12677      var _this;
12678
12679      _this = _Component.call(this, player, options, ready) || this;
12680      var updateDisplayHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateDisplay);
12681      player.on('loadstart', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.toggleDisplay));
12682      player.on('texttrackchange', updateDisplayHandler);
12683      player.on('loadedmetadata', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.preselectTrack)); // This used to be called during player init, but was causing an error
12684      // if a track should show by default and the display hadn't loaded yet.
12685      // Should probably be moved to an external track loader when we support
12686      // tracks that don't need a display.
12687
12688      player.ready(bind(_assertThisInitialized(_assertThisInitialized(_this)), function () {
12689        if (player.tech_ && player.tech_.featuresNativeTextTracks) {
12690          this.hide();
12691          return;
12692        }
12693
12694        player.on('fullscreenchange', updateDisplayHandler);
12695        player.on('playerresize', updateDisplayHandler);
12696        window$1.addEventListener('orientationchange', updateDisplayHandler);
12697        player.on('dispose', function () {
12698          return window$1.removeEventListener('orientationchange', updateDisplayHandler);
12699        });
12700        var tracks = this.options_.playerOptions.tracks || [];
12701
12702        for (var i = 0; i < tracks.length; i++) {
12703          this.player_.addRemoteTextTrack(tracks[i], true);
12704        }
12705
12706        this.preselectTrack();
12707      }));
12708      return _this;
12709    }
12710    /**
12711    * Preselect a track following this precedence:
12712    * - matches the previously selected {@link TextTrack}'s language and kind
12713    * - matches the previously selected {@link TextTrack}'s language only
12714    * - is the first default captions track
12715    * - is the first default descriptions track
12716    *
12717    * @listens Player#loadstart
12718    */
12719
12720
12721    var _proto = TextTrackDisplay.prototype;
12722
12723    _proto.preselectTrack = function preselectTrack() {
12724      var modes = {
12725        captions: 1,
12726        subtitles: 1
12727      };
12728      var trackList = this.player_.textTracks();
12729      var userPref = this.player_.cache_.selectedLanguage;
12730      var firstDesc;
12731      var firstCaptions;
12732      var preferredTrack;
12733
12734      for (var i = 0; i < trackList.length; i++) {
12735        var track = trackList[i];
12736
12737        if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) {
12738          // Always choose the track that matches both language and kind
12739          if (track.kind === userPref.kind) {
12740            preferredTrack = track; // or choose the first track that matches language
12741          } else if (!preferredTrack) {
12742            preferredTrack = track;
12743          } // clear everything if offTextTrackMenuItem was clicked
12744
12745        } else if (userPref && !userPref.enabled) {
12746          preferredTrack = null;
12747          firstDesc = null;
12748          firstCaptions = null;
12749        } else if (track.default) {
12750          if (track.kind === 'descriptions' && !firstDesc) {
12751            firstDesc = track;
12752          } else if (track.kind in modes && !firstCaptions) {
12753            firstCaptions = track;
12754          }
12755        }
12756      } // The preferredTrack matches the user preference and takes
12757      // precedence over all the other tracks.
12758      // So, display the preferredTrack before the first default track
12759      // and the subtitles/captions track before the descriptions track
12760
12761
12762      if (preferredTrack) {
12763        preferredTrack.mode = 'showing';
12764      } else if (firstCaptions) {
12765        firstCaptions.mode = 'showing';
12766      } else if (firstDesc) {
12767        firstDesc.mode = 'showing';
12768      }
12769    }
12770    /**
12771     * Turn display of {@link TextTrack}'s from the current state into the other state.
12772     * There are only two states:
12773     * - 'shown'
12774     * - 'hidden'
12775     *
12776     * @listens Player#loadstart
12777     */
12778    ;
12779
12780    _proto.toggleDisplay = function toggleDisplay() {
12781      if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
12782        this.hide();
12783      } else {
12784        this.show();
12785      }
12786    }
12787    /**
12788     * Create the {@link Component}'s DOM element.
12789     *
12790     * @return {Element}
12791     *         The element that was created.
12792     */
12793    ;
12794
12795    _proto.createEl = function createEl() {
12796      return _Component.prototype.createEl.call(this, 'div', {
12797        className: 'vjs-text-track-display'
12798      }, {
12799        'aria-live': 'off',
12800        'aria-atomic': 'true'
12801      });
12802    }
12803    /**
12804     * Clear all displayed {@link TextTrack}s.
12805     */
12806    ;
12807
12808    _proto.clearDisplay = function clearDisplay() {
12809      if (typeof window$1.WebVTT === 'function') {
12810        window$1.WebVTT.processCues(window$1, [], this.el_);
12811      }
12812    }
12813    /**
12814     * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
12815     * a {@link Player#fullscreenchange} is fired.
12816     *
12817     * @listens Player#texttrackchange
12818     * @listens Player#fullscreenchange
12819     */
12820    ;
12821
12822    _proto.updateDisplay = function updateDisplay() {
12823      var tracks = this.player_.textTracks();
12824      this.clearDisplay(); // Track display prioritization model: if multiple tracks are 'showing',
12825      //  display the first 'subtitles' or 'captions' track which is 'showing',
12826      //  otherwise display the first 'descriptions' track which is 'showing'
12827
12828      var descriptionsTrack = null;
12829      var captionsSubtitlesTrack = null;
12830      var i = tracks.length;
12831
12832      while (i--) {
12833        var track = tracks[i];
12834
12835        if (track.mode === 'showing') {
12836          if (track.kind === 'descriptions') {
12837            descriptionsTrack = track;
12838          } else {
12839            captionsSubtitlesTrack = track;
12840          }
12841        }
12842      }
12843
12844      if (captionsSubtitlesTrack) {
12845        if (this.getAttribute('aria-live') !== 'off') {
12846          this.setAttribute('aria-live', 'off');
12847        }
12848
12849        this.updateForTrack(captionsSubtitlesTrack);
12850      } else if (descriptionsTrack) {
12851        if (this.getAttribute('aria-live') !== 'assertive') {
12852          this.setAttribute('aria-live', 'assertive');
12853        }
12854
12855        this.updateForTrack(descriptionsTrack);
12856      }
12857    }
12858    /**
12859     * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
12860     *
12861     * @param {TextTrack} track
12862     *        Text track object to be added to the list.
12863     */
12864    ;
12865
12866    _proto.updateForTrack = function updateForTrack(track) {
12867      if (typeof window$1.WebVTT !== 'function' || !track.activeCues) {
12868        return;
12869      }
12870
12871      var cues = [];
12872
12873      for (var _i = 0; _i < track.activeCues.length; _i++) {
12874        cues.push(track.activeCues[_i]);
12875      }
12876
12877      window$1.WebVTT.processCues(window$1, cues, this.el_);
12878
12879      if (!this.player_.textTrackSettings) {
12880        return;
12881      }
12882
12883      var overrides = this.player_.textTrackSettings.getValues();
12884      var i = cues.length;
12885
12886      while (i--) {
12887        var cue = cues[i];
12888
12889        if (!cue) {
12890          continue;
12891        }
12892
12893        var cueDiv = cue.displayState;
12894
12895        if (overrides.color) {
12896          cueDiv.firstChild.style.color = overrides.color;
12897        }
12898
12899        if (overrides.textOpacity) {
12900          tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
12901        }
12902
12903        if (overrides.backgroundColor) {
12904          cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
12905        }
12906
12907        if (overrides.backgroundOpacity) {
12908          tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
12909        }
12910
12911        if (overrides.windowColor) {
12912          if (overrides.windowOpacity) {
12913            tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
12914          } else {
12915            cueDiv.style.backgroundColor = overrides.windowColor;
12916          }
12917        }
12918
12919        if (overrides.edgeStyle) {
12920          if (overrides.edgeStyle === 'dropshadow') {
12921            cueDiv.firstChild.style.textShadow = "2px 2px 3px " + darkGray + ", 2px 2px 4px " + darkGray + ", 2px 2px 5px " + darkGray;
12922          } else if (overrides.edgeStyle === 'raised') {
12923            cueDiv.firstChild.style.textShadow = "1px 1px " + darkGray + ", 2px 2px " + darkGray + ", 3px 3px " + darkGray;
12924          } else if (overrides.edgeStyle === 'depressed') {
12925            cueDiv.firstChild.style.textShadow = "1px 1px " + lightGray + ", 0 1px " + lightGray + ", -1px -1px " + darkGray + ", 0 -1px " + darkGray;
12926          } else if (overrides.edgeStyle === 'uniform') {
12927            cueDiv.firstChild.style.textShadow = "0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray;
12928          }
12929        }
12930
12931        if (overrides.fontPercent && overrides.fontPercent !== 1) {
12932          var fontSize = window$1.parseFloat(cueDiv.style.fontSize);
12933          cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
12934          cueDiv.style.height = 'auto';
12935          cueDiv.style.top = 'auto';
12936          cueDiv.style.bottom = '2px';
12937        }
12938
12939        if (overrides.fontFamily && overrides.fontFamily !== 'default') {
12940          if (overrides.fontFamily === 'small-caps') {
12941            cueDiv.firstChild.style.fontVariant = 'small-caps';
12942          } else {
12943            cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
12944          }
12945        }
12946      }
12947    };
12948
12949    return TextTrackDisplay;
12950  }(Component);
12951
12952  Component.registerComponent('TextTrackDisplay', TextTrackDisplay);
12953
12954  /**
12955   * A loading spinner for use during waiting/loading events.
12956   *
12957   * @extends Component
12958   */
12959
12960  var LoadingSpinner =
12961  /*#__PURE__*/
12962  function (_Component) {
12963    _inheritsLoose(LoadingSpinner, _Component);
12964
12965    function LoadingSpinner() {
12966      return _Component.apply(this, arguments) || this;
12967    }
12968
12969    var _proto = LoadingSpinner.prototype;
12970
12971    /**
12972     * Create the `LoadingSpinner`s DOM element.
12973     *
12974     * @return {Element}
12975     *         The dom element that gets created.
12976     */
12977    _proto.createEl = function createEl$$1() {
12978      var isAudio = this.player_.isAudio();
12979      var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
12980      var controlText = createEl('span', {
12981        className: 'vjs-control-text',
12982        innerHTML: this.localize('{1} is loading.', [playerType])
12983      });
12984
12985      var el = _Component.prototype.createEl.call(this, 'div', {
12986        className: 'vjs-loading-spinner',
12987        dir: 'ltr'
12988      });
12989
12990      el.appendChild(controlText);
12991      return el;
12992    };
12993
12994    return LoadingSpinner;
12995  }(Component);
12996
12997  Component.registerComponent('LoadingSpinner', LoadingSpinner);
12998
12999  /**
13000   * Base class for all buttons.
13001   *
13002   * @extends ClickableComponent
13003   */
13004
13005  var Button =
13006  /*#__PURE__*/
13007  function (_ClickableComponent) {
13008    _inheritsLoose(Button, _ClickableComponent);
13009
13010    function Button() {
13011      return _ClickableComponent.apply(this, arguments) || this;
13012    }
13013
13014    var _proto = Button.prototype;
13015
13016    /**
13017     * Create the `Button`s DOM element.
13018     *
13019     * @param {string} [tag="button"]
13020     *        The element's node type. This argument is IGNORED: no matter what
13021     *        is passed, it will always create a `button` element.
13022     *
13023     * @param {Object} [props={}]
13024     *        An object of properties that should be set on the element.
13025     *
13026     * @param {Object} [attributes={}]
13027     *        An object of attributes that should be set on the element.
13028     *
13029     * @return {Element}
13030     *         The element that gets created.
13031     */
13032    _proto.createEl = function createEl(tag, props, attributes) {
13033      if (props === void 0) {
13034        props = {};
13035      }
13036
13037      if (attributes === void 0) {
13038        attributes = {};
13039      }
13040
13041      tag = 'button';
13042      props = assign({
13043        innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
13044        className: this.buildCSSClass()
13045      }, props); // Add attributes for button element
13046
13047      attributes = assign({
13048        // Necessary since the default button type is "submit"
13049        type: 'button'
13050      }, attributes);
13051      var el = Component.prototype.createEl.call(this, tag, props, attributes);
13052      this.createControlTextEl(el);
13053      return el;
13054    }
13055    /**
13056     * Add a child `Component` inside of this `Button`.
13057     *
13058     * @param {string|Component} child
13059     *        The name or instance of a child to add.
13060     *
13061     * @param {Object} [options={}]
13062     *        The key/value store of options that will get passed to children of
13063     *        the child.
13064     *
13065     * @return {Component}
13066     *         The `Component` that gets added as a child. When using a string the
13067     *         `Component` will get created by this process.
13068     *
13069     * @deprecated since version 5
13070     */
13071    ;
13072
13073    _proto.addChild = function addChild(child, options) {
13074      if (options === void 0) {
13075        options = {};
13076      }
13077
13078      var className = this.constructor.name;
13079      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
13080
13081      return Component.prototype.addChild.call(this, child, options);
13082    }
13083    /**
13084     * Enable the `Button` element so that it can be activated or clicked. Use this with
13085     * {@link Button#disable}.
13086     */
13087    ;
13088
13089    _proto.enable = function enable() {
13090      _ClickableComponent.prototype.enable.call(this);
13091
13092      this.el_.removeAttribute('disabled');
13093    }
13094    /**
13095     * Disable the `Button` element so that it cannot be activated or clicked. Use this with
13096     * {@link Button#enable}.
13097     */
13098    ;
13099
13100    _proto.disable = function disable() {
13101      _ClickableComponent.prototype.disable.call(this);
13102
13103      this.el_.setAttribute('disabled', 'disabled');
13104    }
13105    /**
13106     * This gets called when a `Button` has focus and `keydown` is triggered via a key
13107     * press.
13108     *
13109     * @param {EventTarget~Event} event
13110     *        The event that caused this function to get called.
13111     *
13112     * @listens keydown
13113     */
13114    ;
13115
13116    _proto.handleKeyDown = function handleKeyDown(event) {
13117      // Ignore Space or Enter key operation, which is handled by the browser for
13118      // a button - though not for its super class, ClickableComponent. Also,
13119      // prevent the event from propagating through the DOM and triggering Player
13120      // hotkeys. We do not preventDefault here because we _want_ the browser to
13121      // handle it.
13122      if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
13123        event.stopPropagation();
13124        return;
13125      } // Pass keypress handling up for unsupported keys
13126
13127
13128      _ClickableComponent.prototype.handleKeyDown.call(this, event);
13129    };
13130
13131    return Button;
13132  }(ClickableComponent);
13133
13134  Component.registerComponent('Button', Button);
13135
13136  /**
13137   * The initial play button that shows before the video has played. The hiding of the
13138   * `BigPlayButton` get done via CSS and `Player` states.
13139   *
13140   * @extends Button
13141   */
13142
13143  var BigPlayButton =
13144  /*#__PURE__*/
13145  function (_Button) {
13146    _inheritsLoose(BigPlayButton, _Button);
13147
13148    function BigPlayButton(player, options) {
13149      var _this;
13150
13151      _this = _Button.call(this, player, options) || this;
13152      _this.mouseused_ = false;
13153
13154      _this.on('mousedown', _this.handleMouseDown);
13155
13156      return _this;
13157    }
13158    /**
13159     * Builds the default DOM `className`.
13160     *
13161     * @return {string}
13162     *         The DOM `className` for this object. Always returns 'vjs-big-play-button'.
13163     */
13164
13165
13166    var _proto = BigPlayButton.prototype;
13167
13168    _proto.buildCSSClass = function buildCSSClass() {
13169      return 'vjs-big-play-button';
13170    }
13171    /**
13172     * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
13173     * for more detailed information on what a click can be.
13174     *
13175     * @param {EventTarget~Event} event
13176     *        The `keydown`, `tap`, or `click` event that caused this function to be
13177     *        called.
13178     *
13179     * @listens tap
13180     * @listens click
13181     */
13182    ;
13183
13184    _proto.handleClick = function handleClick(event) {
13185      var playPromise = this.player_.play(); // exit early if clicked via the mouse
13186
13187      if (this.mouseused_ && event.clientX && event.clientY) {
13188        silencePromise(playPromise);
13189        this.player_.tech(true).focus();
13190        return;
13191      }
13192
13193      var cb = this.player_.getChild('controlBar');
13194      var playToggle = cb && cb.getChild('playToggle');
13195
13196      if (!playToggle) {
13197        this.player_.tech(true).focus();
13198        return;
13199      }
13200
13201      var playFocus = function playFocus() {
13202        return playToggle.focus();
13203      };
13204
13205      if (isPromise(playPromise)) {
13206        playPromise.then(playFocus, function () {});
13207      } else {
13208        this.setTimeout(playFocus, 1);
13209      }
13210    };
13211
13212    _proto.handleKeyDown = function handleKeyDown(event) {
13213      this.mouseused_ = false;
13214
13215      _Button.prototype.handleKeyDown.call(this, event);
13216    };
13217
13218    _proto.handleMouseDown = function handleMouseDown(event) {
13219      this.mouseused_ = true;
13220    };
13221
13222    return BigPlayButton;
13223  }(Button);
13224  /**
13225   * The text that should display over the `BigPlayButton`s controls. Added to for localization.
13226   *
13227   * @type {string}
13228   * @private
13229   */
13230
13231
13232  BigPlayButton.prototype.controlText_ = 'Play Video';
13233  Component.registerComponent('BigPlayButton', BigPlayButton);
13234
13235  /**
13236   * The `CloseButton` is a `{@link Button}` that fires a `close` event when
13237   * it gets clicked.
13238   *
13239   * @extends Button
13240   */
13241
13242  var CloseButton =
13243  /*#__PURE__*/
13244  function (_Button) {
13245    _inheritsLoose(CloseButton, _Button);
13246
13247    /**
13248    * Creates an instance of the this class.
13249    *
13250    * @param  {Player} player
13251    *         The `Player` that this class should be attached to.
13252    *
13253    * @param  {Object} [options]
13254    *         The key/value store of player options.
13255    */
13256    function CloseButton(player, options) {
13257      var _this;
13258
13259      _this = _Button.call(this, player, options) || this;
13260
13261      _this.controlText(options && options.controlText || _this.localize('Close'));
13262
13263      return _this;
13264    }
13265    /**
13266    * Builds the default DOM `className`.
13267    *
13268    * @return {string}
13269    *         The DOM `className` for this object.
13270    */
13271
13272
13273    var _proto = CloseButton.prototype;
13274
13275    _proto.buildCSSClass = function buildCSSClass() {
13276      return "vjs-close-button " + _Button.prototype.buildCSSClass.call(this);
13277    }
13278    /**
13279     * This gets called when a `CloseButton` gets clicked. See
13280     * {@link ClickableComponent#handleClick} for more information on when
13281     * this will be triggered
13282     *
13283     * @param {EventTarget~Event} event
13284     *        The `keydown`, `tap`, or `click` event that caused this function to be
13285     *        called.
13286     *
13287     * @listens tap
13288     * @listens click
13289     * @fires CloseButton#close
13290     */
13291    ;
13292
13293    _proto.handleClick = function handleClick(event) {
13294      /**
13295       * Triggered when the a `CloseButton` is clicked.
13296       *
13297       * @event CloseButton#close
13298       * @type {EventTarget~Event}
13299       *
13300       * @property {boolean} [bubbles=false]
13301       *           set to false so that the close event does not
13302       *           bubble up to parents if there is no listener
13303       */
13304      this.trigger({
13305        type: 'close',
13306        bubbles: false
13307      });
13308    };
13309
13310    return CloseButton;
13311  }(Button);
13312
13313  Component.registerComponent('CloseButton', CloseButton);
13314
13315  /**
13316   * Button to toggle between play and pause.
13317   *
13318   * @extends Button
13319   */
13320
13321  var PlayToggle =
13322  /*#__PURE__*/
13323  function (_Button) {
13324    _inheritsLoose(PlayToggle, _Button);
13325
13326    /**
13327     * Creates an instance of this class.
13328     *
13329     * @param {Player} player
13330     *        The `Player` that this class should be attached to.
13331     *
13332     * @param {Object} [options={}]
13333     *        The key/value store of player options.
13334     */
13335    function PlayToggle(player, options) {
13336      var _this;
13337
13338      if (options === void 0) {
13339        options = {};
13340      }
13341
13342      _this = _Button.call(this, player, options) || this; // show or hide replay icon
13343
13344      options.replay = options.replay === undefined || options.replay;
13345
13346      _this.on(player, 'play', _this.handlePlay);
13347
13348      _this.on(player, 'pause', _this.handlePause);
13349
13350      if (options.replay) {
13351        _this.on(player, 'ended', _this.handleEnded);
13352      }
13353
13354      return _this;
13355    }
13356    /**
13357     * Builds the default DOM `className`.
13358     *
13359     * @return {string}
13360     *         The DOM `className` for this object.
13361     */
13362
13363
13364    var _proto = PlayToggle.prototype;
13365
13366    _proto.buildCSSClass = function buildCSSClass() {
13367      return "vjs-play-control " + _Button.prototype.buildCSSClass.call(this);
13368    }
13369    /**
13370     * This gets called when an `PlayToggle` is "clicked". See
13371     * {@link ClickableComponent} for more detailed information on what a click can be.
13372     *
13373     * @param {EventTarget~Event} [event]
vendor: 9,894 bytes, lines 13374-13723
13374     *        The `keydown`, `tap`, or `click` event that caused this function to be
13375     *        called.
13376     *
13377     * @listens tap
13378     * @listens click
13379     */
13380    ;
13381
13382    _proto.handleClick = function handleClick(event) {
13383      if (this.player_.paused()) {
13384        this.player_.play();
13385      } else {
13386        this.player_.pause();
13387      }
13388    }
13389    /**
13390     * This gets called once after the video has ended and the user seeks so that
13391     * we can change the replay button back to a play button.
13392     *
13393     * @param {EventTarget~Event} [event]
13394     *        The event that caused this function to run.
13395     *
13396     * @listens Player#seeked
13397     */
13398    ;
13399
13400    _proto.handleSeeked = function handleSeeked(event) {
13401      this.removeClass('vjs-ended');
13402
13403      if (this.player_.paused()) {
13404        this.handlePause(event);
13405      } else {
13406        this.handlePlay(event);
13407      }
13408    }
13409    /**
13410     * Add the vjs-playing class to the element so it can change appearance.
13411     *
13412     * @param {EventTarget~Event} [event]
13413     *        The event that caused this function to run.
13414     *
13415     * @listens Player#play
13416     */
13417    ;
13418
13419    _proto.handlePlay = function handlePlay(event) {
13420      this.removeClass('vjs-ended');
13421      this.removeClass('vjs-paused');
13422      this.addClass('vjs-playing'); // change the button text to "Pause"
13423
13424      this.controlText('Pause');
13425    }
13426    /**
13427     * Add the vjs-paused class to the element so it can change appearance.
13428     *
13429     * @param {EventTarget~Event} [event]
13430     *        The event that caused this function to run.
13431     *
13432     * @listens Player#pause
13433     */
13434    ;
13435
13436    _proto.handlePause = function handlePause(event) {
13437      this.removeClass('vjs-playing');
13438      this.addClass('vjs-paused'); // change the button text to "Play"
13439
13440      this.controlText('Play');
13441    }
13442    /**
13443     * Add the vjs-ended class to the element so it can change appearance
13444     *
13445     * @param {EventTarget~Event} [event]
13446     *        The event that caused this function to run.
13447     *
13448     * @listens Player#ended
13449     */
13450    ;
13451
13452    _proto.handleEnded = function handleEnded(event) {
13453      this.removeClass('vjs-playing');
13454      this.addClass('vjs-ended'); // change the button text to "Replay"
13455
13456      this.controlText('Replay'); // on the next seek remove the replay button
13457
13458      this.one(this.player_, 'seeked', this.handleSeeked);
13459    };
13460
13461    return PlayToggle;
13462  }(Button);
13463  /**
13464   * The text that should display over the `PlayToggle`s controls. Added for localization.
13465   *
13466   * @type {string}
13467   * @private
13468   */
13469
13470
13471  PlayToggle.prototype.controlText_ = 'Play';
13472  Component.registerComponent('PlayToggle', PlayToggle);
13473
13474  /**
13475   * @file format-time.js
13476   * @module format-time
13477   */
13478
13479  /**
13480   * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
13481   * seconds) will force a number of leading zeros to cover the length of the
13482   * guide.
13483   *
13484   * @private
13485   * @param  {number} seconds
13486   *         Number of seconds to be turned into a string
13487   *
13488   * @param  {number} guide
13489   *         Number (in seconds) to model the string after
13490   *
13491   * @return {string}
13492   *         Time formatted as H:MM:SS or M:SS
13493   */
13494  var defaultImplementation = function defaultImplementation(seconds, guide) {
13495    seconds = seconds < 0 ? 0 : seconds;
13496    var s = Math.floor(seconds % 60);
13497    var m = Math.floor(seconds / 60 % 60);
13498    var h = Math.floor(seconds / 3600);
13499    var gm = Math.floor(guide / 60 % 60);
13500    var gh = Math.floor(guide / 3600); // handle invalid times
13501
13502    if (isNaN(seconds) || seconds === Infinity) {
13503      // '-' is false for all relational operators (e.g. <, >=) so this setting
13504      // will add the minimum number of fields specified by the guide
13505      h = m = s = '-';
13506    } // Check if we need to show hours
13507
13508
13509    h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero.
13510    // Always show at least one digit of minutes.
13511
13512    m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds
13513
13514    s = s < 10 ? '0' + s : s;
13515    return h + m + s;
13516  }; // Internal pointer to the current implementation.
13517
13518
13519  var implementation = defaultImplementation;
13520  /**
13521   * Replaces the default formatTime implementation with a custom implementation.
13522   *
13523   * @param {Function} customImplementation
13524   *        A function which will be used in place of the default formatTime
13525   *        implementation. Will receive the current time in seconds and the
13526   *        guide (in seconds) as arguments.
13527   */
13528
13529  function setFormatTime(customImplementation) {
13530    implementation = customImplementation;
13531  }
13532  /**
13533   * Resets formatTime to the default implementation.
13534   */
13535
13536  function resetFormatTime() {
13537    implementation = defaultImplementation;
13538  }
13539  /**
13540   * Delegates to either the default time formatting function or a custom
13541   * function supplied via `setFormatTime`.
13542   *
13543   * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
13544   * guide (in seconds) will force a number of leading zeros to cover the
13545   * length of the guide.
13546   *
13547   * @static
13548   * @example  formatTime(125, 600) === "02:05"
13549   * @param    {number} seconds
13550   *           Number of seconds to be turned into a string
13551   *
13552   * @param    {number} guide
13553   *           Number (in seconds) to model the string after
13554   *
13555   * @return   {string}
13556   *           Time formatted as H:MM:SS or M:SS
13557   */
13558
13559  function formatTime(seconds, guide) {
13560    if (guide === void 0) {
13561      guide = seconds;
13562    }
13563
13564    return implementation(seconds, guide);
13565  }
13566
13567  /**
13568   * Displays time information about the video
13569   *
13570   * @extends Component
13571   */
13572
13573  var TimeDisplay =
13574  /*#__PURE__*/
13575  function (_Component) {
13576    _inheritsLoose(TimeDisplay, _Component);
13577
13578    /**
13579     * Creates an instance of this class.
13580     *
13581     * @param {Player} player
13582     *        The `Player` that this class should be attached to.
13583     *
13584     * @param {Object} [options]
13585     *        The key/value store of player options.
13586     */
13587    function TimeDisplay(player, options) {
13588      var _this;
13589
13590      _this = _Component.call(this, player, options) || this;
13591      _this.throttledUpdateContent = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateContent), 25);
13592
13593      _this.on(player, 'timeupdate', _this.throttledUpdateContent);
13594
13595      return _this;
13596    }
13597    /**
13598     * Create the `Component`'s DOM element
13599     *
13600     * @return {Element}
13601     *         The element that was created.
13602     */
13603
13604
13605    var _proto = TimeDisplay.prototype;
13606
13607    _proto.createEl = function createEl$$1() {
13608      var className = this.buildCSSClass();
13609
13610      var el = _Component.prototype.createEl.call(this, 'div', {
13611        className: className + " vjs-time-control vjs-control",
13612        innerHTML: "<span class=\"vjs-control-text\" role=\"presentation\">" + this.localize(this.labelText_) + "\xA0</span>"
13613      });
13614
13615      this.contentEl_ = createEl('span', {
13616        className: className + "-display"
13617      }, {
13618        // tell screen readers not to automatically read the time as it changes
13619        'aria-live': 'off',
13620        // span elements have no implicit role, but some screen readers (notably VoiceOver)
13621        // treat them as a break between items in the DOM when using arrow keys
13622        // (or left-to-right swipes on iOS) to read contents of a page. Using
13623        // role='presentation' causes VoiceOver to NOT treat this span as a break.
13624        'role': 'presentation'
13625      });
13626      this.updateTextNode_();
13627      el.appendChild(this.contentEl_);
13628      return el;
13629    };
13630
13631    _proto.dispose = function dispose() {
13632      this.contentEl_ = null;
13633      this.textNode_ = null;
13634
13635      _Component.prototype.dispose.call(this);
13636    }
13637    /**
13638     * Updates the "remaining time" text node with new content using the
13639     * contents of the `formattedTime_` property.
13640     *
13641     * @private
13642     */
13643    ;
13644
13645    _proto.updateTextNode_ = function updateTextNode_() {
13646      if (!this.contentEl_) {
13647        return;
13648      }
13649
13650      while (this.contentEl_.firstChild) {
13651        this.contentEl_.removeChild(this.contentEl_.firstChild);
13652      }
13653
13654      this.textNode_ = document.createTextNode(this.formattedTime_ || this.formatTime_(0));
13655      this.contentEl_.appendChild(this.textNode_);
13656    }
13657    /**
13658     * Generates a formatted time for this component to use in display.
13659     *
13660     * @param  {number} time
13661     *         A numeric time, in seconds.
13662     *
13663     * @return {string}
13664     *         A formatted time
13665     *
13666     * @private
13667     */
13668    ;
13669
13670    _proto.formatTime_ = function formatTime_(time) {
13671      return formatTime(time);
13672    }
13673    /**
13674     * Updates the time display text node if it has what was passed in changed
13675     * the formatted time.
13676     *
13677     * @param {number} time
13678     *        The time to update to
13679     *
13680     * @private
13681     */
13682    ;
13683
13684    _proto.updateFormattedTime_ = function updateFormattedTime_(time) {
13685      var formattedTime = this.formatTime_(time);
13686
13687      if (formattedTime === this.formattedTime_) {
13688        return;
13689      }
13690
13691      this.formattedTime_ = formattedTime;
13692      this.requestAnimationFrame(this.updateTextNode_);
13693    }
13694    /**
13695     * To be filled out in the child class, should update the displayed time
13696     * in accordance with the fact that the current time has changed.
13697     *
13698     * @param {EventTarget~Event} [event]
13699     *        The `timeupdate`  event that caused this to run.
13700     *
13701     * @listens Player#timeupdate
13702     */
13703    ;
13704
13705    _proto.updateContent = function updateContent(event) {};
13706
13707    return TimeDisplay;
13708  }(Component);
13709  /**
13710   * The text that is added to the `TimeDisplay` for screen reader users.
13711   *
13712   * @type {string}
13713   * @private
13714   */
13715
13716
13717  TimeDisplay.prototype.labelText_ = 'Time';
13718  /**
13719   * The text that should display over the `TimeDisplay`s controls. Added to for localization.
13720   *
13721   * @type {string}
13722   * @private
13723   *
vendor: 5,455 bytes, lines 13724-13922
13724   * @deprecated in v7; controlText_ is not used in non-active display Components
13725   */
13726
13727  TimeDisplay.prototype.controlText_ = 'Time';
13728  Component.registerComponent('TimeDisplay', TimeDisplay);
13729
13730  /**
13731   * Displays the current time
13732   *
13733   * @extends Component
13734   */
13735
13736  var CurrentTimeDisplay =
13737  /*#__PURE__*/
13738  function (_TimeDisplay) {
13739    _inheritsLoose(CurrentTimeDisplay, _TimeDisplay);
13740
13741    /**
13742     * Creates an instance of this class.
13743     *
13744     * @param {Player} player
13745     *        The `Player` that this class should be attached to.
13746     *
13747     * @param {Object} [options]
13748     *        The key/value store of player options.
13749     */
13750    function CurrentTimeDisplay(player, options) {
13751      var _this;
13752
13753      _this = _TimeDisplay.call(this, player, options) || this;
13754
13755      _this.on(player, 'ended', _this.handleEnded);
13756
13757      return _this;
13758    }
13759    /**
13760     * Builds the default DOM `className`.
13761     *
13762     * @return {string}
13763     *         The DOM `className` for this object.
13764     */
13765
13766
13767    var _proto = CurrentTimeDisplay.prototype;
13768
13769    _proto.buildCSSClass = function buildCSSClass() {
13770      return 'vjs-current-time';
13771    }
13772    /**
13773     * Update current time display
13774     *
13775     * @param {EventTarget~Event} [event]
13776     *        The `timeupdate` event that caused this function to run.
13777     *
13778     * @listens Player#timeupdate
13779     */
13780    ;
13781
13782    _proto.updateContent = function updateContent(event) {
13783      // Allows for smooth scrubbing, when player can't keep up.
13784      var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
13785      this.updateFormattedTime_(time);
13786    }
13787    /**
13788     * When the player fires ended there should be no time left. Sadly
13789     * this is not always the case, lets make it seem like that is the case
13790     * for users.
13791     *
13792     * @param {EventTarget~Event} [event]
13793     *        The `ended` event that caused this to run.
13794     *
13795     * @listens Player#ended
13796     */
13797    ;
13798
13799    _proto.handleEnded = function handleEnded(event) {
13800      if (!this.player_.duration()) {
13801        return;
13802      }
13803
13804      this.updateFormattedTime_(this.player_.duration());
13805    };
13806
13807    return CurrentTimeDisplay;
13808  }(TimeDisplay);
13809  /**
13810   * The text that is added to the `CurrentTimeDisplay` for screen reader users.
13811   *
13812   * @type {string}
13813   * @private
13814   */
13815
13816
13817  CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
13818  /**
13819   * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
13820   *
13821   * @type {string}
13822   * @private
13823   *
13824   * @deprecated in v7; controlText_ is not used in non-active display Components
13825   */
13826
13827  CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
13828  Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
13829
13830  /**
13831   * Displays the duration
13832   *
13833   * @extends Component
13834   */
13835
13836  var DurationDisplay =
13837  /*#__PURE__*/
13838  function (_TimeDisplay) {
13839    _inheritsLoose(DurationDisplay, _TimeDisplay);
13840
13841    /**
13842     * Creates an instance of this class.
13843     *
13844     * @param {Player} player
13845     *        The `Player` that this class should be attached to.
13846     *
13847     * @param {Object} [options]
13848     *        The key/value store of player options.
13849     */
13850    function DurationDisplay(player, options) {
13851      var _this;
13852
13853      _this = _TimeDisplay.call(this, player, options) || this; // we do not want to/need to throttle duration changes,
13854      // as they should always display the changed duration as
13855      // it has changed
13856
13857      _this.on(player, 'durationchange', _this.updateContent); // Listen to loadstart because the player duration is reset when a new media element is loaded,
13858      // but the durationchange on the user agent will not fire.
13859      // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
13860
13861
13862      _this.on(player, 'loadstart', _this.updateContent); // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
13863      // listeners could have broken dependent applications/libraries. These
13864      // can likely be removed for 7.0.
13865
13866
13867      _this.on(player, 'loadedmetadata', _this.throttledUpdateContent);
13868
13869      return _this;
13870    }
13871    /**
13872     * Builds the default DOM `className`.
13873     *
13874     * @return {string}
13875     *         The DOM `className` for this object.
13876     */
13877
13878
13879    var _proto = DurationDisplay.prototype;
13880
13881    _proto.buildCSSClass = function buildCSSClass() {
13882      return 'vjs-duration';
13883    }
13884    /**
13885     * Update duration time display.
13886     *
13887     * @param {EventTarget~Event} [event]
13888     *        The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
13889     *        this function to be called.
13890     *
13891     * @listens Player#durationchange
13892     * @listens Player#timeupdate
13893     * @listens Player#loadedmetadata
13894     */
13895    ;
13896
13897    _proto.updateContent = function updateContent(event) {
13898      var duration = this.player_.duration();
13899
13900      if (this.duration_ !== duration) {
13901        this.duration_ = duration;
13902        this.updateFormattedTime_(duration);
13903      }
13904    };
13905
13906    return DurationDisplay;
13907  }(TimeDisplay);
13908  /**
13909   * The text that is added to the `DurationDisplay` for screen reader users.
13910   *
13911   * @type {string}
13912   * @private
13913   */
13914
13915
13916  DurationDisplay.prototype.labelText_ = 'Duration';
13917  /**
13918   * The text that should display over the `DurationDisplay`s controls. Added to for localization.
13919   *
13920   * @type {string}
13921   * @private
13922   *
13923   * @deprecated in v7; controlText_ is not used in non-active display Components
13924   */
13925
13926  DurationDisplay.prototype.controlText_ = 'Duration';
13927  Component.registerComponent('DurationDisplay', DurationDisplay);
13928
13929  /**
13930   * The separator between the current time and duration.
13931   * Can be hidden if it's not needed in the design.
13932   *
13933   * @extends Component
13934   */
13935
13936  var TimeDivider =
13937  /*#__PURE__*/
13938  function (_Component) {
13939    _inheritsLoose(TimeDivider, _Component);
13940
13941    function TimeDivider() {
13942      return _Component.apply(this, arguments) || this;
13943    }
13944
13945    var _proto = TimeDivider.prototype;
13946
13947    /**
13948     * Create the component's DOM element
13949     *
13950     * @return {Element}
13951     *         The element that was created.
13952     */
13953    _proto.createEl = function createEl() {
13954      return _Component.prototype.createEl.call(this, 'div', {
13955        className: 'vjs-time-control vjs-time-divider',
13956        innerHTML: '<div><span>/</span></div>'
13957      }, {
13958        // this element and its contents can be hidden from assistive techs since
13959        // it is made extraneous by the announcement of the control text
13960        // for the current time and duration displays
13961        'aria-hidden': true
13962      });
13963    };
13964
13965    return TimeDivider;
13966  }(Component);
13967
13968  Component.registerComponent('TimeDivider', TimeDivider);
13969
13970  /**
13971   * Displays the time left in the video
13972   *
13973   * @extends Component
13974   */
13975
13976  var RemainingTimeDisplay =
13977  /*#__PURE__*/
13978  function (_TimeDisplay) {
13979    _inheritsLoose(RemainingTimeDisplay, _TimeDisplay);
13980
13981    /**
13982     * Creates an instance of this class.
13983     *
13984     * @param {Player} player
13985     *        The `Player` that this class should be attached to.
13986     *
13987     * @param {Object} [options]
13988     *        The key/value store of player options.
13989     */
13990    function RemainingTimeDisplay(player, options) {
13991      var _this;
13992
13993      _this = _TimeDisplay.call(this, player, options) || this;
13994
13995      _this.on(player, 'durationchange', _this.throttledUpdateContent);
13996
13997      _this.on(player, 'ended', _this.handleEnded);
13998
13999      return _this;
14000    }
14001    /**
14002     * Builds the default DOM `className`.
14003     *
14004     * @return {string}
14005     *         The DOM `className` for this object.
14006     */
14007
14008
14009    var _proto = RemainingTimeDisplay.prototype;
14010
14011    _proto.buildCSSClass = function buildCSSClass() {
14012      return 'vjs-remaining-time';
14013    }
14014    /**
14015     * Create the `Component`'s DOM element with the "minus" characted prepend to the time
14016     *
14017     * @return {Element}
14018     *         The element that was created.
14019     */
14020    ;
14021
14022    _proto.createEl = function createEl$$1() {
14023      var el = _TimeDisplay.prototype.createEl.call(this);
14024
14025      el.insertBefore(createEl('span', {}, {
14026        'aria-hidden': true
14027      }, '-'), this.contentEl_);
14028      return el;
14029    }
14030    /**
14031     * Update remaining time display.
14032     *
14033     * @param {EventTarget~Event} [event]
14034     *        The `timeupdate` or `durationchange` event that caused this to run.
14035     *
14036     * @listens Player#timeupdate
14037     * @listens Player#durationchange
14038     */
14039    ;
14040
14041    _proto.updateContent = function updateContent(event) {
14042      if (typeof this.player_.duration() !== 'number') {
14043        return;
14044      } // @deprecated We should only use remainingTimeDisplay
14045      // as of video.js 7
14046
14047
14048      if (this.player_.remainingTimeDisplay) {
14049        this.updateFormattedTime_(this.player_.remainingTimeDisplay());
14050      } else {
14051        this.updateFormattedTime_(this.player_.remainingTime());
14052      }
14053    }
14054    /**
14055     * When the player fires ended there should be no time left. Sadly
14056     * this is not always the case, lets make it seem like that is the case
14057     * for users.
14058     *
14059     * @param {EventTarget~Event} [event]
14060     *        The `ended` event that caused this to run.
14061     *
14062     * @listens Player#ended
14063     */
14064    ;
14065
14066    _proto.handleEnded = function handleEnded(event) {
14067      if (!this.player_.duration()) {
14068        return;
14069      }
14070
14071      this.updateFormattedTime_(0);
14072    };
14073
14074    return RemainingTimeDisplay;
14075  }(TimeDisplay);
14076  /**
14077   * The text that is added to the `RemainingTimeDisplay` for screen reader users.
14078   *
14079   * @type {string}
14080   * @private
14081   */
14082
14083
14084  RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
14085  /**
14086   * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
14087   *
14088   * @type {string}
14089   * @private
14090   *
vendor: 9,955 bytes, lines 14091-14457
14091   * @deprecated in v7; controlText_ is not used in non-active display Components
14092   */
14093
14094  RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
14095  Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
14096
14097  /**
14098   * Displays the live indicator when duration is Infinity.
14099   *
14100   * @extends Component
14101   */
14102
14103  var LiveDisplay =
14104  /*#__PURE__*/
14105  function (_Component) {
14106    _inheritsLoose(LiveDisplay, _Component);
14107
14108    /**
14109     * Creates an instance of this class.
14110     *
14111     * @param {Player} player
14112     *        The `Player` that this class should be attached to.
14113     *
14114     * @param {Object} [options]
14115     *        The key/value store of player options.
14116     */
14117    function LiveDisplay(player, options) {
14118      var _this;
14119
14120      _this = _Component.call(this, player, options) || this;
14121
14122      _this.updateShowing();
14123
14124      _this.on(_this.player(), 'durationchange', _this.updateShowing);
14125
14126      return _this;
14127    }
14128    /**
14129     * Create the `Component`'s DOM element
14130     *
14131     * @return {Element}
14132     *         The element that was created.
14133     */
14134
14135
14136    var _proto = LiveDisplay.prototype;
14137
14138    _proto.createEl = function createEl$$1() {
14139      var el = _Component.prototype.createEl.call(this, 'div', {
14140        className: 'vjs-live-control vjs-control'
14141      });
14142
14143      this.contentEl_ = createEl('div', {
14144        className: 'vjs-live-display',
14145        innerHTML: "<span class=\"vjs-control-text\">" + this.localize('Stream Type') + "\xA0</span>" + this.localize('LIVE')
14146      }, {
14147        'aria-live': 'off'
14148      });
14149      el.appendChild(this.contentEl_);
14150      return el;
14151    };
14152
14153    _proto.dispose = function dispose() {
14154      this.contentEl_ = null;
14155
14156      _Component.prototype.dispose.call(this);
14157    }
14158    /**
14159     * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
14160     * it accordingly
14161     *
14162     * @param {EventTarget~Event} [event]
14163     *        The {@link Player#durationchange} event that caused this function to run.
14164     *
14165     * @listens Player#durationchange
14166     */
14167    ;
14168
14169    _proto.updateShowing = function updateShowing(event) {
14170      if (this.player().duration() === Infinity) {
14171        this.show();
14172      } else {
14173        this.hide();
14174      }
14175    };
14176
14177    return LiveDisplay;
14178  }(Component);
14179
14180  Component.registerComponent('LiveDisplay', LiveDisplay);
14181
14182  /**
14183   * Displays the live indicator when duration is Infinity.
14184   *
14185   * @extends Component
14186   */
14187
14188  var SeekToLive =
14189  /*#__PURE__*/
14190  function (_Button) {
14191    _inheritsLoose(SeekToLive, _Button);
14192
14193    /**
14194     * Creates an instance of this class.
14195     *
14196     * @param {Player} player
14197     *        The `Player` that this class should be attached to.
14198     *
14199     * @param {Object} [options]
14200     *        The key/value store of player options.
14201     */
14202    function SeekToLive(player, options) {
14203      var _this;
14204
14205      _this = _Button.call(this, player, options) || this;
14206
14207      _this.updateLiveEdgeStatus();
14208
14209      if (_this.player_.liveTracker) {
14210        _this.on(_this.player_.liveTracker, 'liveedgechange', _this.updateLiveEdgeStatus);
14211      }
14212
14213      return _this;
14214    }
14215    /**
14216     * Create the `Component`'s DOM element
14217     *
14218     * @return {Element}
14219     *         The element that was created.
14220     */
14221
14222
14223    var _proto = SeekToLive.prototype;
14224
14225    _proto.createEl = function createEl$$1() {
14226      var el = _Button.prototype.createEl.call(this, 'button', {
14227        className: 'vjs-seek-to-live-control vjs-control'
14228      });
14229
14230      this.textEl_ = createEl('span', {
14231        className: 'vjs-seek-to-live-text',
14232        innerHTML: this.localize('LIVE')
14233      }, {
14234        'aria-hidden': 'true'
14235      });
14236      el.appendChild(this.textEl_);
14237      return el;
14238    }
14239    /**
14240     * Update the state of this button if we are at the live edge
14241     * or not
14242     */
14243    ;
14244
14245    _proto.updateLiveEdgeStatus = function updateLiveEdgeStatus(e) {
14246      // default to live edge
14247      if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) {
14248        this.setAttribute('aria-disabled', true);
14249        this.addClass('vjs-at-live-edge');
14250        this.controlText('Seek to live, currently playing live');
14251      } else {
14252        this.setAttribute('aria-disabled', false);
14253        this.removeClass('vjs-at-live-edge');
14254        this.controlText('Seek to live, currently behind live');
14255      }
14256    }
14257    /**
14258     * On click bring us as near to the live point as possible.
14259     * This requires that we wait for the next `live-seekable-change`
14260     * event which will happen every segment length seconds.
14261     */
14262    ;
14263
14264    _proto.handleClick = function handleClick() {
14265      this.player_.liveTracker.seekToLiveEdge();
14266    }
14267    /**
14268     * Dispose of the element and stop tracking
14269     */
14270    ;
14271
14272    _proto.dispose = function dispose() {
14273      if (this.player_.liveTracker) {
14274        this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatus);
14275      }
14276
14277      this.textEl_ = null;
14278
14279      _Button.prototype.dispose.call(this);
14280    };
14281
14282    return SeekToLive;
14283  }(Button);
14284
14285  SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live';
14286  Component.registerComponent('SeekToLive', SeekToLive);
14287
14288  /**
14289   * The base functionality for a slider. Can be vertical or horizontal.
14290   * For instance the volume bar or the seek bar on a video is a slider.
14291   *
14292   * @extends Component
14293   */
14294
14295  var Slider =
14296  /*#__PURE__*/
14297  function (_Component) {
14298    _inheritsLoose(Slider, _Component);
14299
14300    /**
14301    * Create an instance of this class
14302    *
14303    * @param {Player} player
14304    *        The `Player` that this class should be attached to.
14305    *
14306    * @param {Object} [options]
14307    *        The key/value store of player options.
14308    */
14309    function Slider(player, options) {
14310      var _this;
14311
14312      _this = _Component.call(this, player, options) || this; // Set property names to bar to match with the child Slider class is looking for
14313
14314      _this.bar = _this.getChild(_this.options_.barName); // Set a horizontal or vertical class on the slider depending on the slider type
14315
14316      _this.vertical(!!_this.options_.vertical);
14317
14318      _this.enable();
14319
14320      return _this;
14321    }
14322    /**
14323     * Are controls are currently enabled for this slider or not.
14324     *
14325     * @return {boolean}
14326     *         true if controls are enabled, false otherwise
14327     */
14328
14329
14330    var _proto = Slider.prototype;
14331
14332    _proto.enabled = function enabled() {
14333      return this.enabled_;
14334    }
14335    /**
14336     * Enable controls for this slider if they are disabled
14337     */
14338    ;
14339
14340    _proto.enable = function enable() {
14341      if (this.enabled()) {
14342        return;
14343      }
14344
14345      this.on('mousedown', this.handleMouseDown);
14346      this.on('touchstart', this.handleMouseDown);
14347      this.on('keydown', this.handleKeyDown);
14348      this.on('click', this.handleClick);
14349      this.on(this.player_, 'controlsvisible', this.update);
14350
14351      if (this.playerEvent) {
14352        this.on(this.player_, this.playerEvent, this.update);
14353      }
14354
14355      this.removeClass('disabled');
14356      this.setAttribute('tabindex', 0);
14357      this.enabled_ = true;
14358    }
14359    /**
14360     * Disable controls for this slider if they are enabled
14361     */
14362    ;
14363
14364    _proto.disable = function disable() {
14365      if (!this.enabled()) {
14366        return;
14367      }
14368
14369      var doc = this.bar.el_.ownerDocument;
14370      this.off('mousedown', this.handleMouseDown);
14371      this.off('touchstart', this.handleMouseDown);
14372      this.off('keydown', this.handleKeyDown);
14373      this.off('click', this.handleClick);
14374      this.off(this.player_, 'controlsvisible', this.update);
14375      this.off(doc, 'mousemove', this.handleMouseMove);
14376      this.off(doc, 'mouseup', this.handleMouseUp);
14377      this.off(doc, 'touchmove', this.handleMouseMove);
14378      this.off(doc, 'touchend', this.handleMouseUp);
14379      this.removeAttribute('tabindex');
14380      this.addClass('disabled');
14381
14382      if (this.playerEvent) {
14383        this.off(this.player_, this.playerEvent, this.update);
14384      }
14385
14386      this.enabled_ = false;
14387    }
14388    /**
14389     * Create the `Slider`s DOM element.
14390     *
14391     * @param {string} type
14392     *        Type of element to create.
14393     *
14394     * @param {Object} [props={}]
14395     *        List of properties in Object form.
14396     *
14397     * @param {Object} [attributes={}]
14398     *        list of attributes in Object form.
14399     *
14400     * @return {Element}
14401     *         The element that gets created.
14402     */
14403    ;
14404
14405    _proto.createEl = function createEl$$1(type, props, attributes) {
14406      if (props === void 0) {
14407        props = {};
14408      }
14409
14410      if (attributes === void 0) {
14411        attributes = {};
14412      }
14413
14414      // Add the slider element class to all sub classes
14415      props.className = props.className + ' vjs-slider';
14416      props = assign({
14417        tabIndex: 0
14418      }, props);
14419      attributes = assign({
14420        'role': 'slider',
14421        'aria-valuenow': 0,
14422        'aria-valuemin': 0,
14423        'aria-valuemax': 100,
14424        'tabIndex': 0
14425      }, attributes);
14426      return _Component.prototype.createEl.call(this, type, props, attributes);
14427    }
14428    /**
14429     * Handle `mousedown` or `touchstart` events on the `Slider`.
14430     *
14431     * @param {EventTarget~Event} event
14432     *        `mousedown` or `touchstart` event that triggered this function
14433     *
14434     * @listens mousedown
14435     * @listens touchstart
14436     * @fires Slider#slideractive
14437     */
14438    ;
14439
14440    _proto.handleMouseDown = function handleMouseDown(event) {
14441      var doc = this.bar.el_.ownerDocument;
14442
14443      if (event.type === 'mousedown') {
14444        event.preventDefault();
14445      } // Do not call preventDefault() on touchstart in Chrome
14446      // to avoid console warnings. Use a 'touch-action: none' style
14447      // instead to prevent unintented scrolling.
14448      // https://developers.google.com/web/updates/2017/01/scrolling-intervention
14449
14450
14451      if (event.type === 'touchstart' && !IS_CHROME) {
14452        event.preventDefault();
14453      }
14454
14455      blockTextSelection();
14456      this.addClass('vjs-sliding');
14457      /**
14458       * Triggered when the slider is in an active state
14459       *
14460       * @event Slider#slideractive
14461       * @type {EventTarget~Event}
14462       */
14463
14464      this.trigger('slideractive');
14465      this.on(doc, 'mousemove', this.handleMouseMove);
14466      this.on(doc, 'mouseup', this.handleMouseUp);
14467      this.on(doc, 'touchmove', this.handleMouseMove);
14468      this.on(doc, 'touchend', this.handleMouseUp);
14469      this.handleMouseMove(event);
14470    }
14471    /**
14472     * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
14473     * The `mousemove` and `touchmove` events will only only trigger this function during
14474     * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
14475     * {@link Slider#handleMouseUp}.
14476     *
14477     * @param {EventTarget~Event} event
14478     *        `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
14479     *        this function
14480     *
14481     * @listens mousemove
14482     * @listens touchmove
14483     */
14484    ;
14485
14486    _proto.handleMouseMove = function handleMouseMove(event) {}
14487    /**
14488     * Handle `mouseup` or `touchend` events on the `Slider`.
14489     *
14490     * @param {EventTarget~Event} event
14491     *        `mouseup` or `touchend` event that triggered this function.
14492     *
14493     * @listens touchend
14494     * @listens mouseup
14495     * @fires Slider#sliderinactive
14496     */
14497    ;
14498
14499    _proto.handleMouseUp = function handleMouseUp() {
14500      var doc = this.bar.el_.ownerDocument;
14501      unblockTextSelection();
14502      this.removeClass('vjs-sliding');
14503      /**
14504       * Triggered when the slider is no longer in an active state.
14505       *
14506       * @event Slider#sliderinactive
14507       * @type {EventTarget~Event}
14508       */
14509
14510      this.trigger('sliderinactive');
14511      this.off(doc, 'mousemove', this.handleMouseMove);
14512      this.off(doc, 'mouseup', this.handleMouseUp);
14513      this.off(doc, 'touchmove', this.handleMouseMove);
14514      this.off(doc, 'touchend', this.handleMouseUp);
14515      this.update();
14516    }
14517    /**
14518     * Update the progress bar of the `Slider`.
14519     *
14520     * @return {number}
14521     *          The percentage of progress the progress bar represents as a
14522     *          number from 0 to 1.
14523     */
14524    ;
14525
14526    _proto.update = function update() {
14527      // In VolumeBar init we have a setTimeout for update that pops and update
14528      // to the end of the execution stack. The player is destroyed before then
14529      // update will cause an error
14530      if (!this.el_) {
14531        return;
14532      } // If scrubbing, we could use a cached value to make the handle keep up
14533      // with the user's mouse. On HTML5 browsers scrubbing is really smooth, but
14534      // some flash players are slow, so we might want to utilize this later.
14535      // var progress =  (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
14536
14537
14538      var progress = this.getPercent();
14539      var bar = this.bar; // If there's no bar...
14540
14541      if (!bar) {
14542        return;
14543      } // Protect against no duration and other division issues
14544
14545
14546      if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) {
14547        progress = 0;
14548      } // Convert to a percentage for setting
14549
14550
14551      var percentage = (progress * 100).toFixed(2) + '%';
14552      var style = bar.el().style; // Set the new bar width or height
14553
14554      if (this.vertical()) {
14555        style.height = percentage;
14556      } else {
14557        style.width = percentage;
14558      }
14559
14560      return progress;
14561    }
14562    /**
14563     * Calculate distance for slider
14564     *
14565     * @param {EventTarget~Event} event
14566     *        The event that caused this function to run.
14567     *
14568     * @return {number}
14569     *         The current position of the Slider.
14570     *         - position.x for vertical `Slider`s
14571     *         - position.y for horizontal `Slider`s
14572     */
14573    ;
14574
14575    _proto.calculateDistance = function calculateDistance(event) {
14576      var position = getPointerPosition(this.el_, event);
14577
14578      if (this.vertical()) {
14579        return position.y;
14580      }
14581
14582      return position.x;
14583    }
14584    /**
14585     * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
14586     * arrow keys. This function will only be called when the slider has focus. See
14587     * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
14588     *
14589     * @param {EventTarget~Event} event
14590     *        the `keydown` event that caused this function to run.
14591     *
14592     * @listens keydown
14593     */
14594    ;
14595
14596    _proto.handleKeyDown = function handleKeyDown(event) {
14597      // Left and Down Arrows
14598      if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
14599        event.preventDefault();
14600        event.stopPropagation();
14601        this.stepBack(); // Up and Right Arrows
14602      } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
14603        event.preventDefault();
14604        event.stopPropagation();
14605        this.stepForward();
14606      } else {
14607        // Pass keydown handling up for unsupported keys
14608        _Component.prototype.handleKeyDown.call(this, event);
14609      }
14610    }
14611    /**
14612     * Listener for click events on slider, used to prevent clicks
14613     *   from bubbling up to parent elements like button menus.
14614     *
14615     * @param {Object} event
14616     *        Event that caused this object to run
14617     */
14618    ;
14619
14620    _proto.handleClick = function handleClick(event) {
14621      event.stopPropagation();
14622      event.preventDefault();
14623    }
14624    /**
14625     * Get/set if slider is horizontal for vertical
14626     *
14627     * @param {boolean} [bool]
14628     *        - true if slider is vertical,
14629     *        - false is horizontal
14630     *
14631     * @return {boolean}
14632     *         - true if slider is vertical, and getting
14633     *         - false if the slider is horizontal, and getting
14634     */
14635    ;
14636
14637    _proto.vertical = function vertical(bool) {
14638      if (bool === undefined) {
14639        return this.vertical_ || false;
14640      }
14641
14642      this.vertical_ = !!bool;
14643
14644      if (this.vertical_) {
14645        this.addClass('vjs-slider-vertical');
14646      } else {
14647        this.addClass('vjs-slider-horizontal');
14648      }
14649    };
14650
14651    return Slider;
14652  }(Component);
14653
14654  Component.registerComponent('Slider', Slider);
14655
14656  /**
14657   * Shows loading progress
14658   *
14659   * @extends Component
14660   */
14661
14662  var LoadProgressBar =
14663  /*#__PURE__*/
14664  function (_Component) {
14665    _inheritsLoose(LoadProgressBar, _Component);
14666
14667    /**
14668     * Creates an instance of this class.
14669     *
14670     * @param {Player} player
14671     *        The `Player` that this class should be attached to.
14672     *
14673     * @param {Object} [options]
14674     *        The key/value store of player options.
14675     */
14676    function LoadProgressBar(player, options) {
14677      var _this;
14678
14679      _this = _Component.call(this, player, options) || this;
14680      _this.partEls_ = [];
14681
14682      _this.on(player, 'progress', _this.update);
14683
14684      return _this;
14685    }
14686    /**
14687     * Create the `Component`'s DOM element
14688     *
14689     * @return {Element}
14690     *         The element that was created.
14691     */
14692
14693
14694    var _proto = LoadProgressBar.prototype;
14695
14696    _proto.createEl = function createEl$$1() {
14697      return _Component.prototype.createEl.call(this, 'div', {
14698        className: 'vjs-load-progress',
14699        innerHTML: "<span class=\"vjs-control-text\"><span>" + this.localize('Loaded') + "</span>: <span class=\"vjs-control-text-loaded-percentage\">0%</span></span>"
14700      });
14701    };
14702
14703    _proto.dispose = function dispose() {
14704      this.partEls_ = null;
14705
14706      _Component.prototype.dispose.call(this);
14707    }
14708    /**
14709     * Update progress bar
14710     *
14711     * @param {EventTarget~Event} [event]
14712     *        The `progress` event that caused this function to run.
14713     *
14714     * @listens Player#progress
14715     */
14716    ;
14717
14718    _proto.update = function update(event) {
14719      var liveTracker = this.player_.liveTracker;
14720      var buffered = this.player_.buffered();
14721      var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
14722      var bufferedEnd = this.player_.bufferedEnd();
14723      var children = this.partEls_;
14724      var controlTextPercentage = this.$('.vjs-control-text-loaded-percentage'); // get the percent width of a time compared to the total end
14725
14726      var percentify = function percentify(time, end, rounded) {
14727        // no NaN
14728        var percent = time / end || 0;
14729        percent = (percent >= 1 ? 1 : percent) * 100;
14730
14731        if (rounded) {
14732          percent = percent.toFixed(2);
14733        }
14734
14735        return percent + '%';
14736      }; // update the width of the progress bar
14737
14738
14739      this.el_.style.width = percentify(bufferedEnd, duration); // update the control-text
14740
14741      textContent(controlTextPercentage, percentify(bufferedEnd, duration, true)); // add child elements to represent the individual buffered time ranges
14742
14743      for (var i = 0; i < buffered.length; i++) {
14744        var start = buffered.start(i);
14745        var end = buffered.end(i);
14746        var part = children[i];
14747
14748        if (!part) {
14749          part = this.el_.appendChild(createEl());
14750          children[i] = part;
vendor: 88,552 bytes, lines 14751-17856
14751        } // set the percent based on the width of the progress bar (bufferedEnd)
14752
14753
14754        part.style.left = percentify(start, bufferedEnd);
14755        part.style.width = percentify(end - start, bufferedEnd);
14756      } // remove unused buffered range elements
14757
14758
14759      for (var _i = children.length; _i > buffered.length; _i--) {
14760        this.el_.removeChild(children[_i - 1]);
14761      }
14762
14763      children.length = buffered.length;
14764    };
14765
14766    return LoadProgressBar;
14767  }(Component);
14768
14769  Component.registerComponent('LoadProgressBar', LoadProgressBar);
14770
14771  /**
14772   * Time tooltips display a time above the progress bar.
14773   *
14774   * @extends Component
14775   */
14776
14777  var TimeTooltip =
14778  /*#__PURE__*/
14779  function (_Component) {
14780    _inheritsLoose(TimeTooltip, _Component);
14781
14782    function TimeTooltip() {
14783      return _Component.apply(this, arguments) || this;
14784    }
14785
14786    var _proto = TimeTooltip.prototype;
14787
14788    /**
14789     * Create the time tooltip DOM element
14790     *
14791     * @return {Element}
14792     *         The element that was created.
14793     */
14794    _proto.createEl = function createEl$$1() {
14795      return _Component.prototype.createEl.call(this, 'div', {
14796        className: 'vjs-time-tooltip'
14797      }, {
14798        'aria-hidden': 'true'
14799      });
14800    }
14801    /**
14802     * Updates the position of the time tooltip relative to the `SeekBar`.
14803     *
14804     * @param {Object} seekBarRect
14805     *        The `ClientRect` for the {@link SeekBar} element.
14806     *
14807     * @param {number} seekBarPoint
14808     *        A number from 0 to 1, representing a horizontal reference point
14809     *        from the left edge of the {@link SeekBar}
14810     */
14811    ;
14812
14813    _proto.update = function update(seekBarRect, seekBarPoint, content) {
14814      var tooltipRect = getBoundingClientRect(this.el_);
14815      var playerRect = getBoundingClientRect(this.player_.el());
14816      var seekBarPointPx = seekBarRect.width * seekBarPoint; // do nothing if either rect isn't available
14817      // for example, if the player isn't in the DOM for testing
14818
14819      if (!playerRect || !tooltipRect) {
14820        return;
14821      } // This is the space left of the `seekBarPoint` available within the bounds
14822      // of the player. We calculate any gap between the left edge of the player
14823      // and the left edge of the `SeekBar` and add the number of pixels in the
14824      // `SeekBar` before hitting the `seekBarPoint`
14825
14826
14827      var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; // This is the space right of the `seekBarPoint` available within the bounds
14828      // of the player. We calculate the number of pixels from the `seekBarPoint`
14829      // to the right edge of the `SeekBar` and add to that any gap between the
14830      // right edge of the `SeekBar` and the player.
14831
14832      var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); // This is the number of pixels by which the tooltip will need to be pulled
14833      // further to the right to center it over the `seekBarPoint`.
14834
14835      var pullTooltipBy = tooltipRect.width / 2; // Adjust the `pullTooltipBy` distance to the left or right depending on
14836      // the results of the space calculations above.
14837
14838      if (spaceLeftOfPoint < pullTooltipBy) {
14839        pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
14840      } else if (spaceRightOfPoint < pullTooltipBy) {
14841        pullTooltipBy = spaceRightOfPoint;
14842      } // Due to the imprecision of decimal/ratio based calculations and varying
14843      // rounding behaviors, there are cases where the spacing adjustment is off
14844      // by a pixel or two. This adds insurance to these calculations.
14845
14846
14847      if (pullTooltipBy < 0) {
14848        pullTooltipBy = 0;
14849      } else if (pullTooltipBy > tooltipRect.width) {
14850        pullTooltipBy = tooltipRect.width;
14851      }
14852
14853      this.el_.style.right = "-" + pullTooltipBy + "px";
14854      textContent(this.el_, content);
14855    }
14856    /**
14857     * Updates the position of the time tooltip relative to the `SeekBar`.
14858     *
14859     * @param {Object} seekBarRect
14860     *        The `ClientRect` for the {@link SeekBar} element.
14861     *
14862     * @param {number} seekBarPoint
14863     *        A number from 0 to 1, representing a horizontal reference point
14864     *        from the left edge of the {@link SeekBar}
14865     *
14866     * @param {number} time
14867     *        The time to update the tooltip to, not used during live playback
14868     *
14869     * @param {Function} cb
14870     *        A function that will be called during the request animation frame
14871     *        for tooltips that need to do additional animations from the default
14872     */
14873    ;
14874
14875    _proto.updateTime = function updateTime(seekBarRect, seekBarPoint, time, cb) {
14876      var _this = this;
14877
14878      // If there is an existing rAF ID, cancel it so we don't over-queue.
14879      if (this.rafId_) {
14880        this.cancelAnimationFrame(this.rafId_);
14881      }
14882
14883      this.rafId_ = this.requestAnimationFrame(function () {
14884        var content;
14885
14886        var duration = _this.player_.duration();
14887
14888        if (_this.player_.liveTracker && _this.player_.liveTracker.isLive()) {
14889          var liveWindow = _this.player_.liveTracker.liveWindow();
14890
14891          var secondsBehind = liveWindow - seekBarPoint * liveWindow;
14892          content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
14893        } else {
14894          content = formatTime(time, duration);
14895        }
14896
14897        _this.update(seekBarRect, seekBarPoint, content);
14898
14899        if (cb) {
14900          cb();
14901        }
14902      });
14903    };
14904
14905    return TimeTooltip;
14906  }(Component);
14907
14908  Component.registerComponent('TimeTooltip', TimeTooltip);
14909
14910  /**
14911   * Used by {@link SeekBar} to display media playback progress as part of the
14912   * {@link ProgressControl}.
14913   *
14914   * @extends Component
14915   */
14916
14917  var PlayProgressBar =
14918  /*#__PURE__*/
14919  function (_Component) {
14920    _inheritsLoose(PlayProgressBar, _Component);
14921
14922    function PlayProgressBar() {
14923      return _Component.apply(this, arguments) || this;
14924    }
14925
14926    var _proto = PlayProgressBar.prototype;
14927
14928    /**
14929     * Create the the DOM element for this class.
14930     *
14931     * @return {Element}
14932     *         The element that was created.
14933     */
14934    _proto.createEl = function createEl() {
14935      return _Component.prototype.createEl.call(this, 'div', {
14936        className: 'vjs-play-progress vjs-slider-bar'
14937      }, {
14938        'aria-hidden': 'true'
14939      });
14940    }
14941    /**
14942     * Enqueues updates to its own DOM as well as the DOM of its
14943     * {@link TimeTooltip} child.
14944     *
14945     * @param {Object} seekBarRect
14946     *        The `ClientRect` for the {@link SeekBar} element.
14947     *
14948     * @param {number} seekBarPoint
14949     *        A number from 0 to 1, representing a horizontal reference point
14950     *        from the left edge of the {@link SeekBar}
14951     */
14952    ;
14953
14954    _proto.update = function update(seekBarRect, seekBarPoint) {
14955      var timeTooltip = this.getChild('timeTooltip');
14956
14957      if (!timeTooltip) {
14958        return;
14959      }
14960
14961      var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
14962      timeTooltip.updateTime(seekBarRect, seekBarPoint, time);
14963    };
14964
14965    return PlayProgressBar;
14966  }(Component);
14967  /**
14968   * Default options for {@link PlayProgressBar}.
14969   *
14970   * @type {Object}
14971   * @private
14972   */
14973
14974
14975  PlayProgressBar.prototype.options_ = {
14976    children: []
14977  }; // Time tooltips should not be added to a player on mobile devices
14978
14979  if (!IS_IOS && !IS_ANDROID) {
14980    PlayProgressBar.prototype.options_.children.push('timeTooltip');
14981  }
14982
14983  Component.registerComponent('PlayProgressBar', PlayProgressBar);
14984
14985  /**
14986   * The {@link MouseTimeDisplay} component tracks mouse movement over the
14987   * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
14988   * indicating the time which is represented by a given point in the
14989   * {@link ProgressControl}.
14990   *
14991   * @extends Component
14992   */
14993
14994  var MouseTimeDisplay =
14995  /*#__PURE__*/
14996  function (_Component) {
14997    _inheritsLoose(MouseTimeDisplay, _Component);
14998
14999    /**
15000     * Creates an instance of this class.
15001     *
15002     * @param {Player} player
15003     *        The {@link Player} that this class should be attached to.
15004     *
15005     * @param {Object} [options]
15006     *        The key/value store of player options.
15007     */
15008    function MouseTimeDisplay(player, options) {
15009      var _this;
15010
15011      _this = _Component.call(this, player, options) || this;
15012      _this.update = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update), 25);
15013      return _this;
15014    }
15015    /**
15016     * Create the DOM element for this class.
15017     *
15018     * @return {Element}
15019     *         The element that was created.
15020     */
15021
15022
15023    var _proto = MouseTimeDisplay.prototype;
15024
15025    _proto.createEl = function createEl() {
15026      return _Component.prototype.createEl.call(this, 'div', {
15027        className: 'vjs-mouse-display'
15028      });
15029    }
15030    /**
15031     * Enqueues updates to its own DOM as well as the DOM of its
15032     * {@link TimeTooltip} child.
15033     *
15034     * @param {Object} seekBarRect
15035     *        The `ClientRect` for the {@link SeekBar} element.
15036     *
15037     * @param {number} seekBarPoint
15038     *        A number from 0 to 1, representing a horizontal reference point
15039     *        from the left edge of the {@link SeekBar}
15040     */
15041    ;
15042
15043    _proto.update = function update(seekBarRect, seekBarPoint) {
15044      var _this2 = this;
15045
15046      var time = seekBarPoint * this.player_.duration();
15047      this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, function () {
15048        _this2.el_.style.left = seekBarRect.width * seekBarPoint + "px";
15049      });
15050    };
15051
15052    return MouseTimeDisplay;
15053  }(Component);
15054  /**
15055   * Default options for `MouseTimeDisplay`
15056   *
15057   * @type {Object}
15058   * @private
15059   */
15060
15061
15062  MouseTimeDisplay.prototype.options_ = {
15063    children: ['timeTooltip']
15064  };
15065  Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
15066
15067  var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline.
15068
15069  var PAGE_KEY_MULTIPLIER = 12; // The interval at which the bar should update as it progresses.
15070
15071  var UPDATE_REFRESH_INTERVAL = 30;
15072  /**
15073   * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
15074   * as its `bar`.
15075   *
15076   * @extends Slider
15077   */
15078
15079  var SeekBar =
15080  /*#__PURE__*/
15081  function (_Slider) {
15082    _inheritsLoose(SeekBar, _Slider);
15083
15084    /**
15085     * Creates an instance of this class.
15086     *
15087     * @param {Player} player
15088     *        The `Player` that this class should be attached to.
15089     *
15090     * @param {Object} [options]
15091     *        The key/value store of player options.
15092     */
15093    function SeekBar(player, options) {
15094      var _this;
15095
15096      _this = _Slider.call(this, player, options) || this;
15097
15098      _this.setEventHandlers_();
15099
15100      return _this;
15101    }
15102    /**
15103     * Sets the event handlers
15104     *
15105     * @private
15106     */
15107
15108
15109    var _proto = SeekBar.prototype;
15110
15111    _proto.setEventHandlers_ = function setEventHandlers_() {
15112      this.update = throttle(bind(this, this.update), UPDATE_REFRESH_INTERVAL);
15113      this.on(this.player_, 'timeupdate', this.update);
15114      this.on(this.player_, 'ended', this.handleEnded);
15115      this.on(this.player_, 'durationchange', this.update);
15116
15117      if (this.player_.liveTracker) {
15118        this.on(this.player_.liveTracker, 'liveedgechange', this.update);
15119      } // when playing, let's ensure we smoothly update the play progress bar
15120      // via an interval
15121
15122
15123      this.updateInterval = null;
15124      this.on(this.player_, ['playing'], this.enableInterval_);
15125      this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); // we don't need to update the play progress if the document is hidden,
15126      // also, this causes the CPU to spike and eventually crash the page on IE11.
15127
15128      if ('hidden' in document && 'visibilityState' in document) {
15129        this.on(document, 'visibilitychange', this.toggleVisibility_);
15130      }
15131    };
15132
15133    _proto.toggleVisibility_ = function toggleVisibility_(e) {
15134      if (document.hidden) {
15135        this.disableInterval_(e);
15136      } else {
15137        this.enableInterval_(); // we just switched back to the page and someone may be looking, so, update ASAP
15138
15139        this.requestAnimationFrame(this.update);
15140      }
15141    };
15142
15143    _proto.enableInterval_ = function enableInterval_() {
15144      var _this2 = this;
15145
15146      this.clearInterval(this.updateInterval);
15147      this.updateInterval = this.setInterval(function () {
15148        _this2.requestAnimationFrame(_this2.update);
15149      }, UPDATE_REFRESH_INTERVAL);
15150    };
15151
15152    _proto.disableInterval_ = function disableInterval_(e) {
15153      if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e.type !== 'ended') {
15154        return;
15155      }
15156
15157      this.clearInterval(this.updateInterval);
15158    }
15159    /**
15160     * Create the `Component`'s DOM element
15161     *
15162     * @return {Element}
15163     *         The element that was created.
15164     */
15165    ;
15166
15167    _proto.createEl = function createEl$$1() {
15168      return _Slider.prototype.createEl.call(this, 'div', {
15169        className: 'vjs-progress-holder'
15170      }, {
15171        'aria-label': this.localize('Progress Bar')
15172      });
15173    }
15174    /**
15175     * This function updates the play progress bar and accessibility
15176     * attributes to whatever is passed in.
15177     *
15178     * @param {number} currentTime
15179     *        The currentTime value that should be used for accessibility
15180     *
15181     * @param {number} percent
15182     *        The percentage as a decimal that the bar should be filled from 0-1.
15183     *
15184     * @private
15185     */
15186    ;
15187
15188    _proto.update_ = function update_(currentTime, percent) {
15189      var liveTracker = this.player_.liveTracker;
15190      var duration = this.player_.duration();
15191
15192      if (liveTracker && liveTracker.isLive()) {
15193        duration = this.player_.liveTracker.liveCurrentTime();
15194      } // machine readable value of progress bar (percentage complete)
15195
15196
15197      this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); // human readable value of progress bar (time complete)
15198
15199      this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}')); // Update the `PlayProgressBar`.
15200
15201      if (this.bar) {
15202        this.bar.update(getBoundingClientRect(this.el_), percent);
15203      }
15204    }
15205    /**
15206     * Update the seek bar's UI.
15207     *
15208     * @param {EventTarget~Event} [event]
15209     *        The `timeupdate` or `ended` event that caused this to run.
15210     *
15211     * @listens Player#timeupdate
15212     *
15213     * @return {number}
15214     *          The current percent at a number from 0-1
15215     */
15216    ;
15217
15218    _proto.update = function update(event) {
15219      // if the offsetParent is null, then this element is hidden, in which case
15220      // we don't need to update it.
15221      if (this.el().offsetParent === null) {
15222        return;
15223      }
15224
15225      var percent = _Slider.prototype.update.call(this);
15226
15227      this.update_(this.getCurrentTime_(), percent);
15228      return percent;
15229    }
15230    /**
15231     * Get the value of current time but allows for smooth scrubbing,
15232     * when player can't keep up.
15233     *
15234     * @return {number}
15235     *         The current time value to display
15236     *
15237     * @private
15238     */
15239    ;
15240
15241    _proto.getCurrentTime_ = function getCurrentTime_() {
15242      return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
15243    }
15244    /**
15245     * We want the seek bar to be full on ended
15246     * no matter what the actual internal values are. so we force it.
15247     *
15248     * @param {EventTarget~Event} [event]
15249     *        The `timeupdate` or `ended` event that caused this to run.
15250     *
15251     * @listens Player#ended
15252     */
15253    ;
15254
15255    _proto.handleEnded = function handleEnded(event) {
15256      this.update_(this.player_.duration(), 1);
15257    }
15258    /**
15259     * Get the percentage of media played so far.
15260     *
15261     * @return {number}
15262     *         The percentage of media played so far (0 to 1).
15263     */
15264    ;
15265
15266    _proto.getPercent = function getPercent() {
15267      var currentTime = this.getCurrentTime_();
15268      var percent;
15269      var liveTracker = this.player_.liveTracker;
15270
15271      if (liveTracker && liveTracker.isLive()) {
15272        percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); // prevent the percent from changing at the live edge
15273
15274        if (liveTracker.atLiveEdge()) {
15275          percent = 1;
15276        }
15277      } else {
15278        percent = currentTime / this.player_.duration();
15279      }
15280
15281      return percent >= 1 ? 1 : percent || 0;
15282    }
15283    /**
15284     * Handle mouse down on seek bar
15285     *
15286     * @param {EventTarget~Event} event
15287     *        The `mousedown` event that caused this to run.
15288     *
15289     * @listens mousedown
15290     */
15291    ;
15292
15293    _proto.handleMouseDown = function handleMouseDown(event) {
15294      if (!isSingleLeftClick(event)) {
15295        return;
15296      } // Stop event propagation to prevent double fire in progress-control.js
15297
15298
15299      event.stopPropagation();
15300      this.player_.scrubbing(true);
15301      this.videoWasPlaying = !this.player_.paused();
15302      this.player_.pause();
15303
15304      _Slider.prototype.handleMouseDown.call(this, event);
15305    }
15306    /**
15307     * Handle mouse move on seek bar
15308     *
15309     * @param {EventTarget~Event} event
15310     *        The `mousemove` event that caused this to run.
15311     *
15312     * @listens mousemove
15313     */
15314    ;
15315
15316    _proto.handleMouseMove = function handleMouseMove(event) {
15317      if (!isSingleLeftClick(event)) {
15318        return;
15319      }
15320
15321      var newTime;
15322      var distance = this.calculateDistance(event);
15323      var liveTracker = this.player_.liveTracker;
15324
15325      if (!liveTracker || !liveTracker.isLive()) {
15326        newTime = distance * this.player_.duration(); // Don't let video end while scrubbing.
15327
15328        if (newTime === this.player_.duration()) {
15329          newTime = newTime - 0.1;
15330        }
15331      } else {
15332        var seekableStart = liveTracker.seekableStart();
15333        var seekableEnd = liveTracker.liveCurrentTime();
15334        newTime = seekableStart + distance * liveTracker.liveWindow(); // Don't let video end while scrubbing.
15335
15336        if (newTime >= seekableEnd) {
15337          newTime = seekableEnd;
15338        } // Compensate for precision differences so that currentTime is not less
15339        // than seekable start
15340
15341
15342        if (newTime <= seekableStart) {
15343          newTime = seekableStart + 0.1;
15344        } // On android seekableEnd can be Infinity sometimes,
15345        // this will cause newTime to be Infinity, which is
15346        // not a valid currentTime.
15347
15348
15349        if (newTime === Infinity) {
15350          return;
15351        }
15352      } // Set new time (tell player to seek to new time)
15353
15354
15355      this.player_.currentTime(newTime);
15356    };
15357
15358    _proto.enable = function enable() {
15359      _Slider.prototype.enable.call(this);
15360
15361      var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
15362
15363      if (!mouseTimeDisplay) {
15364        return;
15365      }
15366
15367      mouseTimeDisplay.show();
15368    };
15369
15370    _proto.disable = function disable() {
15371      _Slider.prototype.disable.call(this);
15372
15373      var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
15374
15375      if (!mouseTimeDisplay) {
15376        return;
15377      }
15378
15379      mouseTimeDisplay.hide();
15380    }
15381    /**
15382     * Handle mouse up on seek bar
15383     *
15384     * @param {EventTarget~Event} event
15385     *        The `mouseup` event that caused this to run.
15386     *
15387     * @listens mouseup
15388     */
15389    ;
15390
15391    _proto.handleMouseUp = function handleMouseUp(event) {
15392      _Slider.prototype.handleMouseUp.call(this, event); // Stop event propagation to prevent double fire in progress-control.js
15393
15394
15395      if (event) {
15396        event.stopPropagation();
15397      }
15398
15399      this.player_.scrubbing(false);
15400      /**
15401       * Trigger timeupdate because we're done seeking and the time has changed.
15402       * This is particularly useful for if the player is paused to time the time displays.
15403       *
15404       * @event Tech#timeupdate
15405       * @type {EventTarget~Event}
15406       */
15407
15408      this.player_.trigger({
15409        type: 'timeupdate',
15410        target: this,
15411        manuallyTriggered: true
15412      });
15413
15414      if (this.videoWasPlaying) {
15415        silencePromise(this.player_.play());
15416      }
15417    }
15418    /**
15419     * Move more quickly fast forward for keyboard-only users
15420     */
15421    ;
15422
15423    _proto.stepForward = function stepForward() {
15424      this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS);
15425    }
15426    /**
15427     * Move more quickly rewind for keyboard-only users
15428     */
15429    ;
15430
15431    _proto.stepBack = function stepBack() {
15432      this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS);
15433    }
15434    /**
15435     * Toggles the playback state of the player
15436     * This gets called when enter or space is used on the seekbar
15437     *
15438     * @param {EventTarget~Event} event
15439     *        The `keydown` event that caused this function to be called
15440     *
15441     */
15442    ;
15443
15444    _proto.handleAction = function handleAction(event) {
15445      if (this.player_.paused()) {
15446        this.player_.play();
15447      } else {
15448        this.player_.pause();
15449      }
15450    }
15451    /**
15452     * Called when this SeekBar has focus and a key gets pressed down.
15453     * Supports the following keys:
15454     *
15455     *   Space or Enter key fire a click event
15456     *   Home key moves to start of the timeline
15457     *   End key moves to end of the timeline
15458     *   Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline
15459     *   PageDown key moves back a larger step than ArrowDown
15460     *   PageUp key moves forward a large step
15461     *
15462     * @param {EventTarget~Event} event
15463     *        The `keydown` event that caused this function to be called.
15464     *
15465     * @listens keydown
15466     */
15467    ;
15468
15469    _proto.handleKeyDown = function handleKeyDown(event) {
15470      if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
15471        event.preventDefault();
15472        event.stopPropagation();
15473        this.handleAction(event);
15474      } else if (keycode.isEventKey(event, 'Home')) {
15475        event.preventDefault();
15476        event.stopPropagation();
15477        this.player_.currentTime(0);
15478      } else if (keycode.isEventKey(event, 'End')) {
15479        event.preventDefault();
15480        event.stopPropagation();
15481        this.player_.currentTime(this.player_.duration());
15482      } else if (/^[0-9]$/.test(keycode(event))) {
15483        event.preventDefault();
15484        event.stopPropagation();
15485        var gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0;
15486        this.player_.currentTime(this.player_.duration() * gotoFraction);
15487      } else if (keycode.isEventKey(event, 'PgDn')) {
15488        event.preventDefault();
15489        event.stopPropagation();
15490        this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER);
15491      } else if (keycode.isEventKey(event, 'PgUp')) {
15492        event.preventDefault();
15493        event.stopPropagation();
15494        this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER);
15495      } else {
15496        // Pass keydown handling up for unsupported keys
15497        _Slider.prototype.handleKeyDown.call(this, event);
15498      }
15499    };
15500
15501    return SeekBar;
15502  }(Slider);
15503  /**
15504   * Default options for the `SeekBar`
15505   *
15506   * @type {Object}
15507   * @private
15508   */
15509
15510
15511  SeekBar.prototype.options_ = {
15512    children: ['loadProgressBar', 'playProgressBar'],
15513    barName: 'playProgressBar'
15514  }; // MouseTimeDisplay tooltips should not be added to a player on mobile devices
15515
15516  if (!IS_IOS && !IS_ANDROID) {
15517    SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
15518  }
15519
15520  Component.registerComponent('SeekBar', SeekBar);
15521
15522  /**
15523   * The Progress Control component contains the seek bar, load progress,
15524   * and play progress.
15525   *
15526   * @extends Component
15527   */
15528
15529  var ProgressControl =
15530  /*#__PURE__*/
15531  function (_Component) {
15532    _inheritsLoose(ProgressControl, _Component);
15533
15534    /**
15535     * Creates an instance of this class.
15536     *
15537     * @param {Player} player
15538     *        The `Player` that this class should be attached to.
15539     *
15540     * @param {Object} [options]
15541     *        The key/value store of player options.
15542     */
15543    function ProgressControl(player, options) {
15544      var _this;
15545
15546      _this = _Component.call(this, player, options) || this;
15547      _this.handleMouseMove = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseMove), 25);
15548      _this.throttledHandleMouseSeek = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseSeek), 25);
15549
15550      _this.enable();
15551
15552      return _this;
15553    }
15554    /**
15555     * Create the `Component`'s DOM element
15556     *
15557     * @return {Element}
15558     *         The element that was created.
15559     */
15560
15561
15562    var _proto = ProgressControl.prototype;
15563
15564    _proto.createEl = function createEl$$1() {
15565      return _Component.prototype.createEl.call(this, 'div', {
15566        className: 'vjs-progress-control vjs-control'
15567      });
15568    }
15569    /**
15570     * When the mouse moves over the `ProgressControl`, the pointer position
15571     * gets passed down to the `MouseTimeDisplay` component.
15572     *
15573     * @param {EventTarget~Event} event
15574     *        The `mousemove` event that caused this function to run.
15575     *
15576     * @listen mousemove
15577     */
15578    ;
15579
15580    _proto.handleMouseMove = function handleMouseMove(event) {
15581      var seekBar = this.getChild('seekBar');
15582
15583      if (seekBar) {
15584        var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
15585        var seekBarEl = seekBar.el();
15586        var seekBarRect = getBoundingClientRect(seekBarEl);
15587        var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means
15588        // that it's possible to trigger this behavior outside the boundaries of
15589        // the `SeekBar`. This ensures we stay within it at all times.
15590
15591        if (seekBarPoint > 1) {
15592          seekBarPoint = 1;
15593        } else if (seekBarPoint < 0) {
15594          seekBarPoint = 0;
15595        }
15596
15597        if (mouseTimeDisplay) {
15598          mouseTimeDisplay.update(seekBarRect, seekBarPoint);
15599        }
15600      }
15601    }
15602    /**
15603     * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
15604     *
15605     * @method ProgressControl#throttledHandleMouseSeek
15606     * @param {EventTarget~Event} event
15607     *        The `mousemove` event that caused this function to run.
15608     *
15609     * @listen mousemove
15610     * @listen touchmove
15611     */
15612
15613    /**
15614     * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
15615     *
15616     * @param {EventTarget~Event} event
15617     *        `mousedown` or `touchstart` event that triggered this function
15618     *
15619     * @listens mousemove
15620     * @listens touchmove
15621     */
15622    ;
15623
15624    _proto.handleMouseSeek = function handleMouseSeek(event) {
15625      var seekBar = this.getChild('seekBar');
15626
15627      if (seekBar) {
15628        seekBar.handleMouseMove(event);
15629      }
15630    }
15631    /**
15632     * Are controls are currently enabled for this progress control.
15633     *
15634     * @return {boolean}
15635     *         true if controls are enabled, false otherwise
15636     */
15637    ;
15638
15639    _proto.enabled = function enabled() {
15640      return this.enabled_;
15641    }
15642    /**
15643     * Disable all controls on the progress control and its children
15644     */
15645    ;
15646
15647    _proto.disable = function disable() {
15648      this.children().forEach(function (child) {
15649        return child.disable && child.disable();
15650      });
15651
15652      if (!this.enabled()) {
15653        return;
15654      }
15655
15656      this.off(['mousedown', 'touchstart'], this.handleMouseDown);
15657      this.off(this.el_, 'mousemove', this.handleMouseMove);
15658      this.handleMouseUp();
15659      this.addClass('disabled');
15660      this.enabled_ = false;
15661    }
15662    /**
15663     * Enable all controls on the progress control and its children
15664     */
15665    ;
15666
15667    _proto.enable = function enable() {
15668      this.children().forEach(function (child) {
15669        return child.enable && child.enable();
15670      });
15671
15672      if (this.enabled()) {
15673        return;
15674      }
15675
15676      this.on(['mousedown', 'touchstart'], this.handleMouseDown);
15677      this.on(this.el_, 'mousemove', this.handleMouseMove);
15678      this.removeClass('disabled');
15679      this.enabled_ = true;
15680    }
15681    /**
15682     * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
15683     *
15684     * @param {EventTarget~Event} event
15685     *        `mousedown` or `touchstart` event that triggered this function
15686     *
15687     * @listens mousedown
15688     * @listens touchstart
15689     */
15690    ;
15691
15692    _proto.handleMouseDown = function handleMouseDown(event) {
15693      var doc = this.el_.ownerDocument;
15694      var seekBar = this.getChild('seekBar');
15695
15696      if (seekBar) {
15697        seekBar.handleMouseDown(event);
15698      }
15699
15700      this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
15701      this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
15702      this.on(doc, 'mouseup', this.handleMouseUp);
15703      this.on(doc, 'touchend', this.handleMouseUp);
15704    }
15705    /**
15706     * Handle `mouseup` or `touchend` events on the `ProgressControl`.
15707     *
15708     * @param {EventTarget~Event} event
15709     *        `mouseup` or `touchend` event that triggered this function.
15710     *
15711     * @listens touchend
15712     * @listens mouseup
15713     */
15714    ;
15715
15716    _proto.handleMouseUp = function handleMouseUp(event) {
15717      var doc = this.el_.ownerDocument;
15718      var seekBar = this.getChild('seekBar');
15719
15720      if (seekBar) {
15721        seekBar.handleMouseUp(event);
15722      }
15723
15724      this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
15725      this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
15726      this.off(doc, 'mouseup', this.handleMouseUp);
15727      this.off(doc, 'touchend', this.handleMouseUp);
15728    };
15729
15730    return ProgressControl;
15731  }(Component);
15732  /**
15733   * Default options for `ProgressControl`
15734   *
15735   * @type {Object}
15736   * @private
15737   */
15738
15739
15740  ProgressControl.prototype.options_ = {
15741    children: ['seekBar']
15742  };
15743  Component.registerComponent('ProgressControl', ProgressControl);
15744
15745  /**
15746   * Toggle fullscreen video
15747   *
15748   * @extends Button
15749   */
15750
15751  var FullscreenToggle =
15752  /*#__PURE__*/
15753  function (_Button) {
15754    _inheritsLoose(FullscreenToggle, _Button);
15755
15756    /**
15757     * Creates an instance of this class.
15758     *
15759     * @param {Player} player
15760     *        The `Player` that this class should be attached to.
15761     *
15762     * @param {Object} [options]
15763     *        The key/value store of player options.
15764     */
15765    function FullscreenToggle(player, options) {
15766      var _this;
15767
15768      _this = _Button.call(this, player, options) || this;
15769
15770      _this.on(player, 'fullscreenchange', _this.handleFullscreenChange);
15771
15772      if (document[FullscreenApi.fullscreenEnabled] === false) {
15773        _this.disable();
15774      }
15775
15776      return _this;
15777    }
15778    /**
15779     * Builds the default DOM `className`.
15780     *
15781     * @return {string}
15782     *         The DOM `className` for this object.
15783     */
15784
15785
15786    var _proto = FullscreenToggle.prototype;
15787
15788    _proto.buildCSSClass = function buildCSSClass() {
15789      return "vjs-fullscreen-control " + _Button.prototype.buildCSSClass.call(this);
15790    }
15791    /**
15792     * Handles fullscreenchange on the player and change control text accordingly.
15793     *
15794     * @param {EventTarget~Event} [event]
15795     *        The {@link Player#fullscreenchange} event that caused this function to be
15796     *        called.
15797     *
15798     * @listens Player#fullscreenchange
15799     */
15800    ;
15801
15802    _proto.handleFullscreenChange = function handleFullscreenChange(event) {
15803      if (this.player_.isFullscreen()) {
15804        this.controlText('Non-Fullscreen');
15805      } else {
15806        this.controlText('Fullscreen');
15807      }
15808    }
15809    /**
15810     * This gets called when an `FullscreenToggle` is "clicked". See
15811     * {@link ClickableComponent} for more detailed information on what a click can be.
15812     *
15813     * @param {EventTarget~Event} [event]
15814     *        The `keydown`, `tap`, or `click` event that caused this function to be
15815     *        called.
15816     *
15817     * @listens tap
15818     * @listens click
15819     */
15820    ;
15821
15822    _proto.handleClick = function handleClick(event) {
15823      if (!this.player_.isFullscreen()) {
15824        this.player_.requestFullscreen();
15825      } else {
15826        this.player_.exitFullscreen();
15827      }
15828    };
15829
15830    return FullscreenToggle;
15831  }(Button);
15832  /**
15833   * The text that should display over the `FullscreenToggle`s controls. Added for localization.
15834   *
15835   * @type {string}
15836   * @private
15837   */
15838
15839
15840  FullscreenToggle.prototype.controlText_ = 'Fullscreen';
15841  Component.registerComponent('FullscreenToggle', FullscreenToggle);
15842
15843  /**
15844   * Check if volume control is supported and if it isn't hide the
15845   * `Component` that was passed  using the `vjs-hidden` class.
15846   *
15847   * @param {Component} self
15848   *        The component that should be hidden if volume is unsupported
15849   *
15850   * @param {Player} player
15851   *        A reference to the player
15852   *
15853   * @private
15854   */
15855  var checkVolumeSupport = function checkVolumeSupport(self, player) {
15856    // hide volume controls when they're not supported by the current tech
15857    if (player.tech_ && !player.tech_.featuresVolumeControl) {
15858      self.addClass('vjs-hidden');
15859    }
15860
15861    self.on(player, 'loadstart', function () {
15862      if (!player.tech_.featuresVolumeControl) {
15863        self.addClass('vjs-hidden');
15864      } else {
15865        self.removeClass('vjs-hidden');
15866      }
15867    });
15868  };
15869
15870  /**
15871   * Shows volume level
15872   *
15873   * @extends Component
15874   */
15875
15876  var VolumeLevel =
15877  /*#__PURE__*/
15878  function (_Component) {
15879    _inheritsLoose(VolumeLevel, _Component);
15880
15881    function VolumeLevel() {
15882      return _Component.apply(this, arguments) || this;
15883    }
15884
15885    var _proto = VolumeLevel.prototype;
15886
15887    /**
15888     * Create the `Component`'s DOM element
15889     *
15890     * @return {Element}
15891     *         The element that was created.
15892     */
15893    _proto.createEl = function createEl() {
15894      return _Component.prototype.createEl.call(this, 'div', {
15895        className: 'vjs-volume-level',
15896        innerHTML: '<span class="vjs-control-text"></span>'
15897      });
15898    };
15899
15900    return VolumeLevel;
15901  }(Component);
15902
15903  Component.registerComponent('VolumeLevel', VolumeLevel);
15904
15905  /**
15906   * The bar that contains the volume level and can be clicked on to adjust the level
15907   *
15908   * @extends Slider
15909   */
15910
15911  var VolumeBar =
15912  /*#__PURE__*/
15913  function (_Slider) {
15914    _inheritsLoose(VolumeBar, _Slider);
15915
15916    /**
15917     * Creates an instance of this class.
15918     *
15919     * @param {Player} player
15920     *        The `Player` that this class should be attached to.
15921     *
15922     * @param {Object} [options]
15923     *        The key/value store of player options.
15924     */
15925    function VolumeBar(player, options) {
15926      var _this;
15927
15928      _this = _Slider.call(this, player, options) || this;
15929
15930      _this.on('slideractive', _this.updateLastVolume_);
15931
15932      _this.on(player, 'volumechange', _this.updateARIAAttributes);
15933
15934      player.ready(function () {
15935        return _this.updateARIAAttributes();
15936      });
15937      return _this;
15938    }
15939    /**
15940     * Create the `Component`'s DOM element
15941     *
15942     * @return {Element}
15943     *         The element that was created.
15944     */
15945
15946
15947    var _proto = VolumeBar.prototype;
15948
15949    _proto.createEl = function createEl$$1() {
15950      return _Slider.prototype.createEl.call(this, 'div', {
15951        className: 'vjs-volume-bar vjs-slider-bar'
15952      }, {
15953        'aria-label': this.localize('Volume Level'),
15954        'aria-live': 'polite'
15955      });
15956    }
15957    /**
15958     * Handle mouse down on volume bar
15959     *
15960     * @param {EventTarget~Event} event
15961     *        The `mousedown` event that caused this to run.
15962     *
15963     * @listens mousedown
15964     */
15965    ;
15966
15967    _proto.handleMouseDown = function handleMouseDown(event) {
15968      if (!isSingleLeftClick(event)) {
15969        return;
15970      }
15971
15972      _Slider.prototype.handleMouseDown.call(this, event);
15973    }
15974    /**
15975     * Handle movement events on the {@link VolumeMenuButton}.
15976     *
15977     * @param {EventTarget~Event} event
15978     *        The event that caused this function to run.
15979     *
15980     * @listens mousemove
15981     */
15982    ;
15983
15984    _proto.handleMouseMove = function handleMouseMove(event) {
15985      if (!isSingleLeftClick(event)) {
15986        return;
15987      }
15988
15989      this.checkMuted();
15990      this.player_.volume(this.calculateDistance(event));
15991    }
15992    /**
15993     * If the player is muted unmute it.
15994     */
15995    ;
15996
15997    _proto.checkMuted = function checkMuted() {
15998      if (this.player_.muted()) {
15999        this.player_.muted(false);
16000      }
16001    }
16002    /**
16003     * Get percent of volume level
16004     *
16005     * @return {number}
16006     *         Volume level percent as a decimal number.
16007     */
16008    ;
16009
16010    _proto.getPercent = function getPercent() {
16011      if (this.player_.muted()) {
16012        return 0;
16013      }
16014
16015      return this.player_.volume();
16016    }
16017    /**
16018     * Increase volume level for keyboard users
16019     */
16020    ;
16021
16022    _proto.stepForward = function stepForward() {
16023      this.checkMuted();
16024      this.player_.volume(this.player_.volume() + 0.1);
16025    }
16026    /**
16027     * Decrease volume level for keyboard users
16028     */
16029    ;
16030
16031    _proto.stepBack = function stepBack() {
16032      this.checkMuted();
16033      this.player_.volume(this.player_.volume() - 0.1);
16034    }
16035    /**
16036     * Update ARIA accessibility attributes
16037     *
16038     * @param {EventTarget~Event} [event]
16039     *        The `volumechange` event that caused this function to run.
16040     *
16041     * @listens Player#volumechange
16042     */
16043    ;
16044
16045    _proto.updateARIAAttributes = function updateARIAAttributes(event) {
16046      var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
16047      this.el_.setAttribute('aria-valuenow', ariaValue);
16048      this.el_.setAttribute('aria-valuetext', ariaValue + '%');
16049    }
16050    /**
16051     * Returns the current value of the player volume as a percentage
16052     *
16053     * @private
16054     */
16055    ;
16056
16057    _proto.volumeAsPercentage_ = function volumeAsPercentage_() {
16058      return Math.round(this.player_.volume() * 100);
16059    }
16060    /**
16061     * When user starts dragging the VolumeBar, store the volume and listen for
16062     * the end of the drag. When the drag ends, if the volume was set to zero,
16063     * set lastVolume to the stored volume.
16064     *
16065     * @listens slideractive
16066     * @private
16067     */
16068    ;
16069
16070    _proto.updateLastVolume_ = function updateLastVolume_() {
16071      var _this2 = this;
16072
16073      var volumeBeforeDrag = this.player_.volume();
16074      this.one('sliderinactive', function () {
16075        if (_this2.player_.volume() === 0) {
16076          _this2.player_.lastVolume_(volumeBeforeDrag);
16077        }
16078      });
16079    };
16080
16081    return VolumeBar;
16082  }(Slider);
16083  /**
16084   * Default options for the `VolumeBar`
16085   *
16086   * @type {Object}
16087   * @private
16088   */
16089
16090
16091  VolumeBar.prototype.options_ = {
16092    children: ['volumeLevel'],
16093    barName: 'volumeLevel'
16094  };
16095  /**
16096   * Call the update event for this Slider when this event happens on the player.
16097   *
16098   * @type {string}
16099   */
16100
16101  VolumeBar.prototype.playerEvent = 'volumechange';
16102  Component.registerComponent('VolumeBar', VolumeBar);
16103
16104  /**
16105   * The component for controlling the volume level
16106   *
16107   * @extends Component
16108   */
16109
16110  var VolumeControl =
16111  /*#__PURE__*/
16112  function (_Component) {
16113    _inheritsLoose(VolumeControl, _Component);
16114
16115    /**
16116     * Creates an instance of this class.
16117     *
16118     * @param {Player} player
16119     *        The `Player` that this class should be attached to.
16120     *
16121     * @param {Object} [options={}]
16122     *        The key/value store of player options.
16123     */
16124    function VolumeControl(player, options) {
16125      var _this;
16126
16127      if (options === void 0) {
16128        options = {};
16129      }
16130
16131      options.vertical = options.vertical || false; // Pass the vertical option down to the VolumeBar if
16132      // the VolumeBar is turned on.
16133
16134      if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
16135        options.volumeBar = options.volumeBar || {};
16136        options.volumeBar.vertical = options.vertical;
16137      }
16138
16139      _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing
16140
16141      checkVolumeSupport(_assertThisInitialized(_assertThisInitialized(_this)), player);
16142      _this.throttledHandleMouseMove = throttle(bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleMouseMove), 25);
16143
16144      _this.on('mousedown', _this.handleMouseDown);
16145
16146      _this.on('touchstart', _this.handleMouseDown); // while the slider is active (the mouse has been pressed down and
16147      // is dragging) or in focus we do not want to hide the VolumeBar
16148
16149
16150      _this.on(_this.volumeBar, ['focus', 'slideractive'], function () {
16151        _this.volumeBar.addClass('vjs-slider-active');
16152
16153        _this.addClass('vjs-slider-active');
16154
16155        _this.trigger('slideractive');
16156      });
16157
16158      _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () {
16159        _this.volumeBar.removeClass('vjs-slider-active');
16160
16161        _this.removeClass('vjs-slider-active');
16162
16163        _this.trigger('sliderinactive');
16164      });
16165
16166      return _this;
16167    }
16168    /**
16169     * Create the `Component`'s DOM element
16170     *
16171     * @return {Element}
16172     *         The element that was created.
16173     */
16174
16175
16176    var _proto = VolumeControl.prototype;
16177
16178    _proto.createEl = function createEl() {
16179      var orientationClass = 'vjs-volume-horizontal';
16180
16181      if (this.options_.vertical) {
16182        orientationClass = 'vjs-volume-vertical';
16183      }
16184
16185      return _Component.prototype.createEl.call(this, 'div', {
16186        className: "vjs-volume-control vjs-control " + orientationClass
16187      });
16188    }
16189    /**
16190     * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
16191     *
16192     * @param {EventTarget~Event} event
16193     *        `mousedown` or `touchstart` event that triggered this function
16194     *
16195     * @listens mousedown
16196     * @listens touchstart
16197     */
16198    ;
16199
16200    _proto.handleMouseDown = function handleMouseDown(event) {
16201      var doc = this.el_.ownerDocument;
16202      this.on(doc, 'mousemove', this.throttledHandleMouseMove);
16203      this.on(doc, 'touchmove', this.throttledHandleMouseMove);
16204      this.on(doc, 'mouseup', this.handleMouseUp);
16205      this.on(doc, 'touchend', this.handleMouseUp);
16206    }
16207    /**
16208     * Handle `mouseup` or `touchend` events on the `VolumeControl`.
16209     *
16210     * @param {EventTarget~Event} event
16211     *        `mouseup` or `touchend` event that triggered this function.
16212     *
16213     * @listens touchend
16214     * @listens mouseup
16215     */
16216    ;
16217
16218    _proto.handleMouseUp = function handleMouseUp(event) {
16219      var doc = this.el_.ownerDocument;
16220      this.off(doc, 'mousemove', this.throttledHandleMouseMove);
16221      this.off(doc, 'touchmove', this.throttledHandleMouseMove);
16222      this.off(doc, 'mouseup', this.handleMouseUp);
16223      this.off(doc, 'touchend', this.handleMouseUp);
16224    }
16225    /**
16226     * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
16227     *
16228     * @param {EventTarget~Event} event
16229     *        `mousedown` or `touchstart` event that triggered this function
16230     *
16231     * @listens mousedown
16232     * @listens touchstart
16233     */
16234    ;
16235
16236    _proto.handleMouseMove = function handleMouseMove(event) {
16237      this.volumeBar.handleMouseMove(event);
16238    };
16239
16240    return VolumeControl;
16241  }(Component);
16242  /**
16243   * Default options for the `VolumeControl`
16244   *
16245   * @type {Object}
16246   * @private
16247   */
16248
16249
16250  VolumeControl.prototype.options_ = {
16251    children: ['volumeBar']
16252  };
16253  Component.registerComponent('VolumeControl', VolumeControl);
16254
16255  /**
16256   * Check if muting volume is supported and if it isn't hide the mute toggle
16257   * button.
16258   *
16259   * @param {Component} self
16260   *        A reference to the mute toggle button
16261   *
16262   * @param {Player} player
16263   *        A reference to the player
16264   *
16265   * @private
16266   */
16267  var checkMuteSupport = function checkMuteSupport(self, player) {
16268    // hide mute toggle button if it's not supported by the current tech
16269    if (player.tech_ && !player.tech_.featuresMuteControl) {
16270      self.addClass('vjs-hidden');
16271    }
16272
16273    self.on(player, 'loadstart', function () {
16274      if (!player.tech_.featuresMuteControl) {
16275        self.addClass('vjs-hidden');
16276      } else {
16277        self.removeClass('vjs-hidden');
16278      }
16279    });
16280  };
16281
16282  /**
16283   * A button component for muting the audio.
16284   *
16285   * @extends Button
16286   */
16287
16288  var MuteToggle =
16289  /*#__PURE__*/
16290  function (_Button) {
16291    _inheritsLoose(MuteToggle, _Button);
16292
16293    /**
16294     * Creates an instance of this class.
16295     *
16296     * @param {Player} player
16297     *        The `Player` that this class should be attached to.
16298     *
16299     * @param {Object} [options]
16300     *        The key/value store of player options.
16301     */
16302    function MuteToggle(player, options) {
16303      var _this;
16304
16305      _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing
16306
16307      checkMuteSupport(_assertThisInitialized(_assertThisInitialized(_this)), player);
16308
16309      _this.on(player, ['loadstart', 'volumechange'], _this.update);
16310
16311      return _this;
16312    }
16313    /**
16314     * Builds the default DOM `className`.
16315     *
16316     * @return {string}
16317     *         The DOM `className` for this object.
16318     */
16319
16320
16321    var _proto = MuteToggle.prototype;
16322
16323    _proto.buildCSSClass = function buildCSSClass() {
16324      return "vjs-mute-control " + _Button.prototype.buildCSSClass.call(this);
16325    }
16326    /**
16327     * This gets called when an `MuteToggle` is "clicked". See
16328     * {@link ClickableComponent} for more detailed information on what a click can be.
16329     *
16330     * @param {EventTarget~Event} [event]
16331     *        The `keydown`, `tap`, or `click` event that caused this function to be
16332     *        called.
16333     *
16334     * @listens tap
16335     * @listens click
16336     */
16337    ;
16338
16339    _proto.handleClick = function handleClick(event) {
16340      var vol = this.player_.volume();
16341      var lastVolume = this.player_.lastVolume_();
16342
16343      if (vol === 0) {
16344        var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
16345        this.player_.volume(volumeToSet);
16346        this.player_.muted(false);
16347      } else {
16348        this.player_.muted(this.player_.muted() ? false : true);
16349      }
16350    }
16351    /**
16352     * Update the `MuteToggle` button based on the state of `volume` and `muted`
16353     * on the player.
16354     *
16355     * @param {EventTarget~Event} [event]
16356     *        The {@link Player#loadstart} event if this function was called
16357     *        through an event.
16358     *
16359     * @listens Player#loadstart
16360     * @listens Player#volumechange
16361     */
16362    ;
16363
16364    _proto.update = function update(event) {
16365      this.updateIcon_();
16366      this.updateControlText_();
16367    }
16368    /**
16369     * Update the appearance of the `MuteToggle` icon.
16370     *
16371     * Possible states (given `level` variable below):
16372     * - 0: crossed out
16373     * - 1: zero bars of volume
16374     * - 2: one bar of volume
16375     * - 3: two bars of volume
16376     *
16377     * @private
16378     */
16379    ;
16380
16381    _proto.updateIcon_ = function updateIcon_() {
16382      var vol = this.player_.volume();
16383      var level = 3; // in iOS when a player is loaded with muted attribute
16384      // and volume is changed with a native mute button
16385      // we want to make sure muted state is updated
16386
16387      if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) {
16388        this.player_.muted(this.player_.tech_.el_.muted);
16389      }
16390
16391      if (vol === 0 || this.player_.muted()) {
16392        level = 0;
16393      } else if (vol < 0.33) {
16394        level = 1;
16395      } else if (vol < 0.67) {
16396        level = 2;
16397      } // TODO improve muted icon classes
16398
16399
16400      for (var i = 0; i < 4; i++) {
16401        removeClass(this.el_, "vjs-vol-" + i);
16402      }
16403
16404      addClass(this.el_, "vjs-vol-" + level);
16405    }
16406    /**
16407     * If `muted` has changed on the player, update the control text
16408     * (`title` attribute on `vjs-mute-control` element and content of
16409     * `vjs-control-text` element).
16410     *
16411     * @private
16412     */
16413    ;
16414
16415    _proto.updateControlText_ = function updateControlText_() {
16416      var soundOff = this.player_.muted() || this.player_.volume() === 0;
16417      var text = soundOff ? 'Unmute' : 'Mute';
16418
16419      if (this.controlText() !== text) {
16420        this.controlText(text);
16421      }
16422    };
16423
16424    return MuteToggle;
16425  }(Button);
16426  /**
16427   * The text that should display over the `MuteToggle`s controls. Added for localization.
16428   *
16429   * @type {string}
16430   * @private
16431   */
16432
16433
16434  MuteToggle.prototype.controlText_ = 'Mute';
16435  Component.registerComponent('MuteToggle', MuteToggle);
16436
16437  /**
16438   * A Component to contain the MuteToggle and VolumeControl so that
16439   * they can work together.
16440   *
16441   * @extends Component
16442   */
16443
16444  var VolumePanel =
16445  /*#__PURE__*/
16446  function (_Component) {
16447    _inheritsLoose(VolumePanel, _Component);
16448
16449    /**
16450     * Creates an instance of this class.
16451     *
16452     * @param {Player} player
16453     *        The `Player` that this class should be attached to.
16454     *
16455     * @param {Object} [options={}]
16456     *        The key/value store of player options.
16457     */
16458    function VolumePanel(player, options) {
16459      var _this;
16460
16461      if (options === void 0) {
16462        options = {};
16463      }
16464
16465      if (typeof options.inline !== 'undefined') {
16466        options.inline = options.inline;
16467      } else {
16468        options.inline = true;
16469      } // pass the inline option down to the VolumeControl as vertical if
16470      // the VolumeControl is on.
16471
16472
16473      if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
16474        options.volumeControl = options.volumeControl || {};
16475        options.volumeControl.vertical = !options.inline;
16476      }
16477
16478      _this = _Component.call(this, player, options) || this;
16479
16480      _this.on(player, ['loadstart'], _this.volumePanelState_); // while the slider is active (the mouse has been pressed down and
16481      // is dragging) we do not want to hide the VolumeBar
16482
16483
16484      _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_);
16485
16486      _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_);
16487
16488      return _this;
16489    }
16490    /**
16491     * Add vjs-slider-active class to the VolumePanel
16492     *
16493     * @listens VolumeControl#slideractive
16494     * @private
16495     */
16496
16497
16498    var _proto = VolumePanel.prototype;
16499
16500    _proto.sliderActive_ = function sliderActive_() {
16501      this.addClass('vjs-slider-active');
16502    }
16503    /**
16504     * Removes vjs-slider-active class to the VolumePanel
16505     *
16506     * @listens VolumeControl#sliderinactive
16507     * @private
16508     */
16509    ;
16510
16511    _proto.sliderInactive_ = function sliderInactive_() {
16512      this.removeClass('vjs-slider-active');
16513    }
16514    /**
16515     * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
16516     * depending on MuteToggle and VolumeControl state
16517     *
16518     * @listens Player#loadstart
16519     * @private
16520     */
16521    ;
16522
16523    _proto.volumePanelState_ = function volumePanelState_() {
16524      // hide volume panel if neither volume control or mute toggle
16525      // are displayed
16526      if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
16527        this.addClass('vjs-hidden');
16528      } // if only mute toggle is visible we don't want
16529      // volume panel expanding when hovered or active
16530
16531
16532      if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
16533        this.addClass('vjs-mute-toggle-only');
16534      }
16535    }
16536    /**
16537     * Create the `Component`'s DOM element
16538     *
16539     * @return {Element}
16540     *         The element that was created.
16541     */
16542    ;
16543
16544    _proto.createEl = function createEl() {
16545      var orientationClass = 'vjs-volume-panel-horizontal';
16546
16547      if (!this.options_.inline) {
16548        orientationClass = 'vjs-volume-panel-vertical';
16549      }
16550
16551      return _Component.prototype.createEl.call(this, 'div', {
16552        className: "vjs-volume-panel vjs-control " + orientationClass
16553      });
16554    };
16555
16556    return VolumePanel;
16557  }(Component);
16558  /**
16559   * Default options for the `VolumeControl`
16560   *
16561   * @type {Object}
16562   * @private
16563   */
16564
16565
16566  VolumePanel.prototype.options_ = {
16567    children: ['muteToggle', 'volumeControl']
16568  };
16569  Component.registerComponent('VolumePanel', VolumePanel);
16570
16571  /**
16572   * The Menu component is used to build popup menus, including subtitle and
16573   * captions selection menus.
16574   *
16575   * @extends Component
16576   */
16577
16578  var Menu =
16579  /*#__PURE__*/
16580  function (_Component) {
16581    _inheritsLoose(Menu, _Component);
16582
16583    /**
16584     * Create an instance of this class.
16585     *
16586     * @param {Player} player
16587     *        the player that this component should attach to
16588     *
16589     * @param {Object} [options]
16590     *        Object of option names and values
16591     *
16592     */
16593    function Menu(player, options) {
16594      var _this;
16595
16596      _this = _Component.call(this, player, options) || this;
16597
16598      if (options) {
16599        _this.menuButton_ = options.menuButton;
16600      }
16601
16602      _this.focusedChild_ = -1;
16603
16604      _this.on('keydown', _this.handleKeyDown); // All the menu item instances share the same blur handler provided by the menu container.
16605
16606
16607      _this.boundHandleBlur_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleBlur);
16608      _this.boundHandleTapClick_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleTapClick);
16609      return _this;
16610    }
16611    /**
16612     * Add event listeners to the {@link MenuItem}.
16613     *
16614     * @param {Object} component
16615     *        The instance of the `MenuItem` to add listeners to.
16616     *
16617     */
16618
16619
16620    var _proto = Menu.prototype;
16621
16622    _proto.addEventListenerForItem = function addEventListenerForItem(component) {
16623      if (!(component instanceof Component)) {
16624        return;
16625      }
16626
16627      this.on(component, 'blur', this.boundHandleBlur_);
16628      this.on(component, ['tap', 'click'], this.boundHandleTapClick_);
16629    }
16630    /**
16631     * Remove event listeners from the {@link MenuItem}.
16632     *
16633     * @param {Object} component
16634     *        The instance of the `MenuItem` to remove listeners.
16635     *
16636     */
16637    ;
16638
16639    _proto.removeEventListenerForItem = function removeEventListenerForItem(component) {
16640      if (!(component instanceof Component)) {
16641        return;
16642      }
16643
16644      this.off(component, 'blur', this.boundHandleBlur_);
16645      this.off(component, ['tap', 'click'], this.boundHandleTapClick_);
16646    }
16647    /**
16648     * This method will be called indirectly when the component has been added
16649     * before the component adds to the new menu instance by `addItem`.
16650     * In this case, the original menu instance will remove the component
16651     * by calling `removeChild`.
16652     *
16653     * @param {Object} component
16654     *        The instance of the `MenuItem`
16655     */
16656    ;
16657
16658    _proto.removeChild = function removeChild(component) {
16659      if (typeof component === 'string') {
16660        component = this.getChild(component);
16661      }
16662
16663      this.removeEventListenerForItem(component);
16664
16665      _Component.prototype.removeChild.call(this, component);
16666    }
16667    /**
16668     * Add a {@link MenuItem} to the menu.
16669     *
16670     * @param {Object|string} component
16671     *        The name or instance of the `MenuItem` to add.
16672     *
16673     */
16674    ;
16675
16676    _proto.addItem = function addItem(component) {
16677      var childComponent = this.addChild(component);
16678
16679      if (childComponent) {
16680        this.addEventListenerForItem(childComponent);
16681      }
16682    }
16683    /**
16684     * Create the `Menu`s DOM element.
16685     *
16686     * @return {Element}
16687     *         the element that was created
16688     */
16689    ;
16690
16691    _proto.createEl = function createEl$$1() {
16692      var contentElType = this.options_.contentElType || 'ul';
16693      this.contentEl_ = createEl(contentElType, {
16694        className: 'vjs-menu-content'
16695      });
16696      this.contentEl_.setAttribute('role', 'menu');
16697
16698      var el = _Component.prototype.createEl.call(this, 'div', {
16699        append: this.contentEl_,
16700        className: 'vjs-menu'
16701      });
16702
16703      el.appendChild(this.contentEl_); // Prevent clicks from bubbling up. Needed for Menu Buttons,
16704      // where a click on the parent is significant
16705
16706      on(el, 'click', function (event) {
16707        event.preventDefault();
16708        event.stopImmediatePropagation();
16709      });
16710      return el;
16711    };
16712
16713    _proto.dispose = function dispose() {
16714      this.contentEl_ = null;
16715      this.boundHandleBlur_ = null;
16716      this.boundHandleTapClick_ = null;
16717
16718      _Component.prototype.dispose.call(this);
16719    }
16720    /**
16721     * Called when a `MenuItem` loses focus.
16722     *
16723     * @param {EventTarget~Event} event
16724     *        The `blur` event that caused this function to be called.
16725     *
16726     * @listens blur
16727     */
16728    ;
16729
16730    _proto.handleBlur = function handleBlur(event) {
16731      var relatedTarget = event.relatedTarget || document.activeElement; // Close menu popup when a user clicks outside the menu
16732
16733      if (!this.children().some(function (element) {
16734        return element.el() === relatedTarget;
16735      })) {
16736        var btn = this.menuButton_;
16737
16738        if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) {
16739          btn.unpressButton();
16740        }
16741      }
16742    }
16743    /**
16744     * Called when a `MenuItem` gets clicked or tapped.
16745     *
16746     * @param {EventTarget~Event} event
16747     *        The `click` or `tap` event that caused this function to be called.
16748     *
16749     * @listens click,tap
16750     */
16751    ;
16752
16753    _proto.handleTapClick = function handleTapClick(event) {
16754      // Unpress the associated MenuButton, and move focus back to it
16755      if (this.menuButton_) {
16756        this.menuButton_.unpressButton();
16757        var childComponents = this.children();
16758
16759        if (!Array.isArray(childComponents)) {
16760          return;
16761        }
16762
16763        var foundComponent = childComponents.filter(function (component) {
16764          return component.el() === event.target;
16765        })[0];
16766
16767        if (!foundComponent) {
16768          return;
16769        } // don't focus menu button if item is a caption settings item
16770        // because focus will move elsewhere
16771
16772
16773        if (foundComponent.name() !== 'CaptionSettingsMenuItem') {
16774          this.menuButton_.focus();
16775        }
16776      }
16777    }
16778    /**
16779     * Handle a `keydown` event on this menu. This listener is added in the constructor.
16780     *
16781     * @param {EventTarget~Event} event
16782     *        A `keydown` event that happened on the menu.
16783     *
16784     * @listens keydown
16785     */
16786    ;
16787
16788    _proto.handleKeyDown = function handleKeyDown(event) {
16789      // Left and Down Arrows
16790      if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
16791        event.preventDefault();
16792        event.stopPropagation();
16793        this.stepForward(); // Up and Right Arrows
16794      } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
16795        event.preventDefault();
16796        event.stopPropagation();
16797        this.stepBack();
16798      }
16799    }
16800    /**
16801     * Move to next (lower) menu item for keyboard users.
16802     */
16803    ;
16804
16805    _proto.stepForward = function stepForward() {
16806      var stepChild = 0;
16807
16808      if (this.focusedChild_ !== undefined) {
16809        stepChild = this.focusedChild_ + 1;
16810      }
16811
16812      this.focus(stepChild);
16813    }
16814    /**
16815     * Move to previous (higher) menu item for keyboard users.
16816     */
16817    ;
16818
16819    _proto.stepBack = function stepBack() {
16820      var stepChild = 0;
16821
16822      if (this.focusedChild_ !== undefined) {
16823        stepChild = this.focusedChild_ - 1;
16824      }
16825
16826      this.focus(stepChild);
16827    }
16828    /**
16829     * Set focus on a {@link MenuItem} in the `Menu`.
16830     *
16831     * @param {Object|string} [item=0]
16832     *        Index of child item set focus on.
16833     */
16834    ;
16835
16836    _proto.focus = function focus(item) {
16837      if (item === void 0) {
16838        item = 0;
16839      }
16840
16841      var children = this.children().slice();
16842      var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className);
16843
16844      if (haveTitle) {
16845        children.shift();
16846      }
16847
16848      if (children.length > 0) {
16849        if (item < 0) {
16850          item = 0;
16851        } else if (item >= children.length) {
16852          item = children.length - 1;
16853        }
16854
16855        this.focusedChild_ = item;
16856        children[item].el_.focus();
16857      }
16858    };
16859
16860    return Menu;
16861  }(Component);
16862
16863  Component.registerComponent('Menu', Menu);
16864
16865  /**
16866   * A `MenuButton` class for any popup {@link Menu}.
16867   *
16868   * @extends Component
16869   */
16870
16871  var MenuButton =
16872  /*#__PURE__*/
16873  function (_Component) {
16874    _inheritsLoose(MenuButton, _Component);
16875
16876    /**
16877     * Creates an instance of this class.
16878     *
16879     * @param {Player} player
16880     *        The `Player` that this class should be attached to.
16881     *
16882     * @param {Object} [options={}]
16883     *        The key/value store of player options.
16884     */
16885    function MenuButton(player, options) {
16886      var _this;
16887
16888      if (options === void 0) {
16889        options = {};
16890      }
16891
16892      _this = _Component.call(this, player, options) || this;
16893      _this.menuButton_ = new Button(player, options);
16894
16895      _this.menuButton_.controlText(_this.controlText_);
16896
16897      _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); // Add buildCSSClass values to the button, not the wrapper
16898
16899
16900      var buttonClass = Button.prototype.buildCSSClass();
16901      _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass;
16902
16903      _this.menuButton_.removeClass('vjs-control');
16904
16905      _this.addChild(_this.menuButton_);
16906
16907      _this.update();
16908
16909      _this.enabled_ = true;
16910
16911      _this.on(_this.menuButton_, 'tap', _this.handleClick);
16912
16913      _this.on(_this.menuButton_, 'click', _this.handleClick);
16914
16915      _this.on(_this.menuButton_, 'keydown', _this.handleKeyDown);
16916
16917      _this.on(_this.menuButton_, 'mouseenter', function () {
16918        _this.menu.show();
16919      });
16920
16921      _this.on('keydown', _this.handleSubmenuKeyDown);
16922
16923      return _this;
16924    }
16925    /**
16926     * Update the menu based on the current state of its items.
16927     */
16928
16929
16930    var _proto = MenuButton.prototype;
16931
16932    _proto.update = function update() {
16933      var menu = this.createMenu();
16934
16935      if (this.menu) {
16936        this.menu.dispose();
16937        this.removeChild(this.menu);
16938      }
16939
16940      this.menu = menu;
16941      this.addChild(menu);
16942      /**
16943       * Track the state of the menu button
16944       *
16945       * @type {Boolean}
16946       * @private
16947       */
16948
16949      this.buttonPressed_ = false;
16950      this.menuButton_.el_.setAttribute('aria-expanded', 'false');
16951
16952      if (this.items && this.items.length <= this.hideThreshold_) {
16953        this.hide();
16954      } else {
16955        this.show();
16956      }
16957    }
16958    /**
16959     * Create the menu and add all items to it.
16960     *
16961     * @return {Menu}
16962     *         The constructed menu
16963     */
16964    ;
16965
16966    _proto.createMenu = function createMenu() {
16967      var menu = new Menu(this.player_, {
16968        menuButton: this
16969      });
16970      /**
16971       * Hide the menu if the number of items is less than or equal to this threshold. This defaults
16972       * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
16973       * it here because every time we run `createMenu` we need to reset the value.
16974       *
16975       * @protected
16976       * @type {Number}
16977       */
16978
16979      this.hideThreshold_ = 0; // Add a title list item to the top
16980
16981      if (this.options_.title) {
16982        var titleEl = createEl('li', {
16983          className: 'vjs-menu-title',
16984          innerHTML: toTitleCase(this.options_.title),
16985          tabIndex: -1
16986        });
16987        this.hideThreshold_ += 1;
16988        var titleComponent = new Component(this.player_, {
16989          el: titleEl
16990        });
16991        menu.addItem(titleComponent);
16992      }
16993
16994      this.items = this.createItems();
16995
16996      if (this.items) {
16997        // Add menu items to the menu
16998        for (var i = 0; i < this.items.length; i++) {
16999          menu.addItem(this.items[i]);
17000        }
17001      }
17002
17003      return menu;
17004    }
17005    /**
17006     * Create the list of menu items. Specific to each subclass.
17007     *
17008     * @abstract
17009     */
17010    ;
17011
17012    _proto.createItems = function createItems() {}
17013    /**
17014     * Create the `MenuButtons`s DOM element.
17015     *
17016     * @return {Element}
17017     *         The element that gets created.
17018     */
17019    ;
17020
17021    _proto.createEl = function createEl$$1() {
17022      return _Component.prototype.createEl.call(this, 'div', {
17023        className: this.buildWrapperCSSClass()
17024      }, {});
17025    }
17026    /**
17027     * Allow sub components to stack CSS class names for the wrapper element
17028     *
17029     * @return {string}
17030     *         The constructed wrapper DOM `className`
17031     */
17032    ;
17033
17034    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
17035      var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
17036
17037      if (this.options_.inline === true) {
17038        menuButtonClass += '-inline';
17039      } else {
17040        menuButtonClass += '-popup';
17041      } // TODO: Fix the CSS so that this isn't necessary
17042
17043
17044      var buttonClass = Button.prototype.buildCSSClass();
17045      return "vjs-menu-button " + menuButtonClass + " " + buttonClass + " " + _Component.prototype.buildCSSClass.call(this);
17046    }
17047    /**
17048     * Builds the default DOM `className`.
17049     *
17050     * @return {string}
17051     *         The DOM `className` for this object.
17052     */
17053    ;
17054
17055    _proto.buildCSSClass = function buildCSSClass() {
17056      var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether.
17057
17058      if (this.options_.inline === true) {
17059        menuButtonClass += '-inline';
17060      } else {
17061        menuButtonClass += '-popup';
17062      }
17063
17064      return "vjs-menu-button " + menuButtonClass + " " + _Component.prototype.buildCSSClass.call(this);
17065    }
17066    /**
17067     * Get or set the localized control text that will be used for accessibility.
17068     *
17069     * > NOTE: This will come from the internal `menuButton_` element.
17070     *
17071     * @param {string} [text]
17072     *        Control text for element.
17073     *
17074     * @param {Element} [el=this.menuButton_.el()]
17075     *        Element to set the title on.
17076     *
17077     * @return {string}
17078     *         - The control text when getting
17079     */
17080    ;
17081
17082    _proto.controlText = function controlText(text, el) {
17083      if (el === void 0) {
17084        el = this.menuButton_.el();
17085      }
17086
17087      return this.menuButton_.controlText(text, el);
17088    }
17089    /**
17090     * Handle a click on a `MenuButton`.
17091     * See {@link ClickableComponent#handleClick} for instances where this is called.
17092     *
17093     * @param {EventTarget~Event} event
17094     *        The `keydown`, `tap`, or `click` event that caused this function to be
17095     *        called.
17096     *
17097     * @listens tap
17098     * @listens click
17099     */
17100    ;
17101
17102    _proto.handleClick = function handleClick(event) {
17103      if (this.buttonPressed_) {
17104        this.unpressButton();
17105      } else {
17106        this.pressButton();
17107      }
17108    }
17109    /**
17110     * Set the focus to the actual button, not to this element
17111     */
17112    ;
17113
17114    _proto.focus = function focus() {
17115      this.menuButton_.focus();
17116    }
17117    /**
17118     * Remove the focus from the actual button, not this element
17119     */
17120    ;
17121
17122    _proto.blur = function blur() {
17123      this.menuButton_.blur();
17124    }
17125    /**
17126     * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
17127     * {@link ClickableComponent#handleKeyDown} for instances where this is called.
17128     *
17129     * @param {EventTarget~Event} event
17130     *        The `keydown` event that caused this function to be called.
17131     *
17132     * @listens keydown
17133     */
17134    ;
17135
17136    _proto.handleKeyDown = function handleKeyDown(event) {
17137      // Escape or Tab unpress the 'button'
17138      if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
17139        if (this.buttonPressed_) {
17140          this.unpressButton();
17141        } // Don't preventDefault for Tab key - we still want to lose focus
17142
17143
17144        if (!keycode.isEventKey(event, 'Tab')) {
17145          event.preventDefault(); // Set focus back to the menu button's button
17146
17147          this.menuButton_.focus();
17148        } // Up Arrow or Down Arrow also 'press' the button to open the menu
17149
17150      } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) {
17151        if (!this.buttonPressed_) {
17152          event.preventDefault();
17153          this.pressButton();
17154        }
17155      }
17156    }
17157    /**
17158     * This method name now delegates to `handleSubmenuKeyDown`. This means
17159     * anyone calling `handleSubmenuKeyPress` will not see their method calls
17160     * stop working.
17161     *
17162     * @param {EventTarget~Event} event
17163     *        The event that caused this function to be called.
17164     */
17165    ;
17166
17167    _proto.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) {
17168      this.handleSubmenuKeyDown(event);
17169    }
17170    /**
17171     * Handle a `keydown` event on a sub-menu. The listener for this is added in
17172     * the constructor.
17173     *
17174     * @param {EventTarget~Event} event
17175     *        Key press event
17176     *
17177     * @listens keydown
17178     */
17179    ;
17180
17181    _proto.handleSubmenuKeyDown = function handleSubmenuKeyDown(event) {
17182      // Escape or Tab unpress the 'button'
17183      if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
17184        if (this.buttonPressed_) {
17185          this.unpressButton();
17186        } // Don't preventDefault for Tab key - we still want to lose focus
17187
17188
17189        if (!keycode.isEventKey(event, 'Tab')) {
17190          event.preventDefault(); // Set focus back to the menu button's button
17191
17192          this.menuButton_.focus();
17193        }
17194      }
17195    }
17196    /**
17197     * Put the current `MenuButton` into a pressed state.
17198     */
17199    ;
17200
17201    _proto.pressButton = function pressButton() {
17202      if (this.enabled_) {
17203        this.buttonPressed_ = true;
17204        this.menu.show();
17205        this.menu.lockShowing();
17206        this.menuButton_.el_.setAttribute('aria-expanded', 'true'); // set the focus into the submenu, except on iOS where it is resulting in
17207        // undesired scrolling behavior when the player is in an iframe
17208
17209        if (IS_IOS && isInFrame()) {
17210          // Return early so that the menu isn't focused
17211          return;
17212        }
17213
17214        this.menu.focus();
17215      }
17216    }
17217    /**
17218     * Take the current `MenuButton` out of a pressed state.
17219     */
17220    ;
17221
17222    _proto.unpressButton = function unpressButton() {
17223      if (this.enabled_) {
17224        this.buttonPressed_ = false;
17225        this.menu.unlockShowing();
17226        this.menu.hide();
17227        this.menuButton_.el_.setAttribute('aria-expanded', 'false');
17228      }
17229    }
17230    /**
17231     * Disable the `MenuButton`. Don't allow it to be clicked.
17232     */
17233    ;
17234
17235    _proto.disable = function disable() {
17236      this.unpressButton();
17237      this.enabled_ = false;
17238      this.addClass('vjs-disabled');
17239      this.menuButton_.disable();
17240    }
17241    /**
17242     * Enable the `MenuButton`. Allow it to be clicked.
17243     */
17244    ;
17245
17246    _proto.enable = function enable() {
17247      this.enabled_ = true;
17248      this.removeClass('vjs-disabled');
17249      this.menuButton_.enable();
17250    };
17251
17252    return MenuButton;
17253  }(Component);
17254
17255  Component.registerComponent('MenuButton', MenuButton);
17256
17257  /**
17258   * The base class for buttons that toggle specific  track types (e.g. subtitles).
17259   *
17260   * @extends MenuButton
17261   */
17262
17263  var TrackButton =
17264  /*#__PURE__*/
17265  function (_MenuButton) {
17266    _inheritsLoose(TrackButton, _MenuButton);
17267
17268    /**
17269     * Creates an instance of this class.
17270     *
17271     * @param {Player} player
17272     *        The `Player` that this class should be attached to.
17273     *
17274     * @param {Object} [options]
17275     *        The key/value store of player options.
17276     */
17277    function TrackButton(player, options) {
17278      var _this;
17279
17280      var tracks = options.tracks;
17281      _this = _MenuButton.call(this, player, options) || this;
17282
17283      if (_this.items.length <= 1) {
17284        _this.hide();
17285      }
17286
17287      if (!tracks) {
17288        return _assertThisInitialized(_this);
17289      }
17290
17291      var updateHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update);
17292      tracks.addEventListener('removetrack', updateHandler);
17293      tracks.addEventListener('addtrack', updateHandler);
17294
17295      _this.player_.on('ready', updateHandler);
17296
17297      _this.player_.on('dispose', function () {
17298        tracks.removeEventListener('removetrack', updateHandler);
17299        tracks.removeEventListener('addtrack', updateHandler);
17300      });
17301
17302      return _this;
17303    }
17304
17305    return TrackButton;
17306  }(MenuButton);
17307
17308  Component.registerComponent('TrackButton', TrackButton);
17309
17310  /**
17311   * @file menu-keys.js
17312   */
17313
17314  /**
17315    * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`)
17316    * Note that 'Enter' and 'Space' are not included here (otherwise they would
17317    * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable)
17318    * @typedef MenuKeys
17319    * @array
17320    */
17321  var MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left'];
17322
17323  /**
17324   * The component for a menu item. `<li>`
17325   *
17326   * @extends ClickableComponent
17327   */
17328
17329  var MenuItem =
17330  /*#__PURE__*/
17331  function (_ClickableComponent) {
17332    _inheritsLoose(MenuItem, _ClickableComponent);
17333
17334    /**
17335     * Creates an instance of the this class.
17336     *
17337     * @param {Player} player
17338     *        The `Player` that this class should be attached to.
17339     *
17340     * @param {Object} [options={}]
17341     *        The key/value store of player options.
17342     *
17343     */
17344    function MenuItem(player, options) {
17345      var _this;
17346
17347      _this = _ClickableComponent.call(this, player, options) || this;
17348      _this.selectable = options.selectable;
17349      _this.isSelected_ = options.selected || false;
17350      _this.multiSelectable = options.multiSelectable;
17351
17352      _this.selected(_this.isSelected_);
17353
17354      if (_this.selectable) {
17355        if (_this.multiSelectable) {
17356          _this.el_.setAttribute('role', 'menuitemcheckbox');
17357        } else {
17358          _this.el_.setAttribute('role', 'menuitemradio');
17359        }
17360      } else {
17361        _this.el_.setAttribute('role', 'menuitem');
17362      }
17363
17364      return _this;
17365    }
17366    /**
17367     * Create the `MenuItem's DOM element
17368     *
17369     * @param {string} [type=li]
17370     *        Element's node type, not actually used, always set to `li`.
17371     *
17372     * @param {Object} [props={}]
17373     *        An object of properties that should be set on the element
17374     *
17375     * @param {Object} [attrs={}]
17376     *        An object of attributes that should be set on the element
17377     *
17378     * @return {Element}
17379     *         The element that gets created.
17380     */
17381
17382
17383    var _proto = MenuItem.prototype;
17384
17385    _proto.createEl = function createEl(type, props, attrs) {
17386      // The control is textual, not just an icon
17387      this.nonIconControl = true;
17388      return _ClickableComponent.prototype.createEl.call(this, 'li', assign({
17389        className: 'vjs-menu-item',
17390        innerHTML: "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label) + "</span>",
17391        tabIndex: -1
17392      }, props), attrs);
17393    }
17394    /**
17395     * Ignore keys which are used by the menu, but pass any other ones up. See
17396     * {@link ClickableComponent#handleKeyDown} for instances where this is called.
17397     *
17398     * @param {EventTarget~Event} event
17399     *        The `keydown` event that caused this function to be called.
17400     *
17401     * @listens keydown
17402     */
17403    ;
17404
17405    _proto.handleKeyDown = function handleKeyDown(event) {
17406      if (!MenuKeys.some(function (key) {
17407        return keycode.isEventKey(event, key);
17408      })) {
17409        // Pass keydown handling up for unused keys
17410        _ClickableComponent.prototype.handleKeyDown.call(this, event);
17411      }
17412    }
17413    /**
17414     * Any click on a `MenuItem` puts it into the selected state.
17415     * See {@link ClickableComponent#handleClick} for instances where this is called.
17416     *
17417     * @param {EventTarget~Event} event
17418     *        The `keydown`, `tap`, or `click` event that caused this function to be
17419     *        called.
17420     *
17421     * @listens tap
17422     * @listens click
17423     */
17424    ;
17425
17426    _proto.handleClick = function handleClick(event) {
17427      this.selected(true);
17428    }
17429    /**
17430     * Set the state for this menu item as selected or not.
17431     *
17432     * @param {boolean} selected
17433     *        if the menu item is selected or not
17434     */
17435    ;
17436
17437    _proto.selected = function selected(_selected) {
17438      if (this.selectable) {
17439        if (_selected) {
17440          this.addClass('vjs-selected');
17441          this.el_.setAttribute('aria-checked', 'true'); // aria-checked isn't fully supported by browsers/screen readers,
17442          // so indicate selected state to screen reader in the control text.
17443
17444          this.controlText(', selected');
17445          this.isSelected_ = true;
17446        } else {
17447          this.removeClass('vjs-selected');
17448          this.el_.setAttribute('aria-checked', 'false'); // Indicate un-selected state to screen reader
17449
17450          this.controlText('');
17451          this.isSelected_ = false;
17452        }
17453      }
17454    };
17455
17456    return MenuItem;
17457  }(ClickableComponent);
17458
17459  Component.registerComponent('MenuItem', MenuItem);
17460
17461  /**
17462   * The specific menu item type for selecting a language within a text track kind
17463   *
17464   * @extends MenuItem
17465   */
17466
17467  var TextTrackMenuItem =
17468  /*#__PURE__*/
17469  function (_MenuItem) {
17470    _inheritsLoose(TextTrackMenuItem, _MenuItem);
17471
17472    /**
17473     * Creates an instance of this class.
17474     *
17475     * @param {Player} player
17476     *        The `Player` that this class should be attached to.
17477     *
17478     * @param {Object} [options]
17479     *        The key/value store of player options.
17480     */
17481    function TextTrackMenuItem(player, options) {
17482      var _this;
17483
17484      var track = options.track;
17485      var tracks = player.textTracks(); // Modify options for parent MenuItem class's init.
17486
17487      options.label = track.label || track.language || 'Unknown';
17488      options.selected = track.mode === 'showing';
17489      _this = _MenuItem.call(this, player, options) || this;
17490      _this.track = track; // Determine the relevant kind(s) of tracks for this component and filter
17491      // out empty kinds.
17492
17493      _this.kinds = (options.kinds || [options.kind || _this.track.kind]).filter(Boolean);
17494
17495      var changeHandler = function changeHandler() {
17496        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
17497          args[_key] = arguments[_key];
17498        }
17499
17500        _this.handleTracksChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args);
17501      };
17502
17503      var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() {
17504        for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
17505          args[_key2] = arguments[_key2];
17506        }
17507
17508        _this.handleSelectedLanguageChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args);
17509      };
17510
17511      player.on(['loadstart', 'texttrackchange'], changeHandler);
17512      tracks.addEventListener('change', changeHandler);
17513      tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
17514
17515      _this.on('dispose', function () {
17516        player.off(['loadstart', 'texttrackchange'], changeHandler);
17517        tracks.removeEventListener('change', changeHandler);
17518        tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
17519      }); // iOS7 doesn't dispatch change events to TextTrackLists when an
17520      // associated track's mode changes. Without something like
17521      // Object.observe() (also not present on iOS7), it's not
17522      // possible to detect changes to the mode attribute and polyfill
17523      // the change event. As a poor substitute, we manually dispatch
17524      // change events whenever the controls modify the mode.
17525
17526
17527      if (tracks.onchange === undefined) {
17528        var event;
17529
17530        _this.on(['tap', 'click'], function () {
17531          if (typeof window$1.Event !== 'object') {
17532            // Android 2.3 throws an Illegal Constructor error for window.Event
17533            try {
17534              event = new window$1.Event('change');
17535            } catch (err) {// continue regardless of error
17536            }
17537          }
17538
17539          if (!event) {
17540            event = document.createEvent('Event');
17541            event.initEvent('change', true, true);
17542          }
17543
17544          tracks.dispatchEvent(event);
17545        });
17546      } // set the default state based on current tracks
17547
17548
17549      _this.handleTracksChange();
17550
17551      return _this;
17552    }
17553    /**
17554     * This gets called when an `TextTrackMenuItem` is "clicked". See
17555     * {@link ClickableComponent} for more detailed information on what a click can be.
17556     *
17557     * @param {EventTarget~Event} event
17558     *        The `keydown`, `tap`, or `click` event that caused this function to be
17559     *        called.
17560     *
17561     * @listens tap
17562     * @listens click
17563     */
17564
17565
17566    var _proto = TextTrackMenuItem.prototype;
17567
17568    _proto.handleClick = function handleClick(event) {
17569      var referenceTrack = this.track;
17570      var tracks = this.player_.textTracks();
17571
17572      _MenuItem.prototype.handleClick.call(this, event);
17573
17574      if (!tracks) {
17575        return;
17576      }
17577
17578      for (var i = 0; i < tracks.length; i++) {
17579        var track = tracks[i]; // If the track from the text tracks list is not of the right kind,
17580        // skip it. We do not want to affect tracks of incompatible kind(s).
17581
17582        if (this.kinds.indexOf(track.kind) === -1) {
17583          continue;
17584        } // If this text track is the component's track and it is not showing,
17585        // set it to showing.
17586
17587
17588        if (track === referenceTrack) {
17589          if (track.mode !== 'showing') {
17590            track.mode = 'showing';
17591          } // If this text track is not the component's track and it is not
17592          // disabled, set it to disabled.
17593
17594        } else if (track.mode !== 'disabled') {
17595          track.mode = 'disabled';
17596        }
17597      }
17598    }
17599    /**
17600     * Handle text track list change
17601     *
17602     * @param {EventTarget~Event} event
17603     *        The `change` event that caused this function to be called.
17604     *
17605     * @listens TextTrackList#change
17606     */
17607    ;
17608
17609    _proto.handleTracksChange = function handleTracksChange(event) {
17610      var shouldBeSelected = this.track.mode === 'showing'; // Prevent redundant selected() calls because they may cause
17611      // screen readers to read the appended control text unnecessarily
17612
17613      if (shouldBeSelected !== this.isSelected_) {
17614        this.selected(shouldBeSelected);
17615      }
17616    };
17617
17618    _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
17619      if (this.track.mode === 'showing') {
17620        var selectedLanguage = this.player_.cache_.selectedLanguage; // Don't replace the kind of track across the same language
17621
17622        if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
17623          return;
17624        }
17625
17626        this.player_.cache_.selectedLanguage = {
17627          enabled: true,
17628          language: this.track.language,
17629          kind: this.track.kind
17630        };
17631      }
17632    };
17633
17634    _proto.dispose = function dispose() {
17635      // remove reference to track object on dispose
17636      this.track = null;
17637
17638      _MenuItem.prototype.dispose.call(this);
17639    };
17640
17641    return TextTrackMenuItem;
17642  }(MenuItem);
17643
17644  Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
17645
17646  /**
17647   * A special menu item for turning of a specific type of text track
17648   *
17649   * @extends TextTrackMenuItem
17650   */
17651
17652  var OffTextTrackMenuItem =
17653  /*#__PURE__*/
17654  function (_TextTrackMenuItem) {
17655    _inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem);
17656
17657    /**
17658     * Creates an instance of this class.
17659     *
17660     * @param {Player} player
17661     *        The `Player` that this class should be attached to.
17662     *
17663     * @param {Object} [options]
17664     *        The key/value store of player options.
17665     */
17666    function OffTextTrackMenuItem(player, options) {
17667      // Create pseudo track info
17668      // Requires options['kind']
17669      options.track = {
17670        player: player,
17671        // it is no longer necessary to store `kind` or `kinds` on the track itself
17672        // since they are now stored in the `kinds` property of all instances of
17673        // TextTrackMenuItem, but this will remain for backwards compatibility
17674        kind: options.kind,
17675        kinds: options.kinds,
17676        default: false,
17677        mode: 'disabled'
17678      };
17679
17680      if (!options.kinds) {
17681        options.kinds = [options.kind];
17682      }
17683
17684      if (options.label) {
17685        options.track.label = options.label;
17686      } else {
17687        options.track.label = options.kinds.join(' and ') + ' off';
17688      } // MenuItem is selectable
17689
17690
17691      options.selectable = true; // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
17692
17693      options.multiSelectable = false;
17694      return _TextTrackMenuItem.call(this, player, options) || this;
17695    }
17696    /**
17697     * Handle text track change
17698     *
17699     * @param {EventTarget~Event} event
17700     *        The event that caused this function to run
17701     */
17702
17703
17704    var _proto = OffTextTrackMenuItem.prototype;
17705
17706    _proto.handleTracksChange = function handleTracksChange(event) {
17707      var tracks = this.player().textTracks();
17708      var shouldBeSelected = true;
17709
17710      for (var i = 0, l = tracks.length; i < l; i++) {
17711        var track = tracks[i];
17712
17713        if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
17714          shouldBeSelected = false;
17715          break;
17716        }
17717      } // Prevent redundant selected() calls because they may cause
17718      // screen readers to read the appended control text unnecessarily
17719
17720
17721      if (shouldBeSelected !== this.isSelected_) {
17722        this.selected(shouldBeSelected);
17723      }
17724    };
17725
17726    _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
17727      var tracks = this.player().textTracks();
17728      var allHidden = true;
17729
17730      for (var i = 0, l = tracks.length; i < l; i++) {
17731        var track = tracks[i];
17732
17733        if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
17734          allHidden = false;
17735          break;
17736        }
17737      }
17738
17739      if (allHidden) {
17740        this.player_.cache_.selectedLanguage = {
17741          enabled: false
17742        };
17743      }
17744    };
17745
17746    return OffTextTrackMenuItem;
17747  }(TextTrackMenuItem);
17748
17749  Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
17750
17751  /**
17752   * The base class for buttons that toggle specific text track types (e.g. subtitles)
17753   *
17754   * @extends MenuButton
17755   */
17756
17757  var TextTrackButton =
17758  /*#__PURE__*/
17759  function (_TrackButton) {
17760    _inheritsLoose(TextTrackButton, _TrackButton);
17761
17762    /**
17763     * Creates an instance of this class.
17764     *
17765     * @param {Player} player
17766     *        The `Player` that this class should be attached to.
17767     *
17768     * @param {Object} [options={}]
17769     *        The key/value store of player options.
17770     */
17771    function TextTrackButton(player, options) {
17772      if (options === void 0) {
17773        options = {};
17774      }
17775
17776      options.tracks = player.textTracks();
17777      return _TrackButton.call(this, player, options) || this;
17778    }
17779    /**
17780     * Create a menu item for each text track
17781     *
17782     * @param {TextTrackMenuItem[]} [items=[]]
17783     *        Existing array of items to use during creation
17784     *
17785     * @return {TextTrackMenuItem[]}
17786     *         Array of menu items that were created
17787     */
17788
17789
17790    var _proto = TextTrackButton.prototype;
17791
17792    _proto.createItems = function createItems(items, TrackMenuItem) {
17793      if (items === void 0) {
17794        items = [];
17795      }
17796
17797      if (TrackMenuItem === void 0) {
17798        TrackMenuItem = TextTrackMenuItem;
17799      }
17800
17801      // Label is an override for the [track] off label
17802      // USed to localise captions/subtitles
17803      var label;
17804
17805      if (this.label_) {
17806        label = this.label_ + " off";
17807      } // Add an OFF menu item to turn all tracks off
17808
17809
17810      items.push(new OffTextTrackMenuItem(this.player_, {
17811        kinds: this.kinds_,
17812        kind: this.kind_,
17813        label: label
17814      }));
17815      this.hideThreshold_ += 1;
17816      var tracks = this.player_.textTracks();
17817
17818      if (!Array.isArray(this.kinds_)) {
17819        this.kinds_ = [this.kind_];
17820      }
17821
17822      for (var i = 0; i < tracks.length; i++) {
17823        var track = tracks[i]; // only add tracks that are of an appropriate kind and have a label
17824
17825        if (this.kinds_.indexOf(track.kind) > -1) {
17826          var item = new TrackMenuItem(this.player_, {
17827            track: track,
17828            kinds: this.kinds_,
17829            kind: this.kind_,
17830            // MenuItem is selectable
17831            selectable: true,
17832            // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
17833            multiSelectable: false
17834          });
17835          item.addClass("vjs-" + track.kind + "-menu-item");
17836          items.push(item);
17837        }
17838      }
17839
17840      return items;
17841    };
17842
17843    return TextTrackButton;
17844  }(TrackButton);
17845
17846  Component.registerComponent('TextTrackButton', TextTrackButton);
17847
17848  /**
17849   * The chapter track menu item
17850   *
17851   * @extends MenuItem
17852   */
17853
17854  var ChaptersTrackMenuItem =
17855  /*#__PURE__*/
17856  function (_MenuItem) {
17857    _inheritsLoose(ChaptersTrackMenuItem, _MenuItem);
17858
17859    /**
17860     * Creates an instance of this class.
17861     *
17862     * @param {Player} player
17863     *        The `Player` that this class should be attached to.
17864     *
17865     * @param {Object} [options]
17866     *        The key/value store of player options.
17867     */
17868    function ChaptersTrackMenuItem(player, options) {
17869      var _this;
17870
17871      var track = options.track;
17872      var cue = options.cue;
17873      var currentTime = player.currentTime(); // Modify options for parent MenuItem class's init.
17874
17875      options.selectable = true;
17876      options.multiSelectable = false;
17877      options.label = cue.text;
17878      options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
17879      _this = _MenuItem.call(this, player, options) || this;
17880      _this.track = track;
17881      _this.cue = cue;
17882      track.addEventListener('cuechange', bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.update));
17883      return _this;
17884    }
17885    /**
17886     * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
17887     * {@link ClickableComponent} for more detailed information on what a click can be.
17888     *
17889     * @param {EventTarget~Event} [event]
17890     *        The `keydown`, `tap`, or `click` event that caused this function to be
17891     *        called.
17892     *
17893     * @listens tap
17894     * @listens click
17895     */
17896
17897
17898    var _proto = ChaptersTrackMenuItem.prototype;
17899
17900    _proto.handleClick = function handleClick(event) {
17901      _MenuItem.prototype.handleClick.call(this);
17902
17903      this.player_.currentTime(this.cue.startTime);
17904      this.update(this.cue.startTime);
17905    }
17906    /**
17907     * Update chapter menu item
17908     *
17909     * @param {EventTarget~Event} [event]
17910     *        The `cuechange` event that caused this function to run.
17911     *
17912     * @listens TextTrack#cuechange
17913     */
17914    ;
17915
17916    _proto.update = function update(event) {
17917      var cue = this.cue;
17918      var currentTime = this.player_.currentTime(); // vjs.log(currentTime, cue.startTime);
17919
17920      this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
17921    };
17922
17923    return ChaptersTrackMenuItem;
17924  }(MenuItem);
17925
17926  Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
17927
17928  /**
17929   * The button component for toggling and selecting chapters
17930   * Chapters act much differently than other text tracks
17931   * Cues are navigation vs. other tracks of alternative languages
17932   *
17933   * @extends TextTrackButton
17934   */
17935
17936  var ChaptersButton =
17937  /*#__PURE__*/
17938  function (_TextTrackButton) {
17939    _inheritsLoose(ChaptersButton, _TextTrackButton);
17940
17941    /**
17942     * Creates an instance of this class.
17943     *
17944     * @param {Player} player
17945     *        The `Player` that this class should be attached to.
17946     *
17947     * @param {Object} [options]
17948     *        The key/value store of player options.
17949     *
17950     * @param {Component~ReadyCallback} [ready]
17951     *        The function to call when this function is ready.
17952     */
17953    function ChaptersButton(player, options, ready) {
17954      return _TextTrackButton.call(this, player, options, ready) || this;
17955    }
17956    /**
17957     * Builds the default DOM `className`.
17958     *
17959     * @return {string}
17960     *         The DOM `className` for this object.
17961     */
17962
17963
17964    var _proto = ChaptersButton.prototype;
17965
17966    _proto.buildCSSClass = function buildCSSClass() {
17967      return "vjs-chapters-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
17968    };
17969
17970    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
17971      return "vjs-chapters-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
17972    }
17973    /**
17974     * Update the menu based on the current state of its items.
17975     *
17976     * @param {EventTarget~Event} [event]
17977     *        An event that triggered this function to run.
17978     *
17979     * @listens TextTrackList#addtrack
17980     * @listens TextTrackList#removetrack
17981     * @listens TextTrackList#change
17982     */
17983    ;
17984
17985    _proto.update = function update(event) {
17986      if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
vendor: 1,499 bytes, lines 17987-18040
17987        this.setTrack(this.findChaptersTrack());
17988      }
17989
17990      _TextTrackButton.prototype.update.call(this);
17991    }
17992    /**
17993     * Set the currently selected track for the chapters button.
17994     *
17995     * @param {TextTrack} track
17996     *        The new track to select. Nothing will change if this is the currently selected
17997     *        track.
17998     */
17999    ;
18000
18001    _proto.setTrack = function setTrack(track) {
18002      if (this.track_ === track) {
18003        return;
18004      }
18005
18006      if (!this.updateHandler_) {
18007        this.updateHandler_ = this.update.bind(this);
18008      } // here this.track_ refers to the old track instance
18009
18010
18011      if (this.track_) {
18012        var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
18013
18014        if (remoteTextTrackEl) {
18015          remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
18016        }
18017
18018        this.track_ = null;
18019      }
18020
18021      this.track_ = track; // here this.track_ refers to the new track instance
18022
18023      if (this.track_) {
18024        this.track_.mode = 'hidden';
18025
18026        var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
18027
18028        if (_remoteTextTrackEl) {
18029          _remoteTextTrackEl.addEventListener('load', this.updateHandler_);
18030        }
18031      }
18032    }
18033    /**
18034     * Find the track object that is currently in use by this ChaptersButton
18035     *
18036     * @return {TextTrack|undefined}
18037     *         The current track or undefined if none was found.
18038     */
18039    ;
18040
vendor: 15,626 bytes, lines 18041-18618
18041    _proto.findChaptersTrack = function findChaptersTrack() {
18042      var tracks = this.player_.textTracks() || [];
18043
18044      for (var i = tracks.length - 1; i >= 0; i--) {
18045        // We will always choose the last track as our chaptersTrack
18046        var track = tracks[i];
18047
18048        if (track.kind === this.kind_) {
18049          return track;
18050        }
18051      }
18052    }
18053    /**
18054     * Get the caption for the ChaptersButton based on the track label. This will also
18055     * use the current tracks localized kind as a fallback if a label does not exist.
18056     *
18057     * @return {string}
18058     *         The tracks current label or the localized track kind.
18059     */
18060    ;
18061
18062    _proto.getMenuCaption = function getMenuCaption() {
18063      if (this.track_ && this.track_.label) {
18064        return this.track_.label;
18065      }
18066
18067      return this.localize(toTitleCase(this.kind_));
18068    }
18069    /**
18070     * Create menu from chapter track
18071     *
18072     * @return {Menu}
18073     *         New menu for the chapter buttons
18074     */
18075    ;
18076
18077    _proto.createMenu = function createMenu() {
18078      this.options_.title = this.getMenuCaption();
18079      return _TextTrackButton.prototype.createMenu.call(this);
18080    }
18081    /**
18082     * Create a menu item for each text track
18083     *
18084     * @return {TextTrackMenuItem[]}
18085     *         Array of menu items
18086     */
18087    ;
18088
18089    _proto.createItems = function createItems() {
18090      var items = [];
18091
18092      if (!this.track_) {
18093        return items;
18094      }
18095
18096      var cues = this.track_.cues;
18097
18098      if (!cues) {
18099        return items;
18100      }
18101
18102      for (var i = 0, l = cues.length; i < l; i++) {
18103        var cue = cues[i];
18104        var mi = new ChaptersTrackMenuItem(this.player_, {
18105          track: this.track_,
18106          cue: cue
18107        });
18108        items.push(mi);
18109      }
18110
18111      return items;
18112    };
18113
18114    return ChaptersButton;
18115  }(TextTrackButton);
18116  /**
18117   * `kind` of TextTrack to look for to associate it with this menu.
18118   *
18119   * @type {string}
18120   * @private
18121   */
18122
18123
18124  ChaptersButton.prototype.kind_ = 'chapters';
18125  /**
18126   * The text that should display over the `ChaptersButton`s controls. Added for localization.
18127   *
18128   * @type {string}
18129   * @private
18130   */
18131
18132  ChaptersButton.prototype.controlText_ = 'Chapters';
18133  Component.registerComponent('ChaptersButton', ChaptersButton);
18134
18135  /**
18136   * The button component for toggling and selecting descriptions
18137   *
18138   * @extends TextTrackButton
18139   */
18140
18141  var DescriptionsButton =
18142  /*#__PURE__*/
18143  function (_TextTrackButton) {
18144    _inheritsLoose(DescriptionsButton, _TextTrackButton);
18145
18146    /**
18147     * Creates an instance of this class.
18148     *
18149     * @param {Player} player
18150     *        The `Player` that this class should be attached to.
18151     *
18152     * @param {Object} [options]
18153     *        The key/value store of player options.
18154     *
18155     * @param {Component~ReadyCallback} [ready]
18156     *        The function to call when this component is ready.
18157     */
18158    function DescriptionsButton(player, options, ready) {
18159      var _this;
18160
18161      _this = _TextTrackButton.call(this, player, options, ready) || this;
18162      var tracks = player.textTracks();
18163      var changeHandler = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.handleTracksChange);
18164      tracks.addEventListener('change', changeHandler);
18165
18166      _this.on('dispose', function () {
18167        tracks.removeEventListener('change', changeHandler);
18168      });
18169
18170      return _this;
18171    }
18172    /**
18173     * Handle text track change
18174     *
18175     * @param {EventTarget~Event} event
18176     *        The event that caused this function to run
18177     *
18178     * @listens TextTrackList#change
18179     */
18180
18181
18182    var _proto = DescriptionsButton.prototype;
18183
18184    _proto.handleTracksChange = function handleTracksChange(event) {
18185      var tracks = this.player().textTracks();
18186      var disabled = false; // Check whether a track of a different kind is showing
18187
18188      for (var i = 0, l = tracks.length; i < l; i++) {
18189        var track = tracks[i];
18190
18191        if (track.kind !== this.kind_ && track.mode === 'showing') {
18192          disabled = true;
18193          break;
18194        }
18195      } // If another track is showing, disable this menu button
18196
18197
18198      if (disabled) {
18199        this.disable();
18200      } else {
18201        this.enable();
18202      }
18203    }
18204    /**
18205     * Builds the default DOM `className`.
18206     *
18207     * @return {string}
18208     *         The DOM `className` for this object.
18209     */
18210    ;
18211
18212    _proto.buildCSSClass = function buildCSSClass() {
18213      return "vjs-descriptions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
18214    };
18215
18216    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
18217      return "vjs-descriptions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
18218    };
18219
18220    return DescriptionsButton;
18221  }(TextTrackButton);
18222  /**
18223   * `kind` of TextTrack to look for to associate it with this menu.
18224   *
18225   * @type {string}
18226   * @private
18227   */
18228
18229
18230  DescriptionsButton.prototype.kind_ = 'descriptions';
18231  /**
18232   * The text that should display over the `DescriptionsButton`s controls. Added for localization.
18233   *
18234   * @type {string}
18235   * @private
18236   */
18237
18238  DescriptionsButton.prototype.controlText_ = 'Descriptions';
18239  Component.registerComponent('DescriptionsButton', DescriptionsButton);
18240
18241  /**
18242   * The button component for toggling and selecting subtitles
18243   *
18244   * @extends TextTrackButton
18245   */
18246
18247  var SubtitlesButton =
18248  /*#__PURE__*/
18249  function (_TextTrackButton) {
18250    _inheritsLoose(SubtitlesButton, _TextTrackButton);
18251
18252    /**
18253     * Creates an instance of this class.
18254     *
18255     * @param {Player} player
18256     *        The `Player` that this class should be attached to.
18257     *
18258     * @param {Object} [options]
18259     *        The key/value store of player options.
18260     *
18261     * @param {Component~ReadyCallback} [ready]
18262     *        The function to call when this component is ready.
18263     */
18264    function SubtitlesButton(player, options, ready) {
18265      return _TextTrackButton.call(this, player, options, ready) || this;
18266    }
18267    /**
18268     * Builds the default DOM `className`.
18269     *
18270     * @return {string}
18271     *         The DOM `className` for this object.
18272     */
18273
18274
18275    var _proto = SubtitlesButton.prototype;
18276
18277    _proto.buildCSSClass = function buildCSSClass() {
18278      return "vjs-subtitles-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
18279    };
18280
18281    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
18282      return "vjs-subtitles-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
18283    };
18284
18285    return SubtitlesButton;
18286  }(TextTrackButton);
18287  /**
18288   * `kind` of TextTrack to look for to associate it with this menu.
18289   *
18290   * @type {string}
18291   * @private
18292   */
18293
18294
18295  SubtitlesButton.prototype.kind_ = 'subtitles';
18296  /**
18297   * The text that should display over the `SubtitlesButton`s controls. Added for localization.
18298   *
18299   * @type {string}
18300   * @private
18301   */
18302
18303  SubtitlesButton.prototype.controlText_ = 'Subtitles';
18304  Component.registerComponent('SubtitlesButton', SubtitlesButton);
18305
18306  /**
18307   * The menu item for caption track settings menu
18308   *
18309   * @extends TextTrackMenuItem
18310   */
18311
18312  var CaptionSettingsMenuItem =
18313  /*#__PURE__*/
18314  function (_TextTrackMenuItem) {
18315    _inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem);
18316
18317    /**
18318     * Creates an instance of this class.
18319     *
18320     * @param {Player} player
18321     *        The `Player` that this class should be attached to.
18322     *
18323     * @param {Object} [options]
18324     *        The key/value store of player options.
18325     */
18326    function CaptionSettingsMenuItem(player, options) {
18327      var _this;
18328
18329      options.track = {
18330        player: player,
18331        kind: options.kind,
18332        label: options.kind + ' settings',
18333        selectable: false,
18334        default: false,
18335        mode: 'disabled'
18336      }; // CaptionSettingsMenuItem has no concept of 'selected'
18337
18338      options.selectable = false;
18339      options.name = 'CaptionSettingsMenuItem';
18340      _this = _TextTrackMenuItem.call(this, player, options) || this;
18341
18342      _this.addClass('vjs-texttrack-settings');
18343
18344      _this.controlText(', opens ' + options.kind + ' settings dialog');
18345
18346      return _this;
18347    }
18348    /**
18349     * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
18350     * {@link ClickableComponent} for more detailed information on what a click can be.
18351     *
18352     * @param {EventTarget~Event} [event]
18353     *        The `keydown`, `tap`, or `click` event that caused this function to be
18354     *        called.
18355     *
18356     * @listens tap
18357     * @listens click
18358     */
18359
18360
18361    var _proto = CaptionSettingsMenuItem.prototype;
18362
18363    _proto.handleClick = function handleClick(event) {
18364      this.player().getChild('textTrackSettings').open();
18365    };
18366
18367    return CaptionSettingsMenuItem;
18368  }(TextTrackMenuItem);
18369
18370  Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
18371
18372  /**
18373   * The button component for toggling and selecting captions
18374   *
18375   * @extends TextTrackButton
18376   */
18377
18378  var CaptionsButton =
18379  /*#__PURE__*/
18380  function (_TextTrackButton) {
18381    _inheritsLoose(CaptionsButton, _TextTrackButton);
18382
18383    /**
18384     * Creates an instance of this class.
18385     *
18386     * @param {Player} player
18387     *        The `Player` that this class should be attached to.
18388     *
18389     * @param {Object} [options]
18390     *        The key/value store of player options.
18391     *
18392     * @param {Component~ReadyCallback} [ready]
18393     *        The function to call when this component is ready.
18394     */
18395    function CaptionsButton(player, options, ready) {
18396      return _TextTrackButton.call(this, player, options, ready) || this;
18397    }
18398    /**
18399     * Builds the default DOM `className`.
18400     *
18401     * @return {string}
18402     *         The DOM `className` for this object.
18403     */
18404
18405
18406    var _proto = CaptionsButton.prototype;
18407
18408    _proto.buildCSSClass = function buildCSSClass() {
18409      return "vjs-captions-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
18410    };
18411
18412    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
18413      return "vjs-captions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
18414    }
18415    /**
18416     * Create caption menu items
18417     *
18418     * @return {CaptionSettingsMenuItem[]}
18419     *         The array of current menu items.
18420     */
18421    ;
18422
18423    _proto.createItems = function createItems() {
18424      var items = [];
18425
18426      if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
18427        items.push(new CaptionSettingsMenuItem(this.player_, {
18428          kind: this.kind_
18429        }));
18430        this.hideThreshold_ += 1;
18431      }
18432
18433      return _TextTrackButton.prototype.createItems.call(this, items);
18434    };
18435
18436    return CaptionsButton;
18437  }(TextTrackButton);
18438  /**
18439   * `kind` of TextTrack to look for to associate it with this menu.
18440   *
18441   * @type {string}
18442   * @private
18443   */
18444
18445
18446  CaptionsButton.prototype.kind_ = 'captions';
18447  /**
18448   * The text that should display over the `CaptionsButton`s controls. Added for localization.
18449   *
18450   * @type {string}
18451   * @private
18452   */
18453
18454  CaptionsButton.prototype.controlText_ = 'Captions';
18455  Component.registerComponent('CaptionsButton', CaptionsButton);
18456
18457  /**
18458   * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
18459   * in the SubsCapsMenu.
18460   *
18461   * @extends TextTrackMenuItem
18462   */
18463
18464  var SubsCapsMenuItem =
18465  /*#__PURE__*/
18466  function (_TextTrackMenuItem) {
18467    _inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem);
18468
18469    function SubsCapsMenuItem() {
18470      return _TextTrackMenuItem.apply(this, arguments) || this;
18471    }
18472
18473    var _proto = SubsCapsMenuItem.prototype;
18474
18475    _proto.createEl = function createEl(type, props, attrs) {
18476      var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label);
18477
18478      if (this.options_.track.kind === 'captions') {
18479        innerHTML += "\n        <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n        <span class=\"vjs-control-text\"> " + this.localize('Captions') + "</span>\n      ";
18480      }
18481
18482      innerHTML += '</span>';
18483
18484      var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({
18485        innerHTML: innerHTML
18486      }, props), attrs);
18487
18488      return el;
18489    };
18490
18491    return SubsCapsMenuItem;
18492  }(TextTrackMenuItem);
18493
18494  Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
18495
18496  /**
18497   * The button component for toggling and selecting captions and/or subtitles
18498   *
18499   * @extends TextTrackButton
18500   */
18501
18502  var SubsCapsButton =
18503  /*#__PURE__*/
18504  function (_TextTrackButton) {
18505    _inheritsLoose(SubsCapsButton, _TextTrackButton);
18506
18507    function SubsCapsButton(player, options) {
18508      var _this;
18509
18510      if (options === void 0) {
18511        options = {};
18512      }
18513
18514      _this = _TextTrackButton.call(this, player, options) || this; // Although North America uses "captions" in most cases for
18515      // "captions and subtitles" other locales use "subtitles"
18516
18517      _this.label_ = 'subtitles';
18518
18519      if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) {
18520        _this.label_ = 'captions';
18521      }
18522
18523      _this.menuButton_.controlText(toTitleCase(_this.label_));
18524
18525      return _this;
18526    }
18527    /**
18528     * Builds the default DOM `className`.
18529     *
18530     * @return {string}
18531     *         The DOM `className` for this object.
18532     */
18533
18534
18535    var _proto = SubsCapsButton.prototype;
18536
18537    _proto.buildCSSClass = function buildCSSClass() {
18538      return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildCSSClass.call(this);
18539    };
18540
18541    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
18542      return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
18543    }
18544    /**
18545     * Create caption/subtitles menu items
18546     *
18547     * @return {CaptionSettingsMenuItem[]}
18548     *         The array of current menu items.
18549     */
18550    ;
18551
18552    _proto.createItems = function createItems() {
18553      var items = [];
18554
18555      if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
18556        items.push(new CaptionSettingsMenuItem(this.player_, {
18557          kind: this.label_
18558        }));
18559        this.hideThreshold_ += 1;
18560      }
18561
18562      items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem);
18563      return items;
18564    };
18565
18566    return SubsCapsButton;
18567  }(TextTrackButton);
18568  /**
18569   * `kind`s of TextTrack to look for to associate it with this menu.
18570   *
18571   * @type {array}
18572   * @private
18573   */
18574
18575
18576  SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
18577  /**
18578   * The text that should display over the `SubsCapsButton`s controls.
18579   *
18580   *
18581   * @type {string}
18582   * @private
18583   */
18584
18585  SubsCapsButton.prototype.controlText_ = 'Subtitles';
18586  Component.registerComponent('SubsCapsButton', SubsCapsButton);
18587
18588  /**
18589   * An {@link AudioTrack} {@link MenuItem}
18590   *
18591   * @extends MenuItem
18592   */
18593
18594  var AudioTrackMenuItem =
18595  /*#__PURE__*/
18596  function (_MenuItem) {
18597    _inheritsLoose(AudioTrackMenuItem, _MenuItem);
18598
18599    /**
18600     * Creates an instance of this class.
18601     *
18602     * @param {Player} player
18603     *        The `Player` that this class should be attached to.
18604     *
18605     * @param {Object} [options]
18606     *        The key/value store of player options.
18607     */
18608    function AudioTrackMenuItem(player, options) {
18609      var _this;
18610
18611      var track = options.track;
18612      var tracks = player.audioTracks(); // Modify options for parent MenuItem class's init.
18613
18614      options.label = track.label || track.language || 'Unknown';
18615      options.selected = track.enabled;
18616      _this = _MenuItem.call(this, player, options) || this;
18617      _this.track = track;
18618
18619      _this.addClass("vjs-" + track.kind + "-menu-item");
18620
18621      var changeHandler = function changeHandler() {
18622        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
18623          args[_key] = arguments[_key];
18624        }
18625
18626        _this.handleTracksChange.apply(_assertThisInitialized(_assertThisInitialized(_this)), args);
18627      };
18628
18629      tracks.addEventListener('change', changeHandler);
18630
18631      _this.on('dispose', function () {
18632        tracks.removeEventListener('change', changeHandler);
18633      });
18634
18635      return _this;
18636    }
18637
18638    var _proto = AudioTrackMenuItem.prototype;
18639
18640    _proto.createEl = function createEl(type, props, attrs) {
18641      var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label);
18642
18643      if (this.options_.track.kind === 'main-desc') {
18644        innerHTML += "\n        <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n        <span class=\"vjs-control-text\"> " + this.localize('Descriptions') + "</span>\n      ";
18645      }
18646
18647      innerHTML += '</span>';
18648
18649      var el = _MenuItem.prototype.createEl.call(this, type, assign({
18650        innerHTML: innerHTML
18651      }, props), attrs);
18652
18653      return el;
18654    }
18655    /**
18656     * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
18657     * for more detailed information on what a click can be.
18658     *
18659     * @param {EventTarget~Event} [event]
18660     *        The `keydown`, `tap`, or `click` event that caused this function to be
18661     *        called.
18662     *
18663     * @listens tap
18664     * @listens click
18665     */
18666    ;
18667
18668    _proto.handleClick = function handleClick(event) {
18669      var tracks = this.player_.audioTracks();
18670
18671      _MenuItem.prototype.handleClick.call(this, event);
18672
18673      for (var i = 0; i < tracks.length; i++) {
18674        var track = tracks[i];
18675        track.enabled = track === this.track;
18676      }
18677    }
18678    /**
18679     * Handle any {@link AudioTrack} change.
18680     *
18681     * @param {EventTarget~Event} [event]
18682     *        The {@link AudioTrackList#change} event that caused this to run.
18683     *
18684     * @listens AudioTrackList#change
18685     */
18686    ;
18687
18688    _proto.handleTracksChange = function handleTracksChange(event) {
18689      this.selected(this.track.enabled);
18690    };
18691
18692    return AudioTrackMenuItem;
18693  }(MenuItem);
18694
18695  Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
18696
18697  /**
18698   * The base class for buttons that toggle specific {@link AudioTrack} types.
18699   *
18700   * @extends TrackButton
18701   */
18702
18703  var AudioTrackButton =
18704  /*#__PURE__*/
18705  function (_TrackButton) {
18706    _inheritsLoose(AudioTrackButton, _TrackButton);
18707
18708    /**
18709     * Creates an instance of this class.
18710     *
18711     * @param {Player} player
18712     *        The `Player` that this class should be attached to.
18713     *
18714     * @param {Object} [options={}]
18715     *        The key/value store of player options.
18716     */
18717    function AudioTrackButton(player, options) {
18718      if (options === void 0) {
18719        options = {};
18720      }
18721
18722      options.tracks = player.audioTracks();
18723      return _TrackButton.call(this, player, options) || this;
18724    }
18725    /**
18726     * Builds the default DOM `className`.
18727     *
18728     * @return {string}
18729     *         The DOM `className` for this object.
18730     */
18731
18732
18733    var _proto = AudioTrackButton.prototype;
18734
18735    _proto.buildCSSClass = function buildCSSClass() {
18736      return "vjs-audio-button " + _TrackButton.prototype.buildCSSClass.call(this);
18737    };
18738
18739    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
18740      return "vjs-audio-button " + _TrackButton.prototype.buildWrapperCSSClass.call(this);
18741    }
18742    /**
18743     * Create a menu item for each audio track
18744     *
18745     * @param {AudioTrackMenuItem[]} [items=[]]
18746     *        An array of existing menu items to use.
18747     *
18748     * @return {AudioTrackMenuItem[]}
18749     *         An array of menu items
18750     */
18751    ;
18752
18753    _proto.createItems = function createItems(items) {
18754      if (items === void 0) {
18755        items = [];
18756      }
18757
18758      // if there's only one audio track, there no point in showing it
18759      this.hideThreshold_ = 1;
18760      var tracks = this.player_.audioTracks();
18761
18762      for (var i = 0; i < tracks.length; i++) {
18763        var track = tracks[i];
18764        items.push(new AudioTrackMenuItem(this.player_, {
18765          track: track,
18766          // MenuItem is selectable
18767          selectable: true,
18768          // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
18769          multiSelectable: false
18770        }));
18771      }
18772
18773      return items;
18774    };
18775
18776    return AudioTrackButton;
18777  }(TrackButton);
18778  /**
18779   * The text that should display over the `AudioTrackButton`s controls. Added for localization.
18780   *
18781   * @type {string}
18782   * @private
18783   */
18784
18785
18786  AudioTrackButton.prototype.controlText_ = 'Audio Track';
18787  Component.registerComponent('AudioTrackButton', AudioTrackButton);
18788
18789  /**
18790   * The specific menu item type for selecting a playback rate.
18791   *
18792   * @extends MenuItem
18793   */
18794
18795  var PlaybackRateMenuItem =
18796  /*#__PURE__*/
18797  function (_MenuItem) {
18798    _inheritsLoose(PlaybackRateMenuItem, _MenuItem);
18799
18800    /**
18801     * Creates an instance of this class.
18802     *
18803     * @param {Player} player
18804     *        The `Player` that this class should be attached to.
18805     *
18806     * @param {Object} [options]
18807     *        The key/value store of player options.
18808     */
18809    function PlaybackRateMenuItem(player, options) {
18810      var _this;
18811
18812      var label = options.rate;
18813      var rate = parseFloat(label, 10); // Modify options for parent MenuItem class's init.
18814
18815      options.label = label;
18816      options.selected = rate === 1;
18817      options.selectable = true;
18818      options.multiSelectable = false;
vendor: 4,379 bytes, lines 18819-18985
18819      _this = _MenuItem.call(this, player, options) || this;
18820      _this.label = label;
18821      _this.rate = rate;
18822
18823      _this.on(player, 'ratechange', _this.update);
18824
18825      return _this;
18826    }
18827    /**
18828     * This gets called when an `PlaybackRateMenuItem` is "clicked". See
18829     * {@link ClickableComponent} for more detailed information on what a click can be.
18830     *
18831     * @param {EventTarget~Event} [event]
18832     *        The `keydown`, `tap`, or `click` event that caused this function to be
18833     *        called.
18834     *
18835     * @listens tap
18836     * @listens click
18837     */
18838
18839
18840    var _proto = PlaybackRateMenuItem.prototype;
18841
18842    _proto.handleClick = function handleClick(event) {
18843      _MenuItem.prototype.handleClick.call(this);
18844
18845      this.player().playbackRate(this.rate);
18846    }
18847    /**
18848     * Update the PlaybackRateMenuItem when the playbackrate changes.
18849     *
18850     * @param {EventTarget~Event} [event]
18851     *        The `ratechange` event that caused this function to run.
18852     *
18853     * @listens Player#ratechange
18854     */
18855    ;
18856
18857    _proto.update = function update(event) {
18858      this.selected(this.player().playbackRate() === this.rate);
18859    };
18860
18861    return PlaybackRateMenuItem;
18862  }(MenuItem);
18863  /**
18864   * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
18865   *
18866   * @type {string}
18867   * @private
18868   */
18869
18870
18871  PlaybackRateMenuItem.prototype.contentElType = 'button';
18872  Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
18873
18874  /**
18875   * The component for controlling the playback rate.
18876   *
18877   * @extends MenuButton
18878   */
18879
18880  var PlaybackRateMenuButton =
18881  /*#__PURE__*/
18882  function (_MenuButton) {
18883    _inheritsLoose(PlaybackRateMenuButton, _MenuButton);
18884
18885    /**
18886     * Creates an instance of this class.
18887     *
18888     * @param {Player} player
18889     *        The `Player` that this class should be attached to.
18890     *
18891     * @param {Object} [options]
18892     *        The key/value store of player options.
18893     */
18894    function PlaybackRateMenuButton(player, options) {
18895      var _this;
18896
18897      _this = _MenuButton.call(this, player, options) || this;
18898
18899      _this.updateVisibility();
18900
18901      _this.updateLabel();
18902
18903      _this.on(player, 'loadstart', _this.updateVisibility);
18904
18905      _this.on(player, 'ratechange', _this.updateLabel);
18906
18907      return _this;
18908    }
18909    /**
18910     * Create the `Component`'s DOM element
18911     *
18912     * @return {Element}
18913     *         The element that was created.
18914     */
18915
18916
18917    var _proto = PlaybackRateMenuButton.prototype;
18918
18919    _proto.createEl = function createEl$$1() {
18920      var el = _MenuButton.prototype.createEl.call(this);
18921
18922      this.labelEl_ = createEl('div', {
18923        className: 'vjs-playback-rate-value',
18924        innerHTML: '1x'
18925      });
18926      el.appendChild(this.labelEl_);
18927      return el;
18928    };
18929
18930    _proto.dispose = function dispose() {
18931      this.labelEl_ = null;
18932
18933      _MenuButton.prototype.dispose.call(this);
18934    }
18935    /**
18936     * Builds the default DOM `className`.
18937     *
18938     * @return {string}
18939     *         The DOM `className` for this object.
18940     */
18941    ;
18942
18943    _proto.buildCSSClass = function buildCSSClass() {
18944      return "vjs-playback-rate " + _MenuButton.prototype.buildCSSClass.call(this);
18945    };
18946
18947    _proto.buildWrapperCSSClass = function buildWrapperCSSClass() {
18948      return "vjs-playback-rate " + _MenuButton.prototype.buildWrapperCSSClass.call(this);
18949    }
18950    /**
18951     * Create the playback rate menu
18952     *
18953     * @return {Menu}
18954     *         Menu object populated with {@link PlaybackRateMenuItem}s
18955     */
18956    ;
18957
18958    _proto.createMenu = function createMenu() {
18959      var menu = new Menu(this.player());
18960      var rates = this.playbackRates();
18961
18962      if (rates) {
18963        for (var i = rates.length - 1; i >= 0; i--) {
18964          menu.addChild(new PlaybackRateMenuItem(this.player(), {
18965            rate: rates[i] + 'x'
18966          }));
18967        }
18968      }
18969
18970      return menu;
18971    }
18972    /**
18973     * Updates ARIA accessibility attributes
18974     */
18975    ;
18976
18977    _proto.updateARIAAttributes = function updateARIAAttributes() {
18978      // Current playback rate
18979      this.el().setAttribute('aria-valuenow', this.player().playbackRate());
18980    }
18981    /**
18982     * This gets called when an `PlaybackRateMenuButton` is "clicked". See
18983     * {@link ClickableComponent} for more detailed information on what a click can be.
18984     *
18985     * @param {EventTarget~Event} [event]
vendor: 13,790 bytes, lines 18986-19481
18986     *        The `keydown`, `tap`, or `click` event that caused this function to be
18987     *        called.
18988     *
18989     * @listens tap
18990     * @listens click
18991     */
18992    ;
18993
18994    _proto.handleClick = function handleClick(event) {
18995      // select next rate option
18996      var currentRate = this.player().playbackRate();
18997      var rates = this.playbackRates(); // this will select first one if the last one currently selected
18998
18999      var newRate = rates[0];
19000
19001      for (var i = 0; i < rates.length; i++) {
19002        if (rates[i] > currentRate) {
19003          newRate = rates[i];
19004          break;
19005        }
19006      }
19007
19008      this.player().playbackRate(newRate);
19009    }
19010    /**
19011     * Get possible playback rates
19012     *
19013     * @return {Array}
19014     *         All possible playback rates
19015     */
19016    ;
19017
19018    _proto.playbackRates = function playbackRates() {
19019      return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates;
19020    }
19021    /**
19022     * Get whether playback rates is supported by the tech
19023     * and an array of playback rates exists
19024     *
19025     * @return {boolean}
19026     *         Whether changing playback rate is supported
19027     */
19028    ;
19029
19030    _proto.playbackRateSupported = function playbackRateSupported() {
19031      return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
19032    }
19033    /**
19034     * Hide playback rate controls when they're no playback rate options to select
19035     *
19036     * @param {EventTarget~Event} [event]
19037     *        The event that caused this function to run.
19038     *
19039     * @listens Player#loadstart
19040     */
19041    ;
19042
19043    _proto.updateVisibility = function updateVisibility(event) {
19044      if (this.playbackRateSupported()) {
19045        this.removeClass('vjs-hidden');
19046      } else {
19047        this.addClass('vjs-hidden');
19048      }
19049    }
19050    /**
19051     * Update button label when rate changed
19052     *
19053     * @param {EventTarget~Event} [event]
19054     *        The event that caused this function to run.
19055     *
19056     * @listens Player#ratechange
19057     */
19058    ;
19059
19060    _proto.updateLabel = function updateLabel(event) {
19061      if (this.playbackRateSupported()) {
19062        this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
19063      }
19064    };
19065
19066    return PlaybackRateMenuButton;
19067  }(MenuButton);
19068  /**
19069   * The text that should display over the `FullscreenToggle`s controls. Added for localization.
19070   *
19071   * @type {string}
19072   * @private
19073   */
19074
19075
19076  PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
19077  Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
19078
19079  /**
19080   * Just an empty spacer element that can be used as an append point for plugins, etc.
19081   * Also can be used to create space between elements when necessary.
19082   *
19083   * @extends Component
19084   */
19085
19086  var Spacer =
19087  /*#__PURE__*/
19088  function (_Component) {
19089    _inheritsLoose(Spacer, _Component);
19090
19091    function Spacer() {
19092      return _Component.apply(this, arguments) || this;
19093    }
19094
19095    var _proto = Spacer.prototype;
19096
19097    /**
19098    * Builds the default DOM `className`.
19099    *
19100    * @return {string}
19101    *         The DOM `className` for this object.
19102    */
19103    _proto.buildCSSClass = function buildCSSClass() {
19104      return "vjs-spacer " + _Component.prototype.buildCSSClass.call(this);
19105    }
19106    /**
19107     * Create the `Component`'s DOM element
19108     *
19109     * @return {Element}
19110     *         The element that was created.
19111     */
19112    ;
19113
19114    _proto.createEl = function createEl() {
19115      return _Component.prototype.createEl.call(this, 'div', {
19116        className: this.buildCSSClass()
19117      });
19118    };
19119
19120    return Spacer;
19121  }(Component);
19122
19123  Component.registerComponent('Spacer', Spacer);
19124
19125  /**
19126   * Spacer specifically meant to be used as an insertion point for new plugins, etc.
19127   *
19128   * @extends Spacer
19129   */
19130
19131  var CustomControlSpacer =
19132  /*#__PURE__*/
19133  function (_Spacer) {
19134    _inheritsLoose(CustomControlSpacer, _Spacer);
19135
19136    function CustomControlSpacer() {
19137      return _Spacer.apply(this, arguments) || this;
19138    }
19139
19140    var _proto = CustomControlSpacer.prototype;
19141
19142    /**
19143     * Builds the default DOM `className`.
19144     *
19145     * @return {string}
19146     *         The DOM `className` for this object.
19147     */
19148    _proto.buildCSSClass = function buildCSSClass() {
19149      return "vjs-custom-control-spacer " + _Spacer.prototype.buildCSSClass.call(this);
19150    }
19151    /**
19152     * Create the `Component`'s DOM element
19153     *
19154     * @return {Element}
19155     *         The element that was created.
19156     */
19157    ;
19158
19159    _proto.createEl = function createEl() {
19160      var el = _Spacer.prototype.createEl.call(this, {
19161        className: this.buildCSSClass()
19162      }); // No-flex/table-cell mode requires there be some content
19163      // in the cell to fill the remaining space of the table.
19164
19165
19166      el.innerHTML = "\xA0";
19167      return el;
19168    };
19169
19170    return CustomControlSpacer;
19171  }(Spacer);
19172
19173  Component.registerComponent('CustomControlSpacer', CustomControlSpacer);
19174
19175  /**
19176   * Container of main controls.
19177   *
19178   * @extends Component
19179   */
19180
19181  var ControlBar =
19182  /*#__PURE__*/
19183  function (_Component) {
19184    _inheritsLoose(ControlBar, _Component);
19185
19186    function ControlBar() {
19187      return _Component.apply(this, arguments) || this;
19188    }
19189
19190    var _proto = ControlBar.prototype;
19191
19192    /**
19193     * Create the `Component`'s DOM element
19194     *
19195     * @return {Element}
19196     *         The element that was created.
19197     */
19198    _proto.createEl = function createEl() {
19199      return _Component.prototype.createEl.call(this, 'div', {
19200        className: 'vjs-control-bar',
19201        dir: 'ltr'
19202      });
19203    };
19204
19205    return ControlBar;
19206  }(Component);
19207  /**
19208   * Default options for `ControlBar`
19209   *
19210   * @type {Object}
19211   * @private
19212   */
19213
19214
19215  ControlBar.prototype.options_ = {
19216    children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle']
19217  };
19218  Component.registerComponent('ControlBar', ControlBar);
19219
19220  /**
19221   * A display that indicates an error has occurred. This means that the video
19222   * is unplayable.
19223   *
19224   * @extends ModalDialog
19225   */
19226
19227  var ErrorDisplay =
19228  /*#__PURE__*/
19229  function (_ModalDialog) {
19230    _inheritsLoose(ErrorDisplay, _ModalDialog);
19231
19232    /**
19233     * Creates an instance of this class.
19234     *
19235     * @param  {Player} player
19236     *         The `Player` that this class should be attached to.
19237     *
19238     * @param  {Object} [options]
19239     *         The key/value store of player options.
19240     */
19241    function ErrorDisplay(player, options) {
19242      var _this;
19243
19244      _this = _ModalDialog.call(this, player, options) || this;
19245
19246      _this.on(player, 'error', _this.open);
19247
19248      return _this;
19249    }
19250    /**
19251     * Builds the default DOM `className`.
19252     *
19253     * @return {string}
19254     *         The DOM `className` for this object.
19255     *
19256     * @deprecated Since version 5.
19257     */
19258
19259
19260    var _proto = ErrorDisplay.prototype;
19261
19262    _proto.buildCSSClass = function buildCSSClass() {
19263      return "vjs-error-display " + _ModalDialog.prototype.buildCSSClass.call(this);
19264    }
19265    /**
19266     * Gets the localized error message based on the `Player`s error.
19267     *
19268     * @return {string}
19269     *         The `Player`s error message localized or an empty string.
19270     */
19271    ;
19272
19273    _proto.content = function content() {
19274      var error = this.player().error();
19275      return error ? this.localize(error.message) : '';
19276    };
19277
19278    return ErrorDisplay;
19279  }(ModalDialog);
19280  /**
19281   * The default options for an `ErrorDisplay`.
19282   *
19283   * @private
19284   */
19285
19286
19287  ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, {
19288    pauseOnOpen: false,
19289    fillAlways: true,
19290    temporary: false,
19291    uncloseable: true
19292  });
19293  Component.registerComponent('ErrorDisplay', ErrorDisplay);
19294
19295  var LOCAL_STORAGE_KEY = 'vjs-text-track-settings';
19296  var COLOR_BLACK = ['#000', 'Black'];
19297  var COLOR_BLUE = ['#00F', 'Blue'];
19298  var COLOR_CYAN = ['#0FF', 'Cyan'];
19299  var COLOR_GREEN = ['#0F0', 'Green'];
19300  var COLOR_MAGENTA = ['#F0F', 'Magenta'];
19301  var COLOR_RED = ['#F00', 'Red'];
19302  var COLOR_WHITE = ['#FFF', 'White'];
19303  var COLOR_YELLOW = ['#FF0', 'Yellow'];
19304  var OPACITY_OPAQUE = ['1', 'Opaque'];
19305  var OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
19306  var OPACITY_TRANS = ['0', 'Transparent']; // Configuration for the various <select> elements in the DOM of this component.
19307  //
19308  // Possible keys include:
19309  //
19310  // `default`:
19311  //   The default option index. Only needs to be provided if not zero.
19312  // `parser`:
19313  //   A function which is used to parse the value from the selected option in
19314  //   a customized way.
19315  // `selector`:
19316  //   The selector used to find the associated <select> element.
19317
19318  var selectConfigs = {
19319    backgroundColor: {
19320      selector: '.vjs-bg-color > select',
19321      id: 'captions-background-color-%s',
19322      label: 'Color',
19323      options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
19324    },
19325    backgroundOpacity: {
19326      selector: '.vjs-bg-opacity > select',
19327      id: 'captions-background-opacity-%s',
19328      label: 'Transparency',
19329      options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
19330    },
19331    color: {
19332      selector: '.vjs-fg-color > select',
19333      id: 'captions-foreground-color-%s',
19334      label: 'Color',
19335      options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
19336    },
19337    edgeStyle: {
19338      selector: '.vjs-edge-style > select',
19339      id: '%s',
19340      label: 'Text Edge Style',
19341      options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']]
19342    },
19343    fontFamily: {
19344      selector: '.vjs-font-family > select',
19345      id: 'captions-font-family-%s',
19346      label: 'Font Family',
19347      options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
19348    },
19349    fontPercent: {
19350      selector: '.vjs-font-percent > select',
19351      id: 'captions-font-size-%s',
19352      label: 'Font Size',
19353      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%']],
19354      default: 2,
19355      parser: function parser(v) {
19356        return v === '1.00' ? null : Number(v);
19357      }
19358    },
19359    textOpacity: {
19360      selector: '.vjs-text-opacity > select',
19361      id: 'captions-foreground-opacity-%s',
19362      label: 'Transparency',
19363      options: [OPACITY_OPAQUE, OPACITY_SEMI]
19364    },
19365    // Options for this object are defined below.
19366    windowColor: {
19367      selector: '.vjs-window-color > select',
19368      id: 'captions-window-color-%s',
19369      label: 'Color'
19370    },
19371    // Options for this object are defined below.
19372    windowOpacity: {
19373      selector: '.vjs-window-opacity > select',
19374      id: 'captions-window-opacity-%s',
19375      label: 'Transparency',
19376      options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
19377    }
19378  };
19379  selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
19380  /**
19381   * Get the actual value of an option.
19382   *
19383   * @param  {string} value
19384   *         The value to get
19385   *
19386   * @param  {Function} [parser]
19387   *         Optional function to adjust the value.
19388   *
19389   * @return {Mixed}
19390   *         - Will be `undefined` if no value exists
19391   *         - Will be `undefined` if the given value is "none".
19392   *         - Will be the actual value otherwise.
19393   *
19394   * @private
19395   */
19396
19397  function parseOptionValue(value, parser) {
19398    if (parser) {
19399      value = parser(value);
19400    }
19401
19402    if (value && value !== 'none') {
19403      return value;
19404    }
19405  }
19406  /**
19407   * Gets the value of the selected <option> element within a <select> element.
19408   *
19409   * @param  {Element} el
19410   *         the element to look in
19411   *
19412   * @param  {Function} [parser]
19413   *         Optional function to adjust the value.
19414   *
19415   * @return {Mixed}
19416   *         - Will be `undefined` if no value exists
19417   *         - Will be `undefined` if the given value is "none".
19418   *         - Will be the actual value otherwise.
19419   *
19420   * @private
19421   */
19422
19423
19424  function getSelectedOptionValue(el, parser) {
19425    var value = el.options[el.options.selectedIndex].value;
19426    return parseOptionValue(value, parser);
19427  }
19428  /**
19429   * Sets the selected <option> element within a <select> element based on a
19430   * given value.
19431   *
19432   * @param {Element} el
19433   *        The element to look in.
19434   *
19435   * @param {string} value
19436   *        the property to look on.
19437   *
19438   * @param {Function} [parser]
19439   *        Optional function to adjust the value before comparing.
19440   *
19441   * @private
19442   */
19443
19444
19445  function setSelectedOption(el, value, parser) {
19446    if (!value) {
19447      return;
19448    }
19449
19450    for (var i = 0; i < el.options.length; i++) {
19451      if (parseOptionValue(el.options[i].value, parser) === value) {
19452        el.selectedIndex = i;
19453        break;
19454      }
19455    }
19456  }
19457  /**
19458   * Manipulate Text Tracks settings.
19459   *
19460   * @extends ModalDialog
19461   */
19462
19463
19464  var TextTrackSettings =
19465  /*#__PURE__*/
19466  function (_ModalDialog) {
19467    _inheritsLoose(TextTrackSettings, _ModalDialog);
19468
19469    /**
19470     * Creates an instance of this class.
19471     *
19472     * @param {Player} player
19473     *         The `Player` that this class should be attached to.
19474     *
19475     * @param {Object} [options]
19476     *         The key/value store of player options.
19477     */
19478    function TextTrackSettings(player, options) {
19479      var _this;
19480
19481      options.temporary = false;
vendor: 16,391 bytes, lines 19482-19998
19482      _this = _ModalDialog.call(this, player, options) || this;
19483      _this.updateDisplay = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.updateDisplay); // fill the modal and pretend we have opened it
19484
19485      _this.fill();
19486
19487      _this.hasBeenOpened_ = _this.hasBeenFilled_ = true;
19488      _this.endDialog = createEl('p', {
19489        className: 'vjs-control-text',
19490        textContent: _this.localize('End of dialog window.')
19491      });
19492
19493      _this.el().appendChild(_this.endDialog);
19494
19495      _this.setDefaults(); // Grab `persistTextTrackSettings` from the player options if not passed in child options
19496
19497
19498      if (options.persistTextTrackSettings === undefined) {
19499        _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings;
19500      }
19501
19502      _this.on(_this.$('.vjs-done-button'), 'click', function () {
19503        _this.saveSettings();
19504
19505        _this.close();
19506      });
19507
19508      _this.on(_this.$('.vjs-default-button'), 'click', function () {
19509        _this.setDefaults();
19510
19511        _this.updateDisplay();
19512      });
19513
19514      each(selectConfigs, function (config) {
19515        _this.on(_this.$(config.selector), 'change', _this.updateDisplay);
19516      });
19517
19518      if (_this.options_.persistTextTrackSettings) {
19519        _this.restoreSettings();
19520      }
19521
19522      return _this;
19523    }
19524
19525    var _proto = TextTrackSettings.prototype;
19526
19527    _proto.dispose = function dispose() {
19528      this.endDialog = null;
19529
19530      _ModalDialog.prototype.dispose.call(this);
19531    }
19532    /**
19533     * Create a <select> element with configured options.
19534     *
19535     * @param {string} key
19536     *        Configuration key to use during creation.
19537     *
19538     * @return {string}
19539     *         An HTML string.
19540     *
19541     * @private
19542     */
19543    ;
19544
19545    _proto.createElSelect_ = function createElSelect_(key, legendId, type) {
19546      var _this2 = this;
19547
19548      if (legendId === void 0) {
19549        legendId = '';
19550      }
19551
19552      if (type === void 0) {
19553        type = 'label';
19554      }
19555
19556      var config = selectConfigs[key];
19557      var id = config.id.replace('%s', this.id_);
19558      var selectLabelledbyIds = [legendId, id].join(' ').trim();
19559      return ["<" + type + " id=\"" + id + "\" class=\"" + (type === 'label' ? 'vjs-label' : '') + "\">", this.localize(config.label), "</" + type + ">", "<select aria-labelledby=\"" + selectLabelledbyIds + "\">"].concat(config.options.map(function (o) {
19560        var optionId = id + '-' + o[1].replace(/\W+/g, '');
19561        return ["<option id=\"" + optionId + "\" value=\"" + o[0] + "\" ", "aria-labelledby=\"" + selectLabelledbyIds + " " + optionId + "\">", _this2.localize(o[1]), '</option>'].join('');
19562      })).concat('</select>').join('');
19563    }
19564    /**
19565     * Create foreground color element for the component
19566     *
19567     * @return {string}
19568     *         An HTML string.
19569     *
19570     * @private
19571     */
19572    ;
19573
19574    _proto.createElFgColor_ = function createElFgColor_() {
19575      var legendId = "captions-text-legend-" + this.id_;
19576      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('');
19577    }
19578    /**
19579     * Create background color element for the component
19580     *
19581     * @return {string}
19582     *         An HTML string.
19583     *
19584     * @private
19585     */
19586    ;
19587
19588    _proto.createElBgColor_ = function createElBgColor_() {
19589      var legendId = "captions-background-" + this.id_;
19590      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('');
19591    }
19592    /**
19593     * Create window color element for the component
19594     *
19595     * @return {string}
19596     *         An HTML string.
19597     *
19598     * @private
19599     */
19600    ;
19601
19602    _proto.createElWinColor_ = function createElWinColor_() {
19603      var legendId = "captions-window-" + this.id_;
19604      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('');
19605    }
19606    /**
19607     * Create color elements for the component
19608     *
19609     * @return {Element}
19610     *         The element that was created
19611     *
19612     * @private
19613     */
19614    ;
19615
19616    _proto.createElColors_ = function createElColors_() {
19617      return createEl('div', {
19618        className: 'vjs-track-settings-colors',
19619        innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
19620      });
19621    }
19622    /**
19623     * Create font elements for the component
19624     *
19625     * @return {Element}
19626     *         The element that was created.
19627     *
19628     * @private
19629     */
19630    ;
19631
19632    _proto.createElFont_ = function createElFont_() {
19633      return createEl('div', {
19634        className: 'vjs-track-settings-font',
19635        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('')
19636      });
19637    }
19638    /**
19639     * Create controls for the component
19640     *
19641     * @return {Element}
19642     *         The element that was created.
19643     *
19644     * @private
19645     */
19646    ;
19647
19648    _proto.createElControls_ = function createElControls_() {
19649      var defaultsDescription = this.localize('restore all settings to the default values');
19650      return createEl('div', {
19651        className: 'vjs-track-settings-controls',
19652        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('')
19653      });
19654    };
19655
19656    _proto.content = function content() {
19657      return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
19658    };
19659
19660    _proto.label = function label() {
19661      return this.localize('Caption Settings Dialog');
19662    };
19663
19664    _proto.description = function description() {
19665      return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
19666    };
19667
19668    _proto.buildCSSClass = function buildCSSClass() {
19669      return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings';
19670    }
19671    /**
19672     * Gets an object of text track settings (or null).
19673     *
19674     * @return {Object}
19675     *         An object with config values parsed from the DOM or localStorage.
19676     */
19677    ;
19678
19679    _proto.getValues = function getValues() {
19680      var _this3 = this;
19681
19682      return reduce(selectConfigs, function (accum, config, key) {
19683        var value = getSelectedOptionValue(_this3.$(config.selector), config.parser);
19684
19685        if (value !== undefined) {
19686          accum[key] = value;
19687        }
19688
19689        return accum;
19690      }, {});
19691    }
19692    /**
19693     * Sets text track settings from an object of values.
19694     *
19695     * @param {Object} values
19696     *        An object with config values parsed from the DOM or localStorage.
19697     */
19698    ;
19699
19700    _proto.setValues = function setValues(values) {
19701      var _this4 = this;
19702
19703      each(selectConfigs, function (config, key) {
19704        setSelectedOption(_this4.$(config.selector), values[key], config.parser);
19705      });
19706    }
19707    /**
19708     * Sets all `<select>` elements to their default values.
19709     */
19710    ;
19711
19712    _proto.setDefaults = function setDefaults() {
19713      var _this5 = this;
19714
19715      each(selectConfigs, function (config) {
19716        var index = config.hasOwnProperty('default') ? config.default : 0;
19717        _this5.$(config.selector).selectedIndex = index;
19718      });
19719    }
19720    /**
19721     * Restore texttrack settings from localStorage
19722     */
19723    ;
19724
19725    _proto.restoreSettings = function restoreSettings() {
19726      var values;
19727
19728      try {
19729        values = JSON.parse(window$1.localStorage.getItem(LOCAL_STORAGE_KEY));
19730      } catch (err) {
19731        log.warn(err);
19732      }
19733
19734      if (values) {
19735        this.setValues(values);
19736      }
19737    }
19738    /**
19739     * Save text track settings to localStorage
19740     */
19741    ;
19742
19743    _proto.saveSettings = function saveSettings() {
19744      if (!this.options_.persistTextTrackSettings) {
19745        return;
19746      }
19747
19748      var values = this.getValues();
19749
19750      try {
19751        if (Object.keys(values).length) {
19752          window$1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values));
19753        } else {
19754          window$1.localStorage.removeItem(LOCAL_STORAGE_KEY);
19755        }
19756      } catch (err) {
19757        log.warn(err);
19758      }
19759    }
19760    /**
19761     * Update display of text track settings
19762     */
19763    ;
19764
19765    _proto.updateDisplay = function updateDisplay() {
19766      var ttDisplay = this.player_.getChild('textTrackDisplay');
19767
19768      if (ttDisplay) {
19769        ttDisplay.updateDisplay();
19770      }
19771    }
19772    /**
19773     * conditionally blur the element and refocus the captions button
19774     *
19775     * @private
19776     */
19777    ;
19778
19779    _proto.conditionalBlur_ = function conditionalBlur_() {
19780      this.previouslyActiveEl_ = null;
19781      var cb = this.player_.controlBar;
19782      var subsCapsBtn = cb && cb.subsCapsButton;
19783      var ccBtn = cb && cb.captionsButton;
19784
19785      if (subsCapsBtn) {
19786        subsCapsBtn.focus();
19787      } else if (ccBtn) {
19788        ccBtn.focus();
19789      }
19790    };
19791
19792    return TextTrackSettings;
19793  }(ModalDialog);
19794
19795  Component.registerComponent('TextTrackSettings', TextTrackSettings);
19796
19797  /**
19798   * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
19799   *
19800   * 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}.
19801   *
19802   * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
19803   * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
19804   * @example <caption>How to disable the resize manager</caption>
19805   * const player = videojs('#vid', {
19806   *   resizeManager: false
19807   * });
19808   *
19809   * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
19810   *
19811   * @extends Component
19812   */
19813
19814  var ResizeManager =
19815  /*#__PURE__*/
19816  function (_Component) {
19817    _inheritsLoose(ResizeManager, _Component);
19818
19819    /**
19820     * Create the ResizeManager.
19821     *
19822     * @param {Object} player
19823     *        The `Player` that this class should be attached to.
19824     *
19825     * @param {Object} [options]
19826     *        The key/value store of ResizeManager options.
19827     *
19828     * @param {Object} [options.ResizeObserver]
19829     *        A polyfill for ResizeObserver can be passed in here.
19830     *        If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
19831     */
19832    function ResizeManager(player, options) {
19833      var _this;
19834
19835      var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window$1.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver
19836
19837      if (options.ResizeObserver === null) {
19838        RESIZE_OBSERVER_AVAILABLE = false;
19839      } // Only create an element when ResizeObserver isn't available
19840
19841
19842      var options_ = mergeOptions({
19843        createEl: !RESIZE_OBSERVER_AVAILABLE,
19844        reportTouchActivity: false
19845      }, options);
19846      _this = _Component.call(this, player, options_) || this;
19847      _this.ResizeObserver = options.ResizeObserver || window$1.ResizeObserver;
19848      _this.loadListener_ = null;
19849      _this.resizeObserver_ = null;
19850      _this.debouncedHandler_ = debounce(function () {
19851        _this.resizeHandler();
19852      }, 100, false, _assertThisInitialized(_assertThisInitialized(_this)));
19853
19854      if (RESIZE_OBSERVER_AVAILABLE) {
19855        _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_);
19856
19857        _this.resizeObserver_.observe(player.el());
19858      } else {
19859        _this.loadListener_ = function () {
19860          if (!_this.el_ || !_this.el_.contentWindow) {
19861            return;
19862          }
19863
19864          var debouncedHandler_ = _this.debouncedHandler_;
19865
19866          var unloadListener_ = _this.unloadListener_ = function () {
19867            off(this, 'resize', debouncedHandler_);
19868            off(this, 'unload', unloadListener_);
19869            unloadListener_ = null;
19870          }; // safari and edge can unload the iframe before resizemanager dispose
19871          // we have to dispose of event handlers correctly before that happens
19872
19873
19874          on(_this.el_.contentWindow, 'unload', unloadListener_);
19875          on(_this.el_.contentWindow, 'resize', debouncedHandler_);
19876        };
19877
19878        _this.one('load', _this.loadListener_);
19879      }
19880
19881      return _this;
19882    }
19883
19884    var _proto = ResizeManager.prototype;
19885
19886    _proto.createEl = function createEl() {
19887      return _Component.prototype.createEl.call(this, 'iframe', {
19888        className: 'vjs-resize-manager',
19889        tabIndex: -1
19890      }, {
19891        'aria-hidden': 'true'
19892      });
19893    }
19894    /**
19895     * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
19896     *
19897     * @fires Player#playerresize
19898     */
19899    ;
19900
19901    _proto.resizeHandler = function resizeHandler() {
19902      /**
19903       * Called when the player size has changed
19904       *
19905       * @event Player#playerresize
19906       * @type {EventTarget~Event}
19907       */
19908      // make sure player is still around to trigger
19909      // prevents this from causing an error after dispose
19910      if (!this.player_ || !this.player_.trigger) {
19911        return;
19912      }
19913
19914      this.player_.trigger('playerresize');
19915    };
19916
19917    _proto.dispose = function dispose() {
19918      if (this.debouncedHandler_) {
19919        this.debouncedHandler_.cancel();
19920      }
19921
19922      if (this.resizeObserver_) {
19923        if (this.player_.el()) {
19924          this.resizeObserver_.unobserve(this.player_.el());
19925        }
19926
19927        this.resizeObserver_.disconnect();
19928      }
19929
19930      if (this.loadListener_) {
19931        this.off('load', this.loadListener_);
19932      }
19933
19934      if (this.el_ && this.el_.contentWindow && this.unloadListener_) {
19935        this.unloadListener_.call(this.el_.contentWindow);
19936      }
19937
19938      this.ResizeObserver = null;
19939      this.resizeObserver = null;
19940      this.debouncedHandler_ = null;
19941      this.loadListener_ = null;
19942
19943      _Component.prototype.dispose.call(this);
19944    };
19945
19946    return ResizeManager;
19947  }(Component);
19948
19949  Component.registerComponent('ResizeManager', ResizeManager);
19950
19951  /* track when we are at the live edge, and other helpers for live playback */
19952
19953  var LiveTracker =
19954  /*#__PURE__*/
19955  function (_Component) {
19956    _inheritsLoose(LiveTracker, _Component);
19957
19958    function LiveTracker(player, options) {
19959      var _this;
19960
19961      // LiveTracker does not need an element
19962      var options_ = mergeOptions({
19963        createEl: false
19964      }, options);
19965      _this = _Component.call(this, player, options_) || this;
19966
19967      _this.reset_();
19968
19969      _this.on(_this.player_, 'durationchange', _this.handleDurationchange); // we don't need to track live playback if the document is hidden,
19970      // also, tracking when the document is hidden can
19971      // cause the CPU to spike and eventually crash the page on IE11.
19972
19973
19974      if (IE_VERSION && 'hidden' in document && 'visibilityState' in document) {
19975        _this.on(document, 'visibilitychange', _this.handleVisibilityChange);
19976      }
19977
19978      return _this;
19979    }
19980
19981    var _proto = LiveTracker.prototype;
19982
19983    _proto.handleVisibilityChange = function handleVisibilityChange() {
19984      if (this.player_.duration() !== Infinity) {
19985        return;
19986      }
19987
19988      if (document.hidden) {
19989        this.stopTracking();
19990      } else {
19991        this.startTracking();
19992      }
19993    };
19994
19995    _proto.isBehind_ = function isBehind_() {
19996      // don't report that we are behind until a timeupdate has been seen
19997      if (!this.timeupdateSeen_) {
19998        return false;
19999      }
20000
20001      var liveCurrentTime = this.liveCurrentTime();
20002      var currentTime = this.player_.currentTime();
20003      var seekableIncrement = this.seekableIncrement_; // the live edge window is the amount of seconds away from live
20004      // that a player can be, but still be considered live.
20005      // we add 0.07 because the live tracking happens every 30ms
20006      // and we want some wiggle room for short segment live playback
20007
20008      var liveEdgeWindow = seekableIncrement * 2 + 0.07; // on Android liveCurrentTime can bee Infinity, because seekableEnd
20009      // can be Infinity, so we handle that case.
20010
20011      return liveCurrentTime !== Infinity && liveCurrentTime - liveEdgeWindow >= currentTime;
20012    } // all the functionality for tracking when seek end changes
20013    // and for tracking how far past seek end we should be
20014    ;
20015
20016    _proto.trackLive_ = function trackLive_() {
20017      this.pastSeekEnd_ = this.pastSeekEnd_;
20018      var seekable = this.player_.seekable(); // skip undefined seekable
20019
20020      if (!seekable || !seekable.length) {
20021        return;
20022      }
20023
20024      var newSeekEnd = this.seekableEnd(); // we can only tell if we are behind live, when seekable changes
20025      // once we detect that seekable has changed we check the new seek
20026      // end against current time, with a fudge value of half a second.
20027
20028      if (newSeekEnd !== this.lastSeekEnd_) {
20029        if (this.lastSeekEnd_) {
20030          this.seekableIncrement_ = Math.abs(newSeekEnd - this.lastSeekEnd_);
20031        }
20032
20033        this.pastSeekEnd_ = 0;
20034        this.lastSeekEnd_ = newSeekEnd;
20035        this.trigger('seekableendchange');
20036      }
20037
20038      this.pastSeekEnd_ = this.pastSeekEnd() + 0.03;
20039
20040      if (this.isBehind_() !== this.behindLiveEdge()) {
20041        this.behindLiveEdge_ = this.isBehind_();
20042        this.trigger('liveedgechange');
20043      }
20044    }
20045    /**
20046     * handle a durationchange event on the player
20047     * and start/stop tracking accordingly.
20048     */
20049    ;
20050
20051    _proto.handleDurationchange = function handleDurationchange() {
20052      if (this.player_.duration() === Infinity) {
20053        this.startTracking();
20054      } else {
20055        this.stopTracking();
20056      }
20057    }
20058    /**
20059     * start tracking live playback
20060     */
20061    ;
20062
20063    _proto.startTracking = function startTracking() {
20064      var _this2 = this;
20065
20066      if (this.isTracking()) {
20067        return;
20068      }
20069
20070      this.trackingInterval_ = this.setInterval(this.trackLive_, 30);
20071      this.trackLive_();
20072      this.on(this.player_, 'play', this.trackLive_);
20073      this.on(this.player_, 'pause', this.trackLive_);
20074      this.one(this.player_, 'play', this.handlePlay); // this is to prevent showing that we are not live
20075      // before a video starts to play
20076
20077      if (!this.timeupdateSeen_) {
20078        this.handleTimeupdate = function () {
20079          _this2.timeupdateSeen_ = true;
20080          _this2.handleTimeupdate = null;
20081        };
20082
20083        this.one(this.player_, 'timeupdate', this.handleTimeupdate);
20084      }
20085    };
20086
20087    _proto.handlePlay = function handlePlay() {
20088      this.one(this.player_, 'timeupdate', this.seekToLiveEdge);
20089    }
20090    /**
20091     * Stop tracking, and set all internal variables to
20092     * their initial value.
20093     */
20094    ;
20095
20096    _proto.reset_ = function reset_() {
20097      this.pastSeekEnd_ = 0;
20098      this.lastSeekEnd_ = null;
20099      this.behindLiveEdge_ = null;
20100      this.timeupdateSeen_ = false;
vendor: 21,481 bytes, lines 20101-20775
20101      this.clearInterval(this.trackingInterval_);
20102      this.trackingInterval_ = null;
20103      this.seekableIncrement_ = 12;
20104      this.off(this.player_, 'play', this.trackLive_);
20105      this.off(this.player_, 'pause', this.trackLive_);
20106      this.off(this.player_, 'play', this.handlePlay);
20107      this.off(this.player_, 'timeupdate', this.seekToLiveEdge);
20108
20109      if (this.handleTimeupdate) {
20110        this.off(this.player_, 'timeupdate', this.handleTimeupdate);
20111        this.handleTimeupdate = null;
20112      }
20113    }
20114    /**
20115     * stop tracking live playback
20116     */
20117    ;
20118
20119    _proto.stopTracking = function stopTracking() {
20120      if (!this.isTracking()) {
20121        return;
20122      }
20123
20124      this.reset_();
20125    }
20126    /**
20127     * A helper to get the player seekable end
20128     * so that we don't have to null check everywhere
20129     */
20130    ;
20131
20132    _proto.seekableEnd = function seekableEnd() {
20133      var seekable = this.player_.seekable();
20134      var seekableEnds = [];
20135      var i = seekable ? seekable.length : 0;
20136
20137      while (i--) {
20138        seekableEnds.push(seekable.end(i));
20139      } // grab the furthest seekable end after sorting, or if there are none
20140      // default to Infinity
20141
20142
20143      return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity;
20144    }
20145    /**
20146     * A helper to get the player seekable start
20147     * so that we don't have to null check everywhere
20148     */
20149    ;
20150
20151    _proto.seekableStart = function seekableStart() {
20152      var seekable = this.player_.seekable();
20153      var seekableStarts = [];
20154      var i = seekable ? seekable.length : 0;
20155
20156      while (i--) {
20157        seekableStarts.push(seekable.start(i));
20158      } // grab the first seekable start after sorting, or if there are none
20159      // default to 0
20160
20161
20162      return seekableStarts.length ? seekableStarts.sort()[0] : 0;
20163    }
20164    /**
20165     * Get the live time window
20166     */
20167    ;
20168
20169    _proto.liveWindow = function liveWindow() {
20170      var liveCurrentTime = this.liveCurrentTime();
20171
20172      if (liveCurrentTime === Infinity) {
20173        return Infinity;
20174      }
20175
20176      return liveCurrentTime - this.seekableStart();
20177    }
20178    /**
20179     * Determines if the player is live, only checks if this component
20180     * is tracking live playback or not
20181     */
20182    ;
20183
20184    _proto.isLive = function isLive() {
20185      return this.isTracking();
20186    }
20187    /**
20188     * Determines if currentTime is at the live edge and won't fall behind
20189     * on each seekableendchange
20190     */
20191    ;
20192
20193    _proto.atLiveEdge = function atLiveEdge() {
20194      return !this.behindLiveEdge();
20195    }
20196    /**
20197     * get what we expect the live current time to be
20198     */
20199    ;
20200
20201    _proto.liveCurrentTime = function liveCurrentTime() {
20202      return this.pastSeekEnd() + this.seekableEnd();
20203    }
20204    /**
20205     * Returns how far past seek end we expect current time to be
20206     */
20207    ;
20208
20209    _proto.pastSeekEnd = function pastSeekEnd() {
20210      return this.pastSeekEnd_;
20211    }
20212    /**
20213     * If we are currently behind the live edge, aka currentTime will be
20214     * behind on a seekableendchange
20215     */
20216    ;
20217
20218    _proto.behindLiveEdge = function behindLiveEdge() {
20219      return this.behindLiveEdge_;
20220    };
20221
20222    _proto.isTracking = function isTracking() {
20223      return typeof this.trackingInterval_ === 'number';
20224    }
20225    /**
20226     * Seek to the live edge if we are behind the live edge
20227     */
20228    ;
20229
20230    _proto.seekToLiveEdge = function seekToLiveEdge() {
20231      if (this.atLiveEdge()) {
20232        return;
20233      }
20234
20235      this.player_.currentTime(this.liveCurrentTime());
20236
20237      if (this.player_.paused()) {
20238        this.player_.play();
20239      }
20240    };
20241
20242    _proto.dispose = function dispose() {
20243      this.stopTracking();
20244
20245      _Component.prototype.dispose.call(this);
20246    };
20247
20248    return LiveTracker;
20249  }(Component);
20250
20251  Component.registerComponent('LiveTracker', LiveTracker);
20252
20253  /**
20254   * This function is used to fire a sourceset when there is something
20255   * similar to `mediaEl.load()` being called. It will try to find the source via
20256   * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
20257   * with the source that was found or empty string if we cannot know. If it cannot
20258   * find a source then `sourceset` will not be fired.
20259   *
20260   * @param {Html5} tech
20261   *        The tech object that sourceset was setup on
20262   *
20263   * @return {boolean}
20264   *         returns false if the sourceset was not fired and true otherwise.
20265   */
20266
20267  var sourcesetLoad = function sourcesetLoad(tech) {
20268    var el = tech.el(); // if `el.src` is set, that source will be loaded.
20269
20270    if (el.hasAttribute('src')) {
20271      tech.triggerSourceset(el.src);
20272      return true;
20273    }
20274    /**
20275     * Since there isn't a src property on the media element, source elements will be used for
20276     * implementing the source selection algorithm. This happens asynchronously and
20277     * for most cases were there is more than one source we cannot tell what source will
20278     * be loaded, without re-implementing the source selection algorithm. At this time we are not
20279     * going to do that. There are three special cases that we do handle here though:
20280     *
20281     * 1. If there are no sources, do not fire `sourceset`.
20282     * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
20283     * 3. If there is more than one `<source>` but all of them have the same `src` url.
20284     *    That will be our src.
20285     */
20286
20287
20288    var sources = tech.$$('source');
20289    var srcUrls = [];
20290    var src = ''; // if there are no sources, do not fire sourceset
20291
20292    if (!sources.length) {
20293      return false;
20294    } // only count valid/non-duplicate source elements
20295
20296
20297    for (var i = 0; i < sources.length; i++) {
20298      var url = sources[i].src;
20299
20300      if (url && srcUrls.indexOf(url) === -1) {
20301        srcUrls.push(url);
20302      }
20303    } // there were no valid sources
20304
20305
20306    if (!srcUrls.length) {
20307      return false;
20308    } // there is only one valid source element url
20309    // use that
20310
20311
20312    if (srcUrls.length === 1) {
20313      src = srcUrls[0];
20314    }
20315
20316    tech.triggerSourceset(src);
20317    return true;
20318  };
20319  /**
20320   * our implementation of an `innerHTML` descriptor for browsers
20321   * that do not have one.
20322   */
20323
20324
20325  var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
20326    get: function get() {
20327      return this.cloneNode(true).innerHTML;
20328    },
20329    set: function set(v) {
20330      // make a dummy node to use innerHTML on
20331      var dummy = document.createElement(this.nodeName.toLowerCase()); // set innerHTML to the value provided
20332
20333      dummy.innerHTML = v; // make a document fragment to hold the nodes from dummy
20334
20335      var docFrag = document.createDocumentFragment(); // copy all of the nodes created by the innerHTML on dummy
20336      // to the document fragment
20337
20338      while (dummy.childNodes.length) {
20339        docFrag.appendChild(dummy.childNodes[0]);
20340      } // remove content
20341
20342
20343      this.innerText = ''; // now we add all of that html in one by appending the
20344      // document fragment. This is how innerHTML does it.
20345
20346      window$1.Element.prototype.appendChild.call(this, docFrag); // then return the result that innerHTML's setter would
20347
20348      return this.innerHTML;
20349    }
20350  });
20351  /**
20352   * Get a property descriptor given a list of priorities and the
20353   * property to get.
20354   */
20355
20356  var getDescriptor = function getDescriptor(priority, prop) {
20357    var descriptor = {};
20358
20359    for (var i = 0; i < priority.length; i++) {
20360      descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
20361
20362      if (descriptor && descriptor.set && descriptor.get) {
20363        break;
20364      }
20365    }
20366
20367    descriptor.enumerable = true;
20368    descriptor.configurable = true;
20369    return descriptor;
20370  };
20371
20372  var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) {
20373    return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, window$1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
20374  };
20375  /**
20376   * Patches browser internal functions so that we can tell synchronously
20377   * if a `<source>` was appended to the media element. For some reason this
20378   * causes a `sourceset` if the the media element is ready and has no source.
20379   * This happens when:
20380   * - The page has just loaded and the media element does not have a source.
20381   * - The media element was emptied of all sources, then `load()` was called.
20382   *
20383   * It does this by patching the following functions/properties when they are supported:
20384   *
20385   * - `append()` - can be used to add a `<source>` element to the media element
20386   * - `appendChild()` - can be used to add a `<source>` element to the media element
20387   * - `insertAdjacentHTML()` -  can be used to add a `<source>` element to the media element
20388   * - `innerHTML` -  can be used to add a `<source>` element to the media element
20389   *
20390   * @param {Html5} tech
20391   *        The tech object that sourceset is being setup on.
20392   */
20393
20394
20395  var firstSourceWatch = function firstSourceWatch(tech) {
20396    var el = tech.el(); // make sure firstSourceWatch isn't setup twice.
20397
20398    if (el.resetSourceWatch_) {
20399      return;
20400    }
20401
20402    var old = {};
20403    var innerDescriptor = getInnerHTMLDescriptor(tech);
20404
20405    var appendWrapper = function appendWrapper(appendFn) {
20406      return function () {
20407        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
20408          args[_key] = arguments[_key];
20409        }
20410
20411        var retval = appendFn.apply(el, args);
20412        sourcesetLoad(tech);
20413        return retval;
20414      };
20415    };
20416
20417    ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) {
20418      if (!el[k]) {
20419        return;
20420      } // store the old function
20421
20422
20423      old[k] = el[k]; // call the old function with a sourceset if a source
20424      // was loaded
20425
20426      el[k] = appendWrapper(old[k]);
20427    });
20428    Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, {
20429      set: appendWrapper(innerDescriptor.set)
20430    }));
20431
20432    el.resetSourceWatch_ = function () {
20433      el.resetSourceWatch_ = null;
20434      Object.keys(old).forEach(function (k) {
20435        el[k] = old[k];
20436      });
20437      Object.defineProperty(el, 'innerHTML', innerDescriptor);
20438    }; // on the first sourceset, we need to revert our changes
20439
20440
20441    tech.one('sourceset', el.resetSourceWatch_);
20442  };
20443  /**
20444   * our implementation of a `src` descriptor for browsers
20445   * that do not have one.
20446   */
20447
20448
20449  var srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
20450    get: function get() {
20451      if (this.hasAttribute('src')) {
20452        return getAbsoluteURL(window$1.Element.prototype.getAttribute.call(this, 'src'));
20453      }
20454
20455      return '';
20456    },
20457    set: function set(v) {
20458      window$1.Element.prototype.setAttribute.call(this, 'src', v);
20459      return v;
20460    }
20461  });
20462
20463  var getSrcDescriptor = function getSrcDescriptor(tech) {
20464    return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
20465  };
20466  /**
20467   * setup `sourceset` handling on the `Html5` tech. This function
20468   * patches the following element properties/functions:
20469   *
20470   * - `src` - to determine when `src` is set
20471   * - `setAttribute()` - to determine when `src` is set
20472   * - `load()` - this re-triggers the source selection algorithm, and can
20473   *              cause a sourceset.
20474   *
20475   * If there is no source when we are adding `sourceset` support or during a `load()`
20476   * we also patch the functions listed in `firstSourceWatch`.
20477   *
20478   * @param {Html5} tech
20479   *        The tech to patch
20480   */
20481
20482
20483  var setupSourceset = function setupSourceset(tech) {
20484    if (!tech.featuresSourceset) {
20485      return;
20486    }
20487
20488    var el = tech.el(); // make sure sourceset isn't setup twice.
20489
20490    if (el.resetSourceset_) {
20491      return;
20492    }
20493
20494    var srcDescriptor = getSrcDescriptor(tech);
20495    var oldSetAttribute = el.setAttribute;
20496    var oldLoad = el.load;
20497    Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, {
20498      set: function set(v) {
20499        var retval = srcDescriptor.set.call(el, v); // we use the getter here to get the actual value set on src
20500
20501        tech.triggerSourceset(el.src);
20502        return retval;
20503      }
20504    }));
20505
20506    el.setAttribute = function (n, v) {
20507      var retval = oldSetAttribute.call(el, n, v);
20508
20509      if (/src/i.test(n)) {
20510        tech.triggerSourceset(el.src);
20511      }
20512
20513      return retval;
20514    };
20515
20516    el.load = function () {
20517      var retval = oldLoad.call(el); // if load was called, but there was no source to fire
20518      // sourceset on. We have to watch for a source append
20519      // as that can trigger a `sourceset` when the media element
20520      // has no source
20521
20522      if (!sourcesetLoad(tech)) {
20523        tech.triggerSourceset('');
20524        firstSourceWatch(tech);
20525      }
20526
20527      return retval;
20528    };
20529
20530    if (el.currentSrc) {
20531      tech.triggerSourceset(el.currentSrc);
20532    } else if (!sourcesetLoad(tech)) {
20533      firstSourceWatch(tech);
20534    }
20535
20536    el.resetSourceset_ = function () {
20537      el.resetSourceset_ = null;
20538      el.load = oldLoad;
20539      el.setAttribute = oldSetAttribute;
20540      Object.defineProperty(el, 'src', srcDescriptor);
20541
20542      if (el.resetSourceWatch_) {
20543        el.resetSourceWatch_();
20544      }
20545    };
20546  };
20547
20548  function _templateObject$1() {
20549    var data = _taggedTemplateLiteralLoose(["Text Tracks are being loaded from another origin but the crossorigin attribute isn't used.\n            This may prevent text tracks from loading."]);
20550
20551    _templateObject$1 = function _templateObject() {
20552      return data;
20553    };
20554
20555    return data;
20556  }
20557  /**
20558   * HTML5 Media Controller - Wrapper for HTML5 Media API
20559   *
20560   * @mixes Tech~SourceHandlerAdditions
20561   * @extends Tech
20562   */
20563
20564  var Html5 =
20565  /*#__PURE__*/
20566  function (_Tech) {
20567    _inheritsLoose(Html5, _Tech);
20568
20569    /**
20570    * Create an instance of this Tech.
20571    *
20572    * @param {Object} [options]
20573    *        The key/value store of player options.
20574    *
20575    * @param {Component~ReadyCallback} ready
20576    *        Callback function to call when the `HTML5` Tech is ready.
20577    */
20578    function Html5(options, ready) {
20579      var _this;
20580
20581      _this = _Tech.call(this, options, ready) || this;
20582      var source = options.source;
20583      var crossoriginTracks = false; // Set the source if one is provided
20584      // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
20585      // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
20586      // anyway so the error gets fired.
20587
20588      if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
20589        _this.setSource(source);
20590      } else {
20591        _this.handleLateInit_(_this.el_);
20592      } // setup sourceset after late sourceset/init
20593
20594
20595      if (options.enableSourceset) {
20596        _this.setupSourcesetHandling_();
20597      }
20598
20599      if (_this.el_.hasChildNodes()) {
20600        var nodes = _this.el_.childNodes;
20601        var nodesLength = nodes.length;
20602        var removeNodes = [];
20603
20604        while (nodesLength--) {
20605          var node = nodes[nodesLength];
20606          var nodeName = node.nodeName.toLowerCase();
20607
20608          if (nodeName === 'track') {
20609            if (!_this.featuresNativeTextTracks) {
20610              // Empty video tag tracks so the built-in player doesn't use them also.
20611              // This may not be fast enough to stop HTML5 browsers from reading the tags
20612              // so we'll need to turn off any default tracks if we're manually doing
20613              // captions and subtitles. videoElement.textTracks
20614              removeNodes.push(node);
20615            } else {
20616              // store HTMLTrackElement and TextTrack to remote list
20617              _this.remoteTextTrackEls().addTrackElement_(node);
20618
20619              _this.remoteTextTracks().addTrack(node.track);
20620
20621              _this.textTracks().addTrack(node.track);
20622
20623              if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
20624                crossoriginTracks = true;
20625              }
20626            }
20627          }
20628        }
20629
20630        for (var i = 0; i < removeNodes.length; i++) {
20631          _this.el_.removeChild(removeNodes[i]);
20632        }
20633      }
20634
20635      _this.proxyNativeTracks_();
20636
20637      if (_this.featuresNativeTextTracks && crossoriginTracks) {
20638        log.warn(tsml(_templateObject$1()));
20639      } // prevent iOS Safari from disabling metadata text tracks during native playback
20640
20641
20642      _this.restoreMetadataTracksInIOSNativePlayer_(); // Determine if native controls should be used
20643      // Our goal should be to get the custom controls on mobile solid everywhere
20644      // so we can remove this all together. Right now this will block custom
20645      // controls on touch enabled laptops like the Chrome Pixel
20646
20647
20648      if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) {
20649        _this.setControls(true);
20650      } // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
20651      // into a `fullscreenchange` event
20652
20653
20654      _this.proxyWebkitFullscreen_();
20655
20656      _this.triggerReady();
20657
20658      return _this;
20659    }
20660    /**
20661     * Dispose of `HTML5` media element and remove all tracks.
20662     */
20663
20664
20665    var _proto = Html5.prototype;
20666
20667    _proto.dispose = function dispose() {
20668      if (this.el_ && this.el_.resetSourceset_) {
20669        this.el_.resetSourceset_();
20670      }
20671
20672      Html5.disposeMediaElement(this.el_);
20673      this.options_ = null; // tech will handle clearing of the emulated track list
20674
20675      _Tech.prototype.dispose.call(this);
20676    }
20677    /**
20678     * Modify the media element so that we can detect when
20679     * the source is changed. Fires `sourceset` just after the source has changed
20680     */
20681    ;
20682
20683    _proto.setupSourcesetHandling_ = function setupSourcesetHandling_() {
20684      setupSourceset(this);
20685    }
20686    /**
20687     * When a captions track is enabled in the iOS Safari native player, all other
20688     * tracks are disabled (including metadata tracks), which nulls all of their
20689     * associated cue points. This will restore metadata tracks to their pre-fullscreen
20690     * state in those cases so that cue points are not needlessly lost.
20691     *
20692     * @private
20693     */
20694    ;
20695
20696    _proto.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() {
20697      var textTracks = this.textTracks();
20698      var metadataTracksPreFullscreenState; // captures a snapshot of every metadata track's current state
20699
20700      var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() {
20701        metadataTracksPreFullscreenState = [];
20702
20703        for (var i = 0; i < textTracks.length; i++) {
20704          var track = textTracks[i];
20705
20706          if (track.kind === 'metadata') {
20707            metadataTracksPreFullscreenState.push({
20708              track: track,
20709              storedMode: track.mode
20710            });
20711          }
20712        }
20713      }; // snapshot each metadata track's initial state, and update the snapshot
20714      // each time there is a track 'change' event
20715
20716
20717      takeMetadataTrackSnapshot();
20718      textTracks.addEventListener('change', takeMetadataTrackSnapshot);
20719      this.on('dispose', function () {
20720        return textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
20721      });
20722
20723      var restoreTrackMode = function restoreTrackMode() {
20724        for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) {
20725          var storedTrack = metadataTracksPreFullscreenState[i];
20726
20727          if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
20728            storedTrack.track.mode = storedTrack.storedMode;
20729          }
20730        } // we only want this handler to be executed on the first 'change' event
20731
20732
20733        textTracks.removeEventListener('change', restoreTrackMode);
20734      }; // when we enter fullscreen playback, stop updating the snapshot and
20735      // restore all track modes to their pre-fullscreen state
20736
20737
20738      this.on('webkitbeginfullscreen', function () {
20739        textTracks.removeEventListener('change', takeMetadataTrackSnapshot); // remove the listener before adding it just in case it wasn't previously removed
20740
20741        textTracks.removeEventListener('change', restoreTrackMode);
20742        textTracks.addEventListener('change', restoreTrackMode);
20743      }); // start updating the snapshot again after leaving fullscreen
20744
20745      this.on('webkitendfullscreen', function () {
20746        // remove the listener before adding it just in case it wasn't previously removed
20747        textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
20748        textTracks.addEventListener('change', takeMetadataTrackSnapshot); // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
20749
20750        textTracks.removeEventListener('change', restoreTrackMode);
20751      });
20752    }
20753    /**
20754     * Attempt to force override of tracks for the given type
20755     *
20756     * @param {string} type - Track type to override, possible values include 'Audio',
20757     * 'Video', and 'Text'.
20758     * @param {boolean} override - If set to true native audio/video will be overridden,
20759     * otherwise native audio/video will potentially be used.
20760     * @private
20761     */
20762    ;
20763
20764    _proto.overrideNative_ = function overrideNative_(type, override) {
20765      var _this2 = this;
20766
20767      // If there is no behavioral change don't add/remove listeners
20768      if (override !== this["featuresNative" + type + "Tracks"]) {
20769        return;
20770      }
20771
20772      var lowerCaseType = type.toLowerCase();
20773
20774      if (this[lowerCaseType + "TracksListeners_"]) {
20775        Object.keys(this[lowerCaseType + "TracksListeners_"]).forEac
vendor: 9,613 bytes, lines 20775-21067
20775h(function (eventName) {
20776          var elTracks = _this2.el()[lowerCaseType + "Tracks"];
20777
20778          elTracks.removeEventListener(eventName, _this2[lowerCaseType + "TracksListeners_"][eventName]);
20779        });
20780      }
20781
20782      this["featuresNative" + type + "Tracks"] = !override;
20783      this[lowerCaseType + "TracksListeners_"] = null;
20784      this.proxyNativeTracksForType_(lowerCaseType);
20785    }
20786    /**
20787     * Attempt to force override of native audio tracks.
20788     *
20789     * @param {boolean} override - If set to true native audio will be overridden,
20790     * otherwise native audio will potentially be used.
20791     */
20792    ;
20793
20794    _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) {
20795      this.overrideNative_('Audio', override);
20796    }
20797    /**
20798     * Attempt to force override of native video tracks.
20799     *
20800     * @param {boolean} override - If set to true native video will be overridden,
20801     * otherwise native video will potentially be used.
20802     */
20803    ;
20804
20805    _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) {
20806      this.overrideNative_('Video', override);
20807    }
20808    /**
20809     * Proxy native track list events for the given type to our track
20810     * lists if the browser we are playing in supports that type of track list.
20811     *
20812     * @param {string} name - Track type; values include 'audio', 'video', and 'text'
20813     * @private
20814     */
20815    ;
20816
20817    _proto.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) {
20818      var _this3 = this;
20819
20820      var props = NORMAL[name];
20821      var elTracks = this.el()[props.getterName];
20822      var techTracks = this[props.getterName]();
20823
20824      if (!this["featuresNative" + props.capitalName + "Tracks"] || !elTracks || !elTracks.addEventListener) {
20825        return;
20826      }
20827
20828      var listeners = {
20829        change: function change(e) {
20830          techTracks.trigger({
20831            type: 'change',
20832            target: techTracks,
20833            currentTarget: techTracks,
20834            srcElement: techTracks
20835          });
20836        },
20837        addtrack: function addtrack(e) {
20838          techTracks.addTrack(e.track);
20839        },
20840        removetrack: function removetrack(e) {
20841          techTracks.removeTrack(e.track);
20842        }
20843      };
20844
20845      var removeOldTracks = function removeOldTracks() {
20846        var removeTracks = [];
20847
20848        for (var i = 0; i < techTracks.length; i++) {
20849          var found = false;
20850
20851          for (var j = 0; j < elTracks.length; j++) {
20852            if (elTracks[j] === techTracks[i]) {
20853              found = true;
20854              break;
20855            }
20856          }
20857
20858          if (!found) {
20859            removeTracks.push(techTracks[i]);
20860          }
20861        }
20862
20863        while (removeTracks.length) {
20864          techTracks.removeTrack(removeTracks.shift());
20865        }
20866      };
20867
20868      this[props.getterName + 'Listeners_'] = listeners;
20869      Object.keys(listeners).forEach(function (eventName) {
20870        var listener = listeners[eventName];
20871        elTracks.addEventListener(eventName, listener);
20872
20873        _this3.on('dispose', function (e) {
20874          return elTracks.removeEventListener(eventName, listener);
20875        });
20876      }); // Remove (native) tracks that are not used anymore
20877
20878      this.on('loadstart', removeOldTracks);
20879      this.on('dispose', function (e) {
20880        return _this3.off('loadstart', removeOldTracks);
20881      });
20882    }
20883    /**
20884     * Proxy all native track list events to our track lists if the browser we are playing
20885     * in supports that type of track list.
20886     *
20887     * @private
20888     */
20889    ;
20890
20891    _proto.proxyNativeTracks_ = function proxyNativeTracks_() {
20892      var _this4 = this;
20893
20894      NORMAL.names.forEach(function (name) {
20895        _this4.proxyNativeTracksForType_(name);
20896      });
20897    }
20898    /**
20899     * Create the `Html5` Tech's DOM element.
20900     *
20901     * @return {Element}
20902     *         The element that gets created.
20903     */
20904    ;
20905
20906    _proto.createEl = function createEl$$1() {
20907      var el = this.options_.tag; // Check if this browser supports moving the element into the box.
20908      // On the iPhone video will break if you move the element,
20909      // So we have to create a brand new element.
20910      // If we ingested the player div, we do not need to move the media element.
20911
20912      if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
20913        // If the original tag is still there, clone and remove it.
20914        if (el) {
20915          var clone = el.cloneNode(true);
20916
20917          if (el.parentNode) {
20918            el.parentNode.insertBefore(clone, el);
20919          }
20920
20921          Html5.disposeMediaElement(el);
20922          el = clone;
20923        } else {
20924          el = document.createElement('video'); // determine if native controls should be used
20925
20926          var tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
20927          var attributes = mergeOptions({}, tagAttributes);
20928
20929          if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
20930            delete attributes.controls;
20931          }
20932
20933          setAttributes(el, assign(attributes, {
20934            id: this.options_.techId,
20935            class: 'vjs-tech'
20936          }));
20937        }
20938
20939        el.playerId = this.options_.playerId;
20940      }
20941
20942      if (typeof this.options_.preload !== 'undefined') {
20943        setAttribute(el, 'preload', this.options_.preload);
20944      } // Update specific tag settings, in case they were overridden
20945      // `autoplay` has to be *last* so that `muted` and `playsinline` are present
20946      // when iOS/Safari or other browsers attempt to autoplay.
20947
20948
20949      var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
20950
20951      for (var i = 0; i < settingsAttrs.length; i++) {
20952        var attr = settingsAttrs[i];
20953        var value = this.options_[attr];
20954
20955        if (typeof value !== 'undefined') {
20956          if (value) {
20957            setAttribute(el, attr, attr);
20958          } else {
20959            removeAttribute(el, attr);
20960          }
20961
20962          el[attr] = value;
20963        }
20964      }
20965
20966      return el;
20967    }
20968    /**
20969     * This will be triggered if the loadstart event has already fired, before videojs was
20970     * ready. Two known examples of when this can happen are:
20971     * 1. If we're loading the playback object after it has started loading
20972     * 2. The media is already playing the (often with autoplay on) then
20973     *
20974     * This function will fire another loadstart so that videojs can catchup.
20975     *
20976     * @fires Tech#loadstart
20977     *
20978     * @return {undefined}
20979     *         returns nothing.
20980     */
20981    ;
20982
20983    _proto.handleLateInit_ = function handleLateInit_(el) {
20984      if (el.networkState === 0 || el.networkState === 3) {
20985        // The video element hasn't started loading the source yet
20986        // or didn't find a source
20987        return;
20988      }
20989
20990      if (el.readyState === 0) {
20991        // NetworkState is set synchronously BUT loadstart is fired at the
20992        // end of the current stack, usually before setInterval(fn, 0).
20993        // So at this point we know loadstart may have already fired or is
20994        // about to fire, and either way the player hasn't seen it yet.
20995        // We don't want to fire loadstart prematurely here and cause a
20996        // double loadstart so we'll wait and see if it happens between now
20997        // and the next loop, and fire it if not.
20998        // HOWEVER, we also want to make sure it fires before loadedmetadata
20999        // which could also happen between now and the next loop, so we'll
21000        // watch for that also.
21001        var loadstartFired = false;
21002
21003        var setLoadstartFired = function setLoadstartFired() {
21004          loadstartFired = true;
21005        };
21006
21007        this.on('loadstart', setLoadstartFired);
21008
21009        var triggerLoadstart = function triggerLoadstart() {
21010          // We did miss the original loadstart. Make sure the player
21011          // sees loadstart before loadedmetadata
21012          if (!loadstartFired) {
21013            this.trigger('loadstart');
21014          }
21015        };
21016
21017        this.on('loadedmetadata', triggerLoadstart);
21018        this.ready(function () {
21019          this.off('loadstart', setLoadstartFired);
21020          this.off('loadedmetadata', triggerLoadstart);
21021
21022          if (!loadstartFired) {
21023            // We did miss the original native loadstart. Fire it now.
21024            this.trigger('loadstart');
21025          }
21026        });
21027        return;
21028      } // From here on we know that loadstart already fired and we missed it.
21029      // The other readyState events aren't as much of a problem if we double
21030      // them, so not going to go to as much trouble as loadstart to prevent
21031      // that unless we find reason to.
21032
21033
21034      var eventsToTrigger = ['loadstart']; // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
21035
21036      eventsToTrigger.push('loadedmetadata'); // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
21037
21038      if (el.readyState >= 2) {
21039        eventsToTrigger.push('loadeddata');
21040      } // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
21041
21042
21043      if (el.readyState >= 3) {
21044        eventsToTrigger.push('canplay');
21045      } // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
21046
21047
21048      if (el.readyState >= 4) {
21049        eventsToTrigger.push('canplaythrough');
21050      } // We still need to give the player time to add event listeners
21051
21052
21053      this.ready(function () {
21054        eventsToTrigger.forEach(function (type) {
21055          this.trigger(type);
21056        }, this);
21057      });
21058    }
21059    /**
21060     * Set current time for the `HTML5` tech.
21061     *
21062     * @param {number} seconds
21063     *        Set the current time of the media to this.
21064     */
21065    ;
21066
21067    _proto.setCurrentTime = function setCurrentTime(seconds) {
vendor: 11,918 bytes, lines 21068-21458
21068      try {
21069        this.el_.currentTime = seconds;
21070      } catch (e) {
21071        log(e, 'Video is not ready. (Video.js)'); // this.warning(VideoJS.warnings.videoNotReady);
21072      }
21073    }
21074    /**
21075     * Get the current duration of the HTML5 media element.
21076     *
21077     * @return {number}
21078     *         The duration of the media or 0 if there is no duration.
21079     */
21080    ;
21081
21082    _proto.duration = function duration() {
21083      var _this5 = this;
21084
21085      // Android Chrome will report duration as Infinity for VOD HLS until after
21086      // playback has started, which triggers the live display erroneously.
21087      // Return NaN if playback has not started and trigger a durationupdate once
21088      // the duration can be reliably known.
21089      if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
21090        // Wait for the first `timeupdate` with currentTime > 0 - there may be
21091        // several with 0
21092        var checkProgress = function checkProgress() {
21093          if (_this5.el_.currentTime > 0) {
21094            // Trigger durationchange for genuinely live video
21095            if (_this5.el_.duration === Infinity) {
21096              _this5.trigger('durationchange');
21097            }
21098
21099            _this5.off('timeupdate', checkProgress);
21100          }
21101        };
21102
21103        this.on('timeupdate', checkProgress);
21104        return NaN;
21105      }
21106
21107      return this.el_.duration || NaN;
21108    }
21109    /**
21110     * Get the current width of the HTML5 media element.
21111     *
21112     * @return {number}
21113     *         The width of the HTML5 media element.
21114     */
21115    ;
21116
21117    _proto.width = function width() {
21118      return this.el_.offsetWidth;
21119    }
21120    /**
21121     * Get the current height of the HTML5 media element.
21122     *
21123     * @return {number}
21124     *         The height of the HTML5 media element.
21125     */
21126    ;
21127
21128    _proto.height = function height() {
21129      return this.el_.offsetHeight;
21130    }
21131    /**
21132     * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
21133     * `fullscreenchange` event.
21134     *
21135     * @private
21136     * @fires fullscreenchange
21137     * @listens webkitendfullscreen
21138     * @listens webkitbeginfullscreen
21139     * @listens webkitbeginfullscreen
21140     */
21141    ;
21142
21143    _proto.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() {
21144      var _this6 = this;
21145
21146      if (!('webkitDisplayingFullscreen' in this.el_)) {
21147        return;
21148      }
21149
21150      var endFn = function endFn() {
21151        this.trigger('fullscreenchange', {
21152          isFullscreen: false
21153        });
21154      };
21155
21156      var beginFn = function beginFn() {
21157        if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
21158          this.one('webkitendfullscreen', endFn);
21159          this.trigger('fullscreenchange', {
21160            isFullscreen: true
21161          });
21162        }
21163      };
21164
21165      this.on('webkitbeginfullscreen', beginFn);
21166      this.on('dispose', function () {
21167        _this6.off('webkitbeginfullscreen', beginFn);
21168
21169        _this6.off('webkitendfullscreen', endFn);
21170      });
21171    }
21172    /**
21173     * Check if fullscreen is supported on the current playback device.
21174     *
21175     * @return {boolean}
21176     *         - True if fullscreen is supported.
21177     *         - False if fullscreen is not supported.
21178     */
21179    ;
21180
21181    _proto.supportsFullScreen = function supportsFullScreen() {
21182      if (typeof this.el_.webkitEnterFullScreen === 'function') {
21183        var userAgent = window$1.navigator && window$1.navigator.userAgent || ''; // Seems to be broken in Chromium/Chrome && Safari in Leopard
21184
21185        if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
21186          return true;
21187        }
21188      }
21189
21190      return false;
21191    }
21192    /**
21193     * Request that the `HTML5` Tech enter fullscreen.
21194     */
21195    ;
21196
21197    _proto.enterFullScreen = function enterFullScreen() {
21198      var video = this.el_;
21199
21200      if (video.paused && video.networkState <= video.HAVE_METADATA) {
21201        // attempt to prime the video element for programmatic access
21202        // this isn't necessary on the desktop but shouldn't hurt
21203        this.el_.play(); // playing and pausing synchronously during the transition to fullscreen
21204        // can get iOS ~6.1 devices into a play/pause loop
21205
21206        this.setTimeout(function () {
21207          video.pause();
21208          video.webkitEnterFullScreen();
21209        }, 0);
21210      } else {
21211        video.webkitEnterFullScreen();
21212      }
21213    }
21214    /**
21215     * Request that the `HTML5` Tech exit fullscreen.
21216     */
21217    ;
21218
21219    _proto.exitFullScreen = function exitFullScreen() {
21220      this.el_.webkitExitFullScreen();
21221    }
21222    /**
21223     * A getter/setter for the `Html5` Tech's source object.
21224     * > Note: Please use {@link Html5#setSource}
21225     *
21226     * @param {Tech~SourceObject} [src]
21227     *        The source object you want to set on the `HTML5` techs element.
21228     *
21229     * @return {Tech~SourceObject|undefined}
21230     *         - The current source object when a source is not passed in.
21231     *         - undefined when setting
21232     *
21233     * @deprecated Since version 5.
21234     */
21235    ;
21236
21237    _proto.src = function src(_src) {
21238      if (_src === undefined) {
21239        return this.el_.src;
21240      } // Setting src through `src` instead of `setSrc` will be deprecated
21241
21242
21243      this.setSrc(_src);
21244    }
21245    /**
21246     * Reset the tech by removing all sources and then calling
21247     * {@link Html5.resetMediaElement}.
21248     */
21249    ;
21250
21251    _proto.reset = function reset() {
21252      Html5.resetMediaElement(this.el_);
21253    }
21254    /**
21255     * Get the current source on the `HTML5` Tech. Falls back to returning the source from
21256     * the HTML5 media element.
21257     *
21258     * @return {Tech~SourceObject}
21259     *         The current source object from the HTML5 tech. With a fallback to the
21260     *         elements source.
21261     */
21262    ;
21263
21264    _proto.currentSrc = function currentSrc() {
21265      if (this.currentSource_) {
21266        return this.currentSource_.src;
21267      }
21268
21269      return this.el_.currentSrc;
21270    }
21271    /**
21272     * Set controls attribute for the HTML5 media Element.
21273     *
21274     * @param {string} val
21275     *        Value to set the controls attribute to
21276     */
21277    ;
21278
21279    _proto.setControls = function setControls(val) {
21280      this.el_.controls = !!val;
21281    }
21282    /**
21283     * Create and returns a remote {@link TextTrack} object.
21284     *
21285     * @param {string} kind
21286     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
21287     *
21288     * @param {string} [label]
21289     *        Label to identify the text track
21290     *
21291     * @param {string} [language]
21292     *        Two letter language abbreviation
21293     *
21294     * @return {TextTrack}
21295     *         The TextTrack that gets created.
21296     */
21297    ;
21298
21299    _proto.addTextTrack = function addTextTrack(kind, label, language) {
21300      if (!this.featuresNativeTextTracks) {
21301        return _Tech.prototype.addTextTrack.call(this, kind, label, language);
21302      }
21303
21304      return this.el_.addTextTrack(kind, label, language);
21305    }
21306    /**
21307     * Creates either native TextTrack or an emulated TextTrack depending
21308     * on the value of `featuresNativeTextTracks`
21309     *
21310     * @param {Object} options
21311     *        The object should contain the options to initialize the TextTrack with.
21312     *
21313     * @param {string} [options.kind]
21314     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
21315     *
21316     * @param {string} [options.label]
21317     *        Label to identify the text track
21318     *
21319     * @param {string} [options.language]
21320     *        Two letter language abbreviation.
21321     *
21322     * @param {boolean} [options.default]
21323     *        Default this track to on.
21324     *
21325     * @param {string} [options.id]
21326     *        The internal id to assign this track.
21327     *
21328     * @param {string} [options.src]
21329     *        A source url for the track.
21330     *
21331     * @return {HTMLTrackElement}
21332     *         The track element that gets created.
21333     */
21334    ;
21335
21336    _proto.createRemoteTextTrack = function createRemoteTextTrack(options) {
21337      if (!this.featuresNativeTextTracks) {
21338        return _Tech.prototype.createRemoteTextTrack.call(this, options);
21339      }
21340
21341      var htmlTrackElement = document.createElement('track');
21342
21343      if (options.kind) {
21344        htmlTrackElement.kind = options.kind;
21345      }
21346
21347      if (options.label) {
21348        htmlTrackElement.label = options.label;
21349      }
21350
21351      if (options.language || options.srclang) {
21352        htmlTrackElement.srclang = options.language || options.srclang;
21353      }
21354
21355      if (options.default) {
21356        htmlTrackElement.default = options.default;
21357      }
21358
21359      if (options.id) {
21360        htmlTrackElement.id = options.id;
21361      }
21362
21363      if (options.src) {
21364        htmlTrackElement.src = options.src;
21365      }
21366
21367      return htmlTrackElement;
21368    }
21369    /**
21370     * Creates a remote text track object and returns an html track element.
21371     *
21372     * @param {Object} options The object should contain values for
21373     * kind, language, label, and src (location of the WebVTT file)
21374     * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
21375     * automatically removed from the video element whenever the source changes
21376     * @return {HTMLTrackElement} An Html Track Element.
21377     * This can be an emulated {@link HTMLTrackElement} or a native one.
21378     * @deprecated The default value of the "manualCleanup" parameter will default
21379     * to "false" in upcoming versions of Video.js
21380     */
21381    ;
21382
21383    _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
21384      var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup);
21385
21386      if (this.featuresNativeTextTracks) {
21387        this.el().appendChild(htmlTrackElement);
21388      }
21389
21390      return htmlTrackElement;
21391    }
21392    /**
21393     * Remove remote `TextTrack` from `TextTrackList` object
21394     *
21395     * @param {TextTrack} track
21396     *        `TextTrack` object to remove
21397     */
21398    ;
21399
21400    _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
21401      _Tech.prototype.removeRemoteTextTrack.call(this, track);
21402
21403      if (this.featuresNativeTextTracks) {
21404        var tracks = this.$$('track');
21405        var i = tracks.length;
21406
21407        while (i--) {
21408          if (track === tracks[i] || track === tracks[i].track) {
21409            this.el().removeChild(tracks[i]);
21410          }
21411        }
21412      }
21413    }
21414    /**
21415     * Gets available media playback quality metrics as specified by the W3C's Media
21416     * Playback Quality API.
21417     *
21418     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
21419     *
21420     * @return {Object}
21421     *         An object with supported media playback quality metrics
21422     */
21423    ;
21424
21425    _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
21426      if (typeof this.el().getVideoPlaybackQuality === 'function') {
21427        return this.el().getVideoPlaybackQuality();
21428      }
21429
21430      var videoPlaybackQuality = {};
21431
21432      if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
21433        videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
21434        videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
21435      }
21436
21437      if (window$1.performance && typeof window$1.performance.now === 'function') {
21438        videoPlaybackQuality.creationTime = window$1.performance.now();
21439      } else if (window$1.performance && window$1.performance.timing && typeof window$1.performance.timing.navigationStart === 'number') {
21440        videoPlaybackQuality.creationTime = window$1.Date.now() - window$1.performance.timing.navigationStart;
21441      }
21442
21443      return videoPlaybackQuality;
21444    };
21445
21446    return Html5;
21447  }(Tech);
21448  /* HTML5 Support Testing ---------------------------------------------------- */
21449
21450
21451  if (isReal()) {
21452    /**
21453     * Element for testing browser HTML5 media capabilities
21454     *
21455     * @type {Element}
21456     * @constant
21457     * @private
21458     */
vendor: 4,434 bytes, lines 21459-21619
21459    Html5.TEST_VID = document.createElement('video');
21460    var track = document.createElement('track');
21461    track.kind = 'captions';
21462    track.srclang = 'en';
21463    track.label = 'English';
21464    Html5.TEST_VID.appendChild(track);
21465  }
21466  /**
21467   * Check if HTML5 media is supported by this browser/device.
21468   *
21469   * @return {boolean}
21470   *         - True if HTML5 media is supported.
21471   *         - False if HTML5 media is not supported.
21472   */
21473
21474
21475  Html5.isSupported = function () {
21476    // IE with no Media Player is a LIAR! (#984)
21477    try {
21478      Html5.TEST_VID.volume = 0.5;
21479    } catch (e) {
21480      return false;
21481    }
21482
21483    return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
21484  };
21485  /**
21486   * Check if the tech can support the given type
21487   *
21488   * @param {string} type
21489   *        The mimetype to check
21490   * @return {string} 'probably', 'maybe', or '' (empty string)
21491   */
21492
21493
21494  Html5.canPlayType = function (type) {
21495    return Html5.TEST_VID.canPlayType(type);
21496  };
21497  /**
21498   * Check if the tech can support the given source
21499   *
21500   * @param {Object} srcObj
21501   *        The source object
21502   * @param {Object} options
21503   *        The options passed to the tech
21504   * @return {string} 'probably', 'maybe', or '' (empty string)
21505   */
21506
21507
21508  Html5.canPlaySource = function (srcObj, options) {
21509    return Html5.canPlayType(srcObj.type);
21510  };
21511  /**
21512   * Check if the volume can be changed in this browser/device.
21513   * Volume cannot be changed in a lot of mobile devices.
21514   * Specifically, it can't be changed from 1 on iOS.
21515   *
21516   * @return {boolean}
21517   *         - True if volume can be controlled
21518   *         - False otherwise
21519   */
21520
21521
21522  Html5.canControlVolume = function () {
21523    // IE will error if Windows Media Player not installed #3315
21524    try {
21525      var volume = Html5.TEST_VID.volume;
21526      Html5.TEST_VID.volume = volume / 2 + 0.1;
21527      return volume !== Html5.TEST_VID.volume;
21528    } catch (e) {
21529      return false;
21530    }
21531  };
21532  /**
21533   * Check if the volume can be muted in this browser/device.
21534   * Some devices, e.g. iOS, don't allow changing volume
21535   * but permits muting/unmuting.
21536   *
21537   * @return {bolean}
21538   *      - True if volume can be muted
21539   *      - False otherwise
21540   */
21541
21542
21543  Html5.canMuteVolume = function () {
21544    try {
21545      var muted = Html5.TEST_VID.muted; // in some versions of iOS muted property doesn't always
21546      // work, so we want to set both property and attribute
21547
21548      Html5.TEST_VID.muted = !muted;
21549
21550      if (Html5.TEST_VID.muted) {
21551        setAttribute(Html5.TEST_VID, 'muted', 'muted');
21552      } else {
21553        removeAttribute(Html5.TEST_VID, 'muted', 'muted');
21554      }
21555
21556      return muted !== Html5.TEST_VID.muted;
21557    } catch (e) {
21558      return false;
21559    }
21560  };
21561  /**
21562   * Check if the playback rate can be changed in this browser/device.
21563   *
21564   * @return {boolean}
21565   *         - True if playback rate can be controlled
21566   *         - False otherwise
21567   */
21568
21569
21570  Html5.canControlPlaybackRate = function () {
21571    // Playback rate API is implemented in Android Chrome, but doesn't do anything
21572    // https://github.com/videojs/video.js/issues/3180
21573    if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
21574      return false;
21575    } // IE will error if Windows Media Player not installed #3315
21576
21577
21578    try {
21579      var playbackRate = Html5.TEST_VID.playbackRate;
21580      Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
21581      return playbackRate !== Html5.TEST_VID.playbackRate;
21582    } catch (e) {
21583      return false;
21584    }
21585  };
21586  /**
21587   * Check if we can override a video/audio elements attributes, with
21588   * Object.defineProperty.
21589   *
21590   * @return {boolean}
21591   *         - True if builtin attributes can be overridden
21592   *         - False otherwise
21593   */
21594
21595
21596  Html5.canOverrideAttributes = function () {
21597    // if we cannot overwrite the src/innerHTML property, there is no support
21598    // iOS 7 safari for instance cannot do this.
21599    try {
21600      var noop = function noop() {};
21601
21602      Object.defineProperty(document.createElement('video'), 'src', {
21603        get: noop,
21604        set: noop
21605      });
21606      Object.defineProperty(document.createElement('audio'), 'src', {
21607        get: noop,
21608        set: noop
21609      });
21610      Object.defineProperty(document.createElement('video'), 'innerHTML', {
21611        get: noop,
21612        set: noop
21613      });
21614      Object.defineProperty(document.createElement('audio'), 'innerHTML', {
21615        get: noop,
21616        set: noop
21617      });
21618    } catch (e) {
21619      return false;
21620    }
21621
21622    return true;
21623  };
21624  /**
21625   * Check to see if native `TextTrack`s are supported by this browser/device.
21626   *
21627   * @return {boolean}
21628   *         - True if native `TextTrack`s are supported.
21629   *         - False otherwise
21630   */
21631
21632
21633  Html5.supportsNativeTextTracks = function () {
21634    return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
21635  };
21636  /**
21637   * Check to see if native `VideoTrack`s are supported by this browser/device
21638   *
21639   * @return {boolean}
21640   *        - True if native `VideoTrack`s are supported.
21641   *        - False otherwise
21642   */
21643
21644
21645  Html5.supportsNativeVideoTracks = function () {
21646    return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
21647  };
21648  /**
21649   * Check to see if native `AudioTrack`s are supported by this browser/device
21650   *
21651   * @return {boolean}
21652   *        - True if native `AudioTrack`s are supported.
21653   *        - False otherwise
21654   */
21655
21656
21657  Html5.supportsNativeAudioTracks = function () {
21658    return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
21659  };
21660  /**
21661   * An array of events available on the Html5 tech.
21662   *
21663   * @private
21664   * @type {Array}
21665   */
21666
21667
21668  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'];
21669  /**
21670   * Boolean indicating whether the `Tech` supports volume control.
21671   *
21672   * @type {boolean}
21673   * @default {@link Html5.canControlVolume}
21674   */
21675
21676  Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
21677  /**
21678   * Boolean indicating whether the `Tech` supports muting volume.
21679   *
21680   * @type {bolean}
21681   * @default {@link Html5.canMuteVolume}
21682   */
21683
21684  Html5.prototype.featuresMuteControl = Html5.canMuteVolume();
21685  /**
21686   * Boolean indicating whether the `Tech` supports changing the speed at which the media
21687   * plays. Examples:
21688   *   - Set player to play 2x (twice) as fast
21689   *   - Set player to play 0.5x (half) as fast
21690   *
21691   * @type {boolean}
21692   * @default {@link Html5.canControlPlaybackRate}
21693   */
21694
21695  Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate();
21696  /**
21697   * Boolean indicating whether the `Tech` supports the `sourceset` event.
21698   *
21699   * @type {boolean}
21700   * @default
21701   */
21702
21703  Html5.prototype.featuresSourceset = Html5.canOverrideAttributes();
21704  /**
21705   * Boolean indicating whether the `HTML5` tech currently supports the media element
21706   * moving in the DOM. iOS breaks if you move the media element, so this is set this to
21707   * false there. Everywhere else this should be true.
21708   *
21709   * @type {boolean}
21710   * @default
21711   */
21712
21713  Html5.prototype.movingMediaElementInDOM = !IS_IOS; // TODO: Previous comment: No longer appears to be used. Can probably be removed.
21714  //       Is this true?
21715
21716  /**
21717   * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
21718   * when going into fullscreen.
21719   *
21720   * @type {boolean}
21721   * @default
21722   */
21723
21724  Html5.prototype.featuresFullscreenResize = true;
21725  /**
21726   * Boolean indicating whether the `HTML5` tech currently supports the progress event.
21727   * If this is false, manual `progress` events will be triggered instead.
21728   *
21729   * @type {boolean}
21730   * @default
21731   */
21732
21733  Html5.prototype.featuresProgressEvents = true;
21734  /**
21735   * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
21736   * If this is false, manual `timeupdate` events will be triggered instead.
21737   *
21738   * @default
21739   */
21740
21741  Html5.prototype.featuresTimeupdateEvents = true;
21742  /**
21743   * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
21744   *
21745   * @type {boolean}
21746   * @default {@link Html5.supportsNativeTextTracks}
21747   */
21748
21749  Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks();
21750  /**
21751   * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
21752   *
21753   * @type {boolean}
21754   * @default {@link Html5.supportsNativeVideoTracks}
21755   */
21756
21757  Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks();
21758  /**
21759   * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
21760   *
21761   * @type {boolean}
21762   * @default {@link Html5.supportsNativeAudioTracks}
21763   */
21764
21765  Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks(); // HTML5 Feature detection and Device Fixes --------------------------------- //
21766
vendor: 14,581 bytes, lines 21767-22157
21767  var canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType;
21768  var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
21769
21770  Html5.patchCanPlayType = function () {
21771    // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
21772    // Firefox and Chrome report correctly
21773    if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX && !IS_CHROME) {
21774      Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
21775        if (type && mpegurlRE.test(type)) {
21776          return 'maybe';
21777        }
21778
21779        return canPlayType.call(this, type);
21780      };
21781    }
21782  };
21783
21784  Html5.unpatchCanPlayType = function () {
21785    var r = Html5.TEST_VID.constructor.prototype.canPlayType;
21786    Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
21787    return r;
21788  }; // by default, patch the media element
21789
21790
21791  Html5.patchCanPlayType();
21792
21793  Html5.disposeMediaElement = function (el) {
21794    if (!el) {
21795      return;
21796    }
21797
21798    if (el.parentNode) {
21799      el.parentNode.removeChild(el);
21800    } // remove any child track or source nodes to prevent their loading
21801
21802
21803    while (el.hasChildNodes()) {
21804      el.removeChild(el.firstChild);
21805    } // remove any src reference. not setting `src=''` because that causes a warning
21806    // in firefox
21807
21808
21809    el.removeAttribute('src'); // force the media element to update its loading state by calling load()
21810    // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
21811
21812    if (typeof el.load === 'function') {
21813      // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
21814      (function () {
21815        try {
21816          el.load();
21817        } catch (e) {// not supported
21818        }
21819      })();
21820    }
21821  };
21822
21823  Html5.resetMediaElement = function (el) {
21824    if (!el) {
21825      return;
21826    }
21827
21828    var sources = el.querySelectorAll('source');
21829    var i = sources.length;
21830
21831    while (i--) {
21832      el.removeChild(sources[i]);
21833    } // remove any src reference.
21834    // not setting `src=''` because that throws an error
21835
21836
21837    el.removeAttribute('src');
21838
21839    if (typeof el.load === 'function') {
21840      // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
21841      (function () {
21842        try {
21843          el.load();
21844        } catch (e) {// satisfy linter
21845        }
21846      })();
21847    }
21848  };
21849  /* Native HTML5 element property wrapping ----------------------------------- */
21850  // Wrap native boolean attributes with getters that check both property and attribute
21851  // The list is as followed:
21852  // muted, defaultMuted, autoplay, controls, loop, playsinline
21853
21854
21855  [
21856  /**
21857   * Get the value of `muted` from the media element. `muted` indicates
21858   * that the volume for the media should be set to silent. This does not actually change
21859   * the `volume` attribute.
21860   *
21861   * @method Html5#muted
21862   * @return {boolean}
21863   *         - True if the value of `volume` should be ignored and the audio set to silent.
21864   *         - False if the value of `volume` should be used.
21865   *
21866   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
21867   */
21868  'muted',
21869  /**
21870   * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
21871   * whether the media should start muted or not. Only changes the default state of the
21872   * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
21873   * current state.
21874   *
21875   * @method Html5#defaultMuted
21876   * @return {boolean}
21877   *         - The value of `defaultMuted` from the media element.
21878   *         - True indicates that the media should start muted.
21879   *         - False indicates that the media should not start muted
21880   *
21881   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
21882   */
21883  'defaultMuted',
21884  /**
21885   * Get the value of `autoplay` from the media element. `autoplay` indicates
21886   * that the media should start to play as soon as the page is ready.
21887   *
21888   * @method Html5#autoplay
21889   * @return {boolean}
21890   *         - The value of `autoplay` from the media element.
21891   *         - True indicates that the media should start as soon as the page loads.
21892   *         - False indicates that the media should not start as soon as the page loads.
21893   *
21894   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
21895   */
21896  'autoplay',
21897  /**
21898   * Get the value of `controls` from the media element. `controls` indicates
21899   * whether the native media controls should be shown or hidden.
21900   *
21901   * @method Html5#controls
21902   * @return {boolean}
21903   *         - The value of `controls` from the media element.
21904   *         - True indicates that native controls should be showing.
21905   *         - False indicates that native controls should be hidden.
21906   *
21907   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
21908   */
21909  'controls',
21910  /**
21911   * Get the value of `loop` from the media element. `loop` indicates
21912   * that the media should return to the start of the media and continue playing once
21913   * it reaches the end.
21914   *
21915   * @method Html5#loop
21916   * @return {boolean}
21917   *         - The value of `loop` from the media element.
21918   *         - True indicates that playback should seek back to start once
21919   *           the end of a media is reached.
21920   *         - False indicates that playback should not loop back to the start when the
21921   *           end of the media is reached.
21922   *
21923   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
21924   */
21925  'loop',
21926  /**
21927   * Get the value of `playsinline` from the media element. `playsinline` indicates
21928   * to the browser that non-fullscreen playback is preferred when fullscreen
21929   * playback is the native default, such as in iOS Safari.
21930   *
21931   * @method Html5#playsinline
21932   * @return {boolean}
21933   *         - The value of `playsinline` from the media element.
21934   *         - True indicates that the media should play inline.
21935   *         - False indicates that the media should not play inline.
21936   *
21937   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
21938   */
21939  'playsinline'].forEach(function (prop) {
21940    Html5.prototype[prop] = function () {
21941      return this.el_[prop] || this.el_.hasAttribute(prop);
21942    };
21943  }); // Wrap native boolean attributes with setters that set both property and attribute
21944  // The list is as followed:
21945  // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
21946  // setControls is special-cased above
21947
21948  [
21949  /**
21950   * Set the value of `muted` on the media element. `muted` indicates that the current
21951   * audio level should be silent.
21952   *
21953   * @method Html5#setMuted
21954   * @param {boolean} muted
21955   *        - True if the audio should be set to silent
21956   *        - False otherwise
21957   *
21958   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
21959   */
21960  'muted',
21961  /**
21962   * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
21963   * audio level should be silent, but will only effect the muted level on intial playback..
21964   *
21965   * @method Html5.prototype.setDefaultMuted
21966   * @param {boolean} defaultMuted
21967   *        - True if the audio should be set to silent
21968   *        - False otherwise
21969   *
21970   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
21971   */
21972  'defaultMuted',
21973  /**
21974   * Set the value of `autoplay` on the media element. `autoplay` indicates
21975   * that the media should start to play as soon as the page is ready.
21976   *
21977   * @method Html5#setAutoplay
21978   * @param {boolean} autoplay
21979   *         - True indicates that the media should start as soon as the page loads.
21980   *         - False indicates that the media should not start as soon as the page loads.
21981   *
21982   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
21983   */
21984  'autoplay',
21985  /**
21986   * Set the value of `loop` on the media element. `loop` indicates
21987   * that the media should return to the start of the media and continue playing once
21988   * it reaches the end.
21989   *
21990   * @method Html5#setLoop
21991   * @param {boolean} loop
21992   *         - True indicates that playback should seek back to start once
21993   *           the end of a media is reached.
21994   *         - False indicates that playback should not loop back to the start when the
21995   *           end of the media is reached.
21996   *
21997   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
21998   */
21999  'loop',
22000  /**
22001   * Set the value of `playsinline` from the media element. `playsinline` indicates
22002   * to the browser that non-fullscreen playback is preferred when fullscreen
22003   * playback is the native default, such as in iOS Safari.
22004   *
22005   * @method Html5#setPlaysinline
22006   * @param {boolean} playsinline
22007   *         - True indicates that the media should play inline.
22008   *         - False indicates that the media should not play inline.
22009   *
22010   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
22011   */
22012  'playsinline'].forEach(function (prop) {
22013    Html5.prototype['set' + toTitleCase(prop)] = function (v) {
22014      this.el_[prop] = v;
22015
22016      if (v) {
22017        this.el_.setAttribute(prop, prop);
22018      } else {
22019        this.el_.removeAttribute(prop);
22020      }
22021    };
22022  }); // Wrap native properties with a getter
22023  // The list is as followed
22024  // paused, currentTime, buffered, volume, poster, preload, error, seeking
22025  // seekable, ended, playbackRate, defaultPlaybackRate, played, networkState
22026  // readyState, videoWidth, videoHeight
22027
22028  [
22029  /**
22030   * Get the value of `paused` from the media element. `paused` indicates whether the media element
22031   * is currently paused or not.
22032   *
22033   * @method Html5#paused
22034   * @return {boolean}
22035   *         The value of `paused` from the media element.
22036   *
22037   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
22038   */
22039  'paused',
22040  /**
22041   * Get the value of `currentTime` from the media element. `currentTime` indicates
22042   * the current second that the media is at in playback.
22043   *
22044   * @method Html5#currentTime
22045   * @return {number}
22046   *         The value of `currentTime` from the media element.
22047   *
22048   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
22049   */
22050  'currentTime',
22051  /**
22052   * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
22053   * object that represents the parts of the media that are already downloaded and
22054   * available for playback.
22055   *
22056   * @method Html5#buffered
22057   * @return {TimeRange}
22058   *         The value of `buffered` from the media element.
22059   *
22060   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
22061   */
22062  'buffered',
22063  /**
22064   * Get the value of `volume` from the media element. `volume` indicates
22065   * the current playback volume of audio for a media. `volume` will be a value from 0
22066   * (silent) to 1 (loudest and default).
22067   *
22068   * @method Html5#volume
22069   * @return {number}
22070   *         The value of `volume` from the media element. Value will be between 0-1.
22071   *
22072   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
22073   */
22074  'volume',
22075  /**
22076   * Get the value of `poster` from the media element. `poster` indicates
22077   * that the url of an image file that can/will be shown when no media data is available.
22078   *
22079   * @method Html5#poster
22080   * @return {string}
22081   *         The value of `poster` from the media element. Value will be a url to an
22082   *         image.
22083   *
22084   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
22085   */
22086  'poster',
22087  /**
22088   * Get the value of `preload` from the media element. `preload` indicates
22089   * what should download before the media is interacted with. It can have the following
22090   * values:
22091   * - none: nothing should be downloaded
22092   * - metadata: poster and the first few frames of the media may be downloaded to get
22093   *   media dimensions and other metadata
22094   * - auto: allow the media and metadata for the media to be downloaded before
22095   *    interaction
22096   *
22097   * @method Html5#preload
22098   * @return {string}
22099   *         The value of `preload` from the media element. Will be 'none', 'metadata',
22100   *         or 'auto'.
22101   *
22102   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
22103   */
22104  'preload',
22105  /**
22106   * Get the value of the `error` from the media element. `error` indicates any
22107   * MediaError that may have occurred during playback. If error returns null there is no
22108   * current error.
22109   *
22110   * @method Html5#error
22111   * @return {MediaError|null}
22112   *         The value of `error` from the media element. Will be `MediaError` if there
22113   *         is a current error and null otherwise.
22114   *
22115   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
22116   */
22117  'error',
22118  /**
22119   * Get the value of `seeking` from the media element. `seeking` indicates whether the
22120   * media is currently seeking to a new position or not.
22121   *
22122   * @method Html5#seeking
22123   * @return {boolean}
22124   *         - The value of `seeking` from the media element.
22125   *         - True indicates that the media is currently seeking to a new position.
22126   *         - False indicates that the media is not seeking to a new position at this time.
22127   *
22128   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
22129   */
22130  'seeking',
22131  /**
22132   * Get the value of `seekable` from the media element. `seekable` returns a
22133   * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
22134   *
22135   * @method Html5#seekable
22136   * @return {TimeRange}
22137   *         The value of `seekable` from the media element. A `TimeRange` object
22138   *         indicating the current ranges of time that can be seeked to.
22139   *
22140   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
22141   */
22142  'seekable',
22143  /**
22144   * Get the value of `ended` from the media element. `ended` indicates whether
22145   * the media has reached the end or not.
22146   *
22147   * @method Html5#ended
22148   * @return {boolean}
22149   *         - The value of `ended` from the media element.
22150   *         - True indicates that the media has ended.
22151   *         - False indicates that the media has not ended.
22152   *
22153   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
22154   */
22155  'ended',
22156  /**
22157   * Get the value of `playbackRate` from the media element. `playbackRate` indicates
22158   * the rate at which the media is currently playing back. Examples:
22159   *   - if playbackRate is set to 2, media will play twice as fast.
22160   *   - if playbackRate is set to 0.5, media will play half as fast.
22161   *
22162   * @method Html5#playbackRate
22163   * @return {number}
22164   *         The value of `playbackRate` from the media element. A number indicating
22165   *         the current playback speed of the media, where 1 is normal speed.
22166   *
22167   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
22168   */
22169  'playbackRate',
22170  /**
22171   * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
22172   * the rate at which the media is currently playing back. This value will not indicate the current
22173   * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
22174   *
22175   * Examples:
22176   *   - if defaultPlaybackRate is set to 2, media will play twice as fast.
22177   *   - if defaultPlaybackRate is set to 0.5, media will play half as fast.
22178   *
22179   * @method Html5.prototype.defaultPlaybackRate
22180   * @return {number}
22181   *         The value of `defaultPlaybackRate` from the media element. A number indicating
22182   *         the current playback speed of the media, where 1 is normal speed.
22183   *
22184   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
22185   */
22186  'defaultPlaybackRate',
22187  /**
22188   * Get the value of `played` from the media element. `played` returns a `TimeRange`
22189   * object representing points in the media timeline that have been played.
22190   *
22191   * @method Html5#played
22192   * @return {TimeRange}
22193   *         The value of `played` from the media element. A `TimeRange` object indicating
22194   *         the ranges of time that have been played.
22195   *
22196   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
22197   */
22198  'played',
22199  /**
22200   * Get the value of `networkState` from the media element. `networkState` indicates
22201   * the current network state. It returns an enumeration from the following list:
22202   * - 0: NETWORK_EMPTY
22203   * - 1: NETWORK_IDLE
22204   * - 2: NETWORK_LOADING
22205   * - 3: NETWORK_NO_SOURCE
22206   *
22207   * @method Html5#networkState
22208   * @return {number}
22209   *         The value of `networkState` from the media element. This will be a number
22210   *         from the list in the description.
22211   *
22212   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
22213   */
22214  'networkState',
22215  /**
22216   * Get the value of `readyState` from the media element. `readyState` indicates
22217   * the current state of the media element. It returns an enumeration from the
22218   * following list:
22219   * - 0: HAVE_NOTHING
22220   * - 1: HAVE_METADATA
22221   * - 2: HAVE_CURRENT_DATA
22222   * - 3: HAVE_FUTURE_DATA
22223   * - 4: HAVE_ENOUGH_DATA
22224   *
22225   * @method Html5#readyState
22226   * @return {number}
22227   *         The value of `readyState` from the media element. This will be a number
22228   *         from the list in the description.
22229   *
22230   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
22231   */
22232  'readyState',
22233  /**
22234   * Get the value of `videoWidth` from the video element. `videoWidth` indicates
22235   * the current width of the video in css pixels.
22236   *
22237   * @method Html5#videoWidth
22238   * @return {number}
22239   *         The value of `videoWidth` from the video element. This will be a number
22240   *         in css pixels.
22241   *
22242   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
22243   */
22244  'videoWidth',
22245  /**
22246   * Get the value of `videoHeight` from the video element. `videoHeight` indicates
22247   * the current height of the video in css pixels.
22248   *
22249   * @method Html5#videoHeight
22250   * @return {number}
22251   *         The value of `videoHeight` from the video element. This will be a number
22252   *         in css pixels.
22253   *
22254   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
22255   */
22256  'videoHeight'].forEach(function (prop) {
22257    Html5.prototype[prop] = function () {
22258      return this.el_[prop];
22259    };
22260  }); // Wrap native properties with a setter in this format:
22261  // set + toTitleCase(name)
22262  // The list is as follows:
22263  // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate
22264
22265  [
22266  /**
22267   * Set the value of `volume` on the media element. `volume` indicates the current
22268   * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
22269   * so on.
22270   *
22271   * @method Html5#setVolume
22272   * @param {number} percentAsDecimal
22273   *        The volume percent as a decimal. Valid range is from 0-1.
22274   *
22275   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
22276   */
22277  'volume',
22278  /**
22279   * Set the value of `src` on the media element. `src` indicates the current
22280   * {@link Tech~SourceObject} for the media.
22281   *
22282   * @method Html5#setSrc
22283   * @param {Tech~SourceObject} src
22284   *        The source object to set as the current source.
22285   *
22286   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
22287   */
22288  'src',
22289  /**
22290   * Set the value of `poster` on the media element. `poster` is the url to
22291   * an image file that can/will be shown when no media data is available.
22292   *
22293   * @method Html5#setPoster
22294   * @param {string} poster
22295   *        The url to an image that should be used as the `poster` for the media
22296   *        element.
22297   *
22298   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
22299   */
22300  'poster',
22301  /**
22302   * Set the value of `preload` on the media element. `preload` indicates
22303   * what should download before the media is interacted with. It can have the following
22304   * values:
22305   * - none: nothing should be downloaded
22306   * - metadata: poster and the first few frames of the media may be downloaded to get
22307   *   media dimensions and other metadata
22308   * - auto: allow the media and metadata for the media to be downloaded before
22309   *    interaction
22310   *
22311   * @method Html5#setPreload
22312   * @param {string} preload
22313   *         The value of `preload` to set on the media element. Must be 'none', 'metadata',
22314   *         or 'auto'.
22315   *
22316   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
22317   */
22318  'preload',
22319  /**
22320   * Set the value of `playbackRate` on the media element. `playbackRate` indicates
22321   * the rate at which the media should play back. Examples:
22322   *   - if playbackRate is set to 2, media will play twice as fast.
22323   *   - if playbackRate is set to 0.5, media will play half as fast.
22324   *
22325   * @method Html5#setPlaybackRate
22326   * @return {number}
22327   *         The value of `playbackRate` from the media element. A number indicating
22328   *         the current playback speed of the media, where 1 is normal speed.
22329   *
22330   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
22331   */
22332  'playbackRate',
22333  /**
22334   * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
22335   * the rate at which the media should play back upon initial startup. Changing this value
22336   * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
22337   *
22338   * Example Values:
22339   *   - if playbackRate is set to 2, media will play twice as fast.
22340   *   - if playbackRate is set to 0.5, media will play half as fast.
22341   *
22342   * @method Html5.prototype.setDefaultPlaybackRate
22343   * @return {number}
22344   *         The value of `defaultPlaybackRate` from the media element. A number indicating
22345   *         the current playback speed of the media, where 1 is normal speed.
22346   *
22347   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
22348   */
22349  'defaultPlaybackRate'].forEach(function (prop) {
22350    Html5.prototype['set' + toTitleCase(prop)] = function (v) {
22351      this.el_[prop] = v;
22352    };
22353  }); // wrap native functions with a function
22354  // The list is as follows:
22355  // pause, load, play
22356
22357  [
22358  /**
22359   * A wrapper around the media elements `pause` function. This will call the `HTML5`
22360   * media elements `pause` function.
22361   *
22362   * @method Html5#pause
22363   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
22364   */
22365  'pause',
22366  /**
22367   * A wrapper around the media elements `load` function. This will call the `HTML5`s
22368   * media element `load` function.
22369   *
22370   * @method Html5#load
22371   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
22372   */
22373  'load',
22374  /**
22375   * A wrapper around the media elements `play` function. This will call the `HTML5`s
22376   * media element `play` function.
22377   *
22378   * @method Html5#play
22379   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
22380   */
22381  'play'].forEach(function (prop) {
22382    Html5.prototype[prop] = function () {
22383      return this.el_[prop]();
22384    };
22385  });
22386  Tech.withSourceHandlers(Html5);
22387  /**
22388   * Native source handler for Html5, simply passes the source to the media element.
22389   *
22390   * @property {Tech~SourceObject} source
22391   *        The source object
22392   *
22393   * @property {Html5} tech
22394   *        The instance of the HTML5 tech.
22395   */
22396
22397  Html5.nativeSourceHandler = {};
22398  /**
22399   * Check if the media element can play the given mime type.
22400   *
22401   * @param {string} type
22402   *        The mimetype to check
22403   *
22404   * @return {string}
22405   *         'probably', 'maybe', or '' (empty string)
22406   */
22407
22408  Html5.nativeSourceHandler.canPlayType = function (type) {
22409    // IE without MediaPlayer throws an error (#519)
22410    try {
22411      return Html5.TEST_VID.canPlayType(type);
22412    } catch (e) {
22413      return '';
22414    }
22415  };
22416  /**
22417   * Check if the media element can handle a source natively.
22418   *
22419   * @param {Tech~SourceObject} source
22420   *         The source object
22421   *
22422   * @param {Object} [options]
22423   *         Options to be passed to the tech.
22424   *
22425   * @return {string}
22426   *         'probably', 'maybe', or '' (empty string).
22427   */
22428
22429
22430  Html5.nativeSourceHandler.canHandleSource = function (source, options) {
22431    // If a type was provided we should rely on that
22432    if (source.type) {
22433      return Html5.nativeSourceHandler.canPlayType(source.type); // If no type, fall back to checking 'video/[EXTENSION]'
22434    } else if (source.src) {
22435      var ext = getFileExtension(source.src);
22436      return Html5.nativeSourceHandler.canPlayType("video/" + ext);
22437    }
22438
22439    return '';
22440  };
22441  /**
22442   * Pass the source to the native media element.
22443   *
22444   * @param {Tech~SourceObject} source
22445   *        The source object
22446   *
22447   * @param {Html5} tech
22448   *        The instance of the Html5 tech
22449   *
22450   * @param {Object} [options]
22451   *        The options to pass to the source
22452   */
22453
22454
22455  Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
22456    tech.setSrc(source.src);
22457  };
22458  /**
22459   * A noop for the native dispose function, as cleanup is not needed.
22460   */
22461
22462
22463  Html5.nativeSourceHandler.dispose = function () {}; // Register the native source handler
22464
22465
22466  Html5.registerSourceHandler(Html5.nativeSourceHandler);
22467  Tech.registerTech('Html5', Html5);
22468
22469  function _templateObject$2() {
22470    var data = _taggedTemplateLiteralLoose(["\n        Using the tech directly can be dangerous. I hope you know 
22470what you're doing.\n        See https://github.com/videojs/video.js/issues/2617 for more info.\n      "]);
22471
22472    _templateObject$2 = function _templateObject() {
22473      return data;
22474    };
22475
22476    return data;
22477  }
22478  // on the player when they happen
22479
22480  var TECH_EVENTS_RETRIGGER = [
22481  /**
22482   * Fired while the user agent is downloading media data.
22483   *
22484   * @event Player#progress
22485   * @type {EventTarget~Event}
22486   */
22487
22488  /**
22489   * Retrigger the `progress` event that was triggered by the {@link Tech}.
22490   *
22491   * @private
22492   * @method Player#handleTechProgress_
22493   * @fires Player#progress
22494   * @listens Tech#progress
22495   */
22496  'progress',
22497  /**
22498   * Fires when the loading of an audio/video is aborted.
22499   *
22500   * @event Player#abort
22501   * @type {EventTarget~Event}
22502   */
22503
22504  /**
22505   * Retrigger the `abort` event that was triggered by the {@link Tech}.
22506   *
22507   * @private
22508   * @method Player#handleTechAbort_
22509   * @fires Player#abort
22510   * @listens Tech#abort
22511   */
22512  'abort',
22513  /**
22514   * Fires when the browser is intentionally not getting media data.
22515   *
22516   * @event Player#suspend
22517   * @type {EventTarget~Event}
22518   */
22519
22520  /**
22521   * Retrigger the `suspend` event that was triggered by the {@link Tech}.
22522   *
22523   * @private
22524   * @method Player#handleTechSuspend_
22525   * @fires Player#suspend
22526   * @listens Tech#suspend
22527   */
22528  'suspend',
22529  /**
22530   * Fires when the current playlist is empty.
22531   *
22532   * @event Player#emptied
22533   * @type {EventTarget~Event}
22534   */
22535
22536  /**
22537   * Retrigger the `emptied` event that was triggered by the {@link Tech}.
22538   *
22539   * @private
22540   * @method Player#handleTechEmptied_
22541   * @fires Player#emptied
22542   * @listens Tech#emptied
22543   */
22544  'emptied',
22545  /**
22546   * Fires when the browser is trying to get media data, but data is not available.
22547   *
22548   * @event Player#stalled
22549   * @type {EventTarget~Event}
22550   */
22551
22552  /**
22553   * Retrigger the `stalled` event that was triggered by the {@link Tech}.
22554   *
22555   * @private
22556   * @method Player#handleTechStalled_
22557   * @fires Player#stalled
22558   * @listens Tech#stalled
22559   */
22560  'stalled',
22561  /**
22562   * Fires when the browser has loaded meta data for the audio/video.
22563   *
22564   * @event Player#loadedmetadata
22565   * @type {EventTarget~Event}
22566   */
22567
22568  /**
22569   * Retrigger the `stalled` event that was triggered by the {@link Tech}.
22570   *
22571   * @private
22572   * @method Player#handleTechLoadedmetadata_
22573   * @fires Player#loadedmetadata
22574   * @listens Tech#loadedmetadata
22575   */
22576  'loadedmetadata',
22577  /**
22578   * Fires when the browser has loaded the current frame of the audio/video.
22579   *
22580   * @event Player#loadeddata
22581   * @type {event}
22582   */
22583
22584  /**
22585   * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
22586   *
22587   * @private
22588   * @method Player#handleTechLoaddeddata_
22589   * @fires Player#loadeddata
22590   * @listens Tech#loadeddata
22591   */
22592  'loadeddata',
22593  /**
22594   * Fires when the current playback position has changed.
22595   *
22596   * @event Player#timeupdate
22597   * @type {event}
22598   */
22599
22600  /**
22601   * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
22602   *
22603   * @private
22604   * @method Player#handleTechTimeUpdate_
22605   * @fires Player#timeupdate
22606   * @listens Tech#timeupdate
22607   */
22608  'timeupdate',
22609  /**
22610   * Fires when the video's intrinsic dimensions change
22611   *
22612   * @event Player#resize
22613   * @type {event}
22614   */
22615
22616  /**
22617   * Retrigger the `resize` event that was triggered by the {@link Tech}.
22618   *
22619   * @private
22620   * @method Player#handleTechResize_
22621   * @fires Player#resize
22622   * @listens Tech#resize
22623   */
22624  'resize',
22625  /**
22626   * Fires when the volume has been changed
22627   *
22628   * @event Player#volumechange
22629   * @type {event}
22630   */
22631
22632  /**
22633   * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
22634   *
22635   * @private
22636   * @method Player#handleTechVolumechange_
22637   * @fires Player#volumechange
22638   * @listens Tech#volumechange
22639   */
22640  'volumechange',
22641  /**
22642   * Fires when the text track has been changed
22643   *
22644   * @event Player#texttrackchange
22645   * @type {event}
22646   */
22647
22648  /**
22649   * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
22650   *
22651   * @private
22652   * @method Player#handleTechTexttrackchange_
22653   * @fires Player#texttrackchange
22654   * @listens Tech#texttrackchange
22655   */
22656  'texttrackchange']; // events to queue when playback rate is zero
22657  // this is a hash for the sole purpose of mapping non-camel-cased event names
22658  // to camel-cased function names
22659
22660  var TECH_EVENTS_QUEUE = {
22661    canplay: 'CanPlay',
22662    canplaythrough: 'CanPlayThrough',
22663    playing: 'Playing',
22664    seeked: 'Seeked'
22665  };
22666  var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
22667  var BREAKPOINT_CLASSES = {}; // grep: vjs-layout-tiny
22668  // grep: vjs-layout-x-small
22669  // grep: vjs-layout-small
22670  // grep: vjs-layout-medium
22671  // grep: vjs-layout-large
22672  // grep: vjs-layout-x-large
22673  // grep: vjs-layout-huge
22674
vendor: 21,955 bytes, lines 22675-23351
22675  BREAKPOINT_ORDER.forEach(function (k) {
22676    var v = k.charAt(0) === 'x' ? "x-" + k.substring(1) : k;
22677    BREAKPOINT_CLASSES[k] = "vjs-layout-" + v;
22678  });
22679  var DEFAULT_BREAKPOINTS = {
22680    tiny: 210,
22681    xsmall: 320,
22682    small: 425,
22683    medium: 768,
22684    large: 1440,
22685    xlarge: 2560,
22686    huge: Infinity
22687  };
22688  /**
22689   * An instance of the `Player` class is created when any of the Video.js setup methods
22690   * are used to initialize a video.
22691   *
22692   * After an instance has been created it can be accessed globally in two ways:
22693   * 1. By calling `videojs('example_video_1');`
22694   * 2. By using it directly via  `videojs.players.example_video_1;`
22695   *
22696   * @extends Component
22697   */
22698
22699  var Player =
22700  /*#__PURE__*/
22701  function (_Component) {
22702    _inheritsLoose(Player, _Component);
22703
22704    /**
22705     * Create an instance of this class.
22706     *
22707     * @param {Element} tag
22708     *        The original video DOM element used for configuring options.
22709     *
22710     * @param {Object} [options]
22711     *        Object of option names and values.
22712     *
22713     * @param {Component~ReadyCallback} [ready]
22714     *        Ready callback function.
22715     */
22716    function Player(tag, options, ready) {
22717      var _this;
22718
22719      // Make sure tag ID exists
22720      tag.id = tag.id || options.id || "vjs_video_" + newGUID(); // Set Options
22721      // The options argument overrides options set in the video tag
22722      // which overrides globally set options.
22723      // This latter part coincides with the load order
22724      // (tag must exist before Player)
22725
22726      options = assign(Player.getTagSettings(tag), options); // Delay the initialization of children because we need to set up
22727      // player properties first, and can't use `this` before `super()`
22728
22729      options.initChildren = false; // Same with creating the element
22730
22731      options.createEl = false; // don't auto mixin the evented mixin
22732
22733      options.evented = false; // we don't want the player to report touch activity on itself
22734      // see enableTouchActivity in Component
22735
22736      options.reportTouchActivity = false; // If language is not set, get the closest lang attribute
22737
22738      if (!options.language) {
22739        if (typeof tag.closest === 'function') {
22740          var closest = tag.closest('[lang]');
22741
22742          if (closest && closest.getAttribute) {
22743            options.language = closest.getAttribute('lang');
22744          }
22745        } else {
22746          var element = tag;
22747
22748          while (element && element.nodeType === 1) {
22749            if (getAttributes(element).hasOwnProperty('lang')) {
22750              options.language = element.getAttribute('lang');
22751              break;
22752            }
22753
22754            element = element.parentNode;
22755          }
22756        }
22757      } // Run base component initializing with new options
22758
22759
22760      _this = _Component.call(this, null, options, ready) || this; // Create bound methods for document listeners.
22761
22762      _this.boundDocumentFullscreenChange_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.documentFullscreenChange_);
22763      _this.boundFullWindowOnEscKey_ = bind(_assertThisInitialized(_assertThisInitialized(_this)), _this.fullWindowOnEscKey); // create logger
22764
22765      _this.log = createLogger$1(_this.id_); // Tracks when a tech changes the poster
22766
22767      _this.isPosterFromTech_ = false; // Holds callback info that gets queued when playback rate is zero
22768      // and a seek is happening
22769
22770      _this.queuedCallbacks_ = []; // Turn off API access because we're loading a new tech that might load asynchronously
22771
22772      _this.isReady_ = false; // Init state hasStarted_
22773
22774      _this.hasStarted_ = false; // Init state userActive_
22775
22776      _this.userActive_ = false; // if the global option object was accidentally blown away by
22777      // someone, bail early with an informative error
22778
22779      if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) {
22780        throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
22781      } // Store the original tag used to set options
22782
22783
22784      _this.tag = tag; // Store the tag attributes used to restore html5 element
22785
22786      _this.tagAttributes = tag && getAttributes(tag); // Update current language
22787
22788      _this.language(_this.options_.language); // Update Supported Languages
22789
22790
22791      if (options.languages) {
22792        // Normalise player option languages to lowercase
22793        var languagesToLower = {};
22794        Object.getOwnPropertyNames(options.languages).forEach(function (name$$1) {
22795          languagesToLower[name$$1.toLowerCase()] = options.languages[name$$1];
22796        });
22797        _this.languages_ = languagesToLower;
22798      } else {
22799        _this.languages_ = Player.prototype.options_.languages;
22800      }
22801
22802      _this.resetCache_(); // Set poster
22803
22804
22805      _this.poster_ = options.poster || ''; // Set controls
22806
22807      _this.controls_ = !!options.controls; // Original tag settings stored in options
22808      // now remove immediately so native controls don't flash.
22809      // May be turned back on by HTML5 tech if nativeControlsForTouch is true
22810
22811      tag.controls = false;
22812      tag.removeAttribute('controls');
22813      _this.changingSrc_ = false;
22814      _this.playCallbacks_ = [];
22815      _this.playTerminatedQueue_ = []; // the attribute overrides the option
22816
22817      if (tag.hasAttribute('autoplay')) {
22818        _this.autoplay(true);
22819      } else {
22820        // otherwise use the setter to validate and
22821        // set the correct value.
22822        _this.autoplay(_this.options_.autoplay);
22823      } // check plugins
22824
22825
22826      if (options.plugins) {
22827        Object.keys(options.plugins).forEach(function (name$$1) {
22828          if (typeof _this[name$$1] !== 'function') {
22829            throw new Error("plugin \"" + name$$1 + "\" does not exist");
22830          }
22831        });
22832      }
22833      /*
22834       * Store the internal state of scrubbing
22835       *
22836       * @private
22837       * @return {Boolean} True if the user is scrubbing
22838       */
22839
22840
22841      _this.scrubbing_ = false;
22842      _this.el_ = _this.createEl(); // Make this an evented object and use `el_` as its event bus.
22843
22844      evented(_assertThisInitialized(_assertThisInitialized(_this)), {
22845        eventBusKey: 'el_'
22846      });
22847
22848      if (_this.fluid_) {
22849        _this.on('playerreset', _this.updateStyleEl_);
22850      } // We also want to pass the original player options to each component and plugin
22851      // as well so they don't need to reach back into the player for options later.
22852      // We also need to do another copy of this.options_ so we don't end up with
22853      // an infinite loop.
22854
22855
22856      var playerOptionsCopy = mergeOptions(_this.options_); // Load plugins
22857
22858      if (options.plugins) {
22859        Object.keys(options.plugins).forEach(function (name$$1) {
22860          _this[name$$1](options.plugins[name$$1]);
22861        });
22862      }
22863
22864      _this.options_.playerOptions = playerOptionsCopy;
22865      _this.middleware_ = [];
22866
22867      _this.initChildren(); // Set isAudio based on whether or not an audio tag was used
22868
22869
22870      _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); // Update controls className. Can't do this when the controls are initially
22871      // set because the element doesn't exist yet.
22872
22873
22874      if (_this.controls()) {
22875        _this.addClass('vjs-controls-enabled');
22876      } else {
22877        _this.addClass('vjs-controls-disabled');
22878      } // Set ARIA label and region role depending on player type
22879
22880
22881      _this.el_.setAttribute('role', 'region');
22882
22883      if (_this.isAudio()) {
22884        _this.el_.setAttribute('aria-label', _this.localize('Audio Player'));
22885      } else {
22886        _this.el_.setAttribute('aria-label', _this.localize('Video Player'));
22887      }
22888
22889      if (_this.isAudio()) {
22890        _this.addClass('vjs-audio');
22891      }
22892
22893      if (_this.flexNotSupported_()) {
22894        _this.addClass('vjs-no-flex');
22895      } // TODO: Make this smarter. Toggle user state between touching/mousing
22896      // using events, since devices can have both touch and mouse events.
22897      // TODO: Make this check be performed again when the window switches between monitors
22898      // (See https://github.com/videojs/video.js/issues/5683)
22899
22900
22901      if (TOUCH_ENABLED) {
22902        _this.addClass('vjs-touch-enabled');
22903      } // iOS Safari has broken hover handling
22904
22905
22906      if (!IS_IOS) {
22907        _this.addClass('vjs-workinghover');
22908      } // Make player easily findable by ID
22909
22910
22911      Player.players[_this.id_] = _assertThisInitialized(_assertThisInitialized(_this)); // Add a major version class to aid css in plugins
22912
22913      var majorVersion = version.split('.')[0];
22914
22915      _this.addClass("vjs-v" + majorVersion); // When the player is first initialized, trigger activity so components
22916      // like the control bar show themselves if needed
22917
22918
22919      _this.userActive(true);
22920
22921      _this.reportUserActivity();
22922
22923      _this.one('play', _this.listenForUserActivity_);
22924
22925      _this.on('stageclick', _this.handleStageClick_);
22926
22927      _this.on('keydown', _this.handleKeyDown);
22928
22929      _this.breakpoints(_this.options_.breakpoints);
22930
22931      _this.responsive(_this.options_.responsive);
22932
22933      return _this;
22934    }
22935    /**
22936     * Destroys the video player and does any necessary cleanup.
22937     *
22938     * This is especially helpful if you are dynamically adding and removing videos
22939     * to/from the DOM.
22940     *
22941     * @fires Player#dispose
22942     */
22943
22944
22945    var _proto = Player.prototype;
22946
22947    _proto.dispose = function dispose() {
22948      var _this2 = this;
22949
22950      /**
22951       * Called when the player is being disposed of.
22952       *
22953       * @event Player#dispose
22954       * @type {EventTarget~Event}
22955       */
22956      this.trigger('dispose'); // prevent dispose from being called twice
22957
22958      this.off('dispose'); // Make sure all player-specific document listeners are unbound. This is
22959
22960      off(document, FullscreenApi.fullscreenchange, this.boundDocumentFullscreenChange_);
22961      off(document, 'keydown', this.boundFullWindowOnEscKey_);
22962
22963      if (this.styleEl_ && this.styleEl_.parentNode) {
22964        this.styleEl_.parentNode.removeChild(this.styleEl_);
22965        this.styleEl_ = null;
22966      } // Kill reference to this player
22967
22968
22969      Player.players[this.id_] = null;
22970
22971      if (this.tag && this.tag.player) {
22972        this.tag.player = null;
22973      }
22974
22975      if (this.el_ && this.el_.player) {
22976        this.el_.player = null;
22977      }
22978
22979      if (this.tech_) {
22980        this.tech_.dispose();
22981        this.isPosterFromTech_ = false;
22982        this.poster_ = '';
22983      }
22984
22985      if (this.playerElIngest_) {
22986        this.playerElIngest_ = null;
22987      }
22988
22989      if (this.tag) {
22990        this.tag = null;
22991      }
22992
22993      clearCacheForPlayer(this); // remove all event handlers for track lists
22994      // all tracks and track listeners are removed on
22995      // tech dispose
22996
22997      ALL.names.forEach(function (name$$1) {
22998        var props = ALL[name$$1];
22999
23000        var list = _this2[props.getterName](); // if it is not a native list
23001        // we have to manually remove event listeners
23002
23003
23004        if (list && list.off) {
23005          list.off();
23006        }
23007      }); // the actual .el_ is removed here
23008
23009      _Component.prototype.dispose.call(this);
23010    }
23011    /**
23012     * Create the `Player`'s DOM element.
23013     *
23014     * @return {Element}
23015     *         The DOM element that gets created.
23016     */
23017    ;
23018
23019    _proto.createEl = function createEl$$1() {
23020      var tag = this.tag;
23021      var el;
23022      var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
23023      var divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
23024
23025      if (playerElIngest) {
23026        el = this.el_ = tag.parentNode;
23027      } else if (!divEmbed) {
23028        el = this.el_ = _Component.prototype.createEl.call(this, 'div');
23029      } // Copy over all the attributes from the tag, including ID and class
23030      // ID will now reference player box, not the video tag
23031
23032
23033      var attrs = getAttributes(tag);
23034
23035      if (divEmbed) {
23036        el = this.el_ = tag;
23037        tag = this.tag = document.createElement('video');
23038
23039        while (el.children.length) {
23040          tag.appendChild(el.firstChild);
23041        }
23042
23043        if (!hasClass(el, 'video-js')) {
23044          addClass(el, 'video-js');
23045        }
23046
23047        el.appendChild(tag);
23048        playerElIngest = this.playerElIngest_ = el; // move properties over from our custom `video-js` element
23049        // to our new `video` element. This will move things like
23050        // `src` or `controls` that were set via js before the player
23051        // was initialized.
23052
23053        Object.keys(el).forEach(function (k) {
23054          tag[k] = el[k];
23055        });
23056      } // set tabindex to -1 to remove the video element from the focus order
23057
23058
23059      tag.setAttribute('tabindex', '-1');
23060      attrs.tabindex = '-1'; // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button)
23061      // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
23062      // Note that we can't detect if JAWS is being used, but this ARIA attribute
23063      //  doesn't change behavior of IE11 if JAWS is not being used
23064
23065      if (IE_VERSION) {
23066        tag.setAttribute('role', 'application');
23067        attrs.role = 'application';
23068      } // Remove width/height attrs from tag so CSS can make it 100% width/height
23069
23070
23071      tag.removeAttribute('width');
23072      tag.removeAttribute('height');
23073
23074      if ('width' in attrs) {
23075        delete attrs.width;
23076      }
23077
23078      if ('height' in attrs) {
23079        delete attrs.height;
23080      }
23081
23082      Object.getOwnPropertyNames(attrs).forEach(function (attr) {
23083        // don't copy over the class attribute to the player element when we're in a div embed
23084        // the class is already set up properly in the divEmbed case
23085        // and we want to make sure that the `video-js` class doesn't get lost
23086        if (!(divEmbed && attr === 'class')) {
23087          el.setAttribute(attr, attrs[attr]);
23088        }
23089
23090        if (divEmbed) {
23091          tag.setAttribute(attr, attrs[attr]);
23092        }
23093      }); // Update tag id/class for use as HTML5 playback tech
23094      // Might think we should do this after embedding in container so .vjs-tech class
23095      // doesn't flash 100% width/height, but class only applies with .video-js parent
23096
23097      tag.playerId = tag.id;
23098      tag.id += '_html5_api';
23099      tag.className = 'vjs-tech'; // Make player findable on elements
23100
23101      tag.player = el.player = this; // Default state of video is paused
23102
23103      this.addClass('vjs-paused'); // Add a style element in the player that we'll use to set the width/height
23104      // of the player in a way that's still overrideable by CSS, just like the
23105      // video element
23106
23107      if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
23108        this.styleEl_ = createStyleElement('vjs-styles-dimensions');
23109        var defaultsStyleEl = $('.vjs-styles-defaults');
23110        var head = $('head');
23111        head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
23112      }
23113
23114      this.fill_ = false;
23115      this.fluid_ = false; // Pass in the width/height/aspectRatio options which will update the style el
23116
23117      this.width(this.options_.width);
23118      this.height(this.options_.height);
23119      this.fill(this.options_.fill);
23120      this.fluid(this.options_.fluid);
23121      this.aspectRatio(this.options_.aspectRatio); // Hide any links within the video/audio tag,
23122      // because IE doesn't hide them completely from screen readers.
23123
23124      var links = tag.getElementsByTagName('a');
23125
23126      for (var i = 0; i < links.length; i++) {
23127        var linkEl = links.item(i);
23128        addClass(linkEl, 'vjs-hidden');
23129        linkEl.setAttribute('hidden', 'hidden');
23130      } // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
23131      // keep track of the original for later so we can know if the source originally failed
23132
23133
23134      tag.initNetworkState_ = tag.networkState; // Wrap video tag in div (el/box) container
23135
23136      if (tag.parentNode && !playerElIngest) {
23137        tag.parentNode.insertBefore(el, tag);
23138      } // insert the tag as the first child of the player element
23139      // then manually add it to the children array so that this.addChild
23140      // will work properly for other components
23141      //
23142      // Breaks iPhone, fixed in HTML5 setup.
23143
23144
23145      prependTo(tag, el);
23146      this.children_.unshift(tag); // Set lang attr on player to ensure CSS :lang() in consistent with player
23147      // if it's been set to something different to the doc
23148
23149      this.el_.setAttribute('lang', this.language_);
23150      this.el_ = el;
23151      return el;
23152    }
23153    /**
23154     * A getter/setter for the `Player`'s width. Returns the player's configured value.
23155     * To get the current width use `currentWidth()`.
23156     *
23157     * @param {number} [value]
23158     *        The value to set the `Player`'s width to.
23159     *
23160     * @return {number}
23161     *         The current width of the `Player` when getting.
23162     */
23163    ;
23164
23165    _proto.width = function width(value) {
23166      return this.dimension('width', value);
23167    }
23168    /**
23169     * A getter/setter for the `Player`'s height. Returns the player's configured value.
23170     * To get the current height use `currentheight()`.
23171     *
23172     * @param {number} [value]
23173     *        The value to set the `Player`'s heigth to.
23174     *
23175     * @return {number}
23176     *         The current height of the `Player` when getting.
23177     */
23178    ;
23179
23180    _proto.height = function height(value) {
23181      return this.dimension('height', value);
23182    }
23183    /**
23184     * A getter/setter for the `Player`'s width & height.
23185     *
23186     * @param {string} dimension
23187     *        This string can be:
23188     *        - 'width'
23189     *        - 'height'
23190     *
23191     * @param {number} [value]
23192     *        Value for dimension specified in the first argument.
23193     *
23194     * @return {number}
23195     *         The dimension arguments value when getting (width/height).
23196     */
23197    ;
23198
23199    _proto.dimension = function dimension(_dimension, value) {
23200      var privDimension = _dimension + '_';
23201
23202      if (value === undefined) {
23203        return this[privDimension] || 0;
23204      }
23205
23206      if (value === '') {
23207        // If an empty string is given, reset the dimension to be automatic
23208        this[privDimension] = undefined;
23209        this.updateStyleEl_();
23210        return;
23211      }
23212
23213      var parsedVal = parseFloat(value);
23214
23215      if (isNaN(parsedVal)) {
23216        log.error("Improper value \"" + value + "\" supplied for for " + _dimension);
23217        return;
23218      }
23219
23220      this[privDimension] = parsedVal;
23221      this.updateStyleEl_();
23222    }
23223    /**
23224     * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
23225     *
23226     * Turning this on will turn off fill mode.
23227     *
23228     * @param {boolean} [bool]
23229     *        - A value of true adds the class.
23230     *        - A value of false removes the class.
23231     *        - No value will be a getter.
23232     *
23233     * @return {boolean|undefined}
23234     *         - The value of fluid when getting.
23235     *         - `undefined` when setting.
23236     */
23237    ;
23238
23239    _proto.fluid = function fluid(bool) {
23240      if (bool === undefined) {
23241        return !!this.fluid_;
23242      }
23243
23244      this.fluid_ = !!bool;
23245
23246      if (isEvented(this)) {
23247        this.off('playerreset', this.updateStyleEl_);
23248      }
23249
23250      if (bool) {
23251        this.addClass('vjs-fluid');
23252        this.fill(false);
23253        addEventedCallback(function () {
23254          this.on('playerreset', this.updateStyleEl_);
23255        });
23256      } else {
23257        this.removeClass('vjs-fluid');
23258      }
23259
23260      this.updateStyleEl_();
23261    }
23262    /**
23263     * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
23264     *
23265     * Turning this on will turn off fluid mode.
23266     *
23267     * @param {boolean} [bool]
23268     *        - A value of true adds the class.
23269     *        - A value of false removes the class.
23270     *        - No value will be a getter.
23271     *
23272     * @return {boolean|undefined}
23273     *         - The value of fluid when getting.
23274     *         - `undefined` when setting.
23275     */
23276    ;
23277
23278    _proto.fill = function fill(bool) {
23279      if (bool === undefined) {
23280        return !!this.fill_;
23281      }
23282
23283      this.fill_ = !!bool;
23284
23285      if (bool) {
23286        this.addClass('vjs-fill');
23287        this.fluid(false);
23288      } else {
23289        this.removeClass('vjs-fill');
23290      }
23291    }
23292    /**
23293     * Get/Set the aspect ratio
23294     *
23295     * @param {string} [ratio]
23296     *        Aspect ratio for player
23297     *
23298     * @return {string|undefined}
23299     *         returns the current aspect ratio when getting
23300     */
23301
23302    /**
23303     * A getter/setter for the `Player`'s aspect ratio.
23304     *
23305     * @param {string} [ratio]
23306     *        The value to set the `Player's aspect ratio to.
23307     *
23308     * @return {string|undefined}
23309     *         - The current aspect ratio of the `Player` when getting.
23310     *         - undefined when setting
23311     */
23312    ;
23313
23314    _proto.aspectRatio = function aspectRatio(ratio) {
23315      if (ratio === undefined) {
23316        return this.aspectRatio_;
23317      } // Check for width:height format
23318
23319
23320      if (!/^\d+\:\d+$/.test(ratio)) {
23321        throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
23322      }
23323
23324      this.aspectRatio_ = ratio; // We're assuming if you set an aspect ratio you want fluid mode,
23325      // because in fixed mode you could calculate width and height yourself.
23326
23327      this.fluid(true);
23328      this.updateStyleEl_();
23329    }
23330    /**
23331     * Update styles of the `Player` element (height, width and aspect ratio).
23332     *
23333     * @private
23334     * @listens Tech#loadedmetadata
23335     */
23336    ;
23337
23338    _proto.updateStyleEl_ = function updateStyleEl_() {
23339      if (window$1.VIDEOJS_NO_DYNAMIC_STYLE === true) {
23340        var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
23341
23342        var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
23343
23344        var techEl = this.tech_ && this.tech_.el();
23345
23346        if (techEl) {
23347          if (_width >= 0) {
23348            techEl.width = _width;
23349          }
23350
23351          if (_height >= 0) {
vendor: 8,342 bytes, lines 23352-23560
23352            techEl.height = _height;
23353          }
23354        }
23355
23356        return;
23357      }
23358
23359      var width;
23360      var height;
23361      var aspectRatio;
23362      var idClass; // The aspect ratio is either used directly or to calculate width and height.
23363
23364      if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
23365        // Use any aspectRatio that's been specifically set
23366        aspectRatio = this.aspectRatio_;
23367      } else if (this.videoWidth() > 0) {
23368        // Otherwise try to get the aspect ratio from the video metadata
23369        aspectRatio = this.videoWidth() + ':' + this.videoHeight();
23370      } else {
23371        // Or use a default. The video element's is 2:1, but 16:9 is more common.
23372        aspectRatio = '16:9';
23373      } // Get the ratio as a decimal we can use to calculate dimensions
23374
23375
23376      var ratioParts = aspectRatio.split(':');
23377      var ratioMultiplier = ratioParts[1] / ratioParts[0];
23378
23379      if (this.width_ !== undefined) {
23380        // Use any width that's been specifically set
23381        width = this.width_;
23382      } else if (this.height_ !== undefined) {
23383        // Or calulate the width from the aspect ratio if a height has been set
23384        width = this.height_ / ratioMultiplier;
23385      } else {
23386        // Or use the video's metadata, or use the video el's default of 300
23387        width = this.videoWidth() || 300;
23388      }
23389
23390      if (this.height_ !== undefined) {
23391        // Use any height that's been specifically set
23392        height = this.height_;
23393      } else {
23394        // Otherwise calculate the height from the ratio and the width
23395        height = width * ratioMultiplier;
23396      } // Ensure the CSS class is valid by starting with an alpha character
23397
23398
23399      if (/^[^a-zA-Z]/.test(this.id())) {
23400        idClass = 'dimensions-' + this.id();
23401      } else {
23402        idClass = this.id() + '-dimensions';
23403      } // Ensure the right class is still on the player for the style element
23404
23405
23406      this.addClass(idClass);
23407      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    ");
23408    }
23409    /**
23410     * Load/Create an instance of playback {@link Tech} including element
23411     * and API methods. Then append the `Tech` element in `Player` as a child.
23412     *
23413     * @param {string} techName
23414     *        name of the playback technology
23415     *
23416     * @param {string} source
23417     *        video source
23418     *
23419     * @private
23420     */
23421    ;
23422
23423    _proto.loadTech_ = function loadTech_(techName, source) {
23424      var _this3 = this;
23425
23426      // Pause and remove current playback technology
23427      if (this.tech_) {
23428        this.unloadTech_();
23429      }
23430
23431      var titleTechName = toTitleCase(techName);
23432      var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); // get rid of the HTML5 video tag as soon as we are using another tech
23433
23434      if (titleTechName !== 'Html5' && this.tag) {
23435        Tech.getTech('Html5').disposeMediaElement(this.tag);
23436        this.tag.player = null;
23437        this.tag = null;
23438      }
23439
23440      this.techName_ = titleTechName; // Turn off API access because we're loading a new tech that might load asynchronously
23441
23442      this.isReady_ = false; // if autoplay is a string we pass false to the tech
23443      // because the player is going to handle autoplay on `loadstart`
23444
23445      var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay(); // Grab tech-specific options from player options and add source and parent element to use.
23446
23447      var techOptions = {
23448        source: source,
23449        autoplay: autoplay,
23450        'nativeControlsForTouch': this.options_.nativeControlsForTouch,
23451        'playerId': this.id(),
23452        'techId': this.id() + "_" + camelTechName + "_api",
23453        'playsinline': this.options_.playsinline,
23454        'preload': this.options_.preload,
23455        'loop': this.options_.loop,
23456        'muted': this.options_.muted,
23457        'poster': this.poster(),
23458        'language': this.language(),
23459        'playerElIngest': this.playerElIngest_ || false,
23460        'vtt.js': this.options_['vtt.js'],
23461        'canOverridePoster': !!this.options_.techCanOverridePoster,
23462        'enableSourceset': this.options_.enableSourceset
23463      };
23464      ALL.names.forEach(function (name$$1) {
23465        var props = ALL[name$$1];
23466        techOptions[props.getterName] = _this3[props.privateName];
23467      });
23468      assign(techOptions, this.options_[titleTechName]);
23469      assign(techOptions, this.options_[camelTechName]);
23470      assign(techOptions, this.options_[techName.toLowerCase()]);
23471
23472      if (this.tag) {
23473        techOptions.tag = this.tag;
23474      }
23475
23476      if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
23477        techOptions.startTime = this.cache_.currentTime;
23478      } // Initialize tech instance
23479
23480
23481      var TechClass = Tech.getTech(techName);
23482
23483      if (!TechClass) {
23484        throw new Error("No Tech named '" + titleTechName + "' exists! '" + titleTechName + "' should be registered using videojs.registerTech()'");
23485      }
23486
23487      this.tech_ = new TechClass(techOptions); // player.triggerReady is always async, so don't need this to be async
23488
23489      this.tech_.ready(bind(this, this.handleTechReady_), true);
23490      textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player
23491
23492      TECH_EVENTS_RETRIGGER.forEach(function (event) {
23493        _this3.on(_this3.tech_, event, _this3["handleTech" + toTitleCase(event) + "_"]);
23494      });
23495      Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) {
23496        _this3.on(_this3.tech_, event, function (eventObj) {
23497          if (_this3.tech_.playbackRate() === 0 && _this3.tech_.seeking()) {
23498            _this3.queuedCallbacks_.push({
23499              callback: _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"].bind(_this3),
23500              event: eventObj
23501            });
23502
23503            return;
23504          }
23505
23506          _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"](eventObj);
23507        });
23508      });
23509      this.on(this.tech_, 'loadstart', this.handleTechLoadStart_);
23510      this.on(this.tech_, 'sourceset', this.handleTechSourceset_);
23511      this.on(this.tech_, 'waiting', this.handleTechWaiting_);
23512      this.on(this.tech_, 'ended', this.handleTechEnded_);
23513      this.on(this.tech_, 'seeking', this.handleTechSeeking_);
23514      this.on(this.tech_, 'play', this.handleTechPlay_);
23515      this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_);
23516      this.on(this.tech_, 'pause', this.handleTechPause_);
23517      this.on(this.tech_, 'durationchange', this.handleTechDurationChange_);
23518      this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_);
23519      this.on(this.tech_, 'error', this.handleTechError_);
23520      this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_);
23521      this.on(this.tech_, 'posterchange', this.handleTechPosterChange_);
23522      this.on(this.tech_, 'textdata', this.handleTechTextData_);
23523      this.on(this.tech_, 'ratechange', this.handleTechRateChange_);
23524      this.usingNativeControls(this.techGet_('controls'));
23525
23526      if (this.controls() && !this.usingNativeControls()) {
23527        this.addTechControlsListeners_();
23528      } // Add the tech element in the DOM if it was not already there
23529      // Make sure to not insert the original video element if using Html5
23530
23531
23532      if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
23533        prependTo(this.tech_.el(), this.el());
23534      } // Get rid of the original video tag reference after the first tech is loaded
23535
23536
23537      if (this.tag) {
23538        this.tag.player = null;
23539        this.tag = null;
23540      }
23541    }
23542    /**
23543     * Unload and dispose of the current playback {@link Tech}.
23544     *
23545     * @private
23546     */
23547    ;
23548
23549    _proto.unloadTech_ = function unloadTech_() {
23550      var _this4 = this;
23551
23552      // Save the current text tracks so that we can reuse the same text tracks with the next tech
23553      ALL.names.forEach(function (name$$1) {
23554        var props = ALL[name$$1];
23555        _this4[props.privateName] = _this4[props.getterName]();
23556      });
23557      this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
23558      this.isReady_ = false;
23559      this.tech_.dispose();
23560      this.tech_ = false;
vendor: 44,151 bytes, lines 23561-25017
23561
23562      if (this.isPosterFromTech_) {
23563        this.poster_ = '';
23564        this.trigger('posterchange');
23565      }
23566
23567      this.isPosterFromTech_ = false;
23568    }
23569    /**
23570     * Return a reference to the current {@link Tech}.
23571     * It will print a warning by default about the danger of using the tech directly
23572     * but any argument that is passed in will silence the warning.
23573     *
23574     * @param {*} [safety]
23575     *        Anything passed in to silence the warning
23576     *
23577     * @return {Tech}
23578     *         The Tech
23579     */
23580    ;
23581
23582    _proto.tech = function tech(safety) {
23583      if (safety === undefined) {
23584        log.warn(tsml(_templateObject$2()));
23585      }
23586
23587      return this.tech_;
23588    }
23589    /**
23590     * Set up click and touch listeners for the playback element
23591     *
23592     * - On desktops: a click on the video itself will toggle playback
23593     * - On mobile devices: a click on the video toggles controls
23594     *   which is done by toggling the user state between active and
23595     *   inactive
23596     * - A tap can signal that a user has become active or has become inactive
23597     *   e.g. a quick tap on an iPhone movie should reveal the controls. Another
23598     *   quick tap should hide them again (signaling the user is in an inactive
23599     *   viewing state)
23600     * - In addition to this, we still want the user to be considered inactive after
23601     *   a few seconds of inactivity.
23602     *
23603     * > Note: the only part of iOS interaction we can't mimic with this setup
23604     * is a touch and hold on the video element counting as activity in order to
23605     * keep the controls showing, but that shouldn't be an issue. A touch and hold
23606     * on any controls will still keep the user active
23607     *
23608     * @private
23609     */
23610    ;
23611
23612    _proto.addTechControlsListeners_ = function addTechControlsListeners_() {
23613      // Make sure to remove all the previous listeners in case we are called multiple times.
23614      this.removeTechControlsListeners_(); // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
23615      // trigger mousedown/up.
23616      // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
23617      // Any touch events are set to block the mousedown event from happening
23618
23619      this.on(this.tech_, 'mousedown', this.handleTechClick_);
23620      this.on(this.tech_, 'dblclick', this.handleTechDoubleClick_); // If the controls were hidden we don't want that to change without a tap event
23621      // so we'll check if the controls were already showing before reporting user
23622      // activity
23623
23624      this.on(this.tech_, 'touchstart', this.handleTechTouchStart_);
23625      this.on(this.tech_, 'touchmove', this.handleTechTouchMove_);
23626      this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); // The tap listener needs to come after the touchend listener because the tap
23627      // listener cancels out any reportedUserActivity when setting userActive(false)
23628
23629      this.on(this.tech_, 'tap', this.handleTechTap_);
23630    }
23631    /**
23632     * Remove the listeners used for click and tap controls. This is needed for
23633     * toggling to controls disabled, where a tap/touch should do nothing.
23634     *
23635     * @private
23636     */
23637    ;
23638
23639    _proto.removeTechControlsListeners_ = function removeTechControlsListeners_() {
23640      // We don't want to just use `this.off()` because there might be other needed
23641      // listeners added by techs that extend this.
23642      this.off(this.tech_, 'tap', this.handleTechTap_);
23643      this.off(this.tech_, 'touchstart', this.handleTechTouchStart_);
23644      this.off(this.tech_, 'touchmove', this.handleTechTouchMove_);
23645      this.off(this.tech_, 'touchend', this.handleTechTouchEnd_);
23646      this.off(this.tech_, 'mousedown', this.handleTechClick_);
23647      this.off(this.tech_, 'dblclick', this.handleTechDoubleClick_);
23648    }
23649    /**
23650     * Player waits for the tech to be ready
23651     *
23652     * @private
23653     */
23654    ;
23655
23656    _proto.handleTechReady_ = function handleTechReady_() {
23657      this.triggerReady(); // Keep the same volume as before
23658
23659      if (this.cache_.volume) {
23660        this.techCall_('setVolume', this.cache_.volume);
23661      } // Look if the tech found a higher resolution poster while loading
23662
23663
23664      this.handleTechPosterChange_(); // Update the duration if available
23665
23666      this.handleTechDurationChange_();
23667    }
23668    /**
23669     * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This
23670     * function will also trigger {@link Player#firstplay} if it is the first loadstart
23671     * for a video.
23672     *
23673     * @fires Player#loadstart
23674     * @fires Player#firstplay
23675     * @listens Tech#loadstart
23676     * @private
23677     */
23678    ;
23679
23680    _proto.handleTechLoadStart_ = function handleTechLoadStart_() {
23681      // TODO: Update to use `emptied` event instead. See #1277.
23682      this.removeClass('vjs-ended');
23683      this.removeClass('vjs-seeking'); // reset the error state
23684
23685      this.error(null); // Update the duration
23686
23687      this.handleTechDurationChange_(); // If it's already playing we want to trigger a firstplay event now.
23688      // The firstplay event relies on both the play and loadstart events
23689      // which can happen in any order for a new source
23690
23691      if (!this.paused()) {
23692        /**
23693         * Fired when the user agent begins looking for media data
23694         *
23695         * @event Player#loadstart
23696         * @type {EventTarget~Event}
23697         */
23698        this.trigger('loadstart');
23699        this.trigger('firstplay');
23700      } else {
23701        // reset the hasStarted state
23702        this.hasStarted(false);
23703        this.trigger('loadstart');
23704      } // autoplay happens after loadstart for the browser,
23705      // so we mimic that behavior
23706
23707
23708      this.manualAutoplay_(this.autoplay());
23709    }
23710    /**
23711     * Handle autoplay string values, rather than the typical boolean
23712     * values that should be handled by the tech. Note that this is not
23713     * part of any specification. Valid values and what they do can be
23714     * found on the autoplay getter at Player#autoplay()
23715     */
23716    ;
23717
23718    _proto.manualAutoplay_ = function manualAutoplay_(type) {
23719      var _this5 = this;
23720
23721      if (!this.tech_ || typeof type !== 'string') {
23722        return;
23723      }
23724
23725      var muted = function muted() {
23726        var previouslyMuted = _this5.muted();
23727
23728        _this5.muted(true);
23729
23730        var restoreMuted = function restoreMuted() {
23731          _this5.muted(previouslyMuted);
23732        }; // restore muted on play terminatation
23733
23734
23735        _this5.playTerminatedQueue_.push(restoreMuted);
23736
23737        var mutedPromise = _this5.play();
23738
23739        if (!isPromise(mutedPromise)) {
23740          return;
23741        }
23742
23743        return mutedPromise.catch(restoreMuted);
23744      };
23745
23746      var promise; // if muted defaults to true
23747      // the only thing we can do is call play
23748
23749      if (type === 'any' && this.muted() !== true) {
23750        promise = this.play();
23751
23752        if (isPromise(promise)) {
23753          promise = promise.catch(muted);
23754        }
23755      } else if (type === 'muted' && this.muted() !== true) {
23756        promise = muted();
23757      } else {
23758        promise = this.play();
23759      }
23760
23761      if (!isPromise(promise)) {
23762        return;
23763      }
23764
23765      return promise.then(function () {
23766        _this5.trigger({
23767          type: 'autoplay-success',
23768          autoplay: type
23769        });
23770      }).catch(function (e) {
23771        _this5.trigger({
23772          type: 'autoplay-failure',
23773          autoplay: type
23774        });
23775      });
23776    }
23777    /**
23778     * Update the internal source caches so that we return the correct source from
23779     * `src()`, `currentSource()`, and `currentSources()`.
23780     *
23781     * > Note: `currentSources` will not be updated if the source that is passed in exists
23782     *         in the current `currentSources` cache.
23783     *
23784     *
23785     * @param {Tech~SourceObject} srcObj
23786     *        A string or object source to update our caches to.
23787     */
23788    ;
23789
23790    _proto.updateSourceCaches_ = function updateSourceCaches_(srcObj) {
23791      if (srcObj === void 0) {
23792        srcObj = '';
23793      }
23794
23795      var src = srcObj;
23796      var type = '';
23797
23798      if (typeof src !== 'string') {
23799        src = srcObj.src;
23800        type = srcObj.type;
23801      } // make sure all the caches are set to default values
23802      // to prevent null checking
23803
23804
23805      this.cache_.source = this.cache_.source || {};
23806      this.cache_.sources = this.cache_.sources || []; // try to get the type of the src that was passed in
23807
23808      if (src && !type) {
23809        type = findMimetype(this, src);
23810      } // update `currentSource` cache always
23811
23812
23813      this.cache_.source = mergeOptions({}, srcObj, {
23814        src: src,
23815        type: type
23816      });
23817      var matchingSources = this.cache_.sources.filter(function (s) {
23818        return s.src && s.src === src;
23819      });
23820      var sourceElSources = [];
23821      var sourceEls = this.$$('source');
23822      var matchingSourceEls = [];
23823
23824      for (var i = 0; i < sourceEls.length; i++) {
23825        var sourceObj = getAttributes(sourceEls[i]);
23826        sourceElSources.push(sourceObj);
23827
23828        if (sourceObj.src && sourceObj.src === src) {
23829          matchingSourceEls.push(sourceObj.src);
23830        }
23831      } // if we have matching source els but not matching sources
23832      // the current source cache is not up to date
23833
23834
23835      if (matchingSourceEls.length && !matchingSources.length) {
23836        this.cache_.sources = sourceElSources; // if we don't have matching source or source els set the
23837        // sources cache to the `currentSource` cache
23838      } else if (!matchingSources.length) {
23839        this.cache_.sources = [this.cache_.source];
23840      } // update the tech `src` cache
23841
23842
23843      this.cache_.src = src;
23844    }
23845    /**
23846     * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
23847     * causing the media element to reload.
23848     *
23849     * It will fire for the initial source and each subsequent source.
23850     * This event is a custom event from Video.js and is triggered by the {@link Tech}.
23851     *
23852     * The event object for this event contains a `src` property that will contain the source
23853     * that was available when the event was triggered. This is generally only necessary if Video.js
23854     * is switching techs while the source was being changed.
23855     *
23856     * It is also fired when `load` is called on the player (or media element)
23857     * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
23858     * says that the resource selection algorithm needs to be aborted and restarted.
23859     * In this case, it is very likely that the `src` property will be set to the
23860     * empty string `""` to indicate we do not know what the source will be but
23861     * that it is changing.
23862     *
23863     * *This event is currently still experimental and may change in minor releases.*
23864     * __To use this, pass `enableSourceset` option to the player.__
23865     *
23866     * @event Player#sourceset
23867     * @type {EventTarget~Event}
23868     * @prop {string} src
23869     *                The source url available when the `sourceset` was triggered.
23870     *                It will be an empty string if we cannot know what the source is
23871     *                but know that the source will change.
23872     */
23873
23874    /**
23875     * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
23876     *
23877     * @fires Player#sourceset
23878     * @listens Tech#sourceset
23879     * @private
23880     */
23881    ;
23882
23883    _proto.handleTechSourceset_ = function handleTechSourceset_(event) {
23884      var _this6 = this;
23885
23886      // only update the source cache when the source
23887      // was not updated using the player api
23888      if (!this.changingSrc_) {
23889        var updateSourceCaches = function updateSourceCaches(src) {
23890          return _this6.updateSourceCaches_(src);
23891        };
23892
23893        var playerSrc = this.currentSource().src;
23894        var eventSrc = event.src; // if we have a playerSrc that is not a blob, and a tech src that is a blob
23895
23896        if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) {
23897          // if both the tech source and the player source were updated we assume
23898          // something like @videojs/http-streaming did the sourceset and skip updating the source cache.
23899          if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) {
23900            updateSourceCaches = function updateSourceCaches() {};
23901          }
23902        } // update the source to the intial source right away
23903        // in some cases this will be empty string
23904
23905
23906        updateSourceCaches(eventSrc); // if the `sourceset` `src` was an empty string
23907        // wait for a `loadstart` to update the cache to `currentSrc`.
23908        // If a sourceset happens before a `loadstart`, we reset the state
23909        // as this function will be called again.
23910
23911        if (!event.src) {
23912          var updateCache = function updateCache(e) {
23913            if (e.type !== 'sourceset') {
23914              var techSrc = _this6.techGet('currentSrc');
23915
23916              _this6.lastSource_.tech = techSrc;
23917
23918              _this6.updateSourceCaches_(techSrc);
23919            }
23920
23921            _this6.tech_.off(['sourceset', 'loadstart'], updateCache);
23922          };
23923
23924          this.tech_.one(['sourceset', 'loadstart'], updateCache);
23925        }
23926      }
23927
23928      this.lastSource_ = {
23929        player: this.currentSource().src,
23930        tech: event.src
23931      };
23932      this.trigger({
23933        src: event.src,
23934        type: 'sourceset'
23935      });
23936    }
23937    /**
23938     * Add/remove the vjs-has-started class
23939     *
23940     * @fires Player#firstplay
23941     *
23942     * @param {boolean} request
23943     *        - true: adds the class
23944     *        - false: remove the class
23945     *
23946     * @return {boolean}
23947     *         the boolean value of hasStarted_
23948     */
23949    ;
23950
23951    _proto.hasStarted = function hasStarted(request) {
23952      if (request === undefined) {
23953        // act as getter, if we have no request to change
23954        return this.hasStarted_;
23955      }
23956
23957      if (request === this.hasStarted_) {
23958        return;
23959      }
23960
23961      this.hasStarted_ = request;
23962
23963      if (this.hasStarted_) {
23964        this.addClass('vjs-has-started');
23965        this.trigger('firstplay');
23966      } else {
23967        this.removeClass('vjs-has-started');
23968      }
23969    }
23970    /**
23971     * Fired whenever the media begins or resumes playback
23972     *
23973     * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
23974     * @fires Player#play
23975     * @listens Tech#play
23976     * @private
23977     */
23978    ;
23979
23980    _proto.handleTechPlay_ = function handleTechPlay_() {
23981      this.removeClass('vjs-ended');
23982      this.removeClass('vjs-paused');
23983      this.addClass('vjs-playing'); // hide the poster when the user hits play
23984
23985      this.hasStarted(true);
23986      /**
23987       * Triggered whenever an {@link Tech#play} event happens. Indicates that
23988       * playback has started or resumed.
23989       *
23990       * @event Player#play
23991       * @type {EventTarget~Event}
23992       */
23993
23994      this.trigger('play');
23995    }
23996    /**
23997     * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
23998     *
23999     * If there were any events queued while the playback rate was zero, fire
24000     * those events now.
24001     *
24002     * @private
24003     * @method Player#handleTechRateChange_
24004     * @fires Player#ratechange
24005     * @listens Tech#ratechange
24006     */
24007    ;
24008
24009    _proto.handleTechRateChange_ = function handleTechRateChange_() {
24010      if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
24011        this.queuedCallbacks_.forEach(function (queued) {
24012          return queued.callback(queued.event);
24013        });
24014        this.queuedCallbacks_ = [];
24015      }
24016
24017      this.cache_.lastPlaybackRate = this.tech_.playbackRate();
24018      /**
24019       * Fires when the playing speed of the audio/video is changed
24020       *
24021       * @event Player#ratechange
24022       * @type {event}
24023       */
24024
24025      this.trigger('ratechange');
24026    }
24027    /**
24028     * Retrigger the `waiting` event that was triggered by the {@link Tech}.
24029     *
24030     * @fires Player#waiting
24031     * @listens Tech#waiting
24032     * @private
24033     */
24034    ;
24035
24036    _proto.handleTechWaiting_ = function handleTechWaiting_() {
24037      var _this7 = this;
24038
24039      this.addClass('vjs-waiting');
24040      /**
24041       * A readyState change on the DOM element has caused playback to stop.
24042       *
24043       * @event Player#waiting
24044       * @type {EventTarget~Event}
24045       */
24046
24047      this.trigger('waiting'); // Browsers may emit a timeupdate event after a waiting event. In order to prevent
24048      // premature removal of the waiting class, wait for the time to change.
24049
24050      var timeWhenWaiting = this.currentTime();
24051
24052      var timeUpdateListener = function timeUpdateListener() {
24053        if (timeWhenWaiting !== _this7.currentTime()) {
24054          _this7.removeClass('vjs-waiting');
24055
24056          _this7.off('timeupdate', timeUpdateListener);
24057        }
24058      };
24059
24060      this.on('timeupdate', timeUpdateListener);
24061    }
24062    /**
24063     * Retrigger the `canplay` event that was triggered by the {@link Tech}.
24064     * > Note: This is not consistent between browsers. See #1351
24065     *
24066     * @fires Player#canplay
24067     * @listens Tech#canplay
24068     * @private
24069     */
24070    ;
24071
24072    _proto.handleTechCanPlay_ = function handleTechCanPlay_() {
24073      this.removeClass('vjs-waiting');
24074      /**
24075       * The media has a readyState of HAVE_FUTURE_DATA or greater.
24076       *
24077       * @event Player#canplay
24078       * @type {EventTarget~Event}
24079       */
24080
24081      this.trigger('canplay');
24082    }
24083    /**
24084     * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
24085     *
24086     * @fires Player#canplaythrough
24087     * @listens Tech#canplaythrough
24088     * @private
24089     */
24090    ;
24091
24092    _proto.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() {
24093      this.removeClass('vjs-waiting');
24094      /**
24095       * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
24096       * entire media file can be played without buffering.
24097       *
24098       * @event Player#canplaythrough
24099       * @type {EventTarget~Event}
24100       */
24101
24102      this.trigger('canplaythrough');
24103    }
24104    /**
24105     * Retrigger the `playing` event that was triggered by the {@link Tech}.
24106     *
24107     * @fires Player#playing
24108     * @listens Tech#playing
24109     * @private
24110     */
24111    ;
24112
24113    _proto.handleTechPlaying_ = function handleTechPlaying_() {
24114      this.removeClass('vjs-waiting');
24115      /**
24116       * The media is no longer blocked from playback, and has started playing.
24117       *
24118       * @event Player#playing
24119       * @type {EventTarget~Event}
24120       */
24121
24122      this.trigger('playing');
24123    }
24124    /**
24125     * Retrigger the `seeking` event that was triggered by the {@link Tech}.
24126     *
24127     * @fires Player#seeking
24128     * @listens Tech#seeking
24129     * @private
24130     */
24131    ;
24132
24133    _proto.handleTechSeeking_ = function handleTechSeeking_() {
24134      this.addClass('vjs-seeking');
24135      /**
24136       * Fired whenever the player is jumping to a new time
24137       *
24138       * @event Player#seeking
24139       * @type {EventTarget~Event}
24140       */
24141
24142      this.trigger('seeking');
24143    }
24144    /**
24145     * Retrigger the `seeked` event that was triggered by the {@link Tech}.
24146     *
24147     * @fires Player#seeked
24148     * @listens Tech#seeked
24149     * @private
24150     */
24151    ;
24152
24153    _proto.handleTechSeeked_ = function handleTechSeeked_() {
24154      this.removeClass('vjs-seeking');
24155      this.removeClass('vjs-ended');
24156      /**
24157       * Fired when the player has finished jumping to a new time
24158       *
24159       * @event Player#seeked
24160       * @type {EventTarget~Event}
24161       */
24162
24163      this.trigger('seeked');
24164    }
24165    /**
24166     * Retrigger the `firstplay` event that was triggered by the {@link Tech}.
24167     *
24168     * @fires Player#firstplay
24169     * @listens Tech#firstplay
24170     * @deprecated As of 6.0 firstplay event is deprecated.
24171     *             As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated.
24172     * @private
24173     */
24174    ;
24175
24176    _proto.handleTechFirstPlay_ = function handleTechFirstPlay_() {
24177      // If the first starttime attribute is specified
24178      // then we will start at the given offset in seconds
24179      if (this.options_.starttime) {
24180        log.warn('Passing the `starttime` option to the player will be deprecated in 6.0');
24181        this.currentTime(this.options_.starttime);
24182      }
24183
24184      this.addClass('vjs-has-started');
24185      /**
24186       * Fired the first time a video is played. Not part of the HLS spec, and this is
24187       * probably not the best implementation yet, so use sparingly. If you don't have a
24188       * reason to prevent playback, use `myPlayer.one('play');` instead.
24189       *
24190       * @event Player#firstplay
24191       * @deprecated As of 6.0 firstplay event is deprecated.
24192       * @type {EventTarget~Event}
24193       */
24194
24195      this.trigger('firstplay');
24196    }
24197    /**
24198     * Retrigger the `pause` event that was triggered by the {@link Tech}.
24199     *
24200     * @fires Player#pause
24201     * @listens Tech#pause
24202     * @private
24203     */
24204    ;
24205
24206    _proto.handleTechPause_ = function handleTechPause_() {
24207      this.removeClass('vjs-playing');
24208      this.addClass('vjs-paused');
24209      /**
24210       * Fired whenever the media has been paused
24211       *
24212       * @event Player#pause
24213       * @type {EventTarget~Event}
24214       */
24215
24216      this.trigger('pause');
24217    }
24218    /**
24219     * Retrigger the `ended` event that was triggered by the {@link Tech}.
24220     *
24221     * @fires Player#ended
24222     * @listens Tech#ended
24223     * @private
24224     */
24225    ;
24226
24227    _proto.handleTechEnded_ = function handleTechEnded_() {
24228      this.addClass('vjs-ended');
24229
24230      if (this.options_.loop) {
24231        this.currentTime(0);
24232        this.play();
24233      } else if (!this.paused()) {
24234        this.pause();
24235      }
24236      /**
24237       * Fired when the end of the media resource is reached (currentTime == duration)
24238       *
24239       * @event Player#ended
24240       * @type {EventTarget~Event}
24241       */
24242
24243
24244      this.trigger('ended');
24245    }
24246    /**
24247     * Fired when the duration of the media resource is first known or changed
24248     *
24249     * @listens Tech#durationchange
24250     * @private
24251     */
24252    ;
24253
24254    _proto.handleTechDurationChange_ = function handleTechDurationChange_() {
24255      this.duration(this.techGet_('duration'));
24256    }
24257    /**
24258     * Handle a click on the media element to play/pause
24259     *
24260     * @param {EventTarget~Event} event
24261     *        the event that caused this function to trigger
24262     *
24263     * @listens Tech#mousedown
24264     * @private
24265     */
24266    ;
24267
24268    _proto.handleTechClick_ = function handleTechClick_(event) {
24269      if (!isSingleLeftClick(event)) {
24270        return;
24271      } // When controls are disabled a click should not toggle playback because
24272      // the click is considered a control
24273
24274
24275      if (!this.controls_) {
24276        return;
24277      }
24278
24279      if (this.paused()) {
24280        silencePromise(this.play());
24281      } else {
24282        this.pause();
24283      }
24284    }
24285    /**
24286     * Handle a double-click on the media element to enter/exit fullscreen
24287     *
24288     * @param {EventTarget~Event} event
24289     *        the event that caused this function to trigger
24290     *
24291     * @listens Tech#dblclick
24292     * @private
24293     */
24294    ;
24295
24296    _proto.handleTechDoubleClick_ = function handleTechDoubleClick_(event) {
24297      if (!this.controls_) {
24298        return;
24299      } // we do not want to toggle fullscreen state
24300      // when double-clicking inside a control bar or a modal
24301
24302
24303      var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) {
24304        return el.contains(event.target);
24305      });
24306
24307      if (!inAllowedEls) {
24308        /*
24309         * options.userActions.doubleClick
24310         *
24311         * If `undefined` or `true`, double-click toggles fullscreen if controls are present
24312         * Set to `false` to disable double-click handling
24313         * Set to a function to substitute an external double-click handler
24314         */
24315        if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) {
24316          if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') {
24317            this.options_.userActions.doubleClick.call(this, event);
24318          } else if (this.isFullscreen()) {
24319            this.exitFullscreen();
24320          } else {
24321            this.requestFullscreen();
24322          }
24323        }
24324      }
24325    }
24326    /**
24327     * Handle a tap on the media element. It will toggle the user
24328     * activity state, which hides and shows the controls.
24329     *
24330     * @listens Tech#tap
24331     * @private
24332     */
24333    ;
24334
24335    _proto.handleTechTap_ = function handleTechTap_() {
24336      this.userActive(!this.userActive());
24337    }
24338    /**
24339     * Handle touch to start
24340     *
24341     * @listens Tech#touchstart
24342     * @private
24343     */
24344    ;
24345
24346    _proto.handleTechTouchStart_ = function handleTechTouchStart_() {
24347      this.userWasActive = this.userActive();
24348    }
24349    /**
24350     * Handle touch to move
24351     *
24352     * @listens Tech#touchmove
24353     * @private
24354     */
24355    ;
24356
24357    _proto.handleTechTouchMove_ = function handleTechTouchMove_() {
24358      if (this.userWasActive) {
24359        this.reportUserActivity();
24360      }
24361    }
24362    /**
24363     * Handle touch to end
24364     *
24365     * @param {EventTarget~Event} event
24366     *        the touchend event that triggered
24367     *        this function
24368     *
24369     * @listens Tech#touchend
24370     * @private
24371     */
24372    ;
24373
24374    _proto.handleTechTouchEnd_ = function handleTechTouchEnd_(event) {
24375      // Stop the mouse events from also happening
24376      event.preventDefault();
24377    }
24378    /**
24379     * native click events on the SWF aren't triggered on IE11, Win8.1RT
24380     * use stageclick events triggered from inside the SWF instead
24381     *
24382     * @private
24383     * @listens stageclick
24384     */
24385    ;
24386
24387    _proto.handleStageClick_ = function handleStageClick_() {
24388      this.reportUserActivity();
24389    }
24390    /**
24391     * @private
24392     */
24393    ;
24394
24395    _proto.toggleFullscreenClass_ = function toggleFullscreenClass_() {
24396      if (this.isFullscreen()) {
24397        this.addClass('vjs-fullscreen');
24398      } else {
24399        this.removeClass('vjs-fullscreen');
24400      }
24401    }
24402    /**
24403     * when the document fschange event triggers it calls this
24404     */
24405    ;
24406
24407    _proto.documentFullscreenChange_ = function documentFullscreenChange_(e) {
24408      var fsApi = FullscreenApi;
24409      var el = this.el();
24410      var isFs = document[fsApi.fullscreenElement] === el;
24411
24412      if (!isFs && el.matches) {
24413        isFs = el.matches(':' + fsApi.fullscreen);
24414      } else if (!isFs && el.msMatchesSelector) {
24415        isFs = el.msMatchesSelector(':' + fsApi.fullscreen);
24416      }
24417
24418      this.isFullscreen(isFs); // If cancelling fullscreen, remove event listener.
24419
24420      if (this.isFullscreen() === false) {
24421        off(document, fsApi.fullscreenchange, this.boundDocumentFullscreenChange_);
24422      }
24423
24424      if (!prefixedAPI) {
24425        /**
24426         * @event Player#fullscreenchange
24427         * @type {EventTarget~Event}
24428         */
24429        this.trigger('fullscreenchange');
24430      }
24431    }
24432    /**
24433     * Handle Tech Fullscreen Change
24434     *
24435     * @param {EventTarget~Event} event
24436     *        the fullscreenchange event that triggered this function
24437     *
24438     * @param {Object} data
24439     *        the data that was sent with the event
24440     *
24441     * @private
24442     * @listens Tech#fullscreenchange
24443     * @fires Player#fullscreenchange
24444     */
24445    ;
24446
24447    _proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
24448      if (data) {
24449        this.isFullscreen(data.isFullscreen);
24450      }
24451      /**
24452       * Fired when going in and out of fullscreen.
24453       *
24454       * @event Player#fullscreenchange
24455       * @type {EventTarget~Event}
24456       */
24457
24458
24459      this.trigger('fullscreenchange');
24460    }
24461    /**
24462     * Fires when an error occurred during the loading of an audio/video.
24463     *
24464     * @private
24465     * @listens Tech#error
24466     */
24467    ;
24468
24469    _proto.handleTechError_ = function handleTechError_() {
24470      var error = this.tech_.error();
24471      this.error(error);
24472    }
24473    /**
24474     * Retrigger the `textdata` event that was triggered by the {@link Tech}.
24475     *
24476     * @fires Player#textdata
24477     * @listens Tech#textdata
24478     * @private
24479     */
24480    ;
24481
24482    _proto.handleTechTextData_ = function handleTechTextData_() {
24483      var data = null;
24484
24485      if (arguments.length > 1) {
24486        data = arguments[1];
24487      }
24488      /**
24489       * Fires when we get a textdata event from tech
24490       *
24491       * @event Player#textdata
24492       * @type {EventTarget~Event}
24493       */
24494
24495
24496      this.trigger('textdata', data);
24497    }
24498    /**
24499     * Get object for cached values.
24500     *
24501     * @return {Object}
24502     *         get the current object cache
24503     */
24504    ;
24505
24506    _proto.getCache = function getCache() {
24507      return this.cache_;
24508    }
24509    /**
24510     * Resets the internal cache object.
24511     *
24512     * Using this function outside the player constructor or reset method may
24513     * have unintended side-effects.
24514     *
24515     * @private
24516     */
24517    ;
24518
24519    _proto.resetCache_ = function resetCache_() {
24520      this.cache_ = {
24521        // Right now, the currentTime is not _really_ cached because it is always
24522        // retrieved from the tech (see: currentTime). However, for completeness,
24523        // we set it to zero here to ensure that if we do start actually caching
24524        // it, we reset it along with everything else.
24525        currentTime: 0,
24526        inactivityTimeout: this.options_.inactivityTimeout,
24527        duration: NaN,
24528        lastVolume: 1,
24529        lastPlaybackRate: this.defaultPlaybackRate(),
24530        media: null,
24531        src: '',
24532        source: {},
24533        sources: [],
24534        volume: 1
24535      };
24536    }
24537    /**
24538     * Pass values to the playback tech
24539     *
24540     * @param {string} [method]
24541     *        the method to call
24542     *
24543     * @param {Object} arg
24544     *        the argument to pass
24545     *
24546     * @private
24547     */
24548    ;
24549
24550    _proto.techCall_ = function techCall_(method, arg) {
24551      // If it's not ready yet, call method when it is
24552      this.ready(function () {
24553        if (method in allowedSetters) {
24554          return set$1(this.middleware_, this.tech_, method, arg);
24555        } else if (method in allowedMediators) {
24556          return mediate(this.middleware_, this.tech_, method, arg);
24557        }
24558
24559        try {
24560          if (this.tech_) {
24561            this.tech_[method](arg);
24562          }
24563        } catch (e) {
24564          log(e);
24565          throw e;
24566        }
24567      }, true);
24568    }
24569    /**
24570     * Get calls can't wait for the tech, and sometimes don't need to.
24571     *
24572     * @param {string} method
24573     *        Tech method
24574     *
24575     * @return {Function|undefined}
24576     *         the method or undefined
24577     *
24578     * @private
24579     */
24580    ;
24581
24582    _proto.techGet_ = function techGet_(method) {
24583      if (!this.tech_ || !this.tech_.isReady_) {
24584        return;
24585      }
24586
24587      if (method in allowedGetters) {
24588        return get(this.middleware_, this.tech_, method);
24589      } else if (method in allowedMediators) {
24590        return mediate(this.middleware_, this.tech_, method);
24591      } // Flash likes to die and reload when you hide or reposition it.
24592      // In these cases the object methods go away and we get errors.
24593      // When that happens we'll catch the errors and inform tech that it's not ready any more.
24594
24595
24596      try {
24597        return this.tech_[method]();
24598      } catch (e) {
24599        // When building additional tech libs, an expected method may not be defined yet
24600        if (this.tech_[method] === undefined) {
24601          log("Video.js: " + method + " method not defined for " + this.techName_ + " playback technology.", e);
24602          throw e;
24603        } // When a method isn't available on the object it throws a TypeError
24604
24605
24606        if (e.name === 'TypeError') {
24607          log("Video.js: " + method + " unavailable on " + this.techName_ + " playback technology element.", e);
24608          this.tech_.isReady_ = false;
24609          throw e;
24610        } // If error unknown, just log and throw
24611
24612
24613        log(e);
24614        throw e;
24615      }
24616    }
24617    /**
24618     * Attempt to begin playback at the first opportunity.
24619     *
24620     * @return {Promise|undefined}
24621     *         Returns a promise if the browser supports Promises (or one
24622     *         was passed in as an option). This promise will be resolved on
24623     *         the return value of play. If this is undefined it will fulfill the
24624     *         promise chain otherwise the promise chain will be fulfilled when
24625     *         the promise from play is fulfilled.
24626     */
24627    ;
24628
24629    _proto.play = function play() {
24630      var _this8 = this;
24631
24632      var PromiseClass = this.options_.Promise || window$1.Promise;
24633
24634      if (PromiseClass) {
24635        return new PromiseClass(function (resolve) {
24636          _this8.play_(resolve);
24637        });
24638      }
24639
24640      return this.play_();
24641    }
24642    /**
24643     * The actual logic for play, takes a callback that will be resolved on the
24644     * return value of play. This allows us to resolve to the play promise if there
24645     * is one on modern browsers.
24646     *
24647     * @private
24648     * @param {Function} [callback]
24649     *        The callback that should be called when the techs play is actually called
24650     */
24651    ;
24652
24653    _proto.play_ = function play_(callback) {
24654      var _this9 = this;
24655
24656      if (callback === void 0) {
24657        callback = silencePromise;
24658      }
24659
24660      this.playCallbacks_.push(callback);
24661      var isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc())); // treat calls to play_ somewhat like the `one` event function
24662
24663      if (this.waitToPlay_) {
24664        this.off(['ready', 'loadstart'], this.waitToPlay_);
24665        this.waitToPlay_ = null;
24666      } // if the player/tech is not ready or the src itself is not ready
24667      // queue up a call to play on `ready` or `loadstart`
24668
24669
24670      if (!this.isReady_ || !isSrcReady) {
24671        this.waitToPlay_ = function (e) {
24672          _this9.play_();
24673        };
24674
24675        this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
24676        // in that case, we need to prime the video element by calling load so it'll be ready in time
24677
24678        if (!isSrcReady && (IS_ANY_SAFARI || IS_IOS)) {
24679          this.load();
24680        }
24681
24682        return;
24683      } // If the player/tech is ready and we have a source, we can attempt playback.
24684
24685
24686      var val = this.techGet_('play'); // play was terminated if the returned value is null
24687
24688      if (val === null) {
24689        this.runPlayTerminatedQueue_();
24690      } else {
24691        this.runPlayCallbacks_(val);
24692      }
24693    }
24694    /**
24695     * These functions will be run when if play is terminated. If play
24696     * runPlayCallbacks_ is run these function will not be run. This allows us
24697     * to differenciate between a terminated play and an actual call to play.
24698     */
24699    ;
24700
24701    _proto.runPlayTerminatedQueue_ = function runPlayTerminatedQueue_() {
24702      var queue = this.playTerminatedQueue_.slice(0);
24703      this.playTerminatedQueue_ = [];
24704      queue.forEach(function (q) {
24705        q();
24706      });
24707    }
24708    /**
24709     * When a callback to play is delayed we have to run these
24710     * callbacks when play is actually called on the tech. This function
24711     * runs the callbacks that were delayed and accepts the return value
24712     * from the tech.
24713     *
24714     * @param {undefined|Promise} val
24715     *        The return value from the tech.
24716     */
24717    ;
24718
24719    _proto.runPlayCallbacks_ = function runPlayCallbacks_(val) {
24720      var callbacks = this.playCallbacks_.slice(0);
24721      this.playCallbacks_ = []; // clear play terminatedQueue since we finished a real play
24722
24723      this.playTerminatedQueue_ = [];
24724      callbacks.forEach(function (cb) {
24725        cb(val);
24726      });
24727    }
24728    /**
24729     * Pause the video playback
24730     *
24731     * @return {Player}
24732     *         A reference to the player object this function was called on
24733     */
24734    ;
24735
24736    _proto.pause = function pause() {
24737      this.techCall_('pause');
24738    }
24739    /**
24740     * Check if the player is paused or has yet to play
24741     *
24742     * @return {boolean}
24743     *         - false: if the media is currently playing
24744     *         - true: if media is not currently playing
24745     */
24746    ;
24747
24748    _proto.paused = function paused() {
24749      // The initial state of paused should be true (in Safari it's actually false)
24750      return this.techGet_('paused') === false ? false : true;
24751    }
24752    /**
24753     * Get a TimeRange object representing the current ranges of time that the user
24754     * has played.
24755     *
24756     * @return {TimeRange}
24757     *         A time range object that represents all the increments of time that have
24758     *         been played.
24759     */
24760    ;
24761
24762    _proto.played = function played() {
24763      return this.techGet_('played') || createTimeRanges(0, 0);
24764    }
24765    /**
24766     * Returns whether or not the user is "scrubbing". Scrubbing is
24767     * when the user has clicked the progress bar handle and is
24768     * dragging it along the progress bar.
24769     *
24770     * @param {boolean} [isScrubbing]
24771     *        whether the user is or is not scrubbing
24772     *
24773     * @return {boolean}
24774     *         The value of scrubbing when getting
24775     */
24776    ;
24777
24778    _proto.scrubbing = function scrubbing(isScrubbing) {
24779      if (typeof isScrubbing === 'undefined') {
24780        return this.scrubbing_;
24781      }
24782
24783      this.scrubbing_ = !!isScrubbing;
24784
24785      if (isScrubbing) {
24786        this.addClass('vjs-scrubbing');
24787      } else {
24788        this.removeClass('vjs-scrubbing');
24789      }
24790    }
24791    /**
24792     * Get or set the current time (in seconds)
24793     *
24794     * @param {number|string} [seconds]
24795     *        The time to seek to in seconds
24796     *
24797     * @return {number}
24798     *         - the current time in seconds when getting
24799     */
24800    ;
24801
24802    _proto.currentTime = function currentTime(seconds) {
24803      if (typeof seconds !== 'undefined') {
24804        if (seconds < 0) {
24805          seconds = 0;
24806        }
24807
24808        this.techCall_('setCurrentTime', seconds);
24809        return;
24810      } // cache last currentTime and return. default to 0 seconds
24811      //
24812      // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
24813      // currentTime when scrubbing, but may not provide much performance benefit afterall.
24814      // Should be tested. Also something has to read the actual current time or the cache will
24815      // never get updated.
24816
24817
24818      this.cache_.currentTime = this.techGet_('currentTime') || 0;
24819      return this.cache_.currentTime;
24820    }
24821    /**
24822     * Normally gets the length in time of the video in seconds;
24823     * in all but the rarest use cases an argument will NOT be passed to the method
24824     *
24825     * > **NOTE**: The video must have started loading before the duration can be
24826     * known, and in the case of Flash, may not be known until the video starts
24827     * playing.
24828     *
24829     * @fires Player#durationchange
24830     *
24831     * @param {number} [seconds]
24832     *        The duration of the video to set in seconds
24833     *
24834     * @return {number}
24835     *         - The duration of the video in seconds when getting
24836     */
24837    ;
24838
24839    _proto.duration = function duration(seconds) {
24840      if (seconds === undefined) {
24841        // return NaN if the duration is not known
24842        return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
24843      }
24844
24845      seconds = parseFloat(seconds); // Standardize on Infinity for signaling video is live
24846
24847      if (seconds < 0) {
24848        seconds = Infinity;
24849      }
24850
24851      if (seconds !== this.cache_.duration) {
24852        // Cache the last set value for optimized scrubbing (esp. Flash)
24853        this.cache_.duration = seconds;
24854
24855        if (seconds === Infinity) {
24856          this.addClass('vjs-live');
24857
24858          if (this.options_.liveui && this.player_.liveTracker) {
24859            this.addClass('vjs-liveui');
24860          }
24861        } else {
24862          this.removeClass('vjs-live');
24863          this.removeClass('vjs-liveui');
24864        }
24865
24866        if (!isNaN(seconds)) {
24867          // Do not fire durationchange unless the duration value is known.
24868          // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
24869
24870          /**
24871           * @event Player#durationchange
24872           * @type {EventTarget~Event}
24873           */
24874          this.trigger('durationchange');
24875        }
24876      }
24877    }
24878    /**
24879     * Calculates how much time is left in the video. Not part
24880     * of the native video API.
24881     *
24882     * @return {number}
24883     *         The time remaining in seconds
24884     */
24885    ;
24886
24887    _proto.remainingTime = function remainingTime() {
24888      return this.duration() - this.currentTime();
24889    }
24890    /**
24891     * A remaining time function that is intented to be used when
24892     * the time is to be displayed directly to the user.
24893     *
24894     * @return {number}
24895     *         The rounded time remaining in seconds
24896     */
24897    ;
24898
24899    _proto.remainingTimeDisplay = function remainingTimeDisplay() {
24900      return Math.floor(this.duration()) - Math.floor(this.currentTime());
24901    } //
24902    // Kind of like an array of portions of the video that have been downloaded.
24903
24904    /**
24905     * Get a TimeRange object with an array of the times of the video
24906     * that have been downloaded. If you just want the percent of the
24907     * video that's been downloaded, use bufferedPercent.
24908     *
24909     * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
24910     *
24911     * @return {TimeRange}
24912     *         A mock TimeRange object (following HTML spec)
24913     */
24914    ;
24915
24916    _proto.buffered = function buffered() {
24917      var buffered = this.techGet_('buffered');
24918
24919      if (!buffered || !buffered.length) {
24920        buffered = createTimeRanges(0, 0);
24921      }
24922
24923      return buffered;
24924    }
24925    /**
24926     * Get the percent (as a decimal) of the video that's been downloaded.
24927     * This method is not a part of the native HTML video API.
24928     *
24929     * @return {number}
24930     *         A decimal between 0 and 1 representing the percent
24931     *         that is buffered 0 being 0% and 1 being 100%
24932     */
24933    ;
24934
24935    _proto.bufferedPercent = function bufferedPercent$$1() {
24936      return bufferedPercent(this.buffered(), this.duration());
24937    }
24938    /**
24939     * Get the ending time of the last buffered time range
24940     * This is used in the progress bar to encapsulate all time ranges.
24941     *
24942     * @return {number}
24943     *         The end of the last buffered time range
24944     */
24945    ;
24946
24947    _proto.bufferedEnd = function bufferedEnd() {
24948      var buffered = this.buffered();
24949      var duration = this.duration();
24950      var end = buffered.end(buffered.length - 1);
24951
24952      if (end > duration) {
24953        end = duration;
24954      }
24955
24956      return end;
24957    }
24958    /**
24959     * Get or set the current volume of the media
24960     *
24961     * @param  {number} [percentAsDecimal]
24962     *         The new volume as a decimal percent:
24963     *         - 0 is muted/0%/off
24964     *         - 1.0 is 100%/full
24965     *         - 0.5 is half volume or 50%
24966     *
24967     * @return {number}
24968     *         The current volume as a percent when getting
24969     */
24970    ;
24971
24972    _proto.volume = function volume(percentAsDecimal) {
24973      var vol;
24974
24975      if (percentAsDecimal !== undefined) {
24976        // Force value to between 0 and 1
24977        vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
24978        this.cache_.volume = vol;
24979        this.techCall_('setVolume', vol);
24980
24981        if (vol > 0) {
24982          this.lastVolume_(vol);
24983        }
24984
24985        return;
24986      } // Default to 1 when returning current volume.
24987
24988
24989      vol = parseFloat(this.techGet_('volume'));
24990      return isNaN(vol) ? 1 : vol;
24991    }
24992    /**
24993     * Get the current muted state, or turn mute on or off
24994     *
24995     * @param {boolean} [muted]
24996     *        - true to mute
24997     *        - false to unmute
24998     *
24999     * @return {boolean}
25000     *         - true if mute is on and getting
25001     *         - false if mute is off and getting
25002     */
25003    ;
25004
25005    _proto.muted = function muted(_muted) {
25006      if (_muted !== undefined) {
25007        this.techCall_('setMuted', _muted);
25008        return;
25009      }
25010
25011      return this.techGet_('muted') || false;
25012    }
25013    /**
25014     * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
25015     * indicates the state of muted on initial playback.
25016     *
25017     * ```js
25018     *   var myPlayer = videojs('some-player-id');
25019     *
25020     *   myPlayer.src("http://www.example.com/path/to/video.mp4");
25021     *
25022     *   // get, should be false
25023     *   console.log(myPlayer.defaultMuted());
25024     *   // set to true
25025     *   myPlayer.defaultMuted(true);
25026     *   // get should be true
25027     *   console.log(myPlayer.defaultMuted());
25028     * ```
25029     *
25030     * @param {boolean} [defaultMuted]
25031     *        - true to mute
25032     *        - false to unmute
25033     *
25034     * @return {boolean|Player}
25035     *         - true if defaultMuted is on and getting
25036     *         - false if defaultMuted is off and getting
25037     *         - A reference to the current player when setting
25038     */
25039    ;
25040
25041    _proto.defaultMuted = function defaultMuted(_defaultMuted) {
25042      if (_defaultMuted !== undefined) {
25043        return this.techCall_('setDefaultMuted', _defaultMuted);
25044      }
25045
25046      return this.techGet_('defaultMuted') || false;
25047    }
25048    /**
25049     * Get the last volume, or set it
25050     *
25051     * @param  {number} [percentAsDecimal]
25052     *         The new last volume as a decimal percent:
25053     *         - 0 is muted/0%/off
25054     *         - 1.0 is 100%/full
25055     *         - 0.5 is half volume or 50%
25056     *
25057     * @return {number}
25058     *         the current value of lastVolume as a percent when getting
25059     *
25060     * @private
25061     */
25062    ;
25063
25064    _proto.lastVolume_ = function lastVolume_(percentAsDecimal) {
25065      if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
25066        this.cache_.lastVolume = percentAsDecimal;
25067        return;
25068      }
25069
25070      return this.cache_.lastVolume;
25071    }
25072    /**
25073     * Check if current tech can support native fullscreen
25074     * (e.g. with built in controls like iOS, so not our flash swf)
25075     *
25076     * @return {boolean}
25077     *         if native fullscreen is supported
25078     */
25079    ;
25080
25081    _proto.supportsFullScreen = function supportsFullScreen() {
25082      return this.techGet_('supportsFullScreen') || false;
25083    }
25084    /**
25085     * Check if the player is in fullscreen mode or tell the player that it
25086     * is or is not in fullscreen mode.
25087     *
25088     * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
25089     * property and instead document.fullscreenElement is used. But isFullscreen is
25090     * still a valuable property for internal player workings.
25091     *
25092     * @param  {boolean} [isFS]
25093     *         Set the players current fullscreen state
25094     *
25095     * @return {boolean}
25096     *         - true if fullscreen is on and getting
25097     *         - false if fullscreen is off and getting
25098     */
25099    ;
25100
25101    _proto.isFullscreen = function isFullscreen(isFS) {
25102      if (isFS !== undefined) {
25103        this.isFullscreen_ = !!isFS;
25104        this.toggleFullscreenClass_();
25105        return;
25106      }
25107
25108      if (prefixedAPI) {
25109        var fsApi = FullscreenApi;
25110        var el = this.el();
25111        var isFs = document[fsApi.fullscreenElement] === el;
25112
25113        if (!isFs && el.matches) {
25114          isFs = el.matches(':' + fsApi.fullscreen);
25115        } else if (!isFs && el.msMatchesSelector) {
25116          isFs = el.msMatchesSelector(':' + fsApi.fullscreen);
25117        }
25118
25119        return isFs;
25120      }
25121
25122      return !!this.isFullscreen_;
25123    }
25124    /**
25125     * Increase the size of the video to full screen
25126     * In some browsers, full screen is not supported natively, so it enters
25127     * "full window mode", where the video fills the browser window.
25128     * In browsers and devices that support native full screen, sometimes the
25129     * browser's default controls will be shown, and not the Video.js custom skin.
25130     * This includes most mobile devices (iOS, Android) and older versions of
25131     * Safari.
25132     *
25133     * @fires Player#fullscreenchange
25134     */
25135    ;
25136
25137    _proto.requestFullscreen = function requestFullscreen() {
25138      var fsApi = FullscreenApi;
25139      this.isFullscreen(true);
25140
25141      if (fsApi.requestFullscreen) {
25142        // the browser supports going fullscreen at the element level so we can
25143        // take the controls fullscreen as well as the video
25144        // Trigger fullscreenchange event after change
25145        // We have to specifically add this each time, and remove
25146        // when canceling fullscreen. Otherwise if there's multiple
25147        // players on a page, they would all be reacting to the same fullscreen
25148        // events
25149        on(document, fsApi.fullscreenchange, this.boundDocumentFullscreenChange_);
25150        this.el_[fsApi.requestFullscreen]();
25151      } else if (this.tech_.supportsFullScreen()) {
25152        // we can't take the video.js controls fullscreen but we can go fullscreen
25153        // with native controls
25154        this.techCall_('enterFullScreen');
25155      } else {
25156        // fullscreen isn't supported so we'll just stretch the video element to
25157        // fill the viewport
25158        this.enterFullWindow();
25159        /**
25160         * @event Player#fullscreenchange
25161         * @type {EventTarget~Event}
25162         */
25163
25164        this.trigger('fullscreenchange');
25165      }
25166    }
25167    /**
25168     * Return the video to its normal size after having been in full screen mode
25169     *
25170     * @fires Player#fullscreenchange
25171     */
25172    ;
25173
25174    _proto.exitFullscreen = function exitFullscreen() {
25175      var fsApi = FullscreenApi;
25176      this.isFullscreen(false); // Check for browser element fullscreen support
25177
25178      if (fsApi.requestFullscreen) {
25179        document[fsApi.exitFullscreen]();
25180      } else if (this.tech_.supportsFullScreen()) {
25181        this.techCall_('exitFullScreen');
25182      } else {
25183        this.exitFullWindow();
25184        /**
25185         * @event Player#fullscreenchange
25186         * @type {EventTarget~Event}
25187         */
25188
25189        this.trigger('fullscreenchange');
25190      }
25191    }
25192    /**
25193     * When fullscreen isn't supported we can stretch the
25194     * video container to as wide as the browser will let us.
25195     *
25196     * @fires Player#enterFullWindow
25197     */
25198    ;
25199
25200    _proto.enterFullWindow = function enterFullWindow() {
25201      this.isFullWindow = true; // Storing original doc overflow value to return to when fullscreen is off
25202
25203      this.docOrigOverflow = document.documentElement.style.overflow; // Add listener for esc key to exit fullscreen
25204
25205      on(document, 'keydown', this.boundFullWindowOnEscKey_); // Hide any scroll bars
25206
25207      document.documentElement.style.overflow = 'hidden'; // Apply fullscreen styles
25208
25209      addClass(document.body, 'vjs-full-window');
25210      /**
25211       * @event Player#enterFullWindow
25212       * @type {EventTarget~Event}
25213       */
25214
25215      this.trigger('enterFullWindow');
25216    }
25217    /**
25218     * Check for call to either exit full window or
25219     * full screen on ESC key
25220     *
25221     * @param {string} event
25222     *        Event to check for key press
25223     */
25224    ;
25225
25226    _proto.fullWindowOnEscKey = function fullWindowOnEscKey(event) {
25227      if (keycode.isEventKey(event, 'Esc')) {
25228        if (this.isFullscreen() === true) {
25229          this.exitFullscreen();
25230        } else {
25231          this.exitFullWindow();
25232        }
25233      }
25234    }
25235    /**
25236     * Exit full window
25237     *
25238     * @fires Player#exitFullWindow
25239     */
25240    ;
25241
25242    _proto.exitFullWindow = function exitFullWindow() {
25243      this.isFullWindow = false;
vendor: 47,989 bytes, lines 25244-26757
25244      off(document, 'keydown', this.boundFullWindowOnEscKey_); // Unhide scroll bars.
25245
25246      document.documentElement.style.overflow = this.docOrigOverflow; // Remove fullscreen styles
25247
25248      removeClass(document.body, 'vjs-full-window'); // Resize the box, controller, and poster to original sizes
25249      // this.positionAll();
25250
25251      /**
25252       * @event Player#exitFullWindow
25253       * @type {EventTarget~Event}
25254       */
25255
25256      this.trigger('exitFullWindow');
25257    }
25258    /**
25259     * Called when this Player has focus and a key gets pressed down, or when
25260     * any Component of this player receives a key press that it doesn't handle.
25261     * This allows player-wide hotkeys (either as defined below, or optionally
25262     * by an external function).
25263     *
25264     * @param {EventTarget~Event} event
25265     *        The `keydown` event that caused this function to be called.
25266     *
25267     * @listens keydown
25268     */
25269    ;
25270
25271    _proto.handleKeyDown = function handleKeyDown(event) {
25272      var userActions = this.options_.userActions; // Bail out if hotkeys are not configured.
25273
25274      if (!userActions || !userActions.hotkeys) {
25275        return;
25276      } // Function that determines whether or not to exclude an element from
25277      // hotkeys handling.
25278
25279
25280      var excludeElement = function excludeElement(el) {
25281        var tagName = el.tagName.toLowerCase(); // These tags will be excluded entirely.
25282
25283        var excludedTags = ['textarea']; // Inputs matching these types will still trigger hotkey handling as
25284        // they are not text inputs.
25285
25286        var allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit'];
25287
25288        if (tagName === 'input') {
25289          return allowedInputTypes.indexOf(el.type) === -1;
25290        }
25291
25292        return excludedTags.indexOf(tagName) !== -1;
25293      }; // Bail out if the user is focused on an interactive form element.
25294
25295
25296      if (excludeElement(this.el_.ownerDocument.activeElement)) {
25297        return;
25298      }
25299
25300      if (typeof userActions.hotkeys === 'function') {
25301        userActions.hotkeys.call(this, event);
25302      } else {
25303        this.handleHotkeys(event);
25304      }
25305    }
25306    /**
25307     * Called when this Player receives a hotkey keydown event.
25308     * Supported player-wide hotkeys are:
25309     *
25310     *   f          - toggle fullscreen
25311     *   m          - toggle mute
25312     *   k or Space - toggle play/pause
25313     *
25314     * @param {EventTarget~Event} event
25315     *        The `keydown` event that caused this function to be called.
25316     */
25317    ;
25318
25319    _proto.handleHotkeys = function handleHotkeys(event) {
25320      var hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {}; // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set
25321
25322      var _hotkeys$fullscreenKe = hotkeys.fullscreenKey,
25323          fullscreenKey = _hotkeys$fullscreenKe === void 0 ? function (keydownEvent) {
25324        return keycode.isEventKey(keydownEvent, 'f');
25325      } : _hotkeys$fullscreenKe,
25326          _hotkeys$muteKey = hotkeys.muteKey,
25327          muteKey = _hotkeys$muteKey === void 0 ? function (keydownEvent) {
25328        return keycode.isEventKey(keydownEvent, 'm');
25329      } : _hotkeys$muteKey,
25330          _hotkeys$playPauseKey = hotkeys.playPauseKey,
25331          playPauseKey = _hotkeys$playPauseKey === void 0 ? function (keydownEvent) {
25332        return keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space');
25333      } : _hotkeys$playPauseKey;
25334
25335      if (fullscreenKey.call(this, event)) {
25336        event.preventDefault();
25337        event.stopPropagation();
25338        var FSToggle = Component.getComponent('FullscreenToggle');
25339
25340        if (document[FullscreenApi.fullscreenEnabled] !== false) {
25341          FSToggle.prototype.handleClick.call(this);
25342        }
25343      } else if (muteKey.call(this, event)) {
25344        event.preventDefault();
25345        event.stopPropagation();
25346        var MuteToggle = Component.getComponent('MuteToggle');
25347        MuteToggle.prototype.handleClick.call(this);
25348      } else if (playPauseKey.call(this, event)) {
25349        event.preventDefault();
25350        event.stopPropagation();
25351        var PlayToggle = Component.getComponent('PlayToggle');
25352        PlayToggle.prototype.handleClick.call(this);
25353      }
25354    }
25355    /**
25356     * Check whether the player can play a given mimetype
25357     *
25358     * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
25359     *
25360     * @param {string} type
25361     *        The mimetype to check
25362     *
25363     * @return {string}
25364     *         'probably', 'maybe', or '' (empty string)
25365     */
25366    ;
25367
25368    _proto.canPlayType = function canPlayType(type) {
25369      var can; // Loop through each playback technology in the options order
25370
25371      for (var i = 0, j = this.options_.techOrder; i < j.length; i++) {
25372        var techName = j[i];
25373        var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components.
25374        // Remove once that deprecated behavior is removed.
25375
25376        if (!tech) {
25377          tech = Component.getComponent(techName);
25378        } // Check if the current tech is defined before continuing
25379
25380
25381        if (!tech) {
25382          log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
25383          continue;
25384        } // Check if the browser supports this technology
25385
25386
25387        if (tech.isSupported()) {
25388          can = tech.canPlayType(type);
25389
25390          if (can) {
25391            return can;
25392          }
25393        }
25394      }
25395
25396      return '';
25397    }
25398    /**
25399     * Select source based on tech-order or source-order
25400     * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
25401     * defaults to tech-order selection
25402     *
25403     * @param {Array} sources
25404     *        The sources for a media asset
25405     *
25406     * @return {Object|boolean}
25407     *         Object of source and tech order or false
25408     */
25409    ;
25410
25411    _proto.selectSource = function selectSource(sources) {
25412      var _this10 = this;
25413
25414      // Get only the techs specified in `techOrder` that exist and are supported by the
25415      // current platform
25416      var techs = this.options_.techOrder.map(function (techName) {
25417        return [techName, Tech.getTech(techName)];
25418      }).filter(function (_ref) {
25419        var techName = _ref[0],
25420            tech = _ref[1];
25421
25422        // Check if the current tech is defined before continuing
25423        if (tech) {
25424          // Check if the browser supports this technology
25425          return tech.isSupported();
25426        }
25427
25428        log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech.");
25429        return false;
25430      }); // Iterate over each `innerArray` element once per `outerArray` element and execute
25431      // `tester` with both. If `tester` returns a non-falsy value, exit early and return
25432      // that value.
25433
25434      var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) {
25435        var found;
25436        outerArray.some(function (outerChoice) {
25437          return innerArray.some(function (innerChoice) {
25438            found = tester(outerChoice, innerChoice);
25439
25440            if (found) {
25441              return true;
25442            }
25443          });
25444        });
25445        return found;
25446      };
25447
25448      var foundSourceAndTech;
25449
25450      var flip = function flip(fn) {
25451        return function (a, b) {
25452          return fn(b, a);
25453        };
25454      };
25455
25456      var finder = function finder(_ref2, source) {
25457        var techName = _ref2[0],
25458            tech = _ref2[1];
25459
25460        if (tech.canPlaySource(source, _this10.options_[techName.toLowerCase()])) {
25461          return {
25462            source: source,
25463            tech: techName
25464          };
25465        }
25466      }; // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
25467      // to select from them based on their priority.
25468
25469
25470      if (this.options_.sourceOrder) {
25471        // Source-first ordering
25472        foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
25473      } else {
25474        // Tech-first ordering
25475        foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
25476      }
25477
25478      return foundSourceAndTech || false;
25479    }
25480    /**
25481     * Get or set the video source.
25482     *
25483     * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
25484     *        A SourceObject, an array of SourceObjects, or a string referencing
25485     *        a URL to a media source. It is _highly recommended_ that an object
25486     *        or array of objects is used here, so that source selection
25487     *        algorithms can take the `type` into account.
25488     *
25489     *        If not provided, this method acts as a getter.
25490     *
25491     * @return {string|undefined}
25492     *         If the `source` argument is missing, returns the current source
25493     *         URL. Otherwise, returns nothing/undefined.
25494     */
25495    ;
25496
25497    _proto.src = function src(source) {
25498      var _this11 = this;
25499
25500      // getter usage
25501      if (typeof source === 'undefined') {
25502        return this.cache_.src || '';
25503      } // filter out invalid sources and turn our source into
25504      // an array of source objects
25505
25506
25507      var sources = filterSource(source); // if a source was passed in then it is invalid because
25508      // it was filtered to a zero length Array. So we have to
25509      // show an error
25510
25511      if (!sources.length) {
25512        this.setTimeout(function () {
25513          this.error({
25514            code: 4,
25515            message: this.localize(this.options_.notSupportedMessage)
25516          });
25517        }, 0);
25518        return;
25519      } // intial sources
25520
25521
25522      this.changingSrc_ = true;
25523      this.cache_.sources = sources;
25524      this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware
25525
25526      setSource(this, sources[0], function (middlewareSource, mws) {
25527        _this11.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since
25528        // the source that is selected could be out of order from the cache update above this callback.
25529
25530        _this11.cache_.sources = sources;
25531
25532        _this11.updateSourceCaches_(middlewareSource);
25533
25534        var err = _this11.src_(middlewareSource);
25535
25536        if (err) {
25537          if (sources.length > 1) {
25538            return _this11.src(sources.slice(1));
25539          }
25540
25541          _this11.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers
25542
25543          _this11.setTimeout(function () {
25544            this.error({
25545              code: 4,
25546              message: this.localize(this.options_.notSupportedMessage)
25547            });
25548          }, 0); // we could not find an appropriate tech, but let's still notify the delegate that this is it
25549          // this needs a better comment about why this is needed
25550
25551
25552          _this11.triggerReady();
25553
25554          return;
25555        }
25556
25557        setTech(mws, _this11.tech_);
25558      });
25559    }
25560    /**
25561     * Set the source object on the tech, returns a boolean that indicates whether
25562     * there is a tech that can play the source or not
25563     *
25564     * @param {Tech~SourceObject} source
25565     *        The source object to set on the Tech
25566     *
25567     * @return {boolean}
25568     *         - True if there is no Tech to playback this source
25569     *         - False otherwise
25570     *
25571     * @private
25572     */
25573    ;
25574
25575    _proto.src_ = function src_(source) {
25576      var _this12 = this;
25577
25578      var sourceTech = this.selectSource([source]);
25579
25580      if (!sourceTech) {
25581        return true;
25582      }
25583
25584      if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
25585        this.changingSrc_ = true; // load this technology with the chosen source
25586
25587        this.loadTech_(sourceTech.tech, sourceTech.source);
25588        this.tech_.ready(function () {
25589          _this12.changingSrc_ = false;
25590        });
25591        return false;
25592      } // wait until the tech is ready to set the source
25593      // and set it synchronously if possible (#2326)
25594
25595
25596      this.ready(function () {
25597        // The setSource tech method was added with source handlers
25598        // so older techs won't support it
25599        // We need to check the direct prototype for the case where subclasses
25600        // of the tech do not support source handlers
25601        if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
25602          this.techCall_('setSource', source);
25603        } else {
25604          this.techCall_('src', source.src);
25605        }
25606
25607        this.changingSrc_ = false;
25608      }, true);
25609      return false;
25610    }
25611    /**
25612     * Begin loading the src data.
25613     */
25614    ;
25615
25616    _proto.load = function load() {
25617      this.techCall_('load');
25618    }
25619    /**
25620     * Reset the player. Loads the first tech in the techOrder,
25621     * removes all the text tracks in the existing `tech`,
25622     * and calls `reset` on the `tech`.
25623     */
25624    ;
25625
25626    _proto.reset = function reset() {
25627      var _this13 = this;
25628
25629      var PromiseClass = this.options_.Promise || window$1.Promise;
25630
25631      if (this.paused() || !PromiseClass) {
25632        this.doReset_();
25633      } else {
25634        var playPromise = this.play();
25635        silencePromise(playPromise.then(function () {
25636          return _this13.doReset_();
25637        }));
25638      }
25639    };
25640
25641    _proto.doReset_ = function doReset_() {
25642      if (this.tech_) {
25643        this.tech_.clearTracks('text');
25644      }
25645
25646      this.resetCache_();
25647      this.poster('');
25648      this.loadTech_(this.options_.techOrder[0], null);
25649      this.techCall_('reset');
25650      this.resetControlBarUI_();
25651
25652      if (isEvented(this)) {
25653        this.trigger('playerreset');
25654      }
25655    }
25656    /**
25657     * Reset Control Bar's UI by calling sub-methods that reset
25658     * all of Control Bar's components
25659     */
25660    ;
25661
25662    _proto.resetControlBarUI_ = function resetControlBarUI_() {
25663      this.resetProgressBar_();
25664      this.resetPlaybackRate_();
25665      this.resetVolumeBar_();
25666    }
25667    /**
25668     * Reset tech's progress so progress bar is reset in the UI
25669     */
25670    ;
25671
25672    _proto.resetProgressBar_ = function resetProgressBar_() {
25673      this.currentTime(0);
25674      var _this$controlBar = this.controlBar,
25675          durationDisplay = _this$controlBar.durationDisplay,
25676          remainingTimeDisplay = _this$controlBar.remainingTimeDisplay;
25677
25678      if (durationDisplay) {
25679        durationDisplay.updateContent();
25680      }
25681
25682      if (remainingTimeDisplay) {
25683        remainingTimeDisplay.updateContent();
25684      }
25685    }
25686    /**
25687     * Reset Playback ratio
25688     */
25689    ;
25690
25691    _proto.resetPlaybackRate_ = function resetPlaybackRate_() {
25692      this.playbackRate(this.defaultPlaybackRate());
25693      this.handleTechRateChange_();
25694    }
25695    /**
25696     * Reset Volume bar
25697     */
25698    ;
25699
25700    _proto.resetVolumeBar_ = function resetVolumeBar_() {
25701      this.volume(1.0);
25702      this.trigger('volumechange');
25703    }
25704    /**
25705     * Returns all of the current source objects.
25706     *
25707     * @return {Tech~SourceObject[]}
25708     *         The current source objects
25709     */
25710    ;
25711
25712    _proto.currentSources = function currentSources() {
25713      var source = this.currentSource();
25714      var sources = []; // assume `{}` or `{ src }`
25715
25716      if (Object.keys(source).length !== 0) {
25717        sources.push(source);
25718      }
25719
25720      return this.cache_.sources || sources;
25721    }
25722    /**
25723     * Returns the current source object.
25724     *
25725     * @return {Tech~SourceObject}
25726     *         The current source object
25727     */
25728    ;
25729
25730    _proto.currentSource = function currentSource() {
25731      return this.cache_.source || {};
25732    }
25733    /**
25734     * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
25735     * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
25736     *
25737     * @return {string}
25738     *         The current source
25739     */
25740    ;
25741
25742    _proto.currentSrc = function currentSrc() {
25743      return this.currentSource() && this.currentSource().src || '';
25744    }
25745    /**
25746     * Get the current source type e.g. video/mp4
25747     * This can allow you rebuild the current source object so that you could load the same
25748     * source and tech later
25749     *
25750     * @return {string}
25751     *         The source MIME type
25752     */
25753    ;
25754
25755    _proto.currentType = function currentType() {
25756      return this.currentSource() && this.currentSource().type || '';
25757    }
25758    /**
25759     * Get or set the preload attribute
25760     *
25761     * @param {boolean} [value]
25762     *        - true means that we should preload
25763     *        - false means that we should not preload
25764     *
25765     * @return {string}
25766     *         The preload attribute value when getting
25767     */
25768    ;
25769
25770    _proto.preload = function preload(value) {
25771      if (value !== undefined) {
25772        this.techCall_('setPreload', value);
25773        this.options_.preload = value;
25774        return;
25775      }
25776
25777      return this.techGet_('preload');
25778    }
25779    /**
25780     * Get or set the autoplay option. When this is a boolean it will
25781     * modify the attribute on the tech. When this is a string the attribute on
25782     * the tech will be removed and `Player` will handle autoplay on loadstarts.
25783     *
25784     * @param {boolean|string} [value]
25785     *        - true: autoplay using the browser behavior
25786     *        - false: do not autoplay
25787     *        - 'play': call play() on every loadstart
25788     *        - 'muted': call muted() then play() on every loadstart
25789     *        - 'any': call play() on every loadstart. if that fails call muted() then play().
25790     *        - *: values other than those listed here will be set `autoplay` to true
25791     *
25792     * @return {boolean|string}
25793     *         The current value of autoplay when getting
25794     */
25795    ;
25796
25797    _proto.autoplay = function autoplay(value) {
25798      // getter usage
25799      if (value === undefined) {
25800        return this.options_.autoplay || false;
25801      }
25802
25803      var techAutoplay; // if the value is a valid string set it to that
25804
25805      if (typeof value === 'string' && /(any|play|muted)/.test(value)) {
25806        this.options_.autoplay = value;
25807        this.manualAutoplay_(value);
25808        techAutoplay = false; // any falsy value sets autoplay to false in the browser,
25809        // lets do the same
25810      } else if (!value) {
25811        this.options_.autoplay = false; // any other value (ie truthy) sets autoplay to true
25812      } else {
25813        this.options_.autoplay = true;
25814      }
25815
25816      techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay; // if we don't have a tech then we do not queue up
25817      // a setAutoplay call on tech ready. We do this because the
25818      // autoplay option will be passed in the constructor and we
25819      // do not need to set it twice
25820
25821      if (this.tech_) {
25822        this.techCall_('setAutoplay', techAutoplay);
25823      }
25824    }
25825    /**
25826     * Set or unset the playsinline attribute.
25827     * Playsinline tells the browser that non-fullscreen playback is preferred.
25828     *
25829     * @param {boolean} [value]
25830     *        - true means that we should try to play inline by default
25831     *        - false means that we should use the browser's default playback mode,
25832     *          which in most cases is inline. iOS Safari is a notable exception
25833     *          and plays fullscreen by default.
25834     *
25835     * @return {string|Player}
25836     *         - the current value of playsinline
25837     *         - the player when setting
25838     *
25839     * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
25840     */
25841    ;
25842
25843    _proto.playsinline = function playsinline(value) {
25844      if (value !== undefined) {
25845        this.techCall_('setPlaysinline', value);
25846        this.options_.playsinline = value;
25847        return this;
25848      }
25849
25850      return this.techGet_('playsinline');
25851    }
25852    /**
25853     * Get or set the loop attribute on the video element.
25854     *
25855     * @param {boolean} [value]
25856     *        - true means that we should loop the video
25857     *        - false means that we should not loop the video
25858     *
25859     * @return {boolean}
25860     *         The current value of loop when getting
25861     */
25862    ;
25863
25864    _proto.loop = function loop(value) {
25865      if (value !== undefined) {
25866        this.techCall_('setLoop', value);
25867        this.options_.loop = value;
25868        return;
25869      }
25870
25871      return this.techGet_('loop');
25872    }
25873    /**
25874     * Get or set the poster image source url
25875     *
25876     * @fires Player#posterchange
25877     *
25878     * @param {string} [src]
25879     *        Poster image source URL
25880     *
25881     * @return {string}
25882     *         The current value of poster when getting
25883     */
25884    ;
25885
25886    _proto.poster = function poster(src) {
25887      if (src === undefined) {
25888        return this.poster_;
25889      } // The correct way to remove a poster is to set as an empty string
25890      // other falsey values will throw errors
25891
25892
25893      if (!src) {
25894        src = '';
25895      }
25896
25897      if (src === this.poster_) {
25898        return;
25899      } // update the internal poster variable
25900
25901
25902      this.poster_ = src; // update the tech's poster
25903
25904      this.techCall_('setPoster', src);
25905      this.isPosterFromTech_ = false; // alert components that the poster has been set
25906
25907      /**
25908       * This event fires when the poster image is changed on the player.
25909       *
25910       * @event Player#posterchange
25911       * @type {EventTarget~Event}
25912       */
25913
25914      this.trigger('posterchange');
25915    }
25916    /**
25917     * Some techs (e.g. YouTube) can provide a poster source in an
25918     * asynchronous way. We want the poster component to use this
25919     * poster source so that it covers up the tech's controls.
25920     * (YouTube's play button). However we only want to use this
25921     * source if the player user hasn't set a poster through
25922     * the normal APIs.
25923     *
25924     * @fires Player#posterchange
25925     * @listens Tech#posterchange
25926     * @private
25927     */
25928    ;
25929
25930    _proto.handleTechPosterChange_ = function handleTechPosterChange_() {
25931      if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
25932        var newPoster = this.tech_.poster() || '';
25933
25934        if (newPoster !== this.poster_) {
25935          this.poster_ = newPoster;
25936          this.isPosterFromTech_ = true; // Let components know the poster has changed
25937
25938          this.trigger('posterchange');
25939        }
25940      }
25941    }
25942    /**
25943     * Get or set whether or not the controls are showing.
25944     *
25945     * @fires Player#controlsenabled
25946     *
25947     * @param {boolean} [bool]
25948     *        - true to turn controls on
25949     *        - false to turn controls off
25950     *
25951     * @return {boolean}
25952     *         The current value of controls when getting
25953     */
25954    ;
25955
25956    _proto.controls = function controls(bool) {
25957      if (bool === undefined) {
25958        return !!this.controls_;
25959      }
25960
25961      bool = !!bool; // Don't trigger a change event unless it actually changed
25962
25963      if (this.controls_ === bool) {
25964        return;
25965      }
25966
25967      this.controls_ = bool;
25968
25969      if (this.usingNativeControls()) {
25970        this.techCall_('setControls', bool);
25971      }
25972
25973      if (this.controls_) {
25974        this.removeClass('vjs-controls-disabled');
25975        this.addClass('vjs-controls-enabled');
25976        /**
25977         * @event Player#controlsenabled
25978         * @type {EventTarget~Event}
25979         */
25980
25981        this.trigger('controlsenabled');
25982
25983        if (!this.usingNativeControls()) {
25984          this.addTechControlsListeners_();
25985        }
25986      } else {
25987        this.removeClass('vjs-controls-enabled');
25988        this.addClass('vjs-controls-disabled');
25989        /**
25990         * @event Player#controlsdisabled
25991         * @type {EventTarget~Event}
25992         */
25993
25994        this.trigger('controlsdisabled');
25995
25996        if (!this.usingNativeControls()) {
25997          this.removeTechControlsListeners_();
25998        }
25999      }
26000    }
26001    /**
26002     * Toggle native controls on/off. Native controls are the controls built into
26003     * devices (e.g. default iPhone controls), Flash, or other techs
26004     * (e.g. Vimeo Controls)
26005     * **This should only be set by the current tech, because only the tech knows
26006     * if it can support native controls**
26007     *
26008     * @fires Player#usingnativecontrols
26009     * @fires Player#usingcustomcontrols
26010     *
26011     * @param {boolean} [bool]
26012     *        - true to turn native controls on
26013     *        - false to turn native controls off
26014     *
26015     * @return {boolean}
26016     *         The current value of native controls when getting
26017     */
26018    ;
26019
26020    _proto.usingNativeControls = function usingNativeControls(bool) {
26021      if (bool === undefined) {
26022        return !!this.usingNativeControls_;
26023      }
26024
26025      bool = !!bool; // Don't trigger a change event unless it actually changed
26026
26027      if (this.usingNativeControls_ === bool) {
26028        return;
26029      }
26030
26031      this.usingNativeControls_ = bool;
26032
26033      if (this.usingNativeControls_) {
26034        this.addClass('vjs-using-native-controls');
26035        /**
26036         * player is using the native device controls
26037         *
26038         * @event Player#usingnativecontrols
26039         * @type {EventTarget~Event}
26040         */
26041
26042        this.trigger('usingnativecontrols');
26043      } else {
26044        this.removeClass('vjs-using-native-controls');
26045        /**
26046         * player is using the custom HTML controls
26047         *
26048         * @event Player#usingcustomcontrols
26049         * @type {EventTarget~Event}
26050         */
26051
26052        this.trigger('usingcustomcontrols');
26053      }
26054    }
26055    /**
26056     * Set or get the current MediaError
26057     *
26058     * @fires Player#error
26059     *
26060     * @param  {MediaError|string|number} [err]
26061     *         A MediaError or a string/number to be turned
26062     *         into a MediaError
26063     *
26064     * @return {MediaError|null}
26065     *         The current MediaError when getting (or null)
26066     */
26067    ;
26068
26069    _proto.error = function error(err) {
26070      if (err === undefined) {
26071        return this.error_ || null;
26072      } // restoring to default
26073
26074
26075      if (err === null) {
26076        this.error_ = err;
26077        this.removeClass('vjs-error');
26078
26079        if (this.errorDisplay) {
26080          this.errorDisplay.close();
26081        }
26082
26083        return;
26084      }
26085
26086      this.error_ = new MediaError(err); // add the vjs-error classname to the player
26087
26088      this.addClass('vjs-error'); // log the name of the error type and any message
26089      // IE11 logs "[object object]" and required you to expand message to see error object
26090
26091      log.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_);
26092      /**
26093       * @event Player#error
26094       * @type {EventTarget~Event}
26095       */
26096
26097      this.trigger('error');
26098      return;
26099    }
26100    /**
26101     * Report user activity
26102     *
26103     * @param {Object} event
26104     *        Event object
26105     */
26106    ;
26107
26108    _proto.reportUserActivity = function reportUserActivity(event) {
26109      this.userActivity_ = true;
26110    }
26111    /**
26112     * Get/set if user is active
26113     *
26114     * @fires Player#useractive
26115     * @fires Player#userinactive
26116     *
26117     * @param {boolean} [bool]
26118     *        - true if the user is active
26119     *        - false if the user is inactive
26120     *
26121     * @return {boolean}
26122     *         The current value of userActive when getting
26123     */
26124    ;
26125
26126    _proto.userActive = function userActive(bool) {
26127      if (bool === undefined) {
26128        return this.userActive_;
26129      }
26130
26131      bool = !!bool;
26132
26133      if (bool === this.userActive_) {
26134        return;
26135      }
26136
26137      this.userActive_ = bool;
26138
26139      if (this.userActive_) {
26140        this.userActivity_ = true;
26141        this.removeClass('vjs-user-inactive');
26142        this.addClass('vjs-user-active');
26143        /**
26144         * @event Player#useractive
26145         * @type {EventTarget~Event}
26146         */
26147
26148        this.trigger('useractive');
26149        return;
26150      } // Chrome/Safari/IE have bugs where when you change the cursor it can
26151      // trigger a mousemove event. This causes an issue when you're hiding
26152      // the cursor when the user is inactive, and a mousemove signals user
26153      // activity. Making it impossible to go into inactive mode. Specifically
26154      // this happens in fullscreen when we really need to hide the cursor.
26155      //
26156      // When this gets resolved in ALL browsers it can be removed
26157      // https://code.google.com/p/chromium/issues/detail?id=103041
26158
26159
26160      if (this.tech_) {
26161        this.tech_.one('mousemove', function (e) {
26162          e.stopPropagation();
26163          e.preventDefault();
26164        });
26165      }
26166
26167      this.userActivity_ = false;
26168      this.removeClass('vjs-user-active');
26169      this.addClass('vjs-user-inactive');
26170      /**
26171       * @event Player#userinactive
26172       * @type {EventTarget~Event}
26173       */
26174
26175      this.trigger('userinactive');
26176    }
26177    /**
26178     * Listen for user activity based on timeout value
26179     *
26180     * @private
26181     */
26182    ;
26183
26184    _proto.listenForUserActivity_ = function listenForUserActivity_() {
26185      var mouseInProgress;
26186      var lastMoveX;
26187      var lastMoveY;
26188      var handleActivity = bind(this, this.reportUserActivity);
26189
26190      var handleMouseMove = function handleMouseMove(e) {
26191        // #1068 - Prevent mousemove spamming
26192        // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
26193        if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
26194          lastMoveX = e.screenX;
26195          lastMoveY = e.screenY;
26196          handleActivity();
26197        }
26198      };
26199
26200      var handleMouseDown = function handleMouseDown() {
26201        handleActivity(); // For as long as the they are touching the device or have their mouse down,
26202        // we consider them active even if they're not moving their finger or mouse.
26203        // So we want to continue to update that they are active
26204
26205        this.clearInterval(mouseInProgress); // Setting userActivity=true now and setting the interval to the same time
26206        // as the activityCheck interval (250) should ensure we never miss the
26207        // next activityCheck
26208
26209        mouseInProgress = this.setInterval(handleActivity, 250);
26210      };
26211
26212      var handleMouseUp = function handleMouseUp(event) {
26213        handleActivity(); // Stop the interval that maintains activity if the mouse/touch is down
26214
26215        this.clearInterval(mouseInProgress);
26216      }; // Any mouse movement will be considered user activity
26217
26218
26219      this.on('mousedown', handleMouseDown);
26220      this.on('mousemove', handleMouseMove);
26221      this.on('mouseup', handleMouseUp);
26222      var controlBar = this.getChild('controlBar'); // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed)
26223      // controlBar would no longer be hidden by default timeout.
26224
26225      if (controlBar && !IS_IOS && !IS_ANDROID) {
26226        controlBar.on('mouseenter', function (event) {
26227          this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
26228          this.player().options_.inactivityTimeout = 0;
26229        });
26230        controlBar.on('mouseleave', function (event) {
26231          this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
26232        });
26233      } // Listen for keyboard navigation
26234      // Shouldn't need to use inProgress interval because of key repeat
26235
26236
26237      this.on('keydown', handleActivity);
26238      this.on('keyup', handleActivity); // Run an interval every 250 milliseconds instead of stuffing everything into
26239      // the mousemove/touchmove function itself, to prevent performance degradation.
26240      // `this.reportUserActivity` simply sets this.userActivity_ to true, which
26241      // then gets picked up by this loop
26242      // http://ejohn.org/blog/learning-from-twitter/
26243
26244      var inactivityTimeout;
26245      this.setInterval(function () {
26246        // Check to see if mouse/touch activity has happened
26247        if (!this.userActivity_) {
26248          return;
26249        } // Reset the activity tracker
26250
26251
26252        this.userActivity_ = false; // If the user state was inactive, set the state to active
26253
26254        this.userActive(true); // Clear any existing inactivity timeout to start the timer over
26255
26256        this.clearTimeout(inactivityTimeout);
26257        var timeout = this.options_.inactivityTimeout;
26258
26259        if (timeout <= 0) {
26260          return;
26261        } // In <timeout> milliseconds, if no more activity has occurred the
26262        // user will be considered inactive
26263
26264
26265        inactivityTimeout = this.setTimeout(function () {
26266          // Protect against the case where the inactivityTimeout can trigger just
26267          // before the next user activity is picked up by the activity check loop
26268          // causing a flicker
26269          if (!this.userActivity_) {
26270            this.userActive(false);
26271          }
26272        }, timeout);
26273      }, 250);
26274    }
26275    /**
26276     * Gets or sets the current playback rate. A playback rate of
26277     * 1.0 represents normal speed and 0.5 would indicate half-speed
26278     * playback, for instance.
26279     *
26280     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
26281     *
26282     * @param {number} [rate]
26283     *       New playback rate to set.
26284     *
26285     * @return {number}
26286     *         The current playback rate when getting or 1.0
26287     */
26288    ;
26289
26290    _proto.playbackRate = function playbackRate(rate) {
26291      if (rate !== undefined) {
26292        // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
26293        // that is registered above
26294        this.techCall_('setPlaybackRate', rate);
26295        return;
26296      }
26297
26298      if (this.tech_ && this.tech_.featuresPlaybackRate) {
26299        return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
26300      }
26301
26302      return 1.0;
26303    }
26304    /**
26305     * Gets or sets the current default playback rate. A default playback rate of
26306     * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
26307     * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
26308     * not the current playbackRate.
26309     *
26310     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
26311     *
26312     * @param {number} [rate]
26313     *       New default playback rate to set.
26314     *
26315     * @return {number|Player}
26316     *         - The default playback rate when getting or 1.0
26317     *         - the player when setting
26318     */
26319    ;
26320
26321    _proto.defaultPlaybackRate = function defaultPlaybackRate(rate) {
26322      if (rate !== undefined) {
26323        return this.techCall_('setDefaultPlaybackRate', rate);
26324      }
26325
26326      if (this.tech_ && this.tech_.featuresPlaybackRate) {
26327        return this.techGet_('defaultPlaybackRate');
26328      }
26329
26330      return 1.0;
26331    }
26332    /**
26333     * Gets or sets the audio flag
26334     *
26335     * @param {boolean} bool
26336     *        - true signals that this is an audio player
26337     *        - false signals that this is not an audio player
26338     *
26339     * @return {boolean}
26340     *         The current value of isAudio when getting
26341     */
26342    ;
26343
26344    _proto.isAudio = function isAudio(bool) {
26345      if (bool !== undefined) {
26346        this.isAudio_ = !!bool;
26347        return;
26348      }
26349
26350      return !!this.isAudio_;
26351    }
26352    /**
26353     * A helper method for adding a {@link TextTrack} to our
26354     * {@link TextTrackList}.
26355     *
26356     * In addition to the W3C settings we allow adding additional info through options.
26357     *
26358     * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
26359     *
26360     * @param {string} [kind]
26361     *        the kind of TextTrack you are adding
26362     *
26363     * @param {string} [label]
26364     *        the label to give the TextTrack label
26365     *
26366     * @param {string} [language]
26367     *        the language to set on the TextTrack
26368     *
26369     * @return {TextTrack|undefined}
26370     *         the TextTrack that was added or undefined
26371     *         if there is no tech
26372     */
26373    ;
26374
26375    _proto.addTextTrack = function addTextTrack(kind, label, language) {
26376      if (this.tech_) {
26377        return this.tech_.addTextTrack(kind, label, language);
26378      }
26379    }
26380    /**
26381     * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will
26382     * automatically removed from the video element whenever the source changes, unless
26383     * manualCleanup is set to false.
26384     *
26385     * @param {Object} options
26386     *        Options to pass to {@link HTMLTrackElement} during creation. See
26387     *        {@link HTMLTrackElement} for object properties that you should use.
26388     *
26389     * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
26390     *
26391     * @return {HtmlTrackElement}
26392     *         the HTMLTrackElement that was created and added
26393     *         to the HtmlTrackElementList and the remote
26394     *         TextTrackList
26395     *
26396     * @deprecated The default value of the "manualCleanup" parameter will default
26397     *             to "false" in upcoming versions of Video.js
26398     */
26399    ;
26400
26401    _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
26402      if (this.tech_) {
26403        return this.tech_.addRemoteTextTrack(options, manualCleanup);
26404      }
26405    }
26406    /**
26407     * Remove a remote {@link TextTrack} from the respective
26408     * {@link TextTrackList} and {@link HtmlTrackElementList}.
26409     *
26410     * @param {Object} track
26411     *        Remote {@link TextTrack} to remove
26412     *
26413     * @return {undefined}
26414     *         does not return anything
26415     */
26416    ;
26417
26418    _proto.removeRemoteTextTrack = function removeRemoteTextTrack(obj) {
26419      if (obj === void 0) {
26420        obj = {};
26421      }
26422
26423      var _obj = obj,
26424          track = _obj.track;
26425
26426      if (!track) {
26427        track = obj;
26428      } // destructure the input into an object with a track argument, defaulting to arguments[0]
26429      // default the whole argument to an empty object if nothing was passed in
26430
26431
26432      if (this.tech_) {
26433        return this.tech_.removeRemoteTextTrack(track);
26434      }
26435    }
26436    /**
26437     * Gets available media playback quality metrics as specified by the W3C's Media
26438     * Playback Quality API.
26439     *
26440     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
26441     *
26442     * @return {Object|undefined}
26443     *         An object with supported media playback quality metrics or undefined if there
26444     *         is no tech or the tech does not support it.
26445     */
26446    ;
26447
26448    _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
26449      return this.techGet_('getVideoPlaybackQuality');
26450    }
26451    /**
26452     * Get video width
26453     *
26454     * @return {number}
26455     *         current video width
26456     */
26457    ;
26458
26459    _proto.videoWidth = function videoWidth() {
26460      return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
26461    }
26462    /**
26463     * Get video height
26464     *
26465     * @return {number}
26466     *         current video height
26467     */
26468    ;
26469
26470    _proto.videoHeight = function videoHeight() {
26471      return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
26472    }
26473    /**
26474     * The player's language code
26475     * NOTE: The language should be set in the player options if you want the
26476     * the controls to be built with a specific language. Changing the language
26477     * later will not update controls text.
26478     *
26479     * @param {string} [code]
26480     *        the language code to set the player to
26481     *
26482     * @return {string}
26483     *         The current language code when getting
26484     */
26485    ;
26486
26487    _proto.language = function language(code) {
26488      if (code === undefined) {
26489        return this.language_;
26490      }
26491
26492      this.language_ = String(code).toLowerCase();
26493    }
26494    /**
26495     * Get the player's language dictionary
26496     * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
26497     * Languages specified directly in the player options have precedence
26498     *
26499     * @return {Array}
26500     *         An array of of supported languages
26501     */
26502    ;
26503
26504    _proto.languages = function languages() {
26505      return mergeOptions(Player.prototype.options_.languages, this.languages_);
26506    }
26507    /**
26508     * returns a JavaScript object reperesenting the current track
26509     * information. **DOES not return it as JSON**
26510     *
26511     * @return {Object}
26512     *         Object representing the current of track info
26513     */
26514    ;
26515
26516    _proto.toJSON = function toJSON() {
26517      var options = mergeOptions(this.options_);
26518      var tracks = options.tracks;
26519      options.tracks = [];
26520
26521      for (var i = 0; i < tracks.length; i++) {
26522        var track = tracks[i]; // deep merge tracks and null out player so no circular references
26523
26524        track = mergeOptions(track);
26525        track.player = undefined;
26526        options.tracks[i] = track;
26527      }
26528
26529      return options;
26530    }
26531    /**
26532     * Creates a simple modal dialog (an instance of the {@link ModalDialog}
26533     * component) that immediately overlays the player with arbitrary
26534     * content and removes itself when closed.
26535     *
26536     * @param {string|Function|Element|Array|null} content
26537     *        Same as {@link ModalDialog#content}'s param of the same name.
26538     *        The most straight-forward usage is to provide a string or DOM
26539     *        element.
26540     *
26541     * @param {Object} [options]
26542     *        Extra options which will be passed on to the {@link ModalDialog}.
26543     *
26544     * @return {ModalDialog}
26545     *         the {@link ModalDialog} that was created
26546     */
26547    ;
26548
26549    _proto.createModal = function createModal(content, options) {
26550      var _this14 = this;
26551
26552      options = options || {};
26553      options.content = content || '';
26554      var modal = new ModalDialog(this, options);
26555      this.addChild(modal);
26556      modal.on('dispose', function () {
26557        _this14.removeChild(modal);
26558      });
26559      modal.open();
26560      return modal;
26561    }
26562    /**
26563     * Change breakpoint classes when the player resizes.
26564     *
26565     * @private
26566     */
26567    ;
26568
26569    _proto.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() {
26570      if (!this.responsive()) {
26571        return;
26572      }
26573
26574      var currentBreakpoint = this.currentBreakpoint();
26575      var currentWidth = this.currentWidth();
26576
26577      for (var i = 0; i < BREAKPOINT_ORDER.length; i++) {
26578        var candidateBreakpoint = BREAKPOINT_ORDER[i];
26579        var maxWidth = this.breakpoints_[candidateBreakpoint];
26580
26581        if (currentWidth <= maxWidth) {
26582          // The current breakpoint did not change, nothing to do.
26583          if (currentBreakpoint === candidateBreakpoint) {
26584            return;
26585          } // Only remove a class if there is a current breakpoint.
26586
26587
26588          if (currentBreakpoint) {
26589            this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
26590          }
26591
26592          this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
26593          this.breakpoint_ = candidateBreakpoint;
26594          break;
26595        }
26596      }
26597    }
26598    /**
26599     * Removes the current breakpoint.
26600     *
26601     * @private
26602     */
26603    ;
26604
26605    _proto.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() {
26606      var className = this.currentBreakpointClass();
26607      this.breakpoint_ = '';
26608
26609      if (className) {
26610        this.removeClass(className);
26611      }
26612    }
26613    /**
26614     * Get or set breakpoints on the player.
26615     *
26616     * Calling this method with an object or `true` will remove any previous
26617     * custom breakpoints and start from the defaults again.
26618     *
26619     * @param  {Object|boolean} [breakpoints]
26620     *         If an object is given, it can be used to provide custom
26621     *         breakpoints. If `true` is given, will set default breakpoints.
26622     *         If this argument is not given, will simply return the current
26623     *         breakpoints.
26624     *
26625     * @param  {number} [breakpoints.tiny]
26626     *         The maximum width for the "vjs-layout-tiny" class.
26627     *
26628     * @param  {number} [breakpoints.xsmall]
26629     *         The maximum width for the "vjs-layout-x-small" class.
26630     *
26631     * @param  {number} [breakpoints.small]
26632     *         The maximum width for the "vjs-layout-small" class.
26633     *
26634     * @param  {number} [breakpoints.medium]
26635     *         The maximum width for the "vjs-layout-medium" class.
26636     *
26637     * @param  {number} [breakpoints.large]
26638     *         The maximum width for the "vjs-layout-large" class.
26639     *
26640     * @param  {number} [breakpoints.xlarge]
26641     *         The maximum width for the "vjs-layout-x-large" class.
26642     *
26643     * @param  {number} [breakpoints.huge]
26644     *         The maximum width for the "vjs-layout-huge" class.
26645     *
26646     * @return {Object}
26647     *         An object mapping breakpoint names to maximum width values.
26648     */
26649    ;
26650
26651    _proto.breakpoints = function breakpoints(_breakpoints) {
26652      // Used as a getter.
26653      if (_breakpoints === undefined) {
26654        return assign(this.breakpoints_);
26655      }
26656
26657      this.breakpoint_ = '';
26658      this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints); // When breakpoint definitions change, we need to update the currently
26659      // selected breakpoint.
26660
26661      this.updateCurrentBreakpoint_(); // Clone the breakpoints before returning.
26662
26663      return assign(this.breakpoints_);
26664    }
26665    /**
26666     * Get or set a flag indicating whether or not this player should adjust
26667     * its UI based on its dimensions.
26668     *
26669     * @param  {boolean} value
26670     *         Should be `true` if the player should adjust its UI based on its
26671     *         dimensions; otherwise, should be `false`.
26672     *
26673     * @return {boolean}
26674     *         Will be `true` if this player should adjust its UI based on its
26675     *         dimensions; otherwise, will be `false`.
26676     */
26677    ;
26678
26679    _proto.responsive = function responsive(value) {
26680      // Used as a getter.
26681      if (value === undefined) {
26682        return this.responsive_;
26683      }
26684
26685      value = Boolean(value);
26686      var current = this.responsive_; // Nothing changed.
26687
26688      if (value === current) {
26689        return;
26690      } // The value actually changed, set it.
26691
26692
26693      this.responsive_ = value; // Start listening for breakpoints and set the initial breakpoint if the
26694      // player is now responsive.
26695
26696      if (value) {
26697        this.on('playerresize', this.updateCurrentBreakpoint_);
26698        this.updateCurrentBreakpoint_(); // Stop listening for breakpoints if the player is no longer responsive.
26699      } else {
26700        this.off('playerresize', this.updateCurrentBreakpoint_);
26701        this.removeCurrentBreakpoint_();
26702      }
26703
26704      return value;
26705    }
26706    /**
26707     * Get current breakpoint name, if any.
26708     *
26709     * @return {string}
26710     *         If there is currently a breakpoint set, returns a the key from the
26711     *         breakpoints object matching it. Otherwise, returns an empty string.
26712     */
26713    ;
26714
26715    _proto.currentBreakpoint = function currentBreakpoint() {
26716      return this.breakpoint_;
26717    }
26718    /**
26719     * Get the current breakpoint class name.
26720     *
26721     * @return {string}
26722     *         The matching class name (e.g. `"vjs-layout-tiny"` or
26723     *         `"vjs-layout-large"`) for the current breakpoint. Empty string if
26724     *         there is no current breakpoint.
26725     */
26726    ;
26727
26728    _proto.currentBreakpointClass = function currentBreakpointClass() {
26729      return BREAKPOINT_CLASSES[this.breakpoint_] || '';
26730    }
26731    /**
26732     * An object that describes a single piece of media.
26733     *
26734     * Properties that are not part of this type description will be retained; so,
26735     * this can be viewed as a generic metadata storage mechanism as well.
26736     *
26737     * @see      {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
26738     * @typedef  {Object} Player~MediaObject
26739     *
26740     * @property {string} [album]
26741     *           Unused, except if this object is passed to the `MediaSession`
26742     *           API.
26743     *
26744     * @property {string} [artist]
26745     *           Unused, except if this object is passed to the `MediaSession`
26746     *           API.
26747     *
26748     * @property {Object[]} [artwork]
26749     *           Unused, except if this object is passed to the `MediaSession`
26750     *           API. If not specified, will be populated via the `poster`, if
26751     *           available.
26752     *
26753     * @property {string} [poster]
26754     *           URL to an image that will display before playback.
26755     *
26756     * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
26757     *           A single source object, an array of source objects, or a string
26758     *           referencing a URL to a media source. It is _highly recommended_
26759     *           that an object or array of objects is used here, so that source
26760     *           selection algorithms can take the `type` into account.
26761     *
26762     * @property {string} [title]
26763     *           Unused, except if this object is passed to the `MediaSession`
26764     *           API.
26765     *
26766     * @property {Object[]} [textTracks]
26767     *           An array of objects to be used to create text tracks, following
26768     *           the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}.
26769     *           For ease of removal, these will be created as "remote" text
26770     *           tracks and set to automatically clean up on source changes.
26771     *
26772     *           These objects may have properties like `src`, `kind`, `label`,
26773     *           and `language`, see {@link Tech#createRemoteTextTrack}.
26774     */
26775
26776    /**
26777     * Populate the player using a {@link Player~MediaObject|MediaObject}.
26778     *
26779     * @param  {Player~MediaObject} media
26780     *         A media object.
26781     *
26782     * @param  {Function} ready
26783     *         A callback to be called when the player is ready.
26784     */
26785    ;
26786
26787    _proto.loadMedia = function loadMedia(media, ready) {
26788      var _this15 = this;
26789
26790      if (!media || typeof media !== 'object') {
26791        return;
26792      }
26793
26794      this.reset(); // Clone the media object so it cannot be mutated from outside.
26795
26796      this.cache_.media = mergeOptions(media);
26797      var _this$cache_$media = this.cache_.media,
26798          artwork = _this$cache_$media.artwork,
26799          poster = _this$cache_$media.poster,
26800          src = _this$cache_$media.src,
26801          textTracks = _this$cache_$media.textTracks; // If `artwork` is not given, create it using `poster`.
26802
26803      if (!artwork && poster) {
26804        this.cache_.media.artwork = [{
26805          src: poster,
26806          type: getMimetype(poster)
26807        }];
26808      }
26809
26810      if (src) {
26811        this.src(src);
26812      }
26813
26814      if (poster) {
26815        this.poster(poster);
26816      }
26817
26818      if (Array.isArray(textTracks)) {
26819        textTracks.forEach(function (tt) {
26820          return _this15.addRemoteTextTrack(tt, false);
26821        });
26822      }
26823
26824      this.ready(ready);
26825    }
26826    /**
26827     * Get a clone of the current {@link Player~MediaObject} for this player.
26828     *
26829     * If the `loadMedia` method has not been used, will attempt to return a
26830     * {@link Player~MediaObject} based on the current state of the player.
26831     *
26832     * @return {Player~MediaObject}
26833     */
26834    ;
26835
26836    _proto.getMedia = function getMedia() {
26837      if (!this.cache_.media) {
26838        var poster = this.poster();
26839        var src = this.currentSources();
26840        var textTracks = Array.prototype.map.call(this.remoteTextTracks(), function (tt) {
26841          return {
26842            kind: tt.kind,
26843            label: tt.label,
26844            language: tt.language,
26845            src: tt.src
26846          };
26847        });
26848        var media = {
26849          src: src,
26850          textTracks: textTracks
26851        };
26852
26853        if (poster) {
26854          media.poster = poster;
26855          media.artwork = [{
26856            src: media.poster,
26857            type: getMimetype(media.poster)
26858          }];
26859        }
26860
26861        return media;
26862      }
26863
26864      return mergeOptions(this.cache_.media);
26865    }
26866    /**
26867     * Gets tag settings
26868     *
26869     * @param {Element} tag
26870     *        The player tag
26871     *
26872     * @return {Object}
26873     *         An object containing all of the settings
26874     *         for a player tag
26875     */
26876    ;
26877
26878    Player.getTagSettings = function getTagSettings(tag) {
26879      var baseOptions = {
26880        sources: [],
26881        tracks: []
26882      };
26883      var tagOptions = getAttributes(tag);
26884      var dataSetup = tagOptions['data-setup'];
26885
26886      if (hasClass(tag, 'vjs-fill')) {
26887        tagOptions.fill = true;
26888      }
26889
26890      if (hasClass(tag, 'vjs-fluid')) {
26891        tagOptions.fluid = true;
26892      } // Check if data-setup attr exists.
26893
26894
26895      if (dataSetup !== null) {
26896        // Parse options JSON
26897        // If empty string, make it a parsable json object.
26898        var _safeParseTuple = tuple(dataSetup || '{}'),
26899            err = _safeParseTuple[0],
26900            data = _safeParseTuple[1];
26901
26902        if (err) {
26903          log.error(err);
26904        }
26905
26906        assign(tagOptions, data);
26907      }
26908
26909      assign(baseOptions, tagOptions); // Get tag children settings
26910
26911      if (tag.hasChildNodes()) {
26912        var children = tag.childNodes;
26913
26914        for (var i = 0, j = children.length; i < j; i++) {
26915          var child = children[i]; // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
26916
26917          var childName = child.nodeName.toLowerCase();
26918
26919          if (childName === 'source') {
26920            baseOptions.sources.push(getAttributes(child));
26921          } else if (childName === 'track') {
26922            baseOptions.tracks.push(getAttributes(child));
26923          }
26924        }
26925      }
26926
26927      return baseOptions;
26928    }
26929    /**
26930     * Determine whether or not flexbox is supported
26931     *
26932     * @return {boolean}
26933     *         - true if flexbox is supported
26934     *         - false if flexbox is not supported
26935     */
26936    ;
26937
26938    _proto.flexNotSupported_ = function flexNotSupported_() {
26939      var elem = document.createElement('i'); // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
26940      // common flex features that we can rely on when checking for flex support.
26941
26942      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
26943      'msFlexOrder' in elem.style);
26944    };
26945
26946    return Player;
26947  }(Component);
26948  /**
26949   * Get the {@link VideoTrackList}
26950   * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
26951   *
26952   * @return {VideoTrackList}
26953   *         the current video track list
26954   *
26955   * @method Player.prototype.videoTracks
26956   */
26957
26958  /**
26959   * Get the {@link AudioTrackList}
26960   * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
26961   *
26962   * @return {AudioTrackList}
26963   *         the current audio track list
26964   *
26965   * @method Player.prototype.audioTracks
26966   */
26967
26968  /**
26969   * Get the {@link TextTrackList}
26970   *
26971   * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
26972   *
26973   * @return {TextTrackList}
26974   *         the current text track list
26975   *
26976   * @method Player.prototype.textTracks
26977   */
26978
26979  /**
26980   * Get the remote {@link TextTrackList}
26981   *
26982   * @return {TextTrackList}
26983   *         The current remote text track list
26984   *
26985   * @method Player.prototype.remoteTextTracks
26986   */
26987
26988  /**
26989   * Get the remote {@link HtmlTrackElementList} tracks.
26990   *
26991   * @return {HtmlTrackElementList}
26992   *         The current remote text track element list
26993   *
26994   * @method Player.prototype.remoteTextTrackEls
26995   */
26996
26997
vendor: 4,663 bytes, lines 26998-27125
26998  ALL.names.forEach(function (name$$1) {
26999    var props = ALL[name$$1];
27000
27001    Player.prototype[props.getterName] = function () {
27002      if (this.tech_) {
27003        return this.tech_[props.getterName]();
27004      } // if we have not yet loadTech_, we create {video,audio,text}Tracks_
27005      // these will be passed to the tech during loading
27006
27007
27008      this[props.privateName] = this[props.privateName] || new props.ListClass();
27009      return this[props.privateName];
27010    };
27011  });
27012  /**
27013   * Global enumeration of players.
27014   *
27015   * The keys are the player IDs and the values are either the {@link Player}
27016   * instance or `null` for disposed players.
27017   *
27018   * @type {Object}
27019   */
27020
27021  Player.players = {};
27022  var navigator = window$1.navigator;
27023  /*
27024   * Player instance options, surfaced using options
27025   * options = Player.prototype.options_
27026   * Make changes in options, not here.
27027   *
27028   * @type {Object}
27029   * @private
27030   */
27031
27032  Player.prototype.options_ = {
27033    // Default order of fallback technology
27034    techOrder: Tech.defaultTechOrder_,
27035    html5: {},
27036    flash: {},
27037    // default inactivity timeout
27038    inactivityTimeout: 2000,
27039    // default playback rates
27040    playbackRates: [],
27041    // Add playback rate selection by adding rates
27042    // 'playbackRates': [0.5, 1, 1.5, 2],
27043    liveui: false,
27044    // Included control sets
27045    children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
27046    language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
27047    // locales and their language translations
27048    languages: {},
27049    // Default message to show when a video cannot be played.
27050    notSupportedMessage: 'No compatible source was found for this media.',
27051    breakpoints: {},
27052    responsive: false
27053  };
27054  [
27055  /**
27056   * Returns whether or not the player is in the "ended" state.
27057   *
27058   * @return {Boolean} True if the player is in the ended state, false if not.
27059   * @method Player#ended
27060   */
27061  'ended',
27062  /**
27063   * Returns whether or not the player is in the "seeking" state.
27064   *
27065   * @return {Boolean} True if the player is in the seeking state, false if not.
27066   * @method Player#seeking
27067   */
27068  'seeking',
27069  /**
27070   * Returns the TimeRanges of the media that are currently available
27071   * for seeking to.
27072   *
27073   * @return {TimeRanges} the seekable intervals of the media timeline
27074   * @method Player#seekable
27075   */
27076  'seekable',
27077  /**
27078   * Returns the current state of network activity for the element, from
27079   * the codes in the list below.
27080   * - NETWORK_EMPTY (numeric value 0)
27081   *   The element has not yet been initialised. All attributes are in
27082   *   their initial states.
27083   * - NETWORK_IDLE (numeric value 1)
27084   *   The element's resource selection algorithm is active and has
27085   *   selected a resource, but it is not actually using the network at
27086   *   this time.
27087   * - NETWORK_LOADING (numeric value 2)
27088   *   The user agent is actively trying to download data.
27089   * - NETWORK_NO_SOURCE (numeric value 3)
27090   *   The element's resource selection algorithm is active, but it has
27091   *   not yet found a resource to use.
27092   *
27093   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
27094   * @return {number} the current network activity state
27095   * @method Player#networkState
27096   */
27097  'networkState',
27098  /**
27099   * Returns a value that expresses the current state of the element
27100   * with respect to rendering the current playback position, from the
27101   * codes in the list below.
27102   * - HAVE_NOTHING (numeric value 0)
27103   *   No information regarding the media resource is available.
27104   * - HAVE_METADATA (numeric value 1)
27105   *   Enough of the resource has been obtained that the duration of the
27106   *   resource is available.
27107   * - HAVE_CURRENT_DATA (numeric value 2)
27108   *   Data for the immediate current playback position is available.
27109   * - HAVE_FUTURE_DATA (numeric value 3)
27110   *   Data for the immediate current playback position is available, as
27111   *   well as enough data for the user agent to advance the current
27112   *   playback position in the direction of playback.
27113   * - HAVE_ENOUGH_DATA (numeric value 4)
27114   *   The user agent estimates that enough data is available for
27115   *   playback to proceed uninterrupted.
27116   *
27117   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
27118   * @return {number} the current playback rendering state
27119   * @method Player#readyState
27120   */
27121  'readyState'].forEach(function (fn) {
27122    Player.prototype[fn] = function () {
27123      return this.techGet_(fn);
27124    };
27125  });
vendor: 5,226 bytes, lines 27126-27323
27126  TECH_EVENTS_RETRIGGER.forEach(function (event) {
27127    Player.prototype["handleTech" + toTitleCase(event) + "_"] = function () {
27128      return this.trigger(event);
27129    };
27130  });
27131  /**
27132   * Fired when the player has initial duration and dimension information
27133   *
27134   * @event Player#loadedmetadata
27135   * @type {EventTarget~Event}
27136   */
27137
27138  /**
27139   * Fired when the player has downloaded data at the current playback position
27140   *
27141   * @event Player#loadeddata
27142   * @type {EventTarget~Event}
27143   */
27144
27145  /**
27146   * Fired when the current playback position has changed *
27147   * During playback this is fired every 15-250 milliseconds, depending on the
27148   * playback technology in use.
27149   *
27150   * @event Player#timeupdate
27151   * @type {EventTarget~Event}
27152   */
27153
27154  /**
27155   * Fired when the volume changes
27156   *
27157   * @event Player#volumechange
27158   * @type {EventTarget~Event}
27159   */
27160
27161  /**
27162   * Reports whether or not a player has a plugin available.
27163   *
27164   * This does not report whether or not the plugin has ever been initialized
27165   * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
27166   *
27167   * @method Player#hasPlugin
27168   * @param  {string}  name
27169   *         The name of a plugin.
27170   *
27171   * @return {boolean}
27172   *         Whether or not this player has the requested plugin available.
27173   */
27174
27175  /**
27176   * Reports whether or not a player is using a plugin by name.
27177   *
27178   * For basic plugins, this only reports whether the plugin has _ever_ been
27179   * initialized on this player.
27180   *
27181   * @method Player#usingPlugin
27182   * @param  {string} name
27183   *         The name of a plugin.
27184   *
27185   * @return {boolean}
27186   *         Whether or not this player is using the requested plugin.
27187   */
27188
27189  Component.registerComponent('Player', Player);
27190
27191  /**
27192   * The base plugin name.
27193   *
27194   * @private
27195   * @constant
27196   * @type {string}
27197   */
27198
27199  var BASE_PLUGIN_NAME = 'plugin';
27200  /**
27201   * The key on which a player's active plugins cache is stored.
27202   *
27203   * @private
27204   * @constant
27205   * @type     {string}
27206   */
27207
27208  var PLUGIN_CACHE_KEY = 'activePlugins_';
27209  /**
27210   * Stores registered plugins in a private space.
27211   *
27212   * @private
27213   * @type    {Object}
27214   */
27215
27216  var pluginStorage = {};
27217  /**
27218   * Reports whether or not a plugin has been registered.
27219   *
27220   * @private
27221   * @param   {string} name
27222   *          The name of a plugin.
27223   *
27224   * @return {boolean}
27225   *          Whether or not the plugin has been registered.
27226   */
27227
27228  var pluginExists = function pluginExists(name) {
27229    return pluginStorage.hasOwnProperty(name);
27230  };
27231  /**
27232   * Get a single registered plugin by name.
27233   *
27234   * @private
27235   * @param   {string} name
27236   *          The name of a plugin.
27237   *
27238   * @return {Function|undefined}
27239   *          The plugin (or undefined).
27240   */
27241
27242
27243  var getPlugin = function getPlugin(name) {
27244    return pluginExists(name) ? pluginStorage[name] : undefined;
27245  };
27246  /**
27247   * Marks a plugin as "active" on a player.
27248   *
27249   * Also, ensures that the player has an object for tracking active plugins.
27250   *
27251   * @private
27252   * @param   {Player} player
27253   *          A Video.js player instance.
27254   *
27255   * @param   {string} name
27256   *          The name of a plugin.
27257   */
27258
27259
27260  var markPluginAsActive = function markPluginAsActive(player, name) {
27261    player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
27262    player[PLUGIN_CACHE_KEY][name] = true;
27263  };
27264  /**
27265   * Triggers a pair of plugin setup events.
27266   *
27267   * @private
27268   * @param  {Player} player
27269   *         A Video.js player instance.
27270   *
27271   * @param  {Plugin~PluginEventHash} hash
27272   *         A plugin event hash.
27273   *
27274   * @param  {boolean} [before]
27275   *         If true, prefixes the event name with "before". In other words,
27276   *         use this to trigger "beforepluginsetup" instead of "pluginsetup".
27277   */
27278
27279
27280  var triggerSetupEvent = function triggerSetupEvent(player, hash, before) {
27281    var eventName = (before ? 'before' : '') + 'pluginsetup';
27282    player.trigger(eventName, hash);
27283    player.trigger(eventName + ':' + hash.name, hash);
27284  };
27285  /**
27286   * Takes a basic plugin function and returns a wrapper function which marks
27287   * on the player that the plugin has been activated.
27288   *
27289   * @private
27290   * @param   {string} name
27291   *          The name of the plugin.
27292   *
27293   * @param   {Function} plugin
27294   *          The basic plugin.
27295   *
27296   * @return {Function}
27297   *          A wrapper function for the given plugin.
27298   */
27299
27300
27301  var createBasicPlugin = function createBasicPlugin(name, plugin) {
27302    var basicPluginWrapper = function basicPluginWrapper() {
27303      // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
27304      // regardless, but we want the hash to be consistent with the hash provided
27305      // for advanced plugins.
27306      //
27307      // The only potentially counter-intuitive thing here is the `instance` in
27308      // the "pluginsetup" event is the value returned by the `plugin` function.
27309      triggerSetupEvent(this, {
27310        name: name,
27311        plugin: plugin,
27312        instance: null
27313      }, true);
27314      var instance = plugin.apply(this, arguments);
27315      markPluginAsActive(this, name);
27316      triggerSetupEvent(this, {
27317        name: name,
27318        plugin: plugin,
27319        instance: instance
27320      });
27321      return instance;
27322    };
27323
vendor: 8,907 bytes, lines 27324-27605
27324    Object.keys(plugin).forEach(function (prop) {
27325      basicPluginWrapper[prop] = plugin[prop];
27326    });
27327    return basicPluginWrapper;
27328  };
27329  /**
27330   * Takes a plugin sub-class and returns a factory function for generating
27331   * instances of it.
27332   *
27333   * This factory function will replace itself with an instance of the requested
27334   * sub-class of Plugin.
27335   *
27336   * @private
27337   * @param   {string} name
27338   *          The name of the plugin.
27339   *
27340   * @param   {Plugin} PluginSubClass
27341   *          The advanced plugin.
27342   *
27343   * @return {Function}
27344   */
27345
27346
27347  var createPluginFactory = function createPluginFactory(name, PluginSubClass) {
27348    // Add a `name` property to the plugin prototype so that each plugin can
27349    // refer to itself by name.
27350    PluginSubClass.prototype.name = name;
27351    return function () {
27352      triggerSetupEvent(this, {
27353        name: name,
27354        plugin: PluginSubClass,
27355        instance: null
27356      }, true);
27357
27358      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
27359        args[_key] = arguments[_key];
27360      }
27361
27362      var instance = _construct(PluginSubClass, [this].concat(args)); // The plugin is replaced by a function that returns the current instance.
27363
27364
27365      this[name] = function () {
27366        return instance;
27367      };
27368
27369      triggerSetupEvent(this, instance.getEventHash());
27370      return instance;
27371    };
27372  };
27373  /**
27374   * Parent class for all advanced plugins.
27375   *
27376   * @mixes   module:evented~EventedMixin
27377   * @mixes   module:stateful~StatefulMixin
27378   * @fires   Player#beforepluginsetup
27379   * @fires   Player#beforepluginsetup:$name
27380   * @fires   Player#pluginsetup
27381   * @fires   Player#pluginsetup:$name
27382   * @listens Player#dispose
27383   * @throws  {Error}
27384   *          If attempting to instantiate the base {@link Plugin} class
27385   *          directly instead of via a sub-class.
27386   */
27387
27388
27389  var Plugin =
27390  /*#__PURE__*/
27391  function () {
27392    /**
27393     * Creates an instance of this class.
27394     *
27395     * Sub-classes should call `super` to ensure plugins are properly initialized.
27396     *
27397     * @param {Player} player
27398     *        A Video.js player instance.
27399     */
27400    function Plugin(player) {
27401      if (this.constructor === Plugin) {
27402        throw new Error('Plugin must be sub-classed; not directly instantiated.');
27403      }
27404
27405      this.player = player; // Make this object evented, but remove the added `trigger` method so we
27406      // use the prototype version instead.
27407
27408      evented(this);
27409      delete this.trigger;
27410      stateful(this, this.constructor.defaultState);
27411      markPluginAsActive(player, this.name); // Auto-bind the dispose method so we can use it as a listener and unbind
27412      // it later easily.
27413
27414      this.dispose = bind(this, this.dispose); // If the player is disposed, dispose the plugin.
27415
27416      player.on('dispose', this.dispose);
27417    }
27418    /**
27419     * Get the version of the plugin that was set on <pluginName>.VERSION
27420     */
27421
27422
27423    var _proto = Plugin.prototype;
27424
27425    _proto.version = function version() {
27426      return this.constructor.VERSION;
27427    }
27428    /**
27429     * Each event triggered by plugins includes a hash of additional data with
27430     * conventional properties.
27431     *
27432     * This returns that object or mutates an existing hash.
27433     *
27434     * @param   {Object} [hash={}]
27435     *          An object to be used as event an event hash.
27436     *
27437     * @return {Plugin~PluginEventHash}
27438     *          An event hash object with provided properties mixed-in.
27439     */
27440    ;
27441
27442    _proto.getEventHash = function getEventHash(hash) {
27443      if (hash === void 0) {
27444        hash = {};
27445      }
27446
27447      hash.name = this.name;
27448      hash.plugin = this.constructor;
27449      hash.instance = this;
27450      return hash;
27451    }
27452    /**
27453     * Triggers an event on the plugin object and overrides
27454     * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
27455     *
27456     * @param   {string|Object} event
27457     *          An event type or an object with a type property.
27458     *
27459     * @param   {Object} [hash={}]
27460     *          Additional data hash to merge with a
27461     *          {@link Plugin~PluginEventHash|PluginEventHash}.
27462     *
27463     * @return {boolean}
27464     *          Whether or not default was prevented.
27465     */
27466    ;
27467
27468    _proto.trigger = function trigger$$1(event, hash) {
27469      if (hash === void 0) {
27470        hash = {};
27471      }
27472
27473      return trigger(this.eventBusEl_, event, this.getEventHash(hash));
27474    }
27475    /**
27476     * Handles "statechanged" events on the plugin. No-op by default, override by
27477     * subclassing.
27478     *
27479     * @abstract
27480     * @param    {Event} e
27481     *           An event object provided by a "statechanged" event.
27482     *
27483     * @param    {Object} e.changes
27484     *           An object describing changes that occurred with the "statechanged"
27485     *           event.
27486     */
27487    ;
27488
27489    _proto.handleStateChanged = function handleStateChanged(e) {}
27490    /**
27491     * Disposes a plugin.
27492     *
27493     * Subclasses can override this if they want, but for the sake of safety,
27494     * it's probably best to subscribe the "dispose" event.
27495     *
27496     * @fires Plugin#dispose
27497     */
27498    ;
27499
27500    _proto.dispose = function dispose() {
27501      var name = this.name,
27502          player = this.player;
27503      /**
27504       * Signals that a advanced plugin is about to be disposed.
27505       *
27506       * @event Plugin#dispose
27507       * @type  {EventTarget~Event}
27508       */
27509
27510      this.trigger('dispose');
27511      this.off();
27512      player.off('dispose', this.dispose); // Eliminate any possible sources of leaking memory by clearing up
27513      // references between the player and the plugin instance and nulling out
27514      // the plugin's state and replacing methods with a function that throws.
27515
27516      player[PLUGIN_CACHE_KEY][name] = false;
27517      this.player = this.state = null; // Finally, replace the plugin name on the player with a new factory
27518      // function, so that the plugin is ready to be set up again.
27519
27520      player[name] = createPluginFactory(name, pluginStorage[name]);
27521    }
27522    /**
27523     * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
27524     *
27525     * @param   {string|Function} plugin
27526     *          If a string, matches the name of a plugin. If a function, will be
27527     *          tested directly.
27528     *
27529     * @return {boolean}
27530     *          Whether or not a plugin is a basic plugin.
27531     */
27532    ;
27533
27534    Plugin.isBasic = function isBasic(plugin) {
27535      var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
27536      return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
27537    }
27538    /**
27539     * Register a Video.js plugin.
27540     *
27541     * @param   {string} name
27542     *          The name of the plugin to be registered. Must be a string and
27543     *          must not match an existing plugin or a method on the `Player`
27544     *          prototype.
27545     *
27546     * @param   {Function} plugin
27547     *          A sub-class of `Plugin` or a function for basic plugins.
27548     *
27549     * @return {Function}
27550     *          For advanced plugins, a factory function for that plugin. For
27551     *          basic plugins, a wrapper function that initializes the plugin.
27552     */
27553    ;
27554
27555    Plugin.registerPlugin = function registerPlugin(name, plugin) {
27556      if (typeof name !== 'string') {
27557        throw new Error("Illegal plugin name, \"" + name + "\", must be a string, was " + typeof name + ".");
27558      }
27559
27560      if (pluginExists(name)) {
27561        log.warn("A plugin named \"" + name + "\" already exists. You may want to avoid re-registering plugins!");
27562      } else if (Player.prototype.hasOwnProperty(name)) {
27563        throw new Error("Illegal plugin name, \"" + name + "\", cannot share a name with an existing player method!");
27564      }
27565
27566      if (typeof plugin !== 'function') {
27567        throw new Error("Illegal plugin for \"" + name + "\", must be a function, was " + typeof plugin + ".");
27568      }
27569
27570      pluginStorage[name] = plugin; // Add a player prototype method for all sub-classed plugins (but not for
27571      // the base Plugin class).
27572
27573      if (name !== BASE_PLUGIN_NAME) {
27574        if (Plugin.isBasic(plugin)) {
27575          Player.prototype[name] = createBasicPlugin(name, plugin);
27576        } else {
27577          Player.prototype[name] = createPluginFactory(name, plugin);
27578        }
27579      }
27580
27581      return plugin;
27582    }
27583    /**
27584     * De-register a Video.js plugin.
27585     *
27586     * @param  {string} name
27587     *         The name of the plugin to be de-registered. Must be a string that
27588     *         matches an existing plugin.
27589     *
27590     * @throws {Error}
27591     *         If an attempt is made to de-register the base plugin.
27592     */
27593    ;
27594
27595    Plugin.deregisterPlugin = function deregisterPlugin(name) {
27596      if (name === BASE_PLUGIN_NAME) {
27597        throw new Error('Cannot de-register base plugin.');
27598      }
27599
27600      if (pluginExists(name)) {
27601        delete pluginStorage[name];
27602        delete Player.prototype[name];
27603      }
27604    }
27605    /**
27606     * Gets an object containing multiple Video.js plugins.
27607     *
27608     * @param   {Array} [names]
27609     *          If provided, should be an array of plugin names. Defaults to _all_
27610     *          plugin names.
27611     *
27612     * @return {Object|undefined}
27613     *          An object containing plugin(s) associated with their name(s) or
27614     *          `undefined` if no matching plugins exist).
27615     */
27616    ;
27617
27618    Plugin.getPlugins = function getPlugins(names) {
27619      if (names === void 0) {
27620        names = Object.keys(pluginStorage);
27621      }
27622
27623      var result;
27624      names.forEach(function (name) {
27625        var plugin = getPlugin(name);
27626
27627        if (plugin) {
27628          result = result || {};
27629          result[name] = plugin;
27630        }
27631      });
27632      return result;
27633    }
27634    /**
27635     * Gets a plugin's version, if available
27636     *
27637     * @param   {string} name
27638     *          The name of a plugin.
27639     *
27640     * @return {string}
27641     *          The plugin's version or an empty string.
27642     */
27643    ;
27644
27645    Plugin.getPluginVersion = function getPluginVersion(name) {
27646      var plugin = getPlugin(name);
27647      return plugin && plugin.VERSION || '';
27648    };
27649
27650    return Plugin;
27651  }();
27652  /**
27653   * Gets a plugin by name if it exists.
27654   *
27655   * @static
27656   * @method   getPlugin
27657   * @memberOf Plugin
27658   * @param    {string} name
27659   *           The name of a plugin.
27660   *
27661   * @returns  {Function|undefined}
27662   *           The plugin (or `undefined`).
27663   */
27664
27665
27666  Plugin.getPlugin = getPlugin;
27667  /**
27668   * The name of the base plugin class as it is registered.
27669   *
27670   * @type {string}
27671   */
27672
27673  Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
27674  Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
27675  /**
27676   * Documented in player.js
27677   *
27678   * @ignore
27679   */
27680
27681  Player.prototype.usingPlugin = function (name) {
27682    return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
27683  };
27684  /**
27685   * Documented in player.js
27686   *
27687   * @ignore
27688   */
27689
27690
27691  Player.prototype.hasPlugin = function (name) {
27692    return !!pluginExists(name);
27693  };
27694  /**
27695   * Signals that a plugin is about to be set up on a player.
27696   *
27697   * @event    Player#beforepluginsetup
27698   * @type     {Plugin~PluginEventHash}
27699   */
27700
27701  /**
27702   * Signals that a plugin is about to be set up on a player - by name. The name
27703   * is the name of the plugin.
27704   *
27705   * @event    Player#beforepluginsetup:$name
27706   * @type     {Plugin~PluginEventHash}
27707   */
27708
27709  /**
27710   * Signals that a plugin has just been set up on a player.
27711   *
27712   * @event    Player#pluginsetup
27713   * @type     {Plugin~PluginEventHash}
27714   */
27715
27716  /**
27717   * Signals that a plugin has just been set up on a player - by name. The name
27718   * is the name of the plugin.
27719   *
27720   * @event    Player#pluginsetup:$name
27721   * @type     {Plugin~PluginEventHash}
27722   */
27723
27724  /**
27725   * @typedef  {Object} Plugin~PluginEventHash
27726   *
27727   * @property {string} instance
27728   *           For basic plugins, the return value of the plugin function. For
27729   *           advanced plugins, the plugin instance on which the event is fired.
27730   *
27731   * @property {string} name
27732   *           The name of the plugin.
27733   *
27734   * @property {string} plugin
27735   *           For basic plugins, the plugin function. For advanced plugins, the
27736   *           plugin class/constructor.
27737   */
27738
27739  /**
27740   * @file extend.js
27741   * @module extend
27742   */
27743
27744  /**
27745   * A combination of node inherits and babel's inherits (after transpile).
27746   * Both work the same but node adds `super_` to the subClass
27747   * and Bable adds the superClass as __proto__. Both seem useful.
27748   *
27749   * @param {Object} subClass
27750   *        The class to inherit to
27751   *
27752   * @param {Object} superClass
27753   *        The class to inherit from
27754   *
27755   * @private
27756   */
27757  var _inherits$1 = function _inherits(subClass, superClass) {
27758    if (typeof superClass !== 'function' && superClass !== null) {
27759      throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
27760    }
27761
27762    subClass.prototype = Object.create(superClass && superClass.prototype, {
27763      constructor: {
27764        value: subClass,
27765        enumerable: false,
27766        writable: true,
27767        configurable: true
27768      }
27769    });
27770
27771    if (superClass) {
27772      // node
27773      subClass.super_ = superClass;
27774    }
27775  };
27776  /**
27777   * Used to subclass an existing class by emulating ES subclassing using the
27778   * `extends` keyword.
27779   *
27780   * @function
27781   * @example
27782   * var MyComponent = videojs.extend(videojs.getComponent('Component'), {
27783   *   myCustomMethod: function() {
27784   *     // Do things in my method.
27785   *   }
27786   * });
27787   *
27788   * @param    {Function} superClass
27789   *           The class to inherit from
27790   *
27791   * @param    {Object}   [subClassMethods={}]
27792   *           Methods of the new class
27793   *
27794   * @return   {Function}
27795   *           The new class with subClassMethods that inherited superClass.
27796   */
27797
27798
27799  var extend$1 = function extend(superClass, subClassMethods) {
27800    if (subClassMethods === void 0) {
27801      subClassMethods = {};
27802    }
27803
27804    var subClass = function subClass() {
27805      superClass.apply(this, arguments);
27806    };
27807
27808    var methods = {};
27809
27810    if (typeof subClassMethods === 'object') {
27811      if (subClassMethods.constructor !== Object.prototype.constructor) {
27812        subClass = subClassMethods.constructor;
27813      }
27814
27815      methods = subClassMethods;
27816    } else if (typeof subClassMethods === 'function') {
27817      subClass = subClassMethods;
27818    }
27819
27820    _inherits$1(subClass, superClass); // Extend subObj's prototype with functions and other properties from props
27821
27822
27823    for (var name in methods) {
27824      if (methods.hasOwnProperty(name)) {
27825        subClass.prototype[name] = methods[name];
27826      }
27827    }
27828
27829    return subClass;
27830  };
27831
27832  /**
27833   * @file video.js
27834   * @module videojs
27835   */
27836  /**
27837   * Normalize an `id` value by trimming off a leading `#`
27838   *
27839   * @private
27840   * @param   {string} id
27841   *          A string, maybe with a leading `#`.
27842   *
27843   * @return {string}
27844   *          The string, without any leading `#`.
27845   */
27846
27847  var normalizeId = function normalizeId(id) {
27848    return id.indexOf('#') === 0 ? id.slice(1) : id;
27849  };
27850  /**
27851   * The `videojs()` function doubles as the main function for users to create a
27852   * {@link Player} instance as well as the main library namespace.
27853   *
27854   * It can also be used as a getter for a pre-existing {@link Player} instance.
27855   * However, we _strongly_ recommend using `videojs.getPlayer()` for this
27856   * purpose because it avoids any potential for unintended initialization.
27857   *
27858   * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
27859   * of our JSDoc template, we cannot properly document this as both a function
27860   * and a namespace, so its function signature is documented here.
27861   *
27862   * #### Arguments
27863   * ##### id
27864   * string|Element, **required**
27865   *
27866   * Video element or video element ID.
27867   *
27868   * ##### options
27869   * Object, optional
27870   *
27871   * Options object for providing settings.
27872   * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
27873   *
27874   * ##### ready
27875   * {@link Component~ReadyCallback}, optional
27876   *
27877   * A function to be called when the {@link Player} and {@link Tech} are ready.
27878   *
27879   * #### Return Value
27880   *
27881   * The `videojs()` function returns a {@link Player} instance.
27882   *
27883   * @namespace
27884   *
27885   * @borrows AudioTrack as AudioTrack
27886   * @borrows Component.getComponent as getComponent
27887   * @borrows module:computed-style~computedStyle as computedStyle
27888   * @borrows module:events.on as on
27889   * @borrows module:events.one as one
27890   * @borrows module:events.off as off
27891   * @borrows module:events.trigger as trigger
27892   * @borrows EventTarget as EventTarget
27893   * @borrows module:extend~extend as extend
27894   * @borrows module:fn.bind as bind
27895   * @borrows module:format-time.formatTime as formatTime
27896   * @borrows module:format-time.resetFormatTime as resetFormatTime
27897   * @borrows module:format-time.setFormatTime as setFormatTime
27898   * @borrows module:merge-options.mergeOptions as mergeOptions
27899   * @borrows module:middleware.use as use
27900   * @borrows Player.players as players
27901   * @borrows Plugin.registerPlugin as registerPlugin
27902   * @borrows Plugin.deregisterPlugin as deregisterPlugin
27903   * @borrows Plugin.getPlugins as getPlugins
27904   * @borrows Plugin.getPlugin as getPlugin
27905   * @borrows Plugin.getPluginVersion as getPluginVersion
27906   * @borrows Tech.getTech as getTech
27907   * @borrows Tech.registerTech as registerTech
27908   * @borrows TextTrack as TextTrack
27909   * @borrows module:time-ranges.createTimeRanges as createTimeRange
27910   * @borrows module:time-ranges.createTimeRanges as createTimeRanges
27911   * @borrows module:url.isCrossOrigin as isCrossOrigin
27912   * @borrows module:url.parseUrl as parseUrl
27913   * @borrows VideoTrack as VideoTrack
27914   *
27915   * @param  {string|Element}
27915 id
27916   *         Video element or video element ID.
27917   *
27918   * @param  {Object} [options]
27919   *         Options object for providing settings.
27920   *         See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
27921   *
27922   * @param  {Component~ReadyCallback} [ready]
27923   *         A function to be called when the {@link Player} and {@link Tech} are
27924   *         ready.
27925   *
27926   * @return {Player}
27927   *         The `videojs()` function returns a {@link Player|Player} instance.
27928   */
27929
27930
27931  function videojs$1(id, options, ready) {
27932    var player = videojs$1.getPlayer(id);
27933
27934    if (player) {
27935      if (options) {
27936        log.warn("Player \"" + id + "\" is already initialised. Options will not be applied.");
27937      }
27938
27939      if (ready) {
27940        player.ready(ready);
27941      }
27942
27943      return player;
27944    }
27945
27946    var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
27947
27948    if (!isEl(el)) {
27949      throw new TypeError('The element or ID supplied is not valid. (videojs)');
27950    } // document.body.contains(el) will only check if el is contained within that one document.
27951    // This causes problems for elements in iframes.
27952    // Instead, use the element's ownerDocument instead of the global document.
27953    // This will make sure that the element is indeed in the dom of that document.
27954    // Additionally, check that the document in question has a default view.
27955    // If the document is no longer attached to the dom, the defaultView of the document will be null.
27956
27957
27958    if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) {
27959      log.warn('The element supplied is not included in the DOM');
27960    }
27961
27962    options = options || {};
27963    videojs$1.hooks('beforesetup').forEach(function (hookFunction) {
27964      var opts = hookFunction(el, mergeOptions(options));
27965
27966      if (!isObject(opts) || Array.isArray(opts)) {
27967        log.error('please return an object in beforesetup hooks');
27968        return;
27969      }
27970
27971      options = mergeOptions(options, opts);
27972    }); // We get the current "Player" component here in case an integration has
27973    // replaced it with a custom player.
27974
27975    var PlayerComponent = Component.getComponent('Player');
27976    player = new PlayerComponent(el, options, ready);
27977    videojs$1.hooks('setup').forEach(function (hookFunction) {
27978      return hookFunction(player);
27979    });
27980    return player;
27981  }
27982  /**
27983   * An Object that contains lifecycle hooks as keys which point to an array
27984   * of functions that are run when a lifecycle is triggered
27985   *
27986   * @private
27987   */
27988
27989
27990  videojs$1.hooks_ = {};
27991  /**
27992   * Get a list of hooks for a specific lifecycle
27993   *
27994   * @param  {string} type
27995   *         the lifecyle to get hooks from
27996   *
27997   * @param  {Function|Function[]} [fn]
27998   *         Optionally add a hook (or hooks) to the lifecycle that your are getting.
27999   *
28000   * @return {Array}
28001   *         an array of hooks, or an empty array if there are none.
28002   */
28003
28004  videojs$1.hooks = function (type, fn) {
28005    videojs$1.hooks_[type] = videojs$1.hooks_[type] || [];
28006
28007    if (fn) {
28008      videojs$1.hooks_[type] = videojs$1.hooks_[type].concat(fn);
28009    }
28010
28011    return videojs$1.hooks_[type];
28012  };
28013  /**
28014   * Add a function hook to a specific videojs lifecycle.
28015   *
28016   * @param {string} type
28017   *        the lifecycle to hook the function to.
28018   *
28019   * @param {Function|Function[]}
28020   *        The function or array of functions to attach.
28021   */
28022
28023
28024  videojs$1.hook = function (type, fn) {
28025    videojs$1.hooks(type, fn);
28026  };
28027  /**
28028   * Add a function hook that will only run once to a specific videojs lifecycle.
28029   *
28030   * @param {string} type
28031   *        the lifecycle to hook the function to.
28032   *
28033   * @param {Function|Function[]}
28034   *        The function or array of functions to attach.
28035   */
28036
28037
28038  videojs$1.hookOnce = function (type, fn) {
28039    videojs$1.hooks(type, [].concat(fn).map(function (original) {
28040      var wrapper = function wrapper() {
28041        videojs$1.removeHook(type, wrapper);
28042        return original.apply(void 0, arguments);
28043      };
28044
28045      return wrapper;
28046    }));
28047  };
28048  /**
28049   * Remove a hook from a specific videojs lifecycle.
28050   *
28051   * @param  {string} type
28052   *         the lifecycle that the function hooked to
28053   *
28054   * @param  {Function} fn
28055   *         The hooked function to remove
28056   *
28057   * @return {boolean}
28058   *         The function that was removed or undef
28059   */
28060
28061
vendor: 8,781 bytes, lines 28062-28357
28062  videojs$1.removeHook = function (type, fn) {
28063    var index = videojs$1.hooks(type).indexOf(fn);
28064
28065    if (index <= -1) {
28066      return false;
28067    }
28068
28069    videojs$1.hooks_[type] = videojs$1.hooks_[type].slice();
28070    videojs$1.hooks_[type].splice(index, 1);
28071    return true;
28072  }; // Add default styles
28073
28074
28075  if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
28076    var style$1 = $('.vjs-styles-defaults');
28077
28078    if (!style$1) {
28079      style$1 = createStyleElement('vjs-styles-defaults');
28080      var head = $('head');
28081
28082      if (head) {
28083        head.insertBefore(style$1, head.firstChild);
28084      }
28085
28086      setTextContent(style$1, "\n      .video-js {\n        width: 300px;\n        height: 150px;\n      }\n\n      .vjs-fluid {\n        padding-top: 56.25%\n      }\n    ");
28087    }
28088  } // Run Auto-load players
28089  // You have to wait at least once in case this script is loaded after your
28090  // video in the DOM (weird behavior only with minified version)
28091
28092
28093  autoSetupTimeout(1, videojs$1);
28094  /**
28095   * Current Video.js version. Follows [semantic versioning](https://semver.org/).
28096   *
28097   * @type {string}
28098   */
28099
28100  videojs$1.VERSION = version;
28101  /**
28102   * The global options object. These are the settings that take effect
28103   * if no overrides are specified when the player is created.
28104   *
28105   * @type {Object}
28106   */
28107
28108  videojs$1.options = Player.prototype.options_;
28109  /**
28110   * Get an object with the currently created players, keyed by player ID
28111   *
28112   * @return {Object}
28113   *         The created players
28114   */
28115
28116  videojs$1.getPlayers = function () {
28117    return Player.players;
28118  };
28119  /**
28120   * Get a single player based on an ID or DOM element.
28121   *
28122   * This is useful if you want to check if an element or ID has an associated
28123   * Video.js player, but not create one if it doesn't.
28124   *
28125   * @param   {string|Element} id
28126   *          An HTML element - `<video>`, `<audio>`, or `<video-js>` -
28127   *          or a string matching the `id` of such an element.
28128   *
28129   * @return {Player|undefined}
28130   *          A player instance or `undefined` if there is no player instance
28131   *          matching the argument.
28132   */
28133
28134
28135  videojs$1.getPlayer = function (id) {
28136    var players = Player.players;
28137    var tag;
28138
28139    if (typeof id === 'string') {
28140      var nId = normalizeId(id);
28141      var player = players[nId];
28142
28143      if (player) {
28144        return player;
28145      }
28146
28147      tag = $('#' + nId);
28148    } else {
28149      tag = id;
28150    }
28151
28152    if (isEl(tag)) {
28153      var _tag = tag,
28154          _player = _tag.player,
28155          playerId = _tag.playerId; // Element may have a `player` property referring to an already created
28156      // player instance. If so, return that.
28157
28158      if (_player || players[playerId]) {
28159        return _player || players[playerId];
28160      }
28161    }
28162  };
28163  /**
28164   * Returns an array of all current players.
28165   *
28166   * @return {Array}
28167   *         An array of all players. The array will be in the order that
28168   *         `Object.keys` provides, which could potentially vary between
28169   *         JavaScript engines.
28170   *
28171   */
28172
28173
28174  videojs$1.getAllPlayers = function () {
28175    return (// Disposed players leave a key with a `null` value, so we need to make sure
28176      // we filter those out.
28177      Object.keys(Player.players).map(function (k) {
28178        return Player.players[k];
28179      }).filter(Boolean)
28180    );
28181  };
28182
28183  videojs$1.players = Player.players;
28184  videojs$1.getComponent = Component.getComponent;
28185  /**
28186   * Register a component so it can referred to by name. Used when adding to other
28187   * components, either through addChild `component.addChild('myComponent')` or through
28188   * default children options  `{ children: ['myComponent'] }`.
28189   *
28190   * > NOTE: You could also just initialize the component before adding.
28191   * `component.addChild(new MyComponent());`
28192   *
28193   * @param {string} name
28194   *        The class name of the component
28195   *
28196   * @param {Component} comp
28197   *        The component class
28198   *
28199   * @return {Component}
28200   *         The newly registered component
28201   */
28202
28203  videojs$1.registerComponent = function (name$$1, comp) {
28204    if (Tech.isTech(comp)) {
28205      log.warn("The " + name$$1 + " tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)");
28206    }
28207
28208    Component.registerComponent.call(Component, name$$1, comp);
28209  };
28210
28211  videojs$1.getTech = Tech.getTech;
28212  videojs$1.registerTech = Tech.registerTech;
28213  videojs$1.use = use;
28214  /**
28215   * An object that can be returned by a middleware to signify
28216   * that the middleware is being terminated.
28217   *
28218   * @type {object}
28219   * @property {object} middleware.TERMINATOR
28220   */
28221
28222  Object.defineProperty(videojs$1, 'middleware', {
28223    value: {},
28224    writeable: false,
28225    enumerable: true
28226  });
28227  Object.defineProperty(videojs$1.middleware, 'TERMINATOR', {
28228    value: TERMINATOR,
28229    writeable: false,
28230    enumerable: true
28231  });
28232  /**
28233   * A reference to the {@link module:browser|browser utility module} as an object.
28234   *
28235   * @type {Object}
28236   * @see  {@link module:browser|browser}
28237   */
28238
28239  videojs$1.browser = browser;
28240  /**
28241   * Use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED} instead; only
28242   * included for backward-compatibility with 4.x.
28243   *
28244   * @deprecated Since version 5.0, use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED instead.
28245   * @type {boolean}
28246   */
28247
28248  videojs$1.TOUCH_ENABLED = TOUCH_ENABLED;
28249  videojs$1.extend = extend$1;
28250  videojs$1.mergeOptions = mergeOptions;
28251  videojs$1.bind = bind;
28252  videojs$1.registerPlugin = Plugin.registerPlugin;
28253  videojs$1.deregisterPlugin = Plugin.deregisterPlugin;
28254  /**
28255   * Deprecated method to register a plugin with Video.js
28256   *
28257   * @deprecated videojs.plugin() is deprecated; use videojs.registerPlugin() instead
28258   *
28259   * @param {string} name
28260   *        The plugin name
28261   *
28262   * @param {Plugin|Function} plugin
28263   *         The plugin sub-class or function
28264   */
28265
28266  videojs$1.plugin = function (name$$1, plugin) {
28267    log.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
28268    return Plugin.registerPlugin(name$$1, plugin);
28269  };
28270
28271  videojs$1.getPlugins = Plugin.getPlugins;
28272  videojs$1.getPlugin = Plugin.getPlugin;
28273  videojs$1.getPluginVersion = Plugin.getPluginVersion;
28274  /**
28275   * Adding languages so that they're available to all players.
28276   * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
28277   *
28278   * @param {string} code
28279   *        The language code or dictionary property
28280   *
28281   * @param {Object} data
28282   *        The data values to be translated
28283   *
28284   * @return {Object}
28285   *         The resulting language dictionary object
28286   */
28287
28288  videojs$1.addLanguage = function (code, data) {
28289    var _mergeOptions;
28290
28291    code = ('' + code).toLowerCase();
28292    videojs$1.options.languages = mergeOptions(videojs$1.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions));
28293    return videojs$1.options.languages[code];
28294  };
28295  /**
28296   * A reference to the {@link module:log|log utility module} as an object.
28297   *
28298   * @type {Function}
28299   * @see  {@link module:log|log}
28300   */
28301
28302
28303  videojs$1.log = log;
28304  videojs$1.createLogger = createLogger$1;
28305  videojs$1.createTimeRange = videojs$1.createTimeRanges = createTimeRanges;
28306  videojs$1.formatTime = formatTime;
28307  videojs$1.setFormatTime = setFormatTime;
28308  videojs$1.resetFormatTime = resetFormatTime;
28309  videojs$1.parseUrl = parseUrl;
28310  videojs$1.isCrossOrigin = isCrossOrigin;
28311  videojs$1.EventTarget = EventTarget;
28312  videojs$1.on = on;
28313  videojs$1.one = one;
28314  videojs$1.off = off;
28315  videojs$1.trigger = trigger;
28316  /**
28317   * A cross-browser XMLHttpRequest wrapper.
28318   *
28319   * @function
28320   * @param    {Object} options
28321   *           Settings for the request.
28322   *
28323   * @return   {XMLHttpRequest|XDomainRequest}
28324   *           The request object.
28325   *
28326   * @see      https://github.com/Raynos/xhr
28327   */
28328
28329  videojs$1.xhr = xhr;
28330  videojs$1.TextTrack = TextTrack;
28331  videojs$1.AudioTrack = AudioTrack;
28332  videojs$1.VideoTrack = VideoTrack;
28333  ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) {
28334    videojs$1[k] = function () {
28335      log.warn("videojs." + k + "() is deprecated; use videojs.dom." + k + "() instead");
28336      return Dom[k].apply(null, arguments);
28337    };
28338  });
28339  videojs$1.computedStyle = computedStyle;
28340  /**
28341   * A reference to the {@link module:dom|DOM utility module} as an object.
28342   *
28343   * @type {Object}
28344   * @see  {@link module:dom|dom}
28345   */
28346
28347  videojs$1.dom = Dom;
28348  /**
28349   * A reference to the {@link module:url|URL utility module} as an object.
28350   *
28351   * @type {Object}
28352   * @see  {@link module:url|url}
28353   */
28354
28355  videojs$1.url = Url;
28356
28357  var urlToolkit = createCommonjsModule(function (module, exports
vendor: 4,642 bytes, lines 28357-28464
28357) {
28358    // see https://tools.ietf.org/html/rfc1808
28359
28360    /* jshint ignore:start */
28361    (function (root) {
28362      /* jshint ignore:end */
28363      var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/;
28364      var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/;
28365      var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
28366      var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g;
28367      var URLToolkit = {
28368        // jshint ignore:line
28369        // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
28370        // E.g
28371        // With opts.alwaysNormalize = false (default, spec compliant)
28372        // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
28373        // With opts.alwaysNormalize = true (not spec compliant)
28374        // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
28375        buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) {
28376          opts = opts || {}; // remove any remaining space and CRLF
28377
28378          baseURL = baseURL.trim();
28379          relativeURL = relativeURL.trim();
28380
28381          if (!relativeURL) {
28382            // 2a) If the embedded URL is entirely empty, it inherits the
28383            // entire base URL (i.e., is set equal to the base URL)
28384            // and we are done.
28385            if (!opts.alwaysNormalize) {
28386              return baseURL;
28387            }
28388
28389            var basePartsForNormalise = URLToolkit.parseURL(baseURL);
28390
28391            if (!basePartsForNormalise) {
28392              throw new Error('Error trying to parse base URL.');
28393            }
28394
28395            basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
28396            return URLToolkit.buildURLFromParts(basePartsForNormalise);
28397          }
28398
28399          var relativeParts = URLToolkit.parseURL(relativeURL);
28400
28401          if (!relativeParts) {
28402            throw new Error('Error trying to parse relative URL.');
28403          }
28404
28405          if (relativeParts.scheme) {
28406            // 2b) If the embedded URL starts with a scheme name, it is
28407            // interpreted as an absolute URL and we are done.
28408            if (!opts.alwaysNormalize) {
28409              return relativeURL;
28410            }
28411
28412            relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
28413            return URLToolkit.buildURLFromParts(relativeParts);
28414          }
28415
28416          var baseParts = URLToolkit.parseURL(baseURL);
28417
28418          if (!baseParts) {
28419            throw new Error('Error trying to parse base URL.');
28420          }
28421
28422          if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
28423            // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
28424            // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
28425            var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
28426            baseParts.netLoc = pathParts[1];
28427            baseParts.path = pathParts[2];
28428          }
28429
28430          if (baseParts.netLoc && !baseParts.path) {
28431            baseParts.path = '/';
28432          }
28433
28434          var builtParts = {
28435            // 2c) Otherwise, the embedded URL inherits the scheme of
28436            // the base URL.
28437            scheme: baseParts.scheme,
28438            netLoc: relativeParts.netLoc,
28439            path: null,
28440            params: relativeParts.params,
28441            query: relativeParts.query,
28442            fragment: relativeParts.fragment
28443          };
28444
28445          if (!relativeParts.netLoc) {
28446            // 3) If the embedded URL's <net_loc> is non-empty, we skip to
28447            // Step 7.  Otherwise, the embedded URL inherits the <net_loc>
28448            // (if any) of the base URL.
28449            builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the
28450            // path is not relative and we skip to Step 7.
28451
28452            if (relativeParts.path[0] !== '/') {
28453              if (!relativeParts.path) {
28454                // 5) If the embedded URL path is empty (and not preceded by a
28455                // slash), then the embedded URL inherits the base URL path
28456                builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to
28457                // step 7; otherwise, it inherits the <params> of the base
28458                // URL (if any) and
28459
28460                if (!relativeParts.params) {
28461                  builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to
28462                  // step 7; otherwise, it inherits the <query> of the base
28463                  // URL (if any) and we skip to step 7.
28464
28465                  if (!relativeParts.query) {
28466                    builtParts.query = baseParts.query;
28467                  }
28468                }
28469              } else {
28470                // 6) The last segment of the base URL's path (anything
28471                // following the rightmost slash "/", or the entire path if no
28472                // slash is present) is removed and the embedded URL's path is
28473                // appended in its place.
28474                var baseURLPath = baseParts.path;
28475                var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
28476                builtParts.path = URLToolkit.normalizePath(newPath);
28477              }
28478            }
28479          }
28480
28481          if (builtParts.path === null) {
28482            builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
28483          }
28484
28485          return URLToolkit.buildURLFromParts(builtParts);
28486        },
28487        parseURL: function parseURL(url) {
28488          var parts = URL_REGEX.exec(url);
28489
28490          if (!parts) {
28491            return null;
28492          }
28493
28494          return {
28495            scheme: parts[1] || '',
28496            netLoc: parts[2] || '',
28497            path: parts[3] || '',
28498            params: parts[4] || '',
28499            query: parts[5] || '',
28500            fragment: parts[6] || ''
28501          };
28502        },
28503        normalizePath: function normalizePath(path) {
28504          // The following operations are
28505          // then applied, in order, to the new path:
28506          // 6a) All occurrences of "./", where "." is a complete path
28507          // segment, are removed.
28508          // 6b) If the path ends with "." as a complete path segment,
28509          // that "." is removed.
28510          path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a
28511          // complete path segment not equal to "..", are removed.
28512          // Removal of these path segments is performed iteratively,
28513          // removing the leftmost matching pattern on each iteration,
28514          // until no matching pattern remains.
28515          // 6d) If the path ends with "<segment>/..", where <segment> is a
28516          // complete path segment not equal to "..", that
28517          // "<segment>/.." is removed.
28518
28519          while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line
28520
28521
28522          return path.split('').reverse().join('');
28523        },
28524        buildURLFromParts: function buildURLFromParts(parts) {
28525          return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
28526        }
28527      };
28528      /* jshint ignore:start */
28529
28530      module.exports = URLToolkit;
28531    })(commonjsGlobal);
28532    /* jshint ignore:end */
28533
28534  });
28535
28536  /*! @name m3u8-parser @version 4.3.0 @license Apache-2.0 */
28537  function _extends$1() {
28538    _extends$1 = Object.assign || function (target) {
28539      for (var i = 1; i < arguments.length; i++) {
28540        var source = arguments[i];
28541
28542        for (var key in source) {
28543          if (Object.prototype.hasOwnProperty.call(source, key)) {
28544            target[key] = source[key];
28545          }
28546        }
28547      }
28548
28549      return target;
28550    };
28551
28552    return _extends$1.apply(this, arguments);
28553  }
28554
28555  function _inheritsLoose$1(subClass, superClass) {
28556    subClass.prototype = Object.create(superClass.prototype);
28557    subClass.prototype.constructor = subClass;
28558    subClass.__proto__ = superClass;
28559  }
28560
28561  function _assertThisInitialized$1(self) {
28562    if (self === void 0) {
28563      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
28564    }
28565
28566    return self;
28567  }
28568  /**
28569   * @file stream.js
28570   */
28571
28572  /**
28573   * A lightweight readable stream implementation that handles event dispatching.
28574   *
28575   * @class Stream
28576   */
28577
28578
28579  var Stream =
28580  /*#__PURE__*/
28581  function () {
28582    function Stream() {
28583      this.listeners = {};
28584    }
28585    /**
28586     * Add a listener for a specified event type.
28587     *
28588     * @param {string} type the event name
28589     * @param {Function} listener the callback to be invoked when an event of
28590     * the specified type occurs
28591     */
28592
28593
28594    var _proto = Stream.prototype;
28595
28596    _proto.on = function on(type, listener) {
28597      if (!this.listeners[type]) {
28598        this.listeners[type] = [];
28599      }
28600
28601      this.listeners[type].push(listener);
28602    };
28603    /**
28604     * Remove a listener for a specified event type.
28605     *
28606     * @param {string} type the event name
28607     * @param {Function} listener  a function previously registered for this
28608     * type of event through `on`
28609     * @return {boolean} if we could turn it off or not
28610     */
28611
28612
28613    _proto.off = function off(type, listener) {
28614      if (!this.listeners[type]) {
28615        return false;
vendor: 33,768 bytes, lines 28616-29795
28616      }
28617
28618      var index = this.listeners[type].indexOf(listener);
28619      this.listeners[type].splice(index, 1);
28620      return index > -1;
28621    };
28622    /**
28623     * Trigger an event of the specified type on this stream. Any additional
28624     * arguments to this function are passed as parameters to event listeners.
28625     *
28626     * @param {string} type the event name
28627     */
28628
28629
28630    _proto.trigger = function trigger(type) {
28631      var callbacks = this.listeners[type];
28632      var i;
28633      var length;
28634      var args;
28635
28636      if (!callbacks) {
28637        return;
28638      } // Slicing the arguments on every invocation of this method
28639      // can add a significant amount of overhead. Avoid the
28640      // intermediate object creation for the common case of a
28641      // single callback argument
28642
28643
28644      if (arguments.length === 2) {
28645        length = callbacks.length;
28646
28647        for (i = 0; i < length; ++i) {
28648          callbacks[i].call(this, arguments[1]);
28649        }
28650      } else {
28651        args = Array.prototype.slice.call(arguments, 1);
28652        length = callbacks.length;
28653
28654        for (i = 0; i < length; ++i) {
28655          callbacks[i].apply(this, args);
28656        }
28657      }
28658    };
28659    /**
28660     * Destroys the stream and cleans up.
28661     */
28662
28663
28664    _proto.dispose = function dispose() {
28665      this.listeners = {};
28666    };
28667    /**
28668     * Forwards all `data` events on this stream to the destination stream. The
28669     * destination stream should provide a method `push` to receive the data
28670     * events as they arrive.
28671     *
28672     * @param {Stream} destination the stream that will receive all `data` events
28673     * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
28674     */
28675
28676
28677    _proto.pipe = function pipe(destination) {
28678      this.on('data', function (data) {
28679        destination.push(data);
28680      });
28681    };
28682
28683    return Stream;
28684  }();
28685  /**
28686   * A stream that buffers string input and generates a `data` event for each
28687   * line.
28688   *
28689   * @class LineStream
28690   * @extends Stream
28691   */
28692
28693
28694  var LineStream =
28695  /*#__PURE__*/
28696  function (_Stream) {
28697    _inheritsLoose$1(LineStream, _Stream);
28698
28699    function LineStream() {
28700      var _this;
28701
28702      _this = _Stream.call(this) || this;
28703      _this.buffer = '';
28704      return _this;
28705    }
28706    /**
28707     * Add new data to be parsed.
28708     *
28709     * @param {string} data the text to process
28710     */
28711
28712
28713    var _proto = LineStream.prototype;
28714
28715    _proto.push = function push(data) {
28716      var nextNewline;
28717      this.buffer += data;
28718      nextNewline = this.buffer.indexOf('\n');
28719
28720      for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
28721        this.trigger('data', this.buffer.substring(0, nextNewline));
28722        this.buffer = this.buffer.substring(nextNewline + 1);
28723      }
28724    };
28725
28726    return LineStream;
28727  }(Stream);
28728  /**
28729   * "forgiving" attribute list psuedo-grammar:
28730   * attributes -> keyvalue (',' keyvalue)*
28731   * keyvalue   -> key '=' value
28732   * key        -> [^=]*
28733   * value      -> '"' [^"]* '"' | [^,]*
28734   */
28735
28736
28737  var attributeSeparator = function attributeSeparator() {
28738    var key = '[^=]*';
28739    var value = '"[^"]*"|[^,]*';
28740    var keyvalue = '(?:' + key + ')=(?:' + value + ')';
28741    return new RegExp('(?:^|,)(' + keyvalue + ')');
28742  };
28743  /**
28744   * Parse attributes from a line given the separator
28745   *
28746   * @param {string} attributes the attribute line to parse
28747   */
28748
28749
28750  var parseAttributes = function parseAttributes(attributes) {
28751    // split the string using attributes as the separator
28752    var attrs = attributes.split(attributeSeparator());
28753    var result = {};
28754    var i = attrs.length;
28755    var attr;
28756
28757    while (i--) {
28758      // filter out unmatched portions of the string
28759      if (attrs[i] === '') {
28760        continue;
28761      } // split the key and value
28762
28763
28764      attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value
28765
28766      attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
28767      attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
28768      attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
28769      result[attr[0]] = attr[1];
28770    }
28771
28772    return result;
28773  };
28774  /**
28775   * A line-level M3U8 parser event stream. It expects to receive input one
28776   * line at a time and performs a context-free parse of its contents. A stream
28777   * interpretation of a manifest can be useful if the manifest is expected to
28778   * be too large to fit comfortably into memory or the entirety of the input
28779   * is not immediately available. Otherwise, it's probably much easier to work
28780   * with a regular `Parser` object.
28781   *
28782   * Produces `data` events with an object that captures the parser's
28783   * interpretation of the input. That object has a property `tag` that is one
28784   * of `uri`, `comment`, or `tag`. URIs only have a single additional
28785   * property, `line`, which captures the entirety of the input without
28786   * interpretation. Comments similarly have a single additional property
28787   * `text` which is the input without the leading `#`.
28788   *
28789   * Tags always have a property `tagType` which is the lower-cased version of
28790   * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
28791   * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
28792   * tags are given the tag type `unknown` and a single additional property
28793   * `data` with the remainder of the input.
28794   *
28795   * @class ParseStream
28796   * @extends Stream
28797   */
28798
28799
28800  var ParseStream =
28801  /*#__PURE__*/
28802  function (_Stream) {
28803    _inheritsLoose$1(ParseStream, _Stream);
28804
28805    function ParseStream() {
28806      var _this;
28807
28808      _this = _Stream.call(this) || this;
28809      _this.customParsers = [];
28810      _this.tagMappers = [];
28811      return _this;
28812    }
28813    /**
28814     * Parses an additional line of input.
28815     *
28816     * @param {string} line a single line of an M3U8 file to parse
28817     */
28818
28819
28820    var _proto = ParseStream.prototype;
28821
28822    _proto.push = function push(line) {
28823      var _this2 = this;
28824
28825      var match;
28826      var event; // strip whitespace
28827
28828      line = line.trim();
28829
28830      if (line.length === 0) {
28831        // ignore empty lines
28832        return;
28833      } // URIs
28834
28835
28836      if (line[0] !== '#') {
28837        this.trigger('data', {
28838          type: 'uri',
28839          uri: line
28840        });
28841        return;
28842      } // map tags
28843
28844
28845      var newLines = this.tagMappers.reduce(function (acc, mapper) {
28846        var mappedLine = mapper(line); // skip if unchanged
28847
28848        if (mappedLine === line) {
28849          return acc;
28850        }
28851
28852        return acc.concat([mappedLine]);
28853      }, [line]);
28854      newLines.forEach(function (newLine) {
28855        for (var i = 0; i < _this2.customParsers.length; i++) {
28856          if (_this2.customParsers[i].call(_this2, newLine)) {
28857            return;
28858          }
28859        } // Comments
28860
28861
28862        if (newLine.indexOf('#EXT') !== 0) {
28863          _this2.trigger('data', {
28864            type: 'comment',
28865            text: newLine.slice(1)
28866          });
28867
28868          return;
28869        } // strip off any carriage returns here so the regex matching
28870        // doesn't have to account for them.
28871
28872
28873        newLine = newLine.replace('\r', ''); // Tags
28874
28875        match = /^#EXTM3U/.exec(newLine);
28876
28877        if (match) {
28878          _this2.trigger('data', {
28879            type: 'tag',
28880            tagType: 'm3u'
28881          });
28882
28883          return;
28884        }
28885
28886        match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(newLine);
28887
28888        if (match) {
28889          event = {
28890            type: 'tag',
28891            tagType: 'inf'
28892          };
28893
28894          if (match[1]) {
28895            event.duration = parseFloat(match[1]);
28896          }
28897
28898          if (match[2]) {
28899            event.title = match[2];
28900          }
28901
28902          _this2.trigger('data', event);
28903
28904          return;
28905        }
28906
28907        match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(newLine);
28908
28909        if (match) {
28910          event = {
28911            type: 'tag',
28912            tagType: 'targetduration'
28913          };
28914
28915          if (match[1]) {
28916            event.duration = parseInt(match[1], 10);
28917          }
28918
28919          _this2.trigger('data', event);
28920
28921          return;
28922        }
28923
28924        match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(newLine);
28925
28926        if (match) {
28927          event = {
28928            type: 'tag',
28929            tagType: 'totalduration'
28930          };
28931
28932          if (match[1]) {
28933            event.duration = parseInt(match[1], 10);
28934          }
28935
28936          _this2.trigger('data', event);
28937
28938          return;
28939        }
28940
28941        match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(newLine);
28942
28943        if (match) {
28944          event = {
28945            type: 'tag',
28946            tagType: 'version'
28947          };
28948
28949          if (match[1]) {
28950            event.version = parseInt(match[1], 10);
28951          }
28952
28953          _this2.trigger('data', event);
28954
28955          return;
28956        }
28957
28958        match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
28959
28960        if (match) {
28961          event = {
28962            type: 'tag',
28963            tagType: 'media-sequence'
28964          };
28965
28966          if (match[1]) {
28967            event.number = parseInt(match[1], 10);
28968          }
28969
28970          _this2.trigger('data', event);
28971
28972          return;
28973        }
28974
28975        match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
28976
28977        if (match) {
28978          event = {
28979            type: 'tag',
28980            tagType: 'discontinuity-sequence'
28981          };
28982
28983          if (match[1]) {
28984            event.number = parseInt(match[1], 10);
28985          }
28986
28987          _this2.trigger('data', event);
28988
28989          return;
28990        }
28991
28992        match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(newLine);
28993
28994        if (match) {
28995          event = {
28996            type: 'tag',
28997            tagType: 'playlist-type'
28998          };
28999
29000          if (match[1]) {
29001            event.playlistType = match[1];
29002          }
29003
29004          _this2.trigger('data', event);
29005
29006          return;
29007        }
29008
29009        match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(newLine);
29010
29011        if (match) {
29012          event = {
29013            type: 'tag',
29014            tagType: 'byterange'
29015          };
29016
29017          if (match[1]) {
29018            event.length = parseInt(match[1], 10);
29019          }
29020
29021          if (match[2]) {
29022            event.offset = parseInt(match[2], 10);
29023          }
29024
29025          _this2.trigger('data', event);
29026
29027          return;
29028        }
29029
29030        match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(newLine);
29031
29032        if (match) {
29033          event = {
29034            type: 'tag',
29035            tagType: 'allow-cache'
29036          };
29037
29038          if (match[1]) {
29039            event.allowed = !/NO/.test(match[1]);
29040          }
29041
29042          _this2.trigger('data', event);
29043
29044          return;
29045        }
29046
29047        match = /^#EXT-X-MAP:?(.*)$/.exec(newLine);
29048
29049        if (match) {
29050          event = {
29051            type: 'tag',
29052            tagType: 'map'
29053          };
29054
29055          if (match[1]) {
29056            var attributes = parseAttributes(match[1]);
29057
29058            if (attributes.URI) {
29059              event.uri = attributes.URI;
29060            }
29061
29062            if (attributes.BYTERANGE) {
29063              var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'),
29064                  length = _attributes$BYTERANGE[0],
29065                  offset = _attributes$BYTERANGE[1];
29066
29067              event.byterange = {};
29068
29069              if (length) {
29070                event.byterange.length = parseInt(length, 10);
29071              }
29072
29073              if (offset) {
29074                event.byterange.offset = parseInt(offset, 10);
29075              }
29076            }
29077          }
29078
29079          _this2.trigger('data', event);
29080
29081          return;
29082        }
29083
29084        match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(newLine);
29085
29086        if (match) {
29087          event = {
29088            type: 'tag',
29089            tagType: 'stream-inf'
29090          };
29091
29092          if (match[1]) {
29093            event.attributes = parseAttributes(match[1]);
29094
29095            if (event.attributes.RESOLUTION) {
29096              var split = event.attributes.RESOLUTION.split('x');
29097              var resolution = {};
29098
29099              if (split[0]) {
29100                resolution.width = parseInt(split[0], 10);
29101              }
29102
29103              if (split[1]) {
29104                resolution.height = parseInt(split[1], 10);
29105              }
29106
29107              event.attributes.RESOLUTION = resolution;
29108            }
29109
29110            if (event.attributes.BANDWIDTH) {
29111              event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
29112            }
29113
29114            if (event.attributes['PROGRAM-ID']) {
29115              event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
29116            }
29117          }
29118
29119          _this2.trigger('data', event);
29120
29121          return;
29122        }
29123
29124        match = /^#EXT-X-MEDIA:?(.*)$/.exec(newLine);
29125
29126        if (match) {
29127          event = {
29128            type: 'tag',
29129            tagType: 'media'
29130          };
29131
29132          if (match[1]) {
29133            event.attributes = parseAttributes(match[1]);
29134          }
29135
29136          _this2.trigger('data', event);
29137
29138          return;
29139        }
29140
29141        match = /^#EXT-X-ENDLIST/.exec(newLine);
29142
29143        if (match) {
29144          _this2.trigger('data', {
29145            type: 'tag',
29146            tagType: 'endlist'
29147          });
29148
29149          return;
29150        }
29151
29152        match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
29153
29154        if (match) {
29155          _this2.trigger('data', {
29156            type: 'tag',
29157            tagType: 'discontinuity'
29158          });
29159
29160          return;
29161        }
29162
29163        match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(newLine);
29164
29165        if (match) {
29166          event = {
29167            type: 'tag',
29168            tagType: 'program-date-time'
29169          };
29170
29171          if (match[1]) {
29172            event.dateTimeString = match[1];
29173            event.dateTimeObject = new Date(match[1]);
29174          }
29175
29176          _this2.trigger('data', event);
29177
29178          return;
29179        }
29180
29181        match = /^#EXT-X-KEY:?(.*)$/.exec(newLine);
29182
29183        if (match) {
29184          event = {
29185            type: 'tag',
29186            tagType: 'key'
29187          };
29188
29189          if (match[1]) {
29190            event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array
29191
29192            if (event.attributes.IV) {
29193              if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
29194                event.attributes.IV = event.attributes.IV.substring(2);
29195              }
29196
29197              event.attributes.IV = event.attributes.IV.match(/.{8}/g);
29198              event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
29199              event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
29200              event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
29201              event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
29202              event.attributes.IV = new Uint32Array(event.attributes.IV);
29203            }
29204          }
29205
29206          _this2.trigger('data', event);
29207
29208          return;
29209        }
29210
29211        match = /^#EXT-X-START:?(.*)$/.exec(newLine);
29212
29213        if (match) {
29214          event = {
29215            type: 'tag',
29216            tagType: 'start'
29217          };
29218
29219          if (match[1]) {
29220            event.attributes = parseAttributes(match[1]);
29221            event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']);
29222            event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
29223          }
29224
29225          _this2.trigger('data', event);
29226
29227          return;
29228        }
29229
29230        match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(newLine);
29231
29232        if (match) {
29233          event = {
29234            type: 'tag',
29235            tagType: 'cue-out-cont'
29236          };
29237
29238          if (match[1]) {
29239            event.data = match[1];
29240          } else {
29241            event.data = '';
29242          }
29243
29244          _this2.trigger('data', event);
29245
29246          return;
29247        }
29248
29249        match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(newLine);
29250
29251        if (match) {
29252          event = {
29253            type: 'tag',
29254            tagType: 'cue-out'
29255          };
29256
29257          if (match[1]) {
29258            event.data = match[1];
29259          } else {
29260            event.data = '';
29261          }
29262
29263          _this2.trigger('data', event);
29264
29265          return;
29266        }
29267
29268        match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine);
29269
29270        if (match) {
29271          event = {
29272            type: 'tag',
29273            tagType: 'cue-in'
29274          };
29275
29276          if (match[1]) {
29277            event.data = match[1];
29278          } else {
29279            event.data = '';
29280          }
29281
29282          _this2.trigger('data', event);
29283
29284          return;
29285        } // unknown tag type
29286
29287
29288        _this2.trigger('data', {
29289          type: 'tag',
29290          data: newLine.slice(4)
29291        });
29292      });
29293    };
29294    /**
29295     * Add a parser for custom headers
29296     *
29297     * @param {Object}   options              a map of options for the added parser
29298     * @param {RegExp}   options.expression   a regular expression to match the custom header
29299     * @param {string}   options.customType   the custom type to register to the output
29300     * @param {Function} [options.dataParser] function to parse the line into an object
29301     * @param {boolean}  [options.segment]    should tag data be attached to the segment object
29302     */
29303
29304
29305    _proto.addParser = function addParser(_ref) {
29306      var _this3 = this;
29307
29308      var expression = _ref.expression,
29309          customType = _ref.customType,
29310          dataParser = _ref.dataParser,
29311          segment = _ref.segment;
29312
29313      if (typeof dataParser !== 'function') {
29314        dataParser = function dataParser(line) {
29315          return line;
29316        };
29317      }
29318
29319      this.customParsers.push(function (line) {
29320        var match = expression.exec(line);
29321
29322        if (match) {
29323          _this3.trigger('data', {
29324            type: 'custom',
29325            data: dataParser(line),
29326            customType: customType,
29327            segment: segment
29328          });
29329
29330          return true;
29331        }
29332      });
29333    };
29334    /**
29335     * Add a custom header mapper
29336     *
29337     * @param {Object}   options
29338     * @param {RegExp}   options.expression   a regular expression to match the custom header
29339     * @param {Function} options.map          function to translate tag into a different tag
29340     */
29341
29342
29343    _proto.addTagMapper = function addTagMapper(_ref2) {
29344      var expression = _ref2.expression,
29345          map = _ref2.map;
29346
29347      var mapFn = function mapFn(line) {
29348        if (expression.test(line)) {
29349          return map(line);
29350        }
29351
29352        return line;
29353      };
29354
29355      this.tagMappers.push(mapFn);
29356    };
29357
29358    return ParseStream;
29359  }(Stream);
29360  /**
29361   * A parser for M3U8 files. The current interpretation of the input is
29362   * exposed as a property `manifest` on parser objects. It's just two lines to
29363   * create and parse a manifest once you have the contents available as a string:
29364   *
29365   * ```js
29366   * var parser = new m3u8.Parser();
29367   * parser.push(xhr.responseText);
29368   * ```
29369   *
29370   * New input can later be applied to update the manifest object by calling
29371   * `push` again.
29372   *
29373   * The parser attempts to create a usable manifest object even if the
29374   * underlying input is somewhat nonsensical. It emits `info` and `warning`
29375   * events during the parse if it encounters input that seems invalid or
29376   * requires some property of the manifest object to be defaulted.
29377   *
29378   * @class Parser
29379   * @extends Stream
29380   */
29381
29382
29383  var Parser =
29384  /*#__PURE__*/
29385  function (_Stream) {
29386    _inheritsLoose$1(Parser, _Stream);
29387
29388    function Parser() {
29389      var _this;
29390
29391      _this = _Stream.call(this) || this;
29392      _this.lineStream = new LineStream();
29393      _this.parseStream = new ParseStream();
29394
29395      _this.lineStream.pipe(_this.parseStream);
29396      /* eslint-disable consistent-this */
29397
29398
29399      var self = _assertThisInitialized$1(_assertThisInitialized$1(_this));
29400      /* eslint-enable consistent-this */
29401
29402
29403      var uris = [];
29404      var currentUri = {}; // if specified, the active EXT-X-MAP definition
29405
29406      var currentMap; // if specified, the active decryption key
29407
29408      var _key;
29409
29410      var noop = function noop() {};
29411
29412      var defaultMediaGroups = {
29413        'AUDIO': {},
29414        'VIDEO': {},
29415        'CLOSED-CAPTIONS': {},
29416        'SUBTITLES': {}
29417      }; // group segments into numbered timelines delineated by discontinuities
29418
29419      var currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
29420
29421      _this.manifest = {
29422        allowCache: true,
29423        discontinuityStarts: [],
29424        segments: []
29425      }; // update the manifest with the m3u8 entry from the parse stream
29426
29427      _this.parseStream.on('data', function (entry) {
29428        var mediaGroup;
29429        var rendition;
29430        ({
29431          tag: function tag() {
29432            // switch based on the tag type
29433            (({
29434              'allow-cache': function allowCache() {
29435                this.manifest.allowCache = entry.allowed;
29436
29437                if (!('allowed' in entry)) {
29438                  this.trigger('info', {
29439                    message: 'defaulting allowCache to YES'
29440                  });
29441                  this.manifest.allowCache = true;
29442                }
29443              },
29444              byterange: function byterange() {
29445                var byterange = {};
29446
29447                if ('length' in entry) {
29448                  currentUri.byterange = byterange;
29449                  byterange.length = entry.length;
29450
29451                  if (!('offset' in entry)) {
29452                    this.trigger('info', {
29453                      message: 'defaulting offset to zero'
29454                    });
29455                    entry.offset = 0;
29456                  }
29457                }
29458
29459                if ('offset' in entry) {
29460                  currentUri.byterange = byterange;
29461                  byterange.offset = entry.offset;
29462                }
29463              },
29464              endlist: function endlist() {
29465                this.manifest.endList = true;
29466              },
29467              inf: function inf() {
29468                if (!('mediaSequence' in this.manifest)) {
29469                  this.manifest.mediaSequence = 0;
29470                  this.trigger('info', {
29471                    message: 'defaulting media sequence to zero'
29472                  });
29473                }
29474
29475                if (!('discontinuitySequence' in this.manifest)) {
29476                  this.manifest.discontinuitySequence = 0;
29477                  this.trigger('info', {
29478                    message: 'defaulting discontinuity sequence to zero'
29479                  });
29480                }
29481
29482                if (entry.duration > 0) {
29483                  currentUri.duration = entry.duration;
29484                }
29485
29486                if (entry.duration === 0) {
29487                  currentUri.duration = 0.01;
29488                  this.trigger('info', {
29489                    message: 'updating zero segment duration to a small value'
29490                  });
29491                }
29492
29493                this.manifest.segments = uris;
29494              },
29495              key: function key() {
29496                if (!entry.attributes) {
29497                  this.trigger('warn', {
29498                    message: 'ignoring key declaration without attribute list'
29499                  });
29500                  return;
29501                } // clear the active encryption key
29502
29503
29504                if (entry.attributes.METHOD === 'NONE') {
29505                  _key = null;
29506                  return;
29507                }
29508
29509                if (!entry.attributes.URI) {
29510                  this.trigger('warn', {
29511                    message: 'ignoring key declaration without URI'
29512                  });
29513                  return;
29514                }
29515
29516                if (!entry.attributes.METHOD) {
29517                  this.trigger('warn', {
29518                    message: 'defaulting key method to AES-128'
29519                  });
29520                } // setup an encryption key for upcoming segments
29521
29522
29523                _key = {
29524                  method: entry.attributes.METHOD || 'AES-128',
29525                  uri: entry.attributes.URI
29526                };
29527
29528                if (typeof entry.attributes.IV !== 'undefined') {
29529                  _key.iv = entry.attributes.IV;
29530                }
29531              },
29532              'media-sequence': function mediaSequence() {
29533                if (!isFinite(entry.number)) {
29534                  this.trigger('warn', {
29535                    message: 'ignoring invalid media sequence: ' + entry.number
29536                  });
29537                  return;
29538                }
29539
29540                this.manifest.mediaSequence = entry.number;
29541              },
29542              'discontinuity-sequence': function discontinuitySequence() {
29543                if (!isFinite(entry.number)) {
29544                  this.trigger('warn', {
29545                    message: 'ignoring invalid discontinuity sequence: ' + entry.number
29546                  });
29547                  return;
29548                }
29549
29550                this.manifest.discontinuitySequence = entry.number;
29551                currentTimeline = entry.number;
29552              },
29553              'playlist-type': function playlistType() {
29554                if (!/VOD|EVENT/.test(entry.playlistType)) {
29555                  this.trigger('warn', {
29556                    message: 'ignoring unknown playlist type: ' + entry.playlist
29557                  });
29558                  return;
29559                }
29560
29561                this.manifest.playlistType = entry.playlistType;
29562              },
29563              map: function map() {
29564                currentMap = {};
29565
29566                if (entry.uri) {
29567                  currentMap.uri = entry.uri;
29568                }
29569
29570                if (entry.byterange) {
29571                  currentMap.byterange = entry.byterange;
29572                }
29573              },
29574              'stream-inf': function streamInf() {
29575                this.manifest.playlists = uris;
29576                this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
29577
29578                if (!entry.attributes) {
29579                  this.trigger('warn', {
29580                    message: 'ignoring empty stream-inf attributes'
29581                  });
29582                  return;
29583                }
29584
29585                if (!currentUri.attributes) {
29586                  currentUri.attributes = {};
29587                }
29588
29589                _extends$1(currentUri.attributes, entry.attributes);
29590              },
29591              media: function media() {
29592                this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
29593
29594                if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
29595                  this.trigger('warn', {
29596                    message: 'ignoring incomplete or missing media group'
29597                  });
29598                  return;
29599                } // find the media group, creating defaults as necessary
29600
29601
29602                var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
29603                mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
29604                mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata
29605
29606                rendition = {
29607                  default: /yes/i.test(entry.attributes.DEFAULT)
29608                };
29609
29610                if (rendition.default) {
29611                  rendition.autoselect = true;
29612                } else {
29613                  rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
29614                }
29615
29616                if (entry.attributes.LANGUAGE) {
29617                  rendition.language = entry.attributes.LANGUAGE;
29618                }
29619
29620                if (entry.attributes.URI) {
29621                  rendition.uri = entry.attributes.URI;
29622                }
29623
29624                if (entry.attributes['INSTREAM-ID']) {
29625                  rendition.instreamId = entry.attributes['INSTREAM-ID'];
29626                }
29627
29628                if (entry.attributes.CHARACTERISTICS) {
29629                  rendition.characteristics = entry.attributes.CHARACTERISTICS;
29630                }
29631
29632                if (entry.attributes.FORCED) {
29633                  rendition.forced = /yes/i.test(entry.attributes.FORCED);
29634                } // insert the new rendition
29635
29636
29637                mediaGroup[entry.attributes.NAME] = rendition;
29638              },
29639              discontinuity: function discontinuity() {
29640                currentTimeline += 1;
29641                currentUri.discontinuity = true;
29642                this.manifest.discontinuityStarts.push(uris.length);
29643              },
29644              'program-date-time': function programDateTime() {
29645                if (typeof this.manifest.dateTimeString === 'undefined') {
29646                  // PROGRAM-DATE-TIME is a media-segment tag, but for backwards
29647                  // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
29648                  // to the manifest object
29649                  // TODO: Consider removing this in future major version
29650                  this.manifest.dateTimeString = entry.dateTimeString;
29651                  this.manifest.dateTimeObject = entry.dateTimeObject;
29652                }
29653
29654                currentUri.dateTimeString = entry.dateTimeString;
29655                currentUri.dateTimeObject = entry.dateTimeObject;
29656              },
29657              targetduration: function targetduration() {
29658                if (!isFinite(entry.duration) || entry.duration < 0) {
29659                  this.trigger('warn', {
29660                    message: 'ignoring invalid target duration: ' + entry.duration
29661                  });
29662                  return;
29663                }
29664
29665                this.manifest.targetDuration = entry.duration;
29666              },
29667              totalduration: function totalduration() {
29668                if (!isFinite(entry.duration) || entry.duration < 0) {
29669                  this.trigger('warn', {
29670                    message: 'ignoring invalid total duration: ' + entry.duration
29671                  });
29672                  return;
29673                }
29674
29675                this.manifest.totalDuration = entry.duration;
29676              },
29677              start: function start() {
29678                if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
29679                  this.trigger('warn', {
29680                    message: 'ignoring start declaration without appropriate attribute list'
29681                  });
29682                  return;
29683                }
29684
29685                this.manifest.start = {
29686                  timeOffset: entry.attributes['TIME-OFFSET'],
29687                  precise: entry.attributes.PRECISE
29688                };
29689              },
29690              'cue-out': function cueOut() {
29691                currentUri.cueOut = entry.data;
29692              },
29693              'cue-out-cont': function cueOutCont() {
29694                currentUri.cueOutCont = entry.data;
29695              },
29696              'cue-in': function cueIn() {
29697                currentUri.cueIn = entry.data;
29698              }
29699            })[entry.tagType] || noop).call(self);
29700          },
29701          uri: function uri() {
29702            currentUri.uri = entry.uri;
29703            uris.push(currentUri); // if no explicit duration was declared, use the target duration
29704
29705            if (this.manifest.targetDuration && !('duration' in currentUri)) {
29706              this.trigger('warn', {
29707                message: 'defaulting segment duration to the target duration'
29708              });
29709              currentUri.duration = this.manifest.targetDuration;
29710            } // annotate with encryption information, if necessary
29711
29712
29713            if (_key) {
29714              currentUri.key = _key;
29715            }
29716
29717            currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary
29718
29719            if (currentMap) {
29720              currentUri.map = currentMap;
29721            } // prepare for the next URI
29722
29723
29724            currentUri = {};
29725          },
29726          comment: function comment() {// comments are not important for playback
29727          },
29728          custom: function custom() {
29729            // if this is segment-level data attach the output to the segment
29730            if (entry.segment) {
29731              currentUri.custom = currentUri.custom || {};
29732              currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object
29733            } else {
29734              this.manifest.custom = this.manifest.custom || {};
29735              this.manifest.custom[entry.customType] = entry.data;
29736            }
29737          }
29738        })[entry.type].call(self);
29739      });
29740
29741      return _this;
29742    }
29743    /**
29744     * Parse the input string and update the manifest object.
29745     *
29746     * @param {string} chunk a potentially incomplete portion of the manifest
29747     */
29748
29749
29750    var _proto = Parser.prototype;
29751
29752    _proto.push = function push(chunk) {
29753      this.lineStream.push(chunk);
29754    };
29755    /**
29756     * Flush any remaining input. This can be handy if the last line of an M3U8
29757     * manifest did not contain a trailing newline but the file has been
29758     * completely received.
29759     */
29760
29761
29762    _proto.end = function end() {
29763      // flush any buffered input
29764      this.lineStream.push('\n');
29765    };
29766    /**
29767     * Add an additional parser for non-standard tags
29768     *
29769     * @param {Object}   options              a map of options for the added parser
29770     * @param {RegExp}   options.expression   a regular expression to match the custom header
29771     * @param {string}   options.type         the type to register to the output
29772     * @param {Function} [options.dataParser] function to parse the line into an object
29773     * @param {boolean}  [options.segment]    should tag data be attached to the segment object
29774     */
29775
29776
29777    _proto.addParser = function addParser(options) {
29778      this.parseStream.addParser(options);
29779    };
29780    /**
29781     * Add a custom header mapper
29782     *
29783     * @param {Object}   options
29784     * @param {RegExp}   options.expression   a regular expression to match the custom header
29785     * @param {Function} options.map          function to translate tag into a different tag
29786     */
29787
29788
29789    _proto.addTagMapper = function addTagMapper(options) {
29790      this.parseStream.addTagMapper(options);
29791    };
29792
29793    return Parser;
29794  }(Stream);
29795
29796  /*! @name mpd-parser @version 0.7.0 @license Apache-2.0 */
29797
29798  var isObject$1 = function isObject(obj) {
29799    return !!obj && typeof obj === 'object';
29800  };
29801
29802  var merge = function merge() {
29803    for (var _len = arguments.length, objects = new Array(_len), _key = 0; _key < _len; _key++) {
29804      objects[_key] = arguments[_key];
29805    }
29806
29807    return objects.reduce(function (result, source) {
29808      Object.keys(source).forEach(function (key) {
29809        if (Array.isArray(result[key]) && Array.isArray(source[key])) {
29810          result[key] = result[key].concat(source[key]);
29811        } else if (isObject$1(result[key]) && isObject$1(source[key])) {
29812          result[key] = merge(result[key], source[key]);
29813        } else {
29814          result[key] = source[key];
29815        }
29816      });
29817      return result;
29818    }, {});
29819  };
29820
29821  var values = function values(o) {
29822    return Object.keys(o).map(function (k) {
29823      return o[k];
29824    });
29825  };
29826
29827  var range = function range(start, end) {
29828    var result = [];
29829
29830    for (var i = start; i < end; i++) {
29831      result.push(i);
29832    }
29833
29834    return result;
29835  };
29836
29837  var flatten = function flatten(lists) {
29838    return lists.reduce(function (x, y) {
29839      return x.concat(y);
29840    }, []);
29841  };
29842
29843  var from = function from(list) {
29844    if (!list.length) {
29845      return [];
29846    }
29847
29848    var result = [];
29849
29850    for (var i = 0; i < list.length; i++) {
29851      result.push(list[i]);
29852    }
29853
29854    return result;
29855  };
29856
29857  var findIndexes = function findIndexes(l, key) {
29858    return l.reduce(function (a, e, i) {
29859      if (e[key]) {
29860        a.push(i);
29861      }
29862
29863      return a;
29864    }, []);
29865  };
29866
29867  var mergeDiscontiguousPlaylists = function mergeDiscontiguousPlaylists(playlists) {
29868    var mergedPlaylists = values(playlists.reduce(function (acc, playlist) {
29869      // assuming playlist IDs are the same across periods
29870      // TODO: handle multiperiod where representation sets are not the same
29871      // across periods
29872      var name = playlist.attributes.id + (playlist.attributes.lang || ''); // Periods after first
29873
29874      if (acc[name]) {
29875        var _acc$name$segments; // first segment of subsequent periods signal a discontinuity
29876
29877
29878        playlist.segments[0].discontinuity = true;
29879
29880        (_acc$name$segments = acc[name].segments).push.apply(_acc$name$segments, playlist.segments); // bubble up contentProtection, this assumes all DRM content
29881        // has the same contentProtection
29882
29883
29884        if (playlist.attributes.contentProtection) {
29885          acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
29886        }
29887      } else {
29888        // first Period
29889        acc[name] = playlist;
29890      }
29891
29892      return acc;
29893    }, {}));
29894    return mergedPlaylists.map(function (playlist) {
29895      playlist.discontinuityStarts = findIndexes(playlist.segments, 'discontinuity');
29896      return playlist;
29897    });
29898  };
29899
29900  var formatAudioPlaylist = function formatAudioPlaylist(_ref) {
29901    var _attributes;
29902
29903    var attributes = _ref.attributes,
29904        segments = _ref.segments;
29905    var playlist = {
29906      attributes: (_attributes = {
29907        NAME: attributes.id,
29908        BANDWIDTH: attributes.bandwidth,
29909        CODECS: attributes.codecs
29910      }, _attributes['PROGRAM-ID'] = 1, _attributes),
29911      uri: '',
29912      endList: (attributes.type || 'static') === 'static',
29913      timeline: attributes.periodIndex,
29914      resolvedUri: '',
29915      targetDuration: attributes.duration,
29916      segments: segments,
29917      mediaSequence: segments.length ? segments[0].number : 1
29918    };
29919
29920    if (attributes.contentProtection) {
29921      playlist.contentProtection = attributes.contentProtection;
29922    }
29923
29924    return playlist;
29925  };
29926
29927  var formatVttPlaylist = function formatVttPlaylist(_ref2) {
29928    var _attributes2;
29929
29930    var attributes = _ref2.attributes,
29931        segments = _ref2.segments;
29932
29933    if (typeof segments === 'undefined') {
29934      // vtt tracks may use single file in BaseURL
29935      segments = [{
29936        uri: attributes.baseUrl,
29937        timeline: attributes.periodIndex,
29938        resolvedUri: attributes.baseUrl || '',
29939        duration: attributes.sourceDuration,
29940        number: 0
29941      }]; // targetDuration should be the same duration as the only segment
29942
29943      attributes.duration = attributes.sourceDuration;
29944    }
29945
29946    return {
29947      attributes: (_attributes2 = {
29948        NAME: attributes.id,
29949        BANDWIDTH: attributes.bandwidth
29950      }, _attributes2['PROGRAM-ID'] = 1, _attributes2),
29951      uri: '',
29952      endList: (attributes.type || 'static') === 'static',
29953      timeline: attributes.periodIndex,
29954      resolvedUri: attributes.baseUrl || '',
29955      targetDuration: attributes.duration,
29956      segments: segments,
29957      mediaSequence: segments.length ? segments[0].number : 1
29958    };
29959  };
29960
29961  var organizeAudioPlaylists = function organizeAudioPlaylists(playlists) {
29962    return playlists.reduce(function (a, playlist) {
29963      var role = playlist.attributes.role && playlist.attributes.role.value || 'main';
29964      var language = playlist.attributes.lang || '';
29965      var label = 'main';
29966
29967      if (language) {
29968        label = playlist.attributes.lang + " (" + role + ")";
29969      } // skip if we already have the highest quality audio for a language
29970
29971
29972      if (a[label] && a[label].playlists[0].attributes.BANDWIDTH > playlist.attributes.bandwidth) {
29973        return a;
29974      }
29975
29976      a[label] = {
29977        language: language,
29978        autoselect: true,
29979        default: role === 'main',
29980        playlists: [formatAudioPlaylist(playlist)],
29981        uri: ''
29982      };
29983      return a;
29984    }, {});
29985  };
29986
29987  var organizeVttPlaylists = function organizeVttPlaylists(playlists) {
29988    return playlists.reduce(function (a, playlist) {
29989      var label = playlist.attributes.lang || 'text'; // skip if we already have subtitles
29990
29991      if (a[label]) {
29992        return a;
29993      }
29994
29995      a[label] = {
29996        language: label,
29997        default: false,
29998        autoselect: false,
29999        playlists: [formatVttPlaylist(playlist)],
30000        uri: ''
30001      };
30002      return a;
30003    }, {});
30004  };
30005
30006  var formatVideoPlaylist = function formatVideoPlaylist(_ref3) {
30007    var _attributes3;
30008
30009    var attributes = _ref3.attributes,
30010        segments = _ref3.segments;
30011    var playlist = {
30012      attributes: (_attributes3 = {
30013        NAME: attributes.id,
30014        AUDIO: 'audio',
30015        SUBTITLES: 'subs',
30016        RESOLUTION: {
30017          width: attributes.width,
30018          height: attributes.height
30019        },
30020        CODECS: attributes.codecs,
30021        BANDWIDTH: attributes.bandwidth
30022      }, _attributes3['PROGRAM-ID'] = 1, _attributes3),
30023      uri: '',
30024      endList: (attributes.type || 'static') === 'static',
30025      timeline: attributes.periodIndex,
30026      resolvedUri: '',
30027      targetDuration: attributes.duration,
30028      segments: segments,
30029      mediaSequence: segments.length ? segments[0].number : 1
30030    };
30031
30032    if (attributes.contentProtection) {
30033      playlist.contentProtection = attributes.contentProtection;
30034    }
30035
30036    return playlist;
30037  };
30038
30039  var toM3u8 = function toM3u8(dashPlaylists) {
30040    var _mediaGroups;
30041
30042    if (!dashPlaylists.length) {
30043      return {};
30044    } // grab all master attributes
30045
30046
30047    var _dashPlaylists$0$attr = dashPlaylists[0].attributes,
30048        duration = _dashPlaylists$0$attr.sourceDuration,
30049        _dashPlaylists$0$attr2 = _dashPlaylists$0$attr.minimumUpdatePeriod,
30050        minimumUpdatePeriod = _dashPlaylists$0$attr2 === void 0 ? 0 : _dashPlaylists$0$attr2;
30051
30052    var videoOnly = function videoOnly(_ref4) {
30053      var attributes = _ref4.attributes;
30054      return attributes.mimeType === 'video/mp4' || attributes.contentType === 'video';
30055    };
30056
30057    var audioOnly = function audioOnly(_ref5) {
30058      var attributes = _ref5.attributes;
30059      return attributes.mimeType === 'audio/mp4' || attributes.contentType === 'audio';
30060    };
30061
30062    var vttOnly = function vttOnly(_ref6) {
30063      var attributes = _ref6.attributes;
30064      return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
30065    };
30066
30067    var videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist);
30068    var audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly));
30069    var vttPlaylists = dashPlaylists.filter(vttOnly);
30070    var master = {
30071      allowCache: true,
30072      discontinuityStarts: [],
30073      segments: [],
30074      endList: true,
30075      mediaGroups: (_mediaGroups = {
30076        AUDIO: {},
30077        VIDEO: {}
30078      }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups),
30079      uri: '',
30080      duration: duration,
30081      playlists: videoPlaylists,
30082      minimumUpdatePeriod: minimumUpdatePeriod * 1000
30083    };
30084
30085    if (audioPlaylists.length) {
30086      master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists);
30087    }
30088
30089    if (vttPlaylists.length) {
30090      master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists);
30091    }
30092
30093    return master;
30094  };
30095
30096  var commonjsGlobal$1 = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
30097
30098  function createCommonjsModule$1(fn, module) {
30099    return module = {
30100      exports: {}
30101    }, fn(module, module.exports), module.exports;
30102  }
30103
30104  var urlToolkit$1 = createCommonjsModule$1(function (module, exports
vendor: 4,642 bytes, lines 30104-30211
30104) {
30105    // see https://tools.ietf.org/html/rfc1808
30106
30107    /* jshint ignore:start */
30108    (function (root) {
30109      /* jshint ignore:end */
30110      var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/;
30111      var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/;
30112      var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
30113      var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g;
30114      var URLToolkit = {
30115        // jshint ignore:line
30116        // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
30117        // E.g
30118        // With opts.alwaysNormalize = false (default, spec compliant)
30119        // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
30120        // With opts.alwaysNormalize = true (not spec compliant)
30121        // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
30122        buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) {
30123          opts = opts || {}; // remove any remaining space and CRLF
30124
30125          baseURL = baseURL.trim();
30126          relativeURL = relativeURL.trim();
30127
30128          if (!relativeURL) {
30129            // 2a) If the embedded URL is entirely empty, it inherits the
30130            // entire base URL (i.e., is set equal to the base URL)
30131            // and we are done.
30132            if (!opts.alwaysNormalize) {
30133              return baseURL;
30134            }
30135
30136            var basePartsForNormalise = URLToolkit.parseURL(baseURL);
30137
30138            if (!basePartsForNormalise) {
30139              throw new Error('Error trying to parse base URL.');
30140            }
30141
30142            basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
30143            return URLToolkit.buildURLFromParts(basePartsForNormalise);
30144          }
30145
30146          var relativeParts = URLToolkit.parseURL(relativeURL);
30147
30148          if (!relativeParts) {
30149            throw new Error('Error trying to parse relative URL.');
30150          }
30151
30152          if (relativeParts.scheme) {
30153            // 2b) If the embedded URL starts with a scheme name, it is
30154            // interpreted as an absolute URL and we are done.
30155            if (!opts.alwaysNormalize) {
30156              return relativeURL;
30157            }
30158
30159            relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
30160            return URLToolkit.buildURLFromParts(relativeParts);
30161          }
30162
30163          var baseParts = URLToolkit.parseURL(baseURL);
30164
30165          if (!baseParts) {
30166            throw new Error('Error trying to parse base URL.');
30167          }
30168
30169          if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
30170            // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
30171            // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
30172            var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
30173            baseParts.netLoc = pathParts[1];
30174            baseParts.path = pathParts[2];
30175          }
30176
30177          if (baseParts.netLoc && !baseParts.path) {
30178            baseParts.path = '/';
30179          }
30180
30181          var builtParts = {
30182            // 2c) Otherwise, the embedded URL inherits the scheme of
30183            // the base URL.
30184            scheme: baseParts.scheme,
30185            netLoc: relativeParts.netLoc,
30186            path: null,
30187            params: relativeParts.params,
30188            query: relativeParts.query,
30189            fragment: relativeParts.fragment
30190          };
30191
30192          if (!relativeParts.netLoc) {
30193            // 3) If the embedded URL's <net_loc> is non-empty, we skip to
30194            // Step 7.  Otherwise, the embedded URL inherits the <net_loc>
30195            // (if any) of the base URL.
30196            builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the
30197            // path is not relative and we skip to Step 7.
30198
30199            if (relativeParts.path[0] !== '/') {
30200              if (!relativeParts.path) {
30201                // 5) If the embedded URL path is empty (and not preceded by a
30202                // slash), then the embedded URL inherits the base URL path
30203                builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to
30204                // step 7; otherwise, it inherits the <params> of the base
30205                // URL (if any) and
30206
30207                if (!relativeParts.params) {
30208                  builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to
30209                  // step 7; otherwise, it inherits the <query> of the base
30210                  // URL (if any) and we skip to step 7.
30211
vendor: 21,464 bytes, lines 30212-30787
30212                  if (!relativeParts.query) {
30213                    builtParts.query = baseParts.query;
30214                  }
30215                }
30216              } else {
30217                // 6) The last segment of the base URL's path (anything
30218                // following the rightmost slash "/", or the entire path if no
30219                // slash is present) is removed and the embedded URL's path is
30220                // appended in its place.
30221                var baseURLPath = baseParts.path;
30222                var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
30223                builtParts.path = URLToolkit.normalizePath(newPath);
30224              }
30225            }
30226          }
30227
30228          if (builtParts.path === null) {
30229            builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
30230          }
30231
30232          return URLToolkit.buildURLFromParts(builtParts);
30233        },
30234        parseURL: function parseURL(url) {
30235          var parts = URL_REGEX.exec(url);
30236
30237          if (!parts) {
30238            return null;
30239          }
30240
30241          return {
30242            scheme: parts[1] || '',
30243            netLoc: parts[2] || '',
30244            path: parts[3] || '',
30245            params: parts[4] || '',
30246            query: parts[5] || '',
30247            fragment: parts[6] || ''
30248          };
30249        },
30250        normalizePath: function normalizePath(path) {
30251          // The following operations are
30252          // then applied, in order, to the new path:
30253          // 6a) All occurrences of "./", where "." is a complete path
30254          // segment, are removed.
30255          // 6b) If the path ends with "." as a complete path segment,
30256          // that "." is removed.
30257          path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a
30258          // complete path segment not equal to "..", are removed.
30259          // Removal of these path segments is performed iteratively,
30260          // removing the leftmost matching pattern on each iteration,
30261          // until no matching pattern remains.
30262          // 6d) If the path ends with "<segment>/..", where <segment> is a
30263          // complete path segment not equal to "..", that
30264          // "<segment>/.." is removed.
30265
30266          while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line
30267
30268
30269          return path.split('').reverse().join('');
30270        },
30271        buildURLFromParts: function buildURLFromParts(parts) {
30272          return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
30273        }
30274      };
30275      /* jshint ignore:start */
30276
30277      module.exports = URLToolkit;
30278    })(commonjsGlobal$1);
30279    /* jshint ignore:end */
30280
30281  });
30282
30283  var resolveUrl = function resolveUrl(baseUrl, relativeUrl) {
30284    // return early if we don't need to resolve
30285    if (/^[a-z]+:/i.test(relativeUrl)) {
30286      return relativeUrl;
30287    } // if the base URL is relative then combine with the current location
30288
30289
30290    if (!/\/\//i.test(baseUrl)) {
30291      baseUrl = urlToolkit$1.buildAbsoluteURL(window$1.location.href, baseUrl);
30292    }
30293
30294    return urlToolkit$1.buildAbsoluteURL(baseUrl, relativeUrl);
30295  };
30296  /**
30297   * @typedef {Object} SingleUri
30298   * @property {string} uri - relative location of segment
30299   * @property {string} resolvedUri - resolved location of segment
30300   * @property {Object} byterange - Object containing information on how to make byte range
30301   *   requests following byte-range-spec per RFC2616.
30302   * @property {String} byterange.length - length of range request
30303   * @property {String} byterange.offset - byte offset of range request
30304   *
30305   * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
30306   */
30307
30308  /**
30309   * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object
30310   * that conforms to how m3u8-parser is structured
30311   *
30312   * @see https://github.com/videojs/m3u8-parser
30313   *
30314   * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes
30315   * @param {string} source - source url for segment
30316   * @param {string} range - optional range used for range calls, follows
30317   * @return {SingleUri} full segment information transformed into a format similar
30318   *   to m3u8-parser
30319   */
30320
30321
30322  var urlTypeToSegment = function urlTypeToSegment(_ref) {
30323    var _ref$baseUrl = _ref.baseUrl,
30324        baseUrl = _ref$baseUrl === void 0 ? '' : _ref$baseUrl,
30325        _ref$source = _ref.source,
30326        source = _ref$source === void 0 ? '' : _ref$source,
30327        _ref$range = _ref.range,
30328        range = _ref$range === void 0 ? '' : _ref$range;
30329    var init = {
30330      uri: source,
30331      resolvedUri: resolveUrl(baseUrl || '', source)
30332    };
30333
30334    if (range) {
30335      var ranges = range.split('-');
30336      var startRange = parseInt(ranges[0], 10);
30337      var endRange = parseInt(ranges[1], 10);
30338      init.byterange = {
30339        length: endRange - startRange,
30340        offset: startRange
30341      };
30342    }
30343
30344    return init;
30345  };
30346  /**
30347   * Calculates the R (repetition) value for a live stream (for the final segment
30348   * in a manifest where the r value is negative 1)
30349   *
30350   * @param {Object} attributes
30351   *        Object containing all inherited attributes from parent elements with attribute
30352   *        names as keys
30353   * @param {number} time
30354   *        current time (typically the total time up until the final segment)
30355   * @param {number} duration
30356   *        duration property for the given <S />
30357   *
30358   * @return {number}
30359   *        R value to reach the end of the given period
30360   */
30361
30362
30363  var getLiveRValue = function getLiveRValue(attributes, time, duration) {
30364    var NOW = attributes.NOW,
30365        clientOffset = attributes.clientOffset,
30366        availabilityStartTime = attributes.availabilityStartTime,
30367        _attributes$timescale = attributes.timescale,
30368        timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
30369        _attributes$start = attributes.start,
30370        start = _attributes$start === void 0 ? 0 : _attributes$start,
30371        _attributes$minimumUp = attributes.minimumUpdatePeriod,
30372        minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp;
30373    var now = (NOW + clientOffset) / 1000;
30374    var periodStartWC = availabilityStartTime + start;
30375    var periodEndWC = now + minimumUpdatePeriod;
30376    var periodDuration = periodEndWC - periodStartWC;
30377    return Math.ceil((periodDuration * timescale - time) / duration);
30378  };
30379  /**
30380   * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
30381   * timing and duration
30382   *
30383   * @param {Object} attributes
30384   *        Object containing all inherited attributes from parent elements with attribute
30385   *        names as keys
30386   * @param {Object[]} segmentTimeline
30387   *        List of objects representing the attributes of each S element contained within
30388   *
30389   * @return {{number: number, duration: number, time: number, timeline: number}[]}
30390   *         List of Objects with segment timing and duration info
30391   */
30392
30393
30394  var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) {
30395    var _attributes$type = attributes.type,
30396        type = _attributes$type === void 0 ? 'static' : _attributes$type,
30397        _attributes$minimumUp2 = attributes.minimumUpdatePeriod,
30398        minimumUpdatePeriod = _attributes$minimumUp2 === void 0 ? 0 : _attributes$minimumUp2,
30399        _attributes$media = attributes.media,
30400        media = _attributes$media === void 0 ? '' : _attributes$media,
30401        sourceDuration = attributes.sourceDuration,
30402        _attributes$timescale2 = attributes.timescale,
30403        timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2,
30404        _attributes$startNumb = attributes.startNumber,
30405        startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb,
30406        timeline = attributes.periodIndex;
30407    var segments = [];
30408    var time = -1;
30409
30410    for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) {
30411      var S = segmentTimeline[sIndex];
30412      var duration = S.d;
30413      var repeat = S.r || 0;
30414      var segmentTime = S.t || 0;
30415
30416      if (time < 0) {
30417        // first segment
30418        time = segmentTime;
30419      }
30420
30421      if (segmentTime && segmentTime > time) {
30422        // discontinuity
30423        // TODO: How to handle this type of discontinuity
30424        // timeline++ here would treat it like HLS discontuity and content would
30425        // get appended without gap
30426        // E.G.
30427        //  <S t="0" d="1" />
30428        //  <S d="1" />
30429        //  <S d="1" />
30430        //  <S t="5" d="1" />
30431        // would have $Time$ values of [0, 1, 2, 5]
30432        // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY)
30433        // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP)
30434        // does the value of sourceDuration consider this when calculating arbitrary
30435        // negative @r repeat value?
30436        // E.G. Same elements as above with this added at the end
30437        //  <S d="1" r="-1" />
30438        //  with a sourceDuration of 10
30439        // Would the 2 gaps be included in the time duration calculations resulting in
30440        // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments
30441        // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ?
30442        time = segmentTime;
30443      }
30444
30445      var count = void 0;
30446
30447      if (repeat < 0) {
30448        var nextS = sIndex + 1;
30449
30450        if (nextS === segmentTimeline.length) {
30451          // last segment
30452          if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) {
30453            count = getLiveRValue(attributes, time, duration);
30454          } else {
30455            // TODO: This may be incorrect depending on conclusion of TODO above
30456            count = (sourceDuration * timescale - time) / duration;
30457          }
30458        } else {
30459          count = (segmentTimeline[nextS].t - time) / duration;
30460        }
30461      } else {
30462        count = repeat + 1;
30463      }
30464
30465      var end = startNumber + segments.length + count;
30466      var number = startNumber + segments.length;
30467
30468      while (number < end) {
30469        segments.push({
30470          number: number,
30471          duration: duration / timescale,
30472          time: time,
30473          timeline: timeline
30474        });
30475        time += duration;
30476        number++;
30477      }
30478    }
30479
30480    return segments;
30481  };
30482  /**
30483   * Functions for calculating the range of available segments in static and dynamic
30484   * manifests.
30485   */
30486
30487
30488  var segmentRange = {
30489    /**
30490     * Returns the entire range of available segments for a static MPD
30491     *
30492     * @param {Object} attributes
30493     *        Inheritied MPD attributes
30494     * @return {{ start: number, end: number }}
30495     *         The start and end numbers for available segments
30496     */
30497    static: function _static(attributes) {
30498      var duration = attributes.duration,
30499          _attributes$timescale = attributes.timescale,
30500          timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
30501          sourceDuration = attributes.sourceDuration;
30502      return {
30503        start: 0,
30504        end: Math.ceil(sourceDuration / (duration / timescale))
30505      };
30506    },
30507
30508    /**
30509     * Returns the current live window range of available segments for a dynamic MPD
30510     *
30511     * @param {Object} attributes
30512     *        Inheritied MPD attributes
30513     * @return {{ start: number, end: number }}
30514     *         The start and end numbers for available segments
30515     */
30516    dynamic: function dynamic(attributes) {
30517      var NOW = attributes.NOW,
30518          clientOffset = attributes.clientOffset,
30519          availabilityStartTime = attributes.availabilityStartTime,
30520          _attributes$timescale2 = attributes.timescale,
30521          timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2,
30522          duration = attributes.duration,
30523          _attributes$start = attributes.start,
30524          start = _attributes$start === void 0 ? 0 : _attributes$start,
30525          _attributes$minimumUp = attributes.minimumUpdatePeriod,
30526          minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp,
30527          _attributes$timeShift = attributes.timeShiftBufferDepth,
30528          timeShiftBufferDepth = _attributes$timeShift === void 0 ? Infinity : _attributes$timeShift;
30529      var now = (NOW + clientOffset) / 1000;
30530      var periodStartWC = availabilityStartTime + start;
30531      var periodEndWC = now + minimumUpdatePeriod;
30532      var periodDuration = periodEndWC - periodStartWC;
30533      var segmentCount = Math.ceil(periodDuration * timescale / duration);
30534      var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration);
30535      var availableEnd = Math.floor((now - periodStartWC) * timescale / duration);
30536      return {
30537        start: Math.max(0, availableStart),
30538        end: Math.min(segmentCount, availableEnd)
30539      };
30540    }
30541  };
30542  /**
30543   * Maps a range of numbers to objects with information needed to build the corresponding
30544   * segment list
30545   *
30546   * @name toSegmentsCallback
30547   * @function
30548   * @param {number} number
30549   *        Number of the segment
30550   * @param {number} index
30551   *        Index of the number in the range list
30552   * @return {{ number: Number, duration: Number, timeline: Number, time: Number }}
30553   *         Object with segment timing and duration info
30554   */
30555
30556  /**
30557   * Returns a callback for Array.prototype.map for mapping a range of numbers to
30558   * information needed to build the segment list.
30559   *
30560   * @param {Object} attributes
30561   *        Inherited MPD attributes
30562   * @return {toSegmentsCallback}
30563   *         Callback map function
30564   */
30565
30566  var toSegments = function toSegments(attributes) {
30567    return function (number, index) {
30568      var duration = attributes.duration,
30569          _attributes$timescale3 = attributes.timescale,
30570          timescale = _attributes$timescale3 === void 0 ? 1 : _attributes$timescale3,
30571          periodIndex = attributes.periodIndex,
30572          _attributes$startNumb = attributes.startNumber,
30573          startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb;
30574      return {
30575        number: startNumber + number,
30576        duration: duration / timescale,
30577        timeline: periodIndex,
30578        time: index * duration
30579      };
30580    };
30581  };
30582  /**
30583   * Returns a list of objects containing segment timing and duration info used for
30584   * building the list of segments. This uses the @duration attribute specified
30585   * in the MPD manifest to derive the range of segments.
30586   *
30587   * @param {Object} attributes
30588   *        Inherited MPD attributes
30589   * @return {{number: number, duration: number, time: number, timeline: number}[]}
30590   *         List of Objects with segment timing and duration info
30591   */
30592
30593
30594  var parseByDuration = function parseByDuration(attributes) {
30595    var _attributes$type = attributes.type,
30596        type = _attributes$type === void 0 ? 'static' : _attributes$type,
30597        duration = attributes.duration,
30598        _attributes$timescale4 = attributes.timescale,
30599        timescale = _attributes$timescale4 === void 0 ? 1 : _attributes$timescale4,
30600        sourceDuration = attributes.sourceDuration;
30601
30602    var _segmentRange$type = segmentRange[type](attributes),
30603        start = _segmentRange$type.start,
30604        end = _segmentRange$type.end;
30605
30606    var segments = range(start, end).map(toSegments(attributes));
30607
30608    if (type === 'static') {
30609      var index = segments.length - 1; // final segment may be less than full segment duration
30610
30611      segments[index].duration = sourceDuration - duration / timescale * index;
30612    }
30613
30614    return segments;
30615  };
30616
30617  var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
30618  /**
30619   * Replaces template identifiers with corresponding values. To be used as the callback
30620   * for String.prototype.replace
30621   *
30622   * @name replaceCallback
30623   * @function
30624   * @param {string} match
30625   *        Entire match of identifier
30626   * @param {string} identifier
30627   *        Name of matched identifier
30628   * @param {string} format
30629   *        Format tag string. Its presence indicates that padding is expected
30630   * @param {string} width
30631   *        Desired length of the replaced value. Values less than this width shall be left
30632   *        zero padded
30633   * @return {string}
30634   *         Replacement for the matched identifier
30635   */
30636
30637  /**
30638   * Returns a function to be used as a callback for String.prototype.replace to replace
30639   * template identifiers
30640   *
30641   * @param {Obect} values
30642   *        Object containing values that shall be used to replace known identifiers
30643   * @param {number} values.RepresentationID
30644   *        Value of the Representation@id attribute
30645   * @param {number} values.Number
30646   *        Number of the corresponding segment
30647   * @param {number} values.Bandwidth
30648   *        Value of the Representation@bandwidth attribute.
30649   * @param {number} values.Time
30650   *        Timestamp value of the corresponding segment
30651   * @return {replaceCallback}
30652   *         Callback to be used with String.prototype.replace to replace identifiers
30653   */
30654
30655  var identifierReplacement = function identifierReplacement(values) {
30656    return function (match, identifier, format, width) {
30657      if (match === '$$') {
30658        // escape sequence
30659        return '$';
30660      }
30661
30662      if (typeof values[identifier] === 'undefined') {
30663        return match;
30664      }
30665
30666      var value = '' + values[identifier];
30667
30668      if (identifier === 'RepresentationID') {
30669        // Format tag shall not be present with RepresentationID
30670        return value;
30671      }
30672
30673      if (!format) {
30674        width = 1;
30675      } else {
30676        width = parseInt(width, 10);
30677      }
30678
30679      if (value.length >= width) {
30680        return value;
30681      }
30682
30683      return "" + new Array(width - value.length + 1).join('0') + value;
30684    };
30685  };
30686  /**
30687   * Constructs a segment url from a template string
30688   *
30689   * @param {string} url
30690   *        Template string to construct url from
30691   * @param {Obect} values
30692   *        Object containing values that shall be used to replace known identifiers
30693   * @param {number} values.RepresentationID
30694   *        Value of the Representation@id attribute
30695   * @param {number} values.Number
30696   *        Number of the corresponding segment
30697   * @param {number} values.Bandwidth
30698   *        Value of the Representation@bandwidth attribute.
30699   * @param {number} values.Time
30700   *        Timestamp value of the corresponding segment
30701   * @return {string}
30702   *         Segment url with identifiers replaced
30703   */
30704
30705
30706  var constructTemplateUrl = function constructTemplateUrl(url, values) {
30707    return url.replace(identifierPattern, identifierReplacement(values));
30708  };
30709  /**
30710   * Generates a list of objects containing timing and duration information about each
30711   * segment needed to generate segment uris and the complete segment object
30712   *
30713   * @param {Object} attributes
30714   *        Object containing all inherited attributes from parent elements with attribute
30715   *        names as keys
30716   * @param {Object[]|undefined} segmentTimeline
30717   *        List of objects representing the attributes of each S element contained within
30718   *        the SegmentTimeline element
30719   * @return {{number: number, duration: number, time: number, timeline: number}[]}
30720   *         List of Objects with segment timing and duration info
30721   */
30722
30723
30724  var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) {
30725    if (!attributes.duration && !segmentTimeline) {
30726      // if neither @duration or SegmentTimeline are present, then there shall be exactly
30727      // one media segment
30728      return [{
30729        number: attributes.startNumber || 1,
30730        duration: attributes.sourceDuration,
30731        time: 0,
30732        timeline: attributes.periodIndex
30733      }];
30734    }
30735
30736    if (attributes.duration) {
30737      return parseByDuration(attributes);
30738    }
30739
30740    return parseByTimeline(attributes, segmentTimeline);
30741  };
30742  /**
30743   * Generates a list of segments using information provided by the SegmentTemplate element
30744   *
30745   * @param {Object} attributes
30746   *        Object containing all inherited attributes from parent elements with attribute
30747   *        names as keys
30748   * @param {Object[]|undefined} segmentTimeline
30749   *        List of objects representing the attributes of each S element contained within
30750   *        the SegmentTimeline element
30751   * @return {Object[]}
30752   *         List of segment objects
30753   */
30754
30755
30756  var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) {
30757    var templateValues = {
30758      RepresentationID: attributes.id,
30759      Bandwidth: attributes.bandwidth || 0
30760    };
30761    var _attributes$initializ = attributes.initialization,
30762        initialization = _attributes$initializ === void 0 ? {
30763      sourceURL: '',
30764      range: ''
30765    } : _attributes$initializ;
30766    var mapSegment = urlTypeToSegment({
30767      baseUrl: attributes.baseUrl,
30768      source: constructTemplateUrl(initialization.sourceURL, templateValues),
30769      range: initialization.range
30770    });
30771    var segments = parseTemplateInfo(attributes, segmentTimeline);
30772    return segments.map(function (segment) {
30773      templateValues.Number = segment.number;
30774      templateValues.Time = segment.time;
30775      var uri = constructTemplateUrl(attributes.media || '', templateValues);
30776      return {
30777        uri: uri,
30778        timeline: segment.timeline,
30779        duration: segment.duration,
30780        resolvedUri: resolveUrl(attributes.baseUrl || '', uri),
30781        map: mapSegment,
30782        number: segment.number
30783      };
30784    });
30785  };
30786
30787  var errors = {
30788    INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
30789    DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
30790    DASH_INVALID_XML: 'DASH_INVALID_XML',
30791    NO_BASE_URL: 'NO_BASE_URL',
30792    MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
30793    SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED',
30794    UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME'
30795  };
30796  /**
30797   * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14)
30798   * to an object that matches the output of a segment in videojs/mpd-parser
30799   *
30800   * @param {Object} attributes
30801   *   Object containing all inherited attributes from parent elements with attribute
30802   *   names as keys
30803   * @param {Object} segmentUrl
30804   *   <SegmentURL> node to translate into a segment object
30805   * @return {Object} translated segment object
30806   */
30807
30808  var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) {
30809    var baseUrl = attributes.baseUrl,
30810        _attributes$initializ = attributes.initialization,
30811        initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ;
30812    var initSegment = urlTypeToSegment({
30813      baseUrl: baseUrl,
30814      source: initialization.sourceURL,
30815      range: initialization.range
30816    });
30817    var segment = urlTypeToSegment({
30818      baseUrl: baseUrl,
30819      source: segmentUrl.media,
30820      range: segmentUrl.mediaRange
30821    });
30822    segment.map = initSegment;
30823    return segment;
30824  };
30825  /**
30826   * Generates a list of segments using information provided by the SegmentList element
30827   * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
30828   * node should be translated into segment.
30829   *
30830   * @param {Object} attributes
30831   *   Object containing all inherited attributes from parent elements with attribute
30832   *   names as keys
30833   * @param {Object[]|undefined} segmentTimeline
30834   *        List of objects representing the attributes of each S element contained within
30835   *        the SegmentTimeline element
30836   * @return {Object.<Array>} list of segments
30837   */
30838
30839
vendor: 18,546 bytes, lines 30840-31385
30840  var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) {
30841    var duration = attributes.duration,
30842        _attributes$segmentUr = attributes.segmentUrls,
30843        segmentUrls = _attributes$segmentUr === void 0 ? [] : _attributes$segmentUr; // Per spec (5.3.9.2.1) no way to determine segment duration OR
30844    // if both SegmentTimeline and @duration are defined, it is outside of spec.
30845
30846    if (!duration && !segmentTimeline || duration && segmentTimeline) {
30847      throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
30848    }
30849
30850    var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) {
30851      return SegmentURLToSegmentObject(attributes, segmentUrlObject);
30852    });
30853    var segmentTimeInfo;
30854
30855    if (duration) {
30856      segmentTimeInfo = parseByDuration(attributes);
30857    }
30858
30859    if (segmentTimeline) {
30860      segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
30861    }
30862
30863    var segments = segmentTimeInfo.map(function (segmentTime, index) {
30864      if (segmentUrlMap[index]) {
30865        var segment = segmentUrlMap[index];
30866        segment.timeline = segmentTime.timeline;
30867        segment.duration = segmentTime.duration;
30868        segment.number = segmentTime.number;
30869        return segment;
30870      } // Since we're mapping we should get rid of any blank segments (in case
30871      // the given SegmentTimeline is handling for more elements than we have
30872      // SegmentURLs for).
30873
30874    }).filter(function (segment) {
30875      return segment;
30876    });
30877    return segments;
30878  };
30879  /**
30880   * Translates SegmentBase into a set of segments.
30881   * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
30882   * node should be translated into segment.
30883   *
30884   * @param {Object} attributes
30885   *   Object containing all inherited attributes from parent elements with attribute
30886   *   names as keys
30887   * @return {Object.<Array>} list of segments
30888   */
30889
30890
30891  var segmentsFromBase = function segmentsFromBase(attributes) {
30892    var baseUrl = attributes.baseUrl,
30893        _attributes$initializ = attributes.initialization,
30894        initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ,
30895        sourceDuration = attributes.sourceDuration,
30896        _attributes$timescale = attributes.timescale,
30897        timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
30898        _attributes$indexRang = attributes.indexRange,
30899        indexRange = _attributes$indexRang === void 0 ? '' : _attributes$indexRang,
30900        duration = attributes.duration; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
30901
30902    if (!baseUrl) {
30903      throw new Error(errors.NO_BASE_URL);
30904    }
30905
30906    var initSegment = urlTypeToSegment({
30907      baseUrl: baseUrl,
30908      source: initialization.sourceURL,
30909      range: initialization.range
30910    });
30911    var segment = urlTypeToSegment({
30912      baseUrl: baseUrl,
30913      source: baseUrl,
30914      range: indexRange
30915    });
30916    segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source
30917    // (since SegmentBase is only for one total segment)
30918
30919    if (duration) {
30920      var segmentTimeInfo = parseByDuration(attributes);
30921
30922      if (segmentTimeInfo.length) {
30923        segment.duration = segmentTimeInfo[0].duration;
30924        segment.timeline = segmentTimeInfo[0].timeline;
30925      }
30926    } else if (sourceDuration) {
30927      segment.duration = sourceDuration / timescale;
30928      segment.timeline = 0;
30929    } // This is used for mediaSequence
30930
30931
30932    segment.number = 0;
30933    return [segment];
30934  };
30935
30936  var generateSegments = function generateSegments(_ref) {
30937    var attributes = _ref.attributes,
30938        segmentInfo = _ref.segmentInfo;
30939    var segmentAttributes;
30940    var segmentsFn;
30941
30942    if (segmentInfo.template) {
30943      segmentsFn = segmentsFromTemplate;
30944      segmentAttributes = merge(attributes, segmentInfo.template);
30945    } else if (segmentInfo.base) {
30946      segmentsFn = segmentsFromBase;
30947      segmentAttributes = merge(attributes, segmentInfo.base);
30948    } else if (segmentInfo.list) {
30949      segmentsFn = segmentsFromList;
30950      segmentAttributes = merge(attributes, segmentInfo.list);
30951    }
30952
30953    if (!segmentsFn) {
30954      return {
30955        attributes: attributes
30956      };
30957    }
30958
30959    var segments = segmentsFn(segmentAttributes, segmentInfo.timeline); // The @duration attribute will be used to determin the playlist's targetDuration which
30960    // must be in seconds. Since we've generated the segment list, we no longer need
30961    // @duration to be in @timescale units, so we can convert it here.
30962
30963    if (segmentAttributes.duration) {
30964      var _segmentAttributes = segmentAttributes,
30965          duration = _segmentAttributes.duration,
30966          _segmentAttributes$ti = _segmentAttributes.timescale,
30967          timescale = _segmentAttributes$ti === void 0 ? 1 : _segmentAttributes$ti;
30968      segmentAttributes.duration = duration / timescale;
30969    } else if (segments.length) {
30970      // if there is no @duration attribute, use the largest segment duration as
30971      // as target duration
30972      segmentAttributes.duration = segments.reduce(function (max, segment) {
30973        return Math.max(max, Math.ceil(segment.duration));
30974      }, 0);
30975    } else {
30976      segmentAttributes.duration = 0;
30977    }
30978
30979    return {
30980      attributes: segmentAttributes,
30981      segments: segments
30982    };
30983  };
30984
30985  var toPlaylists = function toPlaylists(representations) {
30986    return representations.map(generateSegments);
30987  };
30988
30989  var findChildren = function findChildren(element, name) {
30990    return from(element.childNodes).filter(function (_ref) {
30991      var tagName = _ref.tagName;
30992      return tagName === name;
30993    });
30994  };
30995
30996  var getContent = function getContent(element) {
30997    return element.textContent.trim();
30998  };
30999
31000  var parseDuration = function parseDuration(str) {
31001    var SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
31002    var SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
31003    var SECONDS_IN_DAY = 24 * 60 * 60;
31004    var SECONDS_IN_HOUR = 60 * 60;
31005    var SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S
31006
31007    var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
31008    var match = durationRegex.exec(str);
31009
31010    if (!match) {
31011      return 0;
31012    }
31013
31014    var _match$slice = match.slice(1),
31015        year = _match$slice[0],
31016        month = _match$slice[1],
31017        day = _match$slice[2],
31018        hour = _match$slice[3],
31019        minute = _match$slice[4],
31020        second = _match$slice[5];
31021
31022    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);
31023  };
31024
31025  var parseDate = function parseDate(str) {
31026    // Date format without timezone according to ISO 8601
31027    // YYY-MM-DDThh:mm:ss.ssssss
31028    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
31029    // expressed by ending with 'Z'
31030
31031    if (dateRegex.test(str)) {
31032      str += 'Z';
31033    }
31034
31035    return Date.parse(str);
31036  };
31037
31038  var parsers = {
31039    /**
31040     * Specifies the duration of the entire Media Presentation. Format is a duration string
31041     * as specified in ISO 8601
31042     *
31043     * @param {string} value
31044     *        value of attribute as a string
31045     * @return {number}
31046     *         The duration in seconds
31047     */
31048    mediaPresentationDuration: function mediaPresentationDuration(value) {
31049      return parseDuration(value);
31050    },
31051
31052    /**
31053     * Specifies the Segment availability start time for all Segments referred to in this
31054     * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability
31055     * time. Format is a date string as specified in ISO 8601
31056     *
31057     * @param {string} value
31058     *        value of attribute as a string
31059     * @return {number}
31060     *         The date as seconds from unix epoch
31061     */
31062    availabilityStartTime: function availabilityStartTime(value) {
31063      return parseDate(value) / 1000;
31064    },
31065
31066    /**
31067     * Specifies the smallest period between potential changes to the MPD. Format is a
31068     * duration string as specified in ISO 8601
31069     *
31070     * @param {string} value
31071     *        value of attribute as a string
31072     * @return {number}
31073     *         The duration in seconds
31074     */
31075    minimumUpdatePeriod: function minimumUpdatePeriod(value) {
31076      return parseDuration(value);
31077    },
31078
31079    /**
31080     * Specifies the duration of the smallest time shifting buffer for any Representation
31081     * in the MPD. Format is a duration string as specified in ISO 8601
31082     *
31083     * @param {string} value
31084     *        value of attribute as a string
31085     * @return {number}
31086     *         The duration in seconds
31087     */
31088    timeShiftBufferDepth: function timeShiftBufferDepth(value) {
31089      return parseDuration(value);
31090    },
31091
31092    /**
31093     * Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
31094     * Format is a duration string as specified in ISO 8601
31095     *
31096     * @param {string} value
31097     *        value of attribute as a string
31098     * @return {number}
31099     *         The duration in seconds
31100     */
31101    start: function start(value) {
31102      return parseDuration(value);
31103    },
31104
31105    /**
31106     * Specifies the width of the visual presentation
31107     *
31108     * @param {string} value
31109     *        value of attribute as a string
31110     * @return {number}
31111     *         The parsed width
31112     */
31113    width: function width(value) {
31114      return parseInt(value, 10);
31115    },
31116
31117    /**
31118     * Specifies the height of the visual presentation
31119     *
31120     * @param {string} value
31121     *        value of attribute as a string
31122     * @return {number}
31123     *         The parsed height
31124     */
31125    height: function height(value) {
31126      return parseInt(value, 10);
31127    },
31128
31129    /**
31130     * Specifies the bitrate of the representation
31131     *
31132     * @param {string} value
31133     *        value of attribute as a string
31134     * @return {number}
31135     *         The parsed bandwidth
31136     */
31137    bandwidth: function bandwidth(value) {
31138      return parseInt(value, 10);
31139    },
31140
31141    /**
31142     * Specifies the number of the first Media Segment in this Representation in the Period
31143     *
31144     * @param {string} value
31145     *        value of attribute as a string
31146     * @return {number}
31147     *         The parsed number
31148     */
31149    startNumber: function startNumber(value) {
31150      return parseInt(value, 10);
31151    },
31152
31153    /**
31154     * Specifies the timescale in units per seconds
31155     *
31156     * @param {string} value
31157     *        value of attribute as a string
31158     * @return {number}
31159     *         The aprsed timescale
31160     */
31161    timescale: function timescale(value) {
31162      return parseInt(value, 10);
31163    },
31164
31165    /**
31166     * Specifies the constant approximate Segment duration
31167     * NOTE: The <Period> element also contains an @duration attribute. This duration
31168     *       specifies the duration of the Period. This attribute is currently not
31169     *       supported by the rest of the parser, however we still check for it to prevent
31170     *       errors.
31171     *
31172     * @param {string} value
31173     *        value of attribute as a string
31174     * @return {number}
31175     *         The parsed duration
31176     */
31177    duration: function duration(value) {
31178      var parsedValue = parseInt(value, 10);
31179
31180      if (isNaN(parsedValue)) {
31181        return parseDuration(value);
31182      }
31183
31184      return parsedValue;
31185    },
31186
31187    /**
31188     * Specifies the Segment duration, in units of the value of the @timescale.
31189     *
31190     * @param {string} value
31191     *        value of attribute as a string
31192     * @return {number}
31193     *         The parsed duration
31194     */
31195    d: function d(value) {
31196      return parseInt(value, 10);
31197    },
31198
31199    /**
31200     * Specifies the MPD start time, in @timescale units, the first Segment in the series
31201     * starts relative to the beginning of the Period
31202     *
31203     * @param {string} value
31204     *        value of attribute as a string
31205     * @return {number}
31206     *         The parsed time
31207     */
31208    t: function t(value) {
31209      return parseInt(value, 10);
31210    },
31211
31212    /**
31213     * Specifies the repeat count of the number of following contiguous Segments with the
31214     * same duration expressed by the value of @d
31215     *
31216     * @param {string} value
31217     *        value of attribute as a string
31218     * @return {number}
31219     *         The parsed number
31220     */
31221    r: function r(value) {
31222      return parseInt(value, 10);
31223    },
31224
31225    /**
31226     * Default parser for all other attributes. Acts as a no-op and just returns the value
31227     * as a string
31228     *
31229     * @param {string} value
31230     *        value of attribute as a string
31231     * @return {string}
31232     *         Unparsed value
31233     */
31234    DEFAULT: function DEFAULT(value) {
31235      return value;
31236    }
31237  };
31238  /**
31239   * Gets all the attributes and values of the provided node, parses attributes with known
31240   * types, and returns an object with attribute names mapped to values.
31241   *
31242   * @param {Node} el
31243   *        The node to parse attributes from
31244   * @return {Object}
31245   *         Object with all attributes of el parsed
31246   */
31247
31248  var parseAttributes$1 = function parseAttributes(el) {
31249    if (!(el && el.attributes)) {
31250      return {};
31251    }
31252
31253    return from(el.attributes).reduce(function (a, e) {
31254      var parseFn = parsers[e.name] || parsers.DEFAULT;
31255      a[e.name] = parseFn(e.value);
31256      return a;
31257    }, {});
31258  };
31259
31260  function decodeB64ToUint8Array(b64Text) {
31261    var decodedString = window$1.atob(b64Text);
31262    var array = new Uint8Array(decodedString.length);
31263
31264    for (var i = 0; i < decodedString.length; i++) {
31265      array[i] = decodedString.charCodeAt(i);
31266    }
31267
31268    return array;
31269  }
31270
31271  var keySystemsMap = {
31272    'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
31273    'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
31274    'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
31275    'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime'
31276  };
31277  /**
31278   * Builds a list of urls that is the product of the reference urls and BaseURL values
31279   *
31280   * @param {string[]} referenceUrls
31281   *        List of reference urls to resolve to
31282   * @param {Node[]} baseUrlElements
31283   *        List of BaseURL nodes from the mpd
31284   * @return {string[]}
31285   *         List of resolved urls
31286   */
31287
31288  var buildBaseUrls = function buildBaseUrls(referenceUrls, baseUrlElements) {
31289    if (!baseUrlElements.length) {
31290      return referenceUrls;
31291    }
31292
31293    return flatten(referenceUrls.map(function (reference) {
31294      return baseUrlElements.map(function (baseUrlElement) {
31295        return resolveUrl(reference, getContent(baseUrlElement));
31296      });
31297    }));
31298  };
31299  /**
31300   * Contains all Segment information for its containing AdaptationSet
31301   *
31302   * @typedef {Object} SegmentInformation
31303   * @property {Object|undefined} template
31304   *           Contains the attributes for the SegmentTemplate node
31305   * @property {Object[]|undefined} timeline
31306   *           Contains a list of atrributes for each S node within the SegmentTimeline node
31307   * @property {Object|undefined} list
31308   *           Contains the attributes for the SegmentList node
31309   * @property {Object|undefined} base
31310   *           Contains the attributes for the SegmentBase node
31311   */
31312
31313  /**
31314   * Returns all available Segment information contained within the AdaptationSet node
31315   *
31316   * @param {Node} adaptationSet
31317   *        The AdaptationSet node to get Segment information from
31318   * @return {SegmentInformation}
31319   *         The Segment information contained within the provided AdaptationSet
31320   */
31321
31322
31323  var getSegmentInformation = function getSegmentInformation(adaptationSet) {
31324    var segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
31325    var segmentList = findChildren(adaptationSet, 'SegmentList')[0];
31326    var segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(function (s) {
31327      return merge({
31328        tag: 'SegmentURL'
31329      }, parseAttributes$1(s));
31330    });
31331    var segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];
31332    var segmentTimelineParentNode = segmentList || segmentTemplate;
31333    var segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0];
31334    var segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate;
31335    var segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both
31336    // @initialization and an <Initialization> node.  @initialization can be templated,
31337    // while the node can have a url and range specified.  If the <SegmentTemplate> has
31338    // both @initialization and an <Initialization> subelement we opt to override with
31339    // the node, as this interaction is not defined in the spec.
31340
31341    var template = segmentTemplate && parseAttributes$1(segmentTemplate);
31342
31343    if (template && segmentInitialization) {
31344      template.initialization = segmentInitialization && parseAttributes$1(segmentInitialization);
31345    } else if (template && template.initialization) {
31346      // If it is @initialization we convert it to an object since this is the format that
31347      // later functions will rely on for the initialization segment.  This is only valid
31348      // for <SegmentTemplate>
31349      template.initialization = {
31350        sourceURL: template.initialization
31351      };
31352    }
31353
31354    var segmentInfo = {
31355      template: template,
31356      timeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(function (s) {
31357        return parseAttributes$1(s);
31358      }),
31359      list: segmentList && merge(parseAttributes$1(segmentList), {
31360        segmentUrls: segmentUrls,
31361        initialization: parseAttributes$1(segmentInitialization)
31362      }),
31363      base: segmentBase && merge(parseAttributes$1(segmentBase), {
31364        initialization: parseAttributes$1(segmentInitialization)
31365      })
31366    };
31367    Object.keys(segmentInfo).forEach(function (key) {
31368      if (!segmentInfo[key]) {
31369        delete segmentInfo[key];
31370      }
31371    });
31372    return segmentInfo;
31373  };
31374  /**
31375   * Contains Segment information and attributes needed to construct a Playlist object
31376   * from a Representation
31377   *
31378   * @typedef {Object} RepresentationInformation
31379   * @property {SegmentInformation} segmentInfo
31380   *           Segment information for this Representation
31381   * @property {Object} attributes
31382   *           Inherited attributes for this Representation
31383   */
31384
31385  /**
31386   * Maps a Representation node to an object containing Segment information and attributes
31387   *
31388   * @name inheritBaseUrlsCallback
31389   * @function
31390   * @param {Node} representation
31391   *        Representation node from the mpd
31392   * @return {RepresentationInformation}
31393   *         Representation information needed to construct a Playlist object
31394   */
31395
31396  /**
31397   * Returns a callback for Array.prototype.map for mapping Representation nodes to
31398   * Segment information and attributes using inherited BaseURL nodes.
31399   *
31400   * @param {Object} adaptationSetAttributes
31401   *        Contains attributes inherited by the AdaptationSet
31402   * @param {string[]} adaptationSetBaseUrls
31403   *        Contains list of resolved base urls inherited by the AdaptationSet
31404   * @param {SegmentInformation} adaptationSetSegmentInfo
31405   *        Contains Segment information for the AdaptationSet
31406   * @return {inheritBaseUrlsCallback}
31407   *         Callback map function
31408   */
31409
31410
31411  var inheritBaseUrls = function inheritBaseUrls(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) {
31412    return function (representation) {
31413      var repBaseUrlElements = findChildren(representation, 'BaseURL');
31414      var repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
31415      var attributes = merge(adaptationSetAttributes, parseAttributes$1(representation));
31416      var representationSegmentInfo = getSegmentInformation(representation);
31417      return repBaseUrls.map(function (baseUrl) {
31418        return {
31419          segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo),
31420          attributes: merge(attributes, {
31421            baseUrl: baseUrl
31422          })
31423        };
31424      });
31425    };
31426  };
31427  /**
31428   * Tranforms a series of content protection nodes to
31429   * an object containing pssh data by key system
31430   *
31431   * @param {Node[]} contentProtectionNodes
31432   *        Content protection nodes
31433   * @return {Object}
31434   *        Object containing pssh data by key system
31435   */
31436
31437
31438  var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) {
31439    return contentProtectionNodes.reduce(function (acc, node) {
31440      var attributes = parseAttributes$1(node);
31441      var keySystem = keySystemsMap[attributes.schemeIdUri];
31442
31443      if (keySystem) {
31444        acc[keySystem] = {
31445          attributes: attributes
31446        };
31447        var psshNode = findChildren(node, 'cenc:pssh')[0];
31448
31449        if (psshNode) {
31450          var pssh = getContent(psshNode);
31451          var psshBuffer = pssh && decodeB64ToUint8Array(pssh);
31452          acc[keySystem].pssh = psshBuffer;
31453        }
31454      }
31455
31456      return acc;
31457    }, {});
31458  };
31459  /**
31460   * Maps an AdaptationSet node to a list of Representation information objects
31461   *
31462   * @name toRepresentationsCallback
31463   * @function
31464   * @param {Node} adaptationSet
31465   *        AdaptationSet node from the mpd
31466   * @return {RepresentationInformation[]}
31467   *         List of objects containing Representaion information
31468   */
31469
31470  /**
31471   * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of
31472   * Representation information objects
31473   *
31474   * @param {Object} periodAttributes
31475   *        Contains attributes inherited by the Period
31476   * @param {string[]} periodBaseUrls
31477   *        Contains list of resolved base urls inherited by the Period
31478   * @param {string[]} periodSegmentInfo
31479   *        Contains Segment Information at the period level
31480   * @return {toRepresentationsCallback}
31481   *         Callback map function
31482   */
31483
31484
31485  var toRepresentations = function toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo) {
31486    return function (adaptationSet) {
31487      var adaptationSetAttributes = parseAttributes$1(adaptationSet);
31488      var adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL'));
31489      var role = findChildren(adaptationSet, 'Role')[0];
31490      var roleAttributes = {
31491        role: parseAttributes$1(role)
31492      };
31493      var attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes);
31494      var contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection'));
31495
31496      if (Object.keys(contentProtection).length) {
31497        attrs = merge(attrs, {
31498          contentProtection: contentProtection
31499        });
31500      }
31501
31502      var segmentInfo = getSegmentInformation(adaptationSet);
31503      var representations = findChildren(adaptationSet, 'Representation');
31504      var adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo);
31505      return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo)));
31506    };
31507  };
31508  /**
31509   * Maps an Period node to a list of Representation inforamtion objects for all
31510   * AdaptationSet nodes contained within the Period
31511   *
31512   * @name toAdaptationSetsCallback
31513   * @function
31514   * @param {Node} period
31515   *        Period node from the mpd
31516   * @param {number} periodIndex
31517   *        Index of the Period within the mpd
31518   * @return {RepresentationInformation[]}
31519   *         List of objects containing Representaion information
31520   */
31521
31522  /**
31523   * Returns a callback for Array.prototype.map for mapping Period nodes to a list of
31524   * Representation information objects
31525   *
31526   * @param {Object} mpdAttributes
31527   *        Contains attributes inherited by the mpd
31528   * @param {string[]} mpdBaseUrls
31529   *        Contains list of resolved base urls inherited by the mpd
31530   * @return {toAdaptationSetsCallback}
31531   *         Callback map function
31532   */
31533
31534
31535  var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) {
31536    return function (period, index) {
31537      var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period, 'BaseURL'));
31538      var periodAtt = parseAttributes$1(period);
31539      var parsedPeriodId = parseInt(periodAtt.id, 10); // fallback to mapping index if Period@id is not a number
31540
31541      var periodIndex = window$1.isNaN(parsedPeriodId) ? index : parsedPeriodId;
31542      var periodAttributes = merge(mpdAttributes, {
31543        periodIndex: periodIndex
31544      });
31545      var adaptationSets = findChildren(period, 'AdaptationSet');
31546      var periodSegmentInfo = getSegmentInformation(period);
31547      return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo)));
31548    };
31549  };
31550  /**
31551   * Traverses the mpd xml tree to generate a list of Representation information objects
31552   * that have inherited attributes from parent nodes
31553   *
31554   * @param {Node} mpd
31555   *        The root node of the mpd
31556   * @param {Object} options
31557   *        Available options for inheritAttributes
31558   * @param {string} options.manifestUri
31559   *        The uri source of the mpd
31560   * @param {number} options.NOW
31561   *        Current time per DASH IOP.  Default is current time in ms since epoch
31562   * @param {number} options.clientOffset
31563   *        Client time difference from NOW (in milliseconds)
31564   * @return {RepresentationInformation[]}
31565   *         List of objects containing Representation information
31566   */
31567
31568
31569  var inheritAttributes = function inheritAttributes(mpd, options) {
31570    if (options === void 0) {
31571      options = {};
31572    }
31573
31574    var _options = options,
31575        _options$manifestUri = _options.manifestUri,
31576        manifestUri = _options$manifestUri === void 0 ? '' : _options$manifestUri,
31577        _options$NOW = _options.NOW,
31578        NOW = _options$NOW === void 0 ? Date.now() : _options$NOW,
31579        _options$clientOffset = _options.clientOffset,
31580        clientOffset = _options$clientOffset === void 0 ? 0 : _options$clientOffset;
vendor: 37,813 bytes, lines 31581-32670
31581    var periods = findChildren(mpd, 'Period');
31582
31583    if (!periods.length) {
31584      throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
31585    }
31586
31587    var mpdAttributes = parseAttributes$1(mpd);
31588    var mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL'));
31589    mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
31590    mpdAttributes.NOW = NOW;
31591    mpdAttributes.clientOffset = clientOffset;
31592    return flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls)));
31593  };
31594
31595  var stringToMpdXml = function stringToMpdXml(manifestString) {
31596    if (manifestString === '') {
31597      throw new Error(errors.DASH_EMPTY_MANIFEST);
31598    }
31599
31600    var parser = new window$1.DOMParser();
31601    var xml = parser.parseFromString(manifestString, 'application/xml');
31602    var mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null;
31603
31604    if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) {
31605      throw new Error(errors.DASH_INVALID_XML);
31606    }
31607
31608    return mpd;
31609  };
31610  /**
31611   * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
31612   *
31613   * @param {string} mpd
31614   *        XML string of the MPD manifest
31615   * @return {Object|null}
31616   *         Attributes of UTCTiming node specified in the manifest. Null if none found
31617   */
31618
31619
31620  var parseUTCTimingScheme = function parseUTCTimingScheme(mpd) {
31621    var UTCTimingNode = findChildren(mpd, 'UTCTiming')[0];
31622
31623    if (!UTCTimingNode) {
31624      return null;
31625    }
31626
31627    var attributes = parseAttributes$1(UTCTimingNode);
31628
31629    switch (attributes.schemeIdUri) {
31630      case 'urn:mpeg:dash:utc:http-head:2014':
31631      case 'urn:mpeg:dash:utc:http-head:2012':
31632        attributes.method = 'HEAD';
31633        break;
31634
31635      case 'urn:mpeg:dash:utc:http-xsdate:2014':
31636      case 'urn:mpeg:dash:utc:http-iso:2014':
31637      case 'urn:mpeg:dash:utc:http-xsdate:2012':
31638      case 'urn:mpeg:dash:utc:http-iso:2012':
31639        attributes.method = 'GET';
31640        break;
31641
31642      case 'urn:mpeg:dash:utc:direct:2014':
31643      case 'urn:mpeg:dash:utc:direct:2012':
31644        attributes.method = 'DIRECT';
31645        attributes.value = Date.parse(attributes.value);
31646        break;
31647
31648      case 'urn:mpeg:dash:utc:http-ntp:2014':
31649      case 'urn:mpeg:dash:utc:ntp:2014':
31650      case 'urn:mpeg:dash:utc:sntp:2014':
31651      default:
31652        throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME);
31653    }
31654
31655    return attributes;
31656  };
31657
31658  var parse = function parse(manifestString, options) {
31659    return toM3u8(toPlaylists(inheritAttributes(stringToMpdXml(manifestString), options)));
31660  };
31661  /**
31662   * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
31663   *
31664   * @param {string} manifestString
31665   *        XML string of the MPD manifest
31666   * @return {Object|null}
31667   *         Attributes of UTCTiming node specified in the manifest. Null if none found
31668   */
31669
31670
31671  var parseUTCTiming = function parseUTCTiming(manifestString) {
31672    return parseUTCTimingScheme(stringToMpdXml(manifestString));
31673  };
31674
31675  var toUnsigned = function toUnsigned(value) {
31676    return value >>> 0;
31677  };
31678
31679  var bin = {
31680    toUnsigned: toUnsigned
31681  };
31682
31683  var toUnsigned$1 = bin.toUnsigned;
31684
31685  var _findBox, parseType, timescale, startTime, getVideoTrackIds; // Find the data for a box specified by its path
31686
31687
31688  _findBox = function findBox(data, path) {
31689    var results = [],
31690        i,
31691        size,
31692        type,
31693        end,
31694        subresults;
31695
31696    if (!path.length) {
31697      // short-circuit the search for empty paths
31698      return null;
31699    }
31700
31701    for (i = 0; i < data.byteLength;) {
31702      size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
31703      type = parseType(data.subarray(i + 4, i + 8));
31704      end = size > 1 ? i + size : data.byteLength;
31705
31706      if (type === path[0]) {
31707        if (path.length === 1) {
31708          // this is the end of the path and we've found the box we were
31709          // looking for
31710          results.push(data.subarray(i + 8, end));
31711        } else {
31712          // recursively search for the next box along the path
31713          subresults = _findBox(data.subarray(i + 8, end), path.slice(1));
31714
31715          if (subresults.length) {
31716            results = results.concat(subresults);
31717          }
31718        }
31719      }
31720
31721      i = end;
31722    } // we've finished searching all of data
31723
31724
31725    return results;
31726  };
31727  /**
31728   * Returns the string representation of an ASCII encoded four byte buffer.
31729   * @param buffer {Uint8Array} a four-byte buffer to translate
31730   * @return {string} the corresponding string
31731   */
31732
31733
31734  parseType = function parseType(buffer) {
31735    var result = '';
31736    result += String.fromCharCode(buffer[0]);
31737    result += String.fromCharCode(buffer[1]);
31738    result += String.fromCharCode(buffer[2]);
31739    result += String.fromCharCode(buffer[3]);
31740    return result;
31741  };
31742  /**
31743   * Parses an MP4 initialization segment and extracts the timescale
31744   * values for any declared tracks. Timescale values indicate the
31745   * number of clock ticks per second to assume for time-based values
31746   * elsewhere in the MP4.
31747   *
31748   * To determine the start time of an MP4, you need two pieces of
31749   * information: the timescale unit and the earliest base media decode
31750   * time. Multiple timescales can be specified within an MP4 but the
31751   * base media decode time is always expressed in the timescale from
31752   * the media header box for the track:
31753   * ```
31754   * moov > trak > mdia > mdhd.timescale
31755   * ```
31756   * @param init {Uint8Array} the bytes of the init segment
31757   * @return {object} a hash of track ids to timescale values or null if
31758   * the init segment is malformed.
31759   */
31760
31761
31762  timescale = function timescale(init) {
31763    var result = {},
31764        traks = _findBox(init, ['moov', 'trak']); // mdhd timescale
31765
31766
31767    return traks.reduce(function (result, trak) {
31768      var tkhd, version, index, id, mdhd;
31769      tkhd = _findBox(trak, ['tkhd'])[0];
31770
31771      if (!tkhd) {
31772        return null;
31773      }
31774
31775      version = tkhd[0];
31776      index = version === 0 ? 12 : 20;
31777      id = toUnsigned$1(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
31778      mdhd = _findBox(trak, ['mdia', 'mdhd'])[0];
31779
31780      if (!mdhd) {
31781        return null;
31782      }
31783
31784      version = mdhd[0];
31785      index = version === 0 ? 12 : 20;
31786      result[id] = toUnsigned$1(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
31787      return result;
31788    }, result);
31789  };
31790  /**
31791   * Determine the base media decode start time, in seconds, for an MP4
31792   * fragment. If multiple fragments are specified, the earliest time is
31793   * returned.
31794   *
31795   * The base media decode time can be parsed from track fragment
31796   * metadata:
31797   * ```
31798   * moof > traf > tfdt.baseMediaDecodeTime
31799   * ```
31800   * It requires the timescale value from the mdhd to interpret.
31801   *
31802   * @param timescale {object} a hash of track ids to timescale values.
31803   * @return {number} the earliest base media decode start time for the
31804   * fragment, in seconds
31805   */
31806
31807
31808  startTime = function startTime(timescale, fragment) {
31809    var trafs, baseTimes, result; // we need info from two childrend of each track fragment box
31810
31811    trafs = _findBox(fragment, ['moof', 'traf']); // determine the start times for each track
31812
31813    baseTimes = [].concat.apply([], trafs.map(function (traf) {
31814      return _findBox(traf, ['tfhd']).map(function (tfhd) {
31815        var id, scale, baseTime; // get the track id from the tfhd
31816
31817        id = toUnsigned$1(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
31818
31819        scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
31820
31821        baseTime = _findBox(traf, ['tfdt']).map(function (tfdt) {
31822          var version, result;
31823          version = tfdt[0];
31824          result = toUnsigned$1(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
31825
31826          if (version === 1) {
31827            result *= Math.pow(2, 32);
31828            result += toUnsigned$1(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
31829          }
31830
31831          return result;
31832        })[0];
31833        baseTime = baseTime || Infinity; // convert base time to seconds
31834
31835        return baseTime / scale;
31836      });
31837    })); // return the minimum
31838
31839    result = Math.min.apply(null, baseTimes);
31840    return isFinite(result) ? result : 0;
31841  };
31842  /**
31843    * Find the trackIds of the video tracks in this source.
31844    * Found by parsing the Handler Reference and Track Header Boxes:
31845    *   moov > trak > mdia > hdlr
31846    *   moov > trak > tkhd
31847    *
31848    * @param {Uint8Array} init - The bytes of the init segment for this source
31849    * @return {Number[]} A list of trackIds
31850    *
31851    * @see ISO-BMFF-12/2015, Section 8.4.3
31852   **/
31853
31854
31855  getVideoTrackIds = function getVideoTrackIds(init) {
31856    var traks = _findBox(init, ['moov', 'trak']);
31857
31858    var videoTrackIds = [];
31859    traks.forEach(function (trak) {
31860      var hdlrs = _findBox(trak, ['mdia', 'hdlr']);
31861
31862      var tkhds = _findBox(trak, ['tkhd']);
31863
31864      hdlrs.forEach(function (hdlr, index) {
31865        var handlerType = parseType(hdlr.subarray(8, 12));
31866        var tkhd = tkhds[index];
31867        var view;
31868        var version;
31869        var trackId;
31870
31871        if (handlerType === 'vide') {
31872          view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
31873          version = view.getUint8(0);
31874          trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
31875          videoTrackIds.push(trackId);
31876        }
31877      });
31878    });
31879    return videoTrackIds;
31880  };
31881
31882  var probe = {
31883    findBox: _findBox,
31884    parseType: parseType,
31885    timescale: timescale,
31886    startTime: startTime,
31887    videoTrackIds: getVideoTrackIds
31888  };
31889
31890  /**
31891   * mux.js
31892   *
31893   * Copyright (c) 2015 Brightcove
31894   * All rights reserved.
31895   *
31896   * Functions that generate fragmented MP4s suitable for use with Media
31897   * Source Extensions.
31898   */
31899
31900  var UINT32_MAX = Math.pow(2, 32) - 1;
31901  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
31902
31903  (function () {
31904    var i;
31905    types = {
31906      avc1: [],
31907      // codingname
31908      avcC: [],
31909      btrt: [],
31910      dinf: [],
31911      dref: [],
31912      esds: [],
31913      ftyp: [],
31914      hdlr: [],
31915      mdat: [],
31916      mdhd: [],
31917      mdia: [],
31918      mfhd: [],
31919      minf: [],
31920      moof: [],
31921      moov: [],
31922      mp4a: [],
31923      // codingname
31924      mvex: [],
31925      mvhd: [],
31926      sdtp: [],
31927      smhd: [],
31928      stbl: [],
31929      stco: [],
31930      stsc: [],
31931      stsd: [],
31932      stsz: [],
31933      stts: [],
31934      styp: [],
31935      tfdt: [],
31936      tfhd: [],
31937      traf: [],
31938      trak: [],
31939      trun: [],
31940      trex: [],
31941      tkhd: [],
31942      vmhd: []
31943    }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
31944    // don't throw an error
31945
31946    if (typeof Uint8Array === 'undefined') {
31947      return;
31948    }
31949
31950    for (i in types) {
31951      if (types.hasOwnProperty(i)) {
31952        types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
31953      }
31954    }
31955
31956    MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
31957    AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
31958    MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
31959    VIDEO_HDLR = new Uint8Array([0x00, // version 0
31960    0x00, 0x00, 0x00, // flags
31961    0x00, 0x00, 0x00, 0x00, // pre_defined
31962    0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
31963    0x00, 0x00, 0x00, 0x00, // reserved
31964    0x00, 0x00, 0x00, 0x00, // reserved
31965    0x00, 0x00, 0x00, 0x00, // reserved
31966    0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
31967    ]);
31968    AUDIO_HDLR = new Uint8Array([0x00, // version 0
31969    0x00, 0x00, 0x00, // flags
31970    0x00, 0x00, 0x00, 0x00, // pre_defined
31971    0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
31972    0x00, 0x00, 0x00, 0x00, // reserved
31973    0x00, 0x00, 0x00, 0x00, // reserved
31974    0x00, 0x00, 0x00, 0x00, // reserved
31975    0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
31976    ]);
31977    HDLR_TYPES = {
31978      video: VIDEO_HDLR,
31979      audio: AUDIO_HDLR
31980    };
31981    DREF = new Uint8Array([0x00, // version 0
31982    0x00, 0x00, 0x00, // flags
31983    0x00, 0x00, 0x00, 0x01, // entry_count
31984    0x00, 0x00, 0x00, 0x0c, // entry_size
31985    0x75, 0x72, 0x6c, 0x20, // 'url' type
31986    0x00, // version 0
31987    0x00, 0x00, 0x01 // entry_flags
31988    ]);
31989    SMHD = new Uint8Array([0x00, // version
31990    0x00, 0x00, 0x00, // flags
31991    0x00, 0x00, // balance, 0 means centered
31992    0x00, 0x00 // reserved
31993    ]);
31994    STCO = new Uint8Array([0x00, // version
31995    0x00, 0x00, 0x00, // flags
31996    0x00, 0x00, 0x00, 0x00 // entry_count
31997    ]);
31998    STSC = STCO;
31999    STSZ = new Uint8Array([0x00, // version
32000    0x00, 0x00, 0x00, // flags
32001    0x00, 0x00, 0x00, 0x00, // sample_size
32002    0x00, 0x00, 0x00, 0x00 // sample_count
32003    ]);
32004    STTS = STCO;
32005    VMHD = new Uint8Array([0x00, // version
32006    0x00, 0x00, 0x01, // flags
32007    0x00, 0x00, // graphicsmode
32008    0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
32009    ]);
32010  })();
32011
32012  box = function box(type) {
32013    var payload = [],
32014        size = 0,
32015        i,
32016        result,
32017        view;
32018
32019    for (i = 1; i < arguments.length; i++) {
32020      payload.push(arguments[i]);
32021    }
32022
32023    i = payload.length; // calculate the total size we need to allocate
32024
32025    while (i--) {
32026      size += payload[i].byteLength;
32027    }
32028
32029    result = new Uint8Array(size + 8);
32030    view = new DataView(result.buffer, result.byteOffset, result.byteLength);
32031    view.setUint32(0, result.byteLength);
32032    result.set(type, 4); // copy the payload into the result
32033
32034    for (i = 0, size = 8; i < payload.length; i++) {
32035      result.set(payload[i], size);
32036      size += payload[i].byteLength;
32037    }
32038
32039    return result;
32040  };
32041
32042  dinf = function dinf() {
32043    return box(types.dinf, box(types.dref, DREF));
32044  };
32045
32046  esds = function esds(track) {
32047    return box(types.esds, new Uint8Array([0x00, // version
32048    0x00, 0x00, 0x00, // flags
32049    // ES_Descriptor
32050    0x03, // tag, ES_DescrTag
32051    0x19, // length
32052    0x00, 0x00, // ES_ID
32053    0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
32054    // DecoderConfigDescriptor
32055    0x04, // tag, DecoderConfigDescrTag
32056    0x11, // length
32057    0x40, // object type
32058    0x15, // streamType
32059    0x00, 0x06, 0x00, // bufferSizeDB
32060    0x00, 0x00, 0xda, 0xc0, // maxBitrate
32061    0x00, 0x00, 0xda, 0xc0, // avgBitrate
32062    // DecoderSpecificInfo
32063    0x05, // tag, DecoderSpecificInfoTag
32064    0x02, // length
32065    // ISO/IEC 14496-3, AudioSpecificConfig
32066    // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
32067    track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
32068    ]));
32069  };
32070
32071  ftyp = function ftyp() {
32072    return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
32073  };
32074
32075  hdlr = function hdlr(type) {
32076    return box(types.hdlr, HDLR_TYPES[type]);
32077  };
32078
32079  mdat = function mdat(data) {
32080    return box(types.mdat, data);
32081  };
32082
32083  mdhd = function mdhd(track) {
32084    var result = new Uint8Array([0x00, // version 0
32085    0x00, 0x00, 0x00, // flags
32086    0x00, 0x00, 0x00, 0x02, // creation_time
32087    0x00, 0x00, 0x00, 0x03, // modification_time
32088    0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
32089    track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
32090    0x55, 0xc4, // 'und' language (undetermined)
32091    0x00, 0x00]); // Use the sample rate from the track metadata, when it is
32092    // defined. The sample rate can be parsed out of an ADTS header, for
32093    // instance.
32094
32095    if (track.samplerate) {
32096      result[12] = track.samplerate >>> 24 & 0xFF;
32097      result[13] = track.samplerate >>> 16 & 0xFF;
32098      result[14] = track.samplerate >>> 8 & 0xFF;
32099      result[15] = track.samplerate & 0xFF;
32100    }
32101
32102    return box(types.mdhd, result);
32103  };
32104
32105  mdia = function mdia(track) {
32106    return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
32107  };
32108
32109  mfhd = function mfhd(sequenceNumber) {
32110    return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
32111    (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
32112    ]));
32113  };
32114
32115  minf = function minf(track) {
32116    return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
32117  };
32118
32119  moof = function moof(sequenceNumber, tracks) {
32120    var trackFragments = [],
32121        i = tracks.length; // build traf boxes for each track fragment
32122
32123    while (i--) {
32124      trackFragments[i] = traf(tracks[i]);
32125    }
32126
32127    return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
32128  };
32129  /**
32130   * Returns a movie box.
32131   * @param tracks {array} the tracks associated with this movie
32132   * @see ISO/IEC 14496-12:2012(E), section 8.2.1
32133   */
32134
32135
32136  moov = function moov(tracks) {
32137    var i = tracks.length,
32138        boxes = [];
32139
32140    while (i--) {
32141      boxes[i] = trak(tracks[i]);
32142    }
32143
32144    return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
32145  };
32146
32147  mvex = function mvex(tracks) {
32148    var i = tracks.length,
32149        boxes = [];
32150
32151    while (i--) {
32152      boxes[i] = trex(tracks[i]);
32153    }
32154
32155    return box.apply(null, [types.mvex].concat(boxes));
32156  };
32157
32158  mvhd = function mvhd(duration) {
32159    var bytes = new Uint8Array([0x00, // version 0
32160    0x00, 0x00, 0x00, // flags
32161    0x00, 0x00, 0x00, 0x01, // creation_time
32162    0x00, 0x00, 0x00, 0x02, // modification_time
32163    0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
32164    (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
32165    0x00, 0x01, 0x00, 0x00, // 1.0 rate
32166    0x01, 0x00, // 1.0 volume
32167    0x00, 0x00, // reserved
32168    0x00, 0x00, 0x00, 0x00, // reserved
32169    0x00, 0x00, 0x00, 0x00, // reserved
32170    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
32171    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
32172    0xff, 0xff, 0xff, 0xff // next_track_ID
32173    ]);
32174    return box(types.mvhd, bytes);
32175  };
32176
32177  sdtp = function sdtp(track) {
32178    var samples = track.samples || [],
32179        bytes = new Uint8Array(4 + samples.length),
32180        flags,
32181        i; // leave the full box header (4 bytes) all zero
32182    // write the sample table
32183
32184    for (i = 0; i < samples.length; i++) {
32185      flags = samples[i].flags;
32186      bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
32187    }
32188
32189    return box(types.sdtp, bytes);
32190  };
32191
32192  stbl = function stbl(track) {
32193    return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
32194  };
32195
32196  (function () {
32197    var videoSample, audioSample;
32198
32199    stsd = function stsd(track) {
32200      return box(types.stsd, new Uint8Array([0x00, // version 0
32201      0x00, 0x00, 0x00, // flags
32202      0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
32203    };
32204
32205    videoSample = function videoSample(track) {
32206      var sps = track.sps || [],
32207          pps = track.pps || [],
32208          sequenceParameterSets = [],
32209          pictureParameterSets = [],
32210          i; // assemble the SPSs
32211
32212      for (i = 0; i < sps.length; i++) {
32213        sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
32214        sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
32215
32216        sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
32217      } // assemble the PPSs
32218
32219
32220      for (i = 0; i < pps.length; i++) {
32221        pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
32222        pictureParameterSets.push(pps[i].byteLength & 0xFF);
32223        pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
32224      }
32225
32226      return box(types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
32227      0x00, 0x01, // data_reference_index
32228      0x00, 0x00, // pre_defined
32229      0x00, 0x00, // reserved
32230      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
32231      (track.width & 0xff00) >> 8, track.width & 0xff, // width
32232      (track.height & 0xff00) >> 8, track.height & 0xff, // height
32233      0x00, 0x48, 0x00, 0x00, // horizresolution
32234      0x00, 0x48, 0x00, 0x00, // vertresolution
32235      0x00, 0x00, 0x00, 0x00, // reserved
32236      0x00, 0x01, // frame_count
32237      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
32238      0x00, 0x18, // depth = 24
32239      0x11, 0x11 // pre_defined = -1
32240      ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
32241      track.profileIdc, // AVCProfileIndication
32242      track.profileCompatibility, // profile_compatibility
32243      track.levelIdc, // AVCLevelIndication
32244      0xff // lengthSizeMinusOne, hard-coded to 4 bytes
32245      ].concat([sps.length // numOfSequenceParameterSets
32246      ]).concat(sequenceParameterSets).concat([pps.length // numOfPictureParameterSets
32247      ]).concat(pictureParameterSets))), // "PPS"
32248      box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
32249      0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
32250      0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate
32251      );
32252    };
32253
32254    audioSample = function audioSample(track) {
32255      return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12
32256      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
32257      0x00, 0x01, // data_reference_index
32258      // AudioSampleEntry, ISO/IEC 14496-12
32259      0x00, 0x00, 0x00, 0x00, // reserved
32260      0x00, 0x00, 0x00, 0x00, // reserved
32261      (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
32262      (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
32263      0x00, 0x00, // pre_defined
32264      0x00, 0x00, // reserved
32265      (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
32266      // MP4AudioSampleEntry, ISO/IEC 14496-14
32267      ]), esds(track));
32268    };
32269  })();
32270
32271  tkhd = function tkhd(track) {
32272    var result = new Uint8Array([0x00, // version 0
32273    0x00, 0x00, 0x07, // flags
32274    0x00, 0x00, 0x00, 0x00, // creation_time
32275    0x00, 0x00, 0x00, 0x00, // modification_time
32276    (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
32277    0x00, 0x00, 0x00, 0x00, // reserved
32278    (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
32279    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
32280    0x00, 0x00, // layer
32281    0x00, 0x00, // alternate_group
32282    0x01, 0x00, // non-audio track volume
32283    0x00, 0x00, // reserved
32284    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
32285    (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
32286    (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
32287    ]);
32288    return box(types.tkhd, result);
32289  };
32290  /**
32291   * Generate a track fragment (traf) box. A traf box collects metadata
32292   * about tracks in a movie fragment (moof) box.
32293   */
32294
32295
32296  traf = function traf(track) {
32297    var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
32298    trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
32299    0x00, 0x00, 0x3a, // flags
32300    (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
32301    0x00, 0x00, 0x00, 0x01, // sample_description_index
32302    0x00, 0x00, 0x00, 0x00, // default_sample_duration
32303    0x00, 0x00, 0x00, 0x00, // default_sample_size
32304    0x00, 0x00, 0x00, 0x00 // default_sample_flags
32305    ]));
32306    upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
32307    lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
32308    trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
32309    0x00, 0x00, 0x00, // flags
32310    // baseMediaDecodeTime
32311    upperWordBaseMediaDecodeTime >>> 24 & 0xFF, upperWordBaseMediaDecodeTime >>> 16 & 0xFF, upperWordBaseMediaDecodeTime >>> 8 & 0xFF, upperWordBaseMediaDecodeTime & 0xFF, lowerWordBaseMediaDecodeTime >>> 24 & 0xFF, lowerWordBaseMediaDecodeTime >>> 16 & 0xFF, lowerWordBaseMediaDecodeTime >>> 8 & 0xFF, lowerWordBaseMediaDecodeTime & 0xFF])); // the data offset specifies the number of bytes from the start of
32312    // the containing moof to the first payload byte of the associated
32313    // mdat
32314
32315    dataOffset = 32 + // tfhd
32316    20 + // tfdt
32317    8 + // traf header
32318    16 + // mfhd
32319    8 + // moof header
32320    8; // mdat header
32321    // audio tracks require less metadata
32322
32323    if (track.type === 'audio') {
32324      trackFragmentRun = trun(track, dataOffset);
32325      return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
32326    } // video tracks should contain an independent and disposable samples
32327    // box (sdtp)
32328    // generate one and adjust offsets to match
32329
32330
32331    sampleDependencyTable = sdtp(track);
32332    trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset);
32333    return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
32334  };
32335  /**
32336   * Generate a track box.
32337   * @param track {object} a track definition
32338   * @return {Uint8Array} the track box
32339   */
32340
32341
32342  trak = function trak(track) {
32343    track.duration = track.duration || 0xffffffff;
32344    return box(types.trak, tkhd(track), mdia(track));
32345  };
32346
32347  trex = function trex(track) {
32348    var result = new Uint8Array([0x00, // version 0
32349    0x00, 0x00, 0x00, // flags
32350    (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
32351    0x00, 0x00, 0x00, 0x01, // default_sample_description_index
32352    0x00, 0x00, 0x00, 0x00, // default_sample_duration
32353    0x00, 0x00, 0x00, 0x00, // default_sample_size
32354    0x00, 0x01, 0x00, 0x01 // default_sample_flags
32355    ]); // the last two bytes of default_sample_flags is the sample
32356    // degradation priority, a hint about the importance of this sample
32357    // relative to others. Lower the degradation priority for all sample
32358    // types other than video.
32359
32360    if (track.type !== 'video') {
32361      result[result.length - 1] = 0x00;
32362    }
32363
32364    return box(types.trex, result);
32365  };
32366
32367  (function () {
32368    var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
32369    // duration is present for the first sample, it will be present for
32370    // all subsequent samples.
32371    // see ISO/IEC 14496-12:2012, Section 8.8.8.1
32372
32373    trunHeader = function trunHeader(samples, offset) {
32374      var durationPresent = 0,
32375          sizePresent = 0,
32376          flagsPresent = 0,
32377          compositionTimeOffset = 0; // trun flag constants
32378
32379      if (samples.length) {
32380        if (samples[0].duration !== undefined) {
32381          durationPresent = 0x1;
32382        }
32383
32384        if (samples[0].size !== undefined) {
32385          sizePresent = 0x2;
32386        }
32387
32388        if (samples[0].flags !== undefined) {
32389          flagsPresent = 0x4;
32390        }
32391
32392        if (samples[0].compositionTimeOffset !== undefined) {
32393          compositionTimeOffset = 0x8;
32394        }
32395      }
32396
32397      return [0x00, // version 0
32398      0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
32399      (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
32400      (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
32401      ];
32402    };
32403
32404    videoTrun = function videoTrun(track, offset) {
32405      var bytes, samples, sample, i;
32406      samples = track.samples || [];
32407      offset += 8 + 12 + 16 * samples.length;
32408      bytes = trunHeader(samples, offset);
32409
32410      for (i = 0; i < samples.length; i++) {
32411        sample = samples[i];
32412        bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
32413        (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size
32414        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
32415        (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
32416        ]);
32417      }
32418
32419      return box(types.trun, new Uint8Array(bytes));
32420    };
32421
32422    audioTrun = function audioTrun(track, offset) {
32423      var bytes, samples, sample, i;
32424      samples = track.samples || [];
32425      offset += 8 + 12 + 8 * samples.length;
32426      bytes = trunHeader(samples, offset);
32427
32428      for (i = 0; i < samples.length; i++) {
32429        sample = samples[i];
32430        bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
32431        (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size
32432      }
32433
32434      return box(types.trun, new Uint8Array(bytes));
32435    };
32436
32437    trun = function trun(track, offset) {
32438      if (track.type === 'audio') {
32439        return audioTrun(track, offset);
32440      }
32441
32442      return videoTrun(track, offset);
32443    };
32444  })();
32445
32446  var mp4Generator = {
32447    ftyp: ftyp,
32448    mdat: mdat,
32449    moof: moof,
32450    moov: moov,
32451    initSegment: function initSegment(tracks) {
32452      var fileType = ftyp(),
32453          movie = moov(tracks),
32454          result;
32455      result = new Uint8Array(fileType.byteLength + movie.byteLength);
32456      result.set(fileType);
32457      result.set(movie, fileType.byteLength);
32458      return result;
32459    }
32460  };
32461
32462  /**
32463   * mux.js
32464   *
32465   * Copyright (c) 2014 Brightcove
32466   * All rights reserved.
32467   *
32468   * A lightweight readable stream implemention that handles event dispatching.
32469   * Objects that inherit from streams should call init in their constructors.
32470   */
32471
32472  var Stream$1 = function Stream() {
32473    this.init = function () {
32474      var listeners = {};
32475      /**
32476       * Add a listener for a specified event type.
32477       * @param type {string} the event name
32478       * @param listener {function} the callback to be invoked when an event of
32479       * the specified type occurs
32480       */
32481
32482      this.on = function (type, listener) {
32483        if (!listeners[type]) {
32484          listeners[type] = [];
32485        }
32486
32487        listeners[type] = listeners[type].concat(listener);
32488      };
32489      /**
32490       * Remove a listener for a specified event type.
32491       * @param type {string} the event name
32492       * @param listener {function} a function previously registered for this
32493       * type of event through `on`
32494       */
32495
32496
32497      this.off = function (type, listener) {
32498        var index;
32499
32500        if (!listeners[type]) {
32501          return false;
32502        }
32503
32504        index = listeners[type].indexOf(listener);
32505        listeners[type] = listeners[type].slice();
32506        listeners[type].splice(index, 1);
32507        return index > -1;
32508      };
32509      /**
32510       * Trigger an event of the specified type on this stream. Any additional
32511       * arguments to this function are passed as parameters to event listeners.
32512       * @param type {string} the event name
32513       */
32514
32515
32516      this.trigger = function (type) {
32517        var callbacks, i, length, args;
32518        callbacks = listeners[type];
32519
32520        if (!callbacks) {
32521          return;
32522        } // Slicing the arguments on every invocation of this method
32523        // can add a significant amount of overhead. Avoid the
32524        // intermediate object creation for the common case of a
32525        // single callback argument
32526
32527
32528        if (arguments.length === 2) {
32529          length = callbacks.length;
32530
32531          for (i = 0; i < length; ++i) {
32532            callbacks[i].call(this, arguments[1]);
32533          }
32534        } else {
32535          args = [];
32536          i = arguments.length;
32537
32538          for (i = 1; i < arguments.length; ++i) {
32539            args.push(arguments[i]);
32540          }
32541
32542          length = callbacks.length;
32543
32544          for (i = 0; i < length; ++i) {
32545            callbacks[i].apply(this, args);
32546          }
32547        }
32548      };
32549      /**
32550       * Destroys the stream and cleans up.
32551       */
32552
32553
32554      this.dispose = function () {
32555        listeners = {};
32556      };
32557    };
32558  };
32559  /**
32560   * Forwards all `data` events on this stream to the destination stream. The
32561   * destination stream should provide a method `push` to receive the data
32562   * events as they arrive.
32563   * @param destination {stream} the stream that will receive all `data` events
32564   * @param autoFlush {boolean} if false, we will not call `flush` on the destination
32565   *                            when the current stream emits a 'done' event
32566   * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
32567   */
32568
32569
32570  Stream$1.prototype.pipe = function (destination) {
32571    this.on('data', function (data) {
32572      destination.push(data);
32573    });
32574    this.on('done', function (flushSource) {
32575      destination.flush(flushSource);
32576    });
32577    return destination;
32578  }; // Default stream functions that are expected to be overridden to perform
32579  // actual work. These are provided by the prototype as a sort of no-op
32580  // implementation so that we don't have to check for their existence in the
32581  // `pipe` function above.
32582
32583
32584  Stream$1.prototype.push = function (data) {
32585    this.trigger('data', data);
32586  };
32587
32588  Stream$1.prototype.flush = function (flushSource) {
32589    this.trigger('done', flushSource);
32590  };
32591
32592  var stream = Stream$1;
32593
32594  // Convert an array of nal units into an array of frames with each frame being
32595  // composed of the nal units that make up that frame
32596  // Also keep track of cummulative data about the frame from the nal units such
32597  // as the frame duration, starting pts, etc.
32598  var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) {
32599    var i,
32600        currentNal,
32601        currentFrame = [],
32602        frames = [];
32603    currentFrame.byteLength = 0;
32604
32605    for (i = 0; i < nalUnits.length; i++) {
32606      currentNal = nalUnits[i]; // Split on 'aud'-type nal units
32607
32608      if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
32609        // Since the very first nal unit is expected to be an AUD
32610        // only push to the frames array when currentFrame is not empty
32611        if (currentFrame.length) {
32612          currentFrame.duration = currentNal.dts - currentFrame.dts;
32613          frames.push(currentFrame);
32614        }
32615
32616        currentFrame = [currentNal];
32617        currentFrame.byteLength = currentNal.data.byteLength;
32618        currentFrame.pts = currentNal.pts;
32619        currentFrame.dts = currentNal.dts;
32620      } else {
32621        // Specifically flag key frames for ease of use later
32622        if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
32623          currentFrame.keyFrame = true;
32624        }
32625
32626        currentFrame.duration = currentNal.dts - currentFrame.dts;
32627        currentFrame.byteLength += currentNal.data.byteLength;
32628        currentFrame.push(currentNal);
32629      }
32630    } // For the last frame, use the duration of the previous frame if we
32631    // have nothing better to go on
32632
32633
32634    if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
32635      currentFrame.duration = frames[frames.length - 1].duration;
32636    } // Push the final frame
32637
32638
32639    frames.push(currentFrame);
32640    return frames;
32641  }; // Convert an array of frames into an array of Gop with each Gop being composed
32642  // of the frames that make up that Gop
32643  // Also keep track of cummulative data about the Gop from the frames such as the
32644  // Gop duration, starting pts, etc.
32645
32646
32647  var groupFramesIntoGops = function groupFramesIntoGops(frames) {
32648    var i,
32649        currentFrame,
32650        currentGop = [],
32651        gops = []; // We must pre-set some of the values on the Gop since we
32652    // keep running totals of these values
32653
32654    currentGop.byteLength = 0;
32655    currentGop.nalCount = 0;
32656    currentGop.duration = 0;
32657    currentGop.pts = frames[0].pts;
32658    currentGop.dts = frames[0].dts; // store some metadata about all the Gops
32659
32660    gops.byteLength = 0;
32661    gops.nalCount = 0;
32662    gops.duration = 0;
32663    gops.pts = frames[0].pts;
32664    gops.dts = frames[0].dts;
32665
32666    for (i = 0; i < frames.length; i++) {
32667      currentFrame = frames[i];
32668
32669      if (currentFrame.keyFrame) {
32670        // Since the very first frame is expected to be an keyframe
vendor: 2,691 bytes, lines 32671-32758
32671        // only push to the gops array when currentGop is not empty
32672        if (currentGop.length) {
32673          gops.push(currentGop);
32674          gops.byteLength += currentGop.byteLength;
32675          gops.nalCount += currentGop.nalCount;
32676          gops.duration += currentGop.duration;
32677        }
32678
32679        currentGop = [currentFrame];
32680        currentGop.nalCount = currentFrame.length;
32681        currentGop.byteLength = currentFrame.byteLength;
32682        currentGop.pts = currentFrame.pts;
32683        currentGop.dts = currentFrame.dts;
32684        currentGop.duration = currentFrame.duration;
32685      } else {
32686        currentGop.duration += currentFrame.duration;
32687        currentGop.nalCount += currentFrame.length;
32688        currentGop.byteLength += currentFrame.byteLength;
32689        currentGop.push(currentFrame);
32690      }
32691    }
32692
32693    if (gops.length && currentGop.duration <= 0) {
32694      currentGop.duration = gops[gops.length - 1].duration;
32695    }
32696
32697    gops.byteLength += currentGop.byteLength;
32698    gops.nalCount += currentGop.nalCount;
32699    gops.duration += currentGop.duration; // push the final Gop
32700
32701    gops.push(currentGop);
32702    return gops;
32703  };
32704  /*
32705   * Search for the first keyframe in the GOPs and throw away all frames
32706   * until that keyframe. Then extend the duration of the pulled keyframe
32707   * and pull the PTS and DTS of the keyframe so that it covers the time
32708   * range of the frames that were disposed.
32709   *
32710   * @param {Array} gops video GOPs
32711   * @returns {Array} modified video GOPs
32712   */
32713
32714
32715  var extendFirstKeyFrame = function extendFirstKeyFrame(gops) {
32716    var currentGop;
32717
32718    if (!gops[0][0].keyFrame && gops.length > 1) {
32719      // Remove the first GOP
32720      currentGop = gops.shift();
32721      gops.byteLength -= currentGop.byteLength;
32722      gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
32723      // first gop to cover the time period of the
32724      // frames we just removed
32725
32726      gops[0][0].dts = currentGop.dts;
32727      gops[0][0].pts = currentGop.pts;
32728      gops[0][0].duration += currentGop.duration;
32729    }
32730
32731    return gops;
32732  };
32733  /**
32734   * Default sample object
32735   * see ISO/IEC 14496-12:2012, section 8.6.4.3
32736   */
32737
32738
32739  var createDefaultSample = function createDefaultSample() {
32740    return {
32741      size: 0,
32742      flags: {
32743        isLeading: 0,
32744        dependsOn: 1,
32745        isDependedOn: 0,
32746        hasRedundancy: 0,
32747        degradationPriority: 0,
32748        isNonSyncSample: 1
32749      }
32750    };
32751  };
32752  /*
32753   * Collates information from a video frame into an object for eventual
32754   * entry into an MP4 sample table.
32755   *
32756   * @param {Object} frame the video frame
32757   * @param {Number} dataOffset the byte offset to position the sample
32758   * @return {Object}
vendor: 22,301 bytes, lines 32758-33460
32758 object containing sample table info for a frame
32759   */
32760
32761
32762  var sampleForFrame = function sampleForFrame(frame, dataOffset) {
32763    var sample = createDefaultSample();
32764    sample.dataOffset = dataOffset;
32765    sample.compositionTimeOffset = frame.pts - frame.dts;
32766    sample.duration = frame.duration;
32767    sample.size = 4 * frame.length; // Space for nal unit size
32768
32769    sample.size += frame.byteLength;
32770
32771    if (frame.keyFrame) {
32772      sample.flags.dependsOn = 2;
32773      sample.flags.isNonSyncSample = 0;
32774    }
32775
32776    return sample;
32777  }; // generate the track's sample table from an array of gops
32778
32779
32780  var generateSampleTable = function generateSampleTable(gops, baseDataOffset) {
32781    var h,
32782        i,
32783        sample,
32784        currentGop,
32785        currentFrame,
32786        dataOffset = baseDataOffset || 0,
32787        samples = [];
32788
32789    for (h = 0; h < gops.length; h++) {
32790      currentGop = gops[h];
32791
32792      for (i = 0; i < currentGop.length; i++) {
32793        currentFrame = currentGop[i];
32794        sample = sampleForFrame(currentFrame, dataOffset);
32795        dataOffset += sample.size;
32796        samples.push(sample);
32797      }
32798    }
32799
32800    return samples;
32801  }; // generate the track's raw mdat data from an array of gops
32802
32803
32804  var concatenateNalData = function concatenateNalData(gops) {
32805    var h,
32806        i,
32807        j,
32808        currentGop,
32809        currentFrame,
32810        currentNal,
32811        dataOffset = 0,
32812        nalsByteLength = gops.byteLength,
32813        numberOfNals = gops.nalCount,
32814        totalByteLength = nalsByteLength + 4 * numberOfNals,
32815        data = new Uint8Array(totalByteLength),
32816        view = new DataView(data.buffer); // For each Gop..
32817
32818    for (h = 0; h < gops.length; h++) {
32819      currentGop = gops[h]; // For each Frame..
32820
32821      for (i = 0; i < currentGop.length; i++) {
32822        currentFrame = currentGop[i]; // For each NAL..
32823
32824        for (j = 0; j < currentFrame.length; j++) {
32825          currentNal = currentFrame[j];
32826          view.setUint32(dataOffset, currentNal.data.byteLength);
32827          dataOffset += 4;
32828          data.set(currentNal.data, dataOffset);
32829          dataOffset += currentNal.data.byteLength;
32830        }
32831      }
32832    }
32833
32834    return data;
32835  };
32836
32837  var frameUtils = {
32838    groupNalsIntoFrames: groupNalsIntoFrames,
32839    groupFramesIntoGops: groupFramesIntoGops,
32840    extendFirstKeyFrame: extendFirstKeyFrame,
32841    generateSampleTable: generateSampleTable,
32842    concatenateNalData: concatenateNalData
32843  };
32844
32845  var highPrefix = [33, 16, 5, 32, 164, 27];
32846  var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
32847
32848  var zeroFill = function zeroFill(count) {
32849    var a = [];
32850
32851    while (count--) {
32852      a.push(0);
32853    }
32854
32855    return a;
32856  };
32857
32858  var makeTable = function makeTable(metaTable) {
32859    return Object.keys(metaTable).reduce(function (obj, key) {
32860      obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
32861        return arr.concat(part);
32862      }, []));
32863      return obj;
32864    }, {});
32865  }; // Frames-of-silence to use for filling in missing AAC frames
32866
32867
32868  var coneOfSilence = {
32869    96000: [highPrefix, [227, 64], zeroFill(154), [56]],
32870    88200: [highPrefix, [231], zeroFill(170), [56]],
32871    64000: [highPrefix, [248, 192], zeroFill(240), [56]],
32872    48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
32873    44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
32874    32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
32875    24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
32876    16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
32877    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]],
32878    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]],
32879    8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
32880  };
32881  var silence = makeTable(coneOfSilence);
32882
32883  var ONE_SECOND_IN_TS = 90000,
32884      // 90kHz clock
32885  secondsToVideoTs,
32886      secondsToAudioTs,
32887      videoTsToSeconds,
32888      audioTsToSeconds,
32889      audioTsToVideoTs,
32890      videoTsToAudioTs;
32891
32892  secondsToVideoTs = function secondsToVideoTs(seconds) {
32893    return seconds * ONE_SECOND_IN_TS;
32894  };
32895
32896  secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
32897    return seconds * sampleRate;
32898  };
32899
32900  videoTsToSeconds = function videoTsToSeconds(timestamp) {
32901    return timestamp / ONE_SECOND_IN_TS;
32902  };
32903
32904  audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
32905    return timestamp / sampleRate;
32906  };
32907
32908  audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
32909    return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
32910  };
32911
32912  videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
32913    return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
32914  };
32915
32916  var clock = {
32917    secondsToVideoTs: secondsToVideoTs,
32918    secondsToAudioTs: secondsToAudioTs,
32919    videoTsToSeconds: videoTsToSeconds,
32920    audioTsToSeconds: audioTsToSeconds,
32921    audioTsToVideoTs: audioTsToVideoTs,
32922    videoTsToAudioTs: videoTsToAudioTs
32923  };
32924
32925  var ONE_SECOND_IN_TS$1 = 90000; // 90kHz clock
32926
32927  /**
32928   * Sum the `byteLength` properties of the data in each AAC frame
32929   */
32930
32931  var sumFrameByteLengths = function sumFrameByteLengths(array) {
32932    var i,
32933        currentObj,
32934        sum = 0; // sum the byteLength's all each nal unit in the frame
32935
32936    for (i = 0; i < array.length; i++) {
32937      currentObj = array[i];
32938      sum += currentObj.data.byteLength;
32939    }
32940
32941    return sum;
32942  }; // Possibly pad (prefix) the audio track with silence if appending this track
32943  // would lead to the introduction of a gap in the audio buffer
32944
32945
32946  var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
32947    var baseMediaDecodeTimeTs,
32948        frameDuration = 0,
32949        audioGapDuration = 0,
32950        audioFillFrameCount = 0,
32951        audioFillDuration = 0,
32952        silentFrame,
32953        i;
32954
32955    if (!frames.length) {
32956      return;
32957    }
32958
32959    baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
32960
32961    frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 / (track.samplerate / 1024));
32962
32963    if (audioAppendStartTs && videoBaseMediaDecodeTime) {
32964      // insert the shortest possible amount (audio gap or audio to video gap)
32965      audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
32966
32967      audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
32968      audioFillDuration = audioFillFrameCount * frameDuration;
32969    } // don't attempt to fill gaps smaller than a single frame or larger
32970    // than a half second
32971
32972
32973    if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS$1 / 2) {
32974      return;
32975    }
32976
32977    silentFrame = silence[track.samplerate];
32978
32979    if (!silentFrame) {
32980      // we don't have a silent frame pregenerated for the sample rate, so use a frame
32981      // from the content instead
32982      silentFrame = frames[0].data;
32983    }
32984
32985    for (i = 0; i < audioFillFrameCount; i++) {
32986      frames.splice(i, 0, {
32987        data: silentFrame
32988      });
32989    }
32990
32991    track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
32992  }; // If the audio segment extends before the earliest allowed dts
32993  // value, remove AAC frames until starts at or after the earliest
32994  // allowed DTS so that we don't end up with a negative baseMedia-
32995  // DecodeTime for the audio track
32996
32997
32998  var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) {
32999    if (track.minSegmentDts >= earliestAllowedDts) {
33000      return adtsFrames;
33001    } // We will need to recalculate the earliest segment Dts
33002
33003
33004    track.minSegmentDts = Infinity;
33005    return adtsFrames.filter(function (currentFrame) {
33006      // If this is an allowed frame, keep it and record it's Dts
33007      if (currentFrame.dts >= earliestAllowedDts) {
33008        track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
33009        track.minSegmentPts = track.minSegmentDts;
33010        return true;
33011      } // Otherwise, discard it
33012
33013
33014      return false;
33015    });
33016  }; // generate the track's raw mdat data from an array of frames
33017
33018
33019  var generateSampleTable$1 = function generateSampleTable(frames) {
33020    var i,
33021        currentFrame,
33022        samples = [];
33023
33024    for (i = 0; i < frames.length; i++) {
33025      currentFrame = frames[i];
33026      samples.push({
33027        size: currentFrame.data.byteLength,
33028        duration: 1024 // For AAC audio, all samples contain 1024 samples
33029
33030      });
33031    }
33032
33033    return samples;
33034  }; // generate the track's sample table from an array of frames
33035
33036
33037  var concatenateFrameData = function concatenateFrameData(frames) {
33038    var i,
33039        currentFrame,
33040        dataOffset = 0,
33041        data = new Uint8Array(sumFrameByteLengths(frames));
33042
33043    for (i = 0; i < frames.length; i++) {
33044      currentFrame = frames[i];
33045      data.set(currentFrame.data, dataOffset);
33046      dataOffset += currentFrame.data.byteLength;
33047    }
33048
33049    return data;
33050  };
33051
33052  var audioFrameUtils = {
33053    prefixWithSilence: prefixWithSilence,
33054    trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
33055    generateSampleTable: generateSampleTable$1,
33056    concatenateFrameData: concatenateFrameData
33057  };
33058
33059  var ONE_SECOND_IN_TS$2 = 90000; // 90kHz clock
33060
33061  /**
33062   * Store information about the start and end of the track and the
33063   * duration for each frame/sample we process in order to calculate
33064   * the baseMediaDecodeTime
33065   */
33066
33067  var collectDtsInfo = function collectDtsInfo(track, data) {
33068    if (typeof data.pts === 'number') {
33069      if (track.timelineStartInfo.pts === undefined) {
33070        track.timelineStartInfo.pts = data.pts;
33071      }
33072
33073      if (track.minSegmentPts === undefined) {
33074        track.minSegmentPts = data.pts;
33075      } else {
33076        track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
33077      }
33078
33079      if (track.maxSegmentPts === undefined) {
33080        track.maxSegmentPts = data.pts;
33081      } else {
33082        track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
33083      }
33084    }
33085
33086    if (typeof data.dts === 'number') {
33087      if (track.timelineStartInfo.dts === undefined) {
33088        track.timelineStartInfo.dts = data.dts;
33089      }
33090
33091      if (track.minSegmentDts === undefined) {
33092        track.minSegmentDts = data.dts;
33093      } else {
33094        track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
33095      }
33096
33097      if (track.maxSegmentDts === undefined) {
33098        track.maxSegmentDts = data.dts;
33099      } else {
33100        track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
33101      }
33102    }
33103  };
33104  /**
33105   * Clear values used to calculate the baseMediaDecodeTime between
33106   * tracks
33107   */
33108
33109
33110  var clearDtsInfo = function clearDtsInfo(track) {
33111    delete track.minSegmentDts;
33112    delete track.maxSegmentDts;
33113    delete track.minSegmentPts;
33114    delete track.maxSegmentPts;
33115  };
33116  /**
33117   * Calculate the track's baseMediaDecodeTime based on the earliest
33118   * DTS the transmuxer has ever seen and the minimum DTS for the
33119   * current track
33120   * @param track {object} track metadata configuration
33121   * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
33122   *        in the source; false to adjust the first segment to start at 0.
33123   */
33124
33125
33126  var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
33127    var baseMediaDecodeTime,
33128        scale,
33129        minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
33130
33131    if (!keepOriginalTimestamps) {
33132      minSegmentDts -= track.timelineStartInfo.dts;
33133    } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
33134    // we want the start of the first segment to be placed
33135
33136
33137    baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
33138
33139    baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
33140
33141    baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
33142
33143    if (track.type === 'audio') {
33144      // Audio has a different clock equal to the sampling_rate so we need to
33145      // scale the PTS values into the clock rate of the track
33146      scale = track.samplerate / ONE_SECOND_IN_TS$2;
33147      baseMediaDecodeTime *= scale;
33148      baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
33149    }
33150
33151    return baseMediaDecodeTime;
33152  };
33153
33154  var trackDecodeInfo = {
33155    clearDtsInfo: clearDtsInfo,
33156    calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
33157    collectDtsInfo: collectDtsInfo
33158  };
33159
33160  /**
33161   * mux.js
33162   *
33163   * Copyright (c) 2015 Brightcove
33164   * All rights reserved.
33165   *
33166   * Reads in-band caption information from a video elementary
33167   * stream. Captions must follow the CEA-708 standard for injection
33168   * into an MPEG-2 transport streams.
33169   * @see https://en.wikipedia.org/wiki/CEA-708
33170   * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
33171   */
33172  // payload type field to indicate how they are to be
33173  // interpreted. CEAS-708 caption content is always transmitted with
33174  // payload type 0x04.
33175
33176  var USER_DATA_REGISTERED_ITU_T_T35 = 4,
33177      RBSP_TRAILING_BITS = 128;
33178  /**
33179    * Parse a supplemental enhancement information (SEI) NAL unit.
33180    * Stops parsing once a message of type ITU T T35 has been found.
33181    *
33182    * @param bytes {Uint8Array} the bytes of a SEI NAL unit
33183    * @return {object} the parsed SEI payload
33184    * @see Rec. ITU-T H.264, 7.3.2.3.1
33185    */
33186
33187  var parseSei = function parseSei(bytes) {
33188    var i = 0,
33189        result = {
33190      payloadType: -1,
33191      payloadSize: 0
33192    },
33193        payloadType = 0,
33194        payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
33195
33196    while (i < bytes.byteLength) {
33197      // stop once we have hit the end of the sei_rbsp
33198      if (bytes[i] === RBSP_TRAILING_BITS) {
33199        break;
33200      } // Parse payload type
33201
33202
33203      while (bytes[i] === 0xFF) {
33204        payloadType += 255;
33205        i++;
33206      }
33207
33208      payloadType += bytes[i++]; // Parse payload size
33209
33210      while (bytes[i] === 0xFF) {
33211        payloadSize += 255;
33212        i++;
33213      }
33214
33215      payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
33216      // there can only ever be one caption message in a frame's sei
33217
33218      if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
33219        result.payloadType = payloadType;
33220        result.payloadSize = payloadSize;
33221        result.payload = bytes.subarray(i, i + payloadSize);
33222        break;
33223      } // skip the payload and parse the next message
33224
33225
33226      i += payloadSize;
33227      payloadType = 0;
33228      payloadSize = 0;
33229    }
33230
33231    return result;
33232  }; // see ANSI/SCTE 128-1 (2013), section 8.1
33233
33234
33235  var parseUserData = function parseUserData(sei) {
33236    // itu_t_t35_contry_code must be 181 (United States) for
33237    // captions
33238    if (sei.payload[0] !== 181) {
33239      return null;
33240    } // itu_t_t35_provider_code should be 49 (ATSC) for captions
33241
33242
33243    if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
33244      return null;
33245    } // the user_identifier should be "GA94" to indicate ATSC1 data
33246
33247
33248    if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
33249      return null;
33250    } // finally, user_data_type_code should be 0x03 for caption data
33251
33252
33253    if (sei.payload[7] !== 0x03) {
33254      return null;
33255    } // return the user_data_type_structure and strip the trailing
33256    // marker bits
33257
33258
33259    return sei.payload.subarray(8, sei.payload.length - 1);
33260  }; // see CEA-708-D, section 4.4
33261
33262
33263  var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
33264    var results = [],
33265        i,
33266        count,
33267        offset,
33268        data; // if this is just filler, return immediately
33269
33270    if (!(userData[0] & 0x40)) {
33271      return results;
33272    } // parse out the cc_data_1 and cc_data_2 fields
33273
33274
33275    count = userData[0] & 0x1f;
33276
33277    for (i = 0; i < count; i++) {
33278      offset = i * 3;
33279      data = {
33280        type: userData[offset + 2] & 0x03,
33281        pts: pts
33282      }; // capture cc data when cc_valid is 1
33283
33284      if (userData[offset + 2] & 0x04) {
33285        data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
33286        results.push(data);
33287      }
33288    }
33289
33290    return results;
33291  };
33292
33293  var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
33294    var length = data.byteLength,
33295        emulationPreventionBytesPositions = [],
33296        i = 1,
33297        newLength,
33298        newData; // Find all `Emulation Prevention Bytes`
33299
33300    while (i < length - 2) {
33301      if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
33302        emulationPreventionBytesPositions.push(i + 2);
33303        i += 2;
33304      } else {
33305        i++;
33306      }
33307    } // If no Emulation Prevention Bytes were found just return the original
33308    // array
33309
33310
33311    if (emulationPreventionBytesPositions.length === 0) {
33312      return data;
33313    } // Create a new array to hold the NAL unit data
33314
33315
33316    newLength = length - emulationPreventionBytesPositions.length;
33317    newData = new Uint8Array(newLength);
33318    var sourceIndex = 0;
33319
33320    for (i = 0; i < newLength; sourceIndex++, i++) {
33321      if (sourceIndex === emulationPreventionBytesPositions[0]) {
33322        // Skip this byte
33323        sourceIndex++; // Remove this position index
33324
33325        emulationPreventionBytesPositions.shift();
33326      }
33327
33328      newData[i] = data[sourceIndex];
33329    }
33330
33331    return newData;
33332  }; // exports
33333
33334
33335  var captionPacketParser = {
33336    parseSei: parseSei,
33337    parseUserData: parseUserData,
33338    parseCaptionPackets: parseCaptionPackets,
33339    discardEmulationPreventionBytes: discardEmulationPreventionBytes,
33340    USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
33341  };
33342
33343  // Link To Transport
33344  // -----------------
33345
33346
33347  var CaptionStream = function CaptionStream() {
33348    CaptionStream.prototype.init.call(this);
33349    this.captionPackets_ = [];
33350    this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
33351    new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
33352    new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
33353    new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
33354    ];
33355    this.reset(); // forward data and done events from CCs to this CaptionStream
33356
33357    this.ccStreams_.forEach(function (cc) {
33358      cc.on('data', this.trigger.bind(this, 'data'));
33359      cc.on('done', this.trigger.bind(this, 'done'));
33360    }, this);
33361  };
33362
33363  CaptionStream.prototype = new stream();
33364
33365  CaptionStream.prototype.push = function (event) {
33366    var sei, userData, newCaptionPackets; // only examine SEI NALs
33367
33368    if (event.nalUnitType !== 'sei_rbsp') {
33369      return;
33370    } // parse the sei
33371
33372
33373    sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35
33374
33375    if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
33376      return;
33377    } // parse out the user data payload
33378
33379
33380    userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData
33381
33382    if (!userData) {
33383      return;
33384    } // Sometimes, the same segment # will be downloaded twice. To stop the
33385    // caption data from being processed twice, we track the latest dts we've
33386    // received and ignore everything with a dts before that. However, since
33387    // data for a specific dts can be split across packets on either side of
33388    // a segment boundary, we need to make sure we *don't* ignore the packets
33389    // from the *next* segment that have dts === this.latestDts_. By constantly
33390    // tracking the number of packets received with dts === this.latestDts_, we
33391    // know how many should be ignored once we start receiving duplicates.
33392
33393
33394    if (event.dts < this.latestDts_) {
33395      // We've started getting older data, so set the flag.
33396      this.ignoreNextEqualDts_ = true;
33397      return;
33398    } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
33399      this.numSameDts_--;
33400
33401      if (!this.numSameDts_) {
33402        // We've received the last duplicate packet, time to start processing again
33403        this.ignoreNextEqualDts_ = false;
33404      }
33405
33406      return;
33407    } // parse out CC data packets and save them for later
33408
33409
33410    newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
33411    this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
33412
33413    if (this.latestDts_ !== event.dts) {
33414      this.numSameDts_ = 0;
33415    }
33416
33417    this.numSameDts_++;
33418    this.latestDts_ = event.dts;
33419  };
33420
33421  CaptionStream.prototype.flush = function () {
33422    // make sure we actually parsed captions before proceeding
33423    if (!this.captionPackets_.length) {
33424      this.ccStreams_.forEach(function (cc) {
33425        cc.flush();
33426      }, this);
33427      return;
33428    } // In Chrome, the Array#sort function is not stable so add a
33429    // presortIndex that we can use to ensure we get a stable-sort
33430
33431
33432    this.captionPackets_.forEach(function (elem, idx) {
33433      elem.presortIndex = idx;
33434    }); // sort caption byte-pairs based on their PTS values
33435
33436    this.captionPackets_.sort(function (a, b) {
33437      if (a.pts === b.pts) {
33438        return a.presortIndex - b.presortIndex;
33439      }
33440
33441      return a.pts - b.pts;
33442    });
33443    this.captionPackets_.forEach(function (packet) {
33444      if (packet.type < 2) {
33445        // Dispatch packet to the right Cea608Stream
33446        this.dispatchCea608Packet(packet);
33447      } // this is where an 'else' would go for a dispatching packets
33448      // to a theoretical Cea708Stream that handles SERVICEn data
33449
33450    }, this);
33451    this.captionPackets_.length = 0;
33452    this.ccStreams_.forEach(function (cc) {
33453      cc.flush();
33454    }, this);
33455    return;
33456  };
33457
33458  CaptionStream.prototype.reset = function () {
33459    this.latestDts_ = null;
33460    this.ignoreNextEqualDts_ = false;
vendor: 1,105 bytes, lines 33461-33494
33461    this.numSameDts_ = 0;
33462    this.activeCea608Channel_ = [null, null];
33463    this.ccStreams_.forEach(function (ccStream) {
33464      ccStream.reset();
33465    });
33466  };
33467
33468  CaptionStream.prototype.dispatchCea608Packet = function (packet) {
33469    // NOTE: packet.type is the CEA608 field
33470    if (this.setsChannel1Active(packet)) {
33471      this.activeCea608Channel_[packet.type] = 0;
33472    } else if (this.setsChannel2Active(packet)) {
33473      this.activeCea608Channel_[packet.type] = 1;
33474    }
33475
33476    if (this.activeCea608Channel_[packet.type] === null) {
33477      // If we haven't received anything to set the active channel, discard the
33478      // data; we don't want jumbled captions
33479      return;
33480    }
33481
33482    this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
33483  };
33484
33485  CaptionStream.prototype.setsChannel1Active = function (packet) {
33486    return (packet.ccData & 0x7800) === 0x1000;
33487  };
33488
33489  CaptionStream.prototype.setsChannel2Active = function (packet) {
33490    return (packet.ccData & 0x7800) === 0x1800;
33491  }; // ----------------------
33492  // Session to Application
33493  // ----------------------
33494 
vendor: 12,175 bytes, lines 33494-33911
33494 // This hash maps non-ASCII, special, and extended character codes to their
33495  // proper Unicode equivalent. The first keys that are only a single byte
33496  // are the non-standard ASCII characters, which simply map the CEA608 byte
33497  // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
33498  // character codes, but have their MSB bitmasked with 0x03 so that a lookup
33499  // can be performed regardless of the field and data channel on which the
33500  // character code was received.
33501
33502
33503  var CHARACTER_TRANSLATION = {
33504    0x2a: 0xe1,
33505    // á
33506    0x5c: 0xe9,
33507    // é
33508    0x5e: 0xed,
33509    // í
33510    0x5f: 0xf3,
33511    // ó
33512    0x60: 0xfa,
33513    // ú
33514    0x7b: 0xe7,
33515    // ç
33516    0x7c: 0xf7,
33517    // ÷
33518    0x7d: 0xd1,
33519    // Ñ
33520    0x7e: 0xf1,
33521    // ñ
33522    0x7f: 0x2588,
33523    // █
33524    0x0130: 0xae,
33525    // ®
33526    0x0131: 0xb0,
33527    // °
33528    0x0132: 0xbd,
33529    // ½
33530    0x0133: 0xbf,
33531    // ¿
33532    0x0134: 0x2122,
33533    // ™
33534    0x0135: 0xa2,
33535    // ¢
33536    0x0136: 0xa3,
33537    // £
33538    0x0137: 0x266a,
33539    // ♪
33540    0x0138: 0xe0,
33541    // à
33542    0x0139: 0xa0,
33543    //
33544    0x013a: 0xe8,
33545    // è
33546    0x013b: 0xe2,
33547    // â
33548    0x013c: 0xea,
33549    // ê
33550    0x013d: 0xee,
33551    // î
33552    0x013e: 0xf4,
33553    // ô
33554    0x013f: 0xfb,
33555    // û
33556    0x0220: 0xc1,
33557    // Á
33558    0x0221: 0xc9,
33559    // É
33560    0x0222: 0xd3,
33561    // Ó
33562    0x0223: 0xda,
33563    // Ú
33564    0x0224: 0xdc,
33565    // Ü
33566    0x0225: 0xfc,
33567    // ü
33568    0x0226: 0x2018,
33569    // ‘
33570    0x0227: 0xa1,
33571    // ¡
33572    0x0228: 0x2a,
33573    // *
33574    0x0229: 0x27,
33575    // '
33576    0x022a: 0x2014,
33577    // —
33578    0x022b: 0xa9,
33579    // ©
33580    0x022c: 0x2120,
33581    // ℠
33582    0x022d: 0x2022,
33583    // •
33584    0x022e: 0x201c,
33585    // “
33586    0x022f: 0x201d,
33587    // ”
33588    0x0230: 0xc0,
33589    // À
33590    0x0231: 0xc2,
33591    // Â
33592    0x0232: 0xc7,
33593    // Ç
33594    0x0233: 0xc8,
33595    // È
33596    0x0234: 0xca,
33597    // Ê
33598    0x0235: 0xcb,
33599    // Ë
33600    0x0236: 0xeb,
33601    // ë
33602    0x0237: 0xce,
33603    // Î
33604    0x0238: 0xcf,
33605    // Ï
33606    0x0239: 0xef,
33607    // ï
33608    0x023a: 0xd4,
33609    // Ô
33610    0x023b: 0xd9,
33611    // Ù
33612    0x023c: 0xf9,
33613    // ù
33614    0x023d: 0xdb,
33615    // Û
33616    0x023e: 0xab,
33617    // «
33618    0x023f: 0xbb,
33619    // »
33620    0x0320: 0xc3,
33621    // Ã
33622    0x0321: 0xe3,
33623    // ã
33624    0x0322: 0xcd,
33625    // Í
33626    0x0323: 0xcc,
33627    // Ì
33628    0x0324: 0xec,
33629    // ì
33630    0x0325: 0xd2,
33631    // Ò
33632    0x0326: 0xf2,
33633    // ò
33634    0x0327: 0xd5,
33635    // Õ
33636    0x0328: 0xf5,
33637    // õ
33638    0x0329: 0x7b,
33639    // {
33640    0x032a: 0x7d,
33641    // }
33642    0x032b: 0x5c,
33643    // \
33644    0x032c: 0x5e,
33645    // ^
33646    0x032d: 0x5f,
33647    // _
33648    0x032e: 0x7c,
33649    // |
33650    0x032f: 0x7e,
33651    // ~
33652    0x0330: 0xc4,
33653    // Ä
33654    0x0331: 0xe4,
33655    // ä
33656    0x0332: 0xd6,
33657    // Ö
33658    0x0333: 0xf6,
33659    // ö
33660    0x0334: 0xdf,
33661    // ß
33662    0x0335: 0xa5,
33663    // ¥
33664    0x0336: 0xa4,
33665    // ¤
33666    0x0337: 0x2502,
33667    // │
33668    0x0338: 0xc5,
33669    // Å
33670    0x0339: 0xe5,
33671    // å
33672    0x033a: 0xd8,
33673    // Ø
33674    0x033b: 0xf8,
33675    // ø
33676    0x033c: 0x250c,
33677    // ┌
33678    0x033d: 0x2510,
33679    // ┐
33680    0x033e: 0x2514,
33681    // └
33682    0x033f: 0x2518 // ┘
33683
33684  };
33685
33686  var getCharFromCode = function getCharFromCode(code) {
33687    if (code === null) {
33688      return '';
33689    }
33690
33691    code = CHARACTER_TRANSLATION[code] || code;
33692    return String.fromCharCode(code);
33693  }; // the index of the last row in a CEA-608 display buffer
33694
33695
33696  var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
33697  // getting it through bit logic.
33698
33699  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
33700  // cells. The "bottom" row is the last element in the outer array.
33701
33702  var createDisplayBuffer = function createDisplayBuffer() {
33703    var result = [],
33704        i = BOTTOM_ROW + 1;
33705
33706    while (i--) {
33707      result.push('');
33708    }
33709
33710    return result;
33711  };
33712
33713  var Cea608Stream = function Cea608Stream(field, dataChannel) {
33714    Cea608Stream.prototype.init.call(this);
33715    this.field_ = field || 0;
33716    this.dataChannel_ = dataChannel || 0;
33717    this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
33718    this.setConstants();
33719    this.reset();
33720
33721    this.push = function (packet) {
33722      var data, swap, char0, char1, text; // remove the parity bits
33723
33724      data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
33725
33726      if (data === this.lastControlCode_) {
33727        this.lastControlCode_ = null;
33728        return;
33729      } // Store control codes
33730
33731
33732      if ((data & 0xf000) === 0x1000) {
33733        this.lastControlCode_ = data;
33734      } else if (data !== this.PADDING_) {
33735        this.lastControlCode_ = null;
33736      }
33737
33738      char0 = data >>> 8;
33739      char1 = data & 0xff;
33740
33741      if (data === this.PADDING_) {
33742        return;
33743      } else if (data === this.RESUME_CAPTION_LOADING_) {
33744        this.mode_ = 'popOn';
33745      } else if (data === this.END_OF_CAPTION_) {
33746        // If an EOC is received while in paint-on mode, the displayed caption
33747        // text should be swapped to non-displayed memory as if it was a pop-on
33748        // caption. Because of that, we should explicitly switch back to pop-on
33749        // mode
33750        this.mode_ = 'popOn';
33751        this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
33752
33753        this.flushDisplayed(packet.pts); // flip memory
33754
33755        swap = this.displayed_;
33756        this.displayed_ = this.nonDisplayed_;
33757        this.nonDisplayed_ = swap; // start measuring the time to display the caption
33758
33759        this.startPts_ = packet.pts;
33760      } else if (data === this.ROLL_UP_2_ROWS_) {
33761        this.rollUpRows_ = 2;
33762        this.setRollUp(packet.pts);
33763      } else if (data === this.ROLL_UP_3_ROWS_) {
33764        this.rollUpRows_ = 3;
33765        this.setRollUp(packet.pts);
33766      } else if (data === this.ROLL_UP_4_ROWS_) {
33767        this.rollUpRows_ = 4;
33768        this.setRollUp(packet.pts);
33769      } else if (data === this.CARRIAGE_RETURN_) {
33770        this.clearFormatting(packet.pts);
33771        this.flushDisplayed(packet.pts);
33772        this.shiftRowsUp_();
33773        this.startPts_ = packet.pts;
33774      } else if (data === this.BACKSPACE_) {
33775        if (this.mode_ === 'popOn') {
33776          this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
33777        } else {
33778          this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
33779        }
33780      } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
33781        this.flushDisplayed(packet.pts);
33782        this.displayed_ = createDisplayBuffer();
33783      } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
33784        this.nonDisplayed_ = createDisplayBuffer();
33785      } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
33786        if (this.mode_ !== 'paintOn') {
33787          // NOTE: This should be removed when proper caption positioning is
33788          // implemented
33789          this.flushDisplayed(packet.pts);
33790          this.displayed_ = createDisplayBuffer();
33791        }
33792
33793        this.mode_ = 'paintOn';
33794        this.startPts_ = packet.pts; // Append special characters to caption text
33795      } else if (this.isSpecialCharacter(char0, char1)) {
33796        // Bitmask char0 so that we can apply character transformations
33797        // regardless of field and data channel.
33798        // Then byte-shift to the left and OR with char1 so we can pass the
33799        // entire character code to `getCharFromCode`.
33800        char0 = (char0 & 0x03) << 8;
33801        text = getCharFromCode(char0 | char1);
33802        this[this.mode_](packet.pts, text);
33803        this.column_++; // Append extended characters to caption text
33804      } else if (this.isExtCharacter(char0, char1)) {
33805        // Extended characters always follow their "non-extended" equivalents.
33806        // IE if a "è" is desired, you'll always receive "eè"; non-compliant
33807        // decoders are supposed to drop the "è", while compliant decoders
33808        // backspace the "e" and insert "è".
33809        // Delete the previous character
33810        if (this.mode_ === 'popOn') {
33811          this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
33812        } else {
33813          this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
33814        } // Bitmask char0 so that we can apply character transformations
33815        // regardless of field and data channel.
33816        // Then byte-shift to the left and OR with char1 so we can pass the
33817        // entire character code to `getCharFromCode`.
33818
33819
33820        char0 = (char0 & 0x03) << 8;
33821        text = getCharFromCode(char0 | char1);
33822        this[this.mode_](packet.pts, text);
33823        this.column_++; // Process mid-row codes
33824      } else if (this.isMidRowCode(char0, char1)) {
33825        // Attributes are not additive, so clear all formatting
33826        this.clearFormatting(packet.pts); // According to the standard, mid-row codes
33827        // should be replaced with spaces, so add one now
33828
33829        this[this.mode_](packet.pts, ' ');
33830        this.column_++;
33831
33832        if ((char1 & 0xe) === 0xe) {
33833          this.addFormatting(packet.pts, ['i']);
33834        }
33835
33836        if ((char1 & 0x1) === 0x1) {
33837          this.addFormatting(packet.pts, ['u']);
33838        } // Detect offset control codes and adjust cursor
33839
33840      } else if (this.isOffsetControlCode(char0, char1)) {
33841        // Cursor position is set by indent PAC (see below) in 4-column
33842        // increments, with an additional offset code of 1-3 to reach any
33843        // of the 32 columns specified by CEA-608. So all we need to do
33844        // here is increment the column cursor by the given offset.
33845        this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
33846      } else if (this.isPAC(char0, char1)) {
33847        // There's no logic for PAC -> row mapping, so we have to just
33848        // find the row code in an array and use its index :(
33849        var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
33850
33851        if (this.mode_ === 'rollUp') {
33852          // This implies that the base row is incorrectly set.
33853          // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
33854          // of roll-up rows set.
33855          if (row - this.rollUpRows_ + 1 < 0) {
33856            row = this.rollUpRows_ - 1;
33857          }
33858
33859          this.setRollUp(packet.pts, row);
33860        }
33861
33862        if (row !== this.row_) {
33863          // formatting is only persistent for current row
33864          this.clearFormatting(packet.pts);
33865          this.row_ = row;
33866        } // All PACs can apply underline, so detect and apply
33867        // (All odd-numbered second bytes set underline)
33868
33869
33870        if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
33871          this.addFormatting(packet.pts, ['u']);
33872        }
33873
33874        if ((data & 0x10) === 0x10) {
33875          // We've got an indent level code. Each successive even number
33876          // increments the column cursor by 4, so we can get the desired
33877          // column position by bit-shifting to the right (to get n/2)
33878          // and multiplying by 4.
33879          this.column_ = ((data & 0xe) >> 1) * 4;
33880        }
33881
33882        if (this.isColorPAC(char1)) {
33883          // it's a color code, though we only support white, which
33884          // can be either normal or italicized. white italics can be
33885          // either 0x4e or 0x6e depending on the row, so we just
33886          // bitwise-and with 0xe to see if italics should be turned on
33887          if ((char1 & 0xe) === 0xe) {
33888            this.addFormatting(packet.pts, ['i']);
33889          }
33890        } // We have a normal character in char0, and possibly one in char1
33891
33892      } else if (this.isNormalChar(char0)) {
33893        if (char1 === 0x00) {
33894          char1 = null;
33895        }
33896
33897        text = getCharFromCode(char0);
33898        text += getCharFromCode(char1);
33899        this[this.mode_](packet.pts, text);
33900        this.column_ += text.length;
33901      } // finish data processing
33902
33903    };
33904  };
33905
33906  Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
33907  // display buffer
33908
33909  Cea608Stream.prototype.flushDisplayed = function (pts) {
33910    var content = this.displayed_ // remove spaces from the start and end of the string
33911    .map(function (row
vendor: 8,441 bytes, lines 33911-34154
33911) {
33912      try {
33913        return row.trim();
33914      } catch (e) {
33915        // Ordinarily, this shouldn't happen. However, caption
33916        // parsing errors should not throw exceptions and
33917        // break playback.
33918        // eslint-disable-next-line no-console
33919        console.error('Skipping malformed caption.');
33920        return '';
33921      }
33922    }) // combine all text rows to display in one cue
33923    .join('\n') // and remove blank rows from the start and end, but not the middle
33924    .replace(/^\n+|\n+$/g, '');
33925
33926    if (content.length) {
33927      this.trigger('data', {
33928        startPts: this.startPts_,
33929        endPts: pts,
33930        text: content,
33931        stream: this.name_
33932      });
33933    }
33934  };
33935  /**
33936   * Zero out the data, used for startup and on seek
33937   */
33938
33939
33940  Cea608Stream.prototype.reset = function () {
33941    this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
33942    // actually display captions. If a caption is shifted to a row
33943    // with a lower index than this, it is cleared from the display
33944    // buffer
33945
33946    this.topRow_ = 0;
33947    this.startPts_ = 0;
33948    this.displayed_ = createDisplayBuffer();
33949    this.nonDisplayed_ = createDisplayBuffer();
33950    this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
33951
33952    this.column_ = 0;
33953    this.row_ = BOTTOM_ROW;
33954    this.rollUpRows_ = 2; // This variable holds currently-applied formatting
33955
33956    this.formatting_ = [];
33957  };
33958  /**
33959   * Sets up control code and related constants for this instance
33960   */
33961
33962
33963  Cea608Stream.prototype.setConstants = function () {
33964    // The following attributes have these uses:
33965    // ext_ :    char0 for mid-row codes, and the base for extended
33966    //           chars (ext_+0, ext_+1, and ext_+2 are char0s for
33967    //           extended codes)
33968    // control_: char0 for control codes, except byte-shifted to the
33969    //           left so that we can do this.control_ | CONTROL_CODE
33970    // offset_:  char0 for tab offset codes
33971    //
33972    // It's also worth noting that control codes, and _only_ control codes,
33973    // differ between field 1 and field2. Field 2 control codes are always
33974    // their field 1 value plus 1. That's why there's the "| field" on the
33975    // control value.
33976    if (this.dataChannel_ === 0) {
33977      this.BASE_ = 0x10;
33978      this.EXT_ = 0x11;
33979      this.CONTROL_ = (0x14 | this.field_) << 8;
33980      this.OFFSET_ = 0x17;
33981    } else if (this.dataChannel_ === 1) {
33982      this.BASE_ = 0x18;
33983      this.EXT_ = 0x19;
33984      this.CONTROL_ = (0x1c | this.field_) << 8;
33985      this.OFFSET_ = 0x1f;
33986    } // Constants for the LSByte command codes recognized by Cea608Stream. This
33987    // list is not exhaustive. For a more comprehensive listing and semantics see
33988    // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
33989    // Padding
33990
33991
33992    this.PADDING_ = 0x0000; // Pop-on Mode
33993
33994    this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
33995    this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
33996
33997    this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
33998    this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
33999    this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
34000    this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
34001
34002    this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
34003
34004    this.BACKSPACE_ = this.CONTROL_ | 0x21;
34005    this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
34006    this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
34007  };
34008  /**
34009   * Detects if the 2-byte packet data is a special character
34010   *
34011   * Special characters have a second byte in the range 0x30 to 0x3f,
34012   * with the first byte being 0x11 (for data channel 1) or 0x19 (for
34013   * data channel 2).
34014   *
34015   * @param  {Integer} char0 The first byte
34016   * @param  {Integer} char1 The second byte
34017   * @return {Boolean}       Whether the 2 bytes are an special character
34018   */
34019
34020
34021  Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
34022    return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
34023  };
34024  /**
34025   * Detects if the 2-byte packet data is an extended character
34026   *
34027   * Extended characters have a second byte in the range 0x20 to 0x3f,
34028   * with the first byte being 0x12 or 0x13 (for data channel 1) or
34029   * 0x1a or 0x1b (for data channel 2).
34030   *
34031   * @param  {Integer} char0 The first byte
34032   * @param  {Integer} char1 The second byte
34033   * @return {Boolean}       Whether the 2 bytes are an extended character
34034   */
34035
34036
34037  Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
34038    return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
34039  };
34040  /**
34041   * Detects if the 2-byte packet is a mid-row code
34042   *
34043   * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
34044   * the first byte being 0x11 (for data channel 1) or 0x19 (for data
34045   * channel 2).
34046   *
34047   * @param  {Integer} char0 The first byte
34048   * @param  {Integer} char1 The second byte
34049   * @return {Boolean}       Whether the 2 bytes are a mid-row code
34050   */
34051
34052
34053  Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
34054    return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
34055  };
34056  /**
34057   * Detects if the 2-byte packet is an offset control code
34058   *
34059   * Offset control codes have a second byte in the range 0x21 to 0x23,
34060   * with the first byte being 0x17 (for data channel 1) or 0x1f (for
34061   * data channel 2).
34062   *
34063   * @param  {Integer} char0 The first byte
34064   * @param  {Integer} char1 The second byte
34065   * @return {Boolean}       Whether the 2 bytes are an offset control code
34066   */
34067
34068
34069  Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
34070    return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
34071  };
34072  /**
34073   * Detects if the 2-byte packet is a Preamble Address Code
34074   *
34075   * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
34076   * or 0x18 to 0x1f (for data channel 2), with the second byte in the
34077   * range 0x40 to 0x7f.
34078   *
34079   * @param  {Integer} char0 The first byte
34080   * @param  {Integer} char1 The second byte
34081   * @return {Boolean}       Whether the 2 bytes are a PAC
34082   */
34083
34084
34085  Cea608Stream.prototype.isPAC = function (char0, char1) {
34086    return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
34087  };
34088  /**
34089   * Detects if a packet's second byte is in the range of a PAC color code
34090   *
34091   * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
34092   * 0x60 to 0x6f.
34093   *
34094   * @param  {Integer} char1 The second byte
34095   * @return {Boolean}       Whether the byte is a color PAC
34096   */
34097
34098
34099  Cea608Stream.prototype.isColorPAC = function (char1) {
34100    return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
34101  };
34102  /**
34103   * Detects if a single byte is in the range of a normal character
34104   *
34105   * Normal text bytes are in the range 0x20 to 0x7f.
34106   *
34107   * @param  {Integer} char  The byte
34108   * @return {Boolean}       Whether the byte is a normal character
34109   */
34110
34111
34112  Cea608Stream.prototype.isNormalChar = function (char) {
34113    return char >= 0x20 && char <= 0x7f;
34114  };
34115  /**
34116   * Configures roll-up
34117   *
34118   * @param  {Integer} pts         Current PTS
34119   * @param  {Integer} newBaseRow  Used by PACs to slide the current window to
34120   *                               a new position
34121   */
34122
34123
34124  Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
34125    // Reset the base row to the bottom row when switching modes
34126    if (this.mode_ !== 'rollUp') {
34127      this.row_ = BOTTOM_ROW;
34128      this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
34129
34130      this.flushDisplayed(pts);
34131      this.nonDisplayed_ = createDisplayBuffer();
34132      this.displayed_ = createDisplayBuffer();
34133    }
34134
34135    if (newBaseRow !== undefined && newBaseRow !== this.row_) {
34136      // move currently displayed captions (up or down) to the new base row
34137      for (var i = 0; i < this.rollUpRows_; i++) {
34138        this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
34139        this.displayed_[this.row_ - i] = '';
34140      }
34141    }
34142
34143    if (newBaseRow === undefined) {
34144      newBaseRow = this.row_;
34145    }
34146
34147    this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
34148  }; // Adds the opening HTML tag for the passed character to the caption text,
34149  // and keeps track of it for later closing
34150
34151
34152  Cea608Stream.prototype.addFormatting = function (pts, format) {
34153    this.formatting_ = this.formatting_.concat(format);
34154    var text = format.reduce(function (text, format) {
vendor: 40,049 bytes, lines 34155-35389
34155      return text + '<' + format + '>';
34156    }, '');
34157    this[this.mode_](pts, text);
34158  }; // Adds HTML closing tags for current formatting to caption text and
34159  // clears remembered formatting
34160
34161
34162  Cea608Stream.prototype.clearFormatting = function (pts) {
34163    if (!this.formatting_.length) {
34164      return;
34165    }
34166
34167    var text = this.formatting_.reverse().reduce(function (text, format) {
34168      return text + '</' + format + '>';
34169    }, '');
34170    this.formatting_ = [];
34171    this[this.mode_](pts, text);
34172  }; // Mode Implementations
34173
34174
34175  Cea608Stream.prototype.popOn = function (pts, text) {
34176    var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
34177
34178    baseRow += text;
34179    this.nonDisplayed_[this.row_] = baseRow;
34180  };
34181
34182  Cea608Stream.prototype.rollUp = function (pts, text) {
34183    var baseRow = this.displayed_[this.row_];
34184    baseRow += text;
34185    this.displayed_[this.row_] = baseRow;
34186  };
34187
34188  Cea608Stream.prototype.shiftRowsUp_ = function () {
34189    var i; // clear out inactive rows
34190
34191    for (i = 0; i < this.topRow_; i++) {
34192      this.displayed_[i] = '';
34193    }
34194
34195    for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
34196      this.displayed_[i] = '';
34197    } // shift displayed rows up
34198
34199
34200    for (i = this.topRow_; i < this.row_; i++) {
34201      this.displayed_[i] = this.displayed_[i + 1];
34202    } // clear out the bottom row
34203
34204
34205    this.displayed_[this.row_] = '';
34206  };
34207
34208  Cea608Stream.prototype.paintOn = function (pts, text) {
34209    var baseRow = this.displayed_[this.row_];
34210    baseRow += text;
34211    this.displayed_[this.row_] = baseRow;
34212  }; // exports
34213
34214
34215  var captionStream = {
34216    CaptionStream: CaptionStream,
34217    Cea608Stream: Cea608Stream
34218  };
34219
34220  var streamTypes = {
34221    H264_STREAM_TYPE: 0x1B,
34222    ADTS_STREAM_TYPE: 0x0F,
34223    METADATA_STREAM_TYPE: 0x15
34224  };
34225
34226  var MAX_TS = 8589934592;
34227  var RO_THRESH = 4294967296;
34228
34229  var handleRollover = function handleRollover(value, reference) {
34230    var direction = 1;
34231
34232    if (value > reference) {
34233      // If the current timestamp value is greater than our reference timestamp and we detect a
34234      // timestamp rollover, this means the roll over is happening in the opposite direction.
34235      // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
34236      // point will be set to a small number, e.g. 1. The user then seeks backwards over the
34237      // rollover point. In loading this segment, the timestamp values will be very large,
34238      // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
34239      // the time stamp to be `value - 2^33`.
34240      direction = -1;
34241    } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
34242    // cause an incorrect adjustment.
34243
34244
34245    while (Math.abs(reference - value) > RO_THRESH) {
34246      value += direction * MAX_TS;
34247    }
34248
34249    return value;
34250  };
34251
34252  var TimestampRolloverStream = function TimestampRolloverStream(type) {
34253    var lastDTS, referenceDTS;
34254    TimestampRolloverStream.prototype.init.call(this);
34255    this.type_ = type;
34256
34257    this.push = function (data) {
34258      if (data.type !== this.type_) {
34259        return;
34260      }
34261
34262      if (referenceDTS === undefined) {
34263        referenceDTS = data.dts;
34264      }
34265
34266      data.dts = handleRollover(data.dts, referenceDTS);
34267      data.pts = handleRollover(data.pts, referenceDTS);
34268      lastDTS = data.dts;
34269      this.trigger('data', data);
34270    };
34271
34272    this.flush = function () {
34273      referenceDTS = lastDTS;
34274      this.trigger('done');
34275    };
34276
34277    this.discontinuity = function () {
34278      referenceDTS = void 0;
34279      lastDTS = void 0;
34280    };
34281  };
34282
34283  TimestampRolloverStream.prototype = new stream();
34284  var timestampRolloverStream = {
34285    TimestampRolloverStream: TimestampRolloverStream,
34286    handleRollover: handleRollover
34287  };
34288
34289  var percentEncode = function percentEncode(bytes, start, end) {
34290    var i,
34291        result = '';
34292
34293    for (i = start; i < end; i++) {
34294      result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
34295    }
34296
34297    return result;
34298  },
34299      // return the string representation of the specified byte range,
34300  // interpreted as UTf-8.
34301  parseUtf8 = function parseUtf8(bytes, start, end) {
34302    return decodeURIComponent(percentEncode(bytes, start, end));
34303  },
34304      // return the string representation of the specified byte range,
34305  // interpreted as ISO-8859-1.
34306  parseIso88591 = function parseIso88591(bytes, start, end) {
34307    return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
34308  },
34309      parseSyncSafeInteger = function parseSyncSafeInteger(data) {
34310    return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
34311  },
34312      tagParsers = {
34313    TXXX: function TXXX(tag) {
34314      var i;
34315
34316      if (tag.data[0] !== 3) {
34317        // ignore frames with unrecognized character encodings
34318        return;
34319      }
34320
34321      for (i = 1; i < tag.data.length; i++) {
34322        if (tag.data[i] === 0) {
34323          // parse the text fields
34324          tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value
34325
34326          tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
34327          break;
34328        }
34329      }
34330
34331      tag.data = tag.value;
34332    },
34333    WXXX: function WXXX(tag) {
34334      var i;
34335
34336      if (tag.data[0] !== 3) {
34337        // ignore frames with unrecognized character encodings
34338        return;
34339      }
34340
34341      for (i = 1; i < tag.data.length; i++) {
34342        if (tag.data[i] === 0) {
34343          // parse the description and URL fields
34344          tag.description = parseUtf8(tag.data, 1, i);
34345          tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
34346          break;
34347        }
34348      }
34349    },
34350    PRIV: function PRIV(tag) {
34351      var i;
34352
34353      for (i = 0; i < tag.data.length; i++) {
34354        if (tag.data[i] === 0) {
34355          // parse the description and URL fields
34356          tag.owner = parseIso88591(tag.data, 0, i);
34357          break;
34358        }
34359      }
34360
34361      tag.privateData = tag.data.subarray(i + 1);
34362      tag.data = tag.privateData;
34363    }
34364  },
34365      _MetadataStream;
34366
34367  _MetadataStream = function MetadataStream(options) {
34368    var settings = {
34369      debug: !!(options && options.debug),
34370      // the bytes of the program-level descriptor field in MP2T
34371      // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
34372      // program element descriptors"
34373      descriptor: options && options.descriptor
34374    },
34375        // the total size in bytes of the ID3 tag being parsed
34376    tagSize = 0,
34377        // tag data that is not complete enough to be parsed
34378    buffer = [],
34379        // the total number of bytes currently in the buffer
34380    bufferSize = 0,
34381        i;
34382
34383    _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
34384    // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
34385
34386
34387    this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
34388
34389    if (settings.descriptor) {
34390      for (i = 0; i < settings.descriptor.length; i++) {
34391        this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
34392      }
34393    }
34394
34395    this.push = function (chunk) {
34396      var tag, frameStart, frameSize, frame, i, frameHeader;
34397
34398      if (chunk.type !== 'timed-metadata') {
34399        return;
34400      } // if data_alignment_indicator is set in the PES header,
34401      // we must have the start of a new ID3 tag. Assume anything
34402      // remaining in the buffer was malformed and throw it out
34403
34404
34405      if (chunk.dataAlignmentIndicator) {
34406        bufferSize = 0;
34407        buffer.length = 0;
34408      } // ignore events that don't look like ID3 data
34409
34410
34411      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))) {
34412        if (settings.debug) {
34413          // eslint-disable-next-line no-console
34414          console.log('Skipping unrecognized metadata packet');
34415        }
34416
34417        return;
34418      } // add this chunk to the data we've collected so far
34419
34420
34421      buffer.push(chunk);
34422      bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
34423
34424      if (buffer.length === 1) {
34425        // the frame size is transmitted as a 28-bit integer in the
34426        // last four bytes of the ID3 header.
34427        // The most significant bit of each byte is dropped and the
34428        // results concatenated to recover the actual value.
34429        tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
34430        // convenient for our comparisons to include it
34431
34432        tagSize += 10;
34433      } // if the entire frame has not arrived, wait for more data
34434
34435
34436      if (bufferSize < tagSize) {
34437        return;
34438      } // collect the entire frame so it can be parsed
34439
34440
34441      tag = {
34442        data: new Uint8Array(tagSize),
34443        frames: [],
34444        pts: buffer[0].pts,
34445        dts: buffer[0].dts
34446      };
34447
34448      for (i = 0; i < tagSize;) {
34449        tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
34450        i += buffer[0].data.byteLength;
34451        bufferSize -= buffer[0].data.byteLength;
34452        buffer.shift();
34453      } // find the start of the first frame and the end of the tag
34454
34455
34456      frameStart = 10;
34457
34458      if (tag.data[5] & 0x40) {
34459        // advance the frame start past the extended header
34460        frameStart += 4; // header size field
34461
34462        frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
34463
34464        tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
34465      } // parse one or more ID3 frames
34466      // http://id3.org/id3v2.3.0#ID3v2_frame_overview
34467
34468
34469      do {
34470        // determine the number of bytes in this frame
34471        frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
34472
34473        if (frameSize < 1) {
34474          // eslint-disable-next-line no-console
34475          return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
34476        }
34477
34478        frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
34479        frame = {
34480          id: frameHeader,
34481          data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
34482        };
34483        frame.key = frame.id;
34484
34485        if (tagParsers[frame.id]) {
34486          tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start
34487          // time for raw AAC data
34488
34489          if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
34490            var d = frame.data,
34491                size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
34492            size *= 4;
34493            size += d[7] & 0x03;
34494            frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
34495            // on the value of this frame
34496            // we couldn't have known the appropriate pts and dts before
34497            // parsing this ID3 tag so set those values now
34498
34499            if (tag.pts === undefined && tag.dts === undefined) {
34500              tag.pts = frame.timeStamp;
34501              tag.dts = frame.timeStamp;
34502            }
34503
34504            this.trigger('timestamp', frame);
34505          }
34506        }
34507
34508        tag.frames.push(frame);
34509        frameStart += 10; // advance past the frame header
34510
34511        frameStart += frameSize; // advance past the frame body
34512      } while (frameStart < tagSize);
34513
34514      this.trigger('data', tag);
34515    };
34516  };
34517
34518  _MetadataStream.prototype = new stream();
34519  var metadataStream = _MetadataStream;
34520
34521  var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types
34522
34523  var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants
34524
34525
34526  var MP2T_PACKET_LENGTH = 188,
34527      // bytes
34528  SYNC_BYTE = 0x47;
34529  /**
34530   * Splits an incoming stream of binary data into MPEG-2 Transport
34531   * Stream packets.
34532   */
34533
34534  _TransportPacketStream = function TransportPacketStream() {
34535    var buffer = new Uint8Array(MP2T_PACKET_LENGTH),
34536        bytesInBuffer = 0;
34537
34538    _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
34539
34540    /**
34541     * Split a stream of data into M2TS packets
34542    **/
34543
34544
34545    this.push = function (bytes) {
34546      var startIndex = 0,
34547          endIndex = MP2T_PACKET_LENGTH,
34548          everything; // If there are bytes remaining from the last segment, prepend them to the
34549      // bytes that were pushed in
34550
34551      if (bytesInBuffer) {
34552        everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
34553        everything.set(buffer.subarray(0, bytesInBuffer));
34554        everything.set(bytes, bytesInBuffer);
34555        bytesInBuffer = 0;
34556      } else {
34557        everything = bytes;
34558      } // While we have enough data for a packet
34559
34560
34561      while (endIndex < everything.byteLength) {
34562        // Look for a pair of start and end sync bytes in the data..
34563        if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
34564          // We found a packet so emit it and jump one whole packet forward in
34565          // the stream
34566          this.trigger('data', everything.subarray(startIndex, endIndex));
34567          startIndex += MP2T_PACKET_LENGTH;
34568          endIndex += MP2T_PACKET_LENGTH;
34569          continue;
34570        } // If we get here, we have somehow become de-synchronized and we need to step
34571        // forward one byte at a time until we find a pair of sync bytes that denote
34572        // a packet
34573
34574
34575        startIndex++;
34576        endIndex++;
34577      } // If there was some data left over at the end of the segment that couldn't
34578      // possibly be a whole packet, keep it because it might be the start of a packet
34579      // that continues in the next segment
34580
34581
34582      if (startIndex < everything.byteLength) {
34583        buffer.set(everything.subarray(startIndex), 0);
34584        bytesInBuffer = everything.byteLength - startIndex;
34585      }
34586    };
34587    /**
34588     * Passes identified M2TS packets to the TransportParseStream to be parsed
34589    **/
34590
34591
34592    this.flush = function () {
34593      // If the buffer contains a whole packet when we are being flushed, emit it
34594      // and empty the buffer. Otherwise hold onto the data because it may be
34595      // important for decoding the next segment
34596      if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
34597        this.trigger('data', buffer);
34598        bytesInBuffer = 0;
34599      }
34600
34601      this.trigger('done');
34602    };
34603  };
34604
34605  _TransportPacketStream.prototype = new stream();
34606  /**
34607   * Accepts an MP2T TransportPacketStream and emits data events with parsed
34608   * forms of the individual transport stream packets.
34609   */
34610
34611  _TransportParseStream = function TransportParseStream() {
34612    var parsePsi, parsePat, parsePmt, self;
34613
34614    _TransportParseStream.prototype.init.call(this);
34615
34616    self = this;
34617    this.packetsWaitingForPmt = [];
34618    this.programMapTable = undefined;
34619
34620    parsePsi = function parsePsi(payload, psi) {
34621      var offset = 0; // PSI packets may be split into multiple sections and those
34622      // sections may be split into multiple packets. If a PSI
34623      // section starts in this packet, the payload_unit_start_indicator
34624      // will be true and the first byte of the payload will indicate
34625      // the offset from the current position to the start of the
34626      // section.
34627
34628      if (psi.payloadUnitStartIndicator) {
34629        offset += payload[offset] + 1;
34630      }
34631
34632      if (psi.type === 'pat') {
34633        parsePat(payload.subarray(offset), psi);
34634      } else {
34635        parsePmt(payload.subarray(offset), psi);
34636      }
34637    };
34638
34639    parsePat = function parsePat(payload, pat) {
34640      pat.section_number = payload[7]; // eslint-disable-line camelcase
34641
34642      pat.last_section_number = payload[8]; // eslint-disable-line camelcase
34643      // skip the PSI header and parse the first PMT entry
34644
34645      self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
34646      pat.pmtPid = self.pmtPid;
34647    };
34648    /**
34649     * Parse out the relevant fields of a Program Map Table (PMT).
34650     * @param payload {Uint8Array} the PMT-specific portion of an MP2T
34651     * packet. The first byte in this array should be the table_id
34652     * field.
34653     * @param pmt {object} the object that should be decorated with
34654     * fields parsed from the PMT.
34655     */
34656
34657
34658    parsePmt = function parsePmt(payload, pmt) {
34659      var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
34660      // take effect. We don't believe this should ever be the case
34661      // for HLS but we'll ignore "forward" PMT declarations if we see
34662      // them. Future PMT declarations have the current_next_indicator
34663      // set to zero.
34664
34665      if (!(payload[5] & 0x01)) {
34666        return;
34667      } // overwrite any existing program map table
34668
34669
34670      self.programMapTable = {
34671        video: null,
34672        audio: null,
34673        'timed-metadata': {}
34674      }; // the mapping table ends at the end of the current section
34675
34676      sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
34677      tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
34678      // long the program info descriptors are
34679
34680      programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
34681
34682      offset = 12 + programInfoLength;
34683
34684      while (offset < tableEnd) {
34685        var streamType = payload[offset];
34686        var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
34687        // TODO: should this be done for metadata too? for now maintain behavior of
34688        //       multiple metadata streams
34689
34690        if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
34691          self.programMapTable.video = pid;
34692        } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
34693          self.programMapTable.audio = pid;
34694        } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
34695          // map pid to stream type for metadata streams
34696          self.programMapTable['timed-metadata'][pid] = streamType;
34697        } // move to the next table entry
34698        // skip past the elementary stream descriptors, if present
34699
34700
34701        offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
34702      } // record the map on the packet as well
34703
34704
34705      pmt.programMapTable = self.programMapTable;
34706    };
34707    /**
34708     * Deliver a new MP2T packet to the next stream in the pipeline.
34709     */
34710
34711
34712    this.push = function (packet) {
34713      var result = {},
34714          offset = 4;
34715      result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
34716
34717      result.pid = packet[1] & 0x1f;
34718      result.pid <<= 8;
34719      result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
34720      // fifth byte of the TS packet header. The adaptation field is
34721      // used to add stuffing to PES packets that don't fill a complete
34722      // TS packet, and to specify some forms of timing and control data
34723      // that we do not currently use.
34724
34725      if ((packet[3] & 0x30) >>> 4 > 0x01) {
34726        offset += packet[offset] + 1;
34727      } // parse the rest of the packet based on the type
34728
34729
34730      if (result.pid === 0) {
34731        result.type = 'pat';
34732        parsePsi(packet.subarray(offset), result);
34733        this.trigger('data', result);
34734      } else if (result.pid === this.pmtPid) {
34735        result.type = 'pmt';
34736        parsePsi(packet.subarray(offset), result);
34737        this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
34738
34739        while (this.packetsWaitingForPmt.length) {
34740          this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
34741        }
34742      } else if (this.programMapTable === undefined) {
34743        // When we have not seen a PMT yet, defer further processing of
34744        // PES packets until one has been parsed
34745        this.packetsWaitingForPmt.push([packet, offset, result]);
34746      } else {
34747        this.processPes_(packet, offset, result);
34748      }
34749    };
34750
34751    this.processPes_ = function (packet, offset, result) {
34752      // set the appropriate stream type
34753      if (result.pid === this.programMapTable.video) {
34754        result.streamType = streamTypes.H264_STREAM_TYPE;
34755      } else if (result.pid === this.programMapTable.audio) {
34756        result.streamType = streamTypes.ADTS_STREAM_TYPE;
34757      } else {
34758        // if not video or audio, it is timed-metadata or unknown
34759        // if unknown, streamType will be undefined
34760        result.streamType = this.programMapTable['timed-metadata'][result.pid];
34761      }
34762
34763      result.type = 'pes';
34764      result.data = packet.subarray(offset);
34765      this.trigger('data', result);
34766    };
34767  };
34768
34769  _TransportParseStream.prototype = new stream();
34770  _TransportParseStream.STREAM_TYPES = {
34771    h264: 0x1b,
34772    adts: 0x0f
34773  };
34774  /**
34775   * Reconsistutes program elementary stream (PES) packets from parsed
34776   * transport stream packets. That is, if you pipe an
34777   * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
34778   * events will be events which capture the bytes for individual PES
34779   * packets plus relevant metadata that has been extracted from the
34780   * container.
34781   */
34782
34783  _ElementaryStream = function ElementaryStream() {
34784    var self = this,
34785        // PES packet fragments
34786    video = {
34787      data: [],
34788      size: 0
34789    },
34790        audio = {
34791      data: [],
34792      size: 0
34793    },
34794        timedMetadata = {
34795      data: [],
34796      size: 0
34797    },
34798        parsePes = function parsePes(payload, pes) {
34799      var ptsDtsFlags; // get the packet length, this will be 0 for video
34800
34801      pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
34802
34803      pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
34804      // and a DTS value. Determine what combination of values is
34805      // available to work with.
34806
34807      ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
34808      // performs all bitwise operations on 32-bit integers but javascript
34809      // supports a much greater range (52-bits) of integer using standard
34810      // mathematical operations.
34811      // We construct a 31-bit value using bitwise operators over the 31
34812      // most significant bits and then multiply by 4 (equal to a left-shift
34813      // of 2) before we add the final 2 least significant bits of the
34814      // timestamp (equal to an OR.)
34815
34816      if (ptsDtsFlags & 0xC0) {
34817        // the PTS and DTS are not written out directly. For information
34818        // on how they are encoded, see
34819        // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
34820        pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
34821        pes.pts *= 4; // Left shift by 2
34822
34823        pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
34824
34825        pes.dts = pes.pts;
34826
34827        if (ptsDtsFlags & 0x40) {
34828          pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
34829          pes.dts *= 4; // Left shift by 2
34830
34831          pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
34832        }
34833      } // the data section starts immediately after the PES header.
34834      // pes_header_data_length specifies the number of header bytes
34835      // that follow the last byte of the field.
34836
34837
34838      pes.data = payload.subarray(9 + payload[8]);
34839    },
34840
34841    /**
34842      * Pass completely parsed PES packets to the next stream in the pipeline
34843     **/
34844    flushStream = function flushStream(stream$$1, type, forceFlush) {
34845      var packetData = new Uint8Array(stream$$1.size),
34846          event = {
34847        type: type
34848      },
34849          i = 0,
34850          offset = 0,
34851          packetFlushable = false,
34852          fragment; // do nothing if there is not enough buffered data for a complete
34853      // PES header
34854
34855      if (!stream$$1.data.length || stream$$1.size < 9) {
34856        return;
34857      }
34858
34859      event.trackId = stream$$1.data[0].pid; // reassemble the packet
34860
34861      for (i = 0; i < stream$$1.data.length; i++) {
34862        fragment = stream$$1.data[i];
34863        packetData.set(fragment.data, offset);
34864        offset += fragment.data.byteLength;
34865      } // parse assembled packet's PES header
34866
34867
34868      parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
34869      // check that there is enough stream data to fill the packet
34870
34871      packetFlushable = type === 'video' || event.packetLength <= stream$$1.size; // flush pending packets if the conditions are right
34872
34873      if (forceFlush || packetFlushable) {
34874        stream$$1.size = 0;
34875        stream$$1.data.length = 0;
34876      } // only emit packets that are complete. this is to avoid assembling
34877      // incomplete PES packets due to poor segmentation
34878
34879
34880      if (packetFlushable) {
34881        self.trigger('data', event);
34882      }
34883    };
34884
34885    _ElementaryStream.prototype.init.call(this);
34886    /**
34887     * Identifies M2TS packet types and parses PES packets using metadata
34888     * parsed from the PMT
34889     **/
34890
34891
34892    this.push = function (data) {
34893      ({
34894        pat: function pat() {// we have to wait for the PMT to arrive as well before we
34895          // have any meaningful metadata
34896        },
34897        pes: function pes() {
34898          var stream$$1, streamType;
34899
34900          switch (data.streamType) {
34901            case streamTypes.H264_STREAM_TYPE:
34902            case streamTypes.H264_STREAM_TYPE:
34903              stream$$1 = video;
34904              streamType = 'video';
34905              break;
34906
34907            case streamTypes.ADTS_STREAM_TYPE:
34908              stream$$1 = audio;
34909              streamType = 'audio';
34910              break;
34911
34912            case streamTypes.METADATA_STREAM_TYPE:
34913              stream$$1 = timedMetadata;
34914              streamType = 'timed-metadata';
34915              break;
34916
34917            default:
34918              // ignore unknown stream types
34919              return;
34920          } // if a new packet is starting, we can flush the completed
34921          // packet
34922
34923
34924          if (data.payloadUnitStartIndicator) {
34925            flushStream(stream$$1, streamType, true);
34926          } // buffer this fragment until we are sure we've received the
34927          // complete payload
34928
34929
34930          stream$$1.data.push(data);
34931          stream$$1.size += data.data.byteLength;
34932        },
34933        pmt: function pmt() {
34934          var event = {
34935            type: 'metadata',
34936            tracks: []
34937          },
34938              programMapTable = data.programMapTable; // translate audio and video streams to tracks
34939
34940          if (programMapTable.video !== null) {
34941            event.tracks.push({
34942              timelineStartInfo: {
34943                baseMediaDecodeTime: 0
34944              },
34945              id: +programMapTable.video,
34946              codec: 'avc',
34947              type: 'video'
34948            });
34949          }
34950
34951          if (programMapTable.audio !== null) {
34952            event.tracks.push({
34953              timelineStartInfo: {
34954                baseMediaDecodeTime: 0
34955              },
34956              id: +programMapTable.audio,
34957              codec: 'adts',
34958              type: 'audio'
34959            });
34960          }
34961
34962          self.trigger('data', event);
34963        }
34964      })[data.type]();
34965    };
34966    /**
34967     * Flush any remaining input. Video PES packets may be of variable
34968     * length. Normally, the start of a new video packet can trigger the
34969     * finalization of the previous packet. That is not possible if no
34970     * more video is forthcoming, however. In that case, some other
34971     * mechanism (like the end of the file) has to be employed. When it is
34972     * clear that no additional data is forthcoming, calling this method
34973     * will flush the buffered packets.
34974     */
34975
34976
34977    this.flush = function () {
34978      // !!THIS ORDER IS IMPORTANT!!
34979      // video first then audio
34980      flushStream(video, 'video');
34981      flushStream(audio, 'audio');
34982      flushStream(timedMetadata, 'timed-metadata');
34983      this.trigger('done');
34984    };
34985  };
34986
34987  _ElementaryStream.prototype = new stream();
34988  var m2ts = {
34989    PAT_PID: 0x0000,
34990    MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
34991    TransportPacketStream: _TransportPacketStream,
34992    TransportParseStream: _TransportParseStream,
34993    ElementaryStream: _ElementaryStream,
34994    TimestampRolloverStream: TimestampRolloverStream$1,
34995    CaptionStream: captionStream.CaptionStream,
34996    Cea608Stream: captionStream.Cea608Stream,
34997    MetadataStream: metadataStream
34998  };
34999
35000  for (var type$1 in streamTypes) {
35001    if (streamTypes.hasOwnProperty(type$1)) {
35002      m2ts[type$1] = streamTypes[type$1];
35003    }
35004  }
35005
35006  var m2ts_1 = m2ts;
35007
35008  var _AdtsStream;
35009
35010  var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
35011  /*
35012   * Accepts a ElementaryStream and emits data events with parsed
35013   * AAC Audio Frames of the individual packets. Input audio in ADTS
35014   * format is unpacked and re-emitted as AAC frames.
35015   *
35016   * @see http://wiki.multimedia.cx/index.php?title=ADTS
35017   * @see http://wiki.multimedia.cx/?title=Understanding_AAC
35018   */
35019
35020  _AdtsStream = function AdtsStream() {
35021    var buffer;
35022
35023    _AdtsStream.prototype.init.call(this);
35024
35025    this.push = function (packet) {
35026      var i = 0,
35027          frameNum = 0,
35028          frameLength,
35029          protectionSkipBytes,
35030          frameEnd,
35031          oldBuffer,
35032          sampleCount,
35033          adtsFrameDuration;
35034
35035      if (packet.type !== 'audio') {
35036        // ignore non-audio data
35037        return;
35038      } // Prepend any data in the buffer to the input data so that we can parse
35039      // aac frames the cross a PES packet boundary
35040
35041
35042      if (buffer) {
35043        oldBuffer = buffer;
35044        buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
35045        buffer.set(oldBuffer);
35046        buffer.set(packet.data, oldBuffer.byteLength);
35047      } else {
35048        buffer = packet.data;
35049      } // unpack any ADTS frames which have been fully received
35050      // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
35051
35052
35053      while (i + 5 < buffer.length) {
35054        // Loook for the start of an ADTS header..
35055        if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
35056          // If a valid header was not found,  jump one forward and attempt to
35057          // find a valid ADTS header starting at the next byte
35058          i++;
35059          continue;
35060        } // The protection skip bit tells us if we have 2 bytes of CRC data at the
35061        // end of the ADTS header
35062
35063
35064        protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
35065        // end of the sync sequence
35066
35067        frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
35068        sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
35069        adtsFrameDuration = sampleCount * 90000 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
35070        frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return
35071        // and wait for more data
35072
35073        if (buffer.byteLength < frameEnd) {
35074          return;
35075        } // Otherwise, deliver the complete AAC frame
35076
35077
35078        this.trigger('data', {
35079          pts: packet.pts + frameNum * adtsFrameDuration,
35080          dts: packet.dts + frameNum * adtsFrameDuration,
35081          sampleCount: sampleCount,
35082          audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
35083          channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
35084          samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
35085          samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
35086          // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
35087          samplesize: 16,
35088          data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
35089        }); // If the buffer is empty, clear it and return
35090
35091        if (buffer.byteLength === frameEnd) {
35092          buffer = undefined;
35093          return;
35094        }
35095
35096        frameNum++; // Remove the finished frame from the buffer and start the process again
35097
35098        buffer = buffer.subarray(frameEnd);
35099      }
35100    };
35101
35102    this.flush = function () {
35103      this.trigger('done');
35104    };
35105  };
35106
35107  _AdtsStream.prototype = new stream();
35108  var adts = _AdtsStream;
35109
35110  var ExpGolomb;
35111  /**
35112   * Parser for exponential Golomb codes, a variable-bitwidth number encoding
35113   * scheme used by h264.
35114   */
35115
35116  ExpGolomb = function ExpGolomb(workingData) {
35117    var // the number of bytes left to examine in workingData
35118    workingBytesAvailable = workingData.byteLength,
35119        // the current word being examined
35120    workingWord = 0,
35121        // :uint
35122    // the number of bits left to examine in the current word
35123    workingBitsAvailable = 0; // :uint;
35124    // ():uint
35125
35126    this.length = function () {
35127      return 8 * workingBytesAvailable;
35128    }; // ():uint
35129
35130
35131    this.bitsAvailable = function () {
35132      return 8 * workingBytesAvailable + workingBitsAvailable;
35133    }; // ():void
35134
35135
35136    this.loadWord = function () {
35137      var position = workingData.byteLength - workingBytesAvailable,
35138          workingBytes = new Uint8Array(4),
35139          availableBytes = Math.min(4, workingBytesAvailable);
35140
35141      if (availableBytes === 0) {
35142        throw new Error('no bytes available');
35143      }
35144
35145      workingBytes.set(workingData.subarray(position, position + availableBytes));
35146      workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
35147
35148      workingBitsAvailable = availableBytes * 8;
35149      workingBytesAvailable -= availableBytes;
35150    }; // (count:int):void
35151
35152
35153    this.skipBits = function (count) {
35154      var skipBytes; // :int
35155
35156      if (workingBitsAvailable > count) {
35157        workingWord <<= count;
35158        workingBitsAvailable -= count;
35159      } else {
35160        count -= workingBitsAvailable;
35161        skipBytes = Math.floor(count / 8);
35162        count -= skipBytes * 8;
35163        workingBytesAvailable -= skipBytes;
35164        this.loadWord();
35165        workingWord <<= count;
35166        workingBitsAvailable -= count;
35167      }
35168    }; // (size:int):uint
35169
35170
35171    this.readBits = function (size) {
35172      var bits = Math.min(workingBitsAvailable, size),
35173          // :uint
35174      valu = workingWord >>> 32 - bits; // :uint
35175      // if size > 31, handle error
35176
35177      workingBitsAvailable -= bits;
35178
35179      if (workingBitsAvailable > 0) {
35180        workingWord <<= bits;
35181      } else if (workingBytesAvailable > 0) {
35182        this.loadWord();
35183      }
35184
35185      bits = size - bits;
35186
35187      if (bits > 0) {
35188        return valu << bits | this.readBits(bits);
35189      }
35190
35191      return valu;
35192    }; // ():uint
35193
35194
35195    this.skipLeadingZeros = function () {
35196      var leadingZeroCount; // :uint
35197
35198      for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
35199        if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
35200          // the first bit of working word is 1
35201          workingWord <<= leadingZeroCount;
35202          workingBitsAvailable -= leadingZeroCount;
35203          return leadingZeroCount;
35204        }
35205      } // we exhausted workingWord and still have not found a 1
35206
35207
35208      this.loadWord();
35209      return leadingZeroCount + this.skipLeadingZeros();
35210    }; // ():void
35211
35212
35213    this.skipUnsignedExpGolomb = function () {
35214      this.skipBits(1 + this.skipLeadingZeros());
35215    }; // ():void
35216
35217
35218    this.skipExpGolomb = function () {
35219      this.skipBits(1 + this.skipLeadingZeros());
35220    }; // ():uint
35221
35222
35223    this.readUnsignedExpGolomb = function () {
35224      var clz = this.skipLeadingZeros(); // :uint
35225
35226      return this.readBits(clz + 1) - 1;
35227    }; // ():int
35228
35229
35230    this.readExpGolomb = function () {
35231      var valu = this.readUnsignedExpGolomb(); // :int
35232
35233      if (0x01 & valu) {
35234        // the number is odd if the low order bit is set
35235        return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
35236      }
35237
35238      return -1 * (valu >>> 1); // divide by two then make it negative
35239    }; // Some convenience functions
35240    // :Boolean
35241
35242
35243    this.readBoolean = function () {
35244      return this.readBits(1) === 1;
35245    }; // ():int
35246
35247
35248    this.readUnsignedByte = function () {
35249      return this.readBits(8);
35250    };
35251
35252    this.loadWord();
35253  };
35254
35255  var expGolomb = ExpGolomb;
35256
35257  var _H264Stream, _NalByteStream;
35258
35259  var PROFILES_WITH_OPTIONAL_SPS_DATA;
35260  /**
35261   * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
35262   */
35263
35264  _NalByteStream = function NalByteStream() {
35265    var syncPoint = 0,
35266        i,
35267        buffer;
35268
35269    _NalByteStream.prototype.init.call(this);
35270    /*
35271     * Scans a byte stream and triggers a data event with the NAL units found.
35272     * @param {Object} data Event received from H264Stream
35273     * @param {Uint8Array} data.data The h264 byte stream to be scanned
35274     *
35275     * @see H264Stream.push
35276     */
35277
35278
35279    this.push = function (data) {
35280      var swapBuffer;
35281
35282      if (!buffer) {
35283        buffer = data.data;
35284      } else {
35285        swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
35286        swapBuffer.set(buffer);
35287        swapBuffer.set(data.data, buffer.byteLength);
35288        buffer = swapBuffer;
35289      } // Rec. ITU-T H.264, Annex B
35290      // scan for NAL unit boundaries
35291      // a match looks like this:
35292      // 0 0 1 .. NAL .. 0 0 1
35293      // ^ sync point        ^ i
35294      // or this:
35295      // 0 0 1 .. NAL .. 0 0 0
35296      // ^ sync point        ^ i
35297      // advance the sync point to a NAL start, if necessary
35298
35299
35300      for (; syncPoint < buffer.byteLength - 3; syncPoint++) {
35301        if (buffer[syncPoint + 2] === 1) {
35302          // the sync point is properly aligned
35303          i = syncPoint + 5;
35304          break;
35305        }
35306      }
35307
35308      while (i < buffer.byteLength) {
35309        // look at the current byte to determine if we've hit the end of
35310        // a NAL unit boundary
35311        switch (buffer[i]) {
35312          case 0:
35313            // skip past non-sync sequences
35314            if (buffer[i - 1] !== 0) {
35315              i += 2;
35316              break;
35317            } else if (buffer[i - 2] !== 0) {
35318              i++;
35319              break;
35320            } // deliver the NAL unit if it isn't empty
35321
35322
35323            if (syncPoint + 3 !== i - 2) {
35324              this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
35325            } // drop trailing zeroes
35326
35327
35328            do {
35329              i++;
35330            } while (buffer[i] !== 1 && i < buffer.length);
35331
35332            syncPoint = i - 2;
35333            i += 3;
35334            break;
35335
35336          case 1:
35337            // skip past non-sync sequences
35338            if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
35339              i += 3;
35340              break;
35341            } // deliver the NAL unit
35342
35343
35344            this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
35345            syncPoint = i - 2;
35346            i += 3;
35347            break;
35348
35349          default:
35350            // the current byte isn't a one or zero, so it cannot be part
35351            // of a sync sequence
35352            i += 3;
35353            break;
35354        }
35355      } // filter out the NAL units that were delivered
35356
35357
35358      buffer = buffer.subarray(syncPoint);
35359      i -= syncPoint;
35360      syncPoint = 0;
35361    };
35362
35363    this.flush = function () {
35364      // deliver the last buffered NAL unit
35365      if (buffer && buffer.byteLength > 3) {
35366        this.trigger('data', buffer.subarray(syncPoint + 3));
35367      } // reset the stream state
35368
35369
35370      buffer = null;
35371      syncPoint = 0;
35372      this.trigger('done');
35373    };
35374  };
35375
35376  _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS
35377  // see Recommendation ITU-T H.264 (4/2013),
35378  // 7.3.2.1.1 Sequence parameter set data syntax
35379
35380  PROFILES_WITH_OPTIONAL_SPS_DATA = {
35381    100: true,
35382    110: true,
35383    122: true,
35384    244: true,
35385    44: true,
35386    83: true,
35387    86: true,
35388    118: true,
35389    128: true,
vendor: 1,727 bytes, lines 35389-35449
35389
35390    138: true,
35391    139: true,
35392    134: true
35393  };
35394  /**
35395   * Accepts input from a ElementaryStream and produces H.264 NAL unit data
35396   * events.
35397   */
35398
35399  _H264Stream = function H264Stream() {
35400    var nalByteStream = new _NalByteStream(),
35401        self,
35402        trackId,
35403        currentPts,
35404        currentDts,
35405        discardEmulationPreventionBytes,
35406        readSequenceParameterSet,
35407        skipScalingList;
35408
35409    _H264Stream.prototype.init.call(this);
35410
35411    self = this;
35412    /*
35413     * Pushes a packet from a stream onto the NalByteStream
35414     *
35415     * @param {Object} packet - A packet received from a stream
35416     * @param {Uint8Array} packet.data - The raw bytes of the packet
35417     * @param {Number} packet.dts - Decode timestamp of the packet
35418     * @param {Number} packet.pts - Presentation timestamp of the packet
35419     * @param {Number} packet.trackId - The id of the h264 track this packet came from
35420     * @param {('video'|'audio')} packet.type - The type of packet
35421     *
35422     */
35423
35424    this.push = function (packet) {
35425      if (packet.type !== 'video') {
35426        return;
35427      }
35428
35429      trackId = packet.trackId;
35430      currentPts = packet.pts;
35431      currentDts = packet.dts;
35432      nalByteStream.push(packet);
35433    };
35434    /*
35435     * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
35436     * for the NALUs to the next stream component.
35437     * Also, preprocess caption and sequence parameter NALUs.
35438     *
35439     * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
35440     * @see NalByteStream.push
35441     */
35442
35443
35444    nalByteStream.on('data', function (data) {
35445      var event = {
35446        trackId: trackId,
35447        pts: currentPts,
35448        dts: currentDts,
35449        data: data
vendor: 10,467 bytes, lines 35449-35795
35449
35450      };
35451
35452      switch (data[0] & 0x1f) {
35453        case 0x05:
35454          event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
35455          break;
35456
35457        case 0x06:
35458          event.nalUnitType = 'sei_rbsp';
35459          event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
35460          break;
35461
35462        case 0x07:
35463          event.nalUnitType = 'seq_parameter_set_rbsp';
35464          event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
35465          event.config = readSequenceParameterSet(event.escapedRBSP);
35466          break;
35467
35468        case 0x08:
35469          event.nalUnitType = 'pic_parameter_set_rbsp';
35470          break;
35471
35472        case 0x09:
35473          event.nalUnitType = 'access_unit_delimiter_rbsp';
35474          break;
35475
35476        default:
35477          break;
35478      } // This triggers data on the H264Stream
35479
35480
35481      self.trigger('data', event);
35482    });
35483    nalByteStream.on('done', function () {
35484      self.trigger('done');
35485    });
35486
35487    this.flush = function () {
35488      nalByteStream.flush();
35489    };
35490    /**
35491     * Advance the ExpGolomb decoder past a scaling list. The scaling
35492     * list is optionally transmitted as part of a sequence parameter
35493     * set and is not relevant to transmuxing.
35494     * @param count {number} the number of entries in this scaling list
35495     * @param expGolombDecoder {object} an ExpGolomb pointed to the
35496     * start of a scaling list
35497     * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
35498     */
35499
35500
35501    skipScalingList = function skipScalingList(count, expGolombDecoder) {
35502      var lastScale = 8,
35503          nextScale = 8,
35504          j,
35505          deltaScale;
35506
35507      for (j = 0; j < count; j++) {
35508        if (nextScale !== 0) {
35509          deltaScale = expGolombDecoder.readExpGolomb();
35510          nextScale = (lastScale + deltaScale + 256) % 256;
35511        }
35512
35513        lastScale = nextScale === 0 ? lastScale : nextScale;
35514      }
35515    };
35516    /**
35517     * Expunge any "Emulation Prevention" bytes from a "Raw Byte
35518     * Sequence Payload"
35519     * @param data {Uint8Array} the bytes of a RBSP from a NAL
35520     * unit
35521     * @return {Uint8Array} the RBSP without any Emulation
35522     * Prevention Bytes
35523     */
35524
35525
35526    discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
35527      var length = data.byteLength,
35528          emulationPreventionBytesPositions = [],
35529          i = 1,
35530          newLength,
35531          newData; // Find all `Emulation Prevention Bytes`
35532
35533      while (i < length - 2) {
35534        if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
35535          emulationPreventionBytesPositions.push(i + 2);
35536          i += 2;
35537        } else {
35538          i++;
35539        }
35540      } // If no Emulation Prevention Bytes were found just return the original
35541      // array
35542
35543
35544      if (emulationPreventionBytesPositions.length === 0) {
35545        return data;
35546      } // Create a new array to hold the NAL unit data
35547
35548
35549      newLength = length - emulationPreventionBytesPositions.length;
35550      newData = new Uint8Array(newLength);
35551      var sourceIndex = 0;
35552
35553      for (i = 0; i < newLength; sourceIndex++, i++) {
35554        if (sourceIndex === emulationPreventionBytesPositions[0]) {
35555          // Skip this byte
35556          sourceIndex++; // Remove this position index
35557
35558          emulationPreventionBytesPositions.shift();
35559        }
35560
35561        newData[i] = data[sourceIndex];
35562      }
35563
35564      return newData;
35565    };
35566    /**
35567     * Read a sequence parameter set and return some interesting video
35568     * properties. A sequence parameter set is the H264 metadata that
35569     * describes the properties of upcoming video frames.
35570     * @param data {Uint8Array} the bytes of a sequence parameter set
35571     * @return {object} an object with configuration parsed from the
35572     * sequence parameter set, including the dimensions of the
35573     * associated video frames.
35574     */
35575
35576
35577    readSequenceParameterSet = function readSequenceParameterSet(data) {
35578      var frameCropLeftOffset = 0,
35579          frameCropRightOffset = 0,
35580          frameCropTopOffset = 0,
35581          frameCropBottomOffset = 0,
35582          sarScale = 1,
35583          expGolombDecoder,
35584          profileIdc,
35585          levelIdc,
35586          profileCompatibility,
35587          chromaFormatIdc,
35588          picOrderCntType,
35589          numRefFramesInPicOrderCntCycle,
35590          picWidthInMbsMinus1,
35591          picHeightInMapUnitsMinus1,
35592          frameMbsOnlyFlag,
35593          scalingListCount,
35594          sarRatio,
35595          aspectRatioIdc,
35596          i;
35597      expGolombDecoder = new expGolomb(data);
35598      profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
35599
35600      profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
35601
35602      levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
35603
35604      expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
35605      // some profiles have more optional data we don't need
35606
35607      if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
35608        chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
35609
35610        if (chromaFormatIdc === 3) {
35611          expGolombDecoder.skipBits(1); // separate_colour_plane_flag
35612        }
35613
35614        expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
35615
35616        expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
35617
35618        expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
35619
35620        if (expGolombDecoder.readBoolean()) {
35621          // seq_scaling_matrix_present_flag
35622          scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
35623
35624          for (i = 0; i < scalingListCount; i++) {
35625            if (expGolombDecoder.readBoolean()) {
35626              // seq_scaling_list_present_flag[ i ]
35627              if (i < 6) {
35628                skipScalingList(16, expGolombDecoder);
35629              } else {
35630                skipScalingList(64, expGolombDecoder);
35631              }
35632            }
35633          }
35634        }
35635      }
35636
35637      expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
35638
35639      picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
35640
35641      if (picOrderCntType === 0) {
35642        expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
35643      } else if (picOrderCntType === 1) {
35644        expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
35645
35646        expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
35647
35648        expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
35649
35650        numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
35651
35652        for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
35653          expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
35654        }
35655      }
35656
35657      expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
35658
35659      expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
35660
35661      picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
35662      picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
35663      frameMbsOnlyFlag = expGolombDecoder.readBits(1);
35664
35665      if (frameMbsOnlyFlag === 0) {
35666        expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
35667      }
35668
35669      expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
35670
35671      if (expGolombDecoder.readBoolean()) {
35672        // frame_cropping_flag
35673        frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
35674        frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
35675        frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
35676        frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
35677      }
35678
35679      if (expGolombDecoder.readBoolean()) {
35680        // vui_parameters_present_flag
35681        if (expGolombDecoder.readBoolean()) {
35682          // aspect_ratio_info_present_flag
35683          aspectRatioIdc = expGolombDecoder.readUnsignedByte();
35684
35685          switch (aspectRatioIdc) {
35686            case 1:
35687              sarRatio = [1, 1];
35688              break;
35689
35690            case 2:
35691              sarRatio = [12, 11];
35692              break;
35693
35694            case 3:
35695              sarRatio = [10, 11];
35696              break;
35697
35698            case 4:
35699              sarRatio = [16, 11];
35700              break;
35701
35702            case 5:
35703              sarRatio = [40, 33];
35704              break;
35705
35706            case 6:
35707              sarRatio = [24, 11];
35708              break;
35709
35710            case 7:
35711              sarRatio = [20, 11];
35712              break;
35713
35714            case 8:
35715              sarRatio = [32, 11];
35716              break;
35717
35718            case 9:
35719              sarRatio = [80, 33];
35720              break;
35721
35722            case 10:
35723              sarRatio = [18, 11];
35724              break;
35725
35726            case 11:
35727              sarRatio = [15, 11];
35728              break;
35729
35730            case 12:
35731              sarRatio = [64, 33];
35732              break;
35733
35734            case 13:
35735              sarRatio = [160, 99];
35736              break;
35737
35738            case 14:
35739              sarRatio = [4, 3];
35740              break;
35741
35742            case 15:
35743              sarRatio = [3, 2];
35744              break;
35745
35746            case 16:
35747              sarRatio = [2, 1];
35748              break;
35749
35750            case 255:
35751              {
35752                sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
35753                break;
35754              }
35755          }
35756
35757          if (sarRatio) {
35758            sarScale = sarRatio[0] / sarRatio[1];
35759          }
35760        }
35761      }
35762
35763      return {
35764        profileIdc: profileIdc,
35765        levelIdc: levelIdc,
35766        profileCompatibility: profileCompatibility,
35767        width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
35768        height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2
35769      };
35770    };
35771  };
35772
35773  _H264Stream.prototype = new stream();
35774  var h264 = {
35775    H264Stream: _H264Stream,
35776    NalByteStream: _NalByteStream
35777  };
35778
35779  /**
35780   * mux.js
35781   *
35782   * Copyright (c) 2016 Brightcove
35783   * All rights reserved.
35784   *
35785   * Utilities to detect basic properties and metadata about Aac data.
35786   */
35787
35788  var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
35789
35790  var isLikelyAacData = function isLikelyAacData(data) {
35791    if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) {
35792      return true;
35793    }
35794
35795    return false;
vendor: 6,657 bytes, lines 35796-36002
35796  };
35797
35798  var parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) {
35799    return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
35800  }; // return a percent-encoded representation of the specified byte range
35801  // @see http://en.wikipedia.org/wiki/Percent-encoding
35802
35803
35804  var percentEncode$1 = function percentEncode(bytes, start, end) {
35805    var i,
35806        result = '';
35807
35808    for (i = start; i < end; i++) {
35809      result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
35810    }
35811
35812    return result;
35813  }; // return the string representation of the specified byte range,
35814  // interpreted as ISO-8859-1.
35815
35816
35817  var parseIso88591$1 = function parseIso88591(bytes, start, end) {
35818    return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
35819  };
35820
35821  var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
35822    var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
35823        flags = header[byteIndex + 5],
35824        footerPresent = (flags & 16) >> 4;
35825
35826    if (footerPresent) {
35827      return returnSize + 20;
35828    }
35829
35830    return returnSize + 10;
35831  };
35832
35833  var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
35834    var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
35835        middle = header[byteIndex + 4] << 3,
35836        highTwo = header[byteIndex + 3] & 0x3 << 11;
35837    return highTwo | middle | lowThree;
35838  };
35839
35840  var parseType$1 = function parseType(header, byteIndex) {
35841    if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
35842      return 'timed-metadata';
35843    } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
35844      return 'audio';
35845    }
35846
35847    return null;
35848  };
35849
35850  var parseSampleRate = function parseSampleRate(packet) {
35851    var i = 0;
35852
35853    while (i + 5 < packet.length) {
35854      if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
35855        // If a valid header was not found,  jump one forward and attempt to
35856        // find a valid ADTS header starting at the next byte
35857        i++;
35858        continue;
35859      }
35860
35861      return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2];
35862    }
35863
35864    return null;
35865  };
35866
35867  var parseAacTimestamp = function parseAacTimestamp(packet) {
35868    var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
35869
35870    frameStart = 10;
35871
35872    if (packet[5] & 0x40) {
35873      // advance the frame start past the extended header
35874      frameStart += 4; // header size field
35875
35876      frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14));
35877    } // parse one or more ID3 frames
35878    // http://id3.org/id3v2.3.0#ID3v2_frame_overview
35879
35880
35881    do {
35882      // determine the number of bytes in this frame
35883      frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8));
35884
35885      if (frameSize < 1) {
35886        return null;
35887      }
35888
35889      frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
35890
35891      if (frameHeader === 'PRIV') {
35892        frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
35893
35894        for (var i = 0; i < frame.byteLength; i++) {
35895          if (frame[i] === 0) {
35896            var owner = parseIso88591$1(frame, 0, i);
35897
35898            if (owner === 'com.apple.streaming.transportStreamTimestamp') {
35899              var d = frame.subarray(i + 1);
35900              var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
35901              size *= 4;
35902              size += d[7] & 0x03;
35903              return size;
35904            }
35905
35906            break;
35907          }
35908        }
35909      }
35910
35911      frameStart += 10; // advance past the frame header
35912
35913      frameStart += frameSize; // advance past the frame body
35914    } while (frameStart < packet.byteLength);
35915
35916    return null;
35917  };
35918
35919  var utils = {
35920    isLikelyAacData: isLikelyAacData,
35921    parseId3TagSize: parseId3TagSize,
35922    parseAdtsSize: parseAdtsSize,
35923    parseType: parseType$1,
35924    parseSampleRate: parseSampleRate,
35925    parseAacTimestamp: parseAacTimestamp
35926  };
35927
35928  var _AacStream;
35929  /**
35930   * Splits an incoming stream of binary data into ADTS and ID3 Frames.
35931   */
35932
35933
35934  _AacStream = function AacStream() {
35935    var everything = new Uint8Array(),
35936        timeStamp = 0;
35937
35938    _AacStream.prototype.init.call(this);
35939
35940    this.setTimestamp = function (timestamp) {
35941      timeStamp = timestamp;
35942    };
35943
35944    this.push = function (bytes) {
35945      var frameSize = 0,
35946          byteIndex = 0,
35947          bytesLeft,
35948          chunk,
35949          packet,
35950          tempLength; // If there are bytes remaining from the last segment, prepend them to the
35951      // bytes that were pushed in
35952
35953      if (everything.length) {
35954        tempLength = everything.length;
35955        everything = new Uint8Array(bytes.byteLength + tempLength);
35956        everything.set(everything.subarray(0, tempLength));
35957        everything.set(bytes, tempLength);
35958      } else {
35959        everything = bytes;
35960      }
35961
35962      while (everything.length - byteIndex >= 3) {
35963        if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
35964          // Exit early because we don't have enough to parse
35965          // the ID3 tag header
35966          if (everything.length - byteIndex < 10) {
35967            break;
35968          } // check framesize
35969
35970
35971          frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
35972          // to emit a full packet
35973          // Add to byteIndex to support multiple ID3 tags in sequence
35974
35975          if (byteIndex + frameSize > everything.length) {
35976            break;
35977          }
35978
35979          chunk = {
35980            type: 'timed-metadata',
35981            data: everything.subarray(byteIndex, byteIndex + frameSize)
35982          };
35983          this.trigger('data', chunk);
35984          byteIndex += frameSize;
35985          continue;
35986        } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
35987          // Exit early because we don't have enough to parse
35988          // the ADTS frame header
35989          if (everything.length - byteIndex < 7) {
35990            break;
35991          }
35992
35993          frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
35994          // to emit a full packet
35995
35996          if (byteIndex + frameSize > everything.length) {
35997            break;
35998          }
35999
36000          packet = {
36001            type: 'audio',
36002            data: everything.subarray(byteIndex, byteIndex + frameSize),
vendor: 5,973 bytes, lines 36003-36176
36003            pts: timeStamp,
36004            dts: timeStamp
36005          };
36006          this.trigger('data', packet);
36007          byteIndex += frameSize;
36008          continue;
36009        }
36010
36011        byteIndex++;
36012      }
36013
36014      bytesLeft = everything.length - byteIndex;
36015
36016      if (bytesLeft > 0) {
36017        everything = everything.subarray(byteIndex);
36018      } else {
36019        everything = new Uint8Array();
36020      }
36021    };
36022  };
36023
36024  _AacStream.prototype = new stream();
36025  var aac = _AacStream;
36026
36027  var H264Stream = h264.H264Stream;
36028  var isLikelyAacData$1 = utils.isLikelyAacData; // constants
36029
36030  var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
36031  var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility']; // object types
36032
36033  var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream;
36034  /**
36035   * Compare two arrays (even typed) for same-ness
36036   */
36037
36038
36039  var arrayEquals = function arrayEquals(a, b) {
36040    var i;
36041
36042    if (a.length !== b.length) {
36043      return false;
36044    } // compare the value of each element in the array
36045
36046
36047    for (i = 0; i < a.length; i++) {
36048      if (a[i] !== b[i]) {
36049        return false;
36050      }
36051    }
36052
36053    return true;
36054  };
36055
36056  var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
36057    var ptsOffsetFromDts = startPts - startDts,
36058        decodeDuration = endDts - startDts,
36059        presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
36060    // however, the player time values will reflect a start from the baseMediaDecodeTime.
36061    // In order to provide relevant values for the player times, base timing info on the
36062    // baseMediaDecodeTime and the DTS and PTS durations of the segment.
36063
36064    return {
36065      start: {
36066        dts: baseMediaDecodeTime,
36067        pts: baseMediaDecodeTime + ptsOffsetFromDts
36068      },
36069      end: {
36070        dts: baseMediaDecodeTime + decodeDuration,
36071        pts: baseMediaDecodeTime + presentationDuration
36072      },
36073      prependedContentDuration: prependedContentDuration,
36074      baseMediaDecodeTime: baseMediaDecodeTime
36075    };
36076  };
36077  /**
36078   * Constructs a single-track, ISO BMFF media segment from AAC data
36079   * events. The output of this stream can be fed to a SourceBuffer
36080   * configured with a suitable initialization segment.
36081   * @param track {object} track metadata configuration
36082   * @param options {object} transmuxer options object
36083   * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
36084   *        in the source; false to adjust the first segment to start at 0.
36085   */
36086
36087
36088  _AudioSegmentStream = function AudioSegmentStream(track, options) {
36089    var adtsFrames = [],
36090        sequenceNumber = 0,
36091        earliestAllowedDts = 0,
36092        audioAppendStartTs = 0,
36093        videoBaseMediaDecodeTime = Infinity;
36094    options = options || {};
36095
36096    _AudioSegmentStream.prototype.init.call(this);
36097
36098    this.push = function (data) {
36099      trackDecodeInfo.collectDtsInfo(track, data);
36100
36101      if (track) {
36102        AUDIO_PROPERTIES.forEach(function (prop) {
36103          track[prop] = data[prop];
36104        });
36105      } // buffer audio data until end() is called
36106
36107
36108      adtsFrames.push(data);
36109    };
36110
36111    this.setEarliestDts = function (earliestDts) {
36112      earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime;
36113    };
36114
36115    this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
36116      videoBaseMediaDecodeTime = baseMediaDecodeTime;
36117    };
36118
36119    this.setAudioAppendStart = function (timestamp) {
36120      audioAppendStartTs = timestamp;
36121    };
36122
36123    this.flush = function () {
36124      var frames, moof, mdat, boxes; // return early if no audio data has been observed
36125
36126      if (adtsFrames.length === 0) {
36127        this.trigger('done', 'AudioSegmentStream');
36128        return;
36129      }
36130
36131      frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
36132      track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
36133      audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
36134      // samples (that is, adts frames) in the audio data
36135
36136      track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
36137
36138      mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
36139      adtsFrames = [];
36140      moof = mp4Generator.moof(sequenceNumber, [track]);
36141      boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
36142
36143      sequenceNumber++;
36144      boxes.set(moof);
36145      boxes.set(mdat, moof.byteLength);
36146      trackDecodeInfo.clearDtsInfo(track);
36147      this.trigger('data', {
36148        track: track,
36149        boxes: boxes
36150      });
36151      this.trigger('done', 'AudioSegmentStream');
36152    };
36153  };
36154
36155  _AudioSegmentStream.prototype = new stream();
36156  /**
36157   * Constructs a single-track, ISO BMFF media segment from H264 data
36158   * events. The output of this stream can be fed to a SourceBuffer
36159   * configured with a suitable initialization segment.
36160   * @param track {object} track metadata configuration
36161   * @param options {object} transmuxer options object
36162   * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
36163   *        gopsToAlignWith list when attempting to align gop pts
36164   * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
36165   *        in the source; false to adjust the first segment to start at 0.
36166   */
36167
36168  _VideoSegmentStream = function VideoSegmentStream(track, options) {
36169    var sequenceNumber = 0,
36170        nalUnits = [],
36171        gopsToAlignWith = [],
36172        config,
36173        pps;
36174    options = options || {};
36175
36176    _VideoSegmentStream.prototype.init.call(this);
vendor: 5,323 bytes, lines 36177-36324
36177
36178    delete track.minPTS;
36179    this.gopCache_ = [];
36180    /**
36181      * Constructs a ISO BMFF segment given H264 nalUnits
36182      * @param {Object} nalUnit A data event representing a nalUnit
36183      * @param {String} nalUnit.nalUnitType
36184      * @param {Object} nalUnit.config Properties for a mp4 track
36185      * @param {Uint8Array} nalUnit.data The nalUnit bytes
36186      * @see lib/codecs/h264.js
36187     **/
36188
36189    this.push = function (nalUnit) {
36190      trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
36191
36192      if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
36193        config = nalUnit.config;
36194        track.sps = [nalUnit.data];
36195        VIDEO_PROPERTIES.forEach(function (prop) {
36196          track[prop] = config[prop];
36197        }, this);
36198      }
36199
36200      if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
36201        pps = nalUnit.data;
36202        track.pps = [nalUnit.data];
36203      } // buffer video until flush() is called
36204
36205
36206      nalUnits.push(nalUnit);
36207    };
36208    /**
36209      * Pass constructed ISO BMFF track and boxes on to the
36210      * next stream in the pipeline
36211     **/
36212
36213
36214    this.flush = function () {
36215      var frames,
36216          gopForFusion,
36217          gops,
36218          moof,
36219          mdat,
36220          boxes,
36221          prependedContentDuration = 0,
36222          firstGop,
36223          lastGop; // Throw away nalUnits at the start of the byte stream until
36224      // we find the first AUD
36225
36226      while (nalUnits.length) {
36227        if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
36228          break;
36229        }
36230
36231        nalUnits.shift();
36232      } // Return early if no video data has been observed
36233
36234
36235      if (nalUnits.length === 0) {
36236        this.resetStream_();
36237        this.trigger('done', 'VideoSegmentStream');
36238        return;
36239      } // Organize the raw nal-units into arrays that represent
36240      // higher-level constructs such as frames and gops
36241      // (group-of-pictures)
36242
36243
36244      frames = frameUtils.groupNalsIntoFrames(nalUnits);
36245      gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
36246      // a problem since MSE (on Chrome) requires a leading keyframe.
36247      //
36248      // We have two approaches to repairing this situation:
36249      // 1) GOP-FUSION:
36250      //    This is where we keep track of the GOPS (group-of-pictures)
36251      //    from previous fragments and attempt to find one that we can
36252      //    prepend to the current fragment in order to create a valid
36253      //    fragment.
36254      // 2) KEYFRAME-PULLING:
36255      //    Here we search for the first keyframe in the fragment and
36256      //    throw away all the frames between the start of the fragment
36257      //    and that keyframe. We then extend the duration and pull the
36258      //    PTS of the keyframe forward so that it covers the time range
36259      //    of the frames that were disposed of.
36260      //
36261      // #1 is far prefereable over #2 which can cause "stuttering" but
36262      // requires more things to be just right.
36263
36264      if (!gops[0][0].keyFrame) {
36265        // Search for a gop for fusion from our gopCache
36266        gopForFusion = this.getGopForFusion_(nalUnits[0], track);
36267
36268        if (gopForFusion) {
36269          // in order to provide more accurate timing information about the segment, save
36270          // the number of seconds prepended to the original segment due to GOP fusion
36271          prependedContentDuration = gopForFusion.duration;
36272          gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
36273          // new gop at the beginning
36274
36275          gops.byteLength += gopForFusion.byteLength;
36276          gops.nalCount += gopForFusion.nalCount;
36277          gops.pts = gopForFusion.pts;
36278          gops.dts = gopForFusion.dts;
36279          gops.duration += gopForFusion.duration;
36280        } else {
36281          // If we didn't find a candidate gop fall back to keyframe-pulling
36282          gops = frameUtils.extendFirstKeyFrame(gops);
36283        }
36284      } // Trim gops to align with gopsToAlignWith
36285
36286
36287      if (gopsToAlignWith.length) {
36288        var alignedGops;
36289
36290        if (options.alignGopsAtEnd) {
36291          alignedGops = this.alignGopsAtEnd_(gops);
36292        } else {
36293          alignedGops = this.alignGopsAtStart_(gops);
36294        }
36295
36296        if (!alignedGops) {
36297          // save all the nals in the last GOP into the gop cache
36298          this.gopCache_.unshift({
36299            gop: gops.pop(),
36300            pps: track.pps,
36301            sps: track.sps
36302          }); // Keep a maximum of 6 GOPs in the cache
36303
36304          this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
36305
36306          nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
36307
36308          this.resetStream_();
36309          this.trigger('done', 'VideoSegmentStream');
36310          return;
36311        } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
36312        // when recalculated before sending off to CoalesceStream
36313
36314
36315        trackDecodeInfo.clearDtsInfo(track);
36316        gops = alignedGops;
36317      }
36318
36319      trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
36320      // samples (that is, frames) in the video data
36321
36322      track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
36323
36324      mdat = mp4
vendor: 5,670 bytes, lines 36324-36479
36324Generator.mdat(frameUtils.concatenateNalData(gops));
36325      track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
36326      this.trigger('processedGopsInfo', gops.map(function (gop) {
36327        return {
36328          pts: gop.pts,
36329          dts: gop.dts,
36330          byteLength: gop.byteLength
36331        };
36332      }));
36333      firstGop = gops[0];
36334      lastGop = gops[gops.length - 1];
36335      this.trigger('segmentTimingInfo', generateVideoSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration)); // save all the nals in the last GOP into the gop cache
36336
36337      this.gopCache_.unshift({
36338        gop: gops.pop(),
36339        pps: track.pps,
36340        sps: track.sps
36341      }); // Keep a maximum of 6 GOPs in the cache
36342
36343      this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
36344
36345      nalUnits = [];
36346      this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
36347      this.trigger('timelineStartInfo', track.timelineStartInfo);
36348      moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
36349      // throwing away hundreds of media segment fragments
36350
36351      boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
36352
36353      sequenceNumber++;
36354      boxes.set(moof);
36355      boxes.set(mdat, moof.byteLength);
36356      this.trigger('data', {
36357        track: track,
36358        boxes: boxes
36359      });
36360      this.resetStream_(); // Continue with the flush process now
36361
36362      this.trigger('done', 'VideoSegmentStream');
36363    };
36364
36365    this.resetStream_ = function () {
36366      trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
36367      // for instance, when we are rendition switching
36368
36369      config = undefined;
36370      pps = undefined;
36371    }; // Search for a candidate Gop for gop-fusion from the gop cache and
36372    // return it or return null if no good candidate was found
36373
36374
36375    this.getGopForFusion_ = function (nalUnit) {
36376      var halfSecond = 45000,
36377          // Half-a-second in a 90khz clock
36378      allowableOverlap = 10000,
36379          // About 3 frames @ 30fps
36380      nearestDistance = Infinity,
36381          dtsDistance,
36382          nearestGopObj,
36383          currentGop,
36384          currentGopObj,
36385          i; // Search for the GOP nearest to the beginning of this nal unit
36386
36387      for (i = 0; i < this.gopCache_.length; i++) {
36388        currentGopObj = this.gopCache_[i];
36389        currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
36390
36391        if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
36392          continue;
36393        } // Reject Gops that would require a negative baseMediaDecodeTime
36394
36395
36396        if (currentGop.dts < track.timelineStartInfo.dts) {
36397          continue;
36398        } // The distance between the end of the gop and the start of the nalUnit
36399
36400
36401        dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
36402        // a half-second of the nal unit
36403
36404        if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
36405          // Always use the closest GOP we found if there is more than
36406          // one candidate
36407          if (!nearestGopObj || nearestDistance > dtsDistance) {
36408            nearestGopObj = currentGopObj;
36409            nearestDistance = dtsDistance;
36410          }
36411        }
36412      }
36413
36414      if (nearestGopObj) {
36415        return nearestGopObj.gop;
36416      }
36417
36418      return null;
36419    }; // trim gop list to the first gop found that has a matching pts with a gop in the list
36420    // of gopsToAlignWith starting from the START of the list
36421
36422
36423    this.alignGopsAtStart_ = function (gops) {
36424      var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
36425      byteLength = gops.byteLength;
36426      nalCount = gops.nalCount;
36427      duration = gops.duration;
36428      alignIndex = gopIndex = 0;
36429
36430      while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
36431        align = gopsToAlignWith[alignIndex];
36432        gop = gops[gopIndex];
36433
36434        if (align.pts === gop.pts) {
36435          break;
36436        }
36437
36438        if (gop.pts > align.pts) {
36439          // this current gop starts after the current gop we want to align on, so increment
36440          // align index
36441          alignIndex++;
36442          continue;
36443        } // current gop starts before the current gop we want to align on. so increment gop
36444        // index
36445
36446
36447        gopIndex++;
36448        byteLength -= gop.byteLength;
36449        nalCount -= gop.nalCount;
36450        duration -= gop.duration;
36451      }
36452
36453      if (gopIndex === 0) {
36454        // no gops to trim
36455        return gops;
36456      }
36457
36458      if (gopIndex === gops.length) {
36459        // all gops trimmed, skip appending all gops
36460        return null;
36461      }
36462
36463      alignedGops = gops.slice(gopIndex);
36464      alignedGops.byteLength = byteLength;
36465      alignedGops.duration = duration;
36466      alignedGops.nalCount = nalCount;
36467      alignedGops.pts = alignedGops[0].pts;
36468      alignedGops.dts = alignedGops[0].dts;
36469      return alignedGops;
36470    }; // trim gop list to the first gop found that has a matching pts with a gop in the list
36471    // of gopsToAlignWith starting from the END of the list
36472
36473
36474    this.alignGopsAtEnd_ = function (gops) {
36475      var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
36476      alignIndex = gopsToAlignWith.length - 1;
36477      gopIndex = gops.length - 1;
36478      alignEndIndex = null;
36479      matchFound = false;
vendor: 42,240 bytes, lines 36480-37758
36480
36481      while (alignIndex >= 0 && gopIndex >= 0) {
36482        align = gopsToAlignWith[alignIndex];
36483        gop = gops[gopIndex];
36484
36485        if (align.pts === gop.pts) {
36486          matchFound = true;
36487          break;
36488        }
36489
36490        if (align.pts > gop.pts) {
36491          alignIndex--;
36492          continue;
36493        }
36494
36495        if (alignIndex === gopsToAlignWith.length - 1) {
36496          // gop.pts is greater than the last alignment candidate. If no match is found
36497          // by the end of this loop, we still want to append gops that come after this
36498          // point
36499          alignEndIndex = gopIndex;
36500        }
36501
36502        gopIndex--;
36503      }
36504
36505      if (!matchFound && alignEndIndex === null) {
36506        return null;
36507      }
36508
36509      var trimIndex;
36510
36511      if (matchFound) {
36512        trimIndex = gopIndex;
36513      } else {
36514        trimIndex = alignEndIndex;
36515      }
36516
36517      if (trimIndex === 0) {
36518        return gops;
36519      }
36520
36521      var alignedGops = gops.slice(trimIndex);
36522      var metadata = alignedGops.reduce(function (total, gop) {
36523        total.byteLength += gop.byteLength;
36524        total.duration += gop.duration;
36525        total.nalCount += gop.nalCount;
36526        return total;
36527      }, {
36528        byteLength: 0,
36529        duration: 0,
36530        nalCount: 0
36531      });
36532      alignedGops.byteLength = metadata.byteLength;
36533      alignedGops.duration = metadata.duration;
36534      alignedGops.nalCount = metadata.nalCount;
36535      alignedGops.pts = alignedGops[0].pts;
36536      alignedGops.dts = alignedGops[0].dts;
36537      return alignedGops;
36538    };
36539
36540    this.alignGopsWith = function (newGopsToAlignWith) {
36541      gopsToAlignWith = newGopsToAlignWith;
36542    };
36543  };
36544
36545  _VideoSegmentStream.prototype = new stream();
36546  /**
36547   * A Stream that can combine multiple streams (ie. audio & video)
36548   * into a single output segment for MSE. Also supports audio-only
36549   * and video-only streams.
36550   * @param options {object} transmuxer options object
36551   * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
36552   *        in the source; false to adjust the first segment to start at media timeline start.
36553   */
36554
36555  _CoalesceStream = function CoalesceStream(options, metadataStream) {
36556    // Number of Tracks per output segment
36557    // If greater than 1, we combine multiple
36558    // tracks into a single segment
36559    this.numberOfTracks = 0;
36560    this.metadataStream = metadataStream;
36561    options = options || {};
36562
36563    if (typeof options.remux !== 'undefined') {
36564      this.remuxTracks = !!options.remux;
36565    } else {
36566      this.remuxTracks = true;
36567    }
36568
36569    if (typeof options.keepOriginalTimestamps === 'boolean') {
36570      this.keepOriginalTimestamps = options.keepOriginalTimestamps;
36571    }
36572
36573    this.pendingTracks = [];
36574    this.videoTrack = null;
36575    this.pendingBoxes = [];
36576    this.pendingCaptions = [];
36577    this.pendingMetadata = [];
36578    this.pendingBytes = 0;
36579    this.emittedTracks = 0;
36580
36581    _CoalesceStream.prototype.init.call(this); // Take output from multiple
36582
36583
36584    this.push = function (output) {
36585      // buffer incoming captions until the associated video segment
36586      // finishes
36587      if (output.text) {
36588        return this.pendingCaptions.push(output);
36589      } // buffer incoming id3 tags until the final flush
36590
36591
36592      if (output.frames) {
36593        return this.pendingMetadata.push(output);
36594      } // Add this track to the list of pending tracks and store
36595      // important information required for the construction of
36596      // the final segment
36597
36598
36599      this.pendingTracks.push(output.track);
36600      this.pendingBoxes.push(output.boxes);
36601      this.pendingBytes += output.boxes.byteLength;
36602
36603      if (output.track.type === 'video') {
36604        this.videoTrack = output.track;
36605      }
36606
36607      if (output.track.type === 'audio') {
36608        this.audioTrack = output.track;
36609      }
36610    };
36611  };
36612
36613  _CoalesceStream.prototype = new stream();
36614
36615  _CoalesceStream.prototype.flush = function (flushSource) {
36616    var offset = 0,
36617        event = {
36618      captions: [],
36619      captionStreams: {},
36620      metadata: [],
36621      info: {}
36622    },
36623        caption,
36624        id3,
36625        initSegment,
36626        timelineStartPts = 0,
36627        i;
36628
36629    if (this.pendingTracks.length < this.numberOfTracks) {
36630      if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
36631        // Return because we haven't received a flush from a data-generating
36632        // portion of the segment (meaning that we have only recieved meta-data
36633        // or captions.)
36634        return;
36635      } else if (this.remuxTracks) {
36636        // Return until we have enough tracks from the pipeline to remux (if we
36637        // are remuxing audio and video into a single MP4)
36638        return;
36639      } else if (this.pendingTracks.length === 0) {
36640        // In the case where we receive a flush without any data having been
36641        // received we consider it an emitted track for the purposes of coalescing
36642        // `done` events.
36643        // We do this for the case where there is an audio and video track in the
36644        // segment but no audio data. (seen in several playlists with alternate
36645        // audio tracks and no audio present in the main TS segments.)
36646        this.emittedTracks++;
36647
36648        if (this.emittedTracks >= this.numberOfTracks) {
36649          this.trigger('done');
36650          this.emittedTracks = 0;
36651        }
36652
36653        return;
36654      }
36655    }
36656
36657    if (this.videoTrack) {
36658      timelineStartPts = this.videoTrack.timelineStartInfo.pts;
36659      VIDEO_PROPERTIES.forEach(function (prop) {
36660        event.info[prop] = this.videoTrack[prop];
36661      }, this);
36662    } else if (this.audioTrack) {
36663      timelineStartPts = this.audioTrack.timelineStartInfo.pts;
36664      AUDIO_PROPERTIES.forEach(function (prop) {
36665        event.info[prop] = this.audioTrack[prop];
36666      }, this);
36667    }
36668
36669    if (this.pendingTracks.length === 1) {
36670      event.type = this.pendingTracks[0].type;
36671    } else {
36672      event.type = 'combined';
36673    }
36674
36675    this.emittedTracks += this.pendingTracks.length;
36676    initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
36677
36678    event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
36679    // and track definitions
36680
36681    event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
36682
36683    event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
36684
36685    for (i = 0; i < this.pendingBoxes.length; i++) {
36686      event.data.set(this.pendingBoxes[i], offset);
36687      offset += this.pendingBoxes[i].byteLength;
36688    } // Translate caption PTS times into second offsets to match the
36689    // video timeline for the segment, and add track info
36690
36691
36692    for (i = 0; i < this.pendingCaptions.length; i++) {
36693      caption = this.pendingCaptions[i];
36694      caption.startTime = caption.startPts;
36695
36696      if (!this.keepOriginalTimestamps) {
36697        caption.startTime -= timelineStartPts;
36698      }
36699
36700      caption.startTime /= 90e3;
36701      caption.endTime = caption.endPts;
36702
36703      if (!this.keepOriginalTimestamps) {
36704        caption.endTime -= timelineStartPts;
36705      }
36706
36707      caption.endTime /= 90e3;
36708      event.captionStreams[caption.stream] = true;
36709      event.captions.push(caption);
36710    } // Translate ID3 frame PTS times into second offsets to match the
36711    // video timeline for the segment
36712
36713
36714    for (i = 0; i < this.pendingMetadata.length; i++) {
36715      id3 = this.pendingMetadata[i];
36716      id3.cueTime = id3.pts;
36717
36718      if (!this.keepOriginalTimestamps) {
36719        id3.cueTime -= timelineStartPts;
36720      }
36721
36722      id3.cueTime /= 90e3;
36723      event.metadata.push(id3);
36724    } // We add this to every single emitted segment even though we only need
36725    // it for the first
36726
36727
36728    event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
36729
36730    this.pendingTracks.length = 0;
36731    this.videoTrack = null;
36732    this.pendingBoxes.length = 0;
36733    this.pendingCaptions.length = 0;
36734    this.pendingBytes = 0;
36735    this.pendingMetadata.length = 0; // Emit the built segment
36736
36737    this.trigger('data', event); // Only emit `done` if all tracks have been flushed and emitted
36738
36739    if (this.emittedTracks >= this.numberOfTracks) {
36740      this.trigger('done');
36741      this.emittedTracks = 0;
36742    }
36743  };
36744  /**
36745   * A Stream that expects MP2T binary data as input and produces
36746   * corresponding media segments, suitable for use with Media Source
36747   * Extension (MSE) implementations that support the ISO BMFF byte
36748   * stream format, like Chrome.
36749   */
36750
36751
36752  _Transmuxer = function Transmuxer(options) {
36753    var self = this,
36754        hasFlushed = true,
36755        videoTrack,
36756        audioTrack;
36757
36758    _Transmuxer.prototype.init.call(this);
36759
36760    options = options || {};
36761    this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
36762    this.transmuxPipeline_ = {};
36763
36764    this.setupAacPipeline = function () {
36765      var pipeline = {};
36766      this.transmuxPipeline_ = pipeline;
36767      pipeline.type = 'aac';
36768      pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
36769
36770      pipeline.aacStream = new aac();
36771      pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
36772      pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
36773      pipeline.adtsStream = new adts();
36774      pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
36775      pipeline.headOfPipeline = pipeline.aacStream;
36776      pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
36777      pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
36778      pipeline.metadataStream.on('timestamp', function (frame) {
36779        pipeline.aacStream.setTimestamp(frame.timeStamp);
36780      });
36781      pipeline.aacStream.on('data', function (data) {
36782        if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) {
36783          audioTrack = audioTrack || {
36784            timelineStartInfo: {
36785              baseMediaDecodeTime: self.baseMediaDecodeTime
36786            },
36787            codec: 'adts',
36788            type: 'audio'
36789          }; // hook up the audio segment stream to the first track with aac data
36790
36791          pipeline.coalesceStream.numberOfTracks++;
36792          pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
36793
36794          pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
36795        }
36796      }); // Re-emit any data coming from the coalesce stream to the outside world
36797
36798      pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
36799
36800      pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
36801    };
36802
36803    this.setupTsPipeline = function () {
36804      var pipeline = {};
36805      this.transmuxPipeline_ = pipeline;
36806      pipeline.type = 'ts';
36807      pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
36808
36809      pipeline.packetStream = new m2ts_1.TransportPacketStream();
36810      pipeline.parseStream = new m2ts_1.TransportParseStream();
36811      pipeline.elementaryStream = new m2ts_1.ElementaryStream();
36812      pipeline.videoTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('video');
36813      pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
36814      pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
36815      pipeline.adtsStream = new adts();
36816      pipeline.h264Stream = new H264Stream();
36817      pipeline.captionStream = new m2ts_1.CaptionStream();
36818      pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
36819      pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
36820
36821      pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream); // !!THIS ORDER IS IMPORTANT!!
36822      // demux the streams
36823
36824      pipeline.elementaryStream.pipe(pipeline.videoTimestampRolloverStream).pipe(pipeline.h264Stream);
36825      pipeline.elementaryStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
36826      pipeline.elementaryStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
36827
36828      pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
36829      pipeline.elementaryStream.on('data', function (data) {
36830        var i;
36831
36832        if (data.type === 'metadata') {
36833          i = data.tracks.length; // scan the tracks listed in the metadata
36834
36835          while (i--) {
36836            if (!videoTrack && data.tracks[i].type === 'video') {
36837              videoTrack = data.tracks[i];
36838              videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
36839            } else if (!audioTrack && data.tracks[i].type === 'audio') {
36840              audioTrack = data.tracks[i];
36841              audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
36842            }
36843          } // hook up the video segment stream to the first track with h264 data
36844
36845
36846          if (videoTrack && !pipeline.videoSegmentStream) {
36847            pipeline.coalesceStream.numberOfTracks++;
36848            pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options);
36849            pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
36850              // When video emits timelineStartInfo data after a flush, we forward that
36851              // info to the AudioSegmentStream, if it exists, because video timeline
36852              // data takes precedence.
36853              if (audioTrack) {
36854                audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
36855                // very earliest DTS we have seen in video because Chrome will
36856                // interpret any video track with a baseMediaDecodeTime that is
36857                // non-zero as a gap.
36858
36859                pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts);
36860              }
36861            });
36862            pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
36863            pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
36864            pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
36865              if (audioTrack) {
36866                pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
36867              }
36868            }); // Set up the final part of the video pipeline
36869
36870            pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
36871          }
36872
36873          if (audioTrack && !pipeline.audioSegmentStream) {
36874            // hook up the audio segment stream to the first track with aac data
36875            pipeline.coalesceStream.numberOfTracks++;
36876            pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
36877
36878            pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
36879          }
36880        }
36881      }); // Re-emit any data coming from the coalesce stream to the outside world
36882
36883      pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
36884
36885      pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
36886    }; // hook up the segment streams once track metadata is delivered
36887
36888
36889    this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
36890      var pipeline = this.transmuxPipeline_;
36891
36892      if (!options.keepOriginalTimestamps) {
36893        this.baseMediaDecodeTime = baseMediaDecodeTime;
36894      }
36895
36896      if (audioTrack) {
36897        audioTrack.timelineStartInfo.dts = undefined;
36898        audioTrack.timelineStartInfo.pts = undefined;
36899        trackDecodeInfo.clearDtsInfo(audioTrack);
36900
36901        if (!options.keepOriginalTimestamps) {
36902          audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
36903        }
36904
36905        if (pipeline.audioTimestampRolloverStream) {
36906          pipeline.audioTimestampRolloverStream.discontinuity();
36907        }
36908      }
36909
36910      if (videoTrack) {
36911        if (pipeline.videoSegmentStream) {
36912          pipeline.videoSegmentStream.gopCache_ = [];
36913          pipeline.videoTimestampRolloverStream.discontinuity();
36914        }
36915
36916        videoTrack.timelineStartInfo.dts = undefined;
36917        videoTrack.timelineStartInfo.pts = undefined;
36918        trackDecodeInfo.clearDtsInfo(videoTrack);
36919        pipeline.captionStream.reset();
36920
36921        if (!options.keepOriginalTimestamps) {
36922          videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
36923        }
36924      }
36925
36926      if (pipeline.timedMetadataTimestampRolloverStream) {
36927        pipeline.timedMetadataTimestampRolloverStream.discontinuity();
36928      }
36929    };
36930
36931    this.setAudioAppendStart = function (timestamp) {
36932      if (audioTrack) {
36933        this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
36934      }
36935    };
36936
36937    this.alignGopsWith = function (gopsToAlignWith) {
36938      if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
36939        this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
36940      }
36941    }; // feed incoming data to the front of the parsing pipeline
36942
36943
36944    this.push = function (data) {
36945      if (hasFlushed) {
36946        var isAac = isLikelyAacData$1(data);
36947
36948        if (isAac && this.transmuxPipeline_.type !== 'aac') {
36949          this.setupAacPipeline();
36950        } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
36951          this.setupTsPipeline();
36952        }
36953
36954        hasFlushed = false;
36955      }
36956
36957      this.transmuxPipeline_.headOfPipeline.push(data);
36958    }; // flush any buffered data
36959
36960
36961    this.flush = function () {
36962      hasFlushed = true; // Start at the top of the pipeline and flush all pending work
36963
36964      this.transmuxPipeline_.headOfPipeline.flush();
36965    }; // Caption data has to be reset when seeking outside buffered range
36966
36967
36968    this.resetCaptions = function () {
36969      if (this.transmuxPipeline_.captionStream) {
36970        this.transmuxPipeline_.captionStream.reset();
36971      }
36972    };
36973  };
36974
36975  _Transmuxer.prototype = new stream();
36976  var transmuxer = {
36977    Transmuxer: _Transmuxer,
36978    VideoSegmentStream: _VideoSegmentStream,
36979    AudioSegmentStream: _AudioSegmentStream,
36980    AUDIO_PROPERTIES: AUDIO_PROPERTIES,
36981    VIDEO_PROPERTIES: VIDEO_PROPERTIES,
36982    // exported for testing
36983    generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo
36984  };
36985
36986  var inspectMp4,
36987      _textifyMp,
36988      parseType$2 = probe.parseType,
36989      parseMp4Date = function parseMp4Date(seconds) {
36990    return new Date(seconds * 1000 - 2082844800000);
36991  },
36992      parseSampleFlags = function parseSampleFlags(flags) {
36993    return {
36994      isLeading: (flags[0] & 0x0c) >>> 2,
36995      dependsOn: flags[0] & 0x03,
36996      isDependedOn: (flags[1] & 0xc0) >>> 6,
36997      hasRedundancy: (flags[1] & 0x30) >>> 4,
36998      paddingValue: (flags[1] & 0x0e) >>> 1,
36999      isNonSyncSample: flags[1] & 0x01,
37000      degradationPriority: flags[2] << 8 | flags[3]
37001    };
37002  },
37003      nalParse = function nalParse(avcStream) {
37004    var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
37005        result = [],
37006        i,
37007        length;
37008
37009    for (i = 0; i + 4 < avcStream.length; i += length) {
37010      length = avcView.getUint32(i);
37011      i += 4; // bail if this doesn't appear to be an H264 stream
37012
37013      if (length <= 0) {
37014        result.push('<span style=\'color:red;\'>MALFORMED DATA</span>');
37015        continue;
37016      }
37017
37018      switch (avcStream[i] & 0x1F) {
37019        case 0x01:
37020          result.push('slice_layer_without_partitioning_rbsp');
37021          break;
37022
37023        case 0x05:
37024          result.push('slice_layer_without_partitioning_rbsp_idr');
37025          break;
37026
37027        case 0x06:
37028          result.push('sei_rbsp');
37029          break;
37030
37031        case 0x07:
37032          result.push('seq_parameter_set_rbsp');
37033          break;
37034
37035        case 0x08:
37036          result.push('pic_parameter_set_rbsp');
37037          break;
37038
37039        case 0x09:
37040          result.push('access_unit_delimiter_rbsp');
37041          break;
37042
37043        default:
37044          result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F);
37045          break;
37046      }
37047    }
37048
37049    return result;
37050  },
37051      // registry of handlers for individual mp4 box types
37052  parse$1 = {
37053    // codingname, not a first-class box type. stsd entries share the
37054    // same format as real boxes so the parsing infrastructure can be
37055    // shared
37056    avc1: function avc1(data) {
37057      var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
37058      return {
37059        dataReferenceIndex: view.getUint16(6),
37060        width: view.getUint16(24),
37061        height: view.getUint16(26),
37062        horizresolution: view.getUint16(28) + view.getUint16(30) / 16,
37063        vertresolution: view.getUint16(32) + view.getUint16(34) / 16,
37064        frameCount: view.getUint16(40),
37065        depth: view.getUint16(74),
37066        config: inspectMp4(data.subarray(78, data.byteLength))
37067      };
37068    },
37069    avcC: function avcC(data) {
37070      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
37071          result = {
37072        configurationVersion: data[0],
37073        avcProfileIndication: data[1],
37074        profileCompatibility: data[2],
37075        avcLevelIndication: data[3],
37076        lengthSizeMinusOne: data[4] & 0x03,
37077        sps: [],
37078        pps: []
37079      },
37080          numOfSequenceParameterSets = data[5] & 0x1f,
37081          numOfPictureParameterSets,
37082          nalSize,
37083          offset,
37084          i; // iterate past any SPSs
37085
37086      offset = 6;
37087
37088      for (i = 0; i < numOfSequenceParameterSets; i++) {
37089        nalSize = view.getUint16(offset);
37090        offset += 2;
37091        result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
37092        offset += nalSize;
37093      } // iterate past any PPSs
37094
37095
37096      numOfPictureParameterSets = data[offset];
37097      offset++;
37098
37099      for (i = 0; i < numOfPictureParameterSets; i++) {
37100        nalSize = view.getUint16(offset);
37101        offset += 2;
37102        result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
37103        offset += nalSize;
37104      }
37105
37106      return result;
37107    },
37108    btrt: function btrt(data) {
37109      var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
37110      return {
37111        bufferSizeDB: view.getUint32(0),
37112        maxBitrate: view.getUint32(4),
37113        avgBitrate: view.getUint32(8)
37114      };
37115    },
37116    esds: function esds(data) {
37117      return {
37118        version: data[0],
37119        flags: new Uint8Array(data.subarray(1, 4)),
37120        esId: data[6] << 8 | data[7],
37121        streamPriority: data[8] & 0x1f,
37122        decoderConfig: {
37123          objectProfileIndication: data[11],
37124          streamType: data[12] >>> 2 & 0x3f,
37125          bufferSize: data[13] << 16 | data[14] << 8 | data[15],
37126          maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
37127          avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23],
37128          decoderConfigDescriptor: {
37129            tag: data[24],
37130            length: data[25],
37131            audioObjectType: data[26] >>> 3 & 0x1f,
37132            samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01,
37133            channelConfiguration: data[27] >>> 3 & 0x0f
37134          }
37135        }
37136      };
37137    },
37138    ftyp: function ftyp(data) {
37139      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
37140          result = {
37141        majorBrand: parseType$2(data.subarray(0, 4)),
37142        minorVersion: view.getUint32(4),
37143        compatibleBrands: []
37144      },
37145          i = 8;
37146
37147      while (i < data.byteLength) {
37148        result.compatibleBrands.push(parseType$2(data.subarray(i, i + 4)));
37149        i += 4;
37150      }
37151
37152      return result;
37153    },
37154    dinf: function dinf(data) {
37155      return {
37156        boxes: inspectMp4(data)
37157      };
37158    },
37159    dref: function dref(data) {
37160      return {
37161        version: data[0],
37162        flags: new Uint8Array(data.subarray(1, 4)),
37163        dataReferences: inspectMp4(data.subarray(8))
37164      };
37165    },
37166    hdlr: function hdlr(data) {
37167      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
37168          result = {
37169        version: view.getUint8(0),
37170        flags: new Uint8Array(data.subarray(1, 4)),
37171        handlerType: parseType$2(data.subarray(8, 12)),
37172        name: ''
37173      },
37174          i = 8; // parse out the name field
37175
37176      for (i = 24; i < data.byteLength; i++) {
37177        if (data[i] === 0x00) {
37178          // the name field is null-terminated
37179          i++;
37180          break;
37181        }
37182
37183        result.name += String.fromCharCode(data[i]);
37184      } // decode UTF-8 to javascript's internal representation
37185      // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
37186
37187
37188      result.name = decodeURIComponent(escape(result.name));
37189      return result;
37190    },
37191    mdat: function mdat(data) {
37192      return {
37193        byteLength: data.byteLength,
37194        nals: nalParse(data)
37195      };
37196    },
37197    mdhd: function mdhd(data) {
37198      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
37199          i = 4,
37200          language,
37201          result = {
37202        version: view.getUint8(0),
37203        flags: new Uint8Array(data.subarray(1, 4)),
37204        language: ''
37205      };
37206
37207      if (result.version === 1) {
37208        i += 4;
37209        result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
37210
37211        i += 8;
37212        result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
37213
37214        i += 4;
37215        result.timescale = view.getUint32(i);
37216        i += 8;
37217        result.duration = view.getUint32(i); // truncating top 4 bytes
37218      } else {
37219        result.creationTime = parseMp4Date(view.getUint32(i));
37220        i += 4;
37221        result.modificationTime = parseMp4Date(view.getUint32(i));
37222        i += 4;
37223        result.timescale = view.getUint32(i);
37224        i += 4;
37225        result.duration = view.getUint32(i);
37226      }
37227
37228      i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
37229      // each field is the packed difference between its ASCII value and 0x60
37230
37231      language = view.getUint16(i);
37232      result.language += String.fromCharCode((language >> 10) + 0x60);
37233      result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60);
37234      result.language += String.fromCharCode((language & 0x1f) + 0x60);
37235      return result;
37236    },
37237    mdia: function mdia(data) {
37238      return {
37239        boxes: inspectMp4(data)
37240      };
37241    },
37242    mfhd: function mfhd(data) {
37243      return {
37244        version: data[0],
37245        flags: new Uint8Array(data.subarray(1, 4)),
37246        sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
37247      };
37248    },
37249    minf: function minf(data) {
37250      return {
37251        boxes: inspectMp4(data)
37252      };
37253    },
37254    // codingname, not a first-class box type. stsd entries share the
37255    // same format as real boxes so the parsing infrastructure can be
37256    // shared
37257    mp4a: function mp4a(data) {
37258      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
37259          result = {
37260        // 6 bytes reserved
37261        dataReferenceIndex: view.getUint16(6),
37262        // 4 + 4 bytes reserved
37263        channelcount: view.getUint16(16),
37264        samplesize: view.getUint16(18),
37265        // 2 bytes pre_defined
37266        // 2 bytes reserved
37267        samplerate: view.getUint16(24) + view.getUint16(26) / 65536
37268      }; // if there are more bytes to process, assume this is an ISO/IEC
37269      // 14496-14 MP4AudioSampleEntry and parse the ESDBox
37270
37271      if (data.byteLength > 28) {
37272        result.streamDescriptor = inspectMp4(data.subarray(28))[0];
37273      }
37274
37275      return result;
37276    },
37277    moof: function moof(data) {
37278      return {
37279        boxes: inspectMp4(data)
37280      };
37281    },
37282    moov: function moov(data) {
37283      return {
37284        boxes: inspectMp4(data)
37285      };
37286    },
37287    mvex: function mvex(data) {
37288      return {
37289        boxes: inspectMp4(data)
37290      };
37291    },
37292    mvhd: function mvhd(data) {
37293      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
37294          i = 4,
37295          result = {
37296        version: view.getUint8(0),
37297        flags: new Uint8Array(data.subarray(1, 4))
37298      };
37299
37300      if (result.version === 1) {
37301        i += 4;
37302        result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
37303
37304        i += 8;
37305        result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
37306
37307        i += 4;
37308        result.timescale = view.getUint32(i);
37309        i += 8;
37310        result.duration = view.getUint32(i); // truncating top 4 bytes
37311      } else {
37312        result.creationTime = parseMp4Date(view.getUint32(i));
37313        i += 4;
37314        result.modificationTime = parseMp4Date(view.getUint32(i));
37315        i += 4;
37316        result.timescale = view.getUint32(i);
37317        i += 4;
37318        result.duration = view.getUint32(i);
37319      }
37320
37321      i += 4; // convert fixed-point, base 16 back to a number
37322
37323      result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16;
37324      i += 4;
37325      result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
37326      i += 2;
37327      i += 2;
37328      i += 2 * 4;
37329      result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
37330      i += 9 * 4;
37331      i += 6 * 4;
37332      result.nextTrackId = view.getUint32(i);
37333      return result;
37334    },
37335    pdin: function pdin(data) {
37336      var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
37337      return {
37338        version: view.getUint8(0),
37339        flags: new Uint8Array(data.subarray(1, 4)),
37340        rate: view.getUint32(4),
37341        initialDelay: view.getUint32(8)
37342      };
37343    },
37344    sdtp: function sdtp(data) {
37345      var result = {
37346        version: data[0],
37347        flags: new Uint8Array(data.subarray(1, 4)),
37348        samples: []
37349      },
37350          i;
37351
37352      for (i = 4; i < data.byteLength; i++) {
37353        result.samples.push({
37354          dependsOn: (data[i] & 0x30) >> 4,
37355          isDependedOn: (data[i] & 0x0c) >> 2,
37356          hasRedundancy: data[i] & 0x03
37357        });
37358      }
37359
37360      return result;
37361    },
37362    sidx: function sidx(data) {
37363      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
37364          result = {
37365        version: data[0],
37366        flags: new Uint8Array(data.subarray(1, 4)),
37367        references: [],
37368        referenceId: view.getUint32(4),
37369        timescale: view.getUint32(8),
37370        earliestPresentationTime: view.getUint32(12),
37371        firstOffset: view.getUint32(16)
37372      },
37373          referenceCount = view.getUint16(22),
37374          i;
37375
37376      for (i = 24; referenceCount; i += 12, referenceCount--) {
37377        result.references.push({
37378          referenceType: (data[i] & 0x80) >>> 7,
37379          referencedSize: view.getUint32(i) & 0x7FFFFFFF,
37380          subsegmentDuration: view.getUint32(i + 4),
37381          startsWithSap: !!(data[i + 8] & 0x80),
37382          sapType: (data[i + 8] & 0x70) >>> 4,
37383          sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
37384        });
37385      }
37386
37387      return result;
37388    },
37389    smhd: function smhd(data) {
37390      return {
37391        version: data[0],
37392        flags: new Uint8Array(data.subarray(1, 4)),
37393        balance: data[4] + data[5] / 256
37394      };
37395    },
37396    stbl: function stbl(data) {
37397      return {
37398        boxes: inspectMp4(data)
37399      };
37400    },
37401    stco: function stco(data) {
37402      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
37403          result = {
37404        version: data[0],
37405        flags: new Uint8Array(data.subarray(1, 4)),
37406        chunkOffsets: []
37407      },
37408          entryCount = view.getUint32(4),
37409          i;
37410
37411      for (i = 8; entryCount; i += 4, entryCount--) {
37412        result.chunkOffsets.push(view.getUint32(i));
37413      }
37414
37415      return result;
37416    },
37417    stsc: function stsc(data) {
37418      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
37419          entryCount = view.getUint32(4),
37420          result = {
37421        version: data[0],
37422        flags: new Uint8Array(data.subarray(1, 4)),
37423        sampleToChunks: []
37424      },
37425          i;
37426
37427      for (i = 8; entryCount; i += 12, entryCount--) {
37428        result.sampleToChunks.push({
37429          firstChunk: view.getUint32(i),
37430          samplesPerChunk: view.getUint32(i + 4),
37431          sampleDescriptionIndex: view.getUint32(i + 8)
37432        });
37433      }
37434
37435      return result;
37436    },
37437    stsd: function stsd(data) {
37438      return {
37439        version: data[0],
37440        flags: new Uint8Array(data.subarray(1, 4)),
37441        sampleDescriptions: inspectMp4(data.subarray(8))
37442      };
37443    },
37444    stsz: function stsz(data) {
37445      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
37446          result = {
37447        version: data[0],
37448        flags: new Uint8Array(data.subarray(1, 4)),
37449        sampleSize: view.getUint32(4),
37450        entries: []
37451      },
37452          i;
37453
37454      for (i = 12; i < data.byteLength; i += 4) {
37455        result.entries.push(view.getUint32(i));
37456      }
37457
37458      return result;
37459    },
37460    stts: function stts(data) {
37461      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
37462          result = {
37463        version: data[0],
37464        flags: new Uint8Array(data.subarray(1, 4)),
37465        timeToSamples: []
37466      },
37467          entryCount = view.getUint32(4),
37468          i;
37469
37470      for (i = 8; entryCount; i += 8, entryCount--) {
37471        result.timeToSamples.push({
37472          sampleCount: view.getUint32(i),
37473          sampleDelta: view.getUint32(i + 4)
37474        });
37475      }
37476
37477      return result;
37478    },
37479    styp: function styp(data) {
37480      return parse$1.ftyp(data);
37481    },
37482    tfdt: function tfdt(data) {
37483      var result = {
37484        version: data[0],
37485        flags: new Uint8Array(data.subarray(1, 4)),
37486        baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
37487      };
37488
37489      if (result.version === 1) {
37490        result.baseMediaDecodeTime *= Math.pow(2, 32);
37491        result.baseMediaDecodeTime += data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11];
37492      }
37493
37494      return result;
37495    },
37496    tfhd: function tfhd(data) {
37497      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
37498          result = {
37499        version: data[0],
37500        flags: new Uint8Array(data.subarray(1, 4)),
37501        trackId: view.getUint32(4)
37502      },
37503          baseDataOffsetPresent = result.flags[2] & 0x01,
37504          sampleDescriptionIndexPresent = result.flags[2] & 0x02,
37505          defaultSampleDurationPresent = result.flags[2] & 0x08,
37506          defaultSampleSizePresent = result.flags[2] & 0x10,
37507          defaultSampleFlagsPresent = result.flags[2] & 0x20,
37508          durationIsEmpty = result.flags[0] & 0x010000,
37509          defaultBaseIsMoof = result.flags[0] & 0x020000,
37510          i;
37511      i = 8;
37512
37513      if (baseDataOffsetPresent) {
37514        i += 4; // truncate top 4 bytes
37515        // FIXME: should we read the full 64 bits?
37516
37517        result.baseDataOffset = view.getUint32(12);
37518        i += 4;
37519      }
37520
37521      if (sampleDescriptionIndexPresent) {
37522        result.sampleDescriptionIndex = view.getUint32(i);
37523        i += 4;
37524      }
37525
37526      if (defaultSampleDurationPresent) {
37527        result.defaultSampleDuration = view.getUint32(i);
37528        i += 4;
37529      }
37530
37531      if (defaultSampleSizePresent) {
37532        result.defaultSampleSize = view.getUint32(i);
37533        i += 4;
37534      }
37535
37536      if (defaultSampleFlagsPresent) {
37537        result.defaultSampleFlags = view.getUint32(i);
37538      }
37539
37540      if (durationIsEmpty) {
37541        result.durationIsEmpty = true;
37542      }
37543
37544      if (!baseDataOffsetPresent && defaultBaseIsMoof) {
37545        result.baseDataOffsetIsMoof = true;
37546      }
37547
37548      return result;
37549    },
37550    tkhd: function tkhd(data) {
37551      var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
37552          i = 4,
37553          result = {
37554        version: view.getUint8(0),
37555        flags: new Uint8Array(data.subarray(1, 4))
37556      };
37557
37558      if (result.version === 1) {
37559        i += 4;
37560        result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
37561
37562        i += 8;
37563        result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
37564
37565        i += 4;
37566        result.trackId = view.getUint32(i);
37567        i += 4;
37568        i += 8;
37569        result.duration = view.getUint32(i); // truncating top 4 bytes
37570      } else {
37571        result.creationTime = parseMp4Date(view.getUint32(i));
37572        i += 4;
37573        result.modificationTime = parseMp4Date(view.getUint32(i));
37574        i += 4;
37575        result.trackId = view.getUint32(i);
37576        i += 4;
37577        i += 4;
37578        result.duration = view.getUint32(i);
37579      }
37580
37581      i += 4;
37582      i += 2 * 4;
37583      result.layer = view.getUint16(i);
37584      i += 2;
37585      result.alternateGroup = view.getUint16(i);
37586      i += 2; // convert fixed-point, base 16 back to a number
37587
37588      result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
37589      i += 2;
37590      i += 2;
37591      result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
37592      i += 9 * 4;
37593      result.width = view.getUint16(i) + view.getUint16(i + 2) / 16;
37594      i += 4;
37595      result.height = view.getUint16(i) + view.getUint16(i + 2) / 16;
37596      return result;
37597    },
37598    traf: function traf(data) {
37599      return {
37600        boxes: inspectMp4(data)
37601      };
37602    },
37603    trak: function trak(data) {
37604      return {
37605        boxes: inspectMp4(data)
37606      };
37607    },
37608    trex: function trex(data) {
37609      var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
37610      return {
37611        version: data[0],
37612        flags: new Uint8Array(data.subarray(1, 4)),
37613        trackId: view.getUint32(4),
37614        defaultSampleDescriptionIndex: view.getUint32(8),
37615        defaultSampleDuration: view.getUint32(12),
37616        defaultSampleSize: view.getUint32(16),
37617        sampleDependsOn: data[20] & 0x03,
37618        sampleIsDependedOn: (data[21] & 0xc0) >> 6,
37619        sampleHasRedundancy: (data[21] & 0x30) >> 4,
37620        samplePaddingValue: (data[21] & 0x0e) >> 1,
37621        sampleIsDifferenceSample: !!(data[21] & 0x01),
37622        sampleDegradationPriority: view.getUint16(22)
37623      };
37624    },
37625    trun: function trun(data) {
37626      var result = {
37627        version: data[0],
37628        flags: new Uint8Array(data.subarray(1, 4)),
37629        samples: []
37630      },
37631          view = new DataView(data.buffer, data.byteOffset, data.byteLength),
37632          // Flag interpretation
37633      dataOffsetPresent = result.flags[2] & 0x01,
37634          // compare with 2nd byte of 0x1
37635      firstSampleFlagsPresent = result.flags[2] & 0x04,
37636          // compare with 2nd byte of 0x4
37637      sampleDurationPresent = result.flags[1] & 0x01,
37638          // compare with 2nd byte of 0x100
37639      sampleSizePresent = result.flags[1] & 0x02,
37640          // compare with 2nd byte of 0x200
37641      sampleFlagsPresent = result.flags[1] & 0x04,
37642          // compare with 2nd byte of 0x400
37643      sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
37644          // compare with 2nd byte of 0x800
37645      sampleCount = view.getUint32(4),
37646          offset = 8,
37647          sample;
37648
37649      if (dataOffsetPresent) {
37650        // 32 bit signed integer
37651        result.dataOffset = view.getInt32(offset);
37652        offset += 4;
37653      } // Overrides the flags for the first sample only. The order of
37654      // optional values will be: duration, size, compositionTimeOffset
37655
37656
37657      if (firstSampleFlagsPresent && sampleCount) {
37658        sample = {
37659          flags: parseSampleFlags(data.subarray(offset, offset + 4))
37660        };
37661        offset += 4;
37662
37663        if (sampleDurationPresent) {
37664          sample.duration = view.getUint32(offset);
37665          offset += 4;
37666        }
37667
37668        if (sampleSizePresent) {
37669          sample.size = view.getUint32(offset);
37670          offset += 4;
37671        }
37672
37673        if (sampleCompositionTimeOffsetPresent) {
37674          // Note: this should be a signed int if version is 1
37675          sample.compositionTimeOffset = view.getUint32(offset);
37676          offset += 4;
37677        }
37678
37679        result.samples.push(sample);
37680        sampleCount--;
37681      }
37682
37683      while (sampleCount--) {
37684        sample = {};
37685
37686        if (sampleDurationPresent) {
37687          sample.duration = view.getUint32(offset);
37688          offset += 4;
37689        }
37690
37691        if (sampleSizePresent) {
37692          sample.size = view.getUint32(offset);
37693          offset += 4;
37694        }
37695
37696        if (sampleFlagsPresent) {
37697          sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
37698          offset += 4;
37699        }
37700
37701        if (sampleCompositionTimeOffsetPresent) {
37702          // Note: this should be a signed int if version is 1
37703          sample.compositionTimeOffset = view.getUint32(offset);
37704          offset += 4;
37705        }
37706
37707        result.samples.push(sample);
37708      }
37709
37710      return result;
37711    },
37712    'url ': function url(data) {
37713      return {
37714        version: data[0],
37715        flags: new Uint8Array(data.subarray(1, 4))
37716      };
37717    },
37718    vmhd: function vmhd(data) {
37719      var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
37720      return {
37721        version: data[0],
37722        flags: new Uint8Array(data.subarray(1, 4)),
37723        graphicsmode: view.getUint16(4),
37724        opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)])
37725      };
37726    }
37727  };
37728  /**
37729   * Return a javascript array of box objects parsed from an ISO base
37730   * media file.
37731   * @param data {Uint8Array} the binary data of the media to be inspected
37732   * @return {array} a javascript array of potentially nested box objects
37733   */
37734
37735
37736  inspectMp4 = function inspectMp4(data) {
37737    var i = 0,
37738        result = [],
37739        view,
37740        size,
37741        type,
37742        end,
37743        box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API
37744
37745    var ab = new ArrayBuffer(data.length);
37746    var v = new Uint8Array(ab);
37747
37748    for (var z = 0; z < data.length; ++z) {
37749      v[z] = data[z];
37750    }
37751
37752    view = new DataView(ab);
37753
37754    while (i < data.byteLength) {
37755      // parse box data
37756      size = view.getUint32(i);
37757      type = parseType$2(data.subarray(i + 4, i + 8));
37758      end = size > 1 ? i + size : data.byteLength;
vendor: 4,013 bytes, lines 37758-37877
37758 // parse type-specific data
37759
37760      box = (parse$1[type] || function (data) {
37761        return {
37762          data: data
37763        };
37764      })(data.subarray(i + 8, end));
37765
37766      box.size = size;
37767      box.type = type; // store this box and move to the next
37768
37769      result.push(box);
37770      i = end;
37771    }
37772
37773    return result;
37774  };
37775  /**
37776   * Returns a textual representation of the javascript represtentation
37777   * of an MP4 file. You can use it as an alternative to
37778   * JSON.stringify() to compare inspected MP4s.
37779   * @param inspectedMp4 {array} the parsed array of boxes in an MP4
37780   * file
37781   * @param depth {number} (optional) the number of ancestor boxes of
37782   * the elements of inspectedMp4. Assumed to be zero if unspecified.
37783   * @return {string} a text representation of the parsed MP4
37784   */
37785
37786
37787  _textifyMp = function textifyMp4(inspectedMp4, depth) {
37788    var indent;
37789    depth = depth || 0;
37790    indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes
37791
37792    return inspectedMp4.map(function (box, index) {
37793      // list the box type first at the current indentation level
37794      return indent + box.type + '\n' + // the type is already included and handle child boxes separately
37795      Object.keys(box).filter(function (key) {
37796        return key !== 'type' && key !== 'boxes'; // output all the box properties
37797      }).map(function (key) {
37798        var prefix = indent + '  ' + key + ': ',
37799            value = box[key]; // print out raw bytes as hexademical
37800
37801        if (value instanceof Uint8Array || value instanceof Uint32Array) {
37802          var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (byte) {
37803            return ' ' + ('00' + byte.toString(16)).slice(-2);
37804          }).join('').match(/.{1,24}/g);
37805
37806          if (!bytes) {
37807            return prefix + '<>';
37808          }
37809
37810          if (bytes.length === 1) {
37811            return prefix + '<' + bytes.join('').slice(1) + '>';
37812          }
37813
37814          return prefix + '<\n' + bytes.map(function (line) {
37815            return indent + '  ' + line;
37816          }).join('\n') + '\n' + indent + '  >';
37817        } // stringify generic objects
37818
37819
37820        return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) {
37821          if (index === 0) {
37822            return line;
37823          }
37824
37825          return indent + '  ' + line;
37826        }).join('\n');
37827      }).join('\n') + ( // recursively textify the child boxes
37828      box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : '');
37829    }).join('\n');
37830  };
37831
37832  var mp4Inspector = {
37833    inspect: inspectMp4,
37834    textify: _textifyMp,
37835    parseTfdt: parse$1.tfdt,
37836    parseHdlr: parse$1.hdlr,
37837    parseTfhd: parse$1.tfhd,
37838    parseTrun: parse$1.trun,
37839    parseSidx: parse$1.sidx
37840  };
37841
37842  var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes;
37843  var CaptionStream$1 = captionStream.CaptionStream;
37844  /**
37845    * Maps an offset in the mdat to a sample based on the the size of the samples.
37846    * Assumes that `parseSamples` has been called first.
37847    *
37848    * @param {Number} offset - The offset into the mdat
37849    * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
37850    * @return {?Object} The matching sample, or null if no match was found.
37851    *
37852    * @see ISO-BMFF-12/2015, Section 8.8.8
37853   **/
37854
37855  var mapToSample = function mapToSample(offset, samples) {
37856    var approximateOffset = offset;
37857
37858    for (var i = 0; i < samples.length; i++) {
37859      var sample = samples[i];
37860
37861      if (approximateOffset < sample.size) {
37862        return sample;
37863      }
37864
37865      approximateOffset -= sample.size;
37866    }
37867
37868    return null;
37869  };
37870  /**
37871    * Finds SEI nal units contained in a Media Data Box.
37872    * Assumes that `parseSamples` has been called first.
37873    *
37874    * @param {Uint8Array} avcStream - The bytes of the mdat
37875    * @param {Object[]} samples - The samples parsed out by `parseSamples`
37876    * @param {Number} trackId - The trackId of this video track
37877    * @return {Object[]}
vendor: 11,803 bytes, lines 37877-38247
37877 seiNals - the parsed SEI NALUs found.
37878    *   The contents of the seiNal should match what is expected by
37879    *   CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
37880    *
37881    * @see ISO-BMFF-12/2015, Section 8.1.1
37882    * @see Rec. ITU-T H.264, 7.3.2.3.1
37883   **/
37884
37885
37886  var findSeiNals = function findSeiNals(avcStream, samples, trackId) {
37887    var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
37888        result = [],
37889        seiNal,
37890        i,
37891        length,
37892        lastMatchedSample;
37893
37894    for (i = 0; i + 4 < avcStream.length; i += length) {
37895      length = avcView.getUint32(i);
37896      i += 4; // Bail if this doesn't appear to be an H264 stream
37897
37898      if (length <= 0) {
37899        continue;
37900      }
37901
37902      switch (avcStream[i] & 0x1F) {
37903        case 0x06:
37904          var data = avcStream.subarray(i + 1, i + 1 + length);
37905          var matchingSample = mapToSample(i, samples);
37906          seiNal = {
37907            nalUnitType: 'sei_rbsp',
37908            size: length,
37909            data: data,
37910            escapedRBSP: discardEmulationPreventionBytes$1(data),
37911            trackId: trackId
37912          };
37913
37914          if (matchingSample) {
37915            seiNal.pts = matchingSample.pts;
37916            seiNal.dts = matchingSample.dts;
37917            lastMatchedSample = matchingSample;
37918          } else {
37919            // If a matching sample cannot be found, use the last
37920            // sample's values as they should be as close as possible
37921            seiNal.pts = lastMatchedSample.pts;
37922            seiNal.dts = lastMatchedSample.dts;
37923          }
37924
37925          result.push(seiNal);
37926          break;
37927
37928        default:
37929          break;
37930      }
37931    }
37932
37933    return result;
37934  };
37935  /**
37936    * Parses sample information out of Track Run Boxes and calculates
37937    * the absolute presentation and decode timestamps of each sample.
37938    *
37939    * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
37940    * @param {Number} baseMediaDecodeTime - base media decode time from tfdt
37941        @see ISO-BMFF-12/2015, Section 8.8.12
37942    * @param {Object} tfhd - The parsed Track Fragment Header
37943    *   @see inspect.parseTfhd
37944    * @return {Object[]} the parsed samples
37945    *
37946    * @see ISO-BMFF-12/2015, Section 8.8.8
37947   **/
37948
37949
37950  var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) {
37951    var currentDts = baseMediaDecodeTime;
37952    var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
37953    var defaultSampleSize = tfhd.defaultSampleSize || 0;
37954    var trackId = tfhd.trackId;
37955    var allSamples = [];
37956    truns.forEach(function (trun) {
37957      // Note: We currently do not parse the sample table as well
37958      // as the trun. It's possible some sources will require this.
37959      // moov > trak > mdia > minf > stbl
37960      var trackRun = mp4Inspector.parseTrun(trun);
37961      var samples = trackRun.samples;
37962      samples.forEach(function (sample) {
37963        if (sample.duration === undefined) {
37964          sample.duration = defaultSampleDuration;
37965        }
37966
37967        if (sample.size === undefined) {
37968          sample.size = defaultSampleSize;
37969        }
37970
37971        sample.trackId = trackId;
37972        sample.dts = currentDts;
37973
37974        if (sample.compositionTimeOffset === undefined) {
37975          sample.compositionTimeOffset = 0;
37976        }
37977
37978        sample.pts = currentDts + sample.compositionTimeOffset;
37979        currentDts += sample.duration;
37980      });
37981      allSamples = allSamples.concat(samples);
37982    });
37983    return allSamples;
37984  };
37985  /**
37986    * Parses out caption nals from an FMP4 segment's video tracks.
37987    *
37988    * @param {Uint8Array} segment - The bytes of a single segment
37989    * @param {Number} videoTrackId - The trackId of a video track in the segment
37990    * @return {Object.<Number, Object[]>} A mapping of video trackId to
37991    *   a list of seiNals found in that track
37992   **/
37993
37994
37995  var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) {
37996    // To get the samples
37997    var trafs = probe.findBox(segment, ['moof', 'traf']); // To get SEI NAL units
37998
37999    var mdats = probe.findBox(segment, ['mdat']);
38000    var captionNals = {};
38001    var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
38002
38003    mdats.forEach(function (mdat, index) {
38004      var matchingTraf = trafs[index];
38005      mdatTrafPairs.push({
38006        mdat: mdat,
38007        traf: matchingTraf
38008      });
38009    });
38010    mdatTrafPairs.forEach(function (pair) {
38011      var mdat = pair.mdat;
38012      var traf = pair.traf;
38013      var tfhd = probe.findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf
38014
38015      var headerInfo = mp4Inspector.parseTfhd(tfhd[0]);
38016      var trackId = headerInfo.trackId;
38017      var tfdt = probe.findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
38018
38019      var baseMediaDecodeTime = tfdt.length > 0 ? mp4Inspector.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0;
38020      var truns = probe.findBox(traf, ['trun']);
38021      var samples;
38022      var seiNals; // Only parse video data for the chosen video track
38023
38024      if (videoTrackId === trackId && truns.length > 0) {
38025        samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
38026        seiNals = findSeiNals(mdat, samples, trackId);
38027
38028        if (!captionNals[trackId]) {
38029          captionNals[trackId] = [];
38030        }
38031
38032        captionNals[trackId] = captionNals[trackId].concat(seiNals);
38033      }
38034    });
38035    return captionNals;
38036  };
38037  /**
38038    * Parses out inband captions from an MP4 container and returns
38039    * caption objects that can be used by WebVTT and the TextTrack API.
38040    * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
38041    * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
38042    * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
38043    *
38044    * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
38045    * @param {Number} trackId - The id of the video track to parse
38046    * @param {Number} timescale - The timescale for the video track from the init segment
38047    *
38048    * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
38049    * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
38050    * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
38051    * @return {String} parsedCaptions[].text - The visible content of the caption
38052   **/
38053
38054
38055  var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) {
38056    var seiNals;
38057
38058    if (!trackId) {
38059      return null;
38060    }
38061
38062    seiNals = parseCaptionNals(segment, trackId);
38063    return {
38064      seiNals: seiNals[trackId],
38065      timescale: timescale
38066    };
38067  };
38068  /**
38069    * Converts SEI NALUs into captions that can be used by video.js
38070   **/
38071
38072
38073  var CaptionParser = function CaptionParser() {
38074    var isInitialized = false;
38075    var captionStream$$1; // Stores segments seen before trackId and timescale are set
38076
38077    var segmentCache; // Stores video track ID of the track being parsed
38078
38079    var trackId; // Stores the timescale of the track being parsed
38080
38081    var timescale; // Stores captions parsed so far
38082
38083    var parsedCaptions;
38084    /**
38085      * A method to indicate whether a CaptionParser has been initalized
38086      * @returns {Boolean}
38087     **/
38088
38089    this.isInitialized = function () {
38090      return isInitialized;
38091    };
38092    /**
38093      * Initializes the underlying CaptionStream, SEI NAL parsing
38094      * and management, and caption collection
38095     **/
38096
38097
38098    this.init = function () {
38099      captionStream$$1 = new CaptionStream$1();
38100      isInitialized = true; // Collect dispatched captions
38101
38102      captionStream$$1.on('data', function (event) {
38103        // Convert to seconds in the source's timescale
38104        event.startTime = event.startPts / timescale;
38105        event.endTime = event.endPts / timescale;
38106        parsedCaptions.captions.push(event);
38107        parsedCaptions.captionStreams[event.stream] = true;
38108      });
38109    };
38110    /**
38111      * Determines if a new video track will be selected
38112      * or if the timescale changed
38113      * @return {Boolean}
38114     **/
38115
38116
38117    this.isNewInit = function (videoTrackIds, timescales) {
38118      if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
38119        return false;
38120      }
38121
38122      return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
38123    };
38124    /**
38125      * Parses out SEI captions and interacts with underlying
38126      * CaptionStream to return dispatched captions
38127      *
38128      * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
38129      * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
38130      * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
38131      * @see parseEmbeddedCaptions
38132      * @see m2ts/caption-stream.js
38133     **/
38134
38135
38136    this.parse = function (segment, videoTrackIds, timescales) {
38137      var parsedData;
38138
38139      if (!this.isInitialized()) {
38140        return null; // This is not likely to be a video segment
38141      } else if (!videoTrackIds || !timescales) {
38142        return null;
38143      } else if (this.isNewInit(videoTrackIds, timescales)) {
38144        // Use the first video track only as there is no
38145        // mechanism to switch to other video tracks
38146        trackId = videoTrackIds[0];
38147        timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
38148        // data until we have one
38149      } else if (!trackId || !timescale) {
38150        segmentCache.push(segment);
38151        return null;
38152      } // Now that a timescale and trackId is set, parse cached segments
38153
38154
38155      while (segmentCache.length > 0) {
38156        var cachedSegment = segmentCache.shift();
38157        this.parse(cachedSegment, videoTrackIds, timescales);
38158      }
38159
38160      parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
38161
38162      if (parsedData === null || !parsedData.seiNals) {
38163        return null;
38164      }
38165
38166      this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
38167
38168      this.flushStream();
38169      return parsedCaptions;
38170    };
38171    /**
38172      * Pushes SEI NALUs onto CaptionStream
38173      * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
38174      * Assumes that `parseCaptionNals` has been called first
38175      * @see m2ts/caption-stream.js
38176      **/
38177
38178
38179    this.pushNals = function (nals) {
38180      if (!this.isInitialized() || !nals || nals.length === 0) {
38181        return null;
38182      }
38183
38184      nals.forEach(function (nal) {
38185        captionStream$$1.push(nal);
38186      });
38187    };
38188    /**
38189      * Flushes underlying CaptionStream to dispatch processed, displayable captions
38190      * @see m2ts/caption-stream.js
38191     **/
38192
38193
38194    this.flushStream = function () {
38195      if (!this.isInitialized()) {
38196        return null;
38197      }
38198
38199      captionStream$$1.flush();
38200    };
38201    /**
38202      * Reset caption buckets for new data
38203     **/
38204
38205
38206    this.clearParsedCaptions = function () {
38207      parsedCaptions.captions = [];
38208      parsedCaptions.captionStreams = {};
38209    };
38210    /**
38211      * Resets underlying CaptionStream
38212      * @see m2ts/caption-stream.js
38213     **/
38214
38215
38216    this.resetCaptionStream = function () {
38217      if (!this.isInitialized()) {
38218        return null;
38219      }
38220
38221      captionStream$$1.reset();
38222    };
38223    /**
38224      * Convenience method to clear all captions flushed from the
38225      * CaptionStream and still being parsed
38226      * @see m2ts/caption-stream.js
38227     **/
38228
38229
38230    this.clearAllCaptions = function () {
38231      this.clearParsedCaptions();
38232      this.resetCaptionStream();
38233    };
38234    /**
38235      * Reset caption parser
38236     **/
38237
38238
38239    this.reset = function () {
38240      segmentCache = [];
38241      trackId = null;
38242      timescale = null;
38243
38244      if (!parsedCaptions) {
38245        parsedCaptions = {
38246          captions: [],
38247          // CC1, CC2, CC3, CC4
vendor: 4,587 bytes, lines 38248-38399
38248          captionStreams: {}
38249        };
38250      } else {
38251        this.clearParsedCaptions();
38252      }
38253
38254      this.resetCaptionStream();
38255    };
38256
38257    this.reset();
38258  };
38259
38260  var captionParser = CaptionParser;
38261
38262  var mp4 = {
38263    generator: mp4Generator,
38264    probe: probe,
38265    Transmuxer: transmuxer.Transmuxer,
38266    AudioSegmentStream: transmuxer.AudioSegmentStream,
38267    VideoSegmentStream: transmuxer.VideoSegmentStream,
38268    CaptionParser: captionParser
38269  };
38270  var mp4_6 = mp4.CaptionParser;
38271
38272  var parsePid = function parsePid(packet) {
38273    var pid = packet[1] & 0x1f;
38274    pid <<= 8;
38275    pid |= packet[2];
38276    return pid;
38277  };
38278
38279  var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) {
38280    return !!(packet[1] & 0x40);
38281  };
38282
38283  var parseAdaptionField = function parseAdaptionField(packet) {
38284    var offset = 0; // if an adaption field is present, its length is specified by the
38285    // fifth byte of the TS packet header. The adaptation field is
38286    // used to add stuffing to PES packets that don't fill a complete
38287    // TS packet, and to specify some forms of timing and control data
38288    // that we do not currently use.
38289
38290    if ((packet[3] & 0x30) >>> 4 > 0x01) {
38291      offset += packet[4] + 1;
38292    }
38293
38294    return offset;
38295  };
38296
38297  var parseType$3 = function parseType(packet, pmtPid) {
38298    var pid = parsePid(packet);
38299
38300    if (pid === 0) {
38301      return 'pat';
38302    } else if (pid === pmtPid) {
38303      return 'pmt';
38304    } else if (pmtPid) {
38305      return 'pes';
38306    }
38307
38308    return null;
38309  };
38310
38311  var parsePat = function parsePat(packet) {
38312    var pusi = parsePayloadUnitStartIndicator(packet);
38313    var offset = 4 + parseAdaptionField(packet);
38314
38315    if (pusi) {
38316      offset += packet[offset] + 1;
38317    }
38318
38319    return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11];
38320  };
38321
38322  var parsePmt = function parsePmt(packet) {
38323    var programMapTable = {};
38324    var pusi = parsePayloadUnitStartIndicator(packet);
38325    var payloadOffset = 4 + parseAdaptionField(packet);
38326
38327    if (pusi) {
38328      payloadOffset += packet[payloadOffset] + 1;
38329    } // PMTs can be sent ahead of the time when they should actually
38330    // take effect. We don't believe this should ever be the case
38331    // for HLS but we'll ignore "forward" PMT declarations if we see
38332    // them. Future PMT declarations have the current_next_indicator
38333    // set to zero.
38334
38335
38336    if (!(packet[payloadOffset + 5] & 0x01)) {
38337      return;
38338    }
38339
38340    var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section
38341
38342    sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2];
38343    tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
38344    // long the program info descriptors are
38345
38346    programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table
38347
38348    var offset = 12 + programInfoLength;
38349
38350    while (offset < tableEnd) {
38351      var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type
38352
38353      programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry
38354      // skip past the elementary stream descriptors, if present
38355
38356      offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5;
38357    }
38358
38359    return programMapTable;
38360  };
38361
38362  var parsePesType = function parsePesType(packet, programMapTable) {
38363    var pid = parsePid(packet);
38364    var type = programMapTable[pid];
38365
38366    switch (type) {
38367      case streamTypes.H264_STREAM_TYPE:
38368        return 'video';
38369
38370      case streamTypes.ADTS_STREAM_TYPE:
38371        return 'audio';
38372
38373      case streamTypes.METADATA_STREAM_TYPE:
38374        return 'timed-metadata';
38375
38376      default:
38377        return null;
38378    }
38379  };
38380
38381  var parsePesTime = function parsePesTime(packet) {
38382    var pusi = parsePayloadUnitStartIndicator(packet);
38383
38384    if (!pusi) {
38385      return null;
38386    }
38387
38388    var offset = 4 + parseAdaptionField(packet);
38389
38390    if (offset >= packet.byteLength) {
38391      // From the H 222.0 MPEG-TS spec
38392      // "For transport stream packets carrying PES packets, stuffing is needed when there
38393      //  is insufficient PES packet data to completely fill the transport stream packet
38394      //  payload bytes. Stuffing is accomplished by defining an adaptation field longer than
38395      //  the sum of the lengths of the data elements in it, so that the payload bytes
38396      //  remaining after the adaptation field exactly accommodates the available PES packet
38397      //  data."
38398      //
38399      // If the offset is >
vendor: 54,401 bytes, lines 38399-40144
38399= the length of the packet, then the packet contains no data
38400      // and instead is just adaption field stuffing bytes
38401      return null;
38402    }
38403
38404    var pes = null;
38405    var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value
38406    // and a DTS value. Determine what combination of values is
38407    // available to work with.
38408
38409    ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
38410    // performs all bitwise operations on 32-bit integers but javascript
38411    // supports a much greater range (52-bits) of integer using standard
38412    // mathematical operations.
38413    // We construct a 31-bit value using bitwise operators over the 31
38414    // most significant bits and then multiply by 4 (equal to a left-shift
38415    // of 2) before we add the final 2 least significant bits of the
38416    // timestamp (equal to an OR.)
38417
38418    if (ptsDtsFlags & 0xC0) {
38419      pes = {}; // the PTS and DTS are not written out directly. For information
38420      // on how they are encoded, see
38421      // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
38422
38423      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;
38424      pes.pts *= 4; // Left shift by 2
38425
38426      pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
38427
38428      pes.dts = pes.pts;
38429
38430      if (ptsDtsFlags & 0x40) {
38431        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;
38432        pes.dts *= 4; // Left shift by 2
38433
38434        pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
38435      }
38436    }
38437
38438    return pes;
38439  };
38440
38441  var parseNalUnitType = function parseNalUnitType(type) {
38442    switch (type) {
38443      case 0x05:
38444        return 'slice_layer_without_partitioning_rbsp_idr';
38445
38446      case 0x06:
38447        return 'sei_rbsp';
38448
38449      case 0x07:
38450        return 'seq_parameter_set_rbsp';
38451
38452      case 0x08:
38453        return 'pic_parameter_set_rbsp';
38454
38455      case 0x09:
38456        return 'access_unit_delimiter_rbsp';
38457
38458      default:
38459        return null;
38460    }
38461  };
38462
38463  var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) {
38464    var offset = 4 + parseAdaptionField(packet);
38465    var frameBuffer = packet.subarray(offset);
38466    var frameI = 0;
38467    var frameSyncPoint = 0;
38468    var foundKeyFrame = false;
38469    var nalType; // advance the sync point to a NAL start, if necessary
38470
38471    for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
38472      if (frameBuffer[frameSyncPoint + 2] === 1) {
38473        // the sync point is properly aligned
38474        frameI = frameSyncPoint + 5;
38475        break;
38476      }
38477    }
38478
38479    while (frameI < frameBuffer.byteLength) {
38480      // look at the current byte to determine if we've hit the end of
38481      // a NAL unit boundary
38482      switch (frameBuffer[frameI]) {
38483        case 0:
38484          // skip past non-sync sequences
38485          if (frameBuffer[frameI - 1] !== 0) {
38486            frameI += 2;
38487            break;
38488          } else if (frameBuffer[frameI - 2] !== 0) {
38489            frameI++;
38490            break;
38491          }
38492
38493          if (frameSyncPoint + 3 !== frameI - 2) {
38494            nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
38495
38496            if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
38497              foundKeyFrame = true;
38498            }
38499          } // drop trailing zeroes
38500
38501
38502          do {
38503            frameI++;
38504          } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
38505
38506          frameSyncPoint = frameI - 2;
38507          frameI += 3;
38508          break;
38509
38510        case 1:
38511          // skip past non-sync sequences
38512          if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
38513            frameI += 3;
38514            break;
38515          }
38516
38517          nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
38518
38519          if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
38520            foundKeyFrame = true;
38521          }
38522
38523          frameSyncPoint = frameI - 2;
38524          frameI += 3;
38525          break;
38526
38527        default:
38528          // the current byte isn't a one or zero, so it cannot be part
38529          // of a sync sequence
38530          frameI += 3;
38531          break;
38532      }
38533    }
38534
38535    frameBuffer = frameBuffer.subarray(frameSyncPoint);
38536    frameI -= frameSyncPoint;
38537    frameSyncPoint = 0; // parse the final nal
38538
38539    if (frameBuffer && frameBuffer.byteLength > 3) {
38540      nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
38541
38542      if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
38543        foundKeyFrame = true;
38544      }
38545    }
38546
38547    return foundKeyFrame;
38548  };
38549
38550  var probe$1 = {
38551    parseType: parseType$3,
38552    parsePat: parsePat,
38553    parsePmt: parsePmt,
38554    parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
38555    parsePesType: parsePesType,
38556    parsePesTime: parsePesTime,
38557    videoPacketContainsKeyFrame: videoPacketContainsKeyFrame
38558  };
38559
38560  var handleRollover$1 = timestampRolloverStream.handleRollover;
38561  var probe$2 = {};
38562  probe$2.ts = probe$1;
38563  probe$2.aac = utils;
38564  var PES_TIMESCALE = 90000,
38565      MP2T_PACKET_LENGTH$1 = 188,
38566      // bytes
38567  SYNC_BYTE$1 = 0x47;
38568  /**
38569   * walks through segment data looking for pat and pmt packets to parse out
38570   * program map table information
38571   */
38572
38573  var parsePsi_ = function parsePsi_(bytes, pmt) {
38574    var startIndex = 0,
38575        endIndex = MP2T_PACKET_LENGTH$1,
38576        packet,
38577        type;
38578
38579    while (endIndex < bytes.byteLength) {
38580      // Look for a pair of start and end sync bytes in the data..
38581      if (bytes[startIndex] === SYNC_BYTE$1 && bytes[endIndex] === SYNC_BYTE$1) {
38582        // We found a packet
38583        packet = bytes.subarray(startIndex, endIndex);
38584        type = probe$2.ts.parseType(packet, pmt.pid);
38585
38586        switch (type) {
38587          case 'pat':
38588            if (!pmt.pid) {
38589              pmt.pid = probe$2.ts.parsePat(packet);
38590            }
38591
38592            break;
38593
38594          case 'pmt':
38595            if (!pmt.table) {
38596              pmt.table = probe$2.ts.parsePmt(packet);
38597            }
38598
38599            break;
38600
38601          default:
38602            break;
38603        } // Found the pat and pmt, we can stop walking the segment
38604
38605
38606        if (pmt.pid && pmt.table) {
38607          return;
38608        }
38609
38610        startIndex += MP2T_PACKET_LENGTH$1;
38611        endIndex += MP2T_PACKET_LENGTH$1;
38612        continue;
38613      } // If we get here, we have somehow become de-synchronized and we need to step
38614      // forward one byte at a time until we find a pair of sync bytes that denote
38615      // a packet
38616
38617
38618      startIndex++;
38619      endIndex++;
38620    }
38621  };
38622  /**
38623   * walks through the segment data from the start and end to get timing information
38624   * for the first and last audio pes packets
38625   */
38626
38627
38628  var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) {
38629    var startIndex = 0,
38630        endIndex = MP2T_PACKET_LENGTH$1,
38631        packet,
38632        type,
38633        pesType,
38634        pusi,
38635        parsed;
38636    var endLoop = false; // Start walking from start of segment to get first audio packet
38637
38638    while (endIndex <= bytes.byteLength) {
38639      // Look for a pair of start and end sync bytes in the data..
38640      if (bytes[startIndex] === SYNC_BYTE$1 && (bytes[endIndex] === SYNC_BYTE$1 || endIndex === bytes.byteLength)) {
38641        // We found a packet
38642        packet = bytes.subarray(startIndex, endIndex);
38643        type = probe$2.ts.parseType(packet, pmt.pid);
38644
38645        switch (type) {
38646          case 'pes':
38647            pesType = probe$2.ts.parsePesType(packet, pmt.table);
38648            pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
38649
38650            if (pesType === 'audio' && pusi) {
38651              parsed = probe$2.ts.parsePesTime(packet);
38652
38653              if (parsed) {
38654                parsed.type = 'audio';
38655                result.audio.push(parsed);
38656                endLoop = true;
38657              }
38658            }
38659
38660            break;
38661
38662          default:
38663            break;
38664        }
38665
38666        if (endLoop) {
38667          break;
38668        }
38669
38670        startIndex += MP2T_PACKET_LENGTH$1;
38671        endIndex += MP2T_PACKET_LENGTH$1;
38672        continue;
38673      } // If we get here, we have somehow become de-synchronized and we need to step
38674      // forward one byte at a time until we find a pair of sync bytes that denote
38675      // a packet
38676
38677
38678      startIndex++;
38679      endIndex++;
38680    } // Start walking from end of segment to get last audio packet
38681
38682
38683    endIndex = bytes.byteLength;
38684    startIndex = endIndex - MP2T_PACKET_LENGTH$1;
38685    endLoop = false;
38686
38687    while (startIndex >= 0) {
38688      // Look for a pair of start and end sync bytes in the data..
38689      if (bytes[startIndex] === SYNC_BYTE$1 && (bytes[endIndex] === SYNC_BYTE$1 || endIndex === bytes.byteLength)) {
38690        // We found a packet
38691        packet = bytes.subarray(startIndex, endIndex);
38692        type = probe$2.ts.parseType(packet, pmt.pid);
38693
38694        switch (type) {
38695          case 'pes':
38696            pesType = probe$2.ts.parsePesType(packet, pmt.table);
38697            pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
38698
38699            if (pesType === 'audio' && pusi) {
38700              parsed = probe$2.ts.parsePesTime(packet);
38701
38702              if (parsed) {
38703                parsed.type = 'audio';
38704                result.audio.push(parsed);
38705                endLoop = true;
38706              }
38707            }
38708
38709            break;
38710
38711          default:
38712            break;
38713        }
38714
38715        if (endLoop) {
38716          break;
38717        }
38718
38719        startIndex -= MP2T_PACKET_LENGTH$1;
38720        endIndex -= MP2T_PACKET_LENGTH$1;
38721        continue;
38722      } // If we get here, we have somehow become de-synchronized and we need to step
38723      // forward one byte at a time until we find a pair of sync bytes that denote
38724      // a packet
38725
38726
38727      startIndex--;
38728      endIndex--;
38729    }
38730  };
38731  /**
38732   * walks through the segment data from the start and end to get timing information
38733   * for the first and last video pes packets as well as timing information for the first
38734   * key frame.
38735   */
38736
38737
38738  var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) {
38739    var startIndex = 0,
38740        endIndex = MP2T_PACKET_LENGTH$1,
38741        packet,
38742        type,
38743        pesType,
38744        pusi,
38745        parsed,
38746        frame,
38747        i,
38748        pes;
38749    var endLoop = false;
38750    var currentFrame = {
38751      data: [],
38752      size: 0
38753    }; // Start walking from start of segment to get first video packet
38754
38755    while (endIndex < bytes.byteLength) {
38756      // Look for a pair of start and end sync bytes in the data..
38757      if (bytes[startIndex] === SYNC_BYTE$1 && bytes[endIndex] === SYNC_BYTE$1) {
38758        // We found a packet
38759        packet = bytes.subarray(startIndex, endIndex);
38760        type = probe$2.ts.parseType(packet, pmt.pid);
38761
38762        switch (type) {
38763          case 'pes':
38764            pesType = probe$2.ts.parsePesType(packet, pmt.table);
38765            pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
38766
38767            if (pesType === 'video') {
38768              if (pusi && !endLoop) {
38769                parsed = probe$2.ts.parsePesTime(packet);
38770
38771                if (parsed) {
38772                  parsed.type = 'video';
38773                  result.video.push(parsed);
38774                  endLoop = true;
38775                }
38776              }
38777
38778              if (!result.firstKeyFrame) {
38779                if (pusi) {
38780                  if (currentFrame.size !== 0) {
38781                    frame = new Uint8Array(currentFrame.size);
38782                    i = 0;
38783
38784                    while (currentFrame.data.length) {
38785                      pes = currentFrame.data.shift();
38786                      frame.set(pes, i);
38787                      i += pes.byteLength;
38788                    }
38789
38790                    if (probe$2.ts.videoPacketContainsKeyFrame(frame)) {
38791                      result.firstKeyFrame = probe$2.ts.parsePesTime(frame);
38792                      result.firstKeyFrame.type = 'video';
38793                    }
38794
38795                    currentFrame.size = 0;
38796                  }
38797                }
38798
38799                currentFrame.data.push(packet);
38800                currentFrame.size += packet.byteLength;
38801              }
38802            }
38803
38804            break;
38805
38806          default:
38807            break;
38808        }
38809
38810        if (endLoop && result.firstKeyFrame) {
38811          break;
38812        }
38813
38814        startIndex += MP2T_PACKET_LENGTH$1;
38815        endIndex += MP2T_PACKET_LENGTH$1;
38816        continue;
38817      } // If we get here, we have somehow become de-synchronized and we need to step
38818      // forward one byte at a time until we find a pair of sync bytes that denote
38819      // a packet
38820
38821
38822      startIndex++;
38823      endIndex++;
38824    } // Start walking from end of segment to get last video packet
38825
38826
38827    endIndex = bytes.byteLength;
38828    startIndex = endIndex - MP2T_PACKET_LENGTH$1;
38829    endLoop = false;
38830
38831    while (startIndex >= 0) {
38832      // Look for a pair of start and end sync bytes in the data..
38833      if (bytes[startIndex] === SYNC_BYTE$1 && bytes[endIndex] === SYNC_BYTE$1) {
38834        // We found a packet
38835        packet = bytes.subarray(startIndex, endIndex);
38836        type = probe$2.ts.parseType(packet, pmt.pid);
38837
38838        switch (type) {
38839          case 'pes':
38840            pesType = probe$2.ts.parsePesType(packet, pmt.table);
38841            pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet);
38842
38843            if (pesType === 'video' && pusi) {
38844              parsed = probe$2.ts.parsePesTime(packet);
38845
38846              if (parsed) {
38847                parsed.type = 'video';
38848                result.video.push(parsed);
38849                endLoop = true;
38850              }
38851            }
38852
38853            break;
38854
38855          default:
38856            break;
38857        }
38858
38859        if (endLoop) {
38860          break;
38861        }
38862
38863        startIndex -= MP2T_PACKET_LENGTH$1;
38864        endIndex -= MP2T_PACKET_LENGTH$1;
38865        continue;
38866      } // If we get here, we have somehow become de-synchronized and we need to step
38867      // forward one byte at a time until we find a pair of sync bytes that denote
38868      // a packet
38869
38870
38871      startIndex--;
38872      endIndex--;
38873    }
38874  };
38875  /**
38876   * Adjusts the timestamp information for the segment to account for
38877   * rollover and convert to seconds based on pes packet timescale (90khz clock)
38878   */
38879
38880
38881  var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) {
38882    if (segmentInfo.audio && segmentInfo.audio.length) {
38883      var audioBaseTimestamp = baseTimestamp;
38884
38885      if (typeof audioBaseTimestamp === 'undefined') {
38886        audioBaseTimestamp = segmentInfo.audio[0].dts;
38887      }
38888
38889      segmentInfo.audio.forEach(function (info) {
38890        info.dts = handleRollover$1(info.dts, audioBaseTimestamp);
38891        info.pts = handleRollover$1(info.pts, audioBaseTimestamp); // time in seconds
38892
38893        info.dtsTime = info.dts / PES_TIMESCALE;
38894        info.ptsTime = info.pts / PES_TIMESCALE;
38895      });
38896    }
38897
38898    if (segmentInfo.video && segmentInfo.video.length) {
38899      var videoBaseTimestamp = baseTimestamp;
38900
38901      if (typeof videoBaseTimestamp === 'undefined') {
38902        videoBaseTimestamp = segmentInfo.video[0].dts;
38903      }
38904
38905      segmentInfo.video.forEach(function (info) {
38906        info.dts = handleRollover$1(info.dts, videoBaseTimestamp);
38907        info.pts = handleRollover$1(info.pts, videoBaseTimestamp); // time in seconds
38908
38909        info.dtsTime = info.dts / PES_TIMESCALE;
38910        info.ptsTime = info.pts / PES_TIMESCALE;
38911      });
38912
38913      if (segmentInfo.firstKeyFrame) {
38914        var frame = segmentInfo.firstKeyFrame;
38915        frame.dts = handleRollover$1(frame.dts, videoBaseTimestamp);
38916        frame.pts = handleRollover$1(frame.pts, videoBaseTimestamp); // time in seconds
38917
38918        frame.dtsTime = frame.dts / PES_TIMESCALE;
38919        frame.ptsTime = frame.dts / PES_TIMESCALE;
38920      }
38921    }
38922  };
38923  /**
38924   * inspects the aac data stream for start and end time information
38925   */
38926
38927
38928  var inspectAac_ = function inspectAac_(bytes) {
38929    var endLoop = false,
38930        audioCount = 0,
38931        sampleRate = null,
38932        timestamp = null,
38933        frameSize = 0,
38934        byteIndex = 0,
38935        packet;
38936
38937    while (bytes.length - byteIndex >= 3) {
38938      var type = probe$2.aac.parseType(bytes, byteIndex);
38939
38940      switch (type) {
38941        case 'timed-metadata':
38942          // Exit early because we don't have enough to parse
38943          // the ID3 tag header
38944          if (bytes.length - byteIndex < 10) {
38945            endLoop = true;
38946            break;
38947          }
38948
38949          frameSize = probe$2.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
38950          // to emit a full packet
38951
38952          if (frameSize > bytes.length) {
38953            endLoop = true;
38954            break;
38955          }
38956
38957          if (timestamp === null) {
38958            packet = bytes.subarray(byteIndex, byteIndex + frameSize);
38959            timestamp = probe$2.aac.parseAacTimestamp(packet);
38960          }
38961
38962          byteIndex += frameSize;
38963          break;
38964
38965        case 'audio':
38966          // Exit early because we don't have enough to parse
38967          // the ADTS frame header
38968          if (bytes.length - byteIndex < 7) {
38969            endLoop = true;
38970            break;
38971          }
38972
38973          frameSize = probe$2.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
38974          // to emit a full packet
38975
38976          if (frameSize > bytes.length) {
38977            endLoop = true;
38978            break;
38979          }
38980
38981          if (sampleRate === null) {
38982            packet = bytes.subarray(byteIndex, byteIndex + frameSize);
38983            sampleRate = probe$2.aac.parseSampleRate(packet);
38984          }
38985
38986          audioCount++;
38987          byteIndex += frameSize;
38988          break;
38989
38990        default:
38991          byteIndex++;
38992          break;
38993      }
38994
38995      if (endLoop) {
38996        return null;
38997      }
38998    }
38999
39000    if (sampleRate === null || timestamp === null) {
39001      return null;
39002    }
39003
39004    var audioTimescale = PES_TIMESCALE / sampleRate;
39005    var result = {
39006      audio: [{
39007        type: 'audio',
39008        dts: timestamp,
39009        pts: timestamp
39010      }, {
39011        type: 'audio',
39012        dts: timestamp + audioCount * 1024 * audioTimescale,
39013        pts: timestamp + audioCount * 1024 * audioTimescale
39014      }]
39015    };
39016    return result;
39017  };
39018  /**
39019   * inspects the transport stream segment data for start and end time information
39020   * of the audio and video tracks (when present) as well as the first key frame's
39021   * start time.
39022   */
39023
39024
39025  var inspectTs_ = function inspectTs_(bytes) {
39026    var pmt = {
39027      pid: null,
39028      table: null
39029    };
39030    var result = {};
39031    parsePsi_(bytes, pmt);
39032
39033    for (var pid in pmt.table) {
39034      if (pmt.table.hasOwnProperty(pid)) {
39035        var type = pmt.table[pid];
39036
39037        switch (type) {
39038          case streamTypes.H264_STREAM_TYPE:
39039            result.video = [];
39040            parseVideoPes_(bytes, pmt, result);
39041
39042            if (result.video.length === 0) {
39043              delete result.video;
39044            }
39045
39046            break;
39047
39048          case streamTypes.ADTS_STREAM_TYPE:
39049            result.audio = [];
39050            parseAudioPes_(bytes, pmt, result);
39051
39052            if (result.audio.length === 0) {
39053              delete result.audio;
39054            }
39055
39056            break;
39057
39058          default:
39059            break;
39060        }
39061      }
39062    }
39063
39064    return result;
39065  };
39066  /**
39067   * Inspects segment byte data and returns an object with start and end timing information
39068   *
39069   * @param {Uint8Array} bytes The segment byte data
39070   * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame
39071   *  timestamps for rollover. This value must be in 90khz clock.
39072   * @return {Object} Object containing start and end frame timing info of segment.
39073   */
39074
39075
39076  var inspect = function inspect(bytes, baseTimestamp) {
39077    var isAacData = probe$2.aac.isLikelyAacData(bytes);
39078    var result;
39079
39080    if (isAacData) {
39081      result = inspectAac_(bytes);
39082    } else {
39083      result = inspectTs_(bytes);
39084    }
39085
39086    if (!result || !result.audio && !result.video) {
39087      return null;
39088    }
39089
39090    adjustTimestamp_(result, baseTimestamp);
39091    return result;
39092  };
39093
39094  var tsInspector = {
39095    inspect: inspect,
39096    parseAudioPes_: parseAudioPes_
39097  };
39098
39099  /*
39100   * pkcs7.pad
39101   * https://github.com/brightcove/pkcs7
39102   *
39103   * Copyright (c) 2014 Brightcove
39104   * Licensed under the apache2 license.
39105   */
39106  /**
39107   * Returns the subarray of a Uint8Array without PKCS#7 padding.
39108   * @param padded {Uint8Array} unencrypted bytes that have been padded
39109   * @return {Uint8Array} the unpadded bytes
39110   * @see http://tools.ietf.org/html/rfc5652
39111   */
39112
39113  function unpad(padded) {
39114    return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
39115  }
39116
39117  var classCallCheck = function classCallCheck(instance, Constructor) {
39118    if (!(instance instanceof Constructor)) {
39119      throw new TypeError("Cannot call a class as a function");
39120    }
39121  };
39122
39123  var createClass = function () {
39124    function defineProperties(target, props) {
39125      for (var i = 0; i < props.length; i++) {
39126        var descriptor = props[i];
39127        descriptor.enumerable = descriptor.enumerable || false;
39128        descriptor.configurable = true;
39129        if ("value" in descriptor) descriptor.writable = true;
39130        Object.defineProperty(target, descriptor.key, descriptor);
39131      }
39132    }
39133
39134    return function (Constructor, protoProps, staticProps) {
39135      if (protoProps) defineProperties(Constructor.prototype, protoProps);
39136      if (staticProps) defineProperties(Constructor, staticProps);
39137      return Constructor;
39138    };
39139  }();
39140
39141  var inherits = function inherits(subClass, superClass) {
39142    if (typeof superClass !== "function" && superClass !== null) {
39143      throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
39144    }
39145
39146    subClass.prototype = Object.create(superClass && superClass.prototype, {
39147      constructor: {
39148        value: subClass,
39149        enumerable: false,
39150        writable: true,
39151        configurable: true
39152      }
39153    });
39154    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
39155  };
39156
39157  var possibleConstructorReturn = function possibleConstructorReturn(self, call) {
39158    if (!self) {
39159      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
39160    }
39161
39162    return call && (typeof call === "object" || typeof call === "function") ? call : self;
39163  };
39164  /**
39165   * @file aes.js
39166   *
39167   * This file contains an adaptation of the AES decryption algorithm
39168   * from the Standford Javascript Cryptography Library. That work is
39169   * covered by the following copyright and permissions notice:
39170   *
39171   * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
39172   * All rights reserved.
39173   *
39174   * Redistribution and use in source and binary forms, with or without
39175   * modification, are permitted provided that the following conditions are
39176   * met:
39177   *
39178   * 1. Redistributions of source code must retain the above copyright
39179   *    notice, this list of conditions and the following disclaimer.
39180   *
39181   * 2. Redistributions in binary form must reproduce the above
39182   *    copyright notice, this list of conditions and the following
39183   *    disclaimer in the documentation and/or other materials provided
39184   *    with the distribution.
39185   *
39186   * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
39187   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
39188   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39189   * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
39190   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
39191   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
39192   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
39193   * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
39194   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
39195   * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
39196   * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39197   *
39198   * The views and conclusions contained in the software and documentation
39199   * are those of the authors and should not be interpreted as representing
39200   * official policies, either expressed or implied, of the authors.
39201   */
39202
39203  /**
39204   * Expand the S-box tables.
39205   *
39206   * @private
39207   */
39208
39209
39210  var precompute = function precompute() {
39211    var tables = [[[], [], [], [], []], [[], [], [], [], []]];
39212    var encTable = tables[0];
39213    var decTable = tables[1];
39214    var sbox = encTable[4];
39215    var sboxInv = decTable[4];
39216    var i = void 0;
39217    var x = void 0;
39218    var xInv = void 0;
39219    var d = [];
39220    var th = [];
39221    var x2 = void 0;
39222    var x4 = void 0;
39223    var x8 = void 0;
39224    var s = void 0;
39225    var tEnc = void 0;
39226    var tDec = void 0; // Compute double and third tables
39227
39228    for (i = 0; i < 256; i++) {
39229      th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
39230    }
39231
39232    for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
39233      // Compute sbox
39234      s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
39235      s = s >> 8 ^ s & 255 ^ 99;
39236      sbox[x] = s;
39237      sboxInv[s] = x; // Compute MixColumns
39238
39239      x8 = d[x4 = d[x2 = d[x]]];
39240      tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
39241      tEnc = d[s] * 0x101 ^ s * 0x1010100;
39242
39243      for (i = 0; i < 4; i++) {
39244        encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
39245        decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
39246      }
39247    } // Compactify. Considerable speedup on Firefox.
39248
39249
39250    for (i = 0; i < 5; i++) {
39251      encTable[i] = encTable[i].slice(0);
39252      decTable[i] = decTable[i].slice(0);
39253    }
39254
39255    return tables;
39256  };
39257
39258  var aesTables = null;
39259  /**
39260   * Schedule out an AES key for both encryption and decryption. This
39261   * is a low-level class. Use a cipher mode to do bulk encryption.
39262   *
39263   * @class AES
39264   * @param key {Array} The key as an array of 4, 6 or 8 words.
39265   */
39266
39267  var AES = function () {
39268    function AES(key) {
39269      classCallCheck(this, AES);
39270      /**
39271       * The expanded S-box and inverse S-box tables. These will be computed
39272       * on the client so that we don't have to send them down the wire.
39273       *
39274       * There are two tables, _tables[0] is for encryption and
39275       * _tables[1] is for decryption.
39276       *
39277       * The first 4 sub-tables are the expanded S-box with MixColumns. The
39278       * last (_tables[01][4]) is the S-box itself.
39279       *
39280       * @private
39281       */
39282      // if we have yet to precompute the S-box tables
39283      // do so now
39284
39285      if (!aesTables) {
39286        aesTables = precompute();
39287      } // then make a copy of that object for use
39288
39289
39290      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()]];
39291      var i = void 0;
39292      var j = void 0;
39293      var tmp = void 0;
39294      var encKey = void 0;
39295      var decKey = void 0;
39296      var sbox = this._tables[0][4];
39297      var decTable = this._tables[1];
39298      var keyLen = key.length;
39299      var rcon = 1;
39300
39301      if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
39302        throw new Error('Invalid aes key size');
39303      }
39304
39305      encKey = key.slice(0);
39306      decKey = [];
39307      this._key = [encKey, decKey]; // schedule encryption keys
39308
39309      for (i = keyLen; i < 4 * keyLen + 28; i++) {
39310        tmp = encKey[i - 1]; // apply sbox
39311
39312        if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
39313          tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
39314
39315          if (i % keyLen === 0) {
39316            tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
39317            rcon = rcon << 1 ^ (rcon >> 7) * 283;
39318          }
39319        }
39320
39321        encKey[i] = encKey[i - keyLen] ^ tmp;
39322      } // schedule decryption keys
39323
39324
39325      for (j = 0; i; j++, i--) {
39326        tmp = encKey[j & 3 ? i : i - 4];
39327
39328        if (i <= 4 || j < 4) {
39329          decKey[j] = tmp;
39330        } else {
39331          decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
39332        }
39333      }
39334    }
39335    /**
39336     * Decrypt 16 bytes, specified as four 32-bit words.
39337     *
39338     * @param {Number} encrypted0 the first word to decrypt
39339     * @param {Number} encrypted1 the second word to decrypt
39340     * @param {Number} encrypted2 the third word to decrypt
39341     * @param {Number} encrypted3 the fourth word to decrypt
39342     * @param {Int32Array} out the array to write the decrypted words
39343     * into
39344     * @param {Number} offset the offset into the output array to start
39345     * writing results
39346     * @return {Array} The plaintext.
39347     */
39348
39349
39350    AES.prototype.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
39351      var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
39352
39353      var a = encrypted0 ^ key[0];
39354      var b = encrypted3 ^ key[1];
39355      var c = encrypted2 ^ key[2];
39356      var d = encrypted1 ^ key[3];
39357      var a2 = void 0;
39358      var b2 = void 0;
39359      var c2 = void 0; // key.length === 2 ?
39360
39361      var nInnerRounds = key.length / 4 - 2;
39362      var i = void 0;
39363      var kIndex = 4;
39364      var table = this._tables[1]; // load up the tables
39365
39366      var table0 = table[0];
39367      var table1 = table[1];
39368      var table2 = table[2];
39369      var table3 = table[3];
39370      var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
39371
39372      for (i = 0; i < nInnerRounds; i++) {
39373        a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
39374        b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
39375        c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
39376        d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
39377        kIndex += 4;
39378        a = a2;
39379        b = b2;
39380        c = c2;
39381      } // Last round.
39382
39383
39384      for (i = 0; i < 4; i++) {
39385        out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
39386        a2 = a;
39387        a = b;
39388        b = c;
39389        c = d;
39390        d = a2;
39391      }
39392    };
39393
39394    return AES;
39395  }();
39396  /**
39397   * @file stream.js
39398   */
39399
39400  /**
39401   * A lightweight readable stream implemention that handles event dispatching.
39402   *
39403   * @class Stream
39404   */
39405
39406
39407  var Stream$2 = function () {
39408    function Stream() {
39409      classCallCheck(this, Stream);
39410      this.listeners = {};
39411    }
39412    /**
39413     * Add a listener for a specified event type.
39414     *
39415     * @param {String} type the event name
39416     * @param {Function} listener the callback to be invoked when an event of
39417     * the specified type occurs
39418     */
39419
39420
39421    Stream.prototype.on = function on(type, listener) {
39422      if (!this.listeners[type]) {
39423        this.listeners[type] = [];
39424      }
39425
39426      this.listeners[type].push(listener);
39427    };
39428    /**
39429     * Remove a listener for a specified event type.
39430     *
39431     * @param {String} type the event name
39432     * @param {Function} listener  a function previously registered for this
39433     * type of event through `on`
39434     * @return {Boolean} if we could turn it off or not
39435     */
39436
39437
39438    Stream.prototype.off = function off(type, listener) {
39439      if (!this.listeners[type]) {
39440        return false;
39441      }
39442
39443      var index = this.listeners[type].indexOf(listener);
39444      this.listeners[type].splice(index, 1);
39445      return index > -1;
39446    };
39447    /**
39448     * Trigger an event of the specified type on this stream. Any additional
39449     * arguments to this function are passed as parameters to event listeners.
39450     *
39451     * @param {String} type the event name
39452     */
39453
39454
39455    Stream.prototype.trigger = function trigger(type) {
39456      var callbacks = this.listeners[type];
39457
39458      if (!callbacks) {
39459        return;
39460      } // Slicing the arguments on every invocation of this method
39461      // can add a significant amount of overhead. Avoid the
39462      // intermediate object creation for the common case of a
39463      // single callback argument
39464
39465
39466      if (arguments.length === 2) {
39467        var length = callbacks.length;
39468
39469        for (var i = 0; i < length; ++i) {
39470          callbacks[i].call(this, arguments[1]);
39471        }
39472      } else {
39473        var args = Array.prototype.slice.call(arguments, 1);
39474        var _length = callbacks.length;
39475
39476        for (var _i = 0; _i < _length; ++_i) {
39477          callbacks[_i].apply(this, args);
39478        }
39479      }
39480    };
39481    /**
39482     * Destroys the stream and cleans up.
39483     */
39484
39485
39486    Stream.prototype.dispose = function dispose() {
39487      this.listeners = {};
39488    };
39489    /**
39490     * Forwards all `data` events on this stream to the destination stream. The
39491     * destination stream should provide a method `push` to receive the data
39492     * events as they arrive.
39493     *
39494     * @param {Stream} destination the stream that will receive all `data` events
39495     * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
39496     */
39497
39498
39499    Stream.prototype.pipe = function pipe(destination) {
39500      this.on('data', function (data) {
39501        destination.push(data);
39502      });
39503    };
39504
39505    return Stream;
39506  }();
39507  /**
39508   * @file async-stream.js
39509   */
39510
39511  /**
39512   * A wrapper around the Stream class to use setTiemout
39513   * and run stream "jobs" Asynchronously
39514   *
39515   * @class AsyncStream
39516   * @extends Stream
39517   */
39518
39519
39520  var AsyncStream = function (_Stream) {
39521    inherits(AsyncStream, _Stream);
39522
39523    function AsyncStream() {
39524      classCallCheck(this, AsyncStream);
39525
39526      var _this = possibleConstructorReturn(this, _Stream.call(this, Stream$2));
39527
39528      _this.jobs = [];
39529      _this.delay = 1;
39530      _this.timeout_ = null;
39531      return _this;
39532    }
39533    /**
39534     * process an async job
39535     *
39536     * @private
39537     */
39538
39539
39540    AsyncStream.prototype.processJob_ = function processJob_() {
39541      this.jobs.shift()();
39542
39543      if (this.jobs.length) {
39544        this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
39545      } else {
39546        this.timeout_ = null;
39547      }
39548    };
39549    /**
39550     * push a job into the stream
39551     *
39552     * @param {Function} job the job to push into the stream
39553     */
39554
39555
39556    AsyncStream.prototype.push = function push(job) {
39557      this.jobs.push(job);
39558
39559      if (!this.timeout_) {
39560        this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
39561      }
39562    };
39563
39564    return AsyncStream;
39565  }(Stream$2);
39566  /**
39567   * @file decrypter.js
39568   *
39569   * An asynchronous implementation of AES-128 CBC decryption with
39570   * PKCS#7 padding.
39571   */
39572
39573  /**
39574   * Convert network-order (big-endian) bytes into their little-endian
39575   * representation.
39576   */
39577
39578
39579  var ntoh = function ntoh(word) {
39580    return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
39581  };
39582  /**
39583   * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
39584   *
39585   * @param {Uint8Array} encrypted the encrypted bytes
39586   * @param {Uint32Array} key the bytes of the decryption key
39587   * @param {Uint32Array} initVector the initialization vector (IV) to
39588   * use for the first round of CBC.
39589   * @return {Uint8Array} the decrypted bytes
39590   *
39591   * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
39592   * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
39593   * @see https://tools.ietf.org/html/rfc2315
39594   */
39595
39596
39597  var decrypt = function decrypt(encrypted, key, initVector) {
39598    // word-level access to the encrypted bytes
39599    var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
39600    var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
39601
39602    var decrypted = new Uint8Array(encrypted.byteLength);
39603    var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
39604    // decrypted data
39605
39606    var init0 = void 0;
39607    var init1 = void 0;
39608    var init2 = void 0;
39609    var init3 = void 0;
39610    var encrypted0 = void 0;
39611    var encrypted1 = void 0;
39612    var encrypted2 = void 0;
39613    var encrypted3 = void 0; // iteration variable
39614
39615    var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the
39616    // passed-in reference and easier access
39617
39618    init0 = initVector[0];
39619    init1 = initVector[1];
39620    init2 = initVector[2];
39621    init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
39622    // to each decrypted block
39623
39624    for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
39625      // convert big-endian (network order) words into little-endian
39626      // (javascript order)
39627      encrypted0 = ntoh(encrypted32[wordIx]);
39628      encrypted1 = ntoh(encrypted32[wordIx + 1]);
39629      encrypted2 = ntoh(encrypted32[wordIx + 2]);
39630      encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
39631
39632      decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
39633      // plaintext
39634
39635      decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
39636      decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
39637      decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
39638      decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
39639
39640      init0 = encrypted0;
39641      init1 = encrypted1;
39642      init2 = encrypted2;
39643      init3 = encrypted3;
39644    }
39645
39646    return decrypted;
39647  };
39648  /**
39649   * The `Decrypter` class that manages decryption of AES
39650   * data through `AsyncStream` objects and the `decrypt`
39651   * function
39652   *
39653   * @param {Uint8Array} encrypted the encrypted bytes
39654   * @param {Uint32Array} key the bytes of the decryption key
39655   * @param {Uint32Array} initVector the initialization vector (IV) to
39656   * @param {Function} done the function to run when done
39657   * @class Decrypter
39658   */
39659
39660
39661  var Decrypter = function () {
39662    function Decrypter(encrypted, key, initVector, done) {
39663      classCallCheck(this, Decrypter);
39664      var step = Decrypter.STEP;
39665      var encrypted32 = new Int32Array(encrypted.buffer);
39666      var decrypted = new Uint8Array(encrypted.byteLength);
39667      var i = 0;
39668      this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously
39669
39670      this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
39671
39672      for (i = step; i < encrypted32.length; i += step) {
39673        initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
39674        this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
39675      } // invoke the done() callback when everything is finished
39676
39677
39678      this.asyncStream_.push(function () {
39679        // remove pkcs#7 padding from the decrypted bytes
39680        done(null, unpad(decrypted));
39681      });
39682    }
39683    /**
39684     * a getter for step the maximum number of bytes to process at one time
39685     *
39686     * @return {Number} the value of step 32000
39687     */
39688
39689    /**
39690     * @private
39691     */
39692
39693
39694    Decrypter.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
39695      return function () {
39696        var bytes = decrypt(encrypted, key, initVector);
39697        decrypted.set(bytes, encrypted.byteOffset);
39698      };
39699    };
39700
39701    createClass(Decrypter, null, [{
39702      key: 'STEP',
39703      get: function get$$1() {
39704        // 4 * 8000;
39705        return 32000;
39706      }
39707    }]);
39708    return Decrypter;
39709  }();
39710
39711  /**
39712   * @videojs/http-streaming
39713   * @version 1.9.3
39714   * @copyright 2019 Brightcove, Inc
39715   * @license Apache-2.0
39716   */
39717  /**
39718   * @file resolve-url.js - Handling how URLs are resolved and manipulated
39719   */
39720
39721  var resolveUrl$1 = function resolveUrl(baseURL, relativeURL) {
39722    // return early if we don't need to resolve
39723    if (/^[a-z]+:/i.test(relativeURL)) {
39724      return relativeURL;
39725    } // if the base URL is relative then combine with the current location
39726
39727
39728    if (!/\/\//i.test(baseURL)) {
39729      baseURL = urlToolkit.buildAbsoluteURL(window$1.location.href, baseURL);
39730    }
39731
39732    return urlToolkit.buildAbsoluteURL(baseURL, relativeURL);
39733  };
39734  /**
39735   * Checks whether xhr request was redirected and returns correct url depending
39736   * on `handleManifestRedirects` option
39737   *
39738   * @api private
39739   *
39740   * @param  {String} url - an url being requested
39741   * @param  {XMLHttpRequest} req - xhr request result
39742   *
39743   * @return {String}
39744   */
39745
39746
39747  var resolveManifestRedirect = function resolveManifestRedirect(handleManifestRedirect, url, req) {
39748    // To understand how the responseURL below is set and generated:
39749    // - https://fetch.spec.whatwg.org/#concept-response-url
39750    // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
39751    if (handleManifestRedirect && req.responseURL && url !== req.responseURL) {
39752      return req.responseURL;
39753    }
39754
39755    return url;
39756  };
39757
39758  var classCallCheck$1 = function classCallCheck(instance, Constructor) {
39759    if (!(instance instanceof Constructor)) {
39760      throw new TypeError("Cannot call a class as a function");
39761    }
39762  };
39763
39764  var createClass$1 = function () {
39765    function defineProperties(target, props) {
39766      for (var i = 0; i < props.length; i++) {
39767        var descriptor = props[i];
39768        descriptor.enumerable = descriptor.enumerable || false;
39769        descriptor.configurable = true;
39770        if ("value" in descriptor) descriptor.writable = true;
39771        Object.defineProperty(target, descriptor.key, descriptor);
39772      }
39773    }
39774
39775    return function (Constructor, protoProps, staticProps) {
39776      if (protoProps) defineProperties(Constructor.prototype, protoProps);
39777      if (staticProps) defineProperties(Constructor, staticProps);
39778      return Constructor;
39779    };
39780  }();
39781
39782  var get$1 = function get(object, property, receiver) {
39783    if (object === null) object = Function.prototype;
39784    var desc = Object.getOwnPropertyDescriptor(object, property);
39785
39786    if (desc === undefined) {
39787      var parent = Object.getPrototypeOf(object);
39788
39789      if (parent === null) {
39790        return undefined;
39791      } else {
39792        return get(parent, property, receiver);
39793      }
39794    } else if ("value" in desc) {
39795      return desc.value;
39796    } else {
39797      var getter = desc.get;
39798
39799      if (getter === undefined) {
39800        return undefined;
39801      }
39802
39803      return getter.call(receiver);
39804    }
39805  };
39806
39807  var inherits$1 = function inherits(subClass, superClass) {
39808    if (typeof superClass !== "function" && superClass !== null) {
39809      throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
39810    }
39811
39812    subClass.prototype = Object.create(superClass && superClass.prototype, {
39813      constructor: {
39814        value: subClass,
39815        enumerable: false,
39816        writable: true,
39817        configurable: true
39818      }
39819    });
39820    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
39821  };
39822
39823  var possibleConstructorReturn$1 = function possibleConstructorReturn(self, call) {
39824    if (!self) {
39825      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
39826    }
39827
39828    return call && (typeof call === "object" || typeof call === "function") ? call : self;
39829  };
39830
39831  var slicedToArray = function () {
39832    function sliceIterator(arr, i) {
39833      var _arr = [];
39834      var _n = true;
39835      var _d = false;
39836      var _e = undefined;
39837
39838      try {
39839        for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
39840          _arr.push(_s.value);
39841
39842          if (i && _arr.length === i) break;
39843        }
39844      } catch (err) {
39845        _d = true;
39846        _e = err;
39847      } finally {
39848        try {
39849          if (!_n && _i["return"]) _i["return"]();
39850        } finally {
39851          if (_d) throw _e;
39852        }
39853      }
39854
39855      return _arr;
39856    }
39857
39858    return function (arr, i) {
39859      if (Array.isArray(arr)) {
39860        return arr;
39861      } else if (Symbol.iterator in Object(arr)) {
39862        return sliceIterator(arr, i);
39863      } else {
39864        throw new TypeError("Invalid attempt to destructure non-iterable instance");
39865      }
39866    };
39867  }();
39868  /**
39869   * @file playlist-loader.js
39870   *
39871   * A state machine that manages the loading, caching, and updating of
39872   * M3U8 playlists.
39873   *
39874   */
39875
39876
39877  var mergeOptions$1 = videojs$1.mergeOptions,
39878      EventTarget$1 = videojs$1.EventTarget,
39879      log$1 = videojs$1.log;
39880  /**
39881   * Loops through all supported media groups in master and calls the provided
39882   * callback for each group
39883   *
39884   * @param {Object} master
39885   *        The parsed master manifest object
39886   * @param {Function} callback
39887   *        Callback to call for each media group
39888   */
39889
39890  var forEachMediaGroup = function forEachMediaGroup(master, callback) {
39891    ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) {
39892      for (var groupKey in master.mediaGroups[mediaType]) {
39893        for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
39894          var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
39895          callback(mediaProperties, mediaType, groupKey, labelKey);
39896        }
39897      }
39898    });
39899  };
39900  /**
39901    * Returns a new array of segments that is the result of merging
39902    * properties from an older list of segments onto an updated
39903    * list. No properties on the updated playlist will be overridden.
39904    *
39905    * @param {Array} original the outdated list of segments
39906    * @param {Array} update the updated list of segments
39907    * @param {Number=} offset the index of the first update
39908    * segment in the original segment list. For non-live playlists,
39909    * this should always be zero and does not need to be
39910    * specified. For live playlists, it should be the difference
39911    * between the media sequence numbers in the original and updated
39912    * playlists.
39913    * @return a list of merged segment objects
39914    */
39915
39916
39917  var updateSegments = function updateSegments(original, update, offset) {
39918    var result = update.slice();
39919    offset = offset || 0;
39920    var length = Math.min(original.length, update.length + offset);
39921
39922    for (var i = offset; i < length; i++) {
39923      result[i - offset] = mergeOptions$1(original[i], result[i - offset]);
39924    }
39925
39926    return result;
39927  };
39928
39929  var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) {
39930    if (!segment.resolvedUri) {
39931      segment.resolvedUri = resolveUrl$1(baseUri, segment.uri);
39932    }
39933
39934    if (segment.key && !segment.key.resolvedUri) {
39935      segment.key.resolvedUri = resolveUrl$1(baseUri, segment.key.uri);
39936    }
39937
39938    if (segment.map && !segment.map.resolvedUri) {
39939      segment.map.resolvedUri = resolveUrl$1(baseUri, segment.map.uri);
39940    }
39941  };
39942  /**
39943    * Returns a new master playlist that is the result of merging an
39944    * updated media playlist into the original version. If the
39945    * updated media playlist does not match any of the playlist
39946    * entries in the original master playlist, null is returned.
39947    *
39948    * @param {Object} master a parsed master M3U8 object
39949    * @param {Object} media a parsed media M3U8 object
39950    * @return {Object} a new object that represents the original
39951    * master playlist with the updated media playlist merged in, or
39952    * null if the merge produced no change.
39953    */
39954
39955
39956  var updateMaster = function updateMaster(master, media) {
39957    var result = mergeOptions$1(master, {});
39958    var playlist = result.playlists[media.uri];
39959
39960    if (!playlist) {
39961      return null;
39962    } // consider the playlist unchanged if the number of segments is equal, the media
39963    // sequence number is unchanged, and this playlist hasn't become the end of the playlist
39964
39965
39966    if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.endList === media.endList && playlist.mediaSequence === media.mediaSequence) {
39967      return null;
39968    }
39969
39970    var mergedPlaylist = mergeOptions$1(playlist, media); // if the update could overlap existing segment information, merge the two segment lists
39971
39972    if (playlist.segments) {
39973      mergedPlaylist.segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence);
39974    } // resolve any segment URIs to prevent us from having to do it later
39975
39976
39977    mergedPlaylist.segments.forEach(function (segment) {
39978      resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
39979    }); // TODO Right now in the playlists array there are two references to each playlist, one
39980    // that is referenced by index, and one by URI. The index reference may no longer be
39981    // necessary.
39982
39983    for (var i = 0; i < result.playlists.length; i++) {
39984      if (result.playlists[i].uri === media.uri) {
39985        result.playlists[i] = mergedPlaylist;
39986      }
39987    }
39988
39989    result.playlists[media.uri] = mergedPlaylist;
39990    return result;
39991  };
39992
39993  var setupMediaPlaylists = function setupMediaPlaylists(master) {
39994    // setup by-URI lookups and resolve media playlist URIs
39995    var i = master.playlists.length;
39996
39997    while (i--) {
39998      var playlist = master.playlists[i];
39999      master.playlists[playlist.uri] = playlist;
40000      playlist.resolvedUri = resolveUrl$1(master.uri, playlist.uri);
40001      playlist.id = i;
40002
40003      if (!playlist.attributes) {
40004        // Although the spec states an #EXT-X-STREAM-INF tag MUST have a
40005        // BANDWIDTH attribute, we can play the stream without it. This means a poorly
40006        // formatted master playlist may not have an attribute list. An attributes
40007        // property is added here to prevent undefined references when we encounter
40008        // this scenario.
40009        playlist.attributes = {};
40010        log$1.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
40011      }
40012    }
40013  };
40014
40015  var resolveMediaGroupUris = function resolveMediaGroupUris(master) {
40016    forEachMediaGroup(master, function (properties) {
40017      if (properties.uri) {
40018        properties.resolvedUri = resolveUrl$1(master.uri, properties.uri);
40019      }
40020    });
40021  };
40022  /**
40023   * Calculates the time to wait before refreshing a live playlist
40024   *
40025   * @param {Object} media
40026   *        The current media
40027   * @param {Boolean} update
40028   *        True if there were any updates from the last refresh, false otherwise
40029   * @return {Number}
40030   *         The time in ms to wait before refreshing the live playlist
40031   */
40032
40033
40034  var refreshDelay = function refreshDelay(media, update) {
40035    var lastSegment = media.segments[media.segments.length - 1];
40036    var delay = void 0;
40037
40038    if (update && lastSegment && lastSegment.duration) {
40039      delay = lastSegment.duration * 1000;
40040    } else {
40041      // if the playlist is unchanged since the last reload or last segment duration
40042      // cannot be determined, try again after half the target duration
40043      delay = (media.targetDuration || 10) * 500;
40044    }
40045
40046    return delay;
40047  };
40048  /**
40049   * Load a playlist from a remote location
40050   *
40051   * @class PlaylistLoader
40052   * @extends Stream
40053   * @param {String} srcUrl the url to start with
40054   * @param {Boolean} withCredentials the withCredentials xhr option
40055   * @constructor
40056   */
40057
40058
40059  var PlaylistLoader = function (_EventTarget) {
40060    inherits$1(PlaylistLoader, _EventTarget);
40061
40062    function PlaylistLoader(srcUrl, hls) {
40063      var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
40064      classCallCheck$1(this, PlaylistLoader);
40065
40066      var _this = possibleConstructorReturn$1(this, (PlaylistLoader.__proto__ || Object.getPrototypeOf(PlaylistLoader)).call(this));
40067
40068      var _options$withCredenti = options.withCredentials,
40069          withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti,
40070          _options$handleManife = options.handleManifestRedirects,
40071          handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife;
40072      _this.srcUrl = srcUrl;
40073      _this.hls_ = hls;
40074      _this.withCredentials = withCredentials;
40075      _this.handleManifestRedirects = handleManifestRedirects;
40076      var hlsOptions = hls.options_;
40077      _this.customTagParsers = hlsOptions && hlsOptions.customTagParsers || [];
40078      _this.customTagMappers = hlsOptions && hlsOptions.customTagMappers || [];
40079
40080      if (!_this.srcUrl) {
40081        throw new Error('A non-empty playlist URL is required');
40082      } // initialize the loader state
40083
40084
40085      _this.state = 'HAVE_NOTHING'; // live playlist staleness timeout
40086
40087      _this.on('mediaupdatetimeout', function () {
40088        if (_this.state !== 'HAVE_METADATA') {
40089          // only refresh the media playlist if no other activity is going on
40090          return;
40091        }
40092
40093        _this.state = 'HAVE_CURRENT_METADATA';
40094        _this.request = _this.hls_.xhr({
40095          uri: resolveUrl$1(_this.master.uri, _this.media().uri),
40096          withCredentials: _this.withCredentials
40097        }, function (error, req) {
40098          // disposed
40099          if (!_this.request) {
40100            return;
40101          }
40102
40103          if (error) {
40104            return _this.playlistRequestError(_this.request, _this.media().uri, 'HAVE_METADATA');
40105          }
40106
40107          _this.haveMetadata(_this.request, _this.media().uri);
40108        });
40109      });
40110
40111      return _this;
40112    }
40113
40114    createClass$1(PlaylistLoader, [{
40115      key: 'playlistRequestError',
40116      value: function playlistRequestError(xhr, url, startingState) {
40117        // any in-flight request is now finished
40118        this.request = null;
40119
40120        if (startingState) {
40121          this.state = startingState;
40122        }
40123
40124        this.error = {
40125          playlist: this.master.playlists[url],
40126          status: xhr.status,
40127          message: 'HLS playlist request error at URL: ' + url,
40128          responseText: xhr.responseText,
40129          code: xhr.status >= 500 ? 4 : 2
40130        };
40131        this.trigger('error');
40132      } // update the playlist loader's state in response to a new or
40133      // updated playlist.
40134
40135    }, {
40136      key: 'haveMetadata',
40137      value: function haveMetadata(xhr, url) {
40138        var _this2 = this; // any in-flight request is now finished
40139
40140
40141        this.request = null;
40142        this.state = 'HAVE_METADATA';
40143        var parser = new Parser(); // adding custom tag parsers
40144
vendor: 4,977 bytes, lines 40145-40298
40145        this.customTagParsers.forEach(function (customParser) {
40146          return parser.addParser(customParser);
40147        }); // adding custom tag mappers
40148
40149        this.customTagMappers.forEach(function (mapper) {
40150          return parser.addTagMapper(mapper);
40151        });
40152        parser.push(xhr.responseText);
40153        parser.end();
40154        parser.manifest.uri = url; // m3u8-parser does not attach an attributes property to media playlists so make
40155        // sure that the property is attached to avoid undefined reference errors
40156
40157        parser.manifest.attributes = parser.manifest.attributes || {}; // merge this playlist into the master
40158
40159        var update = updateMaster(this.master, parser.manifest);
40160        this.targetDuration = parser.manifest.targetDuration;
40161
40162        if (update) {
40163          this.master = update;
40164          this.media_ = this.master.playlists[parser.manifest.uri];
40165        } else {
40166          this.trigger('playlistunchanged');
40167        } // refresh live playlists after a target duration passes
40168
40169
40170        if (!this.media().endList) {
40171          window$1.clearTimeout(this.mediaUpdateTimeout);
40172          this.mediaUpdateTimeout = window$1.setTimeout(function () {
40173            _this2.trigger('mediaupdatetimeout');
40174          }, refreshDelay(this.media(), !!update));
40175        }
40176
40177        this.trigger('loadedplaylist');
40178      }
40179      /**
40180       * Abort any outstanding work and clean up.
40181       */
40182
40183    }, {
40184      key: 'dispose',
40185      value: function dispose() {
40186        this.stopRequest();
40187        window$1.clearTimeout(this.mediaUpdateTimeout);
40188      }
40189    }, {
40190      key: 'stopRequest',
40191      value: function stopRequest() {
40192        if (this.request) {
40193          var oldRequest = this.request;
40194          this.request = null;
40195          oldRequest.onreadystatechange = null;
40196          oldRequest.abort();
40197        }
40198      }
40199      /**
40200       * When called without any arguments, returns the currently
40201       * active media playlist. When called with a single argument,
40202       * triggers the playlist loader to asynchronously switch to the
40203       * specified media playlist. Calling this method while the
40204       * loader is in the HAVE_NOTHING causes an error to be emitted
40205       * but otherwise has no effect.
40206       *
40207       * @param {Object=} playlist the parsed media playlist
40208       * object to switch to
40209       * @return {Playlist} the current loaded media
40210       */
40211
40212    }, {
40213      key: 'media',
40214      value: function media(playlist) {
40215        var _this3 = this; // getter
40216
40217
40218        if (!playlist) {
40219          return this.media_;
40220        } // setter
40221
40222
40223        if (this.state === 'HAVE_NOTHING') {
40224          throw new Error('Cannot switch media playlist from ' + this.state);
40225        }
40226
40227        var startingState = this.state; // find the playlist object if the target playlist has been
40228        // specified by URI
40229
40230        if (typeof playlist === 'string') {
40231          if (!this.master.playlists[playlist]) {
40232            throw new Error('Unknown playlist URI: ' + playlist);
40233          }
40234
40235          playlist = this.master.playlists[playlist];
40236        }
40237
40238        var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to fully loaded playlists immediately
40239
40240        if (this.master.playlists[playlist.uri].endList) {
40241          // abort outstanding playlist requests
40242          if (this.request) {
40243            this.request.onreadystatechange = null;
40244            this.request.abort();
40245            this.request = null;
40246          }
40247
40248          this.state = 'HAVE_METADATA';
40249          this.media_ = playlist; // trigger media change if the active media has been updated
40250
40251          if (mediaChange) {
40252            this.trigger('mediachanging');
40253            this.trigger('mediachange');
40254          }
40255
40256          return;
40257        } // switching to the active playlist is a no-op
40258
40259
40260        if (!mediaChange) {
40261          return;
40262        }
40263
40264        this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request
40265
40266        if (this.request) {
40267          if (playlist.resolvedUri === this.request.url) {
40268            // requesting to switch to the same playlist multiple times
40269            // has no effect after the first
40270            return;
40271          }
40272
40273          this.request.onreadystatechange = null;
40274          this.request.abort();
40275          this.request = null;
40276        } // request the new playlist
40277
40278
40279        if (this.media_) {
40280          this.trigger('mediachanging');
40281        }
40282
40283        this.request = this.hls_.xhr({
40284          uri: playlist.resolvedUri,
40285          withCredentials: this.withCredentials
40286        }, function (error, req) {
40287          // disposed
40288          if (!_this3.request) {
40289            return;
40290          }
40291
40292          playlist.resolvedUri = resolveManifestRedirect(_this3.handleManifestRedirects, playlist.resolvedUri, req);
40293
40294          if (error) {
40295            return _this3.playlistRequestError(_this3.request, playlist.uri, startingState);
40296          }
40297
40298          _this3.haveMetadata(req, playlist.uri);
vendor: 28,840 bytes, lines 40298-41268
40298 // fire loadedmetadata the first time a media playlist is loaded
40299
40300
40301          if (startingState === 'HAVE_MASTER') {
40302            _this3.trigger('loadedmetadata');
40303          } else {
40304            _this3.trigger('mediachange');
40305          }
40306        });
40307      }
40308      /**
40309       * pause loading of the playlist
40310       */
40311
40312    }, {
40313      key: 'pause',
40314      value: function pause() {
40315        this.stopRequest();
40316        window$1.clearTimeout(this.mediaUpdateTimeout);
40317
40318        if (this.state === 'HAVE_NOTHING') {
40319          // If we pause the loader before any data has been retrieved, its as if we never
40320          // started, so reset to an unstarted state.
40321          this.started = false;
40322        } // Need to restore state now that no activity is happening
40323
40324
40325        if (this.state === 'SWITCHING_MEDIA') {
40326          // if the loader was in the process of switching media, it should either return to
40327          // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media
40328          // playlist yet. This is determined by the existence of loader.media_
40329          if (this.media_) {
40330            this.state = 'HAVE_METADATA';
40331          } else {
40332            this.state = 'HAVE_MASTER';
40333          }
40334        } else if (this.state === 'HAVE_CURRENT_METADATA') {
40335          this.state = 'HAVE_METADATA';
40336        }
40337      }
40338      /**
40339       * start loading of the playlist
40340       */
40341
40342    }, {
40343      key: 'load',
40344      value: function load(isFinalRendition) {
40345        var _this4 = this;
40346
40347        window$1.clearTimeout(this.mediaUpdateTimeout);
40348        var media = this.media();
40349
40350        if (isFinalRendition) {
40351          var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
40352          this.mediaUpdateTimeout = window$1.setTimeout(function () {
40353            return _this4.load();
40354          }, delay);
40355          return;
40356        }
40357
40358        if (!this.started) {
40359          this.start();
40360          return;
40361        }
40362
40363        if (media && !media.endList) {
40364          this.trigger('mediaupdatetimeout');
40365        } else {
40366          this.trigger('loadedplaylist');
40367        }
40368      }
40369      /**
40370       * start loading of the playlist
40371       */
40372
40373    }, {
40374      key: 'start',
40375      value: function start() {
40376        var _this5 = this;
40377
40378        this.started = true; // request the specified URL
40379
40380        this.request = this.hls_.xhr({
40381          uri: this.srcUrl,
40382          withCredentials: this.withCredentials
40383        }, function (error, req) {
40384          // disposed
40385          if (!_this5.request) {
40386            return;
40387          } // clear the loader's request reference
40388
40389
40390          _this5.request = null;
40391
40392          if (error) {
40393            _this5.error = {
40394              status: req.status,
40395              message: 'HLS playlist request error at URL: ' + _this5.srcUrl,
40396              responseText: req.responseText,
40397              // MEDIA_ERR_NETWORK
40398              code: 2
40399            };
40400
40401            if (_this5.state === 'HAVE_NOTHING') {
40402              _this5.started = false;
40403            }
40404
40405            return _this5.trigger('error');
40406          }
40407
40408          var parser = new Parser(); // adding custom tag parsers
40409
40410          _this5.customTagParsers.forEach(function (customParser) {
40411            return parser.addParser(customParser);
40412          }); // adding custom tag mappers
40413
40414
40415          _this5.customTagMappers.forEach(function (mapper) {
40416            return parser.addTagMapper(mapper);
40417          });
40418
40419          parser.push(req.responseText);
40420          parser.end();
40421          _this5.state = 'HAVE_MASTER';
40422          _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req);
40423          parser.manifest.uri = _this5.srcUrl; // loaded a master playlist
40424
40425          if (parser.manifest.playlists) {
40426            _this5.master = parser.manifest;
40427            setupMediaPlaylists(_this5.master);
40428            resolveMediaGroupUris(_this5.master);
40429
40430            _this5.trigger('loadedplaylist');
40431
40432            if (!_this5.request) {
40433              // no media playlist was specifically selected so start
40434              // from the first listed one
40435              _this5.media(parser.manifest.playlists[0]);
40436            }
40437
40438            return;
40439          } // loaded a media playlist
40440          // infer a master playlist if none was previously requested
40441
40442
40443          _this5.master = {
40444            mediaGroups: {
40445              'AUDIO': {},
40446              'VIDEO': {},
40447              'CLOSED-CAPTIONS': {},
40448              'SUBTITLES': {}
40449            },
40450            uri: window$1.location.href,
40451            playlists: [{
40452              uri: _this5.srcUrl,
40453              id: 0,
40454              resolvedUri: _this5.srcUrl,
40455              // m3u8-parser does not attach an attributes property to media playlists so make
40456              // sure that the property is attached to avoid undefined reference errors
40457              attributes: {}
40458            }]
40459          };
40460          _this5.master.playlists[_this5.srcUrl] = _this5.master.playlists[0];
40461
40462          _this5.haveMetadata(req, _this5.srcUrl);
40463
40464          return _this5.trigger('loadedmetadata');
40465        });
40466      }
40467    }]);
40468    return PlaylistLoader;
40469  }(EventTarget$1);
40470  /**
40471   * @file playlist.js
40472   *
40473   * Playlist related utilities.
40474   */
40475
40476
40477  var createTimeRange = videojs$1.createTimeRange;
40478  /**
40479   * walk backward until we find a duration we can use
40480   * or return a failure
40481   *
40482   * @param {Playlist} playlist the playlist to walk through
40483   * @param {Number} endSequence the mediaSequence to stop walking on
40484   */
40485
40486  var backwardDuration = function backwardDuration(playlist, endSequence) {
40487    var result = 0;
40488    var i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following
40489    // the interval, use it
40490
40491    var segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline
40492    // information that is earlier than endSequence
40493
40494    if (segment) {
40495      if (typeof segment.start !== 'undefined') {
40496        return {
40497          result: segment.start,
40498          precise: true
40499        };
40500      }
40501
40502      if (typeof segment.end !== 'undefined') {
40503        return {
40504          result: segment.end - segment.duration,
40505          precise: true
40506        };
40507      }
40508    }
40509
40510    while (i--) {
40511      segment = playlist.segments[i];
40512
40513      if (typeof segment.end !== 'undefined') {
40514        return {
40515          result: result + segment.end,
40516          precise: true
40517        };
40518      }
40519
40520      result += segment.duration;
40521
40522      if (typeof segment.start !== 'undefined') {
40523        return {
40524          result: result + segment.start,
40525          precise: true
40526        };
40527      }
40528    }
40529
40530    return {
40531      result: result,
40532      precise: false
40533    };
40534  };
40535  /**
40536   * walk forward until we find a duration we can use
40537   * or return a failure
40538   *
40539   * @param {Playlist} playlist the playlist to walk through
40540   * @param {Number} endSequence the mediaSequence to stop walking on
40541   */
40542
40543
40544  var forwardDuration = function forwardDuration(playlist, endSequence) {
40545    var result = 0;
40546    var segment = void 0;
40547    var i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline
40548    // information
40549
40550    for (; i < playlist.segments.length; i++) {
40551      segment = playlist.segments[i];
40552
40553      if (typeof segment.start !== 'undefined') {
40554        return {
40555          result: segment.start - result,
40556          precise: true
40557        };
40558      }
40559
40560      result += segment.duration;
40561
40562      if (typeof segment.end !== 'undefined') {
40563        return {
40564          result: segment.end - result,
40565          precise: true
40566        };
40567      }
40568    } // indicate we didn't find a useful duration estimate
40569
40570
40571    return {
40572      result: -1,
40573      precise: false
40574    };
40575  };
40576  /**
40577    * Calculate the media duration from the segments associated with a
40578    * playlist. The duration of a subinterval of the available segments
40579    * may be calculated by specifying an end index.
40580    *
40581    * @param {Object} playlist a media playlist object
40582    * @param {Number=} endSequence an exclusive upper boundary
40583    * for the playlist.  Defaults to playlist length.
40584    * @param {Number} expired the amount of time that has dropped
40585    * off the front of the playlist in a live scenario
40586    * @return {Number} the duration between the first available segment
40587    * and end index.
40588    */
40589
40590
40591  var intervalDuration = function intervalDuration(playlist, endSequence, expired) {
40592    var backward = void 0;
40593    var forward = void 0;
40594
40595    if (typeof endSequence === 'undefined') {
40596      endSequence = playlist.mediaSequence + playlist.segments.length;
40597    }
40598
40599    if (endSequence < playlist.mediaSequence) {
40600      return 0;
40601    } // do a backward walk to estimate the duration
40602
40603
40604    backward = backwardDuration(playlist, endSequence);
40605
40606    if (backward.precise) {
40607      // if we were able to base our duration estimate on timing
40608      // information provided directly from the Media Source, return
40609      // it
40610      return backward.result;
40611    } // walk forward to see if a precise duration estimate can be made
40612    // that way
40613
40614
40615    forward = forwardDuration(playlist, endSequence);
40616
40617    if (forward.precise) {
40618      // we found a segment that has been buffered and so it's
40619      // position is known precisely
40620      return forward.result;
40621    } // return the less-precise, playlist-based duration estimate
40622
40623
40624    return backward.result + expired;
40625  };
40626  /**
40627    * Calculates the duration of a playlist. If a start and end index
40628    * are specified, the duration will be for the subset of the media
40629    * timeline between those two indices. The total duration for live
40630    * playlists is always Infinity.
40631    *
40632    * @param {Object} playlist a media playlist object
40633    * @param {Number=} endSequence an exclusive upper
40634    * boundary for the playlist. Defaults to the playlist media
40635    * sequence number plus its length.
40636    * @param {Number=} expired the amount of time that has
40637    * dropped off the front of the playlist in a live scenario
40638    * @return {Number} the duration between the start index and end
40639    * index.
40640    */
40641
40642
40643  var duration = function duration(playlist, endSequence, expired) {
40644    if (!playlist) {
40645      return 0;
40646    }
40647
40648    if (typeof expired !== 'number') {
40649      expired = 0;
40650    } // if a slice of the total duration is not requested, use
40651    // playlist-level duration indicators when they're present
40652
40653
40654    if (typeof endSequence === 'undefined') {
40655      // if present, use the duration specified in the playlist
40656      if (playlist.totalDuration) {
40657        return playlist.totalDuration;
40658      } // duration should be Infinity for live playlists
40659
40660
40661      if (!playlist.endList) {
40662        return window$1.Infinity;
40663      }
40664    } // calculate the total duration based on the segment durations
40665
40666
40667    return intervalDuration(playlist, endSequence, expired);
40668  };
40669  /**
40670    * Calculate the time between two indexes in the current playlist
40671    * neight the start- nor the end-index need to be within the current
40672    * playlist in which case, the targetDuration of the playlist is used
40673    * to approximate the durations of the segments
40674    *
40675    * @param {Object} playlist a media playlist object
40676    * @param {Number} startIndex
40677    * @param {Number} endIndex
40678    * @return {Number} the number of seconds between startIndex and endIndex
40679    */
40680
40681
40682  var sumDurations = function sumDurations(playlist, startIndex, endIndex) {
40683    var durations = 0;
40684
40685    if (startIndex > endIndex) {
40686      var _ref = [endIndex, startIndex];
40687      startIndex = _ref[0];
40688      endIndex = _ref[1];
40689    }
40690
40691    if (startIndex < 0) {
40692      for (var i = startIndex; i < Math.min(0, endIndex); i++) {
40693        durations += playlist.targetDuration;
40694      }
40695
40696      startIndex = 0;
40697    }
40698
40699    for (var _i = startIndex; _i < endIndex; _i++) {
40700      durations += playlist.segments[_i].duration;
40701    }
40702
40703    return durations;
40704  };
40705  /**
40706   * Determines the media index of the segment corresponding to the safe edge of the live
40707   * window which is the duration of the last segment plus 2 target durations from the end
40708   * of the playlist.
40709   *
40710   * @param {Object} playlist
40711   *        a media playlist object
40712   * @return {Number}
40713   *         The media index of the segment at the safe live point. 0 if there is no "safe"
40714   *         point.
40715   * @function safeLiveIndex
40716   */
40717
40718
40719  var safeLiveIndex = function safeLiveIndex(playlist) {
40720    if (!playlist.segments.length) {
40721      return 0;
40722    }
40723
40724    var i = playlist.segments.length - 1;
40725    var distanceFromEnd = playlist.segments[i].duration || playlist.targetDuration;
40726    var safeDistance = distanceFromEnd + playlist.targetDuration * 2;
40727
40728    while (i--) {
40729      distanceFromEnd += playlist.segments[i].duration;
40730
40731      if (distanceFromEnd >= safeDistance) {
40732        break;
40733      }
40734    }
40735
40736    return Math.max(0, i);
40737  };
40738  /**
40739   * Calculates the playlist end time
40740   *
40741   * @param {Object} playlist a media playlist object
40742   * @param {Number=} expired the amount of time that has
40743   *                  dropped off the front of the playlist in a live scenario
40744   * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
40745   *                        playlist end calculation should consider the safe live end
40746   *                        (truncate the playlist end by three segments). This is normally
40747   *                        used for calculating the end of the playlist's seekable range.
40748   * @returns {Number} the end time of playlist
40749   * @function playlistEnd
40750   */
40751
40752
40753  var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd) {
40754    if (!playlist || !playlist.segments) {
40755      return null;
40756    }
40757
40758    if (playlist.endList) {
40759      return duration(playlist);
40760    }
40761
40762    if (expired === null) {
40763      return null;
40764    }
40765
40766    expired = expired || 0;
40767    var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist) : playlist.segments.length;
40768    return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired);
40769  };
40770  /**
40771    * Calculates the interval of time that is currently seekable in a
40772    * playlist. The returned time ranges are relative to the earliest
40773    * moment in the specified playlist that is still available. A full
40774    * seekable implementation for live streams would need to offset
40775    * these values by the duration of content that has expired from the
40776    * stream.
40777    *
40778    * @param {Object} playlist a media playlist object
40779    * dropped off the front of the playlist in a live scenario
40780    * @param {Number=} expired the amount of time that has
40781    * dropped off the front of the playlist in a live scenario
40782    * @return {TimeRanges} the periods of time that are valid targets
40783    * for seeking
40784    */
40785
40786
40787  var seekable = function seekable(playlist, expired) {
40788    var useSafeLiveEnd = true;
40789    var seekableStart = expired || 0;
40790    var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd);
40791
40792    if (seekableEnd === null) {
40793      return createTimeRange();
40794    }
40795
40796    return createTimeRange(seekableStart, seekableEnd);
40797  };
40798
40799  var isWholeNumber = function isWholeNumber(num) {
40800    return num - Math.floor(num) === 0;
40801  };
40802
40803  var roundSignificantDigit = function roundSignificantDigit(increment, num) {
40804    // If we have a whole number, just add 1 to it
40805    if (isWholeNumber(num)) {
40806      return num + increment * 0.1;
40807    }
40808
40809    var numDecimalDigits = num.toString().split('.')[1].length;
40810
40811    for (var i = 1; i <= numDecimalDigits; i++) {
40812      var scale = Math.pow(10, i);
40813      var temp = num * scale;
40814
40815      if (isWholeNumber(temp) || i === numDecimalDigits) {
40816        return (temp + increment) / scale;
40817      }
40818    }
40819  };
40820
40821  var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1);
40822  var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1);
40823  /**
40824   * Determine the index and estimated starting time of the segment that
40825   * contains a specified playback position in a media playlist.
40826   *
40827   * @param {Object} playlist the media playlist to query
40828   * @param {Number} currentTime The number of seconds since the earliest
40829   * possible position to determine the containing segment for
40830   * @param {Number} startIndex
40831   * @param {Number} startTime
40832   * @return {Object}
40833   */
40834
40835  var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) {
40836    var i = void 0;
40837    var segment = void 0;
40838    var numSegments = playlist.segments.length;
40839    var time = currentTime - startTime;
40840
40841    if (time < 0) {
40842      // Walk backward from startIndex in the playlist, adding durations
40843      // until we find a segment that contains `time` and return it
40844      if (startIndex > 0) {
40845        for (i = startIndex - 1; i >= 0; i--) {
40846          segment = playlist.segments[i];
40847          time += floorLeastSignificantDigit(segment.duration);
40848
40849          if (time > 0) {
40850            return {
40851              mediaIndex: i,
40852              startTime: startTime - sumDurations(playlist, startIndex, i)
40853            };
40854          }
40855        }
40856      } // We were unable to find a good segment within the playlist
40857      // so select the first segment
40858
40859
40860      return {
40861        mediaIndex: 0,
40862        startTime: currentTime
40863      };
40864    } // When startIndex is negative, we first walk forward to first segment
40865    // adding target durations. If we "run out of time" before getting to
40866    // the first segment, return the first segment
40867
40868
40869    if (startIndex < 0) {
40870      for (i = startIndex; i < 0; i++) {
40871        time -= playlist.targetDuration;
40872
40873        if (time < 0) {
40874          return {
40875            mediaIndex: 0,
40876            startTime: currentTime
40877          };
40878        }
40879      }
40880
40881      startIndex = 0;
40882    } // Walk forward from startIndex in the playlist, subtracting durations
40883    // until we find a segment that contains `time` and return it
40884
40885
40886    for (i = startIndex; i < numSegments; i++) {
40887      segment = playlist.segments[i];
40888      time -= ceilLeastSignificantDigit(segment.duration);
40889
40890      if (time < 0) {
40891        return {
40892          mediaIndex: i,
40893          startTime: startTime + sumDurations(playlist, startIndex, i)
40894        };
40895      }
40896    } // We are out of possible candidates so load the last one...
40897
40898
40899    return {
40900      mediaIndex: numSegments - 1,
40901      startTime: currentTime
40902    };
40903  };
40904  /**
40905   * Check whether the playlist is blacklisted or not.
40906   *
40907   * @param {Object} playlist the media playlist object
40908   * @return {boolean} whether the playlist is blacklisted or not
40909   * @function isBlacklisted
40910   */
40911
40912
40913  var isBlacklisted = function isBlacklisted(playlist) {
40914    return playlist.excludeUntil && playlist.excludeUntil > Date.now();
40915  };
40916  /**
40917   * Check whether the playlist is compatible with current playback configuration or has
40918   * been blacklisted permanently for being incompatible.
40919   *
40920   * @param {Object} playlist the media playlist object
40921   * @return {boolean} whether the playlist is incompatible or not
40922   * @function isIncompatible
40923   */
40924
40925
40926  var isIncompatible = function isIncompatible(playlist) {
40927    return playlist.excludeUntil && playlist.excludeUntil === Infinity;
40928  };
40929  /**
40930   * Check whether the playlist is enabled or not.
40931   *
40932   * @param {Object} playlist the media playlist object
40933   * @return {boolean} whether the playlist is enabled or not
40934   * @function isEnabled
40935   */
40936
40937
40938  var isEnabled = function isEnabled(playlist) {
40939    var blacklisted = isBlacklisted(playlist);
40940    return !playlist.disabled && !blacklisted;
40941  };
40942  /**
40943   * Check whether the playlist has been manually disabled through the representations api.
40944   *
40945   * @param {Object} playlist the media playlist object
40946   * @return {boolean} whether the playlist is disabled manually or not
40947   * @function isDisabled
40948   */
40949
40950
40951  var isDisabled = function isDisabled(playlist) {
40952    return playlist.disabled;
40953  };
40954  /**
40955   * Returns whether the current playlist is an AES encrypted HLS stream
40956   *
40957   * @return {Boolean} true if it's an AES encrypted HLS stream
40958   */
40959
40960
40961  var isAes = function isAes(media) {
40962    for (var i = 0; i < media.segments.length; i++) {
40963      if (media.segments[i].key) {
40964        return true;
40965      }
40966    }
40967
40968    return false;
40969  };
40970  /**
40971   * Returns whether the current playlist contains fMP4
40972   *
40973   * @return {Boolean} true if the playlist contains fMP4
40974   */
40975
40976
40977  var isFmp4 = function isFmp4(media) {
40978    for (var i = 0; i < media.segments.length; i++) {
40979      if (media.segments[i].map) {
40980        return true;
40981      }
40982    }
40983
40984    return false;
40985  };
40986  /**
40987   * Checks if the playlist has a value for the specified attribute
40988   *
40989   * @param {String} attr
40990   *        Attribute to check for
40991   * @param {Object} playlist
40992   *        The media playlist object
40993   * @return {Boolean}
40994   *         Whether the playlist contains a value for the attribute or not
40995   * @function hasAttribute
40996   */
40997
40998
40999  var hasAttribute = function hasAttribute(attr, playlist) {
41000    return playlist.attributes && playlist.attributes[attr];
41001  };
41002  /**
41003   * Estimates the time required to complete a segment download from the specified playlist
41004   *
41005   * @param {Number} segmentDuration
41006   *        Duration of requested segment
41007   * @param {Number} bandwidth
41008   *        Current measured bandwidth of the player
41009   * @param {Object} playlist
41010   *        The media playlist object
41011   * @param {Number=} bytesReceived
41012   *        Number of bytes already received for the request. Defaults to 0
41013   * @return {Number|NaN}
41014   *         The estimated time to request the segment. NaN if bandwidth information for
41015   *         the given playlist is unavailable
41016   * @function estimateSegmentRequestTime
41017   */
41018
41019
41020  var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist) {
41021    var bytesReceived = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
41022
41023    if (!hasAttribute('BANDWIDTH', playlist)) {
41024      return NaN;
41025    }
41026
41027    var size = segmentDuration * playlist.attributes.BANDWIDTH;
41028    return (size - bytesReceived * 8) / bandwidth;
41029  };
41030  /*
41031   * Returns whether the current playlist is the lowest rendition
41032   *
41033   * @return {Boolean} true if on lowest rendition
41034   */
41035
41036
41037  var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) {
41038    if (master.playlists.length === 1) {
41039      return true;
41040    }
41041
41042    var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
41043    return master.playlists.filter(function (playlist) {
41044      if (!isEnabled(playlist)) {
41045        return false;
41046      }
41047
41048      return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
41049    }).length === 0;
41050  }; // exports
41051
41052
41053  var Playlist = {
41054    duration: duration,
41055    seekable: seekable,
41056    safeLiveIndex: safeLiveIndex,
41057    getMediaInfoForTime: getMediaInfoForTime,
41058    isEnabled: isEnabled,
41059    isDisabled: isDisabled,
41060    isBlacklisted: isBlacklisted,
41061    isIncompatible: isIncompatible,
41062    playlistEnd: playlistEnd,
41063    isAes: isAes,
41064    isFmp4: isFmp4,
41065    hasAttribute: hasAttribute,
41066    estimateSegmentRequestTime: estimateSegmentRequestTime,
41067    isLowestEnabledRendition: isLowestEnabledRendition
41068  };
41069  /**
41070   * @file xhr.js
41071   */
41072
41073  var videojsXHR = videojs$1.xhr,
41074      mergeOptions$1$1 = videojs$1.mergeOptions;
41075
41076  var xhrFactory = function xhrFactory() {
41077    var xhr = function XhrFunction(options, callback) {
41078      // Add a default timeout for all hls requests
41079      options = mergeOptions$1$1({
41080        timeout: 45e3
41081      }, options); // Allow an optional user-specified function to modify the option
41082      // object before we construct the xhr request
41083
41084      var beforeRequest = XhrFunction.beforeRequest || videojs$1.Hls.xhr.beforeRequest;
41085
41086      if (beforeRequest && typeof beforeRequest === 'function') {
41087        var newOptions = beforeRequest(options);
41088
41089        if (newOptions) {
41090          options = newOptions;
41091        }
41092      }
41093
41094      var request = videojsXHR(options, function (error, response) {
41095        var reqResponse = request.response;
41096
41097        if (!error && reqResponse) {
41098          request.responseTime = Date.now();
41099          request.roundTripTime = request.responseTime - request.requestTime;
41100          request.bytesReceived = reqResponse.byteLength || reqResponse.length;
41101
41102          if (!request.bandwidth) {
41103            request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
41104          }
41105        }
41106
41107        if (response.headers) {
41108          request.responseHeaders = response.headers;
41109        } // videojs.xhr now uses a specific code on the error
41110        // object to signal that a request has timed out instead
41111        // of setting a boolean on the request object
41112
41113
41114        if (error && error.code === 'ETIMEDOUT') {
41115          request.timedout = true;
41116        } // videojs.xhr no longer considers status codes outside of 200 and 0
41117        // (for file uris) to be errors, but the old XHR did, so emulate that
41118        // behavior. Status 206 may be used in response to byterange requests.
41119
41120
41121        if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
41122          error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText)));
41123        }
41124
41125        callback(error, request);
41126      });
41127      var originalAbort = request.abort;
41128
41129      request.abort = function () {
41130        request.aborted = true;
41131        return originalAbort.apply(request, arguments);
41132      };
41133
41134      request.uri = options.uri;
41135      request.requestTime = Date.now();
41136      return request;
41137    };
41138
41139    return xhr;
41140  };
41141  /**
41142   * @file bin-utils.js
41143   */
41144
41145  /**
41146   * convert a TimeRange to text
41147   *
41148   * @param {TimeRange} range the timerange to use for conversion
41149   * @param {Number} i the iterator on the range to convert
41150   */
41151
41152
41153  var textRange = function textRange(range, i) {
41154    return range.start(i) + '-' + range.end(i);
41155  };
41156  /**
41157   * format a number as hex string
41158   *
41159   * @param {Number} e The number
41160   * @param {Number} i the iterator
41161   */
41162
41163
41164  var formatHexString = function formatHexString(e, i) {
41165    var value = e.toString(16);
41166    return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
41167  };
41168
41169  var formatAsciiString = function formatAsciiString(e) {
41170    if (e >= 0x20 && e < 0x7e) {
41171      return String.fromCharCode(e);
41172    }
41173
41174    return '.';
41175  };
41176  /**
41177   * Creates an object for sending to a web worker modifying properties that are TypedArrays
41178   * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
41179   *
41180   * @param {Object} message
41181   *        Object of properties and values to send to the web worker
41182   * @return {Object}
41183   *         Modified message with TypedArray values expanded
41184   * @function createTransferableMessage
41185   */
41186
41187
41188  var createTransferableMessage = function createTransferableMessage(message) {
41189    var transferable = {};
41190    Object.keys(message).forEach(function (key) {
41191      var value = message[key];
41192
41193      if (ArrayBuffer.isView(value)) {
41194        transferable[key] = {
41195          bytes: value.buffer,
41196          byteOffset: value.byteOffset,
41197          byteLength: value.byteLength
41198        };
41199      } else {
41200        transferable[key] = value;
41201      }
41202    });
41203    return transferable;
41204  };
41205  /**
41206   * Returns a unique string identifier for a media initialization
41207   * segment.
41208   */
41209
41210
41211  var initSegmentId = function initSegmentId(initSegment) {
41212    var byterange = initSegment.byterange || {
41213      length: Infinity,
41214      offset: 0
41215    };
41216    return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
41217  };
41218  /**
41219   * utils to help dump binary data to the console
41220   */
41221
41222
41223  var hexDump = function hexDump(data) {
41224    var bytes = Array.prototype.slice.call(data);
41225    var step = 16;
41226    var result = '';
41227    var hex = void 0;
41228    var ascii = void 0;
41229
41230    for (var j = 0; j < bytes.length / step; j++) {
41231      hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
41232      ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
41233      result += hex + ' ' + ascii + '\n';
41234    }
41235
41236    return result;
41237  };
41238
41239  var tagDump = function tagDump(_ref) {
41240    var bytes = _ref.bytes;
41241    return hexDump(bytes);
41242  };
41243
41244  var textRanges = function textRanges(ranges) {
41245    var result = '';
41246    var i = void 0;
41247
41248    for (i = 0; i < ranges.length; i++) {
41249      result += textRange(ranges, i) + ' ';
41250    }
41251
41252    return result;
41253  };
41254
41255  var utils$1 =
41256  /*#__PURE__*/
41257  Object.freeze({
41258    createTransferableMessage: createTransferableMessage,
41259    initSegmentId: initSegmentId,
41260    hexDump: hexDump,
41261    tagDump: tagDump,
41262    textRanges: textRanges
41263  }); // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux
41264  // Add 25% to the segment duration to account for small discrepencies in segment timing.
41265  // 25% was arbitrarily chosen, and may need to be refined over time.
41266
41267  var SEGMENT_END_FUDGE_PERCENT = 0.25;
41268  /**
41269   * Converts a player time (any time that can be gotten/set from player.currentTime(),
41270   * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a
41271   * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)).
41272   *
41273   * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor
41274   * point" (a point where we have a mapping from program time to player time, with player
41275   * time being the post transmux start of the segment).
41276   *
41277   * For more details, see [this doc](../../docs/program-time-from-player-time.md).
41278   *
41279   * @param {Number} playerTime the player time
41280   * @param {Object} segment the segment which contains the player time
41281   * @return {Date} program time
41282   */
41283
41284  var playerTimeToProgramTime = function playerTimeToProgramTime(playerTime, segment) {
41285    if (!segment.dateTimeObject) {
41286      // Can't convert without an "anchor point" for the program time (i.e., a time that can
41287      // be used to map the start of a segment with a real world time).
41288      return null;
41289    }
41290
41291    var transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds;
41292    var transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended
41293
41294    var startOfSegment = transmuxedStart + transmuxerPrependedSeconds;
41295    var offsetFromSegmentStart = playerTime - startOfSegment;
41296    return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000);
41297  };
41298
41299  var originalSegmentVideoDuration = function originalSegmentVideoDuration(videoTimingInfo) {
41300    return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds;
41301  };
41302  /**
41303   * Finds a segment that contains the time requested given as an ISO-8601 string. The
41304   * returned segment might be an estimate or an accurate match.
41305   *
41306   * @param {String} programTime The ISO-8601 programTime to find a match for
41307   * @param {Object} playlist A playlist object to search within
41308   */
41309
41310
41311  var findSegmentForProgramTime = function findSegmentForProgramTime(programTime, playlist) {
41312    // Assumptions:
41313    //  - verifyProgramDateTimeTags has already been run
41314    //  - live streams have been started
41315    var dateTimeObject = void 0;
41316
41317    try {
41318      dateTimeObject = new Date(programTime);
41319    } catch (e) {
41320      return null;
41321    }
41322
41323    if (!playlist || !playlist.segments || playlist.segments.length === 0) {
41324      return null;
41325    }
41326
41327    var segment = playlist.segments[0];
41328
41329    if (dateTimeObject < segment.dateTimeObject) {
41330      // Requested time is before stream start.
41331      return null;
41332    }
41333
41334    for (var i = 0; i < playlist.segments.length - 1; i++) {
41335      segment = playlist.segments[i];
41336      var nextSegmentStart = playlist.segments[i + 1].dateTimeObject;
41337
41338      if (dateTimeObject < nextSegmentStart) {
41339        break;
41340      }
41341    }
41342
41343    var lastSegment = playlist.segments[playlist.segments.length - 1];
41344    var lastSegmentStart = lastSegment.dateTimeObject;
41345    var lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT;
41346    var lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000);
41347
41348    if (dateTimeObject > lastSegmentEnd) {
41349      // Beyond the end of the stream, or our best guess of the end of the stream.
41350      return null;
41351    }
41352
41353    if (dateTimeObject > lastSegmentStart) {
41354      segment = lastSegment;
41355    }
41356
41357    return {
41358      segment: segment,
41359      estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)),
41360      // Although, given that all segments have accurate date time objects, the segment
41361      // selected should be accurate, unless the video has been transmuxed at some point
41362      // (determined by the presence of the videoTimingInfo object), the segment's "player
41363      // time" (the start time in the player) can't be considered accurate.
41364      type: segment.videoTimingInfo ? 'accurate' : 'estimate'
41365    };
41366  };
41367  /**
41368   * Finds a segment that contains the given player time(in seconds).
41369   *
41370   * @param {Number} time The player time to find a match for
41371   * @param {Object} playlist A playlist object to search within
41372   */
41373
41374
41375  var findSegmentForPlayerTime = function findSegmentForPlayerTime(time, playlist) {
41376    // Assumptions:
41377    // - there will always be a segment.duration
41378    // - we can start from zero
vendor: 10,433 bytes, lines 41379-41681
41379    // - segments are in time order
41380    if (!playlist || !playlist.segments || playlist.segments.length === 0) {
41381      return null;
41382    }
41383
41384    var segmentEnd = 0;
41385    var segment = void 0;
41386
41387    for (var i = 0; i < playlist.segments.length; i++) {
41388      segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and
41389      // should contain the most accurate values we have for the segment's player times.
41390      //
41391      // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall
41392      // back to an estimate based on the manifest derived (inaccurate) segment.duration, to
41393      // calculate an end value.
41394
41395      segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration;
41396
41397      if (time <= segmentEnd) {
41398        break;
41399      }
41400    }
41401
41402    var lastSegment = playlist.segments[playlist.segments.length - 1];
41403
41404    if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) {
41405      // The time requested is beyond the stream end.
41406      return null;
41407    }
41408
41409    if (time > segmentEnd) {
41410      // The time is within or beyond the last segment.
41411      //
41412      // Check to see if the time is beyond a reasonable guess of the end of the stream.
41413      if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) {
41414        // Technically, because the duration value is only an estimate, the time may still
41415        // exist in the last segment, however, there isn't enough information to make even
41416        // a reasonable estimate.
41417        return null;
41418      }
41419
41420      segment = lastSegment;
41421    }
41422
41423    return {
41424      segment: segment,
41425      estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration,
41426      // Because videoTimingInfo is only set after transmux, it is the only way to get
41427      // accurate timing values.
41428      type: segment.videoTimingInfo ? 'accurate' : 'estimate'
41429    };
41430  };
41431  /**
41432   * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds.
41433   * If the offset returned is positive, the programTime occurs after the
41434   * comparisonTimestamp.
41435   * If the offset is negative, the programTime occurs before the comparisonTimestamp.
41436   *
41437   * @param {String} comparisonTimeStamp An ISO-8601 timestamp to compare against
41438   * @param {String} programTime The programTime as an ISO-8601 string
41439   * @return {Number} offset
41440   */
41441
41442
41443  var getOffsetFromTimestamp = function getOffsetFromTimestamp(comparisonTimeStamp, programTime) {
41444    var segmentDateTime = void 0;
41445    var programDateTime = void 0;
41446
41447    try {
41448      segmentDateTime = new Date(comparisonTimeStamp);
41449      programDateTime = new Date(programTime);
41450    } catch (e) {// TODO handle error
41451    }
41452
41453    var segmentTimeEpoch = segmentDateTime.getTime();
41454    var programTimeEpoch = programDateTime.getTime();
41455    return (programTimeEpoch - segmentTimeEpoch) / 1000;
41456  };
41457  /**
41458   * Checks that all segments in this playlist have programDateTime tags.
41459   *
41460   * @param {Object} playlist A playlist object
41461   */
41462
41463
41464  var verifyProgramDateTimeTags = function verifyProgramDateTimeTags(playlist) {
41465    if (!playlist.segments || playlist.segments.length === 0) {
41466      return false;
41467    }
41468
41469    for (var i = 0; i < playlist.segments.length; i++) {
41470      var segment = playlist.segments[i];
41471
41472      if (!segment.dateTimeObject) {
41473        return false;
41474      }
41475    }
41476
41477    return true;
41478  };
41479  /**
41480   * Returns the programTime of the media given a playlist and a playerTime.
41481   * The playlist must have programDateTime tags for a programDateTime tag to be returned.
41482   * If the segments containing the time requested have not been buffered yet, an estimate
41483   * may be returned to the callback.
41484   *
41485   * @param {Object} args
41486   * @param {Object} args.playlist A playlist object to search within
41487   * @param {Number} time A playerTime in seconds
41488   * @param {Function} callback(err, programTime)
41489   * @returns {String} err.message A detailed error message
41490   * @returns {Object} programTime
41491   * @returns {Number} programTime.mediaSeconds The streamTime in seconds
41492   * @returns {String} programTime.programDateTime The programTime as an ISO-8601 String
41493   */
41494
41495
41496  var getProgramTime = function getProgramTime(_ref) {
41497    var playlist = _ref.playlist,
41498        _ref$time = _ref.time,
41499        time = _ref$time === undefined ? undefined : _ref$time,
41500        callback = _ref.callback;
41501
41502    if (!callback) {
41503      throw new Error('getProgramTime: callback must be provided');
41504    }
41505
41506    if (!playlist || time === undefined) {
41507      return callback({
41508        message: 'getProgramTime: playlist and time must be provided'
41509      });
41510    }
41511
41512    var matchedSegment = findSegmentForPlayerTime(time, playlist);
41513
41514    if (!matchedSegment) {
41515      return callback({
41516        message: 'valid programTime was not found'
41517      });
41518    }
41519
41520    if (matchedSegment.type === 'estimate') {
41521      return callback({
41522        message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again',
41523        seekTime: matchedSegment.estimatedStart
41524      });
41525    }
41526
41527    var programTimeObject = {
41528      mediaSeconds: time
41529    };
41530    var programTime = playerTimeToProgramTime(time, matchedSegment.segment);
41531
41532    if (programTime) {
41533      programTimeObject.programDateTime = programTime.toISOString();
41534    }
41535
41536    return callback(null, programTimeObject);
41537  };
41538  /**
41539   * Seeks in the player to a time that matches the given programTime ISO-8601 string.
41540   *
41541   * @param {Object} args
41542   * @param {String} args.programTime A programTime to seek to as an ISO-8601 String
41543   * @param {Object} args.playlist A playlist to look within
41544   * @param {Number} args.retryCount The number of times to try for an accurate seek. Default is 2.
41545   * @param {Function} args.seekTo A method to perform a seek
41546   * @param {Boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true.
41547   * @param {Object} args.tech The tech to seek on
41548   * @param {Function} args.callback(err, newTime) A callback to return the new time to
41549   * @returns {String} err.message A detailed error message
41550   * @returns {Number} newTime The exact time that was seeked to in seconds
41551   */
41552
41553
41554  var seekToProgramTime = function seekToProgramTime(_ref2) {
41555    var programTime = _ref2.programTime,
41556        playlist = _ref2.playlist,
41557        _ref2$retryCount = _ref2.retryCount,
41558        retryCount = _ref2$retryCount === undefined ? 2 : _ref2$retryCount,
41559        seekTo = _ref2.seekTo,
41560        _ref2$pauseAfterSeek = _ref2.pauseAfterSeek,
41561        pauseAfterSeek = _ref2$pauseAfterSeek === undefined ? true : _ref2$pauseAfterSeek,
41562        tech = _ref2.tech,
41563        callback = _ref2.callback;
41564
41565    if (!callback) {
41566      throw new Error('seekToProgramTime: callback must be provided');
41567    }
41568
41569    if (typeof programTime === 'undefined' || !playlist || !seekTo) {
41570      return callback({
41571        message: 'seekToProgramTime: programTime, seekTo and playlist must be provided'
41572      });
41573    }
41574
41575    if (!playlist.endList && !tech.hasStarted_) {
41576      return callback({
41577        message: 'player must be playing a live stream to start buffering'
41578      });
41579    }
41580
41581    if (!verifyProgramDateTimeTags(playlist)) {
41582      return callback({
41583        message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri
41584      });
41585    }
41586
41587    var matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match
41588
41589    if (!matchedSegment) {
41590      return callback({
41591        message: programTime + ' was not found in the stream'
41592      });
41593    }
41594
41595    var segment = matchedSegment.segment;
41596    var mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime);
41597
41598    if (matchedSegment.type === 'estimate') {
41599      // we've run out of retries
41600      if (retryCount === 0) {
41601        return callback({
41602          message: programTime + ' is not buffered yet. Try again'
41603        });
41604      }
41605
41606      seekTo(matchedSegment.estimatedStart + mediaOffset);
41607      tech.one('seeked', function () {
41608        seekToProgramTime({
41609          programTime: programTime,
41610          playlist: playlist,
41611          retryCount: retryCount - 1,
41612          seekTo: seekTo,
41613          pauseAfterSeek: pauseAfterSeek,
41614          tech: tech,
41615          callback: callback
41616        });
41617      });
41618      return;
41619    } // Since the segment.start value is determined from the buffered end or ending time
41620    // of the prior segment, the seekToTime doesn't need to account for any transmuxer
41621    // modifications.
41622
41623
41624    var seekToTime = segment.start + mediaOffset;
41625
41626    var seekedCallback = function seekedCallback() {
41627      return callback(null, tech.currentTime());
41628    }; // listen for seeked event
41629
41630
41631    tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state
41632
41633    if (pauseAfterSeek) {
41634      tech.pause();
41635    }
41636
41637    seekTo(seekToTime);
41638  };
41639  /**
41640   * ranges
41641   *
41642   * Utilities for working with TimeRanges.
41643   *
41644   */
41645  // Fudge factor to account for TimeRanges rounding
41646
41647
41648  var TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range
41649  // can be misleading because of precision differences or when the current media has poorly
41650  // aligned audio and video, which can cause values to be slightly off from what you would
41651  // expect. This value is what we consider to be safe to use in such comparisons to account
41652  // for these scenarios.
41653
41654  var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
41655
41656  var filterRanges = function filterRanges(timeRanges, predicate) {
41657    var results = [];
41658    var i = void 0;
41659
41660    if (timeRanges && timeRanges.length) {
41661      // Search for ranges that match the predicate
41662      for (i = 0; i < timeRanges.length; i++) {
41663        if (predicate(timeRanges.start(i), timeRanges.end(i))) {
41664          results.push([timeRanges.start(i), timeRanges.end(i)]);
41665        }
41666      }
41667    }
41668
41669    return videojs$1.createTimeRanges(results);
41670  };
41671  /**
41672   * Attempts to find the buffered TimeRange that contains the specified
41673   * time.
41674   * @param {TimeRanges} buffered - the TimeRanges object to query
41675   * @param {number} time  - the time to filter on.
41676   * @returns {TimeRanges} a new TimeRanges object
41677   */
41678
41679
41680  var findRange = function findRange(buffered, time) {
41681    return filterRanges(buffered, function (start, end) {
vendor: 82,872 bytes, lines 41682-44063
41682      return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time;
41683    });
41684  };
41685  /**
41686   * Returns the TimeRanges that begin later than the specified time.
41687   * @param {TimeRanges} timeRanges - the TimeRanges object to query
41688   * @param {number} time - the time to filter on.
41689   * @returns {TimeRanges} a new TimeRanges object.
41690   */
41691
41692
41693  var findNextRange = function findNextRange(timeRanges, time) {
41694    return filterRanges(timeRanges, function (start) {
41695      return start - TIME_FUDGE_FACTOR >= time;
41696    });
41697  };
41698  /**
41699   * Returns gaps within a list of TimeRanges
41700   * @param {TimeRanges} buffered - the TimeRanges object
41701   * @return {TimeRanges} a TimeRanges object of gaps
41702   */
41703
41704
41705  var findGaps = function findGaps(buffered) {
41706    if (buffered.length < 2) {
41707      return videojs$1.createTimeRanges();
41708    }
41709
41710    var ranges = [];
41711
41712    for (var i = 1; i < buffered.length; i++) {
41713      var start = buffered.end(i - 1);
41714      var end = buffered.start(i);
41715      ranges.push([start, end]);
41716    }
41717
41718    return videojs$1.createTimeRanges(ranges);
41719  };
41720  /**
41721   * Gets a human readable string for a TimeRange
41722   *
41723   * @param {TimeRange} range
41724   * @returns {String} a human readable string
41725   */
41726
41727
41728  var printableRange = function printableRange(range) {
41729    var strArr = [];
41730
41731    if (!range || !range.length) {
41732      return '';
41733    }
41734
41735    for (var i = 0; i < range.length; i++) {
41736      strArr.push(range.start(i) + ' => ' + range.end(i));
41737    }
41738
41739    return strArr.join(', ');
41740  };
41741  /**
41742   * Calculates the amount of time left in seconds until the player hits the end of the
41743   * buffer and causes a rebuffer
41744   *
41745   * @param {TimeRange} buffered
41746   *        The state of the buffer
41747   * @param {Numnber} currentTime
41748   *        The current time of the player
41749   * @param {Number} playbackRate
41750   *        The current playback rate of the player. Defaults to 1.
41751   * @return {Number}
41752   *         Time until the player has to start rebuffering in seconds.
41753   * @function timeUntilRebuffer
41754   */
41755
41756
41757  var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime) {
41758    var playbackRate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
41759    var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
41760    return (bufferedEnd - currentTime) / playbackRate;
41761  };
41762  /**
41763   * Converts a TimeRanges object into an array representation
41764   * @param {TimeRanges} timeRanges
41765   * @returns {Array}
41766   */
41767
41768
41769  var timeRangesToArray = function timeRangesToArray(timeRanges) {
41770    var timeRangesList = [];
41771
41772    for (var i = 0; i < timeRanges.length; i++) {
41773      timeRangesList.push({
41774        start: timeRanges.start(i),
41775        end: timeRanges.end(i)
41776      });
41777    }
41778
41779    return timeRangesList;
41780  };
41781  /**
41782   * @file create-text-tracks-if-necessary.js
41783   */
41784
41785  /**
41786   * Create text tracks on video.js if they exist on a segment.
41787   *
41788   * @param {Object} sourceBuffer the VSB or FSB
41789   * @param {Object} mediaSource the HTML media source
41790   * @param {Object} segment the segment that may contain the text track
41791   * @private
41792   */
41793
41794
41795  var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) {
41796    var player = mediaSource.player_; // create an in-band caption track if one is present in the segment
41797
41798    if (segment.captions && segment.captions.length) {
41799      if (!sourceBuffer.inbandTextTracks_) {
41800        sourceBuffer.inbandTextTracks_ = {};
41801      }
41802
41803      for (var trackId in segment.captionStreams) {
41804        if (!sourceBuffer.inbandTextTracks_[trackId]) {
41805          player.tech_.trigger({
41806            type: 'usage',
41807            name: 'hls-608'
41808          });
41809          var track = player.textTracks().getTrackById(trackId);
41810
41811          if (track) {
41812            // Resuse an existing track with a CC# id because this was
41813            // very likely created by videojs-contrib-hls from information
41814            // in the m3u8 for us to use
41815            sourceBuffer.inbandTextTracks_[trackId] = track;
41816          } else {
41817            // Otherwise, create a track with the default `CC#` label and
41818            // without a language
41819            sourceBuffer.inbandTextTracks_[trackId] = player.addRemoteTextTrack({
41820              kind: 'captions',
41821              id: trackId,
41822              label: trackId
41823            }, false).track;
41824          }
41825        }
41826      }
41827    }
41828
41829    if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) {
41830      sourceBuffer.metadataTrack_ = player.addRemoteTextTrack({
41831        kind: 'metadata',
41832        label: 'Timed Metadata'
41833      }, false).track;
41834      sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType;
41835    }
41836  };
41837  /**
41838   * @file remove-cues-from-track.js
41839   */
41840
41841  /**
41842   * Remove cues from a track on video.js.
41843   *
41844   * @param {Double} start start of where we should remove the cue
41845   * @param {Double} end end of where the we should remove the cue
41846   * @param {Object} track the text track to remove the cues from
41847   * @private
41848   */
41849
41850
41851  var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) {
41852    var i = void 0;
41853    var cue = void 0;
41854
41855    if (!track) {
41856      return;
41857    }
41858
41859    if (!track.cues) {
41860      return;
41861    }
41862
41863    i = track.cues.length;
41864
41865    while (i--) {
41866      cue = track.cues[i]; // Remove any overlapping cue
41867
41868      if (cue.startTime <= end && cue.endTime >= start) {
41869        track.removeCue(cue);
41870      }
41871    }
41872  };
41873  /**
41874   * @file add-text-track-data.js
41875   */
41876
41877  /**
41878   * Define properties on a cue for backwards compatability,
41879   * but warn the user that the way that they are using it
41880   * is depricated and will be removed at a later date.
41881   *
41882   * @param {Cue} cue the cue to add the properties on
41883   * @private
41884   */
41885
41886
41887  var deprecateOldCue = function deprecateOldCue(cue) {
41888    Object.defineProperties(cue.frame, {
41889      id: {
41890        get: function get() {
41891          videojs$1.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
41892          return cue.value.key;
41893        }
41894      },
41895      value: {
41896        get: function get() {
41897          videojs$1.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
41898          return cue.value.data;
41899        }
41900      },
41901      privateData: {
41902        get: function get() {
41903          videojs$1.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
41904          return cue.value.data;
41905        }
41906      }
41907    });
41908  };
41909
41910  var durationOfVideo = function durationOfVideo(duration) {
41911    var dur = void 0;
41912
41913    if (isNaN(duration) || Math.abs(duration) === Infinity) {
41914      dur = Number.MAX_VALUE;
41915    } else {
41916      dur = duration;
41917    }
41918
41919    return dur;
41920  };
41921  /**
41922   * Add text track data to a source handler given the captions and
41923   * metadata from the buffer.
41924   *
41925   * @param {Object} sourceHandler the virtual source buffer
41926   * @param {Array} captionArray an array of caption data
41927   * @param {Array} metadataArray an array of meta data
41928   * @private
41929   */
41930
41931
41932  var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) {
41933    var Cue = window$1.WebKitDataCue || window$1.VTTCue;
41934
41935    if (captionArray) {
41936      captionArray.forEach(function (caption) {
41937        var track = caption.stream;
41938        this.inbandTextTracks_[track].addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text));
41939      }, sourceHandler);
41940    }
41941
41942    if (metadataArray) {
41943      var videoDuration = durationOfVideo(sourceHandler.mediaSource_.duration);
41944      metadataArray.forEach(function (metadata) {
41945        var time = metadata.cueTime + this.timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN,
41946        // ignore this bit of metadata.
41947        // This likely occurs when you have an non-timed ID3 tag like TIT2,
41948        // which is the "Title/Songname/Content description" frame
41949
41950        if (typeof time !== 'number' || window$1.isNaN(time) || time < 0 || !(time < Infinity)) {
41951          return;
41952        }
41953
41954        metadata.frames.forEach(function (frame) {
41955          var cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
41956          cue.frame = frame;
41957          cue.value = frame;
41958          deprecateOldCue(cue);
41959          this.metadataTrack_.addCue(cue);
41960        }, this);
41961      }, sourceHandler); // Updating the metadeta cues so that
41962      // the endTime of each cue is the startTime of the next cue
41963      // the endTime of last cue is the duration of the video
41964
41965      if (sourceHandler.metadataTrack_ && sourceHandler.metadataTrack_.cues && sourceHandler.metadataTrack_.cues.length) {
41966        var cues = sourceHandler.metadataTrack_.cues;
41967        var cuesArray = []; // Create a copy of the TextTrackCueList...
41968        // ...disregarding cues with a falsey value
41969
41970        for (var i = 0; i < cues.length; i++) {
41971          if (cues[i]) {
41972            cuesArray.push(cues[i]);
41973          }
41974        } // Group cues by their startTime value
41975
41976
41977        var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) {
41978          var timeSlot = obj[cue.startTime] || [];
41979          timeSlot.push(cue);
41980          obj[cue.startTime] = timeSlot;
41981          return obj;
41982        }, {}); // Sort startTimes by ascending order
41983
41984        var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) {
41985          return Number(a) - Number(b);
41986        }); // Map each cue group's endTime to the next group's startTime
41987
41988        sortedStartTimes.forEach(function (startTime, idx) {
41989          var cueGroup = cuesGroupedByStartTime[startTime];
41990          var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime
41991
41992          cueGroup.forEach(function (cue) {
41993            cue.endTime = nextTime;
41994          });
41995        });
41996      }
41997    }
41998  };
41999
42000  var win = typeof window !== 'undefined' ? window : {},
42001      TARGET = typeof Symbol === 'undefined' ? '__target' : Symbol(),
42002      SCRIPT_TYPE = 'application/javascript',
42003      BlobBuilder = win.BlobBuilder || win.WebKitBlobBuilder || win.MozBlobBuilder || win.MSBlobBuilder,
42004      URL = win.URL || win.webkitURL || URL && URL.msURL,
42005      Worker = win.Worker;
42006  /**
42007   * Returns a wrapper around Web Worker code that is constructible.
42008   *
42009   * @function shimWorker
42010   *
42011   * @param { String }    filename    The name of the file
42012   * @param { Function }  fn          Function wrapping the code of the worker
42013   */
42014
42015  function shimWorker(filename, fn) {
42016    return function ShimWorker(forceFallback) {
42017      var o = this;
42018
42019      if (!fn) {
42020        return new Worker(filename);
42021      } else if (Worker && !forceFallback) {
42022        // Convert the function's inner code to a string to construct the worker
42023        var source = fn.toString().replace(/^function.+?{/, '').slice(0, -1),
42024            objURL = createSourceObject(source);
42025        this[TARGET] = new Worker(objURL);
42026        wrapTerminate(this[TARGET], objURL);
42027        return this[TARGET];
42028      } else {
42029        var selfShim = {
42030          postMessage: function postMessage(m) {
42031            if (o.onmessage) {
42032              setTimeout(function () {
42033                o.onmessage({
42034                  data: m,
42035                  target: selfShim
42036                });
42037              });
42038            }
42039          }
42040        };
42041        fn.call(selfShim);
42042
42043        this.postMessage = function (m) {
42044          setTimeout(function () {
42045            selfShim.onmessage({
42046              data: m,
42047              target: o
42048            });
42049          });
42050        };
42051
42052        this.isThisThread = true;
42053      }
42054    };
42055  } // Test Worker capabilities
42056
42057
42058  if (Worker) {
42059    var testWorker,
42060        objURL = createSourceObject('self.onmessage = function () {}'),
42061        testArray = new Uint8Array(1);
42062
42063    try {
42064      testWorker = new Worker(objURL); // Native browser on some Samsung devices throws for transferables, let's detect it
42065
42066      testWorker.postMessage(testArray, [testArray.buffer]);
42067    } catch (e) {
42068      Worker = null;
42069    } finally {
42070      URL.revokeObjectURL(objURL);
42071
42072      if (testWorker) {
42073        testWorker.terminate();
42074      }
42075    }
42076  }
42077
42078  function createSourceObject(str) {
42079    try {
42080      return URL.createObjectURL(new Blob([str], {
42081        type: SCRIPT_TYPE
42082      }));
42083    } catch (e) {
42084      var blob = new BlobBuilder();
42085      blob.append(str);
42086      return URL.createObjectURL(blob.getBlob(type));
42087    }
42088  }
42089
42090  function wrapTerminate(worker, objURL) {
42091    if (!worker || !objURL) return;
42092    var term = worker.terminate;
42093    worker.objURL = objURL;
42094
42095    worker.terminate = function () {
42096      if (worker.objURL) URL.revokeObjectURL(worker.objURL);
42097      term.call(worker);
42098    };
42099  }
42100
42101  var TransmuxWorker = new shimWorker("./transmuxer-worker.worker.js", function (window, document$$1) {
42102    var self = this;
42103
42104    var transmuxerWorker = function () {
42105      /**
42106       * mux.js
42107       *
42108       * Copyright (c) 2015 Brightcove
42109       * All rights reserved.
42110       *
42111       * Functions that generate fragmented MP4s suitable for use with Media
42112       * Source Extensions.
42113       */
42114      var UINT32_MAX = Math.pow(2, 32) - 1;
42115      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
42116
42117      (function () {
42118        var i;
42119        types = {
42120          avc1: [],
42121          // codingname
42122          avcC: [],
42123          btrt: [],
42124          dinf: [],
42125          dref: [],
42126          esds: [],
42127          ftyp: [],
42128          hdlr: [],
42129          mdat: [],
42130          mdhd: [],
42131          mdia: [],
42132          mfhd: [],
42133          minf: [],
42134          moof: [],
42135          moov: [],
42136          mp4a: [],
42137          // codingname
42138          mvex: [],
42139          mvhd: [],
42140          sdtp: [],
42141          smhd: [],
42142          stbl: [],
42143          stco: [],
42144          stsc: [],
42145          stsd: [],
42146          stsz: [],
42147          stts: [],
42148          styp: [],
42149          tfdt: [],
42150          tfhd: [],
42151          traf: [],
42152          trak: [],
42153          trun: [],
42154          trex: [],
42155          tkhd: [],
42156          vmhd: []
42157        }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
42158        // don't throw an error
42159
42160        if (typeof Uint8Array === 'undefined') {
42161          return;
42162        }
42163
42164        for (i in types) {
42165          if (types.hasOwnProperty(i)) {
42166            types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
42167          }
42168        }
42169
42170        MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
42171        AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
42172        MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
42173        VIDEO_HDLR = new Uint8Array([0x00, // version 0
42174        0x00, 0x00, 0x00, // flags
42175        0x00, 0x00, 0x00, 0x00, // pre_defined
42176        0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
42177        0x00, 0x00, 0x00, 0x00, // reserved
42178        0x00, 0x00, 0x00, 0x00, // reserved
42179        0x00, 0x00, 0x00, 0x00, // reserved
42180        0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
42181        ]);
42182        AUDIO_HDLR = new Uint8Array([0x00, // version 0
42183        0x00, 0x00, 0x00, // flags
42184        0x00, 0x00, 0x00, 0x00, // pre_defined
42185        0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
42186        0x00, 0x00, 0x00, 0x00, // reserved
42187        0x00, 0x00, 0x00, 0x00, // reserved
42188        0x00, 0x00, 0x00, 0x00, // reserved
42189        0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
42190        ]);
42191        HDLR_TYPES = {
42192          video: VIDEO_HDLR,
42193          audio: AUDIO_HDLR
42194        };
42195        DREF = new Uint8Array([0x00, // version 0
42196        0x00, 0x00, 0x00, // flags
42197        0x00, 0x00, 0x00, 0x01, // entry_count
42198        0x00, 0x00, 0x00, 0x0c, // entry_size
42199        0x75, 0x72, 0x6c, 0x20, // 'url' type
42200        0x00, // version 0
42201        0x00, 0x00, 0x01 // entry_flags
42202        ]);
42203        SMHD = new Uint8Array([0x00, // version
42204        0x00, 0x00, 0x00, // flags
42205        0x00, 0x00, // balance, 0 means centered
42206        0x00, 0x00 // reserved
42207        ]);
42208        STCO = new Uint8Array([0x00, // version
42209        0x00, 0x00, 0x00, // flags
42210        0x00, 0x00, 0x00, 0x00 // entry_count
42211        ]);
42212        STSC = STCO;
42213        STSZ = new Uint8Array([0x00, // version
42214        0x00, 0x00, 0x00, // flags
42215        0x00, 0x00, 0x00, 0x00, // sample_size
42216        0x00, 0x00, 0x00, 0x00 // sample_count
42217        ]);
42218        STTS = STCO;
42219        VMHD = new Uint8Array([0x00, // version
42220        0x00, 0x00, 0x01, // flags
42221        0x00, 0x00, // graphicsmode
42222        0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
42223        ]);
42224      })();
42225
42226      box = function box(type) {
42227        var payload = [],
42228            size = 0,
42229            i,
42230            result,
42231            view;
42232
42233        for (i = 1; i < arguments.length; i++) {
42234          payload.push(arguments[i]);
42235        }
42236
42237        i = payload.length; // calculate the total size we need to allocate
42238
42239        while (i--) {
42240          size += payload[i].byteLength;
42241        }
42242
42243        result = new Uint8Array(size + 8);
42244        view = new DataView(result.buffer, result.byteOffset, result.byteLength);
42245        view.setUint32(0, result.byteLength);
42246        result.set(type, 4); // copy the payload into the result
42247
42248        for (i = 0, size = 8; i < payload.length; i++) {
42249          result.set(payload[i], size);
42250          size += payload[i].byteLength;
42251        }
42252
42253        return result;
42254      };
42255
42256      dinf = function dinf() {
42257        return box(types.dinf, box(types.dref, DREF));
42258      };
42259
42260      esds = function esds(track) {
42261        return box(types.esds, new Uint8Array([0x00, // version
42262        0x00, 0x00, 0x00, // flags
42263        // ES_Descriptor
42264        0x03, // tag, ES_DescrTag
42265        0x19, // length
42266        0x00, 0x00, // ES_ID
42267        0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
42268        // DecoderConfigDescriptor
42269        0x04, // tag, DecoderConfigDescrTag
42270        0x11, // length
42271        0x40, // object type
42272        0x15, // streamType
42273        0x00, 0x06, 0x00, // bufferSizeDB
42274        0x00, 0x00, 0xda, 0xc0, // maxBitrate
42275        0x00, 0x00, 0xda, 0xc0, // avgBitrate
42276        // DecoderSpecificInfo
42277        0x05, // tag, DecoderSpecificInfoTag
42278        0x02, // length
42279        // ISO/IEC 14496-3, AudioSpecificConfig
42280        // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
42281        track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
42282        ]));
42283      };
42284
42285      ftyp = function ftyp() {
42286        return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
42287      };
42288
42289      hdlr = function hdlr(type) {
42290        return box(types.hdlr, HDLR_TYPES[type]);
42291      };
42292
42293      mdat = function mdat(data) {
42294        return box(types.mdat, data);
42295      };
42296
42297      mdhd = function mdhd(track) {
42298        var result = new Uint8Array([0x00, // version 0
42299        0x00, 0x00, 0x00, // flags
42300        0x00, 0x00, 0x00, 0x02, // creation_time
42301        0x00, 0x00, 0x00, 0x03, // modification_time
42302        0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
42303        track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
42304        0x55, 0xc4, // 'und' language (undetermined)
42305        0x00, 0x00]); // Use the sample rate from the track metadata, when it is
42306        // defined. The sample rate can be parsed out of an ADTS header, for
42307        // instance.
42308
42309        if (track.samplerate) {
42310          result[12] = track.samplerate >>> 24 & 0xFF;
42311          result[13] = track.samplerate >>> 16 & 0xFF;
42312          result[14] = track.samplerate >>> 8 & 0xFF;
42313          result[15] = track.samplerate & 0xFF;
42314        }
42315
42316        return box(types.mdhd, result);
42317      };
42318
42319      mdia = function mdia(track) {
42320        return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
42321      };
42322
42323      mfhd = function mfhd(sequenceNumber) {
42324        return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
42325        (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
42326        ]));
42327      };
42328
42329      minf = function minf(track) {
42330        return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
42331      };
42332
42333      moof = function moof(sequenceNumber, tracks) {
42334        var trackFragments = [],
42335            i = tracks.length; // build traf boxes for each track fragment
42336
42337        while (i--) {
42338          trackFragments[i] = traf(tracks[i]);
42339        }
42340
42341        return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
42342      };
42343      /**
42344       * Returns a movie box.
42345       * @param tracks {array} the tracks associated with this movie
42346       * @see ISO/IEC 14496-12:2012(E), section 8.2.1
42347       */
42348
42349
42350      moov = function moov(tracks) {
42351        var i = tracks.length,
42352            boxes = [];
42353
42354        while (i--) {
42355          boxes[i] = trak(tracks[i]);
42356        }
42357
42358        return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
42359      };
42360
42361      mvex = function mvex(tracks) {
42362        var i = tracks.length,
42363            boxes = [];
42364
42365        while (i--) {
42366          boxes[i] = trex(tracks[i]);
42367        }
42368
42369        return box.apply(null, [types.mvex].concat(boxes));
42370      };
42371
42372      mvhd = function mvhd(duration) {
42373        var bytes = new Uint8Array([0x00, // version 0
42374        0x00, 0x00, 0x00, // flags
42375        0x00, 0x00, 0x00, 0x01, // creation_time
42376        0x00, 0x00, 0x00, 0x02, // modification_time
42377        0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
42378        (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
42379        0x00, 0x01, 0x00, 0x00, // 1.0 rate
42380        0x01, 0x00, // 1.0 volume
42381        0x00, 0x00, // reserved
42382        0x00, 0x00, 0x00, 0x00, // reserved
42383        0x00, 0x00, 0x00, 0x00, // reserved
42384        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
42385        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
42386        0xff, 0xff, 0xff, 0xff // next_track_ID
42387        ]);
42388        return box(types.mvhd, bytes);
42389      };
42390
42391      sdtp = function sdtp(track) {
42392        var samples = track.samples || [],
42393            bytes = new Uint8Array(4 + samples.length),
42394            flags,
42395            i; // leave the full box header (4 bytes) all zero
42396        // write the sample table
42397
42398        for (i = 0; i < samples.length; i++) {
42399          flags = samples[i].flags;
42400          bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
42401        }
42402
42403        return box(types.sdtp, bytes);
42404      };
42405
42406      stbl = function stbl(track) {
42407        return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
42408      };
42409
42410      (function () {
42411        var videoSample, audioSample;
42412
42413        stsd = function stsd(track) {
42414          return box(types.stsd, new Uint8Array([0x00, // version 0
42415          0x00, 0x00, 0x00, // flags
42416          0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
42417        };
42418
42419        videoSample = function videoSample(track) {
42420          var sps = track.sps || [],
42421              pps = track.pps || [],
42422              sequenceParameterSets = [],
42423              pictureParameterSets = [],
42424              i; // assemble the SPSs
42425
42426          for (i = 0; i < sps.length; i++) {
42427            sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
42428            sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
42429
42430            sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
42431          } // assemble the PPSs
42432
42433
42434          for (i = 0; i < pps.length; i++) {
42435            pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
42436            pictureParameterSets.push(pps[i].byteLength & 0xFF);
42437            pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
42438          }
42439
42440          return box(types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
42441          0x00, 0x01, // data_reference_index
42442          0x00, 0x00, // pre_defined
42443          0x00, 0x00, // reserved
42444          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
42445          (track.width & 0xff00) >> 8, track.width & 0xff, // width
42446          (track.height & 0xff00) >> 8, track.height & 0xff, // height
42447          0x00, 0x48, 0x00, 0x00, // horizresolution
42448          0x00, 0x48, 0x00, 0x00, // vertresolution
42449          0x00, 0x00, 0x00, 0x00, // reserved
42450          0x00, 0x01, // frame_count
42451          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
42452          0x00, 0x18, // depth = 24
42453          0x11, 0x11 // pre_defined = -1
42454          ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
42455          track.profileIdc, // AVCProfileIndication
42456          track.profileCompatibility, // profile_compatibility
42457          track.levelIdc, // AVCLevelIndication
42458          0xff // lengthSizeMinusOne, hard-coded to 4 bytes
42459          ].concat([sps.length // numOfSequenceParameterSets
42460          ]).concat(sequenceParameterSets).concat([pps.length // numOfPictureParameterSets
42461          ]).concat(pictureParameterSets))), // "PPS"
42462          box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
42463          0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
42464          0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate
42465          );
42466        };
42467
42468        audioSample = function audioSample(track) {
42469          return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12
42470          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
42471          0x00, 0x01, // data_reference_index
42472          // AudioSampleEntry, ISO/IEC 14496-12
42473          0x00, 0x00, 0x00, 0x00, // reserved
42474          0x00, 0x00, 0x00, 0x00, // reserved
42475          (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
42476          (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
42477          0x00, 0x00, // pre_defined
42478          0x00, 0x00, // reserved
42479          (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
42480          // MP4AudioSampleEntry, ISO/IEC 14496-14
42481          ]), esds(track));
42482        };
42483      })();
42484
42485      tkhd = function tkhd(track) {
42486        var result = new Uint8Array([0x00, // version 0
42487        0x00, 0x00, 0x07, // flags
42488        0x00, 0x00, 0x00, 0x00, // creation_time
42489        0x00, 0x00, 0x00, 0x00, // modification_time
42490        (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
42491        0x00, 0x00, 0x00, 0x00, // reserved
42492        (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
42493        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
42494        0x00, 0x00, // layer
42495        0x00, 0x00, // alternate_group
42496        0x01, 0x00, // non-audio track volume
42497        0x00, 0x00, // reserved
42498        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
42499        (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
42500        (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
42501        ]);
42502        return box(types.tkhd, result);
42503      };
42504      /**
42505       * Generate a track fragment (traf) box. A traf box collects metadata
42506       * about tracks in a movie fragment (moof) box.
42507       */
42508
42509
42510      traf = function traf(track) {
42511        var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
42512        trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
42513        0x00, 0x00, 0x3a, // flags
42514        (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
42515        0x00, 0x00, 0x00, 0x01, // sample_description_index
42516        0x00, 0x00, 0x00, 0x00, // default_sample_duration
42517        0x00, 0x00, 0x00, 0x00, // default_sample_size
42518        0x00, 0x00, 0x00, 0x00 // default_sample_flags
42519        ]));
42520        upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
42521        lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
42522        trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
42523        0x00, 0x00, 0x00, // flags
42524        // baseMediaDecodeTime
42525        upperWordBaseMediaDecodeTime >>> 24 & 0xFF, upperWordBaseMediaDecodeTime >>> 16 & 0xFF, upperWordBaseMediaDecodeTime >>> 8 & 0xFF, upperWordBaseMediaDecodeTime & 0xFF, lowerWordBaseMediaDecodeTime >>> 24 & 0xFF, lowerWordBaseMediaDecodeTime >>> 16 & 0xFF, lowerWordBaseMediaDecodeTime >>> 8 & 0xFF, lowerWordBaseMediaDecodeTime & 0xFF])); // the data offset specifies the number of bytes from the start of
42526        // the containing moof to the first payload byte of the associated
42527        // mdat
42528
42529        dataOffset = 32 + // tfhd
42530        20 + // tfdt
42531        8 + // traf header
42532        16 + // mfhd
42533        8 + // moof header
42534        8; // mdat header
42535        // audio tracks require less metadata
42536
42537        if (track.type === 'audio') {
42538          trackFragmentRun = trun(track, dataOffset);
42539          return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
42540        } // video tracks should contain an independent and disposable samples
42541        // box (sdtp)
42542        // generate one and adjust offsets to match
42543
42544
42545        sampleDependencyTable = sdtp(track);
42546        trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset);
42547        return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
42548      };
42549      /**
42550       * Generate a track box.
42551       * @param track {object} a track definition
42552       * @return {Uint8Array} the track box
42553       */
42554
42555
42556      trak = function trak(track) {
42557        track.duration = track.duration || 0xffffffff;
42558        return box(types.trak, tkhd(track), mdia(track));
42559      };
42560
42561      trex = function trex(track) {
42562        var result = new Uint8Array([0x00, // version 0
42563        0x00, 0x00, 0x00, // flags
42564        (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
42565        0x00, 0x00, 0x00, 0x01, // default_sample_description_index
42566        0x00, 0x00, 0x00, 0x00, // default_sample_duration
42567        0x00, 0x00, 0x00, 0x00, // default_sample_size
42568        0x00, 0x01, 0x00, 0x01 // default_sample_flags
42569        ]); // the last two bytes of default_sample_flags is the sample
42570        // degradation priority, a hint about the importance of this sample
42571        // relative to others. Lower the degradation priority for all sample
42572        // types other than video.
42573
42574        if (track.type !== 'video') {
42575          result[result.length - 1] = 0x00;
42576        }
42577
42578        return box(types.trex, result);
42579      };
42580
42581      (function () {
42582        var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
42583        // duration is present for the first sample, it will be present for
42584        // all subsequent samples.
42585        // see ISO/IEC 14496-12:2012, Section 8.8.8.1
42586
42587        trunHeader = function trunHeader(samples, offset) {
42588          var durationPresent = 0,
42589              sizePresent = 0,
42590              flagsPresent = 0,
42591              compositionTimeOffset = 0; // trun flag constants
42592
42593          if (samples.length) {
42594            if (samples[0].duration !== undefined) {
42595              durationPresent = 0x1;
42596            }
42597
42598            if (samples[0].size !== undefined) {
42599              sizePresent = 0x2;
42600            }
42601
42602            if (samples[0].flags !== undefined) {
42603              flagsPresent = 0x4;
42604            }
42605
42606            if (samples[0].compositionTimeOffset !== undefined) {
42607              compositionTimeOffset = 0x8;
42608            }
42609          }
42610
42611          return [0x00, // version 0
42612          0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
42613          (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
42614          (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
42615          ];
42616        };
42617
42618        videoTrun = function videoTrun(track, offset) {
42619          var bytes, samples, sample, i;
42620          samples = track.samples || [];
42621          offset += 8 + 12 + 16 * samples.length;
42622          bytes = trunHeader(samples, offset);
42623
42624          for (i = 0; i < samples.length; i++) {
42625            sample = samples[i];
42626            bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
42627            (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size
42628            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
42629            (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
42630            ]);
42631          }
42632
42633          return box(types.trun, new Uint8Array(bytes));
42634        };
42635
42636        audioTrun = function audioTrun(track, offset) {
42637          var bytes, samples, sample, i;
42638          samples = track.samples || [];
42639          offset += 8 + 12 + 8 * samples.length;
42640          bytes = trunHeader(samples, offset);
42641
42642          for (i = 0; i < samples.length; i++) {
42643            sample = samples[i];
42644            bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
42645            (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size
42646          }
42647
42648          return box(types.trun, new Uint8Array(bytes));
42649        };
42650
42651        trun = function trun(track, offset) {
42652          if (track.type === 'audio') {
42653            return audioTrun(track, offset);
42654          }
42655
42656          return videoTrun(track, offset);
42657        };
42658      })();
42659
42660      var mp4Generator = {
42661        ftyp: ftyp,
42662        mdat: mdat,
42663        moof: moof,
42664        moov: moov,
42665        initSegment: function initSegment(tracks) {
42666          var fileType = ftyp(),
42667              movie = moov(tracks),
42668              result;
42669          result = new Uint8Array(fileType.byteLength + movie.byteLength);
42670          result.set(fileType);
42671          result.set(movie, fileType.byteLength);
42672          return result;
42673        }
42674      };
42675
42676      var toUnsigned = function toUnsigned(value) {
42677        return value >>> 0;
42678      };
42679
42680      var bin = {
42681        toUnsigned: toUnsigned
42682      };
42683      var toUnsigned$1 = bin.toUnsigned;
42684
42685      var _findBox, parseType, timescale, startTime, getVideoTrackIds; // Find the data for a box specified by its path
42686
42687
42688      _findBox = function findBox(data, path) {
42689        var results = [],
42690            i,
42691            size,
42692            type,
42693            end,
42694            subresults;
42695
42696        if (!path.length) {
42697          // short-circuit the search for empty paths
42698          return null;
42699        }
42700
42701        for (i = 0; i < data.byteLength;) {
42702          size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
42703          type = parseType(data.subarray(i + 4, i + 8));
42704          end = size > 1 ? i + size : data.byteLength;
42705
42706          if (type === path[0]) {
42707            if (path.length === 1) {
42708              // this is the end of the path and we've found the box we were
42709              // looking for
42710              results.push(data.subarray(i + 8, end));
42711            } else {
42712              // recursively search for the next box along the path
42713              subresults = _findBox(data.subarray(i + 8, end), path.slice(1));
42714
42715              if (subresults.length) {
42716                results = results.concat(subresults);
42717              }
42718            }
42719          }
42720
42721          i = end;
42722        } // we've finished searching all of data
42723
42724
42725        return results;
42726      };
42727      /**
42728       * Returns the string representation of an ASCII encoded four byte buffer.
42729       * @param buffer {Uint8Array} a four-byte buffer to translate
42730       * @return {string} the corresponding string
42731       */
42732
42733
42734      parseType = function parseType(buffer) {
42735        var result = '';
42736        result += String.fromCharCode(buffer[0]);
42737        result += String.fromCharCode(buffer[1]);
42738        result += String.fromCharCode(buffer[2]);
42739        result += String.fromCharCode(buffer[3]);
42740        return result;
42741      };
42742      /**
42743       * Parses an MP4 initialization segment and extracts the timescale
42744       * values for any declared tracks. Timescale values indicate the
42745       * number of clock ticks per second to assume for time-based values
42746       * elsewhere in the MP4.
42747       *
42748       * To determine the start time of an MP4, you need two pieces of
42749       * information: the timescale unit and the earliest base media decode
42750       * time. Multiple timescales can be specified within an MP4 but the
42751       * base media decode time is always expressed in the timescale from
42752       * the media header box for the track:
42753       * ```
42754       * moov > trak > mdia > mdhd.timescale
42755       * ```
42756       * @param init {Uint8Array} the bytes of the init segment
42757       * @return {object} a hash of track ids to timescale values or null if
42758       * the init segment is malformed.
42759       */
42760
42761
42762      timescale = function timescale(init) {
42763        var result = {},
42764            traks = _findBox(init, ['moov', 'trak']); // mdhd timescale
42765
42766
42767        return traks.reduce(function (result, trak) {
42768          var tkhd, version, index, id, mdhd;
42769          tkhd = _findBox(trak, ['tkhd'])[0];
42770
42771          if (!tkhd) {
42772            return null;
42773          }
42774
42775          version = tkhd[0];
42776          index = version === 0 ? 12 : 20;
42777          id = toUnsigned$1(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
42778          mdhd = _findBox(trak, ['mdia', 'mdhd'])[0];
42779
42780          if (!mdhd) {
42781            return null;
42782          }
42783
42784          version = mdhd[0];
42785          index = version === 0 ? 12 : 20;
42786          result[id] = toUnsigned$1(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
42787          return result;
42788        }, result);
42789      };
42790      /**
42791       * Determine the base media decode start time, in seconds, for an MP4
42792       * fragment. If multiple fragments are specified, the earliest time is
42793       * returned.
42794       *
42795       * The base media decode time can be parsed from track fragment
42796       * metadata:
42797       * ```
42798       * moof > traf > tfdt.baseMediaDecodeTime
42799       * ```
42800       * It requires the timescale value from the mdhd to interpret.
42801       *
42802       * @param timescale {object} a hash of track ids to timescale values.
42803       * @return {number} the earliest base media decode start time for the
42804       * fragment, in seconds
42805       */
42806
42807
42808      startTime = function startTime(timescale, fragment) {
42809        var trafs, baseTimes, result; // we need info from two childrend of each track fragment box
42810
42811        trafs = _findBox(fragment, ['moof', 'traf']); // determine the start times for each track
42812
42813        baseTimes = [].concat.apply([], trafs.map(function (traf) {
42814          return _findBox(traf, ['tfhd']).map(function (tfhd) {
42815            var id, scale, baseTime; // get the track id from the tfhd
42816
42817            id = toUnsigned$1(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
42818
42819            scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
42820
42821            baseTime = _findBox(traf, ['tfdt']).map(function (tfdt) {
42822              var version, result;
42823              version = tfdt[0];
42824              result = toUnsigned$1(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
42825
42826              if (version === 1) {
42827                result *= Math.pow(2, 32);
42828                result += toUnsigned$1(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
42829              }
42830
42831              return result;
42832            })[0];
42833            baseTime = baseTime || Infinity; // convert base time to seconds
42834
42835            return baseTime / scale;
42836          });
42837        })); // return the minimum
42838
42839        result = Math.min.apply(null, baseTimes);
42840        return isFinite(result) ? result : 0;
42841      };
42842      /**
42843        * Find the trackIds of the video tracks in this source.
42844        * Found by parsing the Handler Reference and Track Header Boxes:
42845        *   moov > trak > mdia > hdlr
42846        *   moov > trak > tkhd
42847        *
42848        * @param {Uint8Array} init - The bytes of the init segment for this source
42849        * @return {Number[]} A list of trackIds
42850        *
42851        * @see ISO-BMFF-12/2015, Section 8.4.3
42852       **/
42853
42854
42855      getVideoTrackIds = function getVideoTrackIds(init) {
42856        var traks = _findBox(init, ['moov', 'trak']);
42857
42858        var videoTrackIds = [];
42859        traks.forEach(function (trak) {
42860          var hdlrs = _findBox(trak, ['mdia', 'hdlr']);
42861
42862          var tkhds = _findBox(trak, ['tkhd']);
42863
42864          hdlrs.forEach(function (hdlr, index) {
42865            var handlerType = parseType(hdlr.subarray(8, 12));
42866            var tkhd = tkhds[index];
42867            var view;
42868            var version;
42869            var trackId;
42870
42871            if (handlerType === 'vide') {
42872              view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
42873              version = view.getUint8(0);
42874              trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
42875              videoTrackIds.push(trackId);
42876            }
42877          });
42878        });
42879        return videoTrackIds;
42880      };
42881
42882      var probe$$1 = {
42883        findBox: _findBox,
42884        parseType: parseType,
42885        timescale: timescale,
42886        startTime: startTime,
42887        videoTrackIds: getVideoTrackIds
42888      };
42889      /**
42890       * mux.js
42891       *
42892       * Copyright (c) 2014 Brightcove
42893       * All rights reserved.
42894       *
42895       * A lightweight readable stream implemention that handles event dispatching.
42896       * Objects that inherit from streams should call init in their constructors.
42897       */
42898
42899      var Stream = function Stream() {
42900        this.init = function () {
42901          var listeners = {};
42902          /**
42903           * Add a listener for a specified event type.
42904           * @param type {string} the event name
42905           * @param listener {function} the callback to be invoked when an event of
42906           * the specified type occurs
42907           */
42908
42909          this.on = function (type, listener) {
42910            if (!listeners[type]) {
42911              listeners[type] = [];
42912            }
42913
42914            listeners[type] = listeners[type].concat(listener);
42915          };
42916          /**
42917           * Remove a listener for a specified event type.
42918           * @param type {string} the event name
42919           * @param listener {function} a function previously registered for this
42920           * type of event through `on`
42921           */
42922
42923
42924          this.off = function (type, listener) {
42925            var index;
42926
42927            if (!listeners[type]) {
42928              return false;
42929            }
42930
42931            index = listeners[type].indexOf(listener);
42932            listeners[type] = listeners[type].slice();
42933            listeners[type].splice(index, 1);
42934            return index > -1;
42935          };
42936          /**
42937           * Trigger an event of the specified type on this stream. Any additional
42938           * arguments to this function are passed as parameters to event listeners.
42939           * @param type {string} the event name
42940           */
42941
42942
42943          this.trigger = function (type) {
42944            var callbacks, i, length, args;
42945            callbacks = listeners[type];
42946
42947            if (!callbacks) {
42948              return;
42949            } // Slicing the arguments on every invocation of this method
42950            // can add a significant amount of overhead. Avoid the
42951            // intermediate object creation for the common case of a
42952            // single callback argument
42953
42954
42955            if (arguments.length === 2) {
42956              length = callbacks.length;
42957
42958              for (i = 0; i < length; ++i) {
42959                callbacks[i].call(this, arguments[1]);
42960              }
42961            } else {
42962              args = [];
42963              i = arguments.length;
42964
42965              for (i = 1; i < arguments.length; ++i) {
42966                args.push(arguments[i]);
42967              }
42968
42969              length = callbacks.length;
42970
42971              for (i = 0; i < length; ++i) {
42972                callbacks[i].apply(this, args);
42973              }
42974            }
42975          };
42976          /**
42977           * Destroys the stream and cleans up.
42978           */
42979
42980
42981          this.dispose = function () {
42982            listeners = {};
42983          };
42984        };
42985      };
42986      /**
42987       * Forwards all `data` events on this stream to the destination stream. The
42988       * destination stream should provide a method `push` to receive the data
42989       * events as they arrive.
42990       * @param destination {stream} the stream that will receive all `data` events
42991       * @param autoFlush {boolean} if false, we will not call `flush` on the destination
42992       *                            when the current stream emits a 'done' event
42993       * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
42994       */
42995
42996
42997      Stream.prototype.pipe = function (destination) {
42998        this.on('data', function (data) {
42999          destination.push(data);
43000        });
43001        this.on('done', function (flushSource) {
43002          destination.flush(flushSource);
43003        });
43004        return destination;
43005      }; // Default stream functions that are expected to be overridden to perform
43006      // actual work. These are provided by the prototype as a sort of no-op
43007      // implementation so that we don't have to check for their existence in the
43008      // `pipe` function above.
43009
43010
43011      Stream.prototype.push = function (data) {
43012        this.trigger('data', data);
43013      };
43014
43015      Stream.prototype.flush = function (flushSource) {
43016        this.trigger('done', flushSource);
43017      };
43018
43019      var stream = Stream; // Convert an array of nal units into an array of frames with each frame being
43020      // composed of the nal units that make up that frame
43021      // Also keep track of cummulative data about the frame from the nal units such
43022      // as the frame duration, starting pts, etc.
43023
43024      var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) {
43025        var i,
43026            currentNal,
43027            currentFrame = [],
43028            frames = [];
43029        currentFrame.byteLength = 0;
43030
43031        for (i = 0; i < nalUnits.length; i++) {
43032          currentNal = nalUnits[i]; // Split on 'aud'-type nal units
43033
43034          if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
43035            // Since the very first nal unit is expected to be an AUD
43036            // only push to the frames array when currentFrame is not empty
43037            if (currentFrame.length) {
43038              currentFrame.duration = currentNal.dts - currentFrame.dts;
43039              frames.push(currentFrame);
43040            }
43041
43042            currentFrame = [currentNal];
43043            currentFrame.byteLength = currentNal.data.byteLength;
43044            currentFrame.pts = currentNal.pts;
43045            currentFrame.dts = currentNal.dts;
43046          } else {
43047            // Specifically flag key frames for ease of use later
43048            if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
43049              currentFrame.keyFrame = true;
43050            }
43051
43052            currentFrame.duration = currentNal.dts - currentFrame.dts;
43053            currentFrame.byteLength += currentNal.data.byteLength;
43054            currentFrame.push(currentNal);
43055          }
43056        } // For the last frame, use the duration of the previous frame if we
43057        // have nothing better to go on
43058
43059
43060        if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
43061          currentFrame.duration = frames[frames.length - 1].duration;
43062        } // Push the final frame
43063
43064
43065        frames.push(currentFrame);
43066        return frames;
43067      }; // Convert an array of frames into an array of Gop with each Gop being composed
43068      // of the frames that make up that Gop
43069      // Also keep track of cummulative data about the Gop from the frames such as the
43070      // Gop duration, starting pts, etc.
43071
43072
43073      var groupFramesIntoGops = function groupFramesIntoGops(frames) {
43074        var i,
43075            currentFrame,
43076            currentGop = [],
43077            gops = []; // We must pre-set some of the values on the Gop since we
43078        // keep running totals of these values
43079
43080        currentGop.byteLength = 0;
43081        currentGop.nalCount = 0;
43082        currentGop.duration = 0;
43083        currentGop.pts = frames[0].pts;
43084        currentGop.dts = frames[0].dts; // store some metadata about all the Gops
43085
43086        gops.byteLength = 0;
43087        gops.nalCount = 0;
43088        gops.duration = 0;
43089        gops.pts = frames[0].pts;
43090        gops.dts = frames[0].dts;
43091
43092        for (i = 0; i < frames.length; i++) {
43093          currentFrame = frames[i];
43094
43095          if (currentFrame.keyFrame) {
43096            // Since the very first frame is expected to be an keyframe
43097            // only push to the gops array when currentGop is not empty
43098            if (currentGop.length) {
43099              gops.push(currentGop);
43100              gops.byteLength += currentGop.byteLength;
43101              gops.nalCount += currentGop.nalCount;
43102              gops.duration += currentGop.duration;
43103            }
43104
43105            currentGop = [currentFrame];
43106            currentGop.nalCount = currentFrame.length;
43107            currentGop.byteLength = currentFrame.byteLength;
43108            currentGop.pts = currentFrame.pts;
43109            currentGop.dts = currentFrame.dts;
43110            currentGop.duration = currentFrame.duration;
43111          } else {
43112            currentGop.duration += currentFrame.duration;
43113            currentGop.nalCount += currentFrame.length;
43114            currentGop.byteLength += currentFrame.byteLength;
43115            currentGop.push(currentFrame);
43116          }
43117        }
43118
43119        if (gops.length && currentGop.duration <= 0) {
43120          currentGop.duration = gops[gops.length - 1].duration;
43121        }
43122
43123        gops.byteLength += currentGop.byteLength;
43124        gops.nalCount += currentGop.nalCount;
43125        gops.duration += currentGop.duration; // push the final Gop
43126
43127        gops.push(currentGop);
43128        return gops;
43129      };
43130      /*
43131       * Search for the first keyframe in the GOPs and throw away all frames
43132       * until that keyframe. Then extend the duration of the pulled keyframe
43133       * and pull the PTS and DTS of the keyframe so that it covers the time
43134       * range of the frames that were disposed.
43135       *
43136       * @param {Array} gops video GOPs
43137       * @returns {Array} modified video GOPs
43138       */
43139
43140
43141      var extendFirstKeyFrame = function extendFirstKeyFrame(gops) {
43142        var currentGop;
43143
43144        if (!gops[0][0].keyFrame && gops.length > 1) {
43145          // Remove the first GOP
43146          currentGop = gops.shift();
43147          gops.byteLength -= currentGop.byteLength;
43148          gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
43149          // first gop to cover the time period of the
43150          // frames we just removed
43151
43152          gops[0][0].dts = currentGop.dts;
43153          gops[0][0].pts = currentGop.pts;
43154          gops[0][0].duration += currentGop.duration;
43155        }
43156
43157        return gops;
43158      };
43159      /**
43160       * Default sample object
43161       * see ISO/IEC 14496-12:2012, section 8.6.4.3
43162       */
43163
43164
43165      var createDefaultSample = function createDefaultSample() {
43166        return {
43167          size: 0,
43168          flags: {
43169            isLeading: 0,
43170            dependsOn: 1,
43171            isDependedOn: 0,
43172            hasRedundancy: 0,
43173            degradationPriority: 0,
43174            isNonSyncSample: 1
43175          }
43176        };
43177      };
43178      /*
43179       * Collates information from a video frame into an object for eventual
43180       * entry into an MP4 sample table.
43181       *
43182       * @param {Object} frame the video frame
43183       * @param {Number} dataOffset the byte offset to position the sample
43184       * @return {Object} object containing sample table info for a frame
43185       */
43186
43187
43188      var sampleForFrame = function sampleForFrame(frame, dataOffset) {
43189        var sample = createDefaultSample();
43190        sample.dataOffset = dataOffset;
43191        sample.compositionTimeOffset = frame.pts - frame.dts;
43192        sample.duration = frame.duration;
43193        sample.size = 4 * frame.length; // Space for nal unit size
43194
43195        sample.size += frame.byteLength;
43196
43197        if (frame.keyFrame) {
43198          sample.flags.dependsOn = 2;
43199          sample.flags.isNonSyncSample = 0;
43200        }
43201
43202        return sample;
43203      }; // generate the track's sample table from an array of gops
43204
43205
43206      var generateSampleTable = function generateSampleTable(gops, baseDataOffset) {
43207        var h,
43208            i,
43209            sample,
43210            currentGop,
43211            currentFrame,
43212            dataOffset = baseDataOffset || 0,
43213            samples = [];
43214
43215        for (h = 0; h < gops.length; h++) {
43216          currentGop = gops[h];
43217
43218          for (i = 0; i < currentGop.length; i++) {
43219            currentFrame = currentGop[i];
43220            sample = sampleForFrame(currentFrame, dataOffset);
43221            dataOffset += sample.size;
43222            samples.push(sample);
43223          }
43224        }
43225
43226        return samples;
43227      }; // generate the track's raw mdat data from an array of gops
43228
43229
43230      var concatenateNalData = function concatenateNalData(gops) {
43231        var h,
43232            i,
43233            j,
43234            currentGop,
43235            currentFrame,
43236            currentNal,
43237            dataOffset = 0,
43238            nalsByteLength = gops.byteLength,
43239            numberOfNals = gops.nalCount,
43240            totalByteLength = nalsByteLength + 4 * numberOfNals,
43241            data = new Uint8Array(totalByteLength),
43242            view = new DataView(data.buffer); // For each Gop..
43243
43244        for (h = 0; h < gops.length; h++) {
43245          currentGop = gops[h]; // For each Frame..
43246
43247          for (i = 0; i < currentGop.length; i++) {
43248            currentFrame = currentGop[i]; // For each NAL..
43249
43250            for (j = 0; j < currentFrame.length; j++) {
43251              currentNal = currentFrame[j];
43252              view.setUint32(dataOffset, currentNal.data.byteLength);
43253              dataOffset += 4;
43254              data.set(currentNal.data, dataOffset);
43255              dataOffset += currentNal.data.byteLength;
43256            }
43257          }
43258        }
43259
43260        return data;
43261      };
43262
43263      var frameUtils = {
43264        groupNalsIntoFrames: groupNalsIntoFrames,
43265        groupFramesIntoGops: groupFramesIntoGops,
43266        extendFirstKeyFrame: extendFirstKeyFrame,
43267        generateSampleTable: generateSampleTable,
43268        concatenateNalData: concatenateNalData
43269      };
43270      var highPrefix = [33, 16, 5, 32, 164, 27];
43271      var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
43272
43273      var zeroFill = function zeroFill(count) {
43274        var a = [];
43275
43276        while (count--) {
43277          a.push(0);
43278        }
43279
43280        return a;
43281      };
43282
43283      var makeTable = function makeTable(metaTable) {
43284        return Object.keys(metaTable).reduce(function (obj, key) {
43285          obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
43286            return arr.concat(part);
43287          }, []));
43288          return obj;
43289        }, {});
43290      }; // Frames-of-silence to use for filling in missing AAC frames
43291
43292
43293      var coneOfSilence = {
43294        96000: [highPrefix, [227, 64], zeroFill(154), [56]],
43295        88200: [highPrefix, [231], zeroFill(170), [56]],
43296        64000: [highPrefix, [248, 192], zeroFill(240), [56]],
43297        48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
43298        44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
43299        32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
43300        24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
43301        16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
43302        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]],
43303        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]],
43304        8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
43305      };
43306      var silence = makeTable(coneOfSilence);
43307      var ONE_SECOND_IN_TS = 90000,
43308          // 90kHz clock
43309      secondsToVideoTs,
43310          secondsToAudioTs,
43311          videoTsToSeconds,
43312          audioTsToSeconds,
43313          audioTsToVideoTs,
43314          videoTsToAudioTs;
43315
43316      secondsToVideoTs = function secondsToVideoTs(seconds) {
43317        return seconds * ONE_SECOND_IN_TS;
43318      };
43319
43320      secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
43321        return seconds * sampleRate;
43322      };
43323
43324      videoTsToSeconds = function videoTsToSeconds(timestamp) {
43325        return timestamp / ONE_SECOND_IN_TS;
43326      };
43327
43328      audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
43329        return timestamp / sampleRate;
43330      };
43331
43332      audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
43333        return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
43334      };
43335
43336      videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
43337        return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
43338      };
43339
43340      var clock = {
43341        secondsToVideoTs: secondsToVideoTs,
43342        secondsToAudioTs: secondsToAudioTs,
43343        videoTsToSeconds: videoTsToSeconds,
43344        audioTsToSeconds: audioTsToSeconds,
43345        audioTsToVideoTs: audioTsToVideoTs,
43346        videoTsToAudioTs: videoTsToAudioTs
43347      };
43348      var ONE_SECOND_IN_TS$1 = 90000; // 90kHz clock
43349
43350      /**
43351       * Sum the `byteLength` properties of the data in each AAC frame
43352       */
43353
43354      var sumFrameByteLengths = function sumFrameByteLengths(array) {
43355        var i,
43356            currentObj,
43357            sum = 0; // sum the byteLength's all each nal unit in the frame
43358
43359        for (i = 0; i < array.length; i++) {
43360          currentObj = array[i];
43361          sum += currentObj.data.byteLength;
43362        }
43363
43364        return sum;
43365      }; // Possibly pad (prefix) the audio track with silence if appending this track
43366      // would lead to the introduction of a gap in the audio buffer
43367
43368
43369      var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
43370        var baseMediaDecodeTimeTs,
43371            frameDuration = 0,
43372            audioGapDuration = 0,
43373            audioFillFrameCount = 0,
43374            audioFillDuration = 0,
43375            silentFrame,
43376            i;
43377
43378        if (!frames.length) {
43379          return;
43380        }
43381
43382        baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
43383
43384        frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 / (track.samplerate / 1024));
43385
43386        if (audioAppendStartTs && videoBaseMediaDecodeTime) {
43387          // insert the shortest possible amount (audio gap or audio to video gap)
43388          audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
43389
43390          audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
43391          audioFillDuration = audioFillFrameCount * frameDuration;
43392        } // don't attempt to fill gaps smaller than a single frame or larger
43393        // than a half second
43394
43395
43396        if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS$1 / 2) {
43397          return;
43398        }
43399
43400        silentFrame = silence[track.samplerate];
43401
43402        if (!silentFrame) {
43403          // we don't have a silent frame pregenerated for the sample rate, so use a frame
43404          // from the content instead
43405          silentFrame = frames[0].data;
43406        }
43407
43408        for (i = 0; i < audioFillFrameCount; i++) {
43409          frames.splice(i, 0, {
43410            data: silentFrame
43411          });
43412        }
43413
43414        track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
43415      }; // If the audio segment extends before the earliest allowed dts
43416      // value, remove AAC frames until starts at or after the earliest
43417      // allowed DTS so that we don't end up with a negative baseMedia-
43418      // DecodeTime for the audio track
43419
43420
43421      var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) {
43422        if (track.minSegmentDts >= earliestAllowedDts) {
43423          return adtsFrames;
43424        } // We will need to recalculate the earliest segment Dts
43425
43426
43427        track.minSegmentDts = Infinity;
43428        return adtsFrames.filter(function (currentFrame) {
43429          // If this is an allowed frame, keep it and record it's Dts
43430          if (currentFrame.dts >= earliestAllowedDts) {
43431            track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
43432            track.minSegmentPts = track.minSegmentDts;
43433            return true;
43434          } // Otherwise, discard it
43435
43436
43437          return false;
43438        });
43439      }; // generate the track's raw mdat data from an array of frames
43440
43441
43442      var generateSampleTable$1 = function generateSampleTable(frames) {
43443        var i,
43444            currentFrame,
43445            samples = [];
43446
43447        for (i = 0; i < frames.length; i++) {
43448          currentFrame = frames[i];
43449          samples.push({
43450            size: currentFrame.data.byteLength,
43451            duration: 1024 // For AAC audio, all samples contain 1024 samples
43452
43453          });
43454        }
43455
43456        return samples;
43457      }; // generate the track's sample table from an array of frames
43458
43459
43460      var concatenateFrameData = function concatenateFrameData(frames) {
43461        var i,
43462            currentFrame,
43463            dataOffset = 0,
43464            data = new Uint8Array(sumFrameByteLengths(frames));
43465
43466        for (i = 0; i < frames.length; i++) {
43467          currentFrame = frames[i];
43468          data.set(currentFrame.data, dataOffset);
43469          dataOffset += currentFrame.data.byteLength;
43470        }
43471
43472        return data;
43473      };
43474
43475      var audioFrameUtils = {
43476        prefixWithSilence: prefixWithSilence,
43477        trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
43478        generateSampleTable: generateSampleTable$1,
43479        concatenateFrameData: concatenateFrameData
43480      };
43481      var ONE_SECOND_IN_TS$2 = 90000; // 90kHz clock
43482
43483      /**
43484       * Store information about the start and end of the track and the
43485       * duration for each frame/sample we process in order to calculate
43486       * the baseMediaDecodeTime
43487       */
43488
43489      var collectDtsInfo = function collectDtsInfo(track, data) {
43490        if (typeof data.pts === 'number') {
43491          if (track.timelineStartInfo.pts === undefined) {
43492            track.timelineStartInfo.pts = data.pts;
43493          }
43494
43495          if (track.minSegmentPts === undefined) {
43496            track.minSegmentPts = data.pts;
43497          } else {
43498            track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
43499          }
43500
43501          if (track.maxSegmentPts === undefined) {
43502            track.maxSegmentPts = data.pts;
43503          } else {
43504            track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
43505          }
43506        }
43507
43508        if (typeof data.dts === 'number') {
43509          if (track.timelineStartInfo.dts === undefined) {
43510            track.timelineStartInfo.dts = data.dts;
43511          }
43512
43513          if (track.minSegmentDts === undefined) {
43514            track.minSegmentDts = data.dts;
43515          } else {
43516            track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
43517          }
43518
43519          if (track.maxSegmentDts === undefined) {
43520            track.maxSegmentDts = data.dts;
43521          } else {
43522            track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
43523          }
43524        }
43525      };
43526      /**
43527       * Clear values used to calculate the baseMediaDecodeTime between
43528       * tracks
43529       */
43530
43531
43532      var clearDtsInfo = function clearDtsInfo(track) {
43533        delete track.minSegmentDts;
43534        delete track.maxSegmentDts;
43535        delete track.minSegmentPts;
43536        delete track.maxSegmentPts;
43537      };
43538      /**
43539       * Calculate the track's baseMediaDecodeTime based on the earliest
43540       * DTS the transmuxer has ever seen and the minimum DTS for the
43541       * current track
43542       * @param track {object} track metadata configuration
43543       * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
43544       *        in the source; false to adjust the first segment to start at 0.
43545       */
43546
43547
43548      var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
43549        var baseMediaDecodeTime,
43550            scale,
43551            minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
43552
43553        if (!keepOriginalTimestamps) {
43554          minSegmentDts -= track.timelineStartInfo.dts;
43555        } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
43556        // we want the start of the first segment to be placed
43557
43558
43559        baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
43560
43561        baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
43562
43563        baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
43564
43565        if (track.type === 'audio') {
43566          // Audio has a different clock equal to the sampling_rate so we need to
43567          // scale the PTS values into the clock rate of the track
43568          scale = track.samplerate / ONE_SECOND_IN_TS$2;
43569          baseMediaDecodeTime *= scale;
43570          baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
43571        }
43572
43573        return baseMediaDecodeTime;
43574      };
43575
43576      var trackDecodeInfo = {
43577        clearDtsInfo: clearDtsInfo,
43578        calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
43579        collectDtsInfo: collectDtsInfo
43580      };
43581      /**
43582       * mux.js
43583       *
43584       * Copyright (c) 2015 Brightcove
43585       * All rights reserved.
43586       *
43587       * Reads in-band caption information from a video elementary
43588       * stream. Captions must follow the CEA-708 standard for injection
43589       * into an MPEG-2 transport streams.
43590       * @see https://en.wikipedia.org/wiki/CEA-708
43591       * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
43592       */
43593      // Supplemental enhancement information (SEI) NAL units have a
43594      // payload type field to indicate how they are to be
43595      // interpreted. CEAS-708 caption content is always transmitted with
43596      // payload type 0x04.
43597
43598      var USER_DATA_REGISTERED_ITU_T_T35 = 4,
43599          RBSP_TRAILING_BITS = 128;
43600      /**
43601        * Parse a supplemental enhancement information (SEI) NAL unit.
43602        * Stops parsing once a message of type ITU T T35 has been found.
43603        *
43604        * @param bytes {Uint8Array} the bytes of a SEI NAL unit
43605        * @return {object} the parsed SEI payload
43606        * @see Rec. ITU-T H.264, 7.3.2.3.1
43607        */
43608
43609      var parseSei = function parseSei(bytes) {
43610        var i = 0,
43611            result = {
43612          payloadType: -1,
43613          payloadSize: 0
43614        },
43615            payloadType = 0,
43616            payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
43617
43618        while (i < bytes.byteLength) {
43619          // stop once we have hit the end of the sei_rbsp
43620          if (bytes[i] === RBSP_TRAILING_BITS) {
43621            break;
43622          } // Parse payload type
43623
43624
43625          while (bytes[i] === 0xFF) {
43626            payloadType += 255;
43627            i++;
43628          }
43629
43630          payloadType += bytes[i++]; // Parse payload size
43631
43632          while (bytes[i] === 0xFF) {
43633            payloadSize += 255;
43634            i++;
43635          }
43636
43637          payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
43638          // there can only ever be one caption message in a frame's sei
43639
43640          if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
43641            result.payloadType = payloadType;
43642            result.payloadSize = payloadSize;
43643            result.payload = bytes.subarray(i, i + payloadSize);
43644            break;
43645          } // skip the payload and parse the next message
43646
43647
43648          i += payloadSize;
43649          payloadType = 0;
43650          payloadSize = 0;
43651        }
43652
43653        return result;
43654      }; // see ANSI/SCTE 128-1 (2013), section 8.1
43655
43656
43657      var parseUserData = function parseUserData(sei) {
43658        // itu_t_t35_contry_code must be 181 (United States) for
43659        // captions
43660        if (sei.payload[0] !== 181) {
43661          return null;
43662        } // itu_t_t35_provider_code should be 49 (ATSC) for captions
43663
43664
43665        if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
43666          return null;
43667        } // the user_identifier should be "GA94" to indicate ATSC1 data
43668
43669
43670        if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
43671          return null;
43672        } // finally, user_data_type_code should be 0x03 for caption data
43673
43674
43675        if (sei.payload[7] !== 0x03) {
43676          return null;
43677        } // return the user_data_type_structure and strip the trailing
43678        // marker bits
43679
43680
43681        return sei.payload.subarray(8, sei.payload.length - 1);
43682      }; // see CEA-708-D, section 4.4
43683
43684
43685      var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
43686        var results = [],
43687            i,
43688            count,
43689            offset,
43690            data; // if this is just filler, return immediately
43691
43692        if (!(userData[0] & 0x40)) {
43693          return results;
43694        } // parse out the cc_data_1 and cc_data_2 fields
43695
43696
43697        count = userData[0] & 0x1f;
43698
43699        for (i = 0; i < count; i++) {
43700          offset = i * 3;
43701          data = {
43702            type: userData[offset + 2] & 0x03,
43703            pts: pts
43704          }; // capture cc data when cc_valid is 1
43705
43706          if (userData[offset + 2] & 0x04) {
43707            data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
43708            results.push(data);
43709          }
43710        }
43711
43712        return results;
43713      };
43714
43715      var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
43716        var length = data.byteLength,
43717            emulationPreventionBytesPositions = [],
43718            i = 1,
43719            newLength,
43720            newData; // Find all `Emulation Prevention Bytes`
43721
43722        while (i < length - 2) {
43723          if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
43724            emulationPreventionBytesPositions.push(i + 2);
43725            i += 2;
43726          } else {
43727            i++;
43728          }
43729        } // If no Emulation Prevention Bytes were found just return the original
43730        // array
43731
43732
43733        if (emulationPreventionBytesPositions.length === 0) {
43734          return data;
43735        } // Create a new array to hold the NAL unit data
43736
43737
43738        newLength = length - emulationPreventionBytesPositions.length;
43739        newData = new Uint8Array(newLength);
43740        var sourceIndex = 0;
43741
43742        for (i = 0; i < newLength; sourceIndex++, i++) {
43743          if (sourceIndex === emulationPreventionBytesPositions[0]) {
43744            // Skip this byte
43745            sourceIndex++; // Remove this position index
43746
43747            emulationPreventionBytesPositions.shift();
43748          }
43749
43750          newData[i] = data[sourceIndex];
43751        }
43752
43753        return newData;
43754      }; // exports
43755
43756
43757      var captionPacketParser = {
43758        parseSei: parseSei,
43759        parseUserData: parseUserData,
43760        parseCaptionPackets: parseCaptionPackets,
43761        discardEmulationPreventionBytes: discardEmulationPreventionBytes,
43762        USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
43763      }; // -----------------
43764      // Link To Transport
43765      // -----------------
43766
43767      var CaptionStream = function CaptionStream() {
43768        CaptionStream.prototype.init.call(this);
43769        this.captionPackets_ = [];
43770        this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
43771        new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
43772        new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
43773        new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
43774        ];
43775        this.reset(); // forward data and done events from CCs to this CaptionStream
43776
43777        this.ccStreams_.forEach(function (cc) {
43778          cc.on('data', this.trigger.bind(this, 'data'));
43779          cc.on('done', this.trigger.bind(this, 'done'));
43780        }, this);
43781      };
43782
43783      CaptionStream.prototype = new stream();
43784
43785      CaptionStream.prototype.push = function (event) {
43786        var sei, userData, newCaptionPackets; // only examine SEI NALs
43787
43788        if (event.nalUnitType !== 'sei_rbsp') {
43789          return;
43790        } // parse the sei
43791
43792
43793        sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35
43794
43795        if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
43796          return;
43797        } // parse out the user data payload
43798
43799
43800        userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData
43801
43802        if (!userData) {
43803          return;
43804        } // Sometimes, the same segment # will be downloaded twice. To stop the
43805        // caption data from being processed twice, we track the latest dts we've
43806        // received and ignore everything with a dts before that. However, since
43807        // data for a specific dts can be split across packets on either side of
43808        // a segment boundary, we need to make sure we *don't* ignore the packets
43809        // from the *next* segment that have dts === this.latestDts_. By constantly
43810        // tracking the number of packets received with dts === this.latestDts_, we
43811        // know how many should be ignored once we start receiving duplicates.
43812
43813
43814        if (event.dts < this.latestDts_) {
43815          // We've started getting older data, so set the flag.
43816          this.ignoreNextEqualDts_ = true;
43817          return;
43818        } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
43819          this.numSameDts_--;
43820
43821          if (!this.numSameDts_) {
43822            // We've received the last duplicate packet, time to start processing again
43823            this.ignoreNextEqualDts_ = false;
43824          }
43825
43826          return;
43827        } // parse out CC data packets and save them for later
43828
43829
43830        newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
43831        this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
43832
43833        if (this.latestDts_ !== event.dts) {
43834          this.numSameDts_ = 0;
43835        }
43836
43837        this.numSameDts_++;
43838        this.latestDts_ = event.dts;
43839      };
43840
43841      CaptionStream.prototype.flush = function () {
43842        // make sure we actually parsed captions before proceeding
43843        if (!this.captionPackets_.length) {
43844          this.ccStreams_.forEach(function (cc) {
43845            cc.flush();
43846          }, this);
43847          return;
43848        } // In Chrome, the Array#sort function is not stable so add a
43849        // presortIndex that we can use to ensure we get a stable-sort
43850
43851
43852        this.captionPackets_.forEach(function (elem, idx) {
43853          elem.presortIndex = idx;
43854        }); // sort caption byte-pairs based on their PTS values
43855
43856        this.captionPackets_.sort(function (a, b) {
43857          if (a.pts === b.pts) {
43858            return a.presortIndex - b.presortIndex;
43859          }
43860
43861          return a.pts - b.pts;
43862        });
43863        this.captionPackets_.forEach(function (packet) {
43864          if (packet.type < 2) {
43865            // Dispatch packet to the right Cea608Stream
43866            this.dispatchCea608Packet(packet);
43867          } // this is where an 'else' would go for a dispatching packets
43868          // to a theoretical Cea708Stream that handles SERVICEn data
43869
43870        }, this);
43871        this.captionPackets_.length = 0;
43872        this.ccStreams_.forEach(function (cc) {
43873          cc.flush();
43874        }, this);
43875        return;
43876      };
43877
43878      CaptionStream.prototype.reset = function () {
43879        this.latestDts_ = null;
43880        this.ignoreNextEqualDts_ = false;
43881        this.numSameDts_ = 0;
43882        this.activeCea608Channel_ = [null, null];
43883        this.ccStreams_.forEach(function (ccStream) {
43884          ccStream.reset();
43885        });
43886      };
43887
43888      CaptionStream.prototype.dispatchCea608Packet = function (packet) {
43889        // NOTE: packet.type is the CEA608 field
43890        if (this.setsChannel1Active(packet)) {
43891          this.activeCea608Channel_[packet.type] = 0;
43892        } else if (this.setsChannel2Active(packet)) {
43893          this.activeCea608Channel_[packet.type] = 1;
43894        }
43895
43896        if (this.activeCea608Channel_[packet.type] === null) {
43897          // If we haven't received anything to set the active channel, discard the
43898          // data; we don't want jumbled captions
43899          return;
43900        }
43901
43902        this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
43903      };
43904
43905      CaptionStream.prototype.setsChannel1Active = function (packet) {
43906        return (packet.ccData & 0x7800) === 0x1000;
43907      };
43908
43909      CaptionStream.prototype.setsChannel2Active = function (packet) {
43910        return (packet.ccData & 0x7800) === 0x1800;
43911      }; // ----------------------
43912      // Session to Application
43913      // ----------------------
43914      // This hash maps non-ASCII, special, and extended character codes to their
43915      // proper Unicode equivalent. The first keys that are only a single byte
43916      // are the non-standard ASCII characters, which simply map the CEA608 byte
43917      // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
43918      // character codes, but have their MSB bitmasked with 0x03 so that a lookup
43919      // can be performed regardless of the field and data channel on which the
43920      // character code was received.
43921
43922
43923      var CHARACTER_TRANSLATION = {
43924        0x2a: 0xe1,
43925        // á
43926        0x5c: 0xe9,
43927        // é
43928        0x5e: 0xed,
43929        // í
43930        0x5f: 0xf3,
43931        // ó
43932        0x60: 0xfa,
43933        // ú
43934        0x7b: 0xe7,
43935        // ç
43936        0x7c: 0xf7,
43937        // ÷
43938        0x7d: 0xd1,
43939        // Ñ
43940        0x7e: 0xf1,
43941        // ñ
43942        0x7f: 0x2588,
43943        // █
43944        0x0130: 0xae,
43945        // ®
43946        0x0131: 0xb0,
43947        // °
43948        0x0132: 0xbd,
43949        // ½
43950        0x0133: 0xbf,
43951        // ¿
43952        0x0134: 0x2122,
43953        // ™
43954        0x0135: 0xa2,
43955        // ¢
43956        0x0136: 0xa3,
43957        // £
43958        0x0137: 0x266a,
43959        // ♪
43960        0x0138: 0xe0,
43961        // à
43962        0x0139: 0xa0,
43963        //
43964        0x013a: 0xe8,
43965        // è
43966        0x013b: 0xe2,
43967        // â
43968        0x013c: 0xea,
43969        // ê
43970        0x013d: 0xee,
43971        // î
43972        0x013e: 0xf4,
43973        // ô
43974        0x013f: 0xfb,
43975        // û
43976        0x0220: 0xc1,
43977        // Á
43978        0x0221: 0xc9,
43979        // É
43980        0x0222: 0xd3,
43981        // Ó
43982        0x0223: 0xda,
43983        // Ú
43984        0x0224: 0xdc,
43985        // Ü
43986        0x0225: 0xfc,
43987        // ü
43988        0x0226: 0x2018,
43989        // ‘
43990        0x0227: 0xa1,
43991        // ¡
43992        0x0228: 0x2a,
43993        // *
43994        0x0229: 0x27,
43995        // '
43996        0x022a: 0x2014,
43997        // —
43998        0x022b: 0xa9,
43999        // ©
44000        0x022c: 0x2120,
44001        // ℠
44002        0x022d: 0x2022,
44003        // •
44004        0x022e: 0x201c,
44005        // “
44006        0x022f: 0x201d,
44007        // ”
44008        0x0230: 0xc0,
44009        // À
44010        0x0231: 0xc2,
44011        // Â
44012        0x0232: 0xc7,
44013        // Ç
44014        0x0233: 0xc8,
44015        // È
44016        0x0234: 0xca,
44017        // Ê
44018        0x0235: 0xcb,
44019        // Ë
44020        0x0236: 0xeb,
44021        // ë
44022        0x0237: 0xce,
44023        // Î
44024        0x0238: 0xcf,
44025        // Ï
44026        0x0239: 0xef,
44027        // ï
44028        0x023a: 0xd4,
44029        // Ô
44030        0x023b: 0xd9,
44031        // Ù
44032        0x023c: 0xf9,
44033        // ù
44034        0x023d: 0xdb,
44035        // Û
44036        0x023e: 0xab,
44037        // «
44038        0x023f: 0xbb,
44039        // »
44040        0x0320: 0xc3,
44041        // Ã
44042        0x0321: 0xe3,
44043        // ã
44044        0x0322: 0xcd,
44045        // Í
44046        0x0323: 0xcc,
44047        // Ì
44048        0x0324: 0xec,
44049        // ì
44050        0x0325: 0xd2,
44051        // Ò
44052        0x0326: 0xf2,
44053        // ò
44054        0x0327: 0xd5,
44055        // Õ
44056        0x0328: 0xf5,
44057        // õ
44058        0x0329: 0x7b,
44059        // {
44060        0x032a: 0x7d,
44061        // }
44062        0x032b: 0x5c,
44063        // \
vendor: 19,869 bytes, lines 44064-44574
44064        0x032c: 0x5e,
44065        // ^
44066        0x032d: 0x5f,
44067        // _
44068        0x032e: 0x7c,
44069        // |
44070        0x032f: 0x7e,
44071        // ~
44072        0x0330: 0xc4,
44073        // Ä
44074        0x0331: 0xe4,
44075        // ä
44076        0x0332: 0xd6,
44077        // Ö
44078        0x0333: 0xf6,
44079        // ö
44080        0x0334: 0xdf,
44081        // ß
44082        0x0335: 0xa5,
44083        // ¥
44084        0x0336: 0xa4,
44085        // ¤
44086        0x0337: 0x2502,
44087        // │
44088        0x0338: 0xc5,
44089        // Å
44090        0x0339: 0xe5,
44091        // å
44092        0x033a: 0xd8,
44093        // Ø
44094        0x033b: 0xf8,
44095        // ø
44096        0x033c: 0x250c,
44097        // ┌
44098        0x033d: 0x2510,
44099        // ┐
44100        0x033e: 0x2514,
44101        // └
44102        0x033f: 0x2518 // ┘
44103
44104      };
44105
44106      var getCharFromCode = function getCharFromCode(code) {
44107        if (code === null) {
44108          return '';
44109        }
44110
44111        code = CHARACTER_TRANSLATION[code] || code;
44112        return String.fromCharCode(code);
44113      }; // the index of the last row in a CEA-608 display buffer
44114
44115
44116      var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
44117      // getting it through bit logic.
44118
44119      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
44120      // cells. The "bottom" row is the last element in the outer array.
44121
44122      var createDisplayBuffer = function createDisplayBuffer() {
44123        var result = [],
44124            i = BOTTOM_ROW + 1;
44125
44126        while (i--) {
44127          result.push('');
44128        }
44129
44130        return result;
44131      };
44132
44133      var Cea608Stream = function Cea608Stream(field, dataChannel) {
44134        Cea608Stream.prototype.init.call(this);
44135        this.field_ = field || 0;
44136        this.dataChannel_ = dataChannel || 0;
44137        this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
44138        this.setConstants();
44139        this.reset();
44140
44141        this.push = function (packet) {
44142          var data, swap, char0, char1, text; // remove the parity bits
44143
44144          data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
44145
44146          if (data === this.lastControlCode_) {
44147            this.lastControlCode_ = null;
44148            return;
44149          } // Store control codes
44150
44151
44152          if ((data & 0xf000) === 0x1000) {
44153            this.lastControlCode_ = data;
44154          } else if (data !== this.PADDING_) {
44155            this.lastControlCode_ = null;
44156          }
44157
44158          char0 = data >>> 8;
44159          char1 = data & 0xff;
44160
44161          if (data === this.PADDING_) {
44162            return;
44163          } else if (data === this.RESUME_CAPTION_LOADING_) {
44164            this.mode_ = 'popOn';
44165          } else if (data === this.END_OF_CAPTION_) {
44166            // If an EOC is received while in paint-on mode, the displayed caption
44167            // text should be swapped to non-displayed memory as if it was a pop-on
44168            // caption. Because of that, we should explicitly switch back to pop-on
44169            // mode
44170            this.mode_ = 'popOn';
44171            this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
44172
44173            this.flushDisplayed(packet.pts); // flip memory
44174
44175            swap = this.displayed_;
44176            this.displayed_ = this.nonDisplayed_;
44177            this.nonDisplayed_ = swap; // start measuring the time to display the caption
44178
44179            this.startPts_ = packet.pts;
44180          } else if (data === this.ROLL_UP_2_ROWS_) {
44181            this.rollUpRows_ = 2;
44182            this.setRollUp(packet.pts);
44183          } else if (data === this.ROLL_UP_3_ROWS_) {
44184            this.rollUpRows_ = 3;
44185            this.setRollUp(packet.pts);
44186          } else if (data === this.ROLL_UP_4_ROWS_) {
44187            this.rollUpRows_ = 4;
44188            this.setRollUp(packet.pts);
44189          } else if (data === this.CARRIAGE_RETURN_) {
44190            this.clearFormatting(packet.pts);
44191            this.flushDisplayed(packet.pts);
44192            this.shiftRowsUp_();
44193            this.startPts_ = packet.pts;
44194          } else if (data === this.BACKSPACE_) {
44195            if (this.mode_ === 'popOn') {
44196              this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
44197            } else {
44198              this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
44199            }
44200          } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
44201            this.flushDisplayed(packet.pts);
44202            this.displayed_ = createDisplayBuffer();
44203          } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
44204            this.nonDisplayed_ = createDisplayBuffer();
44205          } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
44206            if (this.mode_ !== 'paintOn') {
44207              // NOTE: This should be removed when proper caption positioning is
44208              // implemented
44209              this.flushDisplayed(packet.pts);
44210              this.displayed_ = createDisplayBuffer();
44211            }
44212
44213            this.mode_ = 'paintOn';
44214            this.startPts_ = packet.pts; // Append special characters to caption text
44215          } else if (this.isSpecialCharacter(char0, char1)) {
44216            // Bitmask char0 so that we can apply character transformations
44217            // regardless of field and data channel.
44218            // Then byte-shift to the left and OR with char1 so we can pass the
44219            // entire character code to `getCharFromCode`.
44220            char0 = (char0 & 0x03) << 8;
44221            text = getCharFromCode(char0 | char1);
44222            this[this.mode_](packet.pts, text);
44223            this.column_++; // Append extended characters to caption text
44224          } else if (this.isExtCharacter(char0, char1)) {
44225            // Extended characters always follow their "non-extended" equivalents.
44226            // IE if a "è" is desired, you'll always receive "eè"; non-compliant
44227            // decoders are supposed to drop the "è", while compliant decoders
44228            // backspace the "e" and insert "è".
44229            // Delete the previous character
44230            if (this.mode_ === 'popOn') {
44231              this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
44232            } else {
44233              this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
44234            } // Bitmask char0 so that we can apply character transformations
44235            // regardless of field and data channel.
44236            // Then byte-shift to the left and OR with char1 so we can pass the
44237            // entire character code to `getCharFromCode`.
44238
44239
44240            char0 = (char0 & 0x03) << 8;
44241            text = getCharFromCode(char0 | char1);
44242            this[this.mode_](packet.pts, text);
44243            this.column_++; // Process mid-row codes
44244          } else if (this.isMidRowCode(char0, char1)) {
44245            // Attributes are not additive, so clear all formatting
44246            this.clearFormatting(packet.pts); // According to the standard, mid-row codes
44247            // should be replaced with spaces, so add one now
44248
44249            this[this.mode_](packet.pts, ' ');
44250            this.column_++;
44251
44252            if ((char1 & 0xe) === 0xe) {
44253              this.addFormatting(packet.pts, ['i']);
44254            }
44255
44256            if ((char1 & 0x1) === 0x1) {
44257              this.addFormatting(packet.pts, ['u']);
44258            } // Detect offset control codes and adjust cursor
44259
44260          } else if (this.isOffsetControlCode(char0, char1)) {
44261            // Cursor position is set by indent PAC (see below) in 4-column
44262            // increments, with an additional offset code of 1-3 to reach any
44263            // of the 32 columns specified by CEA-608. So all we need to do
44264            // here is increment the column cursor by the given offset.
44265            this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes)
44266          } else if (this.isPAC(char0, char1)) {
44267            // There's no logic for PAC -> row mapping, so we have to just
44268            // find the row code in an array and use its index :(
44269            var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
44270
44271            if (this.mode_ === 'rollUp') {
44272              // This implies that the base row is incorrectly set.
44273              // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
44274              // of roll-up rows set.
44275              if (row - this.rollUpRows_ + 1 < 0) {
44276                row = this.rollUpRows_ - 1;
44277              }
44278
44279              this.setRollUp(packet.pts, row);
44280            }
44281
44282            if (row !== this.row_) {
44283              // formatting is only persistent for current row
44284              this.clearFormatting(packet.pts);
44285              this.row_ = row;
44286            } // All PACs can apply underline, so detect and apply
44287            // (All odd-numbered second bytes set underline)
44288
44289
44290            if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
44291              this.addFormatting(packet.pts, ['u']);
44292            }
44293
44294            if ((data & 0x10) === 0x10) {
44295              // We've got an indent level code. Each successive even number
44296              // increments the column cursor by 4, so we can get the desired
44297              // column position by bit-shifting to the right (to get n/2)
44298              // and multiplying by 4.
44299              this.column_ = ((data & 0xe) >> 1) * 4;
44300            }
44301
44302            if (this.isColorPAC(char1)) {
44303              // it's a color code, though we only support white, which
44304              // can be either normal or italicized. white italics can be
44305              // either 0x4e or 0x6e depending on the row, so we just
44306              // bitwise-and with 0xe to see if italics should be turned on
44307              if ((char1 & 0xe) === 0xe) {
44308                this.addFormatting(packet.pts, ['i']);
44309              }
44310            } // We have a normal character in char0, and possibly one in char1
44311
44312          } else if (this.isNormalChar(char0)) {
44313            if (char1 === 0x00) {
44314              char1 = null;
44315            }
44316
44317            text = getCharFromCode(char0);
44318            text += getCharFromCode(char1);
44319            this[this.mode_](packet.pts, text);
44320            this.column_ += text.length;
44321          } // finish data processing
44322
44323        };
44324      };
44325
44326      Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the
44327      // display buffer
44328
44329      Cea608Stream.prototype.flushDisplayed = function (pts) {
44330        var content = this.displayed_ // remove spaces from the start and end of the string
44331        .map(function (row) {
44332          try {
44333            return row.trim();
44334          } catch (e) {
44335            // Ordinarily, this shouldn't happen. However, caption
44336            // parsing errors should not throw exceptions and
44337            // break playback.
44338            // eslint-disable-next-line no-console
44339            console.error('Skipping malformed caption.');
44340            return '';
44341          }
44342        }) // combine all text rows to display in one cue
44343        .join('\n') // and remove blank rows from the start and end, but not the middle
44344        .replace(/^\n+|\n+$/g, '');
44345
44346        if (content.length) {
44347          this.trigger('data', {
44348            startPts: this.startPts_,
44349            endPts: pts,
44350            text: content,
44351            stream: this.name_
44352          });
44353        }
44354      };
44355      /**
44356       * Zero out the data, used for startup and on seek
44357       */
44358
44359
44360      Cea608Stream.prototype.reset = function () {
44361        this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
44362        // actually display captions. If a caption is shifted to a row
44363        // with a lower index than this, it is cleared from the display
44364        // buffer
44365
44366        this.topRow_ = 0;
44367        this.startPts_ = 0;
44368        this.displayed_ = createDisplayBuffer();
44369        this.nonDisplayed_ = createDisplayBuffer();
44370        this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
44371
44372        this.column_ = 0;
44373        this.row_ = BOTTOM_ROW;
44374        this.rollUpRows_ = 2; // This variable holds currently-applied formatting
44375
44376        this.formatting_ = [];
44377      };
44378      /**
44379       * Sets up control code and related constants for this instance
44380       */
44381
44382
44383      Cea608Stream.prototype.setConstants = function () {
44384        // The following attributes have these uses:
44385        // ext_ :    char0 for mid-row codes, and the base for extended
44386        //           chars (ext_+0, ext_+1, and ext_+2 are char0s for
44387        //           extended codes)
44388        // control_: char0 for control codes, except byte-shifted to the
44389        //           left so that we can do this.control_ | CONTROL_CODE
44390        // offset_:  char0 for tab offset codes
44391        //
44392        // It's also worth noting that control codes, and _only_ control codes,
44393        // differ between field 1 and field2. Field 2 control codes are always
44394        // their field 1 value plus 1. That's why there's the "| field" on the
44395        // control value.
44396        if (this.dataChannel_ === 0) {
44397          this.BASE_ = 0x10;
44398          this.EXT_ = 0x11;
44399          this.CONTROL_ = (0x14 | this.field_) << 8;
44400          this.OFFSET_ = 0x17;
44401        } else if (this.dataChannel_ === 1) {
44402          this.BASE_ = 0x18;
44403          this.EXT_ = 0x19;
44404          this.CONTROL_ = (0x1c | this.field_) << 8;
44405          this.OFFSET_ = 0x1f;
44406        } // Constants for the LSByte command codes recognized by Cea608Stream. This
44407        // list is not exhaustive. For a more comprehensive listing and semantics see
44408        // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
44409        // Padding
44410
44411
44412        this.PADDING_ = 0x0000; // Pop-on Mode
44413
44414        this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
44415        this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
44416
44417        this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
44418        this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
44419        this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
44420        this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
44421
44422        this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
44423
44424        this.BACKSPACE_ = this.CONTROL_ | 0x21;
44425        this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
44426        this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
44427      };
44428      /**
44429       * Detects if the 2-byte packet data is a special character
44430       *
44431       * Special characters have a second byte in the range 0x30 to 0x3f,
44432       * with the first byte being 0x11 (for data channel 1) or 0x19 (for
44433       * data channel 2).
44434       *
44435       * @param  {Integer} char0 The first byte
44436       * @param  {Integer} char1 The second byte
44437       * @return {Boolean}       Whether the 2 bytes are an special character
44438       */
44439
44440
44441      Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
44442        return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
44443      };
44444      /**
44445       * Detects if the 2-byte packet data is an extended character
44446       *
44447       * Extended characters have a second byte in the range 0x20 to 0x3f,
44448       * with the first byte being 0x12 or 0x13 (for data channel 1) or
44449       * 0x1a or 0x1b (for data channel 2).
44450       *
44451       * @param  {Integer} char0 The first byte
44452       * @param  {Integer} char1 The second byte
44453       * @return {Boolean}       Whether the 2 bytes are an extended character
44454       */
44455
44456
44457      Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
44458        return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
44459      };
44460      /**
44461       * Detects if the 2-byte packet is a mid-row code
44462       *
44463       * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
44464       * the first byte being 0x11 (for data channel 1) or 0x19 (for data
44465       * channel 2).
44466       *
44467       * @param  {Integer} char0 The first byte
44468       * @param  {Integer} char1 The second byte
44469       * @return {Boolean}       Whether the 2 bytes are a mid-row code
44470       */
44471
44472
44473      Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
44474        return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
44475      };
44476      /**
44477       * Detects if the 2-byte packet is an offset control code
44478       *
44479       * Offset control codes have a second byte in the range 0x21 to 0x23,
44480       * with the first byte being 0x17 (for data channel 1) or 0x1f (for
44481       * data channel 2).
44482       *
44483       * @param  {Integer} char0 The first byte
44484       * @param  {Integer} char1 The second byte
44485       * @return {Boolean}       Whether the 2 bytes are an offset control code
44486       */
44487
44488
44489      Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
44490        return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
44491      };
44492      /**
44493       * Detects if the 2-byte packet is a Preamble Address Code
44494       *
44495       * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
44496       * or 0x18 to 0x1f (for data channel 2), with the second byte in the
44497       * range 0x40 to 0x7f.
44498       *
44499       * @param  {Integer} char0 The first byte
44500       * @param  {Integer} char1 The second byte
44501       * @return {Boolean}       Whether the 2 bytes are a PAC
44502       */
44503
44504
44505      Cea608Stream.prototype.isPAC = function (char0, char1) {
44506        return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
44507      };
44508      /**
44509       * Detects if a packet's second byte is in the range of a PAC color code
44510       *
44511       * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
44512       * 0x60 to 0x6f.
44513       *
44514       * @param  {Integer} char1 The second byte
44515       * @return {Boolean}       Whether the byte is a color PAC
44516       */
44517
44518
44519      Cea608Stream.prototype.isColorPAC = function (char1) {
44520        return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
44521      };
44522      /**
44523       * Detects if a single byte is in the range of a normal character
44524       *
44525       * Normal text bytes are in the range 0x20 to 0x7f.
44526       *
44527       * @param  {Integer} char  The byte
44528       * @return {Boolean}       Whether the byte is a normal character
44529       */
44530
44531
44532      Cea608Stream.prototype.isNormalChar = function (char) {
44533        return char >= 0x20 && char <= 0x7f;
44534      };
44535      /**
44536       * Configures roll-up
44537       *
44538       * @param  {Integer} pts         Current PTS
44539       * @param  {Integer} newBaseRow  Used by PACs to slide the current window to
44540       *                               a new position
44541       */
44542
44543
44544      Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
44545        // Reset the base row to the bottom row when switching modes
44546        if (this.mode_ !== 'rollUp') {
44547          this.row_ = BOTTOM_ROW;
44548          this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
44549
44550          this.flushDisplayed(pts);
44551          this.nonDisplayed_ = createDisplayBuffer();
44552          this.displayed_ = createDisplayBuffer();
44553        }
44554
44555        if (newBaseRow !== undefined && newBaseRow !== this.row_) {
44556          // move currently displayed captions (up or down) to the new base row
44557          for (var i = 0; i < this.rollUpRows_; i++) {
44558            this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
44559            this.displayed_[this.row_ - i] = '';
44560          }
44561        }
44562
44563        if (newBaseRow === undefined) {
44564          newBaseRow = this.row_;
44565        }
44566
44567        this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
44568      }; // Adds the opening HTML tag for the passed character to the caption text,
44569      // and keeps track of it for later closing
44570
44571
44572      Cea608Stream.prototype.addFormatting = function (pts, format) {
44573        this.formatting_ = this.formatting_.concat(format);
44574        var text = format.reduce(function (text, format) {
vendor: 44,246 bytes, lines 44575-45818
44575          return text + '<' + format + '>';
44576        }, '');
44577        this[this.mode_](pts, text);
44578      }; // Adds HTML closing tags for current formatting to caption text and
44579      // clears remembered formatting
44580
44581
44582      Cea608Stream.prototype.clearFormatting = function (pts) {
44583        if (!this.formatting_.length) {
44584          return;
44585        }
44586
44587        var text = this.formatting_.reverse().reduce(function (text, format) {
44588          return text + '</' + format + '>';
44589        }, '');
44590        this.formatting_ = [];
44591        this[this.mode_](pts, text);
44592      }; // Mode Implementations
44593
44594
44595      Cea608Stream.prototype.popOn = function (pts, text) {
44596        var baseRow = this.nonDisplayed_[this.row_]; // buffer characters
44597
44598        baseRow += text;
44599        this.nonDisplayed_[this.row_] = baseRow;
44600      };
44601
44602      Cea608Stream.prototype.rollUp = function (pts, text) {
44603        var baseRow = this.displayed_[this.row_];
44604        baseRow += text;
44605        this.displayed_[this.row_] = baseRow;
44606      };
44607
44608      Cea608Stream.prototype.shiftRowsUp_ = function () {
44609        var i; // clear out inactive rows
44610
44611        for (i = 0; i < this.topRow_; i++) {
44612          this.displayed_[i] = '';
44613        }
44614
44615        for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
44616          this.displayed_[i] = '';
44617        } // shift displayed rows up
44618
44619
44620        for (i = this.topRow_; i < this.row_; i++) {
44621          this.displayed_[i] = this.displayed_[i + 1];
44622        } // clear out the bottom row
44623
44624
44625        this.displayed_[this.row_] = '';
44626      };
44627
44628      Cea608Stream.prototype.paintOn = function (pts, text) {
44629        var baseRow = this.displayed_[this.row_];
44630        baseRow += text;
44631        this.displayed_[this.row_] = baseRow;
44632      }; // exports
44633
44634
44635      var captionStream = {
44636        CaptionStream: CaptionStream,
44637        Cea608Stream: Cea608Stream
44638      };
44639      var streamTypes = {
44640        H264_STREAM_TYPE: 0x1B,
44641        ADTS_STREAM_TYPE: 0x0F,
44642        METADATA_STREAM_TYPE: 0x15
44643      };
44644      var MAX_TS = 8589934592;
44645      var RO_THRESH = 4294967296;
44646
44647      var handleRollover = function handleRollover(value, reference) {
44648        var direction = 1;
44649
44650        if (value > reference) {
44651          // If the current timestamp value is greater than our reference timestamp and we detect a
44652          // timestamp rollover, this means the roll over is happening in the opposite direction.
44653          // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
44654          // point will be set to a small number, e.g. 1. The user then seeks backwards over the
44655          // rollover point. In loading this segment, the timestamp values will be very large,
44656          // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
44657          // the time stamp to be `value - 2^33`.
44658          direction = -1;
44659        } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
44660        // cause an incorrect adjustment.
44661
44662
44663        while (Math.abs(reference - value) > RO_THRESH) {
44664          value += direction * MAX_TS;
44665        }
44666
44667        return value;
44668      };
44669
44670      var TimestampRolloverStream = function TimestampRolloverStream(type) {
44671        var lastDTS, referenceDTS;
44672        TimestampRolloverStream.prototype.init.call(this);
44673        this.type_ = type;
44674
44675        this.push = function (data) {
44676          if (data.type !== this.type_) {
44677            return;
44678          }
44679
44680          if (referenceDTS === undefined) {
44681            referenceDTS = data.dts;
44682          }
44683
44684          data.dts = handleRollover(data.dts, referenceDTS);
44685          data.pts = handleRollover(data.pts, referenceDTS);
44686          lastDTS = data.dts;
44687          this.trigger('data', data);
44688        };
44689
44690        this.flush = function () {
44691          referenceDTS = lastDTS;
44692          this.trigger('done');
44693        };
44694
44695        this.discontinuity = function () {
44696          referenceDTS = void 0;
44697          lastDTS = void 0;
44698        };
44699      };
44700
44701      TimestampRolloverStream.prototype = new stream();
44702      var timestampRolloverStream = {
44703        TimestampRolloverStream: TimestampRolloverStream,
44704        handleRollover: handleRollover
44705      };
44706
44707      var percentEncode = function percentEncode(bytes, start, end) {
44708        var i,
44709            result = '';
44710
44711        for (i = start; i < end; i++) {
44712          result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
44713        }
44714
44715        return result;
44716      },
44717          // return the string representation of the specified byte range,
44718      // interpreted as UTf-8.
44719      parseUtf8 = function parseUtf8(bytes, start, end) {
44720        return decodeURIComponent(percentEncode(bytes, start, end));
44721      },
44722          // return the string representation of the specified byte range,
44723      // interpreted as ISO-8859-1.
44724      parseIso88591 = function parseIso88591(bytes, start, end) {
44725        return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
44726      },
44727          parseSyncSafeInteger = function parseSyncSafeInteger(data) {
44728        return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
44729      },
44730          tagParsers = {
44731        TXXX: function TXXX(tag) {
44732          var i;
44733
44734          if (tag.data[0] !== 3) {
44735            // ignore frames with unrecognized character encodings
44736            return;
44737          }
44738
44739          for (i = 1; i < tag.data.length; i++) {
44740            if (tag.data[i] === 0) {
44741              // parse the text fields
44742              tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value
44743
44744              tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
44745              break;
44746            }
44747          }
44748
44749          tag.data = tag.value;
44750        },
44751        WXXX: function WXXX(tag) {
44752          var i;
44753
44754          if (tag.data[0] !== 3) {
44755            // ignore frames with unrecognized character encodings
44756            return;
44757          }
44758
44759          for (i = 1; i < tag.data.length; i++) {
44760            if (tag.data[i] === 0) {
44761              // parse the description and URL fields
44762              tag.description = parseUtf8(tag.data, 1, i);
44763              tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
44764              break;
44765            }
44766          }
44767        },
44768        PRIV: function PRIV(tag) {
44769          var i;
44770
44771          for (i = 0; i < tag.data.length; i++) {
44772            if (tag.data[i] === 0) {
44773              // parse the description and URL fields
44774              tag.owner = parseIso88591(tag.data, 0, i);
44775              break;
44776            }
44777          }
44778
44779          tag.privateData = tag.data.subarray(i + 1);
44780          tag.data = tag.privateData;
44781        }
44782      },
44783          _MetadataStream;
44784
44785      _MetadataStream = function MetadataStream(options) {
44786        var settings = {
44787          debug: !!(options && options.debug),
44788          // the bytes of the program-level descriptor field in MP2T
44789          // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
44790          // program element descriptors"
44791          descriptor: options && options.descriptor
44792        },
44793            // the total size in bytes of the ID3 tag being parsed
44794        tagSize = 0,
44795            // tag data that is not complete enough to be parsed
44796        buffer = [],
44797            // the total number of bytes currently in the buffer
44798        bufferSize = 0,
44799            i;
44800
44801        _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
44802        // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
44803
44804
44805        this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
44806
44807        if (settings.descriptor) {
44808          for (i = 0; i < settings.descriptor.length; i++) {
44809            this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
44810          }
44811        }
44812
44813        this.push = function (chunk) {
44814          var tag, frameStart, frameSize, frame, i, frameHeader;
44815
44816          if (chunk.type !== 'timed-metadata') {
44817            return;
44818          } // if data_alignment_indicator is set in the PES header,
44819          // we must have the start of a new ID3 tag. Assume anything
44820          // remaining in the buffer was malformed and throw it out
44821
44822
44823          if (chunk.dataAlignmentIndicator) {
44824            bufferSize = 0;
44825            buffer.length = 0;
44826          } // ignore events that don't look like ID3 data
44827
44828
44829          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))) {
44830            if (settings.debug) {
44831              // eslint-disable-next-line no-console
44832              console.log('Skipping unrecognized metadata packet');
44833            }
44834
44835            return;
44836          } // add this chunk to the data we've collected so far
44837
44838
44839          buffer.push(chunk);
44840          bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
44841
44842          if (buffer.length === 1) {
44843            // the frame size is transmitted as a 28-bit integer in the
44844            // last four bytes of the ID3 header.
44845            // The most significant bit of each byte is dropped and the
44846            // results concatenated to recover the actual value.
44847            tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
44848            // convenient for our comparisons to include it
44849
44850            tagSize += 10;
44851          } // if the entire frame has not arrived, wait for more data
44852
44853
44854          if (bufferSize < tagSize) {
44855            return;
44856          } // collect the entire frame so it can be parsed
44857
44858
44859          tag = {
44860            data: new Uint8Array(tagSize),
44861            frames: [],
44862            pts: buffer[0].pts,
44863            dts: buffer[0].dts
44864          };
44865
44866          for (i = 0; i < tagSize;) {
44867            tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
44868            i += buffer[0].data.byteLength;
44869            bufferSize -= buffer[0].data.byteLength;
44870            buffer.shift();
44871          } // find the start of the first frame and the end of the tag
44872
44873
44874          frameStart = 10;
44875
44876          if (tag.data[5] & 0x40) {
44877            // advance the frame start past the extended header
44878            frameStart += 4; // header size field
44879
44880            frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
44881
44882            tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
44883          } // parse one or more ID3 frames
44884          // http://id3.org/id3v2.3.0#ID3v2_frame_overview
44885
44886
44887          do {
44888            // determine the number of bytes in this frame
44889            frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
44890
44891            if (frameSize < 1) {
44892              // eslint-disable-next-line no-console
44893              return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
44894            }
44895
44896            frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
44897            frame = {
44898              id: frameHeader,
44899              data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
44900            };
44901            frame.key = frame.id;
44902
44903            if (tagParsers[frame.id]) {
44904              tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start
44905              // time for raw AAC data
44906
44907              if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
44908                var d = frame.data,
44909                    size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
44910                size *= 4;
44911                size += d[7] & 0x03;
44912                frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
44913                // on the value of this frame
44914                // we couldn't have known the appropriate pts and dts before
44915                // parsing this ID3 tag so set those values now
44916
44917                if (tag.pts === undefined && tag.dts === undefined) {
44918                  tag.pts = frame.timeStamp;
44919                  tag.dts = frame.timeStamp;
44920                }
44921
44922                this.trigger('timestamp', frame);
44923              }
44924            }
44925
44926            tag.frames.push(frame);
44927            frameStart += 10; // advance past the frame header
44928
44929            frameStart += frameSize; // advance past the frame body
44930          } while (frameStart < tagSize);
44931
44932          this.trigger('data', tag);
44933        };
44934      };
44935
44936      _MetadataStream.prototype = new stream();
44937      var metadataStream = _MetadataStream;
44938      var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types
44939
44940      var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants
44941
44942
44943      var MP2T_PACKET_LENGTH = 188,
44944          // bytes
44945      SYNC_BYTE = 0x47;
44946      /**
44947       * Splits an incoming stream of binary data into MPEG-2 Transport
44948       * Stream packets.
44949       */
44950
44951      _TransportPacketStream = function TransportPacketStream() {
44952        var buffer = new Uint8Array(MP2T_PACKET_LENGTH),
44953            bytesInBuffer = 0;
44954
44955        _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
44956
44957        /**
44958         * Split a stream of data into M2TS packets
44959        **/
44960
44961
44962        this.push = function (bytes) {
44963          var startIndex = 0,
44964              endIndex = MP2T_PACKET_LENGTH,
44965              everything; // If there are bytes remaining from the last segment, prepend them to the
44966          // bytes that were pushed in
44967
44968          if (bytesInBuffer) {
44969            everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
44970            everything.set(buffer.subarray(0, bytesInBuffer));
44971            everything.set(bytes, bytesInBuffer);
44972            bytesInBuffer = 0;
44973          } else {
44974            everything = bytes;
44975          } // While we have enough data for a packet
44976
44977
44978          while (endIndex < everything.byteLength) {
44979            // Look for a pair of start and end sync bytes in the data..
44980            if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
44981              // We found a packet so emit it and jump one whole packet forward in
44982              // the stream
44983              this.trigger('data', everything.subarray(startIndex, endIndex));
44984              startIndex += MP2T_PACKET_LENGTH;
44985              endIndex += MP2T_PACKET_LENGTH;
44986              continue;
44987            } // If we get here, we have somehow become de-synchronized and we need to step
44988            // forward one byte at a time until we find a pair of sync bytes that denote
44989            // a packet
44990
44991
44992            startIndex++;
44993            endIndex++;
44994          } // If there was some data left over at the end of the segment that couldn't
44995          // possibly be a whole packet, keep it because it might be the start of a packet
44996          // that continues in the next segment
44997
44998
44999          if (startIndex < everything.byteLength) {
45000            buffer.set(everything.subarray(startIndex), 0);
45001            bytesInBuffer = everything.byteLength - startIndex;
45002          }
45003        };
45004        /**
45005         * Passes identified M2TS packets to the TransportParseStream to be parsed
45006        **/
45007
45008
45009        this.flush = function () {
45010          // If the buffer contains a whole packet when we are being flushed, emit it
45011          // and empty the buffer. Otherwise hold onto the data because it may be
45012          // important for decoding the next segment
45013          if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
45014            this.trigger('data', buffer);
45015            bytesInBuffer = 0;
45016          }
45017
45018          this.trigger('done');
45019        };
45020      };
45021
45022      _TransportPacketStream.prototype = new stream();
45023      /**
45024       * Accepts an MP2T TransportPacketStream and emits data events with parsed
45025       * forms of the individual transport stream packets.
45026       */
45027
45028      _TransportParseStream = function TransportParseStream() {
45029        var parsePsi, parsePat, parsePmt, self;
45030
45031        _TransportParseStream.prototype.init.call(this);
45032
45033        self = this;
45034        this.packetsWaitingForPmt = [];
45035        this.programMapTable = undefined;
45036
45037        parsePsi = function parsePsi(payload, psi) {
45038          var offset = 0; // PSI packets may be split into multiple sections and those
45039          // sections may be split into multiple packets. If a PSI
45040          // section starts in this packet, the payload_unit_start_indicator
45041          // will be true and the first byte of the payload will indicate
45042          // the offset from the current position to the start of the
45043          // section.
45044
45045          if (psi.payloadUnitStartIndicator) {
45046            offset += payload[offset] + 1;
45047          }
45048
45049          if (psi.type === 'pat') {
45050            parsePat(payload.subarray(offset), psi);
45051          } else {
45052            parsePmt(payload.subarray(offset), psi);
45053          }
45054        };
45055
45056        parsePat = function parsePat(payload, pat) {
45057          pat.section_number = payload[7]; // eslint-disable-line camelcase
45058
45059          pat.last_section_number = payload[8]; // eslint-disable-line camelcase
45060          // skip the PSI header and parse the first PMT entry
45061
45062          self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
45063          pat.pmtPid = self.pmtPid;
45064        };
45065        /**
45066         * Parse out the relevant fields of a Program Map Table (PMT).
45067         * @param payload {Uint8Array} the PMT-specific portion of an MP2T
45068         * packet. The first byte in this array should be the table_id
45069         * field.
45070         * @param pmt {object} the object that should be decorated with
45071         * fields parsed from the PMT.
45072         */
45073
45074
45075        parsePmt = function parsePmt(payload, pmt) {
45076          var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
45077          // take effect. We don't believe this should ever be the case
45078          // for HLS but we'll ignore "forward" PMT declarations if we see
45079          // them. Future PMT declarations have the current_next_indicator
45080          // set to zero.
45081
45082          if (!(payload[5] & 0x01)) {
45083            return;
45084          } // overwrite any existing program map table
45085
45086
45087          self.programMapTable = {
45088            video: null,
45089            audio: null,
45090            'timed-metadata': {}
45091          }; // the mapping table ends at the end of the current section
45092
45093          sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
45094          tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
45095          // long the program info descriptors are
45096
45097          programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
45098
45099          offset = 12 + programInfoLength;
45100
45101          while (offset < tableEnd) {
45102            var streamType = payload[offset];
45103            var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
45104            // TODO: should this be done for metadata too? for now maintain behavior of
45105            //       multiple metadata streams
45106
45107            if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
45108              self.programMapTable.video = pid;
45109            } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
45110              self.programMapTable.audio = pid;
45111            } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
45112              // map pid to stream type for metadata streams
45113              self.programMapTable['timed-metadata'][pid] = streamType;
45114            } // move to the next table entry
45115            // skip past the elementary stream descriptors, if present
45116
45117
45118            offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
45119          } // record the map on the packet as well
45120
45121
45122          pmt.programMapTable = self.programMapTable;
45123        };
45124        /**
45125         * Deliver a new MP2T packet to the next stream in the pipeline.
45126         */
45127
45128
45129        this.push = function (packet) {
45130          var result = {},
45131              offset = 4;
45132          result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
45133
45134          result.pid = packet[1] & 0x1f;
45135          result.pid <<= 8;
45136          result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
45137          // fifth byte of the TS packet header. The adaptation field is
45138          // used to add stuffing to PES packets that don't fill a complete
45139          // TS packet, and to specify some forms of timing and control data
45140          // that we do not currently use.
45141
45142          if ((packet[3] & 0x30) >>> 4 > 0x01) {
45143            offset += packet[offset] + 1;
45144          } // parse the rest of the packet based on the type
45145
45146
45147          if (result.pid === 0) {
45148            result.type = 'pat';
45149            parsePsi(packet.subarray(offset), result);
45150            this.trigger('data', result);
45151          } else if (result.pid === this.pmtPid) {
45152            result.type = 'pmt';
45153            parsePsi(packet.subarray(offset), result);
45154            this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
45155
45156            while (this.packetsWaitingForPmt.length) {
45157              this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
45158            }
45159          } else if (this.programMapTable === undefined) {
45160            // When we have not seen a PMT yet, defer further processing of
45161            // PES packets until one has been parsed
45162            this.packetsWaitingForPmt.push([packet, offset, result]);
45163          } else {
45164            this.processPes_(packet, offset, result);
45165          }
45166        };
45167
45168        this.processPes_ = function (packet, offset, result) {
45169          // set the appropriate stream type
45170          if (result.pid === this.programMapTable.video) {
45171            result.streamType = streamTypes.H264_STREAM_TYPE;
45172          } else if (result.pid === this.programMapTable.audio) {
45173            result.streamType = streamTypes.ADTS_STREAM_TYPE;
45174          } else {
45175            // if not video or audio, it is timed-metadata or unknown
45176            // if unknown, streamType will be undefined
45177            result.streamType = this.programMapTable['timed-metadata'][result.pid];
45178          }
45179
45180          result.type = 'pes';
45181          result.data = packet.subarray(offset);
45182          this.trigger('data', result);
45183        };
45184      };
45185
45186      _TransportParseStream.prototype = new stream();
45187      _TransportParseStream.STREAM_TYPES = {
45188        h264: 0x1b,
45189        adts: 0x0f
45190      };
45191      /**
45192       * Reconsistutes program elementary stream (PES) packets from parsed
45193       * transport stream packets. That is, if you pipe an
45194       * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
45195       * events will be events which capture the bytes for individual PES
45196       * packets plus relevant metadata that has been extracted from the
45197       * container.
45198       */
45199
45200      _ElementaryStream = function ElementaryStream() {
45201        var self = this,
45202            // PES packet fragments
45203        video = {
45204          data: [],
45205          size: 0
45206        },
45207            audio = {
45208          data: [],
45209          size: 0
45210        },
45211            timedMetadata = {
45212          data: [],
45213          size: 0
45214        },
45215            parsePes = function parsePes(payload, pes) {
45216          var ptsDtsFlags; // get the packet length, this will be 0 for video
45217
45218          pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
45219
45220          pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
45221          // and a DTS value. Determine what combination of values is
45222          // available to work with.
45223
45224          ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
45225          // performs all bitwise operations on 32-bit integers but javascript
45226          // supports a much greater range (52-bits) of integer using standard
45227          // mathematical operations.
45228          // We construct a 31-bit value using bitwise operators over the 31
45229          // most significant bits and then multiply by 4 (equal to a left-shift
45230          // of 2) before we add the final 2 least significant bits of the
45231          // timestamp (equal to an OR.)
45232
45233          if (ptsDtsFlags & 0xC0) {
45234            // the PTS and DTS are not written out directly. For information
45235            // on how they are encoded, see
45236            // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
45237            pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
45238            pes.pts *= 4; // Left shift by 2
45239
45240            pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
45241
45242            pes.dts = pes.pts;
45243
45244            if (ptsDtsFlags & 0x40) {
45245              pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
45246              pes.dts *= 4; // Left shift by 2
45247
45248              pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
45249            }
45250          } // the data section starts immediately after the PES header.
45251          // pes_header_data_length specifies the number of header bytes
45252          // that follow the last byte of the field.
45253
45254
45255          pes.data = payload.subarray(9 + payload[8]);
45256        },
45257
45258        /**
45259          * Pass completely parsed PES packets to the next stream in the pipeline
45260         **/
45261        flushStream = function flushStream(stream$$1, type, forceFlush) {
45262          var packetData = new Uint8Array(stream$$1.size),
45263              event = {
45264            type: type
45265          },
45266              i = 0,
45267              offset = 0,
45268              packetFlushable = false,
45269              fragment; // do nothing if there is not enough buffered data for a complete
45270          // PES header
45271
45272          if (!stream$$1.data.length || stream$$1.size < 9) {
45273            return;
45274          }
45275
45276          event.trackId = stream$$1.data[0].pid; // reassemble the packet
45277
45278          for (i = 0; i < stream$$1.data.length; i++) {
45279            fragment = stream$$1.data[i];
45280            packetData.set(fragment.data, offset);
45281            offset += fragment.data.byteLength;
45282          } // parse assembled packet's PES header
45283
45284
45285          parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
45286          // check that there is enough stream data to fill the packet
45287
45288          packetFlushable = type === 'video' || event.packetLength <= stream$$1.size; // flush pending packets if the conditions are right
45289
45290          if (forceFlush || packetFlushable) {
45291            stream$$1.size = 0;
45292            stream$$1.data.length = 0;
45293          } // only emit packets that are complete. this is to avoid assembling
45294          // incomplete PES packets due to poor segmentation
45295
45296
45297          if (packetFlushable) {
45298            self.trigger('data', event);
45299          }
45300        };
45301
45302        _ElementaryStream.prototype.init.call(this);
45303        /**
45304         * Identifies M2TS packet types and parses PES packets using metadata
45305         * parsed from the PMT
45306         **/
45307
45308
45309        this.push = function (data) {
45310          ({
45311            pat: function pat() {// we have to wait for the PMT to arrive as well before we
45312              // have any meaningful metadata
45313            },
45314            pes: function pes() {
45315              var stream$$1, streamType;
45316
45317              switch (data.streamType) {
45318                case streamTypes.H264_STREAM_TYPE:
45319                case streamTypes.H264_STREAM_TYPE:
45320                  stream$$1 = video;
45321                  streamType = 'video';
45322                  break;
45323
45324                case streamTypes.ADTS_STREAM_TYPE:
45325                  stream$$1 = audio;
45326                  streamType = 'audio';
45327                  break;
45328
45329                case streamTypes.METADATA_STREAM_TYPE:
45330                  stream$$1 = timedMetadata;
45331                  streamType = 'timed-metadata';
45332                  break;
45333
45334                default:
45335                  // ignore unknown stream types
45336                  return;
45337              } // if a new packet is starting, we can flush the completed
45338              // packet
45339
45340
45341              if (data.payloadUnitStartIndicator) {
45342                flushStream(stream$$1, streamType, true);
45343              } // buffer this fragment until we are sure we've received the
45344              // complete payload
45345
45346
45347              stream$$1.data.push(data);
45348              stream$$1.size += data.data.byteLength;
45349            },
45350            pmt: function pmt() {
45351              var event = {
45352                type: 'metadata',
45353                tracks: []
45354              },
45355                  programMapTable = data.programMapTable; // translate audio and video streams to tracks
45356
45357              if (programMapTable.video !== null) {
45358                event.tracks.push({
45359                  timelineStartInfo: {
45360                    baseMediaDecodeTime: 0
45361                  },
45362                  id: +programMapTable.video,
45363                  codec: 'avc',
45364                  type: 'video'
45365                });
45366              }
45367
45368              if (programMapTable.audio !== null) {
45369                event.tracks.push({
45370                  timelineStartInfo: {
45371                    baseMediaDecodeTime: 0
45372                  },
45373                  id: +programMapTable.audio,
45374                  codec: 'adts',
45375                  type: 'audio'
45376                });
45377              }
45378
45379              self.trigger('data', event);
45380            }
45381          })[data.type]();
45382        };
45383        /**
45384         * Flush any remaining input. Video PES packets may be of variable
45385         * length. Normally, the start of a new video packet can trigger the
45386         * finalization of the previous packet. That is not possible if no
45387         * more video is forthcoming, however. In that case, some other
45388         * mechanism (like the end of the file) has to be employed. When it is
45389         * clear that no additional data is forthcoming, calling this method
45390         * will flush the buffered packets.
45391         */
45392
45393
45394        this.flush = function () {
45395          // !!THIS ORDER IS IMPORTANT!!
45396          // video first then audio
45397          flushStream(video, 'video');
45398          flushStream(audio, 'audio');
45399          flushStream(timedMetadata, 'timed-metadata');
45400          this.trigger('done');
45401        };
45402      };
45403
45404      _ElementaryStream.prototype = new stream();
45405      var m2ts = {
45406        PAT_PID: 0x0000,
45407        MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
45408        TransportPacketStream: _TransportPacketStream,
45409        TransportParseStream: _TransportParseStream,
45410        ElementaryStream: _ElementaryStream,
45411        TimestampRolloverStream: TimestampRolloverStream$1,
45412        CaptionStream: captionStream.CaptionStream,
45413        Cea608Stream: captionStream.Cea608Stream,
45414        MetadataStream: metadataStream
45415      };
45416
45417      for (var type in streamTypes) {
45418        if (streamTypes.hasOwnProperty(type)) {
45419          m2ts[type] = streamTypes[type];
45420        }
45421      }
45422
45423      var m2ts_1 = m2ts;
45424
45425      var _AdtsStream;
45426
45427      var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
45428      /*
45429       * Accepts a ElementaryStream and emits data events with parsed
45430       * AAC Audio Frames of the individual packets. Input audio in ADTS
45431       * format is unpacked and re-emitted as AAC frames.
45432       *
45433       * @see http://wiki.multimedia.cx/index.php?title=ADTS
45434       * @see http://wiki.multimedia.cx/?title=Understanding_AAC
45435       */
45436
45437      _AdtsStream = function AdtsStream() {
45438        var buffer;
45439
45440        _AdtsStream.prototype.init.call(this);
45441
45442        this.push = function (packet) {
45443          var i = 0,
45444              frameNum = 0,
45445              frameLength,
45446              protectionSkipBytes,
45447              frameEnd,
45448              oldBuffer,
45449              sampleCount,
45450              adtsFrameDuration;
45451
45452          if (packet.type !== 'audio') {
45453            // ignore non-audio data
45454            return;
45455          } // Prepend any data in the buffer to the input data so that we can parse
45456          // aac frames the cross a PES packet boundary
45457
45458
45459          if (buffer) {
45460            oldBuffer = buffer;
45461            buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
45462            buffer.set(oldBuffer);
45463            buffer.set(packet.data, oldBuffer.byteLength);
45464          } else {
45465            buffer = packet.data;
45466          } // unpack any ADTS frames which have been fully received
45467          // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
45468
45469
45470          while (i + 5 < buffer.length) {
45471            // Loook for the start of an ADTS header..
45472            if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
45473              // If a valid header was not found,  jump one forward and attempt to
45474              // find a valid ADTS header starting at the next byte
45475              i++;
45476              continue;
45477            } // The protection skip bit tells us if we have 2 bytes of CRC data at the
45478            // end of the ADTS header
45479
45480
45481            protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
45482            // end of the sync sequence
45483
45484            frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
45485            sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
45486            adtsFrameDuration = sampleCount * 90000 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
45487            frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return
45488            // and wait for more data
45489
45490            if (buffer.byteLength < frameEnd) {
45491              return;
45492            } // Otherwise, deliver the complete AAC frame
45493
45494
45495            this.trigger('data', {
45496              pts: packet.pts + frameNum * adtsFrameDuration,
45497              dts: packet.dts + frameNum * adtsFrameDuration,
45498              sampleCount: sampleCount,
45499              audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
45500              channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
45501              samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
45502              samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
45503              // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
45504              samplesize: 16,
45505              data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
45506            }); // If the buffer is empty, clear it and return
45507
45508            if (buffer.byteLength === frameEnd) {
45509              buffer = undefined;
45510              return;
45511            }
45512
45513            frameNum++; // Remove the finished frame from the buffer and start the process again
45514
45515            buffer = buffer.subarray(frameEnd);
45516          }
45517        };
45518
45519        this.flush = function () {
45520          this.trigger('done');
45521        };
45522      };
45523
45524      _AdtsStream.prototype = new stream();
45525      var adts = _AdtsStream;
45526      var ExpGolomb;
45527      /**
45528       * Parser for exponential Golomb codes, a variable-bitwidth number encoding
45529       * scheme used by h264.
45530       */
45531
45532      ExpGolomb = function ExpGolomb(workingData) {
45533        var // the number of bytes left to examine in workingData
45534        workingBytesAvailable = workingData.byteLength,
45535            // the current word being examined
45536        workingWord = 0,
45537            // :uint
45538        // the number of bits left to examine in the current word
45539        workingBitsAvailable = 0; // :uint;
45540        // ():uint
45541
45542        this.length = function () {
45543          return 8 * workingBytesAvailable;
45544        }; // ():uint
45545
45546
45547        this.bitsAvailable = function () {
45548          return 8 * workingBytesAvailable + workingBitsAvailable;
45549        }; // ():void
45550
45551
45552        this.loadWord = function () {
45553          var position = workingData.byteLength - workingBytesAvailable,
45554              workingBytes = new Uint8Array(4),
45555              availableBytes = Math.min(4, workingBytesAvailable);
45556
45557          if (availableBytes === 0) {
45558            throw new Error('no bytes available');
45559          }
45560
45561          workingBytes.set(workingData.subarray(position, position + availableBytes));
45562          workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
45563
45564          workingBitsAvailable = availableBytes * 8;
45565          workingBytesAvailable -= availableBytes;
45566        }; // (count:int):void
45567
45568
45569        this.skipBits = function (count) {
45570          var skipBytes; // :int
45571
45572          if (workingBitsAvailable > count) {
45573            workingWord <<= count;
45574            workingBitsAvailable -= count;
45575          } else {
45576            count -= workingBitsAvailable;
45577            skipBytes = Math.floor(count / 8);
45578            count -= skipBytes * 8;
45579            workingBytesAvailable -= skipBytes;
45580            this.loadWord();
45581            workingWord <<= count;
45582            workingBitsAvailable -= count;
45583          }
45584        }; // (size:int):uint
45585
45586
45587        this.readBits = function (size) {
45588          var bits = Math.min(workingBitsAvailable, size),
45589              // :uint
45590          valu = workingWord >>> 32 - bits; // :uint
45591          // if size > 31, handle error
45592
45593          workingBitsAvailable -= bits;
45594
45595          if (workingBitsAvailable > 0) {
45596            workingWord <<= bits;
45597          } else if (workingBytesAvailable > 0) {
45598            this.loadWord();
45599          }
45600
45601          bits = size - bits;
45602
45603          if (bits > 0) {
45604            return valu << bits | this.readBits(bits);
45605          }
45606
45607          return valu;
45608        }; // ():uint
45609
45610
45611        this.skipLeadingZeros = function () {
45612          var leadingZeroCount; // :uint
45613
45614          for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
45615            if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
45616              // the first bit of working word is 1
45617              workingWord <<= leadingZeroCount;
45618              workingBitsAvailable -= leadingZeroCount;
45619              return leadingZeroCount;
45620            }
45621          } // we exhausted workingWord and still have not found a 1
45622
45623
45624          this.loadWord();
45625          return leadingZeroCount + this.skipLeadingZeros();
45626        }; // ():void
45627
45628
45629        this.skipUnsignedExpGolomb = function () {
45630          this.skipBits(1 + this.skipLeadingZeros());
45631        }; // ():void
45632
45633
45634        this.skipExpGolomb = function () {
45635          this.skipBits(1 + this.skipLeadingZeros());
45636        }; // ():uint
45637
45638
45639        this.readUnsignedExpGolomb = function () {
45640          var clz = this.skipLeadingZeros(); // :uint
45641
45642          return this.readBits(clz + 1) - 1;
45643        }; // ():int
45644
45645
45646        this.readExpGolomb = function () {
45647          var valu = this.readUnsignedExpGolomb(); // :int
45648
45649          if (0x01 & valu) {
45650            // the number is odd if the low order bit is set
45651            return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
45652          }
45653
45654          return -1 * (valu >>> 1); // divide by two then make it negative
45655        }; // Some convenience functions
45656        // :Boolean
45657
45658
45659        this.readBoolean = function () {
45660          return this.readBits(1) === 1;
45661        }; // ():int
45662
45663
45664        this.readUnsignedByte = function () {
45665          return this.readBits(8);
45666        };
45667
45668        this.loadWord();
45669      };
45670
45671      var expGolomb = ExpGolomb;
45672
45673      var _H264Stream, _NalByteStream;
45674
45675      var PROFILES_WITH_OPTIONAL_SPS_DATA;
45676      /**
45677       * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
45678       */
45679
45680      _NalByteStream = function NalByteStream() {
45681        var syncPoint = 0,
45682            i,
45683            buffer;
45684
45685        _NalByteStream.prototype.init.call(this);
45686        /*
45687         * Scans a byte stream and triggers a data event with the NAL units found.
45688         * @param {Object} data Event received from H264Stream
45689         * @param {Uint8Array} data.data The h264 byte stream to be scanned
45690         *
45691         * @see H264Stream.push
45692         */
45693
45694
45695        this.push = function (data) {
45696          var swapBuffer;
45697
45698          if (!buffer) {
45699            buffer = data.data;
45700          } else {
45701            swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
45702            swapBuffer.set(buffer);
45703            swapBuffer.set(data.data, buffer.byteLength);
45704            buffer = swapBuffer;
45705          } // Rec. ITU-T H.264, Annex B
45706          // scan for NAL unit boundaries
45707          // a match looks like this:
45708          // 0 0 1 .. NAL .. 0 0 1
45709          // ^ sync point        ^ i
45710          // or this:
45711          // 0 0 1 .. NAL .. 0 0 0
45712          // ^ sync point        ^ i
45713          // advance the sync point to a NAL start, if necessary
45714
45715
45716          for (; syncPoint < buffer.byteLength - 3; syncPoint++) {
45717            if (buffer[syncPoint + 2] === 1) {
45718              // the sync point is properly aligned
45719              i = syncPoint + 5;
45720              break;
45721            }
45722          }
45723
45724          while (i < buffer.byteLength) {
45725            // look at the current byte to determine if we've hit the end of
45726            // a NAL unit boundary
45727            switch (buffer[i]) {
45728              case 0:
45729                // skip past non-sync sequences
45730                if (buffer[i - 1] !== 0) {
45731                  i += 2;
45732                  break;
45733                } else if (buffer[i - 2] !== 0) {
45734                  i++;
45735                  break;
45736                } // deliver the NAL unit if it isn't empty
45737
45738
45739                if (syncPoint + 3 !== i - 2) {
45740                  this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
45741                } // drop trailing zeroes
45742
45743
45744                do {
45745                  i++;
45746                } while (buffer[i] !== 1 && i < buffer.length);
45747
45748                syncPoint = i - 2;
45749                i += 3;
45750                break;
45751
45752              case 1:
45753                // skip past non-sync sequences
45754                if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
45755                  i += 3;
45756                  break;
45757                } // deliver the NAL unit
45758
45759
45760                this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
45761                syncPoint = i - 2;
45762                i += 3;
45763                break;
45764
45765              default:
45766                // the current byte isn't a one or zero, so it cannot be part
45767                // of a sync sequence
45768                i += 3;
45769                break;
45770            }
45771          } // filter out the NAL units that were delivered
45772
45773
45774          buffer = buffer.subarray(syncPoint);
45775          i -= syncPoint;
45776          syncPoint = 0;
45777        };
45778
45779        this.flush = function () {
45780          // deliver the last buffered NAL unit
45781          if (buffer && buffer.byteLength > 3) {
45782            this.trigger('data', buffer.subarray(syncPoint + 3));
45783          } // reset the stream state
45784
45785
45786          buffer = null;
45787          syncPoint = 0;
45788          this.trigger('done');
45789        };
45790      };
45791
45792      _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS
45793      // see Recommendation ITU-T H.264 (4/2013),
45794      // 7.3.2.1.1 Sequence parameter set data syntax
45795
45796      PROFILES_WITH_OPTIONAL_SPS_DATA = {
45797        100: true,
45798        110: true,
45799        122: true,
45800        244: true,
45801        44: true,
45802        83: true,
45803        86: true,
45804        118: true,
45805        128: true,
45806        138: true,
45807        139: true,
45808        134: true
45809      };
45810      /**
45811       * Accepts input from a ElementaryStream and produces H.264 NAL unit data
45812       * events.
45813       */
45814
45815      _H264Stream = function H264Stream() {
45816        var nalByteStream = new _NalByteStream(),
45817            self,
45818            trackId,
45819            currentPts,
45820            currentDts,
45821            discardEmulationPreventionBytes,
45822            readSequenceParameterSet,
45823            skipScalingList;
45824
45825        _H264Stream.prototype.init.call(this);
45826
45827        self = this;
45828        /*
45829         * Pushes a packet from a stream onto the NalByteStream
45830         *
45831         * @param {Object} packet - A packet received from a stream
45832         * @param {Uint8Array} packet.data - The raw bytes of the packet
45833         * @param {Number} packet.dts - Decode timestamp of the packet
45834         * @param {Number} packet.pts - Presentation timestamp of the packet
45835         * @param {Number} packet.trackId - The id of the h264 track this packet came from
45836         * @param {('video'|'audio')} packet.type - The type of packet
45837         *
45838         */
45839
45840        this.push = function (packet) {
45841          if (packet.type !== 'video') {
45842            return;
45843          }
45844
45845          trackId = packet.trackId;
45846          currentPts = packet.pts;
45847          currentDts = packet.dts;
45848          nalByteStream.push(packet);
45849        };
45850        /*
45851         * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
45852         * for the NALUs to the next stream component.
45853         * Also, preprocess caption and sequence parameter NALUs.
45854         *
45855         * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
45856         * @see NalByteStream.push
45857         */
45858
45859
45860        nalByteStream.on('data', function (data) {
45861          var event = {
45862            trackId: trackId,
vendor: 11,634 bytes, lines 45863-46210
45863            pts: currentPts,
45864            dts: currentDts,
45865            data: data
45866          };
45867
45868          switch (data[0] & 0x1f) {
45869            case 0x05:
45870              event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
45871              break;
45872
45873            case 0x06:
45874              event.nalUnitType = 'sei_rbsp';
45875              event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
45876              break;
45877
45878            case 0x07:
45879              event.nalUnitType = 'seq_parameter_set_rbsp';
45880              event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
45881              event.config = readSequenceParameterSet(event.escapedRBSP);
45882              break;
45883
45884            case 0x08:
45885              event.nalUnitType = 'pic_parameter_set_rbsp';
45886              break;
45887
45888            case 0x09:
45889              event.nalUnitType = 'access_unit_delimiter_rbsp';
45890              break;
45891
45892            default:
45893              break;
45894          } // This triggers data on the H264Stream
45895
45896
45897          self.trigger('data', event);
45898        });
45899        nalByteStream.on('done', function () {
45900          self.trigger('done');
45901        });
45902
45903        this.flush = function () {
45904          nalByteStream.flush();
45905        };
45906        /**
45907         * Advance the ExpGolomb decoder past a scaling list. The scaling
45908         * list is optionally transmitted as part of a sequence parameter
45909         * set and is not relevant to transmuxing.
45910         * @param count {number} the number of entries in this scaling list
45911         * @param expGolombDecoder {object} an ExpGolomb pointed to the
45912         * start of a scaling list
45913         * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
45914         */
45915
45916
45917        skipScalingList = function skipScalingList(count, expGolombDecoder) {
45918          var lastScale = 8,
45919              nextScale = 8,
45920              j,
45921              deltaScale;
45922
45923          for (j = 0; j < count; j++) {
45924            if (nextScale !== 0) {
45925              deltaScale = expGolombDecoder.readExpGolomb();
45926              nextScale = (lastScale + deltaScale + 256) % 256;
45927            }
45928
45929            lastScale = nextScale === 0 ? lastScale : nextScale;
45930          }
45931        };
45932        /**
45933         * Expunge any "Emulation Prevention" bytes from a "Raw Byte
45934         * Sequence Payload"
45935         * @param data {Uint8Array} the bytes of a RBSP from a NAL
45936         * unit
45937         * @return {Uint8Array} the RBSP without any Emulation
45938         * Prevention Bytes
45939         */
45940
45941
45942        discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
45943          var length = data.byteLength,
45944              emulationPreventionBytesPositions = [],
45945              i = 1,
45946              newLength,
45947              newData; // Find all `Emulation Prevention Bytes`
45948
45949          while (i < length - 2) {
45950            if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
45951              emulationPreventionBytesPositions.push(i + 2);
45952              i += 2;
45953            } else {
45954              i++;
45955            }
45956          } // If no Emulation Prevention Bytes were found just return the original
45957          // array
45958
45959
45960          if (emulationPreventionBytesPositions.length === 0) {
45961            return data;
45962          } // Create a new array to hold the NAL unit data
45963
45964
45965          newLength = length - emulationPreventionBytesPositions.length;
45966          newData = new Uint8Array(newLength);
45967          var sourceIndex = 0;
45968
45969          for (i = 0; i < newLength; sourceIndex++, i++) {
45970            if (sourceIndex === emulationPreventionBytesPositions[0]) {
45971              // Skip this byte
45972              sourceIndex++; // Remove this position index
45973
45974              emulationPreventionBytesPositions.shift();
45975            }
45976
45977            newData[i] = data[sourceIndex];
45978          }
45979
45980          return newData;
45981        };
45982        /**
45983         * Read a sequence parameter set and return some interesting video
45984         * properties. A sequence parameter set is the H264 metadata that
45985         * describes the properties of upcoming video frames.
45986         * @param data {Uint8Array} the bytes of a sequence parameter set
45987         * @return {object} an object with configuration parsed from the
45988         * sequence parameter set, including the dimensions of the
45989         * associated video frames.
45990         */
45991
45992
45993        readSequenceParameterSet = function readSequenceParameterSet(data) {
45994          var frameCropLeftOffset = 0,
45995              frameCropRightOffset = 0,
45996              frameCropTopOffset = 0,
45997              frameCropBottomOffset = 0,
45998              sarScale = 1,
45999              expGolombDecoder,
46000              profileIdc,
46001              levelIdc,
46002              profileCompatibility,
46003              chromaFormatIdc,
46004              picOrderCntType,
46005              numRefFramesInPicOrderCntCycle,
46006              picWidthInMbsMinus1,
46007              picHeightInMapUnitsMinus1,
46008              frameMbsOnlyFlag,
46009              scalingListCount,
46010              sarRatio,
46011              aspectRatioIdc,
46012              i;
46013          expGolombDecoder = new expGolomb(data);
46014          profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
46015
46016          profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
46017
46018          levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
46019
46020          expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
46021          // some profiles have more optional data we don't need
46022
46023          if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
46024            chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
46025
46026            if (chromaFormatIdc === 3) {
46027              expGolombDecoder.skipBits(1); // separate_colour_plane_flag
46028            }
46029
46030            expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
46031
46032            expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
46033
46034            expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
46035
46036            if (expGolombDecoder.readBoolean()) {
46037              // seq_scaling_matrix_present_flag
46038              scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
46039
46040              for (i = 0; i < scalingListCount; i++) {
46041                if (expGolombDecoder.readBoolean()) {
46042                  // seq_scaling_list_present_flag[ i ]
46043                  if (i < 6) {
46044                    skipScalingList(16, expGolombDecoder);
46045                  } else {
46046                    skipScalingList(64, expGolombDecoder);
46047                  }
46048                }
46049              }
46050            }
46051          }
46052
46053          expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
46054
46055          picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
46056
46057          if (picOrderCntType === 0) {
46058            expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
46059          } else if (picOrderCntType === 1) {
46060            expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
46061
46062            expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
46063
46064            expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
46065
46066            numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
46067
46068            for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
46069              expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
46070            }
46071          }
46072
46073          expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
46074
46075          expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
46076
46077          picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
46078          picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
46079          frameMbsOnlyFlag = expGolombDecoder.readBits(1);
46080
46081          if (frameMbsOnlyFlag === 0) {
46082            expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
46083          }
46084
46085          expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
46086
46087          if (expGolombDecoder.readBoolean()) {
46088            // frame_cropping_flag
46089            frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
46090            frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
46091            frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
46092            frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
46093          }
46094
46095          if (expGolombDecoder.readBoolean()) {
46096            // vui_parameters_present_flag
46097            if (expGolombDecoder.readBoolean()) {
46098              // aspect_ratio_info_present_flag
46099              aspectRatioIdc = expGolombDecoder.readUnsignedByte();
46100
46101              switch (aspectRatioIdc) {
46102                case 1:
46103                  sarRatio = [1, 1];
46104                  break;
46105
46106                case 2:
46107                  sarRatio = [12, 11];
46108                  break;
46109
46110                case 3:
46111                  sarRatio = [10, 11];
46112                  break;
46113
46114                case 4:
46115                  sarRatio = [16, 11];
46116                  break;
46117
46118                case 5:
46119                  sarRatio = [40, 33];
46120                  break;
46121
46122                case 6:
46123                  sarRatio = [24, 11];
46124                  break;
46125
46126                case 7:
46127                  sarRatio = [20, 11];
46128                  break;
46129
46130                case 8:
46131                  sarRatio = [32, 11];
46132                  break;
46133
46134                case 9:
46135                  sarRatio = [80, 33];
46136                  break;
46137
46138                case 10:
46139                  sarRatio = [18, 11];
46140                  break;
46141
46142                case 11:
46143                  sarRatio = [15, 11];
46144                  break;
46145
46146                case 12:
46147                  sarRatio = [64, 33];
46148                  break;
46149
46150                case 13:
46151                  sarRatio = [160, 99];
46152                  break;
46153
46154                case 14:
46155                  sarRatio = [4, 3];
46156                  break;
46157
46158                case 15:
46159                  sarRatio = [3, 2];
46160                  break;
46161
46162                case 16:
46163                  sarRatio = [2, 1];
46164                  break;
46165
46166                case 255:
46167                  {
46168                    sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
46169                    break;
46170                  }
46171              }
46172
46173              if (sarRatio) {
46174                sarScale = sarRatio[0] / sarRatio[1];
46175              }
46176            }
46177          }
46178
46179          return {
46180            profileIdc: profileIdc,
46181            levelIdc: levelIdc,
46182            profileCompatibility: profileCompatibility,
46183            width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
46184            height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2
46185          };
46186        };
46187      };
46188
46189      _H264Stream.prototype = new stream();
46190      var h264 = {
46191        H264Stream: _H264Stream,
46192        NalByteStream: _NalByteStream
46193      };
46194      /**
46195       * mux.js
46196       *
46197       * Copyright (c) 2016 Brightcove
46198       * All rights reserved.
46199       *
46200       * Utilities to detect basic properties and metadata about Aac data.
46201       */
46202
46203      var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
46204
46205      var isLikelyAacData = function isLikelyAacData(data) {
46206        if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) {
46207          return true;
46208        }
46209
46210        return false;
vendor: 7,306 bytes, lines 46211-46417
46211      };
46212
46213      var parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) {
46214        return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
46215      }; // return a percent-encoded representation of the specified byte range
46216      // @see http://en.wikipedia.org/wiki/Percent-encoding
46217
46218
46219      var percentEncode$1 = function percentEncode(bytes, start, end) {
46220        var i,
46221            result = '';
46222
46223        for (i = start; i < end; i++) {
46224          result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
46225        }
46226
46227        return result;
46228      }; // return the string representation of the specified byte range,
46229      // interpreted as ISO-8859-1.
46230
46231
46232      var parseIso88591$1 = function parseIso88591(bytes, start, end) {
46233        return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
46234      };
46235
46236      var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
46237        var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
46238            flags = header[byteIndex + 5],
46239            footerPresent = (flags & 16) >> 4;
46240
46241        if (footerPresent) {
46242          return returnSize + 20;
46243        }
46244
46245        return returnSize + 10;
46246      };
46247
46248      var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
46249        var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
46250            middle = header[byteIndex + 4] << 3,
46251            highTwo = header[byteIndex + 3] & 0x3 << 11;
46252        return highTwo | middle | lowThree;
46253      };
46254
46255      var parseType$1 = function parseType(header, byteIndex) {
46256        if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
46257          return 'timed-metadata';
46258        } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
46259          return 'audio';
46260        }
46261
46262        return null;
46263      };
46264
46265      var parseSampleRate = function parseSampleRate(packet) {
46266        var i = 0;
46267
46268        while (i + 5 < packet.length) {
46269          if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
46270            // If a valid header was not found,  jump one forward and attempt to
46271            // find a valid ADTS header starting at the next byte
46272            i++;
46273            continue;
46274          }
46275
46276          return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2];
46277        }
46278
46279        return null;
46280      };
46281
46282      var parseAacTimestamp = function parseAacTimestamp(packet) {
46283        var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
46284
46285        frameStart = 10;
46286
46287        if (packet[5] & 0x40) {
46288          // advance the frame start past the extended header
46289          frameStart += 4; // header size field
46290
46291          frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14));
46292        } // parse one or more ID3 frames
46293        // http://id3.org/id3v2.3.0#ID3v2_frame_overview
46294
46295
46296        do {
46297          // determine the number of bytes in this frame
46298          frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8));
46299
46300          if (frameSize < 1) {
46301            return null;
46302          }
46303
46304          frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
46305
46306          if (frameHeader === 'PRIV') {
46307            frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
46308
46309            for (var i = 0; i < frame.byteLength; i++) {
46310              if (frame[i] === 0) {
46311                var owner = parseIso88591$1(frame, 0, i);
46312
46313                if (owner === 'com.apple.streaming.transportStreamTimestamp') {
46314                  var d = frame.subarray(i + 1);
46315                  var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
46316                  size *= 4;
46317                  size += d[7] & 0x03;
46318                  return size;
46319                }
46320
46321                break;
46322              }
46323            }
46324          }
46325
46326          frameStart += 10; // advance past the frame header
46327
46328          frameStart += frameSize; // advance past the frame body
46329        } while (frameStart < packet.byteLength);
46330
46331        return null;
46332      };
46333
46334      var utils = {
46335        isLikelyAacData: isLikelyAacData,
46336        parseId3TagSize: parseId3TagSize,
46337        parseAdtsSize: parseAdtsSize,
46338        parseType: parseType$1,
46339        parseSampleRate: parseSampleRate,
46340        parseAacTimestamp: parseAacTimestamp
46341      }; // Constants
46342
46343      var _AacStream;
46344      /**
46345       * Splits an incoming stream of binary data into ADTS and ID3 Frames.
46346       */
46347
46348
46349      _AacStream = function AacStream() {
46350        var everything = new Uint8Array(),
46351            timeStamp = 0;
46352
46353        _AacStream.prototype.init.call(this);
46354
46355        this.setTimestamp = function (timestamp) {
46356          timeStamp = timestamp;
46357        };
46358
46359        this.push = function (bytes) {
46360          var frameSize = 0,
46361              byteIndex = 0,
46362              bytesLeft,
46363              chunk,
46364              packet,
46365              tempLength; // If there are bytes remaining from the last segment, prepend them to the
46366          // bytes that were pushed in
46367
46368          if (everything.length) {
46369            tempLength = everything.length;
46370            everything = new Uint8Array(bytes.byteLength + tempLength);
46371            everything.set(everything.subarray(0, tempLength));
46372            everything.set(bytes, tempLength);
46373          } else {
46374            everything = bytes;
46375          }
46376
46377          while (everything.length - byteIndex >= 3) {
46378            if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
46379              // Exit early because we don't have enough to parse
46380              // the ID3 tag header
46381              if (everything.length - byteIndex < 10) {
46382                break;
46383              } // check framesize
46384
46385
46386              frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
46387              // to emit a full packet
46388              // Add to byteIndex to support multiple ID3 tags in sequence
46389
46390              if (byteIndex + frameSize > everything.length) {
46391                break;
46392              }
46393
46394              chunk = {
46395                type: 'timed-metadata',
46396                data: everything.subarray(byteIndex, byteIndex + frameSize)
46397              };
46398              this.trigger('data', chunk);
46399              byteIndex += frameSize;
46400              continue;
46401            } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
46402              // Exit early because we don't have enough to parse
46403              // the ADTS frame header
46404              if (everything.length - byteIndex < 7) {
46405                break;
46406              }
46407
46408              frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
46409              // to emit a full packet
46410
46411              if (byteIndex + frameSize > everything.length) {
46412                break;
46413              }
46414
46415              packet = {
46416                type: 'audio',
46417                data: everything.subarray(byteIndex, byteIndex + frameSize),
vendor: 6,886 bytes, lines 46418-46599
46418                pts: timeStamp,
46419                dts: timeStamp
46420              };
46421              this.trigger('data', packet);
46422              byteIndex += frameSize;
46423              continue;
46424            }
46425
46426            byteIndex++;
46427          }
46428
46429          bytesLeft = everything.length - byteIndex;
46430
46431          if (bytesLeft > 0) {
46432            everything = everything.subarray(byteIndex);
46433          } else {
46434            everything = new Uint8Array();
46435          }
46436        };
46437      };
46438
46439      _AacStream.prototype = new stream();
46440      var aac = _AacStream;
46441      var H264Stream = h264.H264Stream;
46442      var isLikelyAacData$1 = utils.isLikelyAacData; // constants
46443
46444      var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
46445      var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility']; // object types
46446
46447      var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream;
46448      /**
46449       * Compare two arrays (even typed) for same-ness
46450       */
46451
46452
46453      var arrayEquals = function arrayEquals(a, b) {
46454        var i;
46455
46456        if (a.length !== b.length) {
46457          return false;
46458        } // compare the value of each element in the array
46459
46460
46461        for (i = 0; i < a.length; i++) {
46462          if (a[i] !== b[i]) {
46463            return false;
46464          }
46465        }
46466
46467        return true;
46468      };
46469
46470      var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
46471        var ptsOffsetFromDts = startPts - startDts,
46472            decodeDuration = endDts - startDts,
46473            presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
46474        // however, the player time values will reflect a start from the baseMediaDecodeTime.
46475        // In order to provide relevant values for the player times, base timing info on the
46476        // baseMediaDecodeTime and the DTS and PTS durations of the segment.
46477
46478        return {
46479          start: {
46480            dts: baseMediaDecodeTime,
46481            pts: baseMediaDecodeTime + ptsOffsetFromDts
46482          },
46483          end: {
46484            dts: baseMediaDecodeTime + decodeDuration,
46485            pts: baseMediaDecodeTime + presentationDuration
46486          },
46487          prependedContentDuration: prependedContentDuration,
46488          baseMediaDecodeTime: baseMediaDecodeTime
46489        };
46490      };
46491      /**
46492       * Constructs a single-track, ISO BMFF media segment from AAC data
46493       * events. The output of this stream can be fed to a SourceBuffer
46494       * configured with a suitable initialization segment.
46495       * @param track {object} track metadata configuration
46496       * @param options {object} transmuxer options object
46497       * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
46498       *        in the source; false to adjust the first segment to start at 0.
46499       */
46500
46501
46502      _AudioSegmentStream = function AudioSegmentStream(track, options) {
46503        var adtsFrames = [],
46504            sequenceNumber = 0,
46505            earliestAllowedDts = 0,
46506            audioAppendStartTs = 0,
46507            videoBaseMediaDecodeTime = Infinity;
46508        options = options || {};
46509
46510        _AudioSegmentStream.prototype.init.call(this);
46511
46512        this.push = function (data) {
46513          trackDecodeInfo.collectDtsInfo(track, data);
46514
46515          if (track) {
46516            AUDIO_PROPERTIES.forEach(function (prop) {
46517              track[prop] = data[prop];
46518            });
46519          } // buffer audio data until end() is called
46520
46521
46522          adtsFrames.push(data);
46523        };
46524
46525        this.setEarliestDts = function (earliestDts) {
46526          earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime;
46527        };
46528
46529        this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
46530          videoBaseMediaDecodeTime = baseMediaDecodeTime;
46531        };
46532
46533        this.setAudioAppendStart = function (timestamp) {
46534          audioAppendStartTs = timestamp;
46535        };
46536
46537        this.flush = function () {
46538          var frames, moof, mdat, boxes; // return early if no audio data has been observed
46539
46540          if (adtsFrames.length === 0) {
46541            this.trigger('done', 'AudioSegmentStream');
46542            return;
46543          }
46544
46545          frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
46546          track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
46547          audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
46548          // samples (that is, adts frames) in the audio data
46549
46550          track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
46551
46552          mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
46553          adtsFrames = [];
46554          moof = mp4Generator.moof(sequenceNumber, [track]);
46555          boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
46556
46557          sequenceNumber++;
46558          boxes.set(moof);
46559          boxes.set(mdat, moof.byteLength);
46560          trackDecodeInfo.clearDtsInfo(track);
46561          this.trigger('data', {
46562            track: track,
46563            boxes: boxes
46564          });
46565          this.trigger('done', 'AudioSegmentStream');
46566        };
46567      };
46568
46569      _AudioSegmentStream.prototype = new stream();
46570      /**
46571       * Constructs a single-track, ISO BMFF media segment from H264 data
46572       * events. The output of this stream can be fed to a SourceBuffer
46573       * configured with a suitable initialization segment.
46574       * @param track {object} track metadata configuration
46575       * @param options {object} transmuxer options object
46576       * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
46577       *        gopsToAlignWith list when attempting to align gop pts
46578       * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
46579       *        in the source; false to adjust the first segment to start at 0.
46580       */
46581
46582      _VideoSegmentStream = function VideoSegmentStream(track, options) {
46583        var sequenceNumber = 0,
46584            nalUnits = [],
46585            gopsToAlignWith = [],
46586            config,
46587            pps;
46588        options = options || {};
46589
46590        _VideoSegmentStream.prototype.init.call(this);
46591
46592        delete track.minPTS;
46593        this.gopCache_ = [];
46594        /**
46595          * Constructs a ISO BMFF segment given H264 nalUnits
46596          * @param {Object} nalUnit A data event representing a nalUnit
46597          * @param {String} nalUnit.nalUnitType
46598          * @param {Object} nalUnit.config Properties for a mp4 track
46599          * @param {Uint8Array}
vendor: 11,611 bytes, lines 46599-46893
46599 nalUnit.data The nalUnit bytes
46600          * @see lib/codecs/h264.js
46601         **/
46602
46603        this.push = function (nalUnit) {
46604          trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
46605
46606          if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
46607            config = nalUnit.config;
46608            track.sps = [nalUnit.data];
46609            VIDEO_PROPERTIES.forEach(function (prop) {
46610              track[prop] = config[prop];
46611            }, this);
46612          }
46613
46614          if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
46615            pps = nalUnit.data;
46616            track.pps = [nalUnit.data];
46617          } // buffer video until flush() is called
46618
46619
46620          nalUnits.push(nalUnit);
46621        };
46622        /**
46623          * Pass constructed ISO BMFF track and boxes on to the
46624          * next stream in the pipeline
46625         **/
46626
46627
46628        this.flush = function () {
46629          var frames,
46630              gopForFusion,
46631              gops,
46632              moof,
46633              mdat,
46634              boxes,
46635              prependedContentDuration = 0,
46636              firstGop,
46637              lastGop; // Throw away nalUnits at the start of the byte stream until
46638          // we find the first AUD
46639
46640          while (nalUnits.length) {
46641            if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
46642              break;
46643            }
46644
46645            nalUnits.shift();
46646          } // Return early if no video data has been observed
46647
46648
46649          if (nalUnits.length === 0) {
46650            this.resetStream_();
46651            this.trigger('done', 'VideoSegmentStream');
46652            return;
46653          } // Organize the raw nal-units into arrays that represent
46654          // higher-level constructs such as frames and gops
46655          // (group-of-pictures)
46656
46657
46658          frames = frameUtils.groupNalsIntoFrames(nalUnits);
46659          gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
46660          // a problem since MSE (on Chrome) requires a leading keyframe.
46661          //
46662          // We have two approaches to repairing this situation:
46663          // 1) GOP-FUSION:
46664          //    This is where we keep track of the GOPS (group-of-pictures)
46665          //    from previous fragments and attempt to find one that we can
46666          //    prepend to the current fragment in order to create a valid
46667          //    fragment.
46668          // 2) KEYFRAME-PULLING:
46669          //    Here we search for the first keyframe in the fragment and
46670          //    throw away all the frames between the start of the fragment
46671          //    and that keyframe. We then extend the duration and pull the
46672          //    PTS of the keyframe forward so that it covers the time range
46673          //    of the frames that were disposed of.
46674          //
46675          // #1 is far prefereable over #2 which can cause "stuttering" but
46676          // requires more things to be just right.
46677
46678          if (!gops[0][0].keyFrame) {
46679            // Search for a gop for fusion from our gopCache
46680            gopForFusion = this.getGopForFusion_(nalUnits[0], track);
46681
46682            if (gopForFusion) {
46683              // in order to provide more accurate timing information about the segment, save
46684              // the number of seconds prepended to the original segment due to GOP fusion
46685              prependedContentDuration = gopForFusion.duration;
46686              gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
46687              // new gop at the beginning
46688
46689              gops.byteLength += gopForFusion.byteLength;
46690              gops.nalCount += gopForFusion.nalCount;
46691              gops.pts = gopForFusion.pts;
46692              gops.dts = gopForFusion.dts;
46693              gops.duration += gopForFusion.duration;
46694            } else {
46695              // If we didn't find a candidate gop fall back to keyframe-pulling
46696              gops = frameUtils.extendFirstKeyFrame(gops);
46697            }
46698          } // Trim gops to align with gopsToAlignWith
46699
46700
46701          if (gopsToAlignWith.length) {
46702            var alignedGops;
46703
46704            if (options.alignGopsAtEnd) {
46705              alignedGops = this.alignGopsAtEnd_(gops);
46706            } else {
46707              alignedGops = this.alignGopsAtStart_(gops);
46708            }
46709
46710            if (!alignedGops) {
46711              // save all the nals in the last GOP into the gop cache
46712              this.gopCache_.unshift({
46713                gop: gops.pop(),
46714                pps: track.pps,
46715                sps: track.sps
46716              }); // Keep a maximum of 6 GOPs in the cache
46717
46718              this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
46719
46720              nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
46721
46722              this.resetStream_();
46723              this.trigger('done', 'VideoSegmentStream');
46724              return;
46725            } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
46726            // when recalculated before sending off to CoalesceStream
46727
46728
46729            trackDecodeInfo.clearDtsInfo(track);
46730            gops = alignedGops;
46731          }
46732
46733          trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
46734          // samples (that is, frames) in the video data
46735
46736          track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
46737
46738          mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops));
46739          track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
46740          this.trigger('processedGopsInfo', gops.map(function (gop) {
46741            return {
46742              pts: gop.pts,
46743              dts: gop.dts,
46744              byteLength: gop.byteLength
46745            };
46746          }));
46747          firstGop = gops[0];
46748          lastGop = gops[gops.length - 1];
46749          this.trigger('segmentTimingInfo', generateVideoSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration)); // save all the nals in the last GOP into the gop cache
46750
46751          this.gopCache_.unshift({
46752            gop: gops.pop(),
46753            pps: track.pps,
46754            sps: track.sps
46755          }); // Keep a maximum of 6 GOPs in the cache
46756
46757          this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
46758
46759          nalUnits = [];
46760          this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
46761          this.trigger('timelineStartInfo', track.timelineStartInfo);
46762          moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
46763          // throwing away hundreds of media segment fragments
46764
46765          boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
46766
46767          sequenceNumber++;
46768          boxes.set(moof);
46769          boxes.set(mdat, moof.byteLength);
46770          this.trigger('data', {
46771            track: track,
46772            boxes: boxes
46773          });
46774          this.resetStream_(); // Continue with the flush process now
46775
46776          this.trigger('done', 'VideoSegmentStream');
46777        };
46778
46779        this.resetStream_ = function () {
46780          trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
46781          // for instance, when we are rendition switching
46782
46783          config = undefined;
46784          pps = undefined;
46785        }; // Search for a candidate Gop for gop-fusion from the gop cache and
46786        // return it or return null if no good candidate was found
46787
46788
46789        this.getGopForFusion_ = function (nalUnit) {
46790          var halfSecond = 45000,
46791              // Half-a-second in a 90khz clock
46792          allowableOverlap = 10000,
46793              // About 3 frames @ 30fps
46794          nearestDistance = Infinity,
46795              dtsDistance,
46796              nearestGopObj,
46797              currentGop,
46798              currentGopObj,
46799              i; // Search for the GOP nearest to the beginning of this nal unit
46800
46801          for (i = 0; i < this.gopCache_.length; i++) {
46802            currentGopObj = this.gopCache_[i];
46803            currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
46804
46805            if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
46806              continue;
46807            } // Reject Gops that would require a negative baseMediaDecodeTime
46808
46809
46810            if (currentGop.dts < track.timelineStartInfo.dts) {
46811              continue;
46812            } // The distance between the end of the gop and the start of the nalUnit
46813
46814
46815            dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
46816            // a half-second of the nal unit
46817
46818            if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
46819              // Always use the closest GOP we found if there is more than
46820              // one candidate
46821              if (!nearestGopObj || nearestDistance > dtsDistance) {
46822                nearestGopObj = currentGopObj;
46823                nearestDistance = dtsDistance;
46824              }
46825            }
46826          }
46827
46828          if (nearestGopObj) {
46829            return nearestGopObj.gop;
46830          }
46831
46832          return null;
46833        }; // trim gop list to the first gop found that has a matching pts with a gop in the list
46834        // of gopsToAlignWith starting from the START of the list
46835
46836
46837        this.alignGopsAtStart_ = function (gops) {
46838          var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
46839          byteLength = gops.byteLength;
46840          nalCount = gops.nalCount;
46841          duration = gops.duration;
46842          alignIndex = gopIndex = 0;
46843
46844          while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
46845            align = gopsToAlignWith[alignIndex];
46846            gop = gops[gopIndex];
46847
46848            if (align.pts === gop.pts) {
46849              break;
46850            }
46851
46852            if (gop.pts > align.pts) {
46853              // this current gop starts after the current gop we want to align on, so increment
46854              // align index
46855              alignIndex++;
46856              continue;
46857            } // current gop starts before the current gop we want to align on. so increment gop
46858            // index
46859
46860
46861            gopIndex++;
46862            byteLength -= gop.byteLength;
46863            nalCount -= gop.nalCount;
46864            duration -= gop.duration;
46865          }
46866
46867          if (gopIndex === 0) {
46868            // no gops to trim
46869            return gops;
46870          }
46871
46872          if (gopIndex === gops.length) {
46873            // all gops trimmed, skip appending all gops
46874            return null;
46875          }
46876
46877          alignedGops = gops.slice(gopIndex);
46878          alignedGops.byteLength = byteLength;
46879          alignedGops.duration = duration;
46880          alignedGops.nalCount = nalCount;
46881          alignedGops.pts = alignedGops[0].pts;
46882          alignedGops.dts = alignedGops[0].dts;
46883          return alignedGops;
46884        }; // trim gop list to the first gop found that has a matching pts with a gop in the list
46885        // of gopsToAlignWith starting from the END of the list
46886
46887
46888        this.alignGopsAtEnd_ = function (gops) {
46889          var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
46890          alignIndex = gopsToAlignWith.length - 1;
46891          gopIndex = gops.length - 1;
46892          alignEndIndex = null;
46893          matchFound = false;
vendor: 46,629 bytes, lines 46894-48172
46894
46895          while (alignIndex >= 0 && gopIndex >= 0) {
46896            align = gopsToAlignWith[alignIndex];
46897            gop = gops[gopIndex];
46898
46899            if (align.pts === gop.pts) {
46900              matchFound = true;
46901              break;
46902            }
46903
46904            if (align.pts > gop.pts) {
46905              alignIndex--;
46906              continue;
46907            }
46908
46909            if (alignIndex === gopsToAlignWith.length - 1) {
46910              // gop.pts is greater than the last alignment candidate. If no match is found
46911              // by the end of this loop, we still want to append gops that come after this
46912              // point
46913              alignEndIndex = gopIndex;
46914            }
46915
46916            gopIndex--;
46917          }
46918
46919          if (!matchFound && alignEndIndex === null) {
46920            return null;
46921          }
46922
46923          var trimIndex;
46924
46925          if (matchFound) {
46926            trimIndex = gopIndex;
46927          } else {
46928            trimIndex = alignEndIndex;
46929          }
46930
46931          if (trimIndex === 0) {
46932            return gops;
46933          }
46934
46935          var alignedGops = gops.slice(trimIndex);
46936          var metadata = alignedGops.reduce(function (total, gop) {
46937            total.byteLength += gop.byteLength;
46938            total.duration += gop.duration;
46939            total.nalCount += gop.nalCount;
46940            return total;
46941          }, {
46942            byteLength: 0,
46943            duration: 0,
46944            nalCount: 0
46945          });
46946          alignedGops.byteLength = metadata.byteLength;
46947          alignedGops.duration = metadata.duration;
46948          alignedGops.nalCount = metadata.nalCount;
46949          alignedGops.pts = alignedGops[0].pts;
46950          alignedGops.dts = alignedGops[0].dts;
46951          return alignedGops;
46952        };
46953
46954        this.alignGopsWith = function (newGopsToAlignWith) {
46955          gopsToAlignWith = newGopsToAlignWith;
46956        };
46957      };
46958
46959      _VideoSegmentStream.prototype = new stream();
46960      /**
46961       * A Stream that can combine multiple streams (ie. audio & video)
46962       * into a single output segment for MSE. Also supports audio-only
46963       * and video-only streams.
46964       * @param options {object} transmuxer options object
46965       * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
46966       *        in the source; false to adjust the first segment to start at media timeline start.
46967       */
46968
46969      _CoalesceStream = function CoalesceStream(options, metadataStream) {
46970        // Number of Tracks per output segment
46971        // If greater than 1, we combine multiple
46972        // tracks into a single segment
46973        this.numberOfTracks = 0;
46974        this.metadataStream = metadataStream;
46975        options = options || {};
46976
46977        if (typeof options.remux !== 'undefined') {
46978          this.remuxTracks = !!options.remux;
46979        } else {
46980          this.remuxTracks = true;
46981        }
46982
46983        if (typeof options.keepOriginalTimestamps === 'boolean') {
46984          this.keepOriginalTimestamps = options.keepOriginalTimestamps;
46985        }
46986
46987        this.pendingTracks = [];
46988        this.videoTrack = null;
46989        this.pendingBoxes = [];
46990        this.pendingCaptions = [];
46991        this.pendingMetadata = [];
46992        this.pendingBytes = 0;
46993        this.emittedTracks = 0;
46994
46995        _CoalesceStream.prototype.init.call(this); // Take output from multiple
46996
46997
46998        this.push = function (output) {
46999          // buffer incoming captions until the associated video segment
47000          // finishes
47001          if (output.text) {
47002            return this.pendingCaptions.push(output);
47003          } // buffer incoming id3 tags until the final flush
47004
47005
47006          if (output.frames) {
47007            return this.pendingMetadata.push(output);
47008          } // Add this track to the list of pending tracks and store
47009          // important information required for the construction of
47010          // the final segment
47011
47012
47013          this.pendingTracks.push(output.track);
47014          this.pendingBoxes.push(output.boxes);
47015          this.pendingBytes += output.boxes.byteLength;
47016
47017          if (output.track.type === 'video') {
47018            this.videoTrack = output.track;
47019          }
47020
47021          if (output.track.type === 'audio') {
47022            this.audioTrack = output.track;
47023          }
47024        };
47025      };
47026
47027      _CoalesceStream.prototype = new stream();
47028
47029      _CoalesceStream.prototype.flush = function (flushSource) {
47030        var offset = 0,
47031            event = {
47032          captions: [],
47033          captionStreams: {},
47034          metadata: [],
47035          info: {}
47036        },
47037            caption,
47038            id3,
47039            initSegment,
47040            timelineStartPts = 0,
47041            i;
47042
47043        if (this.pendingTracks.length < this.numberOfTracks) {
47044          if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
47045            // Return because we haven't received a flush from a data-generating
47046            // portion of the segment (meaning that we have only recieved meta-data
47047            // or captions.)
47048            return;
47049          } else if (this.remuxTracks) {
47050            // Return until we have enough tracks from the pipeline to remux (if we
47051            // are remuxing audio and video into a single MP4)
47052            return;
47053          } else if (this.pendingTracks.length === 0) {
47054            // In the case where we receive a flush without any data having been
47055            // received we consider it an emitted track for the purposes of coalescing
47056            // `done` events.
47057            // We do this for the case where there is an audio and video track in the
47058            // segment but no audio data. (seen in several playlists with alternate
47059            // audio tracks and no audio present in the main TS segments.)
47060            this.emittedTracks++;
47061
47062            if (this.emittedTracks >= this.numberOfTracks) {
47063              this.trigger('done');
47064              this.emittedTracks = 0;
47065            }
47066
47067            return;
47068          }
47069        }
47070
47071        if (this.videoTrack) {
47072          timelineStartPts = this.videoTrack.timelineStartInfo.pts;
47073          VIDEO_PROPERTIES.forEach(function (prop) {
47074            event.info[prop] = this.videoTrack[prop];
47075          }, this);
47076        } else if (this.audioTrack) {
47077          timelineStartPts = this.audioTrack.timelineStartInfo.pts;
47078          AUDIO_PROPERTIES.forEach(function (prop) {
47079            event.info[prop] = this.audioTrack[prop];
47080          }, this);
47081        }
47082
47083        if (this.pendingTracks.length === 1) {
47084          event.type = this.pendingTracks[0].type;
47085        } else {
47086          event.type = 'combined';
47087        }
47088
47089        this.emittedTracks += this.pendingTracks.length;
47090        initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
47091
47092        event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
47093        // and track definitions
47094
47095        event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
47096
47097        event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
47098
47099        for (i = 0; i < this.pendingBoxes.length; i++) {
47100          event.data.set(this.pendingBoxes[i], offset);
47101          offset += this.pendingBoxes[i].byteLength;
47102        } // Translate caption PTS times into second offsets to match the
47103        // video timeline for the segment, and add track info
47104
47105
47106        for (i = 0; i < this.pendingCaptions.length; i++) {
47107          caption = this.pendingCaptions[i];
47108          caption.startTime = caption.startPts;
47109
47110          if (!this.keepOriginalTimestamps) {
47111            caption.startTime -= timelineStartPts;
47112          }
47113
47114          caption.startTime /= 90e3;
47115          caption.endTime = caption.endPts;
47116
47117          if (!this.keepOriginalTimestamps) {
47118            caption.endTime -= timelineStartPts;
47119          }
47120
47121          caption.endTime /= 90e3;
47122          event.captionStreams[caption.stream] = true;
47123          event.captions.push(caption);
47124        } // Translate ID3 frame PTS times into second offsets to match the
47125        // video timeline for the segment
47126
47127
47128        for (i = 0; i < this.pendingMetadata.length; i++) {
47129          id3 = this.pendingMetadata[i];
47130          id3.cueTime = id3.pts;
47131
47132          if (!this.keepOriginalTimestamps) {
47133            id3.cueTime -= timelineStartPts;
47134          }
47135
47136          id3.cueTime /= 90e3;
47137          event.metadata.push(id3);
47138        } // We add this to every single emitted segment even though we only need
47139        // it for the first
47140
47141
47142        event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
47143
47144        this.pendingTracks.length = 0;
47145        this.videoTrack = null;
47146        this.pendingBoxes.length = 0;
47147        this.pendingCaptions.length = 0;
47148        this.pendingBytes = 0;
47149        this.pendingMetadata.length = 0; // Emit the built segment
47150
47151        this.trigger('data', event); // Only emit `done` if all tracks have been flushed and emitted
47152
47153        if (this.emittedTracks >= this.numberOfTracks) {
47154          this.trigger('done');
47155          this.emittedTracks = 0;
47156        }
47157      };
47158      /**
47159       * A Stream that expects MP2T binary data as input and produces
47160       * corresponding media segments, suitable for use with Media Source
47161       * Extension (MSE) implementations that support the ISO BMFF byte
47162       * stream format, like Chrome.
47163       */
47164
47165
47166      _Transmuxer = function Transmuxer(options) {
47167        var self = this,
47168            hasFlushed = true,
47169            videoTrack,
47170            audioTrack;
47171
47172        _Transmuxer.prototype.init.call(this);
47173
47174        options = options || {};
47175        this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
47176        this.transmuxPipeline_ = {};
47177
47178        this.setupAacPipeline = function () {
47179          var pipeline = {};
47180          this.transmuxPipeline_ = pipeline;
47181          pipeline.type = 'aac';
47182          pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
47183
47184          pipeline.aacStream = new aac();
47185          pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
47186          pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
47187          pipeline.adtsStream = new adts();
47188          pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
47189          pipeline.headOfPipeline = pipeline.aacStream;
47190          pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
47191          pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
47192          pipeline.metadataStream.on('timestamp', function (frame) {
47193            pipeline.aacStream.setTimestamp(frame.timeStamp);
47194          });
47195          pipeline.aacStream.on('data', function (data) {
47196            if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) {
47197              audioTrack = audioTrack || {
47198                timelineStartInfo: {
47199                  baseMediaDecodeTime: self.baseMediaDecodeTime
47200                },
47201                codec: 'adts',
47202                type: 'audio'
47203              }; // hook up the audio segment stream to the first track with aac data
47204
47205              pipeline.coalesceStream.numberOfTracks++;
47206              pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
47207
47208              pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
47209            }
47210          }); // Re-emit any data coming from the coalesce stream to the outside world
47211
47212          pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
47213
47214          pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
47215        };
47216
47217        this.setupTsPipeline = function () {
47218          var pipeline = {};
47219          this.transmuxPipeline_ = pipeline;
47220          pipeline.type = 'ts';
47221          pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline
47222
47223          pipeline.packetStream = new m2ts_1.TransportPacketStream();
47224          pipeline.parseStream = new m2ts_1.TransportParseStream();
47225          pipeline.elementaryStream = new m2ts_1.ElementaryStream();
47226          pipeline.videoTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('video');
47227          pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
47228          pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
47229          pipeline.adtsStream = new adts();
47230          pipeline.h264Stream = new H264Stream();
47231          pipeline.captionStream = new m2ts_1.CaptionStream();
47232          pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
47233          pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
47234
47235          pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream); // !!THIS ORDER IS IMPORTANT!!
47236          // demux the streams
47237
47238          pipeline.elementaryStream.pipe(pipeline.videoTimestampRolloverStream).pipe(pipeline.h264Stream);
47239          pipeline.elementaryStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
47240          pipeline.elementaryStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
47241
47242          pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
47243          pipeline.elementaryStream.on('data', function (data) {
47244            var i;
47245
47246            if (data.type === 'metadata') {
47247              i = data.tracks.length; // scan the tracks listed in the metadata
47248
47249              while (i--) {
47250                if (!videoTrack && data.tracks[i].type === 'video') {
47251                  videoTrack = data.tracks[i];
47252                  videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
47253                } else if (!audioTrack && data.tracks[i].type === 'audio') {
47254                  audioTrack = data.tracks[i];
47255                  audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
47256                }
47257              } // hook up the video segment stream to the first track with h264 data
47258
47259
47260              if (videoTrack && !pipeline.videoSegmentStream) {
47261                pipeline.coalesceStream.numberOfTracks++;
47262                pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options);
47263                pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
47264                  // When video emits timelineStartInfo data after a flush, we forward that
47265                  // info to the AudioSegmentStream, if it exists, because video timeline
47266                  // data takes precedence.
47267                  if (audioTrack) {
47268                    audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
47269                    // very earliest DTS we have seen in video because Chrome will
47270                    // interpret any video track with a baseMediaDecodeTime that is
47271                    // non-zero as a gap.
47272
47273                    pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts);
47274                  }
47275                });
47276                pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
47277                pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
47278                pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
47279                  if (audioTrack) {
47280                    pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
47281                  }
47282                }); // Set up the final part of the video pipeline
47283
47284                pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
47285              }
47286
47287              if (audioTrack && !pipeline.audioSegmentStream) {
47288                // hook up the audio segment stream to the first track with aac data
47289                pipeline.coalesceStream.numberOfTracks++;
47290                pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); // Set up the final part of the audio pipeline
47291
47292                pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
47293              }
47294            }
47295          }); // Re-emit any data coming from the coalesce stream to the outside world
47296
47297          pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
47298
47299          pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
47300        }; // hook up the segment streams once track metadata is delivered
47301
47302
47303        this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
47304          var pipeline = this.transmuxPipeline_;
47305
47306          if (!options.keepOriginalTimestamps) {
47307            this.baseMediaDecodeTime = baseMediaDecodeTime;
47308          }
47309
47310          if (audioTrack) {
47311            audioTrack.timelineStartInfo.dts = undefined;
47312            audioTrack.timelineStartInfo.pts = undefined;
47313            trackDecodeInfo.clearDtsInfo(audioTrack);
47314
47315            if (!options.keepOriginalTimestamps) {
47316              audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
47317            }
47318
47319            if (pipeline.audioTimestampRolloverStream) {
47320              pipeline.audioTimestampRolloverStream.discontinuity();
47321            }
47322          }
47323
47324          if (videoTrack) {
47325            if (pipeline.videoSegmentStream) {
47326              pipeline.videoSegmentStream.gopCache_ = [];
47327              pipeline.videoTimestampRolloverStream.discontinuity();
47328            }
47329
47330            videoTrack.timelineStartInfo.dts = undefined;
47331            videoTrack.timelineStartInfo.pts = undefined;
47332            trackDecodeInfo.clearDtsInfo(videoTrack);
47333            pipeline.captionStream.reset();
47334
47335            if (!options.keepOriginalTimestamps) {
47336              videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
47337            }
47338          }
47339
47340          if (pipeline.timedMetadataTimestampRolloverStream) {
47341            pipeline.timedMetadataTimestampRolloverStream.discontinuity();
47342          }
47343        };
47344
47345        this.setAudioAppendStart = function (timestamp) {
47346          if (audioTrack) {
47347            this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
47348          }
47349        };
47350
47351        this.alignGopsWith = function (gopsToAlignWith) {
47352          if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
47353            this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
47354          }
47355        }; // feed incoming data to the front of the parsing pipeline
47356
47357
47358        this.push = function (data) {
47359          if (hasFlushed) {
47360            var isAac = isLikelyAacData$1(data);
47361
47362            if (isAac && this.transmuxPipeline_.type !== 'aac') {
47363              this.setupAacPipeline();
47364            } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
47365              this.setupTsPipeline();
47366            }
47367
47368            hasFlushed = false;
47369          }
47370
47371          this.transmuxPipeline_.headOfPipeline.push(data);
47372        }; // flush any buffered data
47373
47374
47375        this.flush = function () {
47376          hasFlushed = true; // Start at the top of the pipeline and flush all pending work
47377
47378          this.transmuxPipeline_.headOfPipeline.flush();
47379        }; // Caption data has to be reset when seeking outside buffered range
47380
47381
47382        this.resetCaptions = function () {
47383          if (this.transmuxPipeline_.captionStream) {
47384            this.transmuxPipeline_.captionStream.reset();
47385          }
47386        };
47387      };
47388
47389      _Transmuxer.prototype = new stream();
47390      var transmuxer = {
47391        Transmuxer: _Transmuxer,
47392        VideoSegmentStream: _VideoSegmentStream,
47393        AudioSegmentStream: _AudioSegmentStream,
47394        AUDIO_PROPERTIES: AUDIO_PROPERTIES,
47395        VIDEO_PROPERTIES: VIDEO_PROPERTIES,
47396        // exported for testing
47397        generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo
47398      };
47399
47400      var inspectMp4,
47401          _textifyMp,
47402          parseType$2 = probe$$1.parseType,
47403          parseMp4Date = function parseMp4Date(seconds) {
47404        return new Date(seconds * 1000 - 2082844800000);
47405      },
47406          parseSampleFlags = function parseSampleFlags(flags) {
47407        return {
47408          isLeading: (flags[0] & 0x0c) >>> 2,
47409          dependsOn: flags[0] & 0x03,
47410          isDependedOn: (flags[1] & 0xc0) >>> 6,
47411          hasRedundancy: (flags[1] & 0x30) >>> 4,
47412          paddingValue: (flags[1] & 0x0e) >>> 1,
47413          isNonSyncSample: flags[1] & 0x01,
47414          degradationPriority: flags[2] << 8 | flags[3]
47415        };
47416      },
47417          nalParse = function nalParse(avcStream) {
47418        var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
47419            result = [],
47420            i,
47421            length;
47422
47423        for (i = 0; i + 4 < avcStream.length; i += length) {
47424          length = avcView.getUint32(i);
47425          i += 4; // bail if this doesn't appear to be an H264 stream
47426
47427          if (length <= 0) {
47428            result.push('<span style=\'color:red;\'>MALFORMED DATA</span>');
47429            continue;
47430          }
47431
47432          switch (avcStream[i] & 0x1F) {
47433            case 0x01:
47434              result.push('slice_layer_without_partitioning_rbsp');
47435              break;
47436
47437            case 0x05:
47438              result.push('slice_layer_without_partitioning_rbsp_idr');
47439              break;
47440
47441            case 0x06:
47442              result.push('sei_rbsp');
47443              break;
47444
47445            case 0x07:
47446              result.push('seq_parameter_set_rbsp');
47447              break;
47448
47449            case 0x08:
47450              result.push('pic_parameter_set_rbsp');
47451              break;
47452
47453            case 0x09:
47454              result.push('access_unit_delimiter_rbsp');
47455              break;
47456
47457            default:
47458              result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F);
47459              break;
47460          }
47461        }
47462
47463        return result;
47464      },
47465          // registry of handlers for individual mp4 box types
47466      parse$$1 = {
47467        // codingname, not a first-class box type. stsd entries share the
47468        // same format as real boxes so the parsing infrastructure can be
47469        // shared
47470        avc1: function avc1(data) {
47471          var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
47472          return {
47473            dataReferenceIndex: view.getUint16(6),
47474            width: view.getUint16(24),
47475            height: view.getUint16(26),
47476            horizresolution: view.getUint16(28) + view.getUint16(30) / 16,
47477            vertresolution: view.getUint16(32) + view.getUint16(34) / 16,
47478            frameCount: view.getUint16(40),
47479            depth: view.getUint16(74),
47480            config: inspectMp4(data.subarray(78, data.byteLength))
47481          };
47482        },
47483        avcC: function avcC(data) {
47484          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
47485              result = {
47486            configurationVersion: data[0],
47487            avcProfileIndication: data[1],
47488            profileCompatibility: data[2],
47489            avcLevelIndication: data[3],
47490            lengthSizeMinusOne: data[4] & 0x03,
47491            sps: [],
47492            pps: []
47493          },
47494              numOfSequenceParameterSets = data[5] & 0x1f,
47495              numOfPictureParameterSets,
47496              nalSize,
47497              offset,
47498              i; // iterate past any SPSs
47499
47500          offset = 6;
47501
47502          for (i = 0; i < numOfSequenceParameterSets; i++) {
47503            nalSize = view.getUint16(offset);
47504            offset += 2;
47505            result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
47506            offset += nalSize;
47507          } // iterate past any PPSs
47508
47509
47510          numOfPictureParameterSets = data[offset];
47511          offset++;
47512
47513          for (i = 0; i < numOfPictureParameterSets; i++) {
47514            nalSize = view.getUint16(offset);
47515            offset += 2;
47516            result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
47517            offset += nalSize;
47518          }
47519
47520          return result;
47521        },
47522        btrt: function btrt(data) {
47523          var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
47524          return {
47525            bufferSizeDB: view.getUint32(0),
47526            maxBitrate: view.getUint32(4),
47527            avgBitrate: view.getUint32(8)
47528          };
47529        },
47530        esds: function esds(data) {
47531          return {
47532            version: data[0],
47533            flags: new Uint8Array(data.subarray(1, 4)),
47534            esId: data[6] << 8 | data[7],
47535            streamPriority: data[8] & 0x1f,
47536            decoderConfig: {
47537              objectProfileIndication: data[11],
47538              streamType: data[12] >>> 2 & 0x3f,
47539              bufferSize: data[13] << 16 | data[14] << 8 | data[15],
47540              maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
47541              avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23],
47542              decoderConfigDescriptor: {
47543                tag: data[24],
47544                length: data[25],
47545                audioObjectType: data[26] >>> 3 & 0x1f,
47546                samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01,
47547                channelConfiguration: data[27] >>> 3 & 0x0f
47548              }
47549            }
47550          };
47551        },
47552        ftyp: function ftyp(data) {
47553          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
47554              result = {
47555            majorBrand: parseType$2(data.subarray(0, 4)),
47556            minorVersion: view.getUint32(4),
47557            compatibleBrands: []
47558          },
47559              i = 8;
47560
47561          while (i < data.byteLength) {
47562            result.compatibleBrands.push(parseType$2(data.subarray(i, i + 4)));
47563            i += 4;
47564          }
47565
47566          return result;
47567        },
47568        dinf: function dinf(data) {
47569          return {
47570            boxes: inspectMp4(data)
47571          };
47572        },
47573        dref: function dref(data) {
47574          return {
47575            version: data[0],
47576            flags: new Uint8Array(data.subarray(1, 4)),
47577            dataReferences: inspectMp4(data.subarray(8))
47578          };
47579        },
47580        hdlr: function hdlr(data) {
47581          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
47582              result = {
47583            version: view.getUint8(0),
47584            flags: new Uint8Array(data.subarray(1, 4)),
47585            handlerType: parseType$2(data.subarray(8, 12)),
47586            name: ''
47587          },
47588              i = 8; // parse out the name field
47589
47590          for (i = 24; i < data.byteLength; i++) {
47591            if (data[i] === 0x00) {
47592              // the name field is null-terminated
47593              i++;
47594              break;
47595            }
47596
47597            result.name += String.fromCharCode(data[i]);
47598          } // decode UTF-8 to javascript's internal representation
47599          // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
47600
47601
47602          result.name = decodeURIComponent(escape(result.name));
47603          return result;
47604        },
47605        mdat: function mdat(data) {
47606          return {
47607            byteLength: data.byteLength,
47608            nals: nalParse(data)
47609          };
47610        },
47611        mdhd: function mdhd(data) {
47612          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
47613              i = 4,
47614              language,
47615              result = {
47616            version: view.getUint8(0),
47617            flags: new Uint8Array(data.subarray(1, 4)),
47618            language: ''
47619          };
47620
47621          if (result.version === 1) {
47622            i += 4;
47623            result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
47624
47625            i += 8;
47626            result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
47627
47628            i += 4;
47629            result.timescale = view.getUint32(i);
47630            i += 8;
47631            result.duration = view.getUint32(i); // truncating top 4 bytes
47632          } else {
47633            result.creationTime = parseMp4Date(view.getUint32(i));
47634            i += 4;
47635            result.modificationTime = parseMp4Date(view.getUint32(i));
47636            i += 4;
47637            result.timescale = view.getUint32(i);
47638            i += 4;
47639            result.duration = view.getUint32(i);
47640          }
47641
47642          i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
47643          // each field is the packed difference between its ASCII value and 0x60
47644
47645          language = view.getUint16(i);
47646          result.language += String.fromCharCode((language >> 10) + 0x60);
47647          result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60);
47648          result.language += String.fromCharCode((language & 0x1f) + 0x60);
47649          return result;
47650        },
47651        mdia: function mdia(data) {
47652          return {
47653            boxes: inspectMp4(data)
47654          };
47655        },
47656        mfhd: function mfhd(data) {
47657          return {
47658            version: data[0],
47659            flags: new Uint8Array(data.subarray(1, 4)),
47660            sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
47661          };
47662        },
47663        minf: function minf(data) {
47664          return {
47665            boxes: inspectMp4(data)
47666          };
47667        },
47668        // codingname, not a first-class box type. stsd entries share the
47669        // same format as real boxes so the parsing infrastructure can be
47670        // shared
47671        mp4a: function mp4a(data) {
47672          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
47673              result = {
47674            // 6 bytes reserved
47675            dataReferenceIndex: view.getUint16(6),
47676            // 4 + 4 bytes reserved
47677            channelcount: view.getUint16(16),
47678            samplesize: view.getUint16(18),
47679            // 2 bytes pre_defined
47680            // 2 bytes reserved
47681            samplerate: view.getUint16(24) + view.getUint16(26) / 65536
47682          }; // if there are more bytes to process, assume this is an ISO/IEC
47683          // 14496-14 MP4AudioSampleEntry and parse the ESDBox
47684
47685          if (data.byteLength > 28) {
47686            result.streamDescriptor = inspectMp4(data.subarray(28))[0];
47687          }
47688
47689          return result;
47690        },
47691        moof: function moof(data) {
47692          return {
47693            boxes: inspectMp4(data)
47694          };
47695        },
47696        moov: function moov(data) {
47697          return {
47698            boxes: inspectMp4(data)
47699          };
47700        },
47701        mvex: function mvex(data) {
47702          return {
47703            boxes: inspectMp4(data)
47704          };
47705        },
47706        mvhd: function mvhd(data) {
47707          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
47708              i = 4,
47709              result = {
47710            version: view.getUint8(0),
47711            flags: new Uint8Array(data.subarray(1, 4))
47712          };
47713
47714          if (result.version === 1) {
47715            i += 4;
47716            result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
47717
47718            i += 8;
47719            result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
47720
47721            i += 4;
47722            result.timescale = view.getUint32(i);
47723            i += 8;
47724            result.duration = view.getUint32(i); // truncating top 4 bytes
47725          } else {
47726            result.creationTime = parseMp4Date(view.getUint32(i));
47727            i += 4;
47728            result.modificationTime = parseMp4Date(view.getUint32(i));
47729            i += 4;
47730            result.timescale = view.getUint32(i);
47731            i += 4;
47732            result.duration = view.getUint32(i);
47733          }
47734
47735          i += 4; // convert fixed-point, base 16 back to a number
47736
47737          result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16;
47738          i += 4;
47739          result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
47740          i += 2;
47741          i += 2;
47742          i += 2 * 4;
47743          result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
47744          i += 9 * 4;
47745          i += 6 * 4;
47746          result.nextTrackId = view.getUint32(i);
47747          return result;
47748        },
47749        pdin: function pdin(data) {
47750          var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
47751          return {
47752            version: view.getUint8(0),
47753            flags: new Uint8Array(data.subarray(1, 4)),
47754            rate: view.getUint32(4),
47755            initialDelay: view.getUint32(8)
47756          };
47757        },
47758        sdtp: function sdtp(data) {
47759          var result = {
47760            version: data[0],
47761            flags: new Uint8Array(data.subarray(1, 4)),
47762            samples: []
47763          },
47764              i;
47765
47766          for (i = 4; i < data.byteLength; i++) {
47767            result.samples.push({
47768              dependsOn: (data[i] & 0x30) >> 4,
47769              isDependedOn: (data[i] & 0x0c) >> 2,
47770              hasRedundancy: data[i] & 0x03
47771            });
47772          }
47773
47774          return result;
47775        },
47776        sidx: function sidx(data) {
47777          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
47778              result = {
47779            version: data[0],
47780            flags: new Uint8Array(data.subarray(1, 4)),
47781            references: [],
47782            referenceId: view.getUint32(4),
47783            timescale: view.getUint32(8),
47784            earliestPresentationTime: view.getUint32(12),
47785            firstOffset: view.getUint32(16)
47786          },
47787              referenceCount = view.getUint16(22),
47788              i;
47789
47790          for (i = 24; referenceCount; i += 12, referenceCount--) {
47791            result.references.push({
47792              referenceType: (data[i] & 0x80) >>> 7,
47793              referencedSize: view.getUint32(i) & 0x7FFFFFFF,
47794              subsegmentDuration: view.getUint32(i + 4),
47795              startsWithSap: !!(data[i + 8] & 0x80),
47796              sapType: (data[i + 8] & 0x70) >>> 4,
47797              sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
47798            });
47799          }
47800
47801          return result;
47802        },
47803        smhd: function smhd(data) {
47804          return {
47805            version: data[0],
47806            flags: new Uint8Array(data.subarray(1, 4)),
47807            balance: data[4] + data[5] / 256
47808          };
47809        },
47810        stbl: function stbl(data) {
47811          return {
47812            boxes: inspectMp4(data)
47813          };
47814        },
47815        stco: function stco(data) {
47816          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
47817              result = {
47818            version: data[0],
47819            flags: new Uint8Array(data.subarray(1, 4)),
47820            chunkOffsets: []
47821          },
47822              entryCount = view.getUint32(4),
47823              i;
47824
47825          for (i = 8; entryCount; i += 4, entryCount--) {
47826            result.chunkOffsets.push(view.getUint32(i));
47827          }
47828
47829          return result;
47830        },
47831        stsc: function stsc(data) {
47832          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
47833              entryCount = view.getUint32(4),
47834              result = {
47835            version: data[0],
47836            flags: new Uint8Array(data.subarray(1, 4)),
47837            sampleToChunks: []
47838          },
47839              i;
47840
47841          for (i = 8; entryCount; i += 12, entryCount--) {
47842            result.sampleToChunks.push({
47843              firstChunk: view.getUint32(i),
47844              samplesPerChunk: view.getUint32(i + 4),
47845              sampleDescriptionIndex: view.getUint32(i + 8)
47846            });
47847          }
47848
47849          return result;
47850        },
47851        stsd: function stsd(data) {
47852          return {
47853            version: data[0],
47854            flags: new Uint8Array(data.subarray(1, 4)),
47855            sampleDescriptions: inspectMp4(data.subarray(8))
47856          };
47857        },
47858        stsz: function stsz(data) {
47859          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
47860              result = {
47861            version: data[0],
47862            flags: new Uint8Array(data.subarray(1, 4)),
47863            sampleSize: view.getUint32(4),
47864            entries: []
47865          },
47866              i;
47867
47868          for (i = 12; i < data.byteLength; i += 4) {
47869            result.entries.push(view.getUint32(i));
47870          }
47871
47872          return result;
47873        },
47874        stts: function stts(data) {
47875          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
47876              result = {
47877            version: data[0],
47878            flags: new Uint8Array(data.subarray(1, 4)),
47879            timeToSamples: []
47880          },
47881              entryCount = view.getUint32(4),
47882              i;
47883
47884          for (i = 8; entryCount; i += 8, entryCount--) {
47885            result.timeToSamples.push({
47886              sampleCount: view.getUint32(i),
47887              sampleDelta: view.getUint32(i + 4)
47888            });
47889          }
47890
47891          return result;
47892        },
47893        styp: function styp(data) {
47894          return parse$$1.ftyp(data);
47895        },
47896        tfdt: function tfdt(data) {
47897          var result = {
47898            version: data[0],
47899            flags: new Uint8Array(data.subarray(1, 4)),
47900            baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
47901          };
47902
47903          if (result.version === 1) {
47904            result.baseMediaDecodeTime *= Math.pow(2, 32);
47905            result.baseMediaDecodeTime += data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11];
47906          }
47907
47908          return result;
47909        },
47910        tfhd: function tfhd(data) {
47911          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
47912              result = {
47913            version: data[0],
47914            flags: new Uint8Array(data.subarray(1, 4)),
47915            trackId: view.getUint32(4)
47916          },
47917              baseDataOffsetPresent = result.flags[2] & 0x01,
47918              sampleDescriptionIndexPresent = result.flags[2] & 0x02,
47919              defaultSampleDurationPresent = result.flags[2] & 0x08,
47920              defaultSampleSizePresent = result.flags[2] & 0x10,
47921              defaultSampleFlagsPresent = result.flags[2] & 0x20,
47922              durationIsEmpty = result.flags[0] & 0x010000,
47923              defaultBaseIsMoof = result.flags[0] & 0x020000,
47924              i;
47925          i = 8;
47926
47927          if (baseDataOffsetPresent) {
47928            i += 4; // truncate top 4 bytes
47929            // FIXME: should we read the full 64 bits?
47930
47931            result.baseDataOffset = view.getUint32(12);
47932            i += 4;
47933          }
47934
47935          if (sampleDescriptionIndexPresent) {
47936            result.sampleDescriptionIndex = view.getUint32(i);
47937            i += 4;
47938          }
47939
47940          if (defaultSampleDurationPresent) {
47941            result.defaultSampleDuration = view.getUint32(i);
47942            i += 4;
47943          }
47944
47945          if (defaultSampleSizePresent) {
47946            result.defaultSampleSize = view.getUint32(i);
47947            i += 4;
47948          }
47949
47950          if (defaultSampleFlagsPresent) {
47951            result.defaultSampleFlags = view.getUint32(i);
47952          }
47953
47954          if (durationIsEmpty) {
47955            result.durationIsEmpty = true;
47956          }
47957
47958          if (!baseDataOffsetPresent && defaultBaseIsMoof) {
47959            result.baseDataOffsetIsMoof = true;
47960          }
47961
47962          return result;
47963        },
47964        tkhd: function tkhd(data) {
47965          var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
47966              i = 4,
47967              result = {
47968            version: view.getUint8(0),
47969            flags: new Uint8Array(data.subarray(1, 4))
47970          };
47971
47972          if (result.version === 1) {
47973            i += 4;
47974            result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
47975
47976            i += 8;
47977            result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
47978
47979            i += 4;
47980            result.trackId = view.getUint32(i);
47981            i += 4;
47982            i += 8;
47983            result.duration = view.getUint32(i); // truncating top 4 bytes
47984          } else {
47985            result.creationTime = parseMp4Date(view.getUint32(i));
47986            i += 4;
47987            result.modificationTime = parseMp4Date(view.getUint32(i));
47988            i += 4;
47989            result.trackId = view.getUint32(i);
47990            i += 4;
47991            i += 4;
47992            result.duration = view.getUint32(i);
47993          }
47994
47995          i += 4;
47996          i += 2 * 4;
47997          result.layer = view.getUint16(i);
47998          i += 2;
47999          result.alternateGroup = view.getUint16(i);
48000          i += 2; // convert fixed-point, base 16 back to a number
48001
48002          result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
48003          i += 2;
48004          i += 2;
48005          result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
48006          i += 9 * 4;
48007          result.width = view.getUint16(i) + view.getUint16(i + 2) / 16;
48008          i += 4;
48009          result.height = view.getUint16(i) + view.getUint16(i + 2) / 16;
48010          return result;
48011        },
48012        traf: function traf(data) {
48013          return {
48014            boxes: inspectMp4(data)
48015          };
48016        },
48017        trak: function trak(data) {
48018          return {
48019            boxes: inspectMp4(data)
48020          };
48021        },
48022        trex: function trex(data) {
48023          var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
48024          return {
48025            version: data[0],
48026            flags: new Uint8Array(data.subarray(1, 4)),
48027            trackId: view.getUint32(4),
48028            defaultSampleDescriptionIndex: view.getUint32(8),
48029            defaultSampleDuration: view.getUint32(12),
48030            defaultSampleSize: view.getUint32(16),
48031            sampleDependsOn: data[20] & 0x03,
48032            sampleIsDependedOn: (data[21] & 0xc0) >> 6,
48033            sampleHasRedundancy: (data[21] & 0x30) >> 4,
48034            samplePaddingValue: (data[21] & 0x0e) >> 1,
48035            sampleIsDifferenceSample: !!(data[21] & 0x01),
48036            sampleDegradationPriority: view.getUint16(22)
48037          };
48038        },
48039        trun: function trun(data) {
48040          var result = {
48041            version: data[0],
48042            flags: new Uint8Array(data.subarray(1, 4)),
48043            samples: []
48044          },
48045              view = new DataView(data.buffer, data.byteOffset, data.byteLength),
48046              // Flag interpretation
48047          dataOffsetPresent = result.flags[2] & 0x01,
48048              // compare with 2nd byte of 0x1
48049          firstSampleFlagsPresent = result.flags[2] & 0x04,
48050              // compare with 2nd byte of 0x4
48051          sampleDurationPresent = result.flags[1] & 0x01,
48052              // compare with 2nd byte of 0x100
48053          sampleSizePresent = result.flags[1] & 0x02,
48054              // compare with 2nd byte of 0x200
48055          sampleFlagsPresent = result.flags[1] & 0x04,
48056              // compare with 2nd byte of 0x400
48057          sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
48058              // compare with 2nd byte of 0x800
48059          sampleCount = view.getUint32(4),
48060              offset = 8,
48061              sample;
48062
48063          if (dataOffsetPresent) {
48064            // 32 bit signed integer
48065            result.dataOffset = view.getInt32(offset);
48066            offset += 4;
48067          } // Overrides the flags for the first sample only. The order of
48068          // optional values will be: duration, size, compositionTimeOffset
48069
48070
48071          if (firstSampleFlagsPresent && sampleCount) {
48072            sample = {
48073              flags: parseSampleFlags(data.subarray(offset, offset + 4))
48074            };
48075            offset += 4;
48076
48077            if (sampleDurationPresent) {
48078              sample.duration = view.getUint32(offset);
48079              offset += 4;
48080            }
48081
48082            if (sampleSizePresent) {
48083              sample.size = view.getUint32(offset);
48084              offset += 4;
48085            }
48086
48087            if (sampleCompositionTimeOffsetPresent) {
48088              // Note: this should be a signed int if version is 1
48089              sample.compositionTimeOffset = view.getUint32(offset);
48090              offset += 4;
48091            }
48092
48093            result.samples.push(sample);
48094            sampleCount--;
48095          }
48096
48097          while (sampleCount--) {
48098            sample = {};
48099
48100            if (sampleDurationPresent) {
48101              sample.duration = view.getUint32(offset);
48102              offset += 4;
48103            }
48104
48105            if (sampleSizePresent) {
48106              sample.size = view.getUint32(offset);
48107              offset += 4;
48108            }
48109
48110            if (sampleFlagsPresent) {
48111              sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
48112              offset += 4;
48113            }
48114
48115            if (sampleCompositionTimeOffsetPresent) {
48116              // Note: this should be a signed int if version is 1
48117              sample.compositionTimeOffset = view.getUint32(offset);
48118              offset += 4;
48119            }
48120
48121            result.samples.push(sample);
48122          }
48123
48124          return result;
48125        },
48126        'url ': function url(data) {
48127          return {
48128            version: data[0],
48129            flags: new Uint8Array(data.subarray(1, 4))
48130          };
48131        },
48132        vmhd: function vmhd(data) {
48133          var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
48134          return {
48135            version: data[0],
48136            flags: new Uint8Array(data.subarray(1, 4)),
48137            graphicsmode: view.getUint16(4),
48138            opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)])
48139          };
48140        }
48141      };
48142      /**
48143       * Return a javascript array of box objects parsed from an ISO base
48144       * media file.
48145       * @param data {Uint8Array} the binary data of the media to be inspected
48146       * @return {array} a javascript array of potentially nested box objects
48147       */
48148
48149
48150      inspectMp4 = function inspectMp4(data) {
48151        var i = 0,
48152            result = [],
48153            view,
48154            size,
48155            type,
48156            end,
48157            box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API
48158
48159        var ab = new ArrayBuffer(data.length);
48160        var v = new Uint8Array(ab);
48161
48162        for (var z = 0; z < data.length; ++z) {
48163          v[z] = data[z];
48164        }
48165
48166        view = new DataView(ab);
48167
48168        while (i < data.byteLength) {
48169          // parse box data
48170          size = view.getUint32(i);
48171          type = parseType$2(data.subarray(i + 4, i + 8));
48172          end = size > 1 ? i + size : data.byteLength;
vendor: 4,410 bytes, lines 48172-48290
48172 // parse type-specific data
48173
48174          box = (parse$$1[type] || function (data) {
48175            return {
48176              data: data
48177            };
48178          })(data.subarray(i + 8, end));
48179
48180          box.size = size;
48181          box.type = type; // store this box and move to the next
48182
48183          result.push(box);
48184          i = end;
48185        }
48186
48187        return result;
48188      };
48189      /**
48190       * Returns a textual representation of the javascript represtentation
48191       * of an MP4 file. You can use it as an alternative to
48192       * JSON.stringify() to compare inspected MP4s.
48193       * @param inspectedMp4 {array} the parsed array of boxes in an MP4
48194       * file
48195       * @param depth {number} (optional) the number of ancestor boxes of
48196       * the elements of inspectedMp4. Assumed to be zero if unspecified.
48197       * @return {string} a text representation of the parsed MP4
48198       */
48199
48200
48201      _textifyMp = function textifyMp4(inspectedMp4, depth) {
48202        var indent;
48203        depth = depth || 0;
48204        indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes
48205
48206        return inspectedMp4.map(function (box, index) {
48207          // list the box type first at the current indentation level
48208          return indent + box.type + '\n' + // the type is already included and handle child boxes separately
48209          Object.keys(box).filter(function (key) {
48210            return key !== 'type' && key !== 'boxes'; // output all the box properties
48211          }).map(function (key) {
48212            var prefix = indent + '  ' + key + ': ',
48213                value = box[key]; // print out raw bytes as hexademical
48214
48215            if (value instanceof Uint8Array || value instanceof Uint32Array) {
48216              var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (byte) {
48217                return ' ' + ('00' + byte.toString(16)).slice(-2);
48218              }).join('').match(/.{1,24}/g);
48219
48220              if (!bytes) {
48221                return prefix + '<>';
48222              }
48223
48224              if (bytes.length === 1) {
48225                return prefix + '<' + bytes.join('').slice(1) + '>';
48226              }
48227
48228              return prefix + '<\n' + bytes.map(function (line) {
48229                return indent + '  ' + line;
48230              }).join('\n') + '\n' + indent + '  >';
48231            } // stringify generic objects
48232
48233
48234            return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) {
48235              if (index === 0) {
48236                return line;
48237              }
48238
48239              return indent + '  ' + line;
48240            }).join('\n');
48241          }).join('\n') + ( // recursively textify the child boxes
48242          box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : '');
48243        }).join('\n');
48244      };
48245
48246      var mp4Inspector = {
48247        inspect: inspectMp4,
48248        textify: _textifyMp,
48249        parseTfdt: parse$$1.tfdt,
48250        parseHdlr: parse$$1.hdlr,
48251        parseTfhd: parse$$1.tfhd,
48252        parseTrun: parse$$1.trun,
48253        parseSidx: parse$$1.sidx
48254      };
48255      var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes;
48256      var CaptionStream$1 = captionStream.CaptionStream;
48257      /**
48258        * Maps an offset in the mdat to a sample based on the the size of the samples.
48259        * Assumes that `parseSamples` has been called first.
48260        *
48261        * @param {Number} offset - The offset into the mdat
48262        * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
48263        * @return {?Object} The matching sample, or null if no match was found.
48264        *
48265        * @see ISO-BMFF-12/2015, Section 8.8.8
48266       **/
48267
48268      var mapToSample = function mapToSample(offset, samples) {
48269        var approximateOffset = offset;
48270
48271        for (var i = 0; i < samples.length; i++) {
48272          var sample = samples[i];
48273
48274          if (approximateOffset < sample.size) {
48275            return sample;
48276          }
48277
48278          approximateOffset -= sample.size;
48279        }
48280
48281        return null;
48282      };
48283      /**
48284        * Finds SEI nal units contained in a Media Data Box.
48285        * Assumes that `parseSamples` has been called first.
48286        *
48287        * @param {Uint8Array} avcStream - The bytes of the mdat
48288        * @param {Object[]} samples - The samples parsed out by `parseSamples`
48289        * @param {Number} trackId - The trackId of this video track
48290        * @return {Object[]}
vendor: 44,069 bytes, lines 48290-49619
48290 seiNals - the parsed SEI NALUs found.
48291        *   The contents of the seiNal should match what is expected by
48292        *   CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
48293        *
48294        * @see ISO-BMFF-12/2015, Section 8.1.1
48295        * @see Rec. ITU-T H.264, 7.3.2.3.1
48296       **/
48297
48298
48299      var findSeiNals = function findSeiNals(avcStream, samples, trackId) {
48300        var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
48301            result = [],
48302            seiNal,
48303            i,
48304            length,
48305            lastMatchedSample;
48306
48307        for (i = 0; i + 4 < avcStream.length; i += length) {
48308          length = avcView.getUint32(i);
48309          i += 4; // Bail if this doesn't appear to be an H264 stream
48310
48311          if (length <= 0) {
48312            continue;
48313          }
48314
48315          switch (avcStream[i] & 0x1F) {
48316            case 0x06:
48317              var data = avcStream.subarray(i + 1, i + 1 + length);
48318              var matchingSample = mapToSample(i, samples);
48319              seiNal = {
48320                nalUnitType: 'sei_rbsp',
48321                size: length,
48322                data: data,
48323                escapedRBSP: discardEmulationPreventionBytes$1(data),
48324                trackId: trackId
48325              };
48326
48327              if (matchingSample) {
48328                seiNal.pts = matchingSample.pts;
48329                seiNal.dts = matchingSample.dts;
48330                lastMatchedSample = matchingSample;
48331              } else {
48332                // If a matching sample cannot be found, use the last
48333                // sample's values as they should be as close as possible
48334                seiNal.pts = lastMatchedSample.pts;
48335                seiNal.dts = lastMatchedSample.dts;
48336              }
48337
48338              result.push(seiNal);
48339              break;
48340
48341            default:
48342              break;
48343          }
48344        }
48345
48346        return result;
48347      };
48348      /**
48349        * Parses sample information out of Track Run Boxes and calculates
48350        * the absolute presentation and decode timestamps of each sample.
48351        *
48352        * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
48353        * @param {Number} baseMediaDecodeTime - base media decode time from tfdt
48354            @see ISO-BMFF-12/2015, Section 8.8.12
48355        * @param {Object} tfhd - The parsed Track Fragment Header
48356        *   @see inspect.parseTfhd
48357        * @return {Object[]} the parsed samples
48358        *
48359        * @see ISO-BMFF-12/2015, Section 8.8.8
48360       **/
48361
48362
48363      var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) {
48364        var currentDts = baseMediaDecodeTime;
48365        var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
48366        var defaultSampleSize = tfhd.defaultSampleSize || 0;
48367        var trackId = tfhd.trackId;
48368        var allSamples = [];
48369        truns.forEach(function (trun) {
48370          // Note: We currently do not parse the sample table as well
48371          // as the trun. It's possible some sources will require this.
48372          // moov > trak > mdia > minf > stbl
48373          var trackRun = mp4Inspector.parseTrun(trun);
48374          var samples = trackRun.samples;
48375          samples.forEach(function (sample) {
48376            if (sample.duration === undefined) {
48377              sample.duration = defaultSampleDuration;
48378            }
48379
48380            if (sample.size === undefined) {
48381              sample.size = defaultSampleSize;
48382            }
48383
48384            sample.trackId = trackId;
48385            sample.dts = currentDts;
48386
48387            if (sample.compositionTimeOffset === undefined) {
48388              sample.compositionTimeOffset = 0;
48389            }
48390
48391            sample.pts = currentDts + sample.compositionTimeOffset;
48392            currentDts += sample.duration;
48393          });
48394          allSamples = allSamples.concat(samples);
48395        });
48396        return allSamples;
48397      };
48398      /**
48399        * Parses out caption nals from an FMP4 segment's video tracks.
48400        *
48401        * @param {Uint8Array} segment - The bytes of a single segment
48402        * @param {Number} videoTrackId - The trackId of a video track in the segment
48403        * @return {Object.<Number, Object[]>} A mapping of video trackId to
48404        *   a list of seiNals found in that track
48405       **/
48406
48407
48408      var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) {
48409        // To get the samples
48410        var trafs = probe$$1.findBox(segment, ['moof', 'traf']); // To get SEI NAL units
48411
48412        var mdats = probe$$1.findBox(segment, ['mdat']);
48413        var captionNals = {};
48414        var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
48415
48416        mdats.forEach(function (mdat, index) {
48417          var matchingTraf = trafs[index];
48418          mdatTrafPairs.push({
48419            mdat: mdat,
48420            traf: matchingTraf
48421          });
48422        });
48423        mdatTrafPairs.forEach(function (pair) {
48424          var mdat = pair.mdat;
48425          var traf = pair.traf;
48426          var tfhd = probe$$1.findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf
48427
48428          var headerInfo = mp4Inspector.parseTfhd(tfhd[0]);
48429          var trackId = headerInfo.trackId;
48430          var tfdt = probe$$1.findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
48431
48432          var baseMediaDecodeTime = tfdt.length > 0 ? mp4Inspector.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0;
48433          var truns = probe$$1.findBox(traf, ['trun']);
48434          var samples;
48435          var seiNals; // Only parse video data for the chosen video track
48436
48437          if (videoTrackId === trackId && truns.length > 0) {
48438            samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
48439            seiNals = findSeiNals(mdat, samples, trackId);
48440
48441            if (!captionNals[trackId]) {
48442              captionNals[trackId] = [];
48443            }
48444
48445            captionNals[trackId] = captionNals[trackId].concat(seiNals);
48446          }
48447        });
48448        return captionNals;
48449      };
48450      /**
48451        * Parses out inband captions from an MP4 container and returns
48452        * caption objects that can be used by WebVTT and the TextTrack API.
48453        * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
48454        * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
48455        * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
48456        *
48457        * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
48458        * @param {Number} trackId - The id of the video track to parse
48459        * @param {Number} timescale - The timescale for the video track from the init segment
48460        *
48461        * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
48462        * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
48463        * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
48464        * @return {String} parsedCaptions[].text - The visible content of the caption
48465       **/
48466
48467
48468      var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) {
48469        var seiNals;
48470
48471        if (!trackId) {
48472          return null;
48473        }
48474
48475        seiNals = parseCaptionNals(segment, trackId);
48476        return {
48477          seiNals: seiNals[trackId],
48478          timescale: timescale
48479        };
48480      };
48481      /**
48482        * Converts SEI NALUs into captions that can be used by video.js
48483       **/
48484
48485
48486      var CaptionParser$$1 = function CaptionParser$$1() {
48487        var isInitialized = false;
48488        var captionStream$$1; // Stores segments seen before trackId and timescale are set
48489
48490        var segmentCache; // Stores video track ID of the track being parsed
48491
48492        var trackId; // Stores the timescale of the track being parsed
48493
48494        var timescale; // Stores captions parsed so far
48495
48496        var parsedCaptions;
48497        /**
48498          * A method to indicate whether a CaptionParser has been initalized
48499          * @returns {Boolean}
48500         **/
48501
48502        this.isInitialized = function () {
48503          return isInitialized;
48504        };
48505        /**
48506          * Initializes the underlying CaptionStream, SEI NAL parsing
48507          * and management, and caption collection
48508         **/
48509
48510
48511        this.init = function () {
48512          captionStream$$1 = new CaptionStream$1();
48513          isInitialized = true; // Collect dispatched captions
48514
48515          captionStream$$1.on('data', function (event) {
48516            // Convert to seconds in the source's timescale
48517            event.startTime = event.startPts / timescale;
48518            event.endTime = event.endPts / timescale;
48519            parsedCaptions.captions.push(event);
48520            parsedCaptions.captionStreams[event.stream] = true;
48521          });
48522        };
48523        /**
48524          * Determines if a new video track will be selected
48525          * or if the timescale changed
48526          * @return {Boolean}
48527         **/
48528
48529
48530        this.isNewInit = function (videoTrackIds, timescales) {
48531          if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
48532            return false;
48533          }
48534
48535          return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
48536        };
48537        /**
48538          * Parses out SEI captions and interacts with underlying
48539          * CaptionStream to return dispatched captions
48540          *
48541          * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
48542          * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
48543          * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
48544          * @see parseEmbeddedCaptions
48545          * @see m2ts/caption-stream.js
48546         **/
48547
48548
48549        this.parse = function (segment, videoTrackIds, timescales) {
48550          var parsedData;
48551
48552          if (!this.isInitialized()) {
48553            return null; // This is not likely to be a video segment
48554          } else if (!videoTrackIds || !timescales) {
48555            return null;
48556          } else if (this.isNewInit(videoTrackIds, timescales)) {
48557            // Use the first video track only as there is no
48558            // mechanism to switch to other video tracks
48559            trackId = videoTrackIds[0];
48560            timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
48561            // data until we have one
48562          } else if (!trackId || !timescale) {
48563            segmentCache.push(segment);
48564            return null;
48565          } // Now that a timescale and trackId is set, parse cached segments
48566
48567
48568          while (segmentCache.length > 0) {
48569            var cachedSegment = segmentCache.shift();
48570            this.parse(cachedSegment, videoTrackIds, timescales);
48571          }
48572
48573          parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
48574
48575          if (parsedData === null || !parsedData.seiNals) {
48576            return null;
48577          }
48578
48579          this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
48580
48581          this.flushStream();
48582          return parsedCaptions;
48583        };
48584        /**
48585          * Pushes SEI NALUs onto CaptionStream
48586          * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
48587          * Assumes that `parseCaptionNals` has been called first
48588          * @see m2ts/caption-stream.js
48589          **/
48590
48591
48592        this.pushNals = function (nals) {
48593          if (!this.isInitialized() || !nals || nals.length === 0) {
48594            return null;
48595          }
48596
48597          nals.forEach(function (nal) {
48598            captionStream$$1.push(nal);
48599          });
48600        };
48601        /**
48602          * Flushes underlying CaptionStream to dispatch processed, displayable captions
48603          * @see m2ts/caption-stream.js
48604         **/
48605
48606
48607        this.flushStream = function () {
48608          if (!this.isInitialized()) {
48609            return null;
48610          }
48611
48612          captionStream$$1.flush();
48613        };
48614        /**
48615          * Reset caption buckets for new data
48616         **/
48617
48618
48619        this.clearParsedCaptions = function () {
48620          parsedCaptions.captions = [];
48621          parsedCaptions.captionStreams = {};
48622        };
48623        /**
48624          * Resets underlying CaptionStream
48625          * @see m2ts/caption-stream.js
48626         **/
48627
48628
48629        this.resetCaptionStream = function () {
48630          if (!this.isInitialized()) {
48631            return null;
48632          }
48633
48634          captionStream$$1.reset();
48635        };
48636        /**
48637          * Convenience method to clear all captions flushed from the
48638          * CaptionStream and still being parsed
48639          * @see m2ts/caption-stream.js
48640         **/
48641
48642
48643        this.clearAllCaptions = function () {
48644          this.clearParsedCaptions();
48645          this.resetCaptionStream();
48646        };
48647        /**
48648          * Reset caption parser
48649         **/
48650
48651
48652        this.reset = function () {
48653          segmentCache = [];
48654          trackId = null;
48655          timescale = null;
48656
48657          if (!parsedCaptions) {
48658            parsedCaptions = {
48659              captions: [],
48660              // CC1, CC2, CC3, CC4
48661              captionStreams: {}
48662            };
48663          } else {
48664            this.clearParsedCaptions();
48665          }
48666
48667          this.resetCaptionStream();
48668        };
48669
48670        this.reset();
48671      };
48672
48673      var captionParser = CaptionParser$$1;
48674      var mp4$$1 = {
48675        generator: mp4Generator,
48676        probe: probe$$1,
48677        Transmuxer: transmuxer.Transmuxer,
48678        AudioSegmentStream: transmuxer.AudioSegmentStream,
48679        VideoSegmentStream: transmuxer.VideoSegmentStream,
48680        CaptionParser: captionParser
48681      };
48682
48683      var classCallCheck = function classCallCheck(instance, Constructor) {
48684        if (!(instance instanceof Constructor)) {
48685          throw new TypeError("Cannot call a class as a function");
48686        }
48687      };
48688
48689      var createClass = function () {
48690        function defineProperties(target, props) {
48691          for (var i = 0; i < props.length; i++) {
48692            var descriptor = props[i];
48693            descriptor.enumerable = descriptor.enumerable || false;
48694            descriptor.configurable = true;
48695            if ("value" in descriptor) descriptor.writable = true;
48696            Object.defineProperty(target, descriptor.key, descriptor);
48697          }
48698        }
48699
48700        return function (Constructor, protoProps, staticProps) {
48701          if (protoProps) defineProperties(Constructor.prototype, protoProps);
48702          if (staticProps) defineProperties(Constructor, staticProps);
48703          return Constructor;
48704        };
48705      }();
48706      /**
48707       * @file transmuxer-worker.js
48708       */
48709
48710      /**
48711       * Re-emits transmuxer events by converting them into messages to the
48712       * world outside the worker.
48713       *
48714       * @param {Object} transmuxer the transmuxer to wire events on
48715       * @private
48716       */
48717
48718
48719      var wireTransmuxerEvents = function wireTransmuxerEvents(self, transmuxer) {
48720        transmuxer.on('data', function (segment) {
48721          // transfer ownership of the underlying ArrayBuffer
48722          // instead of doing a copy to save memory
48723          // ArrayBuffers are transferable but generic TypedArrays are not
48724          // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
48725          var initArray = segment.initSegment;
48726          segment.initSegment = {
48727            data: initArray.buffer,
48728            byteOffset: initArray.byteOffset,
48729            byteLength: initArray.byteLength
48730          };
48731          var typedArray = segment.data;
48732          segment.data = typedArray.buffer;
48733          self.postMessage({
48734            action: 'data',
48735            segment: segment,
48736            byteOffset: typedArray.byteOffset,
48737            byteLength: typedArray.byteLength
48738          }, [segment.data]);
48739        });
48740
48741        if (transmuxer.captionStream) {
48742          transmuxer.captionStream.on('data', function (caption) {
48743            self.postMessage({
48744              action: 'caption',
48745              data: caption
48746            });
48747          });
48748        }
48749
48750        transmuxer.on('done', function (data) {
48751          self.postMessage({
48752            action: 'done'
48753          });
48754        });
48755        transmuxer.on('gopInfo', function (gopInfo) {
48756          self.postMessage({
48757            action: 'gopInfo',
48758            gopInfo: gopInfo
48759          });
48760        });
48761        transmuxer.on('videoSegmentTimingInfo', function (videoSegmentTimingInfo) {
48762          self.postMessage({
48763            action: 'videoSegmentTimingInfo',
48764            videoSegmentTimingInfo: videoSegmentTimingInfo
48765          });
48766        });
48767      };
48768      /**
48769       * All incoming messages route through this hash. If no function exists
48770       * to handle an incoming message, then we ignore the message.
48771       *
48772       * @class MessageHandlers
48773       * @param {Object} options the options to initialize with
48774       */
48775
48776
48777      var MessageHandlers = function () {
48778        function MessageHandlers(self, options) {
48779          classCallCheck(this, MessageHandlers);
48780          this.options = options || {};
48781          this.self = self;
48782          this.init();
48783        }
48784        /**
48785         * initialize our web worker and wire all the events.
48786         */
48787
48788
48789        createClass(MessageHandlers, [{
48790          key: 'init',
48791          value: function init() {
48792            if (this.transmuxer) {
48793              this.transmuxer.dispose();
48794            }
48795
48796            this.transmuxer = new mp4$$1.Transmuxer(this.options);
48797            wireTransmuxerEvents(this.self, this.transmuxer);
48798          }
48799          /**
48800           * Adds data (a ts segment) to the start of the transmuxer pipeline for
48801           * processing.
48802           *
48803           * @param {ArrayBuffer} data data to push into the muxer
48804           */
48805
48806        }, {
48807          key: 'push',
48808          value: function push(data) {
48809            // Cast array buffer to correct type for transmuxer
48810            var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
48811            this.transmuxer.push(segment);
48812          }
48813          /**
48814           * Recreate the transmuxer so that the next segment added via `push`
48815           * start with a fresh transmuxer.
48816           */
48817
48818        }, {
48819          key: 'reset',
48820          value: function reset() {
48821            this.init();
48822          }
48823          /**
48824           * Set the value that will be used as the `baseMediaDecodeTime` time for the
48825           * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
48826           * set relative to the first based on the PTS values.
48827           *
48828           * @param {Object} data used to set the timestamp offset in the muxer
48829           */
48830
48831        }, {
48832          key: 'setTimestampOffset',
48833          value: function setTimestampOffset(data) {
48834            var timestampOffset = data.timestampOffset || 0;
48835            this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000));
48836          }
48837        }, {
48838          key: 'setAudioAppendStart',
48839          value: function setAudioAppendStart(data) {
48840            this.transmuxer.setAudioAppendStart(Math.ceil(data.appendStart * 90000));
48841          }
48842          /**
48843           * Forces the pipeline to finish processing the last segment and emit it's
48844           * results.
48845           *
48846           * @param {Object} data event data, not really used
48847           */
48848
48849        }, {
48850          key: 'flush',
48851          value: function flush(data) {
48852            this.transmuxer.flush();
48853          }
48854        }, {
48855          key: 'resetCaptions',
48856          value: function resetCaptions() {
48857            this.transmuxer.resetCaptions();
48858          }
48859        }, {
48860          key: 'alignGopsWith',
48861          value: function alignGopsWith(data) {
48862            this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
48863          }
48864        }]);
48865        return MessageHandlers;
48866      }();
48867      /**
48868       * Our web wroker interface so that things can talk to mux.js
48869       * that will be running in a web worker. the scope is passed to this by
48870       * webworkify.
48871       *
48872       * @param {Object} self the scope for the web worker
48873       */
48874
48875
48876      var TransmuxerWorker = function TransmuxerWorker(self) {
48877        self.onmessage = function (event) {
48878          if (event.data.action === 'init' && event.data.options) {
48879            this.messageHandlers = new MessageHandlers(self, event.data.options);
48880            return;
48881          }
48882
48883          if (!this.messageHandlers) {
48884            this.messageHandlers = new MessageHandlers(self);
48885          }
48886
48887          if (event.data && event.data.action && event.data.action !== 'init') {
48888            if (this.messageHandlers[event.data.action]) {
48889              this.messageHandlers[event.data.action](event.data);
48890            }
48891          }
48892        };
48893      };
48894
48895      var transmuxerWorker = new TransmuxerWorker(self);
48896      return transmuxerWorker;
48897    }();
48898  });
48899  /**
48900   * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
48901   * codec strings, or translating codec strings into objects that can be examined.
48902   */
48903  // Default codec parameters if none were provided for video and/or audio
48904
48905  var defaultCodecs = {
48906    videoCodec: 'avc1',
48907    videoObjectTypeIndicator: '.4d400d',
48908    // AAC-LC
48909    audioProfile: '2'
48910  };
48911  /**
48912   * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
48913   * `avc1.<hhhhhh>`
48914   *
48915   * @param {Array} codecs an array of codec strings to fix
48916   * @return {Array} the translated codec array
48917   * @private
48918   */
48919
48920  var translateLegacyCodecs = function translateLegacyCodecs(codecs) {
48921    return codecs.map(function (codec) {
48922      return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
48923        var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
48924        var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
48925        return 'avc1.' + profileHex + '00' + avcLevelHex;
48926      });
48927    });
48928  };
48929  /**
48930   * Parses a codec string to retrieve the number of codecs specified,
48931   * the video codec and object type indicator, and the audio profile.
48932   */
48933
48934
48935  var parseCodecs = function parseCodecs() {
48936    var codecs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
48937    var result = {
48938      codecCount: 0
48939    };
48940    var parsed = void 0;
48941    result.codecCount = codecs.split(',').length;
48942    result.codecCount = result.codecCount || 2; // parse the video codec
48943
48944    parsed = /(^|\s|,)+(avc[13])([^ ,]*)/i.exec(codecs);
48945
48946    if (parsed) {
48947      result.videoCodec = parsed[2];
48948      result.videoObjectTypeIndicator = parsed[3];
48949    } // parse the last field of the audio codec
48950
48951
48952    result.audioProfile = /(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i.exec(codecs);
48953    result.audioProfile = result.audioProfile && result.audioProfile[2];
48954    return result;
48955  };
48956  /**
48957   * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
48958   * standard `avc1.<hhhhhh>`.
48959   *
48960   * @param codecString {String} the codec string
48961   * @return {String} the codec string with old apple-style codecs replaced
48962   *
48963   * @private
48964   */
48965
48966
48967  var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) {
48968    return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) {
48969      return translateLegacyCodecs([match])[0];
48970    });
48971  };
48972  /**
48973   * Build a media mime-type string from a set of parameters
48974   * @param {String} type either 'audio' or 'video'
48975   * @param {String} container either 'mp2t' or 'mp4'
48976   * @param {Array} codecs an array of codec strings to add
48977   * @return {String} a valid media mime-type
48978   */
48979
48980
48981  var makeMimeTypeString = function makeMimeTypeString(type, container, codecs) {
48982    // The codecs array is filtered so that falsey values are
48983    // dropped and don't cause Array#join to create spurious
48984    // commas
48985    return type + '/' + container + '; codecs="' + codecs.filter(function (c) {
48986      return !!c;
48987    }).join(', ') + '"';
48988  };
48989  /**
48990   * Returns the type container based on information in the playlist
48991   * @param {Playlist} media the current media playlist
48992   * @return {String} a valid media container type
48993   */
48994
48995
48996  var getContainerType = function getContainerType(media) {
48997    // An initialization segment means the media playlist is an iframe
48998    // playlist or is using the mp4 container. We don't currently
48999    // support iframe playlists, so assume this is signalling mp4
49000    // fragments.
49001    if (media.segments && media.segments.length && media.segments[0].map) {
49002      return 'mp4';
49003    }
49004
49005    return 'mp2t';
49006  };
49007  /**
49008   * Returns a set of codec strings parsed from the playlist or the default
49009   * codec strings if no codecs were specified in the playlist
49010   * @param {Playlist} media the current media playlist
49011   * @return {Object} an object with the video and audio codecs
49012   */
49013
49014
49015  var getCodecs = function getCodecs(media) {
49016    // if the codecs were explicitly specified, use them instead of the
49017    // defaults
49018    var mediaAttributes = media.attributes || {};
49019
49020    if (mediaAttributes.CODECS) {
49021      return parseCodecs(mediaAttributes.CODECS);
49022    }
49023
49024    return defaultCodecs;
49025  };
49026
49027  var audioProfileFromDefault = function audioProfileFromDefault(master, audioGroupId) {
49028    if (!master.mediaGroups.AUDIO || !audioGroupId) {
49029      return null;
49030    }
49031
49032    var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
49033
49034    if (!audioGroup) {
49035      return null;
49036    }
49037
49038    for (var name in audioGroup) {
49039      var audioType = audioGroup[name];
49040
49041      if (audioType.default && audioType.playlists) {
49042        // codec should be the same for all playlists within the audio type
49043        return parseCodecs(audioType.playlists[0].attributes.CODECS).audioProfile;
49044      }
49045    }
49046
49047    return null;
49048  };
49049  /**
49050   * Calculates the MIME type strings for a working configuration of
49051   * SourceBuffers to play variant streams in a master playlist. If
49052   * there is no possible working configuration, an empty array will be
49053   * returned.
49054   *
49055   * @param master {Object} the m3u8 object for the master playlist
49056   * @param media {Object} the m3u8 object for the variant playlist
49057   * @return {Array} the MIME type strings. If the array has more than
49058   * one entry, the first element should be applied to the video
49059   * SourceBuffer and the second to the audio SourceBuffer.
49060   *
49061   * @private
49062   */
49063
49064
49065  var mimeTypesForPlaylist = function mimeTypesForPlaylist(master, media) {
49066    var containerType = getContainerType(media);
49067    var codecInfo = getCodecs(media);
49068    var mediaAttributes = media.attributes || {}; // Default condition for a traditional HLS (no demuxed audio/video)
49069
49070    var isMuxed = true;
49071    var isMaat = false;
49072
49073    if (!media) {
49074      // Not enough information
49075      return [];
49076    }
49077
49078    if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) {
49079      var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; // Handle the case where we are in a multiple-audio track scenario
49080
49081      if (audioGroup) {
49082        isMaat = true; // Start with the everything demuxed then...
49083
49084        isMuxed = false; // ...check to see if any audio group tracks are muxed (ie. lacking a uri)
49085
49086        for (var groupId in audioGroup) {
49087          // either a uri is present (if the case of HLS and an external playlist), or
49088          // playlists is present (in the case of DASH where we don't have external audio
49089          // playlists)
49090          if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
49091            isMuxed = true;
49092            break;
49093          }
49094        }
49095      }
49096    } // HLS with multiple-audio tracks must always get an audio codec.
49097    // Put another way, there is no way to have a video-only multiple-audio HLS!
49098
49099
49100    if (isMaat && !codecInfo.audioProfile) {
49101      if (!isMuxed) {
49102        // It is possible for codecs to be specified on the audio media group playlist but
49103        // not on the rendition playlist. This is mostly the case for DASH, where audio and
49104        // video are always separate (and separately specified).
49105        codecInfo.audioProfile = audioProfileFromDefault(master, mediaAttributes.AUDIO);
49106      }
49107
49108      if (!codecInfo.audioProfile) {
49109        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)');
49110        codecInfo.audioProfile = defaultCodecs.audioProfile;
49111      }
49112    } // Generate the final codec strings from the codec object generated above
49113
49114
49115    var codecStrings = {};
49116
49117    if (codecInfo.videoCodec) {
49118      codecStrings.video = '' + codecInfo.videoCodec + codecInfo.videoObjectTypeIndicator;
49119    }
49120
49121    if (codecInfo.audioProfile) {
49122      codecStrings.audio = 'mp4a.40.' + codecInfo.audioProfile;
49123    } // Finally, make and return an array with proper mime-types depending on
49124    // the configuration
49125
49126
49127    var justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]);
49128    var justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]);
49129    var bothVideoAudio = makeMimeTypeString('video', containerType, [codecStrings.video, codecStrings.audio]);
49130
49131    if (isMaat) {
49132      if (!isMuxed && codecStrings.video) {
49133        return [justVideo, justAudio];
49134      }
49135
49136      if (!isMuxed && !codecStrings.video) {
49137        // There is no muxed content and no video codec string, so this is an audio only
49138        // stream with alternate audio.
49139        return [justAudio, justAudio];
49140      } // There exists the possiblity that this will return a `video/container`
49141      // mime-type for the first entry in the array even when there is only audio.
49142      // This doesn't appear to be a problem and simplifies the code.
49143
49144
49145      return [bothVideoAudio, justAudio];
49146    } // If there is no video codec at all, always just return a single
49147    // audio/<container> mime-type
49148
49149
49150    if (!codecStrings.video) {
49151      return [justAudio];
49152    } // When not using separate audio media groups, audio and video is
49153    // *always* muxed
49154
49155
49156    return [bothVideoAudio];
49157  };
49158  /**
49159   * Parse a content type header into a type and parameters
49160   * object
49161   *
49162   * @param {String} type the content type header
49163   * @return {Object} the parsed content-type
49164   * @private
49165   */
49166
49167
49168  var parseContentType = function parseContentType(type) {
49169    var object = {
49170      type: '',
49171      parameters: {}
49172    };
49173    var parameters = type.trim().split(';'); // first parameter should always be content-type
49174
49175    object.type = parameters.shift().trim();
49176    parameters.forEach(function (parameter) {
49177      var pair = parameter.trim().split('=');
49178
49179      if (pair.length > 1) {
49180        var name = pair[0].replace(/"/g, '').trim();
49181        var value = pair[1].replace(/"/g, '').trim();
49182        object.parameters[name] = value;
49183      }
49184    });
49185    return object;
49186  };
49187  /**
49188   * Check if a codec string refers to an audio codec.
49189   *
49190   * @param {String} codec codec string to check
49191   * @return {Boolean} if this is an audio codec
49192   * @private
49193   */
49194
49195
49196  var isAudioCodec = function isAudioCodec(codec) {
49197    return /mp4a\.\d+.\d+/i.test(codec);
49198  };
49199  /**
49200   * Check if a codec string refers to a video codec.
49201   *
49202   * @param {String} codec codec string to check
49203   * @return {Boolean} if this is a video codec
49204   * @private
49205   */
49206
49207
49208  var isVideoCodec = function isVideoCodec(codec) {
49209    return /avc1\.[\da-f]+/i.test(codec);
49210  };
49211  /**
49212   * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
49213   * front of current time.
49214   *
49215   * @param {Array} buffer
49216   *        The current buffer of gop information
49217   * @param {Number} currentTime
49218   *        The current time
49219   * @param {Double} mapping
49220   *        Offset to map display time to stream presentation time
49221   * @return {Array}
49222   *         List of gops considered safe to append over
49223   */
49224
49225
49226  var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, currentTime, mapping) {
49227    if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) {
49228      return [];
49229    } // pts value for current time + 3 seconds to give a bit more wiggle room
49230
49231
49232    var currentTimePts = Math.ceil((currentTime - mapping + 3) * 90000);
49233    var i = void 0;
49234
49235    for (i = 0; i < buffer.length; i++) {
49236      if (buffer[i].pts > currentTimePts) {
49237        break;
49238      }
49239    }
49240
49241    return buffer.slice(i);
49242  };
49243  /**
49244   * Appends gop information (timing and byteLength) received by the transmuxer for the
49245   * gops appended in the last call to appendBuffer
49246   *
49247   * @param {Array} buffer
49248   *        The current buffer of gop information
49249   * @param {Array} gops
49250   *        List of new gop information
49251   * @param {boolean} replace
49252   *        If true, replace the buffer with the new gop information. If false, append the
49253   *        new gop information to the buffer in the right location of time.
49254   * @return {Array}
49255   *         Updated list of gop information
49256   */
49257
49258
49259  var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) {
49260    if (!gops.length) {
49261      return buffer;
49262    }
49263
49264    if (replace) {
49265      // If we are in safe append mode, then completely overwrite the gop buffer
49266      // with the most recent appeneded data. This will make sure that when appending
49267      // future segments, we only try to align with gops that are both ahead of current
49268      // time and in the last segment appended.
49269      return gops.slice();
49270    }
49271
49272    var start = gops[0].pts;
49273    var i = 0;
49274
49275    for (i; i < buffer.length; i++) {
49276      if (buffer[i].pts >= start) {
49277        break;
49278      }
49279    }
49280
49281    return buffer.slice(0, i).concat(gops);
49282  };
49283  /**
49284   * Removes gop information in buffer that overlaps with provided start and end
49285   *
49286   * @param {Array} buffer
49287   *        The current buffer of gop information
49288   * @param {Double} start
49289   *        position to start the remove at
49290   * @param {Double} end
49291   *        position to end the remove at
49292   * @param {Double} mapping
49293   *        Offset to map display time to stream presentation time
49294   */
49295
49296
49297  var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) {
49298    var startPts = Math.ceil((start - mapping) * 90000);
49299    var endPts = Math.ceil((end - mapping) * 90000);
49300    var updatedBuffer = buffer.slice();
49301    var i = buffer.length;
49302
49303    while (i--) {
49304      if (buffer[i].pts <= endPts) {
49305        break;
49306      }
49307    }
49308
49309    if (i === -1) {
49310      // no removal because end of remove range is before start of buffer
49311      return updatedBuffer;
49312    }
49313
49314    var j = i + 1;
49315
49316    while (j--) {
49317      if (buffer[j].pts <= startPts) {
49318        break;
49319      }
49320    } // clamp remove range start to 0 index
49321
49322
49323    j = Math.max(j, 0);
49324    updatedBuffer.splice(j, i - j + 1);
49325    return updatedBuffer;
49326  };
49327
49328  var buffered = function buffered(videoBuffer, audioBuffer, audioDisabled) {
49329    var start = null;
49330    var end = null;
49331    var arity = 0;
49332    var extents = [];
49333    var ranges = []; // neither buffer has been created yet
49334
49335    if (!videoBuffer && !audioBuffer) {
49336      return videojs$1.createTimeRange();
49337    } // only one buffer is configured
49338
49339
49340    if (!videoBuffer) {
49341      return audioBuffer.buffered;
49342    }
49343
49344    if (!audioBuffer) {
49345      return videoBuffer.buffered;
49346    } // both buffers are configured
49347
49348
49349    if (audioDisabled) {
49350      return videoBuffer.buffered;
49351    } // both buffers are empty
49352
49353
49354    if (videoBuffer.buffered.length === 0 && audioBuffer.buffered.length === 0) {
49355      return videojs$1.createTimeRange();
49356    } // Handle the case where we have both buffers and create an
49357    // intersection of the two
49358
49359
49360    var videoBuffered = videoBuffer.buffered;
49361    var audioBuffered = audioBuffer.buffered;
49362    var count = videoBuffered.length; // A) Gather up all start and end times
49363
49364    while (count--) {
49365      extents.push({
49366        time: videoBuffered.start(count),
49367        type: 'start'
49368      });
49369      extents.push({
49370        time: videoBuffered.end(count),
49371        type: 'end'
49372      });
49373    }
49374
49375    count = audioBuffered.length;
49376
49377    while (count--) {
49378      extents.push({
49379        time: audioBuffered.start(count),
49380        type: 'start'
49381      });
49382      extents.push({
49383        time: audioBuffered.end(count),
49384        type: 'end'
49385      });
49386    } // B) Sort them by time
49387
49388
49389    extents.sort(function (a, b) {
49390      return a.time - b.time;
49391    }); // C) Go along one by one incrementing arity for start and decrementing
49392    //    arity for ends
49393
49394    for (count = 0; count < extents.length; count++) {
49395      if (extents[count].type === 'start') {
49396        arity++; // D) If arity is ever incremented to 2 we are entering an
49397        //    overlapping range
49398
49399        if (arity === 2) {
49400          start = extents[count].time;
49401        }
49402      } else if (extents[count].type === 'end') {
49403        arity--; // E) If arity is ever decremented to 1 we leaving an
49404        //    overlapping range
49405
49406        if (arity === 1) {
49407          end = extents[count].time;
49408        }
49409      } // F) Record overlapping ranges
49410
49411
49412      if (start !== null && end !== null) {
49413        ranges.push([start, end]);
49414        start = null;
49415        end = null;
49416      }
49417    }
49418
49419    return videojs$1.createTimeRanges(ranges);
49420  };
49421  /**
49422   * @file virtual-source-buffer.js
49423   */
49424
49425
49426  var ONE_SECOND_IN_TS$3 = 90000; // We create a wrapper around the SourceBuffer so that we can manage the
49427  // state of the `updating` property manually. We have to do this because
49428  // Firefox changes `updating` to false long before triggering `updateend`
49429  // events and that was causing strange problems in videojs-contrib-hls
49430
49431  var makeWrappedSourceBuffer = function makeWrappedSourceBuffer(mediaSource, mimeType) {
49432    var sourceBuffer = mediaSource.addSourceBuffer(mimeType);
49433    var wrapper = Object.create(null);
49434    wrapper.updating = false;
49435    wrapper.realBuffer_ = sourceBuffer;
49436
49437    var _loop = function _loop(key) {
49438      if (typeof sourceBuffer[key] === 'function') {
49439        wrapper[key] = function () {
49440          return sourceBuffer[key].apply(sourceBuffer, arguments);
49441        };
49442      } else if (typeof wrapper[key] === 'undefined') {
49443        Object.defineProperty(wrapper, key, {
49444          get: function get$$1() {
49445            return sourceBuffer[key];
49446          },
49447          set: function set$$1(v) {
49448            return sourceBuffer[key] = v;
49449          }
49450        });
49451      }
49452    };
49453
49454    for (var key in sourceBuffer) {
49455      _loop(key);
49456    }
49457
49458    return wrapper;
49459  };
49460  /**
49461   * VirtualSourceBuffers exist so that we can transmux non native formats
49462   * into a native format, but keep the same api as a native source buffer.
49463   * It creates a transmuxer, that works in its own thread (a web worker) and
49464   * that transmuxer muxes the data into a native format. VirtualSourceBuffer will
49465   * then send all of that data to the naive sourcebuffer so that it is
49466   * indestinguishable from a natively supported format.
49467   *
49468   * @param {HtmlMediaSource} mediaSource the parent mediaSource
49469   * @param {Array} codecs array of codecs that we will be dealing with
49470   * @class VirtualSourceBuffer
49471   * @extends video.js.EventTarget
49472   */
49473
49474
49475  var VirtualSourceBuffer = function (_videojs$EventTarget) {
49476    inherits$1(VirtualSourceBuffer, _videojs$EventTarget);
49477
49478    function VirtualSourceBuffer(mediaSource, codecs) {
49479      classCallCheck$1(this, VirtualSourceBuffer);
49480
49481      var _this = possibleConstructorReturn$1(this, (VirtualSourceBuffer.__proto__ || Object.getPrototypeOf(VirtualSourceBuffer)).call(this, videojs$1.EventTarget));
49482
49483      _this.timestampOffset_ = 0;
49484      _this.pendingBuffers_ = [];
49485      _this.bufferUpdating_ = false;
49486      _this.mediaSource_ = mediaSource;
49487      _this.codecs_ = codecs;
49488      _this.audioCodec_ = null;
49489      _this.videoCodec_ = null;
49490      _this.audioDisabled_ = false;
49491      _this.appendAudioInitSegment_ = true;
49492      _this.gopBuffer_ = [];
49493      _this.timeMapping_ = 0;
49494      _this.safeAppend_ = videojs$1.browser.IE_VERSION >= 11;
49495      var options = {
49496        remux: false,
49497        alignGopsAtEnd: _this.safeAppend_
49498      };
49499
49500      _this.codecs_.forEach(function (codec) {
49501        if (isAudioCodec(codec)) {
49502          _this.audioCodec_ = codec;
49503        } else if (isVideoCodec(codec)) {
49504          _this.videoCodec_ = codec;
49505        }
49506      }); // append muxed segments to their respective native buffers as
49507      // soon as they are available
49508
49509
49510      _this.transmuxer_ = new TransmuxWorker();
49511
49512      _this.transmuxer_.postMessage({
49513        action: 'init',
49514        options: options
49515      });
49516
49517      _this.transmuxer_.onmessage = function (event) {
49518        if (event.data.action === 'data') {
49519          return _this.data_(event);
49520        }
49521
49522        if (event.data.action === 'done') {
49523          return _this.done_(event);
49524        }
49525
49526        if (event.data.action === 'gopInfo') {
49527          return _this.appendGopInfo_(event);
49528        }
49529
49530        if (event.data.action === 'videoSegmentTimingInfo') {
49531          return _this.videoSegmentTimingInfo_(event.data.videoSegmentTimingInfo);
49532        }
49533      }; // this timestampOffset is a property with the side-effect of resetting
49534      // baseMediaDecodeTime in the transmuxer on the setter
49535
49536
49537      Object.defineProperty(_this, 'timestampOffset', {
49538        get: function get$$1() {
49539          return this.timestampOffset_;
49540        },
49541        set: function set$$1(val) {
49542          if (typeof val === 'number' && val >= 0) {
49543            this.timestampOffset_ = val;
49544            this.appendAudioInitSegment_ = true; // reset gop buffer on timestampoffset as this signals a change in timeline
49545
49546            this.gopBuffer_.length = 0;
49547            this.timeMapping_ = 0; // We have to tell the transmuxer to set the baseMediaDecodeTime to
49548            // the desired timestampOffset for the next segment
49549
49550            this.transmuxer_.postMessage({
49551              action: 'setTimestampOffset',
49552              timestampOffset: val
49553            });
49554          }
49555        }
49556      }); // setting the append window affects both source buffers
49557
49558      Object.defineProperty(_this, 'appendWindowStart', {
49559        get: function get$$1() {
49560          return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart;
49561        },
49562        set: function set$$1(start) {
49563          if (this.videoBuffer_) {
49564            this.videoBuffer_.appendWindowStart = start;
49565          }
49566
49567          if (this.audioBuffer_) {
49568            this.audioBuffer_.appendWindowStart = start;
49569          }
49570        }
49571      }); // this buffer is "updating" if either of its native buffers are
49572
49573      Object.defineProperty(_this, 'updating', {
49574        get: function get$$1() {
49575          return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating);
49576        }
49577      }); // the buffered property is the intersection of the buffered
49578      // ranges of the native source buffers
49579
49580      Object.defineProperty(_this, 'buffered', {
49581        get: function get$$1() {
49582          return buffered(this.videoBuffer_, this.audioBuffer_, this.audioDisabled_);
49583        }
49584      });
49585      return _this;
49586    }
49587    /**
49588     * When we get a data event from the transmuxer
49589     * we call this function and handle the data that
49590     * was sent to us
49591     *
49592     * @private
49593     * @param {Event} event the data event from the transmuxer
49594     */
49595
49596
49597    createClass$1(VirtualSourceBuffer, [{
49598      key: 'data_',
49599      value: function data_(event) {
49600        var segment = event.data.segment; // Cast ArrayBuffer to TypedArray
49601
49602        segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength);
49603        segment.initSegment = new Uint8Array(segment.initSegment.data, segment.initSegment.byteOffset, segment.initSegment.byteLength);
49604        createTextTracksIfNecessary(this, this.mediaSource_, segment); // Add the segments to the pendingBuffers array
49605
49606        this.pendingBuffers_.push(segment);
49607        return;
49608      }
49609      /**
49610       * When we get a done event from the transmuxer
49611       * we call this function and we process all
49612       * of the pending data that we have been saving in the
49613       * data_ function
49614       *
49615       * @private
49616       * @param {Event} event the done event from the transmuxer
49617       */
49618
49619    }, {
49620      key: 'done_',
49621      value: function done_(event) {
49622        // Don't process and append data if the mediaSource is closed
49623        if (this.mediaSource_.readyState === 'closed') {
49624          this.pendingBuffers_.length = 0;
49625          return;
49626        } // All buffers should have been flushed from the muxer
49627        // start processing anything we have received
49628
49629
49630        this.processPendingSegments_();
49631        return;
49632      }
49633    }, {
49634      key: 'videoSegmentTimingInfo_',
49635      value: function videoSegmentTimingInfo_(timingInfo) {
49636        var timingInfoInSeconds = {
49637          start: {
49638            decode: timingInfo.start.dts / ONE_SECOND_IN_TS$3,
49639            presentation: timingInfo.start.pts / ONE_SECOND_IN_TS$3
49640          },
49641          end: {
49642            decode: timingInfo.end.dts / ONE_SECOND_IN_TS$3,
49643            presentation: timingInfo.end.pts / ONE_SECOND_IN_TS$3
49644          },
49645          baseMediaDecodeTime: timingInfo.baseMediaDecodeTime / ONE_SECOND_IN_TS$3
49646        };
49647
49648        if (timingInfo.prependedContentDuration) {
49649          timingInfoInSeconds.prependedContentDuration = timingInfo.prependedContentDuration / ONE_SECOND_IN_TS$3;
49650        }
49651
49652        this.trigger({
49653          type: 'videoSegmentTimingInfo',
49654          videoSegmentTimingInfo: timingInfoInSeconds
49655        });
49656      }
49657      /**
49658       * Create our internal native audio/video source buffers and add
49659       * event handlers to them with the following conditions:
49660       * 1. they do not already exist on the mediaSource
49661       * 2. this VSB has a codec for them
49662       *
49663       * @private
49664       */
49665
49666    }, {
49667      key: 'createRealSourceBuffers_',
49668      value: function createRealSourceBuffers_() {
49669        var _this2 = this;
49670
49671        var types = ['audio', 'video'];
49672        types.forEach(function (type) {
49673          // Don't create a SourceBuffer of this type if we don't have a
49674          // codec for it
49675          if (!_this2[type + 'Codec_']) {
49676            return;
49677          } // Do nothing if a SourceBuffer of this type already exists
49678
49679
49680          if (_this2[type + 'Buffer_']) {
49681            return;
49682          }
49683
49684          var buffer = null; // If the mediasource already has a SourceBuffer for the codec
49685          // use that
49686
49687          if (_this2.mediaSource_[type + 'Buffer_']) {
49688            buffer = _this2.mediaSource_[type + 'Buffer_']; // In multiple audio track cases, the audio source buffer is disabled
49689            // on the main VirtualSourceBuffer by the HTMLMediaSource much earlier
49690            // than createRealSourceBuffers_ is called to create the second
49691            // VirtualSourceBuffer because that happens as a side-effect of
49692            // videojs-contrib-hls starting the audioSegmentLoader. As a result,
49693            // the audioBuffer is essentially "ownerless" and no one will toggle
vendor: 29,081 bytes, lines 49694-50511
49694            // the `updating` state back to false once the `updateend` event is received
49695            //
49696            // Setting `updating` to false manually will work around this
49697            // situation and allow work to continue
49698
49699            buffer.updating = false;
49700          } else {
49701            var codecProperty = type + 'Codec_';
49702            var mimeType = type + '/mp4;codecs="' + _this2[codecProperty] + '"';
49703            buffer = makeWrappedSourceBuffer(_this2.mediaSource_.nativeMediaSource_, mimeType);
49704            _this2.mediaSource_[type + 'Buffer_'] = buffer;
49705          }
49706
49707          _this2[type + 'Buffer_'] = buffer; // Wire up the events to the SourceBuffer
49708
49709          ['update', 'updatestart', 'updateend'].forEach(function (event) {
49710            buffer.addEventListener(event, function () {
49711              // if audio is disabled
49712              if (type === 'audio' && _this2.audioDisabled_) {
49713                return;
49714              }
49715
49716              if (event === 'updateend') {
49717                _this2[type + 'Buffer_'].updating = false;
49718              }
49719
49720              var shouldTrigger = types.every(function (t) {
49721                // skip checking audio's updating status if audio
49722                // is not enabled
49723                if (t === 'audio' && _this2.audioDisabled_) {
49724                  return true;
49725                } // if the other type if updating we don't trigger
49726
49727
49728                if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) {
49729                  return false;
49730                }
49731
49732                return true;
49733              });
49734
49735              if (shouldTrigger) {
49736                return _this2.trigger(event);
49737              }
49738            });
49739          });
49740        });
49741      }
49742      /**
49743       * Emulate the native mediasource function, but our function will
49744       * send all of the proposed segments to the transmuxer so that we
49745       * can transmux them before we append them to our internal
49746       * native source buffers in the correct format.
49747       *
49748       * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer
49749       * @param {Uint8Array} segment the segment to append to the buffer
49750       */
49751
49752    }, {
49753      key: 'appendBuffer',
49754      value: function appendBuffer(segment) {
49755        // Start the internal "updating" state
49756        this.bufferUpdating_ = true;
49757
49758        if (this.audioBuffer_ && this.audioBuffer_.buffered.length) {
49759          var audioBuffered = this.audioBuffer_.buffered;
49760          this.transmuxer_.postMessage({
49761            action: 'setAudioAppendStart',
49762            appendStart: audioBuffered.end(audioBuffered.length - 1)
49763          });
49764        }
49765
49766        if (this.videoBuffer_) {
49767          this.transmuxer_.postMessage({
49768            action: 'alignGopsWith',
49769            gopsToAlignWith: gopsSafeToAlignWith(this.gopBuffer_, this.mediaSource_.player_ ? this.mediaSource_.player_.currentTime() : null, this.timeMapping_)
49770          });
49771        }
49772
49773        this.transmuxer_.postMessage({
49774          action: 'push',
49775          // Send the typed-array of data as an ArrayBuffer so that
49776          // it can be sent as a "Transferable" and avoid the costly
49777          // memory copy
49778          data: segment.buffer,
49779          // To recreate the original typed-array, we need information
49780          // about what portion of the ArrayBuffer it was a view into
49781          byteOffset: segment.byteOffset,
49782          byteLength: segment.byteLength
49783        }, [segment.buffer]);
49784        this.transmuxer_.postMessage({
49785          action: 'flush'
49786        });
49787      }
49788      /**
49789       * Appends gop information (timing and byteLength) received by the transmuxer for the
49790       * gops appended in the last call to appendBuffer
49791       *
49792       * @param {Event} event
49793       *        The gopInfo event from the transmuxer
49794       * @param {Array} event.data.gopInfo
49795       *        List of gop info to append
49796       */
49797
49798    }, {
49799      key: 'appendGopInfo_',
49800      value: function appendGopInfo_(event) {
49801        this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, event.data.gopInfo, this.safeAppend_);
49802      }
49803      /**
49804       * Emulate the native mediasource function and remove parts
49805       * of the buffer from any of our internal buffers that exist
49806       *
49807       * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove
49808       * @param {Double} start position to start the remove at
49809       * @param {Double} end position to end the remove at
49810       */
49811
49812    }, {
49813      key: 'remove',
49814      value: function remove(start, end) {
49815        if (this.videoBuffer_) {
49816          this.videoBuffer_.updating = true;
49817          this.videoBuffer_.remove(start, end);
49818          this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
49819        }
49820
49821        if (!this.audioDisabled_ && this.audioBuffer_) {
49822          this.audioBuffer_.updating = true;
49823          this.audioBuffer_.remove(start, end);
49824        } // Remove Metadata Cues (id3)
49825
49826
49827        removeCuesFromTrack(start, end, this.metadataTrack_); // Remove Any Captions
49828
49829        if (this.inbandTextTracks_) {
49830          for (var track in this.inbandTextTracks_) {
49831            removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
49832          }
49833        }
49834      }
49835      /**
49836       * Process any segments that the muxer has output
49837       * Concatenate segments together based on type and append them into
49838       * their respective sourceBuffers
49839       *
49840       * @private
49841       */
49842
49843    }, {
49844      key: 'processPendingSegments_',
49845      value: function processPendingSegments_() {
49846        var sortedSegments = {
49847          video: {
49848            segments: [],
49849            bytes: 0
49850          },
49851          audio: {
49852            segments: [],
49853            bytes: 0
49854          },
49855          captions: [],
49856          metadata: []
49857        }; // Sort segments into separate video/audio arrays and
49858        // keep track of their total byte lengths
49859
49860        sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) {
49861          var type = segment.type;
49862          var data = segment.data;
49863          var initSegment = segment.initSegment;
49864          segmentObj[type].segments.push(data);
49865          segmentObj[type].bytes += data.byteLength;
49866          segmentObj[type].initSegment = initSegment; // Gather any captions into a single array
49867
49868          if (segment.captions) {
49869            segmentObj.captions = segmentObj.captions.concat(segment.captions);
49870          }
49871
49872          if (segment.info) {
49873            segmentObj[type].info = segment.info;
49874          } // Gather any metadata into a single array
49875
49876
49877          if (segment.metadata) {
49878            segmentObj.metadata = segmentObj.metadata.concat(segment.metadata);
49879          }
49880
49881          return segmentObj;
49882        }, sortedSegments); // Create the real source buffers if they don't exist by now since we
49883        // finally are sure what tracks are contained in the source
49884
49885        if (!this.videoBuffer_ && !this.audioBuffer_) {
49886          // Remove any codecs that may have been specified by default but
49887          // are no longer applicable now
49888          if (sortedSegments.video.bytes === 0) {
49889            this.videoCodec_ = null;
49890          }
49891
49892          if (sortedSegments.audio.bytes === 0) {
49893            this.audioCodec_ = null;
49894          }
49895
49896          this.createRealSourceBuffers_();
49897        }
49898
49899        if (sortedSegments.audio.info) {
49900          this.mediaSource_.trigger({
49901            type: 'audioinfo',
49902            info: sortedSegments.audio.info
49903          });
49904        }
49905
49906        if (sortedSegments.video.info) {
49907          this.mediaSource_.trigger({
49908            type: 'videoinfo',
49909            info: sortedSegments.video.info
49910          });
49911        }
49912
49913        if (this.appendAudioInitSegment_) {
49914          if (!this.audioDisabled_ && this.audioBuffer_) {
49915            sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment);
49916            sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength;
49917          }
49918
49919          this.appendAudioInitSegment_ = false;
49920        }
49921
49922        var triggerUpdateend = false; // Merge multiple video and audio segments into one and append
49923
49924        if (this.videoBuffer_ && sortedSegments.video.bytes) {
49925          sortedSegments.video.segments.unshift(sortedSegments.video.initSegment);
49926          sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength;
49927          this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_);
49928        } else if (this.videoBuffer_ && (this.audioDisabled_ || !this.audioBuffer_)) {
49929          // The transmuxer did not return any bytes of video, meaning it was all trimmed
49930          // for gop alignment. Since we have a video buffer and audio is disabled, updateend
49931          // will never be triggered by this source buffer, which will cause contrib-hls
49932          // to be stuck forever waiting for updateend. If audio is not disabled, updateend
49933          // will be triggered by the audio buffer, which will be sent upwards since the video
49934          // buffer will not be in an updating state.
49935          triggerUpdateend = true;
49936        } // Add text-track data for all
49937
49938
49939        addTextTrackData(this, sortedSegments.captions, sortedSegments.metadata);
49940
49941        if (!this.audioDisabled_ && this.audioBuffer_) {
49942          this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_);
49943        }
49944
49945        this.pendingBuffers_.length = 0;
49946
49947        if (triggerUpdateend) {
49948          this.trigger('updateend');
49949        } // We are no longer in the internal "updating" state
49950
49951
49952        this.bufferUpdating_ = false;
49953      }
49954      /**
49955       * Combine all segments into a single Uint8Array and then append them
49956       * to the destination buffer
49957       *
49958       * @param {Object} segmentObj
49959       * @param {SourceBuffer} destinationBuffer native source buffer to append data to
49960       * @private
49961       */
49962
49963    }, {
49964      key: 'concatAndAppendSegments_',
49965      value: function concatAndAppendSegments_(segmentObj, destinationBuffer) {
49966        var offset = 0;
49967        var tempBuffer = void 0;
49968
49969        if (segmentObj.bytes) {
49970          tempBuffer = new Uint8Array(segmentObj.bytes); // Combine the individual segments into one large typed-array
49971
49972          segmentObj.segments.forEach(function (segment) {
49973            tempBuffer.set(segment, offset);
49974            offset += segment.byteLength;
49975          });
49976
49977          try {
49978            destinationBuffer.updating = true;
49979            destinationBuffer.appendBuffer(tempBuffer);
49980          } catch (error) {
49981            if (this.mediaSource_.player_) {
49982              this.mediaSource_.player_.error({
49983                code: -3,
49984                type: 'APPEND_BUFFER_ERR',
49985                message: error.message,
49986                originalError: error
49987              });
49988            }
49989          }
49990        }
49991      }
49992      /**
49993       * Emulate the native mediasource function. abort any soureBuffer
49994       * actions and throw out any un-appended data.
49995       *
49996       * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort
49997       */
49998
49999    }, {
50000      key: 'abort',
50001      value: function abort() {
50002        if (this.videoBuffer_) {
50003          this.videoBuffer_.abort();
50004        }
50005
50006        if (!this.audioDisabled_ && this.audioBuffer_) {
50007          this.audioBuffer_.abort();
50008        }
50009
50010        if (this.transmuxer_) {
50011          this.transmuxer_.postMessage({
50012            action: 'reset'
50013          });
50014        }
50015
50016        this.pendingBuffers_.length = 0;
50017        this.bufferUpdating_ = false;
50018      }
50019    }]);
50020    return VirtualSourceBuffer;
50021  }(videojs$1.EventTarget);
50022  /**
50023   * @file html-media-source.js
50024   */
50025
50026  /**
50027   * Our MediaSource implementation in HTML, mimics native
50028   * MediaSource where/if possible.
50029   *
50030   * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
50031   * @class HtmlMediaSource
50032   * @extends videojs.EventTarget
50033   */
50034
50035
50036  var HtmlMediaSource = function (_videojs$EventTarget) {
50037    inherits$1(HtmlMediaSource, _videojs$EventTarget);
50038
50039    function HtmlMediaSource() {
50040      classCallCheck$1(this, HtmlMediaSource);
50041
50042      var _this = possibleConstructorReturn$1(this, (HtmlMediaSource.__proto__ || Object.getPrototypeOf(HtmlMediaSource)).call(this));
50043
50044      var property = void 0;
50045      _this.nativeMediaSource_ = new window$1.MediaSource(); // delegate to the native MediaSource's methods by default
50046
50047      for (property in _this.nativeMediaSource_) {
50048        if (!(property in HtmlMediaSource.prototype) && typeof _this.nativeMediaSource_[property] === 'function') {
50049          _this[property] = _this.nativeMediaSource_[property].bind(_this.nativeMediaSource_);
50050        }
50051      } // emulate `duration` and `seekable` until seeking can be
50052      // handled uniformly for live streams
50053      // see https://github.com/w3c/media-source/issues/5
50054
50055
50056      _this.duration_ = NaN;
50057      Object.defineProperty(_this, 'duration', {
50058        get: function get$$1() {
50059          if (this.duration_ === Infinity) {
50060            return this.duration_;
50061          }
50062
50063          return this.nativeMediaSource_.duration;
50064        },
50065        set: function set$$1(duration) {
50066          this.duration_ = duration;
50067
50068          if (duration !== Infinity) {
50069            this.nativeMediaSource_.duration = duration;
50070            return;
50071          }
50072        }
50073      });
50074      Object.defineProperty(_this, 'seekable', {
50075        get: function get$$1() {
50076          if (this.duration_ === Infinity) {
50077            return videojs$1.createTimeRanges([[0, this.nativeMediaSource_.duration]]);
50078          }
50079
50080          return this.nativeMediaSource_.seekable;
50081        }
50082      });
50083      Object.defineProperty(_this, 'readyState', {
50084        get: function get$$1() {
50085          return this.nativeMediaSource_.readyState;
50086        }
50087      });
50088      Object.defineProperty(_this, 'activeSourceBuffers', {
50089        get: function get$$1() {
50090          return this.activeSourceBuffers_;
50091        }
50092      }); // the list of virtual and native SourceBuffers created by this
50093      // MediaSource
50094
50095      _this.sourceBuffers = [];
50096      _this.activeSourceBuffers_ = [];
50097      /**
50098       * update the list of active source buffers based upon various
50099       * imformation from HLS and video.js
50100       *
50101       * @private
50102       */
50103
50104      _this.updateActiveSourceBuffers_ = function () {
50105        // Retain the reference but empty the array
50106        _this.activeSourceBuffers_.length = 0; // If there is only one source buffer, then it will always be active and audio will
50107        // be disabled based on the codec of the source buffer
50108
50109        if (_this.sourceBuffers.length === 1) {
50110          var sourceBuffer = _this.sourceBuffers[0];
50111          sourceBuffer.appendAudioInitSegment_ = true;
50112          sourceBuffer.audioDisabled_ = !sourceBuffer.audioCodec_;
50113
50114          _this.activeSourceBuffers_.push(sourceBuffer);
50115
50116          return;
50117        } // There are 2 source buffers, a combined (possibly video only) source buffer and
50118        // and an audio only source buffer.
50119        // By default, the audio in the combined virtual source buffer is enabled
50120        // and the audio-only source buffer (if it exists) is disabled.
50121
50122
50123        var disableCombined = false;
50124        var disableAudioOnly = true; // TODO: maybe we can store the sourcebuffers on the track objects?
50125        // safari may do something like this
50126
50127        for (var i = 0; i < _this.player_.audioTracks().length; i++) {
50128          var track = _this.player_.audioTracks()[i];
50129
50130          if (track.enabled && track.kind !== 'main') {
50131            // The enabled track is an alternate audio track so disable the audio in
50132            // the combined source buffer and enable the audio-only source buffer.
50133            disableCombined = true;
50134            disableAudioOnly = false;
50135            break;
50136          }
50137        }
50138
50139        _this.sourceBuffers.forEach(function (sourceBuffer, index) {
50140          /* eslinst-disable */
50141          // TODO once codecs are required, we can switch to using the codecs to determine
50142          //      what stream is the video stream, rather than relying on videoTracks
50143
50144          /* eslinst-enable */
50145          sourceBuffer.appendAudioInitSegment_ = true;
50146
50147          if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
50148            // combined
50149            sourceBuffer.audioDisabled_ = disableCombined;
50150          } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) {
50151            // If the "combined" source buffer is video only, then we do not want
50152            // disable the audio-only source buffer (this is mostly for demuxed
50153            // audio and video hls)
50154            sourceBuffer.audioDisabled_ = true;
50155            disableAudioOnly = false;
50156          } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
50157            // audio only
50158            // In the case of audio only with alternate audio and disableAudioOnly is true
50159            // this means we want to disable the audio on the alternate audio sourcebuffer
50160            // but not the main "combined" source buffer. The "combined" source buffer is
50161            // always at index 0, so this ensures audio won't be disabled in both source
50162            // buffers.
50163            sourceBuffer.audioDisabled_ = index ? disableAudioOnly : !disableAudioOnly;
50164
50165            if (sourceBuffer.audioDisabled_) {
50166              return;
50167            }
50168          }
50169
50170          _this.activeSourceBuffers_.push(sourceBuffer);
50171        });
50172      };
50173
50174      _this.onPlayerMediachange_ = function () {
50175        _this.sourceBuffers.forEach(function (sourceBuffer) {
50176          sourceBuffer.appendAudioInitSegment_ = true;
50177        });
50178      };
50179
50180      _this.onHlsReset_ = function () {
50181        _this.sourceBuffers.forEach(function (sourceBuffer) {
50182          if (sourceBuffer.transmuxer_) {
50183            sourceBuffer.transmuxer_.postMessage({
50184              action: 'resetCaptions'
50185            });
50186          }
50187        });
50188      };
50189
50190      _this.onHlsSegmentTimeMapping_ = function (event) {
50191        _this.sourceBuffers.forEach(function (buffer) {
50192          return buffer.timeMapping_ = event.mapping;
50193        });
50194      }; // Re-emit MediaSource events on the polyfill
50195
50196
50197      ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) {
50198        this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this));
50199      }, _this); // capture the associated player when the MediaSource is
50200      // successfully attached
50201
50202      _this.on('sourceopen', function (event) {
50203        // Get the player this MediaSource is attached to
50204        var video = document.querySelector('[src="' + _this.url_ + '"]');
50205
50206        if (!video) {
50207          return;
50208        }
50209
50210        _this.player_ = videojs$1(video.parentNode);
50211
50212        if (!_this.player_) {
50213          return;
50214        } // hls-reset is fired by videojs.Hls on to the tech after the main SegmentLoader
50215        // resets its state and flushes the buffer
50216
50217
50218        _this.player_.tech_.on('hls-reset', _this.onHlsReset_); // hls-segment-time-mapping is fired by videojs.Hls on to the tech after the main
50219        // SegmentLoader inspects an MTS segment and has an accurate stream to display
50220        // time mapping
50221
50222
50223        _this.player_.tech_.on('hls-segment-time-mapping', _this.onHlsSegmentTimeMapping_);
50224
50225        if (_this.player_.audioTracks && _this.player_.audioTracks()) {
50226          _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_);
50227
50228          _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_);
50229
50230          _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_);
50231        }
50232
50233        _this.player_.on('mediachange', _this.onPlayerMediachange_);
50234      });
50235
50236      _this.on('sourceended', function (event) {
50237        var duration = durationOfVideo(_this.duration);
50238
50239        for (var i = 0; i < _this.sourceBuffers.length; i++) {
50240          var sourcebuffer = _this.sourceBuffers[i];
50241          var cues = sourcebuffer.metadataTrack_ && sourcebuffer.metadataTrack_.cues;
50242
50243          if (cues && cues.length) {
50244            cues[cues.length - 1].endTime = duration;
50245          }
50246        }
50247      }); // explicitly terminate any WebWorkers that were created
50248      // by SourceHandlers
50249
50250
50251      _this.on('sourceclose', function (event) {
50252        this.sourceBuffers.forEach(function (sourceBuffer) {
50253          if (sourceBuffer.transmuxer_) {
50254            sourceBuffer.transmuxer_.terminate();
50255          }
50256        });
50257        this.sourceBuffers.length = 0;
50258
50259        if (!this.player_) {
50260          return;
50261        }
50262
50263        if (this.player_.audioTracks && this.player_.audioTracks()) {
50264          this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_);
50265          this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_);
50266          this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_);
50267        } // We can only change this if the player hasn't been disposed of yet
50268        // because `off` eventually tries to use the el_ property. If it has
50269        // been disposed of, then don't worry about it because there are no
50270        // event handlers left to unbind anyway
50271
50272
50273        if (this.player_.el_) {
50274          this.player_.off('mediachange', this.onPlayerMediachange_);
50275        }
50276
50277        if (this.player_.tech_ && this.player_.tech_.el_) {
50278          this.player_.tech_.off('hls-reset', this.onHlsReset_);
50279          this.player_.tech_.off('hls-segment-time-mapping', this.onHlsSegmentTimeMapping_);
50280        }
50281      });
50282
50283      return _this;
50284    }
50285    /**
50286     * Add a range that that can now be seeked to.
50287     *
50288     * @param {Double} start where to start the addition
50289     * @param {Double} end where to end the addition
50290     * @private
50291     */
50292
50293
50294    createClass$1(HtmlMediaSource, [{
50295      key: 'addSeekableRange_',
50296      value: function addSeekableRange_(start, end) {
50297        var error = void 0;
50298
50299        if (this.duration !== Infinity) {
50300          error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity');
50301          error.name = 'InvalidStateError';
50302          error.code = 11;
50303          throw error;
50304        }
50305
50306        if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) {
50307          this.nativeMediaSource_.duration = end;
50308        }
50309      }
50310      /**
50311       * Add a source buffer to the media source.
50312       *
50313       * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer
50314       * @param {String} type the content-type of the content
50315       * @return {Object} the created source buffer
50316       */
50317
50318    }, {
50319      key: 'addSourceBuffer',
50320      value: function addSourceBuffer(type) {
50321        var buffer = void 0;
50322        var parsedType = parseContentType(type); // Create a VirtualSourceBuffer to transmux MPEG-2 transport
50323        // stream segments into fragmented MP4s
50324
50325        if (/^(video|audio)\/mp2t$/i.test(parsedType.type)) {
50326          var codecs = [];
50327
50328          if (parsedType.parameters && parsedType.parameters.codecs) {
50329            codecs = parsedType.parameters.codecs.split(',');
50330            codecs = translateLegacyCodecs(codecs);
50331            codecs = codecs.filter(function (codec) {
50332              return isAudioCodec(codec) || isVideoCodec(codec);
50333            });
50334          }
50335
50336          if (codecs.length === 0) {
50337            codecs = ['avc1.4d400d', 'mp4a.40.2'];
50338          }
50339
50340          buffer = new VirtualSourceBuffer(this, codecs);
50341
50342          if (this.sourceBuffers.length !== 0) {
50343            // If another VirtualSourceBuffer already exists, then we are creating a
50344            // SourceBuffer for an alternate audio track and therefore we know that
50345            // the source has both an audio and video track.
50346            // That means we should trigger the manual creation of the real
50347            // SourceBuffers instead of waiting for the transmuxer to return data
50348            this.sourceBuffers[0].createRealSourceBuffers_();
50349            buffer.createRealSourceBuffers_(); // Automatically disable the audio on the first source buffer if
50350            // a second source buffer is ever created
50351
50352            this.sourceBuffers[0].audioDisabled_ = true;
50353          }
50354        } else {
50355          // delegate to the native implementation
50356          buffer = this.nativeMediaSource_.addSourceBuffer(type);
50357        }
50358
50359        this.sourceBuffers.push(buffer);
50360        return buffer;
50361      }
50362    }]);
50363    return HtmlMediaSource;
50364  }(videojs$1.EventTarget);
50365  /**
50366   * @file videojs-contrib-media-sources.js
50367   */
50368
50369
50370  var urlCount = 0; // ------------
50371  // Media Source
50372  // ------------
50373  // store references to the media sources so they can be connected
50374  // to a video element (a swf object)
50375  // TODO: can we store this somewhere local to this module?
50376
50377  videojs$1.mediaSources = {};
50378  /**
50379   * Provide a method for a swf object to notify JS that a
50380   * media source is now open.
50381   *
50382   * @param {String} msObjectURL string referencing the MSE Object URL
50383   * @param {String} swfId the swf id
50384   */
50385
50386  var open = function open(msObjectURL, swfId) {
50387    var mediaSource = videojs$1.mediaSources[msObjectURL];
50388
50389    if (mediaSource) {
50390      mediaSource.trigger({
50391        type: 'sourceopen',
50392        swfId: swfId
50393      });
50394    } else {
50395      throw new Error('Media Source not found (Video.js)');
50396    }
50397  };
50398  /**
50399   * Check to see if the native MediaSource object exists and supports
50400   * an MP4 container with both H.264 video and AAC-LC audio.
50401   *
50402   * @return {Boolean} if  native media sources are supported
50403   */
50404
50405
50406  var supportsNativeMediaSources = function supportsNativeMediaSources() {
50407    return !!window$1.MediaSource && !!window$1.MediaSource.isTypeSupported && window$1.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"');
50408  };
50409  /**
50410   * An emulation of the MediaSource API so that we can support
50411   * native and non-native functionality. returns an instance of
50412   * HtmlMediaSource.
50413   *
50414   * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource
50415   */
50416
50417
50418  var MediaSource = function MediaSource() {
50419    this.MediaSource = {
50420      open: open,
50421      supportsNativeMediaSources: supportsNativeMediaSources
50422    };
50423
50424    if (supportsNativeMediaSources()) {
50425      return new HtmlMediaSource();
50426    }
50427
50428    throw new Error('Cannot use create a virtual MediaSource for this video');
50429  };
50430
50431  MediaSource.open = open;
50432  MediaSource.supportsNativeMediaSources = supportsNativeMediaSources;
50433  /**
50434   * A wrapper around the native URL for our MSE object
50435   * implementation, this object is exposed under videojs.URL
50436   *
50437   * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL
50438   */
50439
50440  var URL$1 = {
50441    /**
50442     * A wrapper around the native createObjectURL for our objects.
50443     * This function maps a native or emulated mediaSource to a blob
50444     * url so that it can be loaded into video.js
50445     *
50446     * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
50447     * @param {MediaSource} object the object to create a blob url to
50448     */
50449    createObjectURL: function createObjectURL(object) {
50450      var objectUrlPrefix = 'blob:vjs-media-source/';
50451      var url = void 0; // use the native MediaSource to generate an object URL
50452
50453      if (object instanceof HtmlMediaSource) {
50454        url = window$1.URL.createObjectURL(object.nativeMediaSource_);
50455        object.url_ = url;
50456        return url;
50457      } // if the object isn't an emulated MediaSource, delegate to the
50458      // native implementation
50459
50460
50461      if (!(object instanceof HtmlMediaSource)) {
50462        url = window$1.URL.createObjectURL(object);
50463        object.url_ = url;
50464        return url;
50465      } // build a URL that can be used to map back to the emulated
50466      // MediaSource
50467
50468
50469      url = objectUrlPrefix + urlCount;
50470      urlCount++; // setup the mapping back to object
50471
50472      videojs$1.mediaSources[url] = object;
50473      return url;
50474    }
50475  };
50476  videojs$1.MediaSource = MediaSource;
50477  videojs$1.URL = URL$1;
50478  var EventTarget$1$1 = videojs$1.EventTarget,
50479      mergeOptions$2 = videojs$1.mergeOptions;
50480  /**
50481   * Returns a new master manifest that is the result of merging an updated master manifest
50482   * into the original version.
50483   *
50484   * @param {Object} oldMaster
50485   *        The old parsed mpd object
50486   * @param {Object} newMaster
50487   *        The updated parsed mpd object
50488   * @return {Object}
50489   *         A new object representing the original master manifest with the updated media
50490   *         playlists merged in
50491   */
50492
50493  var updateMaster$1 = function updateMaster$$1(oldMaster, newMaster) {
50494    var noChanges = void 0;
50495    var update = mergeOptions$2(oldMaster, {
50496      // These are top level properties that can be updated
50497      duration: newMaster.duration,
50498      minimumUpdatePeriod: newMaster.minimumUpdatePeriod
50499    }); // First update the playlists in playlist list
50500
50501    for (var i = 0; i < newMaster.playlists.length; i++) {
50502      var playlistUpdate = updateMaster(update, newMaster.playlists[i]);
50503
50504      if (playlistUpdate) {
50505        update = playlistUpdate;
50506      } else {
50507        noChanges = true;
50508      }
50509    } // Then update media group playlists
50510
50511
50512    forEachMediaGroup(newMaster, function (properties, type, group, label) {
50513      if (properties.playlists && properties.playlists.length) {
50514        var uri = properties.playlists[0].uri;
50515
50516        var _playlistUpdate = updateMaster(update, properties.playlists[0]);
50517
50518        if (_playlistUpdate) {
50519          update = _playlistUpdate; // update the playlist reference within media groups
50520
50521          update.mediaGroups[type][group][label].playlists[0] = update.playlists[uri];
50522          noChanges = false;
50523        }
50524      }
50525    });
50526
50527    if (noChanges) {
50528      return null;
50529    }
50530
50531    return update;
50532  };
50533
50534  var DashPlaylistLoader = function (_EventTarget) {
50535    inherits$1(DashPlaylistLoader, _EventTarget); // DashPlaylistLoader must accept either a src url or a playlist because subsequent
50536    // playlist loader setups from media groups will expect to be able to pass a playlist
50537    // (since there aren't external URLs to media playlists with DASH)
50538
50539    function DashPlaylistLoader(srcUrlOrPlaylist, hls) {
50540      var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
50541      var masterPlaylistLoader = arguments[3];
50542      classCallCheck$1(this, DashPlaylistLoader);
50543
50544      var _this = possibleConstructorReturn$1(this, (DashPlaylistLoader.__proto__ || Object.getPrototypeOf(DashPlaylistLoader)).call(this));
50545
50546      var _options$withCredenti = options.withCredentials,
50547          withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti,
50548          _options$handleManife = options.handleManifestRedirects,
50549          handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife;
50550      _this.hls_ = hls;
50551      _this.withCredentials = withCredentials;
50552      _this.handleManifestRedirects = handleManifestRedirects;
50553
50554      if (!srcUrlOrPlaylist) {
50555        throw new Error('A non-empty playlist URL or playlist is required');
50556      } // event naming?
50557
50558
50559      _this.on('minimumUpdatePeriod', function () {
50560        _this.refreshXml_();
50561      }); // live playlist staleness timeout
50562
50563
50564      _this.on('mediaupdatetimeout', function () {
50565        _this.refreshMedia_();
50566      });
50567
50568      _this.state = 'HAVE_NOTHING';
50569      _this.loadedPlaylists_ = {}; // initialize the loader state
50570      // The masterPlaylistLoader will be created with a string
50571
50572      if (typeof srcUrlOrPlaylist === 'string') {
50573        _this.srcUrl = srcUrlOrPlaylist;
50574        return possibleConstructorReturn$1(_this);
50575      }
50576
50577      _this.setupChildLoader(masterPlaylistLoader, srcUrlOrPlaylist);
50578
50579      return _this;
50580    }
50581
50582    createClass$1(DashPlaylistLoader, [{
50583      key: 'setupChildLoader',
50584      value: function setupChildLoader(masterPlaylistLoader, playlist) {
50585        this.masterPlaylistLoader_ = masterPlaylistLoader;
50586        this.childPlaylist_ = playlist;
50587      }
50588    }, {
50589      key: 'dispose',
50590      value: function dispose() {
50591        this.stopRequest();
50592        this.loadedPlaylists_ = {};
50593        window$1.clearTimeout(this.mediaUpdateTimeout);
50594      }
50595    }, {
50596      key: 'hasPendingRequest',
50597      value: function hasPendingRequest() {
50598        return this.request || this.mediaRequest_;
50599      }
50600    }, {
50601      key: 'stopRequest',
50602      value: function stopRequest() {
50603        if (this.request) {
50604          var oldRequest = this.request;
50605          this.request = null;
50606          oldRequest.onreadystatechange = null;
50607          oldRequest.abort();
50608        }
50609      }
50610    }, {
50611      key: 'media',
50612      value: function media(playlist) {
50613        // getter
50614        if (!playlist) {
50615          return this.media_;
50616        } // setter
50617
50618
50619        if (this.state === 'HAVE_NOTHING') {
50620          throw new Error('Cannot switch media playlist from ' + this.state);
50621        }
50622
50623        var startingState = this.state; // find the playlist object if the target playlist has been specified by URI
50624
50625        if (typeof playlist === 'string') {
50626          if (!this.master.playlists[playlist]) {
50627            throw new Error('Unknown playlist URI: ' + playlist);
50628          }
50629
50630          playlist = this.master.playlists[playlist];
50631        }
50632
50633        var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to previously loaded playlists immediately
50634
50635        if (mediaChange && this.loadedPlaylists_[playlist.uri] && this.loadedPlaylists_[playlist.uri].endList) {
50636          this.state = 'HAVE_METADATA';
50637          this.media_ = playlist; // trigger media change if the active media has been updated
50638
50639          if (mediaChange) {
50640            this.trigger('mediachanging');
50641            this.trigger('mediachange');
50642          }
50643
50644          return;
50645        } // switching to the active playlist is a no-op
50646
50647
50648        if (!mediaChange) {
50649          return;
50650        } // switching from an already loaded playlist
50651
50652
50653        if (this.media_) {
50654          this.trigger('mediachanging');
50655        } // TODO: check for sidx here
50656        // Continue asynchronously if there is no sidx
50657        // wait one tick to allow haveMaster to run first on a child loader
50658
50659
50660        this.mediaRequest_ = window$1.setTimeout(this.haveMetadata.bind(this, {
50661          startingState: startingState,
50662          playlist: playlist
50663        }), 0);
50664      }
50665    }, {
vendor: 26,747 bytes, lines 50666-51502
50666      key: 'haveMetadata',
50667      value: function haveMetadata(_ref) {
50668        var startingState = _ref.startingState,
50669            playlist = _ref.playlist;
50670        this.state = 'HAVE_METADATA';
50671        this.media_ = playlist;
50672        this.loadedPlaylists_[playlist.uri] = playlist;
50673        this.mediaRequest_ = null; // This will trigger loadedplaylist
50674
50675        this.refreshMedia_(); // fire loadedmetadata the first time a media playlist is loaded
50676        // to resolve setup of media groups
50677
50678        if (startingState === 'HAVE_MASTER') {
50679          this.trigger('loadedmetadata');
50680        } else {
50681          // trigger media change if the active media has been updated
50682          this.trigger('mediachange');
50683        }
50684      }
50685    }, {
50686      key: 'pause',
50687      value: function pause() {
50688        this.stopRequest();
50689        window$1.clearTimeout(this.mediaUpdateTimeout);
50690
50691        if (this.state === 'HAVE_NOTHING') {
50692          // If we pause the loader before any data has been retrieved, its as if we never
50693          // started, so reset to an unstarted state.
50694          this.started = false;
50695        }
50696      }
50697    }, {
50698      key: 'load',
50699      value: function load(isFinalRendition) {
50700        var _this2 = this;
50701
50702        window$1.clearTimeout(this.mediaUpdateTimeout);
50703        var media = this.media();
50704
50705        if (isFinalRendition) {
50706          var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
50707          this.mediaUpdateTimeout = window$1.setTimeout(function () {
50708            return _this2.load();
50709          }, delay);
50710          return;
50711        } // because the playlists are internal to the manifest, load should either load the
50712        // main manifest, or do nothing but trigger an event
50713
50714
50715        if (!this.started) {
50716          this.start();
50717          return;
50718        }
50719
50720        this.trigger('loadedplaylist');
50721      }
50722      /**
50723       * Parses the master xml string and updates playlist uri references
50724       *
50725       * @return {Object}
50726       *         The parsed mpd manifest object
50727       */
50728
50729    }, {
50730      key: 'parseMasterXml',
50731      value: function parseMasterXml() {
50732        var master = parse(this.masterXml_, {
50733          manifestUri: this.srcUrl,
50734          clientOffset: this.clientOffset_
50735        });
50736        master.uri = this.srcUrl; // Set up phony URIs for the playlists since we won't have external URIs for DASH
50737        // but reference playlists by their URI throughout the project
50738        // TODO: Should we create the dummy uris in mpd-parser as well (leaning towards yes).
50739
50740        for (var i = 0; i < master.playlists.length; i++) {
50741          var phonyUri = 'placeholder-uri-' + i;
50742          master.playlists[i].uri = phonyUri; // set up by URI references
50743
50744          master.playlists[phonyUri] = master.playlists[i];
50745        } // set up phony URIs for the media group playlists since we won't have external
50746        // URIs for DASH but reference playlists by their URI throughout the project
50747
50748
50749        forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
50750          if (properties.playlists && properties.playlists.length) {
50751            var _phonyUri = 'placeholder-uri-' + mediaType + '-' + groupKey + '-' + labelKey;
50752
50753            properties.playlists[0].uri = _phonyUri; // setup URI references
50754
50755            master.playlists[_phonyUri] = properties.playlists[0];
50756          }
50757        });
50758        setupMediaPlaylists(master);
50759        resolveMediaGroupUris(master);
50760        return master;
50761      }
50762    }, {
50763      key: 'start',
50764      value: function start() {
50765        var _this3 = this;
50766
50767        this.started = true; // We don't need to request the master manifest again
50768        // Call this asynchronously to match the xhr request behavior below
50769
50770        if (this.masterPlaylistLoader_) {
50771          this.mediaRequest_ = window$1.setTimeout(this.haveMaster_.bind(this), 0);
50772          return;
50773        } // request the specified URL
50774
50775
50776        this.request = this.hls_.xhr({
50777          uri: this.srcUrl,
50778          withCredentials: this.withCredentials
50779        }, function (error, req) {
50780          // disposed
50781          if (!_this3.request) {
50782            return;
50783          } // clear the loader's request reference
50784
50785
50786          _this3.request = null;
50787
50788          if (error) {
50789            _this3.error = {
50790              status: req.status,
50791              message: 'DASH playlist request error at URL: ' + _this3.srcUrl,
50792              responseText: req.responseText,
50793              // MEDIA_ERR_NETWORK
50794              code: 2
50795            };
50796
50797            if (_this3.state === 'HAVE_NOTHING') {
50798              _this3.started = false;
50799            }
50800
50801            return _this3.trigger('error');
50802          }
50803
50804          _this3.masterXml_ = req.responseText;
50805
50806          if (req.responseHeaders && req.responseHeaders.date) {
50807            _this3.masterLoaded_ = Date.parse(req.responseHeaders.date);
50808          } else {
50809            _this3.masterLoaded_ = Date.now();
50810          }
50811
50812          _this3.srcUrl = resolveManifestRedirect(_this3.handleManifestRedirects, _this3.srcUrl, req);
50813
50814          _this3.syncClientServerClock_(_this3.onClientServerClockSync_.bind(_this3));
50815        });
50816      }
50817      /**
50818       * Parses the master xml for UTCTiming node to sync the client clock to the server
50819       * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
50820       *
50821       * @param {Function} done
50822       *        Function to call when clock sync has completed
50823       */
50824
50825    }, {
50826      key: 'syncClientServerClock_',
50827      value: function syncClientServerClock_(done) {
50828        var _this4 = this;
50829
50830        var utcTiming = parseUTCTiming(this.masterXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the
50831        // server clock
50832
50833        if (utcTiming === null) {
50834          this.clientOffset_ = this.masterLoaded_ - Date.now();
50835          return done();
50836        }
50837
50838        if (utcTiming.method === 'DIRECT') {
50839          this.clientOffset_ = utcTiming.value - Date.now();
50840          return done();
50841        }
50842
50843        this.request = this.hls_.xhr({
50844          uri: resolveUrl$1(this.srcUrl, utcTiming.value),
50845          method: utcTiming.method,
50846          withCredentials: this.withCredentials
50847        }, function (error, req) {
50848          // disposed
50849          if (!_this4.request) {
50850            return;
50851          }
50852
50853          if (error) {
50854            // sync request failed, fall back to using date header from mpd
50855            // TODO: log warning
50856            _this4.clientOffset_ = _this4.masterLoaded_ - Date.now();
50857            return done();
50858          }
50859
50860          var serverTime = void 0;
50861
50862          if (utcTiming.method === 'HEAD') {
50863            if (!req.responseHeaders || !req.responseHeaders.date) {
50864              // expected date header not preset, fall back to using date header from mpd
50865              // TODO: log warning
50866              serverTime = _this4.masterLoaded_;
50867            } else {
50868              serverTime = Date.parse(req.responseHeaders.date);
50869            }
50870          } else {
50871            serverTime = Date.parse(req.responseText);
50872          }
50873
50874          _this4.clientOffset_ = serverTime - Date.now();
50875          done();
50876        });
50877      }
50878    }, {
50879      key: 'haveMaster_',
50880      value: function haveMaster_() {
50881        this.state = 'HAVE_MASTER'; // clear media request
50882
50883        this.mediaRequest_ = null;
50884
50885        if (!this.masterPlaylistLoader_) {
50886          this.master = this.parseMasterXml(); // We have the master playlist at this point, so
50887          // trigger this to allow MasterPlaylistController
50888          // to make an initial playlist selection
50889
50890          this.trigger('loadedplaylist');
50891        } else if (!this.media_) {
50892          // no media playlist was specifically selected so select
50893          // the one the child playlist loader was created with
50894          this.media(this.childPlaylist_);
50895        }
50896      }
50897      /**
50898       * Handler for after client/server clock synchronization has happened. Sets up
50899       * xml refresh timer if specificed by the manifest.
50900       */
50901
50902    }, {
50903      key: 'onClientServerClockSync_',
50904      value: function onClientServerClockSync_() {
50905        var _this5 = this;
50906
50907        this.haveMaster_();
50908
50909        if (!this.hasPendingRequest() && !this.media_) {
50910          this.media(this.master.playlists[0]);
50911        } // TODO: minimumUpdatePeriod can have a value of 0. Currently the manifest will not
50912        // be refreshed when this is the case. The inter-op guide says that when the
50913        // minimumUpdatePeriod is 0, the manifest should outline all currently available
50914        // segments, but future segments may require an update. I think a good solution
50915        // would be to update the manifest at the same rate that the media playlists
50916        // are "refreshed", i.e. every targetDuration.
50917
50918
50919        if (this.master && this.master.minimumUpdatePeriod) {
50920          window$1.setTimeout(function () {
50921            _this5.trigger('minimumUpdatePeriod');
50922          }, this.master.minimumUpdatePeriod);
50923        }
50924      }
50925      /**
50926       * Sends request to refresh the master xml and updates the parsed master manifest
50927       * TODO: Does the client offset need to be recalculated when the xml is refreshed?
50928       */
50929
50930    }, {
50931      key: 'refreshXml_',
50932      value: function refreshXml_() {
50933        var _this6 = this; // The srcUrl here *may* need to pass through handleManifestsRedirects when
50934        // sidx is implemented
50935
50936
50937        this.request = this.hls_.xhr({
50938          uri: this.srcUrl,
50939          withCredentials: this.withCredentials
50940        }, function (error, req) {
50941          // disposed
50942          if (!_this6.request) {
50943            return;
50944          } // clear the loader's request reference
50945
50946
50947          _this6.request = null;
50948
50949          if (error) {
50950            _this6.error = {
50951              status: req.status,
50952              message: 'DASH playlist request error at URL: ' + _this6.srcUrl,
50953              responseText: req.responseText,
50954              // MEDIA_ERR_NETWORK
50955              code: 2
50956            };
50957
50958            if (_this6.state === 'HAVE_NOTHING') {
50959              _this6.started = false;
50960            }
50961
50962            return _this6.trigger('error');
50963          }
50964
50965          _this6.masterXml_ = req.responseText;
50966
50967          var newMaster = _this6.parseMasterXml();
50968
50969          var updatedMaster = updateMaster$1(_this6.master, newMaster);
50970
50971          if (updatedMaster) {
50972            _this6.master = updatedMaster;
50973          }
50974
50975          window$1.setTimeout(function () {
50976            _this6.trigger('minimumUpdatePeriod');
50977          }, _this6.master.minimumUpdatePeriod);
50978        });
50979      }
50980      /**
50981       * Refreshes the media playlist by re-parsing the master xml and updating playlist
50982       * references. If this is an alternate loader, the updated parsed manifest is retrieved
50983       * from the master loader.
50984       */
50985
50986    }, {
50987      key: 'refreshMedia_',
50988      value: function refreshMedia_() {
50989        var _this7 = this;
50990
50991        var oldMaster = void 0;
50992        var newMaster = void 0;
50993
50994        if (this.masterPlaylistLoader_) {
50995          oldMaster = this.masterPlaylistLoader_.master;
50996          newMaster = this.masterPlaylistLoader_.parseMasterXml();
50997        } else {
50998          oldMaster = this.master;
50999          newMaster = this.parseMasterXml();
51000        }
51001
51002        var updatedMaster = updateMaster$1(oldMaster, newMaster);
51003
51004        if (updatedMaster) {
51005          if (this.masterPlaylistLoader_) {
51006            this.masterPlaylistLoader_.master = updatedMaster;
51007          } else {
51008            this.master = updatedMaster;
51009          }
51010
51011          this.media_ = updatedMaster.playlists[this.media_.uri];
51012        } else {
51013          this.trigger('playlistunchanged');
51014        }
51015
51016        if (!this.media().endList) {
51017          this.mediaUpdateTimeout = window$1.setTimeout(function () {
51018            _this7.trigger('mediaupdatetimeout');
51019          }, refreshDelay(this.media(), !!updatedMaster));
51020        }
51021
51022        this.trigger('loadedplaylist');
51023      }
51024    }]);
51025    return DashPlaylistLoader;
51026  }(EventTarget$1$1);
51027
51028  var logger = function logger(source) {
51029    if (videojs$1.log.debug) {
51030      return videojs$1.log.debug.bind(videojs$1, 'VHS:', source + ' >');
51031    }
51032
51033    return function () {};
51034  };
51035
51036  function noop$1() {}
51037  /**
51038   * @file source-updater.js
51039   */
51040
51041  /**
51042   * A queue of callbacks to be serialized and applied when a
51043   * MediaSource and its associated SourceBuffers are not in the
51044   * updating state. It is used by the segment loader to update the
51045   * underlying SourceBuffers when new data is loaded, for instance.
51046   *
51047   * @class SourceUpdater
51048   * @param {MediaSource} mediaSource the MediaSource to create the
51049   * SourceBuffer from
51050   * @param {String} mimeType the desired MIME type of the underlying
51051   * SourceBuffer
51052   * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer is
51053   * added to the media source
51054   */
51055
51056
51057  var SourceUpdater = function () {
51058    function SourceUpdater(mediaSource, mimeType, type, sourceBufferEmitter) {
51059      classCallCheck$1(this, SourceUpdater);
51060      this.callbacks_ = [];
51061      this.pendingCallback_ = null;
51062      this.timestampOffset_ = 0;
51063      this.mediaSource = mediaSource;
51064      this.processedAppend_ = false;
51065      this.type_ = type;
51066      this.mimeType_ = mimeType;
51067      this.logger_ = logger('SourceUpdater[' + type + '][' + mimeType + ']');
51068
51069      if (mediaSource.readyState === 'closed') {
51070        mediaSource.addEventListener('sourceopen', this.createSourceBuffer_.bind(this, mimeType, sourceBufferEmitter));
51071      } else {
51072        this.createSourceBuffer_(mimeType, sourceBufferEmitter);
51073      }
51074    }
51075
51076    createClass$1(SourceUpdater, [{
51077      key: 'createSourceBuffer_',
51078      value: function createSourceBuffer_(mimeType, sourceBufferEmitter) {
51079        var _this = this;
51080
51081        this.sourceBuffer_ = this.mediaSource.addSourceBuffer(mimeType);
51082        this.logger_('created SourceBuffer');
51083
51084        if (sourceBufferEmitter) {
51085          sourceBufferEmitter.trigger('sourcebufferadded');
51086
51087          if (this.mediaSource.sourceBuffers.length < 2) {
51088            // There's another source buffer we must wait for before we can start updating
51089            // our own (or else we can get into a bad state, i.e., appending video/audio data
51090            // before the other video/audio source buffer is available and leading to a video
51091            // or audio only buffer).
51092            sourceBufferEmitter.on('sourcebufferadded', function () {
51093              _this.start_();
51094            });
51095            return;
51096          }
51097        }
51098
51099        this.start_();
51100      }
51101    }, {
51102      key: 'start_',
51103      value: function start_() {
51104        var _this2 = this;
51105
51106        this.started_ = true; // run completion handlers and process callbacks as updateend
51107        // events fire
51108
51109        this.onUpdateendCallback_ = function () {
51110          var pendingCallback = _this2.pendingCallback_;
51111          _this2.pendingCallback_ = null;
51112
51113          _this2.logger_('buffered [' + printableRange(_this2.buffered()) + ']');
51114
51115          if (pendingCallback) {
51116            pendingCallback();
51117          }
51118
51119          _this2.runCallback_();
51120        };
51121
51122        this.sourceBuffer_.addEventListener('updateend', this.onUpdateendCallback_);
51123        this.runCallback_();
51124      }
51125      /**
51126       * Aborts the current segment and resets the segment parser.
51127       *
51128       * @param {Function} done function to call when done
51129       * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void
51130       */
51131
51132    }, {
51133      key: 'abort',
51134      value: function abort(done) {
51135        var _this3 = this;
51136
51137        if (this.processedAppend_) {
51138          this.queueCallback_(function () {
51139            _this3.sourceBuffer_.abort();
51140          }, done);
51141        }
51142      }
51143      /**
51144       * Queue an update to append an ArrayBuffer.
51145       *
51146       * @param {ArrayBuffer} bytes
51147       * @param {Function} done the function to call when done
51148       * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
51149       */
51150
51151    }, {
51152      key: 'appendBuffer',
51153      value: function appendBuffer(config, done) {
51154        var _this4 = this;
51155
51156        this.processedAppend_ = true;
51157        this.queueCallback_(function () {
51158          if (config.videoSegmentTimingInfoCallback) {
51159            _this4.sourceBuffer_.addEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback);
51160          }
51161
51162          _this4.sourceBuffer_.appendBuffer(config.bytes);
51163        }, function () {
51164          if (config.videoSegmentTimingInfoCallback) {
51165            _this4.sourceBuffer_.removeEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback);
51166          }
51167
51168          done();
51169        });
51170      }
51171      /**
51172       * Indicates what TimeRanges are buffered in the managed SourceBuffer.
51173       *
51174       * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered
51175       */
51176
51177    }, {
51178      key: 'buffered',
51179      value: function buffered() {
51180        if (!this.sourceBuffer_) {
51181          return videojs$1.createTimeRanges();
51182        }
51183
51184        return this.sourceBuffer_.buffered;
51185      }
51186      /**
51187       * Queue an update to remove a time range from the buffer.
51188       *
51189       * @param {Number} start where to start the removal
51190       * @param {Number} end where to end the removal
51191       * @param {Function} [done=noop] optional callback to be executed when the remove
51192       * operation is complete
51193       * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
51194       */
51195
51196    }, {
51197      key: 'remove',
51198      value: function remove(start, end) {
51199        var _this5 = this;
51200
51201        var done = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : noop$1;
51202
51203        if (this.processedAppend_) {
51204          this.queueCallback_(function () {
51205            _this5.logger_('remove [' + start + ' => ' + end + ']');
51206
51207            _this5.sourceBuffer_.remove(start, end);
51208          }, done);
51209        }
51210      }
51211      /**
51212       * Whether the underlying sourceBuffer is updating or not
51213       *
51214       * @return {Boolean} the updating status of the SourceBuffer
51215       */
51216
51217    }, {
51218      key: 'updating',
51219      value: function updating() {
51220        // we are updating if the sourcebuffer is updating or
51221        return !this.sourceBuffer_ || this.sourceBuffer_.updating || // if we have a pending callback that is not our internal noop
51222        !!this.pendingCallback_ && this.pendingCallback_ !== noop$1;
51223      }
51224      /**
51225       * Set/get the timestampoffset on the SourceBuffer
51226       *
51227       * @return {Number} the timestamp offset
51228       */
51229
51230    }, {
51231      key: 'timestampOffset',
51232      value: function timestampOffset(offset) {
51233        var _this6 = this;
51234
51235        if (typeof offset !== 'undefined') {
51236          this.queueCallback_(function () {
51237            _this6.sourceBuffer_.timestampOffset = offset;
51238          });
51239          this.timestampOffset_ = offset;
51240        }
51241
51242        return this.timestampOffset_;
51243      }
51244      /**
51245       * Queue a callback to run
51246       */
51247
51248    }, {
51249      key: 'queueCallback_',
51250      value: function queueCallback_(callback, done) {
51251        this.callbacks_.push([callback.bind(this), done]);
51252        this.runCallback_();
51253      }
51254      /**
51255       * Run a queued callback
51256       */
51257
51258    }, {
51259      key: 'runCallback_',
51260      value: function runCallback_() {
51261        var callbacks = void 0;
51262
51263        if (!this.updating() && this.callbacks_.length && this.started_) {
51264          callbacks = this.callbacks_.shift();
51265          this.pendingCallback_ = callbacks[1];
51266          callbacks[0]();
51267        }
51268      }
51269      /**
51270       * dispose of the source updater and the underlying sourceBuffer
51271       */
51272
51273    }, {
51274      key: 'dispose',
51275      value: function dispose() {
51276        this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_);
51277
51278        if (this.sourceBuffer_ && this.mediaSource.readyState === 'open') {
51279          this.sourceBuffer_.abort();
51280        }
51281      }
51282    }]);
51283    return SourceUpdater;
51284  }();
51285
51286  var Config = {
51287    GOAL_BUFFER_LENGTH: 30,
51288    MAX_GOAL_BUFFER_LENGTH: 60,
51289    GOAL_BUFFER_LENGTH_RATE: 1,
51290    // 0.5 MB/s
51291    INITIAL_BANDWIDTH: 4194304,
51292    // A fudge factor to apply to advertised playlist bitrates to account for
51293    // temporary flucations in client bandwidth
51294    BANDWIDTH_VARIANCE: 1.2,
51295    // How much of the buffer must be filled before we consider upswitching
51296    BUFFER_LOW_WATER_LINE: 0,
51297    MAX_BUFFER_LOW_WATER_LINE: 30,
51298    BUFFER_LOW_WATER_LINE_RATE: 1
51299  };
51300  var REQUEST_ERRORS = {
51301    FAILURE: 2,
51302    TIMEOUT: -101,
51303    ABORTED: -102
51304  };
51305  /**
51306   * Turns segment byterange into a string suitable for use in
51307   * HTTP Range requests
51308   *
51309   * @param {Object} byterange - an object with two values defining the start and end
51310   *                             of a byte-range
51311   */
51312
51313  var byterangeStr = function byterangeStr(byterange) {
51314    var byterangeStart = void 0;
51315    var byterangeEnd = void 0; // `byterangeEnd` is one less than `offset + length` because the HTTP range
51316    // header uses inclusive ranges
51317
51318    byterangeEnd = byterange.offset + byterange.length - 1;
51319    byterangeStart = byterange.offset;
51320    return 'bytes=' + byterangeStart + '-' + byterangeEnd;
51321  };
51322  /**
51323   * Defines headers for use in the xhr request for a particular segment.
51324   *
51325   * @param {Object} segment - a simplified copy of the segmentInfo object
51326   *                           from SegmentLoader
51327   */
51328
51329
51330  var segmentXhrHeaders = function segmentXhrHeaders(segment) {
51331    var headers = {};
51332
51333    if (segment.byterange) {
51334      headers.Range = byterangeStr(segment.byterange);
51335    }
51336
51337    return headers;
51338  };
51339  /**
51340   * Abort all requests
51341   *
51342   * @param {Object} activeXhrs - an object that tracks all XHR requests
51343   */
51344
51345
51346  var abortAll = function abortAll(activeXhrs) {
51347    activeXhrs.forEach(function (xhr) {
51348      xhr.abort();
51349    });
51350  };
51351  /**
51352   * Gather important bandwidth stats once a request has completed
51353   *
51354   * @param {Object} request - the XHR request from which to gather stats
51355   */
51356
51357
51358  var getRequestStats = function getRequestStats(request) {
51359    return {
51360      bandwidth: request.bandwidth,
51361      bytesReceived: request.bytesReceived || 0,
51362      roundTripTime: request.roundTripTime || 0
51363    };
51364  };
51365  /**
51366   * If possible gather bandwidth stats as a request is in
51367   * progress
51368   *
51369   * @param {Event} progressEvent - an event object from an XHR's progress event
51370   */
51371
51372
51373  var getProgressStats = function getProgressStats(progressEvent) {
51374    var request = progressEvent.target;
51375    var roundTripTime = Date.now() - request.requestTime;
51376    var stats = {
51377      bandwidth: Infinity,
51378      bytesReceived: 0,
51379      roundTripTime: roundTripTime || 0
51380    };
51381    stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok
51382    // because we should only use bandwidth stats on progress to determine when
51383    // abort a request early due to insufficient bandwidth
51384
51385    stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000);
51386    return stats;
51387  };
51388  /**
51389   * Handle all error conditions in one place and return an object
51390   * with all the information
51391   *
51392   * @param {Error|null} error - if non-null signals an error occured with the XHR
51393   * @param {Object} request -  the XHR request that possibly generated the error
51394   */
51395
51396
51397  var handleErrors = function handleErrors(error, request) {
51398    if (request.timedout) {
51399      return {
51400        status: request.status,
51401        message: 'HLS request timed-out at URL: ' + request.uri,
51402        code: REQUEST_ERRORS.TIMEOUT,
51403        xhr: request
51404      };
51405    }
51406
51407    if (request.aborted) {
51408      return {
51409        status: request.status,
51410        message: 'HLS request aborted at URL: ' + request.uri,
51411        code: REQUEST_ERRORS.ABORTED,
51412        xhr: request
51413      };
51414    }
51415
51416    if (error) {
51417      return {
51418        status: request.status,
51419        message: 'HLS request errored at URL: ' + request.uri,
51420        code: REQUEST_ERRORS.FAILURE,
51421        xhr: request
51422      };
51423    }
51424
51425    return null;
51426  };
51427  /**
51428   * Handle responses for key data and convert the key data to the correct format
51429   * for the decryption step later
51430   *
51431   * @param {Object} segment - a simplified copy of the segmentInfo object
51432   *                           from SegmentLoader
51433   * @param {Function} finishProcessingFn - a callback to execute to continue processing
51434   *                                        this request
51435   */
51436
51437
51438  var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) {
51439    return function (error, request) {
51440      var response = request.response;
51441      var errorObj = handleErrors(error, request);
51442
51443      if (errorObj) {
51444        return finishProcessingFn(errorObj, segment);
51445      }
51446
51447      if (response.byteLength !== 16) {
51448        return finishProcessingFn({
51449          status: request.status,
51450          message: 'Invalid HLS key at URL: ' + request.uri,
51451          code: REQUEST_ERRORS.FAILURE,
51452          xhr: request
51453        }, segment);
51454      }
51455
51456      var view = new DataView(response);
51457      segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
51458      return finishProcessingFn(null, segment);
51459    };
51460  };
51461  /**
51462   * Handle init-segment responses
51463   *
51464   * @param {Object} segment - a simplified copy of the segmentInfo object
51465   *                           from SegmentLoader
51466   * @param {Function} finishProcessingFn - a callback to execute to continue processing
51467   *                                        this request
51468   */
51469
51470
51471  var handleInitSegmentResponse = function handleInitSegmentResponse(segment, captionParser, finishProcessingFn) {
51472    return function (error, request) {
51473      var response = request.response;
51474      var errorObj = handleErrors(error, request);
51475
51476      if (errorObj) {
51477        return finishProcessingFn(errorObj, segment);
51478      } // stop processing if received empty content
51479
51480
51481      if (response.byteLength === 0) {
51482        return finishProcessingFn({
51483          status: request.status,
51484          message: 'Empty HLS segment content at URL: ' + request.uri,
51485          code: REQUEST_ERRORS.FAILURE,
51486          xhr: request
51487        }, segment);
51488      }
51489
51490      segment.map.bytes = new Uint8Array(request.response); // Initialize CaptionParser if it hasn't been yet
51491
51492      if (!captionParser.isInitialized()) {
51493        captionParser.init();
51494      }
51495
51496      segment.map.timescales = probe.timescale(segment.map.bytes);
51497      segment.map.videoTrackIds = probe.videoTrackIds(segment.map.bytes);
51498      return finishProcessingFn(null, segment);
51499    };
51500  };
51501  /**
51502   * Response handler for segment-requests being sure to set the correct
51503   * property depending on whether the segment is encryped or not
51504   * Also records and keeps track of stats that are used for ABR purposes
51505   *
51506   * @param {Object} segment - a simplified copy of the segmentInfo object
51507   *                           from SegmentLoader
51508   * @param {Function} finishProcessingFn - a callback to execute to continue processing
51509   *                                        this request
51510   */
51511
51512
51513  var handleSegmentResponse = function handleSegmentResponse(segment, captionParser, finishProcessingFn) {
51514    return function (error, request) {
51515      var response = request.response;
51516      var errorObj = handleErrors(error, request);
51517      var parsed = void 0;
51518
51519      if (errorObj) {
51520        return finishProcessingFn(errorObj, segment);
51521      } // stop processing if received empty content
51522
51523
51524      if (response.byteLength === 0) {
51525        return finishProcessingFn({
51526          status: request.status,
51527          message: 'Empty HLS segment content at URL: ' + request.uri,
51528          code: REQUEST_ERRORS.FAILURE,
51529          xhr: request
51530        }, segment);
51531      }
51532
51533      segment.stats = getRequestStats(request);
51534
51535      if (segment.key) {
51536        segment.encryptedBytes = new Uint8Array(request.response);
51537      } else {
51538        segment.bytes = new Uint8Array(request.response);
51539      } // This is likely an FMP4 and has the init segment.
51540      // Run through the CaptionParser in case there are captions.
51541
51542
51543      if (segment.map && segment.map.bytes) {
51544        // Initialize CaptionParser if it hasn't been yet
51545        if (!captionParser.isInitialized()) {
51546          captionParser.init();
51547        }
51548
51549        parsed = captionParser.parse(segment.bytes, segment.map.videoTrackIds, segment.map.timescales);
51550
51551        if (parsed && parsed.captions) {
51552          segment.captionStreams = parsed.captionStreams;
51553          segment.fmp4Captions = parsed.captions;
51554        }
51555      }
51556
51557      return finishProcessingFn(null, segment);
51558    };
51559  };
51560  /**
51561   * Decrypt the segment via the decryption web worker
51562   *
51563   * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
51564   * @param {Object} segment - a simplified copy of the segmentInfo object
51565   *                           from SegmentLoader
51566   * @param {Function} doneFn - a callback that is executed after decryption has completed
51567   */
51568
51569
51570  var decryptSegment = function decryptSegment(decrypter, segment, doneFn) {
51571    var decryptionHandler = function decryptionHandler(event) {
51572      if (event.data.source === segment.requestId) {
51573        decrypter.removeEventListener('message', decryptionHandler);
51574        var decrypted = event.data.decrypted;
51575        segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength);
51576        return doneFn(null, segment);
51577      }
51578    };
51579
51580    decrypter.addEventListener('message', decryptionHandler); // this is an encrypted segment
51581    // incrementally decrypt the segment
51582
51583    decrypter.postMessage(createTransferableMessage({
51584      source: segment.requestId,
51585      encrypted: segment.encryptedBytes,
51586      key: segment.key.bytes,
51587      iv: segment.key.iv
51588    }), [segment.encryptedBytes.buffer, segment.key.bytes.buffer]);
51589  };
51590  /**
51591   * This function waits for all XHRs to finish (with either success or failure)
51592   * before continueing processing via it's callback. The function gathers errors
51593   * from each request into a single errors array so that the error status for
51594   * each request can be examined later.
51595   *
51596   * @param {Object} activeXhrs - an object that tracks all XHR requests
51597   * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
51598   * @param {Function} doneFn - a callback that is executed after all resources have been
51599   *                            downloaded and any decryption completed
51600   */
51601
51602
51603  var waitForCompletion = function waitForCompletion(activeXhrs, decrypter, doneFn) {
51604    var count = 0;
51605    var didError = false;
51606    return function (error, segment) {
51607      if (didError) {
51608        return;
51609      }
51610
51611      if (error) {
51612        didError = true; // If there are errors, we have to abort any outstanding requests
51613
51614        abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we
51615        // handle the aborted events from those requests, there are some cases where we may
51616        // never get an aborted event. For instance, if the network connection is lost and
vendor: 203,709 bytes, lines 51617-57234
51617        // there were two requests, the first may have triggered an error immediately, while
51618        // the second request remains unsent. In that case, the aborted algorithm will not
51619        // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method
51620        //
51621        // We also can't rely on the ready state of the XHR, since the request that
51622        // triggered the connection error may also show as a ready state of 0 (unsent).
51623        // Therefore, we have to finish this group of requests immediately after the first
51624        // seen error.
51625
51626        return doneFn(error, segment);
51627      }
51628
51629      count += 1;
51630
51631      if (count === activeXhrs.length) {
51632        // Keep track of when *all* of the requests have completed
51633        segment.endOfAllRequests = Date.now();
51634
51635        if (segment.encryptedBytes) {
51636          return decryptSegment(decrypter, segment, doneFn);
51637        } // Otherwise, everything is ready just continue
51638
51639
51640        return doneFn(null, segment);
51641      }
51642    };
51643  };
51644  /**
51645   * Simple progress event callback handler that gathers some stats before
51646   * executing a provided callback with the `segment` object
51647   *
51648   * @param {Object} segment - a simplified copy of the segmentInfo object
51649   *                           from SegmentLoader
51650   * @param {Function} progressFn - a callback that is executed each time a progress event
51651   *                                is received
51652   * @param {Event} event - the progress event object from XMLHttpRequest
51653   */
51654
51655
51656  var handleProgress = function handleProgress(segment, progressFn) {
51657    return function (event) {
51658      segment.stats = videojs$1.mergeOptions(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data
51659
51660      if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
51661        segment.stats.firstBytesReceivedAt = Date.now();
51662      }
51663
51664      return progressFn(event, segment);
51665    };
51666  };
51667  /**
51668   * Load all resources and does any processing necessary for a media-segment
51669   *
51670   * Features:
51671   *   decrypts the media-segment if it has a key uri and an iv
51672   *   aborts *all* requests if *any* one request fails
51673   *
51674   * The segment object, at minimum, has the following format:
51675   * {
51676   *   resolvedUri: String,
51677   *   [byterange]: {
51678   *     offset: Number,
51679   *     length: Number
51680   *   },
51681   *   [key]: {
51682   *     resolvedUri: String
51683   *     [byterange]: {
51684   *       offset: Number,
51685   *       length: Number
51686   *     },
51687   *     iv: {
51688   *       bytes: Uint32Array
51689   *     }
51690   *   },
51691   *   [map]: {
51692   *     resolvedUri: String,
51693   *     [byterange]: {
51694   *       offset: Number,
51695   *       length: Number
51696   *     },
51697   *     [bytes]: Uint8Array
51698   *   }
51699   * }
51700   * ...where [name] denotes optional properties
51701   *
51702   * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
51703   * @param {Object} xhrOptions - the base options to provide to all xhr requests
51704   * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
51705   *                                       decryption routines
51706   * @param {Object} segment - a simplified copy of the segmentInfo object
51707   *                           from SegmentLoader
51708   * @param {Function} progressFn - a callback that receives progress events from the main
51709   *                                segment's xhr request
51710   * @param {Function} doneFn - a callback that is executed only once all requests have
51711   *                            succeeded or failed
51712   * @returns {Function} a function that, when invoked, immediately aborts all
51713   *                     outstanding requests
51714   */
51715
51716
51717  var mediaSegmentRequest = function mediaSegmentRequest(xhr, xhrOptions, decryptionWorker, captionParser, segment, progressFn, doneFn) {
51718    var activeXhrs = [];
51719    var finishProcessingFn = waitForCompletion(activeXhrs, decryptionWorker, doneFn); // optionally, request the decryption key
51720
51721    if (segment.key) {
51722      var keyRequestOptions = videojs$1.mergeOptions(xhrOptions, {
51723        uri: segment.key.resolvedUri,
51724        responseType: 'arraybuffer'
51725      });
51726      var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn);
51727      var keyXhr = xhr(keyRequestOptions, keyRequestCallback);
51728      activeXhrs.push(keyXhr);
51729    } // optionally, request the associated media init segment
51730
51731
51732    if (segment.map && !segment.map.bytes) {
51733      var initSegmentOptions = videojs$1.mergeOptions(xhrOptions, {
51734        uri: segment.map.resolvedUri,
51735        responseType: 'arraybuffer',
51736        headers: segmentXhrHeaders(segment.map)
51737      });
51738      var initSegmentRequestCallback = handleInitSegmentResponse(segment, captionParser, finishProcessingFn);
51739      var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback);
51740      activeXhrs.push(initSegmentXhr);
51741    }
51742
51743    var segmentRequestOptions = videojs$1.mergeOptions(xhrOptions, {
51744      uri: segment.resolvedUri,
51745      responseType: 'arraybuffer',
51746      headers: segmentXhrHeaders(segment)
51747    });
51748    var segmentRequestCallback = handleSegmentResponse(segment, captionParser, finishProcessingFn);
51749    var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback);
51750    segmentXhr.addEventListener('progress', handleProgress(segment, progressFn));
51751    activeXhrs.push(segmentXhr);
51752    return function () {
51753      return abortAll(activeXhrs);
51754    };
51755  }; // Utilities
51756
51757  /**
51758   * Returns the CSS value for the specified property on an element
51759   * using `getComputedStyle`. Firefox has a long-standing issue where
51760   * getComputedStyle() may return null when running in an iframe with
51761   * `display: none`.
51762   *
51763   * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
51764   * @param {HTMLElement} el the htmlelement to work on
51765   * @param {string} the proprety to get the style for
51766   */
51767
51768
51769  var safeGetComputedStyle = function safeGetComputedStyle(el, property) {
51770    var result = void 0;
51771
51772    if (!el) {
51773      return '';
51774    }
51775
51776    result = window$1.getComputedStyle(el);
51777
51778    if (!result) {
51779      return '';
51780    }
51781
51782    return result[property];
51783  };
51784  /**
51785   * Resuable stable sort function
51786   *
51787   * @param {Playlists} array
51788   * @param {Function} sortFn Different comparators
51789   * @function stableSort
51790   */
51791
51792
51793  var stableSort = function stableSort(array, sortFn) {
51794    var newArray = array.slice();
51795    array.sort(function (left, right) {
51796      var cmp = sortFn(left, right);
51797
51798      if (cmp === 0) {
51799        return newArray.indexOf(left) - newArray.indexOf(right);
51800      }
51801
51802      return cmp;
51803    });
51804  };
51805  /**
51806   * A comparator function to sort two playlist object by bandwidth.
51807   *
51808   * @param {Object} left a media playlist object
51809   * @param {Object} right a media playlist object
51810   * @return {Number} Greater than zero if the bandwidth attribute of
51811   * left is greater than the corresponding attribute of right. Less
51812   * than zero if the bandwidth of right is greater than left and
51813   * exactly zero if the two are equal.
51814   */
51815
51816
51817  var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) {
51818    var leftBandwidth = void 0;
51819    var rightBandwidth = void 0;
51820
51821    if (left.attributes.BANDWIDTH) {
51822      leftBandwidth = left.attributes.BANDWIDTH;
51823    }
51824
51825    leftBandwidth = leftBandwidth || window$1.Number.MAX_VALUE;
51826
51827    if (right.attributes.BANDWIDTH) {
51828      rightBandwidth = right.attributes.BANDWIDTH;
51829    }
51830
51831    rightBandwidth = rightBandwidth || window$1.Number.MAX_VALUE;
51832    return leftBandwidth - rightBandwidth;
51833  };
51834  /**
51835   * A comparator function to sort two playlist object by resolution (width).
51836   * @param {Object} left a media playlist object
51837   * @param {Object} right a media playlist object
51838   * @return {Number} Greater than zero if the resolution.width attribute of
51839   * left is greater than the corresponding attribute of right. Less
51840   * than zero if the resolution.width of right is greater than left and
51841   * exactly zero if the two are equal.
51842   */
51843
51844
51845  var comparePlaylistResolution = function comparePlaylistResolution(left, right) {
51846    var leftWidth = void 0;
51847    var rightWidth = void 0;
51848
51849    if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
51850      leftWidth = left.attributes.RESOLUTION.width;
51851    }
51852
51853    leftWidth = leftWidth || window$1.Number.MAX_VALUE;
51854
51855    if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
51856      rightWidth = right.attributes.RESOLUTION.width;
51857    }
51858
51859    rightWidth = rightWidth || window$1.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
51860    // have the same media dimensions/ resolution
51861
51862    if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
51863      return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
51864    }
51865
51866    return leftWidth - rightWidth;
51867  };
51868  /**
51869   * Chooses the appropriate media playlist based on bandwidth and player size
51870   *
51871   * @param {Object} master
51872   *        Object representation of the master manifest
51873   * @param {Number} playerBandwidth
51874   *        Current calculated bandwidth of the player
51875   * @param {Number} playerWidth
51876   *        Current width of the player element
51877   * @param {Number} playerHeight
51878   *        Current height of the player element
51879   * @param {Boolean} limitRenditionByPlayerDimensions
51880   *        True if the player width and height should be used during the selection, false otherwise
51881   * @return {Playlist} the highest bitrate playlist less than the
51882   * currently detected bandwidth, accounting for some amount of
51883   * bandwidth variance
51884   */
51885
51886
51887  var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions) {
51888    // convert the playlists to an intermediary representation to make comparisons easier
51889    var sortedPlaylistReps = master.playlists.map(function (playlist) {
51890      var width = void 0;
51891      var height = void 0;
51892      var bandwidth = void 0;
51893      width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
51894      height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
51895      bandwidth = playlist.attributes.BANDWIDTH;
51896      bandwidth = bandwidth || window$1.Number.MAX_VALUE;
51897      return {
51898        bandwidth: bandwidth,
51899        width: width,
51900        height: height,
51901        playlist: playlist
51902      };
51903    });
51904    stableSort(sortedPlaylistReps, function (left, right) {
51905      return left.bandwidth - right.bandwidth;
51906    }); // filter out any playlists that have been excluded due to
51907    // incompatible configurations
51908
51909    sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) {
51910      return !Playlist.isIncompatible(rep.playlist);
51911    }); // filter out any playlists that have been disabled manually through the representations
51912    // api or blacklisted temporarily due to playback errors.
51913
51914    var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
51915      return Playlist.isEnabled(rep.playlist);
51916    });
51917
51918    if (!enabledPlaylistReps.length) {
51919      // if there are no enabled playlists, then they have all been blacklisted or disabled
51920      // by the user through the representations api. In this case, ignore blacklisting and
51921      // fallback to what the user wants by using playlists the user has not disabled.
51922      enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
51923        return !Playlist.isDisabled(rep.playlist);
51924      });
51925    } // filter out any variant that has greater effective bitrate
51926    // than the current estimated bandwidth
51927
51928
51929    var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) {
51930      return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth;
51931    });
51932    var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth
51933    // and then taking the very first element
51934
51935    var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) {
51936      return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
51937    })[0]; // if we're not going to limit renditions by player size, make an early decision.
51938
51939    if (limitRenditionByPlayerDimensions === false) {
51940      var _chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
51941
51942      return _chosenRep ? _chosenRep.playlist : null;
51943    } // filter out playlists without resolution information
51944
51945
51946    var haveResolution = bandwidthPlaylistReps.filter(function (rep) {
51947      return rep.width && rep.height;
51948    }); // sort variants by resolution
51949
51950    stableSort(haveResolution, function (left, right) {
51951      return left.width - right.width;
51952    }); // if we have the exact resolution as the player use it
51953
51954    var resolutionBestRepList = haveResolution.filter(function (rep) {
51955      return rep.width === playerWidth && rep.height === playerHeight;
51956    });
51957    highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution
51958
51959    var resolutionBestRep = resolutionBestRepList.filter(function (rep) {
51960      return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
51961    })[0];
51962    var resolutionPlusOneList = void 0;
51963    var resolutionPlusOneSmallest = void 0;
51964    var resolutionPlusOneRep = void 0; // find the smallest variant that is larger than the player
51965    // if there is no match of exact resolution
51966
51967    if (!resolutionBestRep) {
51968      resolutionPlusOneList = haveResolution.filter(function (rep) {
51969        return rep.width > playerWidth || rep.height > playerHeight;
51970      }); // find all the variants have the same smallest resolution
51971
51972      resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) {
51973        return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height;
51974      }); // ensure that we also pick the highest bandwidth variant that
51975      // is just-larger-than the video player
51976
51977      highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
51978      resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) {
51979        return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
51980      })[0];
51981    } // fallback chain of variants
51982
51983
51984    var chosenRep = resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
51985    return chosenRep ? chosenRep.playlist : null;
51986  }; // Playlist Selectors
51987
51988  /**
51989   * Chooses the appropriate media playlist based on the most recent
51990   * bandwidth estimate and the player size.
51991   *
51992   * Expects to be called within the context of an instance of HlsHandler
51993   *
51994   * @return {Playlist} the highest bitrate playlist less than the
51995   * currently detected bandwidth, accounting for some amount of
51996   * bandwidth variance
51997   */
51998
51999
52000  var lastBandwidthSelector = function lastBandwidthSelector() {
52001    return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10), this.limitRenditionByPlayerDimensions);
52002  };
52003  /**
52004   * Chooses the appropriate media playlist based on the potential to rebuffer
52005   *
52006   * @param {Object} settings
52007   *        Object of information required to use this selector
52008   * @param {Object} settings.master
52009   *        Object representation of the master manifest
52010   * @param {Number} settings.currentTime
52011   *        The current time of the player
52012   * @param {Number} settings.bandwidth
52013   *        Current measured bandwidth
52014   * @param {Number} settings.duration
52015   *        Duration of the media
52016   * @param {Number} settings.segmentDuration
52017   *        Segment duration to be used in round trip time calculations
52018   * @param {Number} settings.timeUntilRebuffer
52019   *        Time left in seconds until the player has to rebuffer
52020   * @param {Number} settings.currentTimeline
52021   *        The current timeline segments are being loaded from
52022   * @param {SyncController} settings.syncController
52023   *        SyncController for determining if we have a sync point for a given playlist
52024   * @return {Object|null}
52025   *         {Object} return.playlist
52026   *         The highest bandwidth playlist with the least amount of rebuffering
52027   *         {Number} return.rebufferingImpact
52028   *         The amount of time in seconds switching to this playlist will rebuffer. A
52029   *         negative value means that switching will cause zero rebuffering.
52030   */
52031
52032
52033  var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) {
52034    var master = settings.master,
52035        currentTime = settings.currentTime,
52036        bandwidth = settings.bandwidth,
52037        duration$$1 = settings.duration,
52038        segmentDuration = settings.segmentDuration,
52039        timeUntilRebuffer = settings.timeUntilRebuffer,
52040        currentTimeline = settings.currentTimeline,
52041        syncController = settings.syncController; // filter out any playlists that have been excluded due to
52042    // incompatible configurations
52043
52044    var compatiblePlaylists = master.playlists.filter(function (playlist) {
52045      return !Playlist.isIncompatible(playlist);
52046    }); // filter out any playlists that have been disabled manually through the representations
52047    // api or blacklisted temporarily due to playback errors.
52048
52049    var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
52050
52051    if (!enabledPlaylists.length) {
52052      // if there are no enabled playlists, then they have all been blacklisted or disabled
52053      // by the user through the representations api. In this case, ignore blacklisting and
52054      // fallback to what the user wants by using playlists the user has not disabled.
52055      enabledPlaylists = compatiblePlaylists.filter(function (playlist) {
52056        return !Playlist.isDisabled(playlist);
52057      });
52058    }
52059
52060    var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
52061    var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) {
52062      var syncPoint = syncController.getSyncPoint(playlist, duration$$1, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a
52063      // sync request first. This will double the request time
52064
52065      var numRequests = syncPoint ? 1 : 2;
52066      var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
52067      var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
52068      return {
52069        playlist: playlist,
52070        rebufferingImpact: rebufferingImpact
52071      };
52072    });
52073    var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) {
52074      return estimate.rebufferingImpact <= 0;
52075    }); // Sort by bandwidth DESC
52076
52077    stableSort(noRebufferingPlaylists, function (a, b) {
52078      return comparePlaylistBandwidth(b.playlist, a.playlist);
52079    });
52080
52081    if (noRebufferingPlaylists.length) {
52082      return noRebufferingPlaylists[0];
52083    }
52084
52085    stableSort(rebufferingEstimates, function (a, b) {
52086      return a.rebufferingImpact - b.rebufferingImpact;
52087    });
52088    return rebufferingEstimates[0] || null;
52089  };
52090  /**
52091   * Chooses the appropriate media playlist, which in this case is the lowest bitrate
52092   * one with video.  If no renditions with video exist, return the lowest audio rendition.
52093   *
52094   * Expects to be called within the context of an instance of HlsHandler
52095   *
52096   * @return {Object|null}
52097   *         {Object} return.playlist
52098   *         The lowest bitrate playlist that contains a video codec.  If no such rendition
52099   *         exists pick the lowest audio rendition.
52100   */
52101
52102
52103  var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() {
52104    // filter out any playlists that have been excluded due to
52105    // incompatible configurations or playback errors
52106    var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate
52107
52108    stableSort(playlists, function (a, b) {
52109      return comparePlaylistBandwidth(a, b);
52110    }); // Parse and assume that playlists with no video codec have no video
52111    // (this is not necessarily true, although it is generally true).
52112    //
52113    // If an entire manifest has no valid videos everything will get filtered
52114    // out.
52115
52116    var playlistsWithVideo = playlists.filter(function (playlist) {
52117      return parseCodecs(playlist.attributes.CODECS).videoCodec;
52118    });
52119    return playlistsWithVideo[0] || null;
52120  };
52121  /**
52122   * Create captions text tracks on video.js if they do not exist
52123   *
52124   * @param {Object} inbandTextTracks a reference to current inbandTextTracks
52125   * @param {Object} tech the video.js tech
52126   * @param {Object} captionStreams the caption streams to create
52127   * @private
52128   */
52129
52130
52131  var createCaptionsTrackIfNotExists = function createCaptionsTrackIfNotExists(inbandTextTracks, tech, captionStreams) {
52132    for (var trackId in captionStreams) {
52133      if (!inbandTextTracks[trackId]) {
52134        tech.trigger({
52135          type: 'usage',
52136          name: 'hls-608'
52137        });
52138        var track = tech.textTracks().getTrackById(trackId);
52139
52140        if (track) {
52141          // Resuse an existing track with a CC# id because this was
52142          // very likely created by videojs-contrib-hls from information
52143          // in the m3u8 for us to use
52144          inbandTextTracks[trackId] = track;
52145        } else {
52146          // Otherwise, create a track with the default `CC#` label and
52147          // without a language
52148          inbandTextTracks[trackId] = tech.addRemoteTextTrack({
52149            kind: 'captions',
52150            id: trackId,
52151            label: trackId
52152          }, false).track;
52153        }
52154      }
52155    }
52156  };
52157
52158  var addCaptionData = function addCaptionData(_ref) {
52159    var inbandTextTracks = _ref.inbandTextTracks,
52160        captionArray = _ref.captionArray,
52161        timestampOffset = _ref.timestampOffset;
52162
52163    if (!captionArray) {
52164      return;
52165    }
52166
52167    var Cue = window.WebKitDataCue || window.VTTCue;
52168    captionArray.forEach(function (caption) {
52169      var track = caption.stream;
52170      var startTime = caption.startTime;
52171      var endTime = caption.endTime;
52172
52173      if (!inbandTextTracks[track]) {
52174        return;
52175      }
52176
52177      startTime += timestampOffset;
52178      endTime += timestampOffset;
52179      inbandTextTracks[track].addCue(new Cue(startTime, endTime, caption.text));
52180    });
52181  };
52182  /**
52183   * @file segment-loader.js
52184   */
52185  // in ms
52186
52187
52188  var CHECK_BUFFER_DELAY = 500;
52189  /**
52190   * Determines if we should call endOfStream on the media source based
52191   * on the state of the buffer or if appened segment was the final
52192   * segment in the playlist.
52193   *
52194   * @param {Object} playlist a media playlist object
52195   * @param {Object} mediaSource the MediaSource object
52196   * @param {Number} segmentIndex the index of segment we last appended
52197   * @returns {Boolean} do we need to call endOfStream on the MediaSource
52198   */
52199
52200  var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) {
52201    if (!playlist || !mediaSource) {
52202      return false;
52203    }
52204
52205    var segments = playlist.segments; // determine a few boolean values to help make the branch below easier
52206    // to read
52207
52208    var appendedLastSegment = segmentIndex === segments.length; // if we've buffered to the end of the video, we need to call endOfStream
52209    // so that MediaSources can trigger the `ended` event when it runs out of
52210    // buffered data instead of waiting for me
52211
52212    return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment;
52213  };
52214
52215  var finite = function finite(num) {
52216    return typeof num === 'number' && isFinite(num);
52217  };
52218
52219  var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, newSegmentMedia) {
52220    // Although these checks should most likely cover non 'main' types, for now it narrows
52221    // the scope of our checks.
52222    if (loaderType !== 'main' || !startingMedia || !newSegmentMedia) {
52223      return null;
52224    }
52225
52226    if (!newSegmentMedia.containsAudio && !newSegmentMedia.containsVideo) {
52227      return 'Neither audio nor video found in segment.';
52228    }
52229
52230    if (startingMedia.containsVideo && !newSegmentMedia.containsVideo) {
52231      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.';
52232    }
52233
52234    if (!startingMedia.containsVideo && newSegmentMedia.containsVideo) {
52235      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.';
52236    }
52237
52238    return null;
52239  };
52240  /**
52241   * Calculates a time value that is safe to remove from the back buffer without interupting
52242   * playback.
52243   *
52244   * @param {TimeRange} seekable
52245   *        The current seekable range
52246   * @param {Number} currentTime
52247   *        The current time of the player
52248   * @param {Number} targetDuration
52249   *        The target duration of the current playlist
52250   * @return {Number}
52251   *         Time that is safe to remove from the back buffer without interupting playback
52252   */
52253
52254
52255  var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable$$1, currentTime, targetDuration) {
52256    var removeToTime = void 0;
52257
52258    if (seekable$$1.length && seekable$$1.start(0) > 0 && seekable$$1.start(0) < currentTime) {
52259      // If we have a seekable range use that as the limit for what can be removed safely
52260      removeToTime = seekable$$1.start(0);
52261    } else {
52262      // otherwise remove anything older than 30 seconds before the current play head
52263      removeToTime = currentTime - 30;
52264    } // Don't allow removing from the buffer within target duration of current time
52265    // to avoid the possibility of removing the GOP currently being played which could
52266    // cause playback stalls.
52267
52268
52269    return Math.min(removeToTime, currentTime - targetDuration);
52270  };
52271
52272  var segmentInfoString = function segmentInfoString(segmentInfo) {
52273    var _segmentInfo$segment = segmentInfo.segment,
52274        start = _segmentInfo$segment.start,
52275        end = _segmentInfo$segment.end,
52276        _segmentInfo$playlist = segmentInfo.playlist,
52277        seq = _segmentInfo$playlist.mediaSequence,
52278        id = _segmentInfo$playlist.id,
52279        _segmentInfo$playlist2 = _segmentInfo$playlist.segments,
52280        segments = _segmentInfo$playlist2 === undefined ? [] : _segmentInfo$playlist2,
52281        index = segmentInfo.mediaIndex,
52282        timeline = segmentInfo.timeline;
52283    return ['appending [' + index + '] of [' + seq + ', ' + (seq + segments.length) + '] from playlist [' + id + ']', '[' + start + ' => ' + end + '] in timeline [' + timeline + ']'].join(' ');
52284  };
52285  /**
52286   * An object that manages segment loading and appending.
52287   *
52288   * @class SegmentLoader
52289   * @param {Object} options required and optional options
52290   * @extends videojs.EventTarget
52291   */
52292
52293
52294  var SegmentLoader = function (_videojs$EventTarget) {
52295    inherits$1(SegmentLoader, _videojs$EventTarget);
52296
52297    function SegmentLoader(settings) {
52298      classCallCheck$1(this, SegmentLoader); // check pre-conditions
52299
52300      var _this = possibleConstructorReturn$1(this, (SegmentLoader.__proto__ || Object.getPrototypeOf(SegmentLoader)).call(this));
52301
52302      if (!settings) {
52303        throw new TypeError('Initialization settings are required');
52304      }
52305
52306      if (typeof settings.currentTime !== 'function') {
52307        throw new TypeError('No currentTime getter specified');
52308      }
52309
52310      if (!settings.mediaSource) {
52311        throw new TypeError('No MediaSource specified');
52312      } // public properties
52313
52314
52315      _this.bandwidth = settings.bandwidth;
52316      _this.throughput = {
52317        rate: 0,
52318        count: 0
52319      };
52320      _this.roundTrip = NaN;
52321
52322      _this.resetStats_();
52323
52324      _this.mediaIndex = null; // private settings
52325
52326      _this.hasPlayed_ = settings.hasPlayed;
52327      _this.currentTime_ = settings.currentTime;
52328      _this.seekable_ = settings.seekable;
52329      _this.seeking_ = settings.seeking;
52330      _this.duration_ = settings.duration;
52331      _this.mediaSource_ = settings.mediaSource;
52332      _this.hls_ = settings.hls;
52333      _this.loaderType_ = settings.loaderType;
52334      _this.startingMedia_ = void 0;
52335      _this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
52336      _this.goalBufferLength_ = settings.goalBufferLength;
52337      _this.sourceType_ = settings.sourceType;
52338      _this.inbandTextTracks_ = settings.inbandTextTracks;
52339      _this.state_ = 'INIT'; // private instance variables
52340
52341      _this.checkBufferTimeout_ = null;
52342      _this.error_ = void 0;
52343      _this.currentTimeline_ = -1;
52344      _this.pendingSegment_ = null;
52345      _this.mimeType_ = null;
52346      _this.sourceUpdater_ = null;
52347      _this.xhrOptions_ = null; // Fragmented mp4 playback
52348
52349      _this.activeInitSegmentId_ = null;
52350      _this.initSegments_ = {}; // Fmp4 CaptionParser
52351
52352      _this.captionParser_ = new mp4_6();
52353      _this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings
52354      // between a time in the display time and a segment index within
52355      // a playlist
52356
52357      _this.syncController_ = settings.syncController;
52358      _this.syncPoint_ = {
52359        segmentIndex: 0,
52360        time: 0
52361      };
52362
52363      _this.syncController_.on('syncinfoupdate', function () {
52364        return _this.trigger('syncinfoupdate');
52365      });
52366
52367      _this.mediaSource_.addEventListener('sourceopen', function () {
52368        return _this.ended_ = false;
52369      }); // ...for determining the fetch location
52370
52371
52372      _this.fetchAtBuffer_ = false;
52373      _this.logger_ = logger('SegmentLoader[' + _this.loaderType_ + ']');
52374      Object.defineProperty(_this, 'state', {
52375        get: function get$$1() {
52376          return this.state_;
52377        },
52378        set: function set$$1(newState) {
52379          if (newState !== this.state_) {
52380            this.logger_(this.state_ + ' -> ' + newState);
52381            this.state_ = newState;
52382          }
52383        }
52384      });
52385      return _this;
52386    }
52387    /**
52388     * reset all of our media stats
52389     *
52390     * @private
52391     */
52392
52393
52394    createClass$1(SegmentLoader, [{
52395      key: 'resetStats_',
52396      value: function resetStats_() {
52397        this.mediaBytesTransferred = 0;
52398        this.mediaRequests = 0;
52399        this.mediaRequestsAborted = 0;
52400        this.mediaRequestsTimedout = 0;
52401        this.mediaRequestsErrored = 0;
52402        this.mediaTransferDuration = 0;
52403        this.mediaSecondsLoaded = 0;
52404      }
52405      /**
52406       * dispose of the SegmentLoader and reset to the default state
52407       */
52408
52409    }, {
52410      key: 'dispose',
52411      value: function dispose() {
52412        this.state = 'DISPOSED';
52413        this.pause();
52414        this.abort_();
52415
52416        if (this.sourceUpdater_) {
52417          this.sourceUpdater_.dispose();
52418        }
52419
52420        this.resetStats_();
52421        this.captionParser_.reset();
52422      }
52423      /**
52424       * abort anything that is currently doing on with the SegmentLoader
52425       * and reset to a default state
52426       */
52427
52428    }, {
52429      key: 'abort',
52430      value: function abort() {
52431        if (this.state !== 'WAITING') {
52432          if (this.pendingSegment_) {
52433            this.pendingSegment_ = null;
52434          }
52435
52436          return;
52437        }
52438
52439        this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY
52440        // since we are no longer "waiting" on any requests. XHR callback is not always run
52441        // when the request is aborted. This will prevent the loader from being stuck in the
52442        // WAITING state indefinitely.
52443
52444        this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the
52445        // next segment
52446
52447        if (!this.paused()) {
52448          this.monitorBuffer_();
52449        }
52450      }
52451      /**
52452       * abort all pending xhr requests and null any pending segements
52453       *
52454       * @private
52455       */
52456
52457    }, {
52458      key: 'abort_',
52459      value: function abort_() {
52460        if (this.pendingSegment_) {
52461          this.pendingSegment_.abortRequests();
52462        } // clear out the segment being processed
52463
52464
52465        this.pendingSegment_ = null;
52466      }
52467      /**
52468       * set an error on the segment loader and null out any pending segements
52469       *
52470       * @param {Error} error the error to set on the SegmentLoader
52471       * @return {Error} the error that was set or that is currently set
52472       */
52473
52474    }, {
52475      key: 'error',
52476      value: function error(_error) {
52477        if (typeof _error !== 'undefined') {
52478          this.error_ = _error;
52479        }
52480
52481        this.pendingSegment_ = null;
52482        return this.error_;
52483      }
52484    }, {
52485      key: 'endOfStream',
52486      value: function endOfStream() {
52487        this.ended_ = true;
52488        this.pause();
52489        this.trigger('ended');
52490      }
52491      /**
52492       * Indicates which time ranges are buffered
52493       *
52494       * @return {TimeRange}
52495       *         TimeRange object representing the current buffered ranges
52496       */
52497
52498    }, {
52499      key: 'buffered_',
52500      value: function buffered_() {
52501        if (!this.sourceUpdater_) {
52502          return videojs$1.createTimeRanges();
52503        }
52504
52505        return this.sourceUpdater_.buffered();
52506      }
52507      /**
52508       * Gets and sets init segment for the provided map
52509       *
52510       * @param {Object} map
52511       *        The map object representing the init segment to get or set
52512       * @param {Boolean=} set
52513       *        If true, the init segment for the provided map should be saved
52514       * @return {Object}
52515       *         map object for desired init segment
52516       */
52517
52518    }, {
52519      key: 'initSegment',
52520      value: function initSegment(map) {
52521        var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
52522
52523        if (!map) {
52524          return null;
52525        }
52526
52527        var id = initSegmentId(map);
52528        var storedMap = this.initSegments_[id];
52529
52530        if (set$$1 && !storedMap && map.bytes) {
52531          this.initSegments_[id] = storedMap = {
52532            resolvedUri: map.resolvedUri,
52533            byterange: map.byterange,
52534            bytes: map.bytes,
52535            timescales: map.timescales,
52536            videoTrackIds: map.videoTrackIds
52537          };
52538        }
52539
52540        return storedMap || map;
52541      }
52542      /**
52543       * Returns true if all configuration required for loading is present, otherwise false.
52544       *
52545       * @return {Boolean} True if the all configuration is ready for loading
52546       * @private
52547       */
52548
52549    }, {
52550      key: 'couldBeginLoading_',
52551      value: function couldBeginLoading_() {
52552        return this.playlist_ && ( // the source updater is created when init_ is called, so either having a
52553        // source updater or being in the INIT state with a mimeType is enough
52554        // to say we have all the needed configuration to start loading.
52555        this.sourceUpdater_ || this.mimeType_ && this.state === 'INIT') && !this.paused();
52556      }
52557      /**
52558       * load a playlist and start to fill the buffer
52559       */
52560
52561    }, {
52562      key: 'load',
52563      value: function load() {
52564        // un-pause
52565        this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be
52566        // specified
52567
52568        if (!this.playlist_) {
52569          return;
52570        } // not sure if this is the best place for this
52571
52572
52573        this.syncController_.setDateTimeMapping(this.playlist_); // if all the configuration is ready, initialize and begin loading
52574
52575        if (this.state === 'INIT' && this.couldBeginLoading_()) {
52576          return this.init_();
52577        } // if we're in the middle of processing a segment already, don't
52578        // kick off an additional segment request
52579
52580
52581        if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
52582          return;
52583        }
52584
52585        this.state = 'READY';
52586      }
52587      /**
52588       * Once all the starting parameters have been specified, begin
52589       * operation. This method should only be invoked from the INIT
52590       * state.
52591       *
52592       * @private
52593       */
52594
52595    }, {
52596      key: 'init_',
52597      value: function init_() {
52598        this.state = 'READY';
52599        this.sourceUpdater_ = new SourceUpdater(this.mediaSource_, this.mimeType_, this.loaderType_, this.sourceBufferEmitter_);
52600        this.resetEverything();
52601        return this.monitorBuffer_();
52602      }
52603      /**
52604       * set a playlist on the segment loader
52605       *
52606       * @param {PlaylistLoader} media the playlist to set on the segment loader
52607       */
52608
52609    }, {
52610      key: 'playlist',
52611      value: function playlist(newPlaylist) {
52612        var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
52613
52614        if (!newPlaylist) {
52615          return;
52616        }
52617
52618        var oldPlaylist = this.playlist_;
52619        var segmentInfo = this.pendingSegment_;
52620        this.playlist_ = newPlaylist;
52621        this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist
52622        // is always our zero-time so force a sync update each time the playlist
52623        // is refreshed from the server
52624
52625        if (!this.hasPlayed_()) {
52626          newPlaylist.syncInfo = {
52627            mediaSequence: newPlaylist.mediaSequence,
52628            time: 0
52629          };
52630        }
52631
52632        var oldId = null;
52633
52634        if (oldPlaylist) {
52635          if (oldPlaylist.id) {
52636            oldId = oldPlaylist.id;
52637          } else if (oldPlaylist.uri) {
52638            oldId = oldPlaylist.uri;
52639          }
52640        }
52641
52642        this.logger_('playlist update [' + oldId + ' => ' + (newPlaylist.id || newPlaylist.uri) + ']'); // in VOD, this is always a rendition switch (or we updated our syncInfo above)
52643        // in LIVE, we always want to update with new playlists (including refreshes)
52644
52645        this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start
52646        // buffering now
52647
52648        if (this.state === 'INIT' && this.couldBeginLoading_()) {
52649          return this.init_();
52650        }
52651
52652        if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
52653          if (this.mediaIndex !== null) {
52654            // we must "resync" the segment loader when we switch renditions and
52655            // the segment loader is already synced to the previous rendition
52656            this.resyncLoader();
52657          } // the rest of this function depends on `oldPlaylist` being defined
52658
52659
52660          return;
52661        } // we reloaded the same playlist so we are in a live scenario
52662        // and we will likely need to adjust the mediaIndex
52663
52664
52665        var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
52666        this.logger_('live window shift [' + mediaSequenceDiff + ']'); // update the mediaIndex on the SegmentLoader
52667        // this is important because we can abort a request and this value must be
52668        // equal to the last appended mediaIndex
52669
52670        if (this.mediaIndex !== null) {
52671          this.mediaIndex -= mediaSequenceDiff;
52672        } // update the mediaIndex on the SegmentInfo object
52673        // this is important because we will update this.mediaIndex with this value
52674        // in `handleUpdateEnd_` after the segment has been successfully appended
52675
52676
52677        if (segmentInfo) {
52678          segmentInfo.mediaIndex -= mediaSequenceDiff; // we need to update the referenced segment so that timing information is
52679          // saved for the new playlist's segment, however, if the segment fell off the
52680          // playlist, we can leave the old reference and just lose the timing info
52681
52682          if (segmentInfo.mediaIndex >= 0) {
52683            segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
52684          }
52685        }
52686
52687        this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
52688      }
52689      /**
52690       * Prevent the loader from fetching additional segments. If there
52691       * is a segment request outstanding, it will finish processing
52692       * before the loader halts. A segment loader can be unpaused by
52693       * calling load().
52694       */
52695
52696    }, {
52697      key: 'pause',
52698      value: function pause() {
52699        if (this.checkBufferTimeout_) {
52700          window$1.clearTimeout(this.checkBufferTimeout_);
52701          this.checkBufferTimeout_ = null;
52702        }
52703      }
52704      /**
52705       * Returns whether the segment loader is fetching additional
52706       * segments when given the opportunity. This property can be
52707       * modified through calls to pause() and load().
52708       */
52709
52710    }, {
52711      key: 'paused',
52712      value: function paused() {
52713        return this.checkBufferTimeout_ === null;
52714      }
52715      /**
52716       * create/set the following mimetype on the SourceBuffer through a
52717       * SourceUpdater
52718       *
52719       * @param {String} mimeType the mime type string to use
52720       * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer
52721       * is added to the media source
52722       */
52723
52724    }, {
52725      key: 'mimeType',
52726      value: function mimeType(_mimeType, sourceBufferEmitter) {
52727        if (this.mimeType_) {
52728          return;
52729        }
52730
52731        this.mimeType_ = _mimeType;
52732        this.sourceBufferEmitter_ = sourceBufferEmitter; // if we were unpaused but waiting for a sourceUpdater, start
52733        // buffering now
52734
52735        if (this.state === 'INIT' && this.couldBeginLoading_()) {
52736          this.init_();
52737        }
52738      }
52739      /**
52740       * Delete all the buffered data and reset the SegmentLoader
52741       * @param {Function} [done] an optional callback to be executed when the remove
52742       * operation is complete
52743       */
52744
52745    }, {
52746      key: 'resetEverything',
52747      value: function resetEverything(done) {
52748        this.ended_ = false;
52749        this.resetLoader();
52750        this.remove(0, this.duration_(), done); // clears fmp4 captions
52751
52752        this.captionParser_.clearAllCaptions();
52753        this.trigger('reseteverything');
52754      }
52755      /**
52756       * Force the SegmentLoader to resync and start loading around the currentTime instead
52757       * of starting at the end of the buffer
52758       *
52759       * Useful for fast quality changes
52760       */
52761
52762    }, {
52763      key: 'resetLoader',
52764      value: function resetLoader() {
52765        this.fetchAtBuffer_ = false;
52766        this.resyncLoader();
52767      }
52768      /**
52769       * Force the SegmentLoader to restart synchronization and make a conservative guess
52770       * before returning to the simple walk-forward method
52771       */
52772
52773    }, {
52774      key: 'resyncLoader',
52775      value: function resyncLoader() {
52776        this.mediaIndex = null;
52777        this.syncPoint_ = null;
52778        this.abort();
52779      }
52780      /**
52781       * Remove any data in the source buffer between start and end times
52782       * @param {Number} start - the start time of the region to remove from the buffer
52783       * @param {Number} end - the end time of the region to remove from the buffer
52784       * @param {Function} [done] - an optional callback to be executed when the remove
52785       * operation is complete
52786       */
52787
52788    }, {
52789      key: 'remove',
52790      value: function remove(start, end, done) {
52791        if (this.sourceUpdater_) {
52792          this.sourceUpdater_.remove(start, end, done);
52793        }
52794
52795        removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
52796
52797        if (this.inbandTextTracks_) {
52798          for (var id in this.inbandTextTracks_) {
52799            removeCuesFromTrack(start, end, this.inbandTextTracks_[id]);
52800          }
52801        }
52802      }
52803      /**
52804       * (re-)schedule monitorBufferTick_ to run as soon as possible
52805       *
52806       * @private
52807       */
52808
52809    }, {
52810      key: 'monitorBuffer_',
52811      value: function monitorBuffer_() {
52812        if (this.checkBufferTimeout_) {
52813          window$1.clearTimeout(this.checkBufferTimeout_);
52814        }
52815
52816        this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), 1);
52817      }
52818      /**
52819       * As long as the SegmentLoader is in the READY state, periodically
52820       * invoke fillBuffer_().
52821       *
52822       * @private
52823       */
52824
52825    }, {
52826      key: 'monitorBufferTick_',
52827      value: function monitorBufferTick_() {
52828        if (this.state === 'READY') {
52829          this.fillBuffer_();
52830        }
52831
52832        if (this.checkBufferTimeout_) {
52833          window$1.clearTimeout(this.checkBufferTimeout_);
52834        }
52835
52836        this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
52837      }
52838      /**
52839       * fill the buffer with segements unless the sourceBuffers are
52840       * currently updating
52841       *
52842       * Note: this function should only ever be called by monitorBuffer_
52843       * and never directly
52844       *
52845       * @private
52846       */
52847
52848    }, {
52849      key: 'fillBuffer_',
52850      value: function fillBuffer_() {
52851        if (this.sourceUpdater_.updating()) {
52852          return;
52853        }
52854
52855        if (!this.syncPoint_) {
52856          this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
52857        } // see if we need to begin loading immediately
52858
52859
52860        var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
52861
52862        if (!segmentInfo) {
52863          return;
52864        }
52865
52866        if (this.isEndOfStream_(segmentInfo.mediaIndex)) {
52867          this.endOfStream();
52868          return;
52869        }
52870
52871        if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) {
52872          return;
52873        } // We will need to change timestampOffset of the sourceBuffer if either of
52874        // the following conditions are true:
52875        // - The segment.timeline !== this.currentTimeline
52876        //   (we are crossing a discontinuity somehow)
52877        // - The "timestampOffset" for the start of this segment is less than
52878        //   the currently set timestampOffset
52879        // Also, clear captions if we are crossing a discontinuity boundary
52880
52881
52882        if (segmentInfo.timeline !== this.currentTimeline_ || segmentInfo.startOfSegment !== null && segmentInfo.startOfSegment < this.sourceUpdater_.timestampOffset()) {
52883          this.syncController_.reset();
52884          segmentInfo.timestampOffset = segmentInfo.startOfSegment;
52885          this.captionParser_.clearAllCaptions();
52886        }
52887
52888        this.loadSegment_(segmentInfo);
52889      }
52890      /**
52891       * Determines if this segment loader is at the end of it's stream.
52892       *
52893       * @param {Number} mediaIndex the index of segment we last appended
52894       * @param {Object} [playlist=this.playlist_] a media playlist object
52895       * @returns {Boolean} true if at end of stream, false otherwise.
52896       */
52897
52898    }, {
52899      key: 'isEndOfStream_',
52900      value: function isEndOfStream_(mediaIndex) {
52901        var playlist = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.playlist_;
52902        return detectEndOfStream(playlist, this.mediaSource_, mediaIndex) && !this.sourceUpdater_.updating();
52903      }
52904      /**
52905       * Determines what segment request should be made, given current playback
52906       * state.
52907       *
52908       * @param {TimeRanges} buffered - the state of the buffer
52909       * @param {Object} playlist - the playlist object to fetch segments from
52910       * @param {Number} mediaIndex - the previous mediaIndex fetched or null
52911       * @param {Boolean} hasPlayed - a flag indicating whether we have played or not
52912       * @param {Number} currentTime - the playback position in seconds
52913       * @param {Object} syncPoint - a segment info object that describes the
52914       * @returns {Object} a segment request object that describes the segment to load
52915       */
52916
52917    }, {
52918      key: 'checkBuffer_',
52919      value: function checkBuffer_(buffered, playlist, mediaIndex, hasPlayed, currentTime, syncPoint) {
52920        var lastBufferedEnd = 0;
52921        var startOfSegment = void 0;
52922
52923        if (buffered.length) {
52924          lastBufferedEnd = buffered.end(buffered.length - 1);
52925        }
52926
52927        var bufferedTime = Math.max(0, lastBufferedEnd - currentTime);
52928
52929        if (!playlist.segments.length) {
52930          return null;
52931        } // if there is plenty of content buffered, and the video has
52932        // been played before relax for awhile
52933
52934
52935        if (bufferedTime >= this.goalBufferLength_()) {
52936          return null;
52937        } // if the video has not yet played once, and we already have
52938        // one segment downloaded do nothing
52939
52940
52941        if (!hasPlayed && bufferedTime >= 1) {
52942          return null;
52943        } // When the syncPoint is null, there is no way of determining a good
52944        // conservative segment index to fetch from
52945        // The best thing to do here is to get the kind of sync-point data by
52946        // making a request
52947
52948
52949        if (syncPoint === null) {
52950          mediaIndex = this.getSyncSegmentCandidate_(playlist);
52951          return this.generateSegmentInfo_(playlist, mediaIndex, null, true);
52952        } // Under normal playback conditions fetching is a simple walk forward
52953
52954
52955        if (mediaIndex !== null) {
52956          var segment = playlist.segments[mediaIndex];
52957
52958          if (segment && segment.end) {
52959            startOfSegment = segment.end;
52960          } else {
52961            startOfSegment = lastBufferedEnd;
52962          }
52963
52964          return this.generateSegmentInfo_(playlist, mediaIndex + 1, startOfSegment, false);
52965        } // There is a sync-point but the lack of a mediaIndex indicates that
52966        // we need to make a good conservative guess about which segment to
52967        // fetch
52968
52969
52970        if (this.fetchAtBuffer_) {
52971          // Find the segment containing the end of the buffer
52972          var mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time);
52973          mediaIndex = mediaSourceInfo.mediaIndex;
52974          startOfSegment = mediaSourceInfo.startTime;
52975        } else {
52976          // Find the segment containing currentTime
52977          var _mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time);
52978
52979          mediaIndex = _mediaSourceInfo.mediaIndex;
52980          startOfSegment = _mediaSourceInfo.startTime;
52981        }
52982
52983        return this.generateSegmentInfo_(playlist, mediaIndex, startOfSegment, false);
52984      }
52985      /**
52986       * The segment loader has no recourse except to fetch a segment in the
52987       * current playlist and use the internal timestamps in that segment to
52988       * generate a syncPoint. This function returns a good candidate index
52989       * for that process.
52990       *
52991       * @param {Object} playlist - the playlist object to look for a
52992       * @returns {Number} An index of a segment from the playlist to load
52993       */
52994
52995    }, {
52996      key: 'getSyncSegmentCandidate_',
52997      value: function getSyncSegmentCandidate_(playlist) {
52998        var _this2 = this;
52999
53000        if (this.currentTimeline_ === -1) {
53001          return 0;
53002        }
53003
53004        var segmentIndexArray = playlist.segments.map(function (s, i) {
53005          return {
53006            timeline: s.timeline,
53007            segmentIndex: i
53008          };
53009        }).filter(function (s) {
53010          return s.timeline === _this2.currentTimeline_;
53011        });
53012
53013        if (segmentIndexArray.length) {
53014          return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex;
53015        }
53016
53017        return Math.max(playlist.segments.length - 1, 0);
53018      }
53019    }, {
53020      key: 'generateSegmentInfo_',
53021      value: function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) {
53022        if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) {
53023          return null;
53024        }
53025
53026        var segment = playlist.segments[mediaIndex];
53027        return {
53028          requestId: 'segment-loader-' + Math.random(),
53029          // resolve the segment URL relative to the playlist
53030          uri: segment.resolvedUri,
53031          // the segment's mediaIndex at the time it was requested
53032          mediaIndex: mediaIndex,
53033          // whether or not to update the SegmentLoader's state with this
53034          // segment's mediaIndex
53035          isSyncRequest: isSyncRequest,
53036          startOfSegment: startOfSegment,
53037          // the segment's playlist
53038          playlist: playlist,
53039          // unencrypted bytes of the segment
53040          bytes: null,
53041          // when a key is defined for this segment, the encrypted bytes
53042          encryptedBytes: null,
53043          // The target timestampOffset for this segment when we append it
53044          // to the source buffer
53045          timestampOffset: null,
53046          // The timeline that the segment is in
53047          timeline: segment.timeline,
53048          // The expected duration of the segment in seconds
53049          duration: segment.duration,
53050          // retain the segment in case the playlist updates while doing an async process
53051          segment: segment
53052        };
53053      }
53054      /**
53055       * Determines if the network has enough bandwidth to complete the current segment
53056       * request in a timely manner. If not, the request will be aborted early and bandwidth
53057       * updated to trigger a playlist switch.
53058       *
53059       * @param {Object} stats
53060       *        Object containing stats about the request timing and size
53061       * @return {Boolean} True if the request was aborted, false otherwise
53062       * @private
53063       */
53064
53065    }, {
53066      key: 'abortRequestEarly_',
53067      value: function abortRequestEarly_(stats) {
53068        if (this.hls_.tech_.paused() || // Don't abort if the current playlist is on the lowestEnabledRendition
53069        // TODO: Replace using timeout with a boolean indicating whether this playlist is
53070        //       the lowestEnabledRendition.
53071        !this.xhrOptions_.timeout || // Don't abort if we have no bandwidth information to estimate segment sizes
53072        !this.playlist_.attributes.BANDWIDTH) {
53073          return false;
53074        } // Wait at least 1 second since the first byte of data has been received before
53075        // using the calculated bandwidth from the progress event to allow the bitrate
53076        // to stabilize
53077
53078
53079        if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
53080          return false;
53081        }
53082
53083        var currentTime = this.currentTime_();
53084        var measuredBandwidth = stats.bandwidth;
53085        var segmentDuration = this.pendingSegment_.duration;
53086        var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
53087        // if we are only left with less than 1 second when the request completes.
53088        // A negative timeUntilRebuffering indicates we are already rebuffering
53089
53090        var timeUntilRebuffer$$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.hls_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download
53091        // is larger than the estimated time until the player runs out of forward buffer
53092
53093        if (requestTimeRemaining <= timeUntilRebuffer$$1) {
53094          return false;
53095        }
53096
53097        var switchCandidate = minRebufferMaxBandwidthSelector({
53098          master: this.hls_.playlists.master,
53099          currentTime: currentTime,
53100          bandwidth: measuredBandwidth,
53101          duration: this.duration_(),
53102          segmentDuration: segmentDuration,
53103          timeUntilRebuffer: timeUntilRebuffer$$1,
53104          currentTimeline: this.currentTimeline_,
53105          syncController: this.syncController_
53106        });
53107
53108        if (!switchCandidate) {
53109          return;
53110        }
53111
53112        var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$$1;
53113        var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
53114        var minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the
53115        // potential round trip time of the new request so that we are not too aggressive
53116        // with switching to a playlist that might save us a fraction of a second.
53117
53118        if (timeUntilRebuffer$$1 <= TIME_FUDGE_FACTOR) {
53119          minimumTimeSaving = 1;
53120        }
53121
53122        if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
53123          return false;
53124        } // set the bandwidth to that of the desired playlist being sure to scale by
53125        // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
53126        // don't trigger a bandwidthupdate as the bandwidth is artifial
53127
53128
53129        this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
53130        this.abort();
53131        this.trigger('earlyabort');
53132        return true;
53133      }
53134      /**
53135       * XHR `progress` event handler
53136       *
53137       * @param {Event}
53138       *        The XHR `progress` event
53139       * @param {Object} simpleSegment
53140       *        A simplified segment object copy
53141       * @private
53142       */
53143
53144    }, {
53145      key: 'handleProgress_',
53146      value: function handleProgress_(event, simpleSegment) {
53147        if (!this.pendingSegment_ || simpleSegment.requestId !== this.pendingSegment_.requestId || this.abortRequestEarly_(simpleSegment.stats)) {
53148          return;
53149        }
53150
53151        this.trigger('progress');
53152      }
53153      /**
53154       * load a specific segment from a request into the buffer
53155       *
53156       * @private
53157       */
53158
53159    }, {
53160      key: 'loadSegment_',
53161      value: function loadSegment_(segmentInfo) {
53162        this.state = 'WAITING';
53163        this.pendingSegment_ = segmentInfo;
53164        this.trimBackBuffer_(segmentInfo);
53165        segmentInfo.abortRequests = mediaSegmentRequest(this.hls_.xhr, this.xhrOptions_, this.decrypter_, this.captionParser_, this.createSimplifiedSegmentObj_(segmentInfo), // progress callback
53166        this.handleProgress_.bind(this), this.segmentRequestFinished_.bind(this));
53167      }
53168      /**
53169       * trim the back buffer so that we don't have too much data
53170       * in the source buffer
53171       *
53172       * @private
53173       *
53174       * @param {Object} segmentInfo - the current segment
53175       */
53176
53177    }, {
53178      key: 'trimBackBuffer_',
53179      value: function trimBackBuffer_(segmentInfo) {
53180        var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of
53181        // buffer and a very conservative "garbage collector"
53182        // We manually clear out the old buffer to ensure
53183        // we don't trigger the QuotaExceeded error
53184        // on the source buffer during subsequent appends
53185
53186        if (removeToTime > 0) {
53187          this.remove(0, removeToTime);
53188        }
53189      }
53190      /**
53191       * created a simplified copy of the segment object with just the
53192       * information necessary to perform the XHR and decryption
53193       *
53194       * @private
53195       *
53196       * @param {Object} segmentInfo - the current segment
53197       * @returns {Object} a simplified segment object copy
53198       */
53199
53200    }, {
53201      key: 'createSimplifiedSegmentObj_',
53202      value: function createSimplifiedSegmentObj_(segmentInfo) {
53203        var segment = segmentInfo.segment;
53204        var simpleSegment = {
53205          resolvedUri: segment.resolvedUri,
53206          byterange: segment.byterange,
53207          requestId: segmentInfo.requestId
53208        };
53209
53210        if (segment.key) {
53211          // if the media sequence is greater than 2^32, the IV will be incorrect
53212          // assuming 10s segments, that would be about 1300 years
53213          var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
53214          simpleSegment.key = {
53215            resolvedUri: segment.key.resolvedUri,
53216            iv: iv
53217          };
53218        }
53219
53220        if (segment.map) {
53221          simpleSegment.map = this.initSegment(segment.map);
53222        }
53223
53224        return simpleSegment;
53225      }
53226      /**
53227       * Handle the callback from the segmentRequest function and set the
53228       * associated SegmentLoader state and errors if necessary
53229       *
53230       * @private
53231       */
53232
53233    }, {
53234      key: 'segmentRequestFinished_',
53235      value: function segmentRequestFinished_(error, simpleSegment) {
53236        // every request counts as a media request even if it has been aborted
53237        // or canceled due to a timeout
53238        this.mediaRequests += 1;
53239
53240        if (simpleSegment.stats) {
53241          this.mediaBytesTransferred += simpleSegment.stats.bytesReceived;
53242          this.mediaTransferDuration += simpleSegment.stats.roundTripTime;
53243        } // The request was aborted and the SegmentLoader has already been reset
53244
53245
53246        if (!this.pendingSegment_) {
53247          this.mediaRequestsAborted += 1;
53248          return;
53249        } // the request was aborted and the SegmentLoader has already started
53250        // another request. this can happen when the timeout for an aborted
53251        // request triggers due to a limitation in the XHR library
53252        // do not count this as any sort of request or we risk double-counting
53253
53254
53255        if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
53256          return;
53257        } // an error occurred from the active pendingSegment_ so reset everything
53258
53259
53260        if (error) {
53261          this.pendingSegment_ = null;
53262          this.state = 'READY'; // the requests were aborted just record the aborted stat and exit
53263          // this is not a true error condition and nothing corrective needs
53264          // to be done
53265
53266          if (error.code === REQUEST_ERRORS.ABORTED) {
53267            this.mediaRequestsAborted += 1;
53268            return;
53269          }
53270
53271          this.pause(); // the error is really just that at least one of the requests timed-out
53272          // set the bandwidth to a very low value and trigger an ABR switch to
53273          // take emergency action
53274
53275          if (error.code === REQUEST_ERRORS.TIMEOUT) {
53276            this.mediaRequestsTimedout += 1;
53277            this.bandwidth = 1;
53278            this.roundTrip = NaN;
53279            this.trigger('bandwidthupdate');
53280            return;
53281          } // if control-flow has arrived here, then the error is real
53282          // emit an error event to blacklist the current playlist
53283
53284
53285          this.mediaRequestsErrored += 1;
53286          this.error(error);
53287          this.trigger('error');
53288          return;
53289        } // the response was a success so set any bandwidth stats the request
53290        // generated for ABR purposes
53291
53292
53293        this.bandwidth = simpleSegment.stats.bandwidth;
53294        this.roundTrip = simpleSegment.stats.roundTripTime; // if this request included an initialization segment, save that data
53295        // to the initSegment cache
53296
53297        if (simpleSegment.map) {
53298          simpleSegment.map = this.initSegment(simpleSegment.map, true);
53299        }
53300
53301        this.processSegmentResponse_(simpleSegment);
53302      }
53303      /**
53304       * Move any important data from the simplified segment object
53305       * back to the real segment object for future phases
53306       *
53307       * @private
53308       */
53309
53310    }, {
53311      key: 'processSegmentResponse_',
53312      value: function processSegmentResponse_(simpleSegment) {
53313        var segmentInfo = this.pendingSegment_;
53314        segmentInfo.bytes = simpleSegment.bytes;
53315
53316        if (simpleSegment.map) {
53317          segmentInfo.segment.map.bytes = simpleSegment.map.bytes;
53318        }
53319
53320        segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests; // This has fmp4 captions, add them to text tracks
53321
53322        if (simpleSegment.fmp4Captions) {
53323          createCaptionsTrackIfNotExists(this.inbandTextTracks_, this.hls_.tech_, simpleSegment.captionStreams);
53324          addCaptionData({
53325            inbandTextTracks: this.inbandTextTracks_,
53326            captionArray: simpleSegment.fmp4Captions,
53327            // fmp4s will not have a timestamp offset
53328            timestampOffset: 0
53329          }); // Reset stored captions since we added parsed
53330          // captions to a text track at this point
53331
53332          this.captionParser_.clearParsedCaptions();
53333        }
53334
53335        this.handleSegment_();
53336      }
53337      /**
53338       * append a decrypted segement to the SourceBuffer through a SourceUpdater
53339       *
53340       * @private
53341       */
53342
53343    }, {
53344      key: 'handleSegment_',
53345      value: function handleSegment_() {
53346        var _this3 = this;
53347
53348        if (!this.pendingSegment_) {
53349          this.state = 'READY';
53350          return;
53351        }
53352
53353        var segmentInfo = this.pendingSegment_;
53354        var segment = segmentInfo.segment;
53355        var timingInfo = this.syncController_.probeSegmentInfo(segmentInfo); // When we have our first timing info, determine what media types this loader is
53356        // dealing with. Although we're maintaining extra state, it helps to preserve the
53357        // separation of segment loader from the actual source buffers.
53358
53359        if (typeof this.startingMedia_ === 'undefined' && timingInfo && ( // Guard against cases where we're not getting timing info at all until we are
53360        // certain that all streams will provide it.
53361        timingInfo.containsAudio || timingInfo.containsVideo)) {
53362          this.startingMedia_ = {
53363            containsAudio: timingInfo.containsAudio,
53364            containsVideo: timingInfo.containsVideo
53365          };
53366        }
53367
53368        var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.startingMedia_, timingInfo);
53369
53370        if (illegalMediaSwitchError) {
53371          this.error({
53372            message: illegalMediaSwitchError,
53373            blacklistDuration: Infinity
53374          });
53375          this.trigger('error');
53376          return;
53377        }
53378
53379        if (segmentInfo.isSyncRequest) {
53380          this.trigger('syncinfoupdate');
53381          this.pendingSegment_ = null;
53382          this.state = 'READY';
53383          return;
53384        }
53385
53386        if (segmentInfo.timestampOffset !== null && segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) {
53387          this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset); // fired when a timestamp offset is set in HLS (can also identify discontinuities)
53388
53389          this.trigger('timestampoffset');
53390        }
53391
53392        var timelineMapping = this.syncController_.mappingForTimeline(segmentInfo.timeline);
53393
53394        if (timelineMapping !== null) {
53395          this.trigger({
53396            type: 'segmenttimemapping',
53397            mapping: timelineMapping
53398          });
53399        }
53400
53401        this.state = 'APPENDING'; // if the media initialization segment is changing, append it
53402        // before the content segment
53403
53404        if (segment.map) {
53405          var initId = initSegmentId(segment.map);
53406
53407          if (!this.activeInitSegmentId_ || this.activeInitSegmentId_ !== initId) {
53408            var initSegment = this.initSegment(segment.map);
53409            this.sourceUpdater_.appendBuffer({
53410              bytes: initSegment.bytes
53411            }, function () {
53412              _this3.activeInitSegmentId_ = initId;
53413            });
53414          }
53415        }
53416
53417        segmentInfo.byteLength = segmentInfo.bytes.byteLength;
53418
53419        if (typeof segment.start === 'number' && typeof segment.end === 'number') {
53420          this.mediaSecondsLoaded += segment.end - segment.start;
53421        } else {
53422          this.mediaSecondsLoaded += segment.duration;
53423        }
53424
53425        this.logger_(segmentInfoString(segmentInfo));
53426        this.sourceUpdater_.appendBuffer({
53427          bytes: segmentInfo.bytes,
53428          videoSegmentTimingInfoCallback: this.handleVideoSegmentTimingInfo_.bind(this, segmentInfo.requestId)
53429        }, this.handleUpdateEnd_.bind(this));
53430      }
53431    }, {
53432      key: 'handleVideoSegmentTimingInfo_',
53433      value: function handleVideoSegmentTimingInfo_(requestId, event) {
53434        if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) {
53435          return;
53436        }
53437
53438        var segment = this.pendingSegment_.segment;
53439
53440        if (!segment.videoTimingInfo) {
53441          segment.videoTimingInfo = {};
53442        }
53443
53444        segment.videoTimingInfo.transmuxerPrependedSeconds = event.videoSegmentTimingInfo.prependedContentDuration || 0;
53445        segment.videoTimingInfo.transmuxedPresentationStart = event.videoSegmentTimingInfo.start.presentation;
53446        segment.videoTimingInfo.transmuxedPresentationEnd = event.videoSegmentTimingInfo.end.presentation; // mainly used as a reference for debugging
53447
53448        segment.videoTimingInfo.baseMediaDecodeTime = event.videoSegmentTimingInfo.baseMediaDecodeTime;
53449      }
53450      /**
53451       * callback to run when appendBuffer is finished. detects if we are
53452       * in a good state to do things with the data we got, or if we need
53453       * to wait for more
53454       *
53455       * @private
53456       */
53457
53458    }, {
53459      key: 'handleUpdateEnd_',
53460      value: function handleUpdateEnd_() {
53461        if (!this.pendingSegment_) {
53462          this.state = 'READY';
53463
53464          if (!this.paused()) {
53465            this.monitorBuffer_();
53466          }
53467
53468          return;
53469        }
53470
53471        var segmentInfo = this.pendingSegment_;
53472        var segment = segmentInfo.segment;
53473        var isWalkingForward = this.mediaIndex !== null;
53474        this.pendingSegment_ = null;
53475        this.recordThroughput_(segmentInfo);
53476        this.addSegmentMetadataCue_(segmentInfo);
53477        this.state = 'READY';
53478        this.mediaIndex = segmentInfo.mediaIndex;
53479        this.fetchAtBuffer_ = true;
53480        this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before
53481        // the following conditional otherwise it may consider this a bad "guess"
53482        // and attempt to resync when the post-update seekable window and live
53483        // point would mean that this was the perfect segment to fetch
53484
53485        this.trigger('syncinfoupdate'); // If we previously appended a segment that ends more than 3 targetDurations before
53486        // the currentTime_ that means that our conservative guess was too conservative.
53487        // In that case, reset the loader state so that we try to use any information gained
53488        // from the previous request to create a new, more accurate, sync-point.
53489
53490        if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) {
53491          this.resetEverything();
53492          return;
53493        } // Don't do a rendition switch unless we have enough time to get a sync segment
53494        // and conservatively guess
53495
53496
53497        if (isWalkingForward) {
53498          this.trigger('bandwidthupdate');
53499        }
53500
53501        this.trigger('progress'); // any time an update finishes and the last segment is in the
53502        // buffer, end the stream. this ensures the "ended" event will
53503        // fire if playback reaches that point.
53504
53505        if (this.isEndOfStream_(segmentInfo.mediaIndex + 1, segmentInfo.playlist)) {
53506          this.endOfStream();
53507        }
53508
53509        if (!this.paused()) {
53510          this.monitorBuffer_();
53511        }
53512      }
53513      /**
53514       * Records the current throughput of the decrypt, transmux, and append
53515       * portion of the semgment pipeline. `throughput.rate` is a the cumulative
53516       * moving average of the throughput. `throughput.count` is the number of
53517       * data points in the average.
53518       *
53519       * @private
53520       * @param {Object} segmentInfo the object returned by loadSegment
53521       */
53522
53523    }, {
53524      key: 'recordThroughput_',
53525      value: function recordThroughput_(segmentInfo) {
53526        var rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide
53527        // by zero in the case where the throughput is ridiculously high
53528
53529        var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second
53530
53531        var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation:
53532        //   newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
53533
53534        this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
53535      }
53536      /**
53537       * Adds a cue to the segment-metadata track with some metadata information about the
53538       * segment
53539       *
53540       * @private
53541       * @param {Object} segmentInfo
53542       *        the object returned by loadSegment
53543       * @method addSegmentMetadataCue_
53544       */
53545
53546    }, {
53547      key: 'addSegmentMetadataCue_',
53548      value: function addSegmentMetadataCue_(segmentInfo) {
53549        if (!this.segmentMetadataTrack_) {
53550          return;
53551        }
53552
53553        var segment = segmentInfo.segment;
53554        var start = segment.start;
53555        var end = segment.end; // Do not try adding the cue if the start and end times are invalid.
53556
53557        if (!finite(start) || !finite(end)) {
53558          return;
53559        }
53560
53561        removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
53562        var Cue = window$1.WebKitDataCue || window$1.VTTCue;
53563        var value = {
53564          custom: segment.custom,
53565          dateTimeObject: segment.dateTimeObject,
53566          dateTimeString: segment.dateTimeString,
53567          bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
53568          resolution: segmentInfo.playlist.attributes.RESOLUTION,
53569          codecs: segmentInfo.playlist.attributes.CODECS,
53570          byteLength: segmentInfo.byteLength,
53571          uri: segmentInfo.uri,
53572          timeline: segmentInfo.timeline,
53573          playlist: segmentInfo.playlist.uri,
53574          start: start,
53575          end: end
53576        };
53577        var data = JSON.stringify(value);
53578        var cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between
53579        // the differences of WebKitDataCue in safari and VTTCue in other browsers
53580
53581        cue.value = value;
53582        this.segmentMetadataTrack_.addCue(cue);
53583      }
53584    }]);
53585    return SegmentLoader;
53586  }(videojs$1.EventTarget);
53587
53588  var uint8ToUtf8 = function uint8ToUtf8(uintArray) {
53589    return decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray)));
53590  };
53591  /**
53592   * @file vtt-segment-loader.js
53593   */
53594
53595
53596  var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (char) {
53597    return char.charCodeAt(0);
53598  }));
53599  /**
53600   * An object that manages segment loading and appending.
53601   *
53602   * @class VTTSegmentLoader
53603   * @param {Object} options required and optional options
53604   * @extends videojs.EventTarget
53605   */
53606
53607  var VTTSegmentLoader = function (_SegmentLoader) {
53608    inherits$1(VTTSegmentLoader, _SegmentLoader);
53609
53610    function VTTSegmentLoader(settings) {
53611      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
53612      classCallCheck$1(this, VTTSegmentLoader); // SegmentLoader requires a MediaSource be specified or it will throw an error;
53613      // however, VTTSegmentLoader has no need of a media source, so delete the reference
53614
53615      var _this = possibleConstructorReturn$1(this, (VTTSegmentLoader.__proto__ || Object.getPrototypeOf(VTTSegmentLoader)).call(this, settings, options));
53616
53617      _this.mediaSource_ = null;
53618      _this.subtitlesTrack_ = null;
53619      return _this;
53620    }
53621    /**
53622     * Indicates which time ranges are buffered
53623     *
53624     * @return {TimeRange}
53625     *         TimeRange object representing the current buffered ranges
53626     */
53627
53628
53629    createClass$1(VTTSegmentLoader, [{
53630      key: 'buffered_',
53631      value: function buffered_() {
53632        if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) {
53633          return videojs$1.createTimeRanges();
53634        }
53635
53636        var cues = this.subtitlesTrack_.cues;
53637        var start = cues[0].startTime;
53638        var end = cues[cues.length - 1].startTime;
53639        return videojs$1.createTimeRanges([[start, end]]);
53640      }
53641      /**
53642       * Gets and sets init segment for the provided map
53643       *
53644       * @param {Object} map
53645       *        The map object representing the init segment to get or set
53646       * @param {Boolean=} set
53647       *        If true, the init segment for the provided map should be saved
53648       * @return {Object}
53649       *         map object for desired init segment
53650       */
53651
53652    }, {
53653      key: 'initSegment',
53654      value: function initSegment(map) {
53655        var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
53656
53657        if (!map) {
53658          return null;
53659        }
53660
53661        var id = initSegmentId(map);
53662        var storedMap = this.initSegments_[id];
53663
53664        if (set$$1 && !storedMap && map.bytes) {
53665          // append WebVTT line terminators to the media initialization segment if it exists
53666          // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
53667          // requires two or more WebVTT line terminators between the WebVTT header and the
53668          // rest of the file
53669          var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
53670          var combinedSegment = new Uint8Array(combinedByteLength);
53671          combinedSegment.set(map.bytes);
53672          combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
53673          this.initSegments_[id] = storedMap = {
53674            resolvedUri: map.resolvedUri,
53675            byterange: map.byterange,
53676            bytes: combinedSegment
53677          };
53678        }
53679
53680        return storedMap || map;
53681      }
53682      /**
53683       * Returns true if all configuration required for loading is present, otherwise false.
53684       *
53685       * @return {Boolean} True if the all configuration is ready for loading
53686       * @private
53687       */
53688
53689    }, {
53690      key: 'couldBeginLoading_',
53691      value: function couldBeginLoading_() {
53692        return this.playlist_ && this.subtitlesTrack_ && !this.paused();
53693      }
53694      /**
53695       * Once all the starting parameters have been specified, begin
53696       * operation. This method should only be invoked from the INIT
53697       * state.
53698       *
53699       * @private
53700       */
53701
53702    }, {
53703      key: 'init_',
53704      value: function init_() {
53705        this.state = 'READY';
53706        this.resetEverything();
53707        return this.monitorBuffer_();
53708      }
53709      /**
53710       * Set a subtitle track on the segment loader to add subtitles to
53711       *
53712       * @param {TextTrack=} track
53713       *        The text track to add loaded subtitles to
53714       * @return {TextTrack}
53715       *        Returns the subtitles track
53716       */
53717
53718    }, {
53719      key: 'track',
53720      value: function track(_track) {
53721        if (typeof _track === 'undefined') {
53722          return this.subtitlesTrack_;
53723        }
53724
53725        this.subtitlesTrack_ = _track; // if we were unpaused but waiting for a sourceUpdater, start
53726        // buffering now
53727
53728        if (this.state === 'INIT' && this.couldBeginLoading_()) {
53729          this.init_();
53730        }
53731
53732        return this.subtitlesTrack_;
53733      }
53734      /**
53735       * Remove any data in the source buffer between start and end times
53736       * @param {Number} start - the start time of the region to remove from the buffer
53737       * @param {Number} end - the end time of the region to remove from the buffer
53738       */
53739
53740    }, {
53741      key: 'remove',
53742      value: function remove(start, end) {
53743        removeCuesFromTrack(start, end, this.subtitlesTrack_);
53744      }
53745      /**
53746       * fill the buffer with segements unless the sourceBuffers are
53747       * currently updating
53748       *
53749       * Note: this function should only ever be called by monitorBuffer_
53750       * and never directly
53751       *
53752       * @private
53753       */
53754
53755    }, {
53756      key: 'fillBuffer_',
53757      value: function fillBuffer_() {
53758        var _this2 = this;
53759
53760        if (!this.syncPoint_) {
53761          this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
53762        } // see if we need to begin loading immediately
53763
53764
53765        var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
53766        segmentInfo = this.skipEmptySegments_(segmentInfo);
53767
53768        if (!segmentInfo) {
53769          return;
53770        }
53771
53772        if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
53773          // We don't have the timestamp offset that we need to sync subtitles.
53774          // Rerun on a timestamp offset or user interaction.
53775          var checkTimestampOffset = function checkTimestampOffset() {
53776            _this2.state = 'READY';
53777
53778            if (!_this2.paused()) {
53779              // if not paused, queue a buffer check as soon as possible
53780              _this2.monitorBuffer_();
53781            }
53782          };
53783
53784          this.syncController_.one('timestampoffset', checkTimestampOffset);
53785          this.state = 'WAITING_ON_TIMELINE';
53786          return;
53787        }
53788
53789        this.loadSegment_(segmentInfo);
53790      }
53791      /**
53792       * Prevents the segment loader from requesting segments we know contain no subtitles
53793       * by walking forward until we find the next segment that we don't know whether it is
53794       * empty or not.
53795       *
53796       * @param {Object} segmentInfo
53797       *        a segment info object that describes the current segment
53798       * @return {Object}
53799       *         a segment info object that describes the current segment
53800       */
53801
53802    }, {
53803      key: 'skipEmptySegments_',
53804      value: function skipEmptySegments_(segmentInfo) {
53805        while (segmentInfo && segmentInfo.segment.empty) {
53806          segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest);
53807        }
53808
53809        return segmentInfo;
53810      }
53811      /**
53812       * append a decrypted segement to the SourceBuffer through a SourceUpdater
53813       *
53814       * @private
53815       */
53816
53817    }, {
53818      key: 'handleSegment_',
53819      value: function handleSegment_() {
53820        var _this3 = this;
53821
53822        if (!this.pendingSegment_ || !this.subtitlesTrack_) {
53823          this.state = 'READY';
53824          return;
53825        }
53826
53827        this.state = 'APPENDING';
53828        var segmentInfo = this.pendingSegment_;
53829        var segment = segmentInfo.segment; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
53830
53831        if (typeof window$1.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) {
53832          var loadHandler = function loadHandler() {
53833            _this3.handleSegment_();
53834          };
53835
53836          this.state = 'WAITING_ON_VTTJS';
53837          this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler);
53838          this.subtitlesTrack_.tech_.one('vttjserror', function () {
53839            _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler);
53840
53841            _this3.error({
53842              message: 'Error loading vtt.js'
53843            });
53844
53845            _this3.state = 'READY';
53846
53847            _this3.pause();
53848
53849            _this3.trigger('error');
53850          });
53851          return;
53852        }
53853
53854        segment.requested = true;
53855
53856        try {
53857          this.parseVTTCues_(segmentInfo);
53858        } catch (e) {
53859          this.error({
53860            message: e.message
53861          });
53862          this.state = 'READY';
53863          this.pause();
53864          return this.trigger('error');
53865        }
53866
53867        this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_);
53868
53869        if (segmentInfo.isSyncRequest) {
53870          this.trigger('syncinfoupdate');
53871          this.pendingSegment_ = null;
53872          this.state = 'READY';
53873          return;
53874        }
53875
53876        segmentInfo.byteLength = segmentInfo.bytes.byteLength;
53877        this.mediaSecondsLoaded += segment.duration;
53878
53879        if (segmentInfo.cues.length) {
53880          // remove any overlapping cues to prevent doubling
53881          this.remove(segmentInfo.cues[0].endTime, segmentInfo.cues[segmentInfo.cues.length - 1].endTime);
53882        }
53883
53884        segmentInfo.cues.forEach(function (cue) {
53885          _this3.subtitlesTrack_.addCue(cue);
53886        });
53887        this.handleUpdateEnd_();
53888      }
53889      /**
53890       * Uses the WebVTT parser to parse the segment response
53891       *
53892       * @param {Object} segmentInfo
53893       *        a segment info object that describes the current segment
53894       * @private
53895       */
53896
53897    }, {
53898      key: 'parseVTTCues_',
53899      value: function parseVTTCues_(segmentInfo) {
53900        var decoder = void 0;
53901        var decodeBytesToString = false;
53902
53903        if (typeof window$1.TextDecoder === 'function') {
53904          decoder = new window$1.TextDecoder('utf8');
53905        } else {
53906          decoder = window$1.WebVTT.StringDecoder();
53907          decodeBytesToString = true;
53908        }
53909
53910        var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, decoder);
53911        segmentInfo.cues = [];
53912        segmentInfo.timestampmap = {
53913          MPEGTS: 0,
53914          LOCAL: 0
53915        };
53916        parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
53917
53918        parser.ontimestampmap = function (map) {
53919          return segmentInfo.timestampmap = map;
53920        };
53921
53922        parser.onparsingerror = function (error) {
53923          videojs$1.log.warn('Error encountered when parsing cues: ' + error.message);
53924        };
53925
53926        if (segmentInfo.segment.map) {
53927          var mapData = segmentInfo.segment.map.bytes;
53928
53929          if (decodeBytesToString) {
53930            mapData = uint8ToUtf8(mapData);
53931          }
53932
53933          parser.parse(mapData);
53934        }
53935
53936        var segmentData = segmentInfo.bytes;
53937
53938        if (decodeBytesToString) {
53939          segmentData = uint8ToUtf8(segmentData);
53940        }
53941
53942        parser.parse(segmentData);
53943        parser.flush();
53944      }
53945      /**
53946       * Updates the start and end times of any cues parsed by the WebVTT parser using
53947       * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
53948       * from the SyncController
53949       *
53950       * @param {Object} segmentInfo
53951       *        a segment info object that describes the current segment
53952       * @param {Object} mappingObj
53953       *        object containing a mapping from TS to media time
53954       * @param {Object} playlist
53955       *        the playlist object containing the segment
53956       * @private
53957       */
53958
53959    }, {
53960      key: 'updateTimeMapping_',
53961      value: function updateTimeMapping_(segmentInfo, mappingObj, playlist) {
53962        var segment = segmentInfo.segment;
53963
53964        if (!mappingObj) {
53965          // If the sync controller does not have a mapping of TS to Media Time for the
53966          // timeline, then we don't have enough information to update the cue
53967          // start/end times
53968          return;
53969        }
53970
53971        if (!segmentInfo.cues.length) {
53972          // If there are no cues, we also do not have enough information to figure out
53973          // segment timing. Mark that the segment contains no cues so we don't re-request
53974          // an empty segment.
53975          segment.empty = true;
53976          return;
53977        }
53978
53979        var timestampmap = segmentInfo.timestampmap;
53980        var diff = timestampmap.MPEGTS / 90000 - timestampmap.LOCAL + mappingObj.mapping;
53981        segmentInfo.cues.forEach(function (cue) {
53982          // First convert cue time to TS time using the timestamp-map provided within the vtt
53983          cue.startTime += diff;
53984          cue.endTime += diff;
53985        });
53986
53987        if (!playlist.syncInfo) {
53988          var firstStart = segmentInfo.cues[0].startTime;
53989          var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
53990          playlist.syncInfo = {
53991            mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
53992            time: Math.min(firstStart, lastStart - segment.duration)
53993          };
53994        }
53995      }
53996    }]);
53997    return VTTSegmentLoader;
53998  }(SegmentLoader);
53999  /**
54000   * @file ad-cue-tags.js
54001   */
54002
54003  /**
54004   * Searches for an ad cue that overlaps with the given mediaTime
54005   */
54006
54007
54008  var findAdCue = function findAdCue(track, mediaTime) {
54009    var cues = track.cues;
54010
54011    for (var i = 0; i < cues.length; i++) {
54012      var cue = cues[i];
54013
54014      if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
54015        return cue;
54016      }
54017    }
54018
54019    return null;
54020  };
54021
54022  var updateAdCues = function updateAdCues(media, track) {
54023    var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
54024
54025    if (!media.segments) {
54026      return;
54027    }
54028
54029    var mediaTime = offset;
54030    var cue = void 0;
54031
54032    for (var i = 0; i < media.segments.length; i++) {
54033      var segment = media.segments[i];
54034
54035      if (!cue) {
54036        // Since the cues will span for at least the segment duration, adding a fudge
54037        // factor of half segment duration will prevent duplicate cues from being
54038        // created when timing info is not exact (e.g. cue start time initialized
54039        // at 10.006677, but next call mediaTime is 10.003332 )
54040        cue = findAdCue(track, mediaTime + segment.duration / 2);
54041      }
54042
54043      if (cue) {
54044        if ('cueIn' in segment) {
54045          // Found a CUE-IN so end the cue
54046          cue.endTime = mediaTime;
54047          cue.adEndTime = mediaTime;
54048          mediaTime += segment.duration;
54049          cue = null;
54050          continue;
54051        }
54052
54053        if (mediaTime < cue.endTime) {
54054          // Already processed this mediaTime for this cue
54055          mediaTime += segment.duration;
54056          continue;
54057        } // otherwise extend cue until a CUE-IN is found
54058
54059
54060        cue.endTime += segment.duration;
54061      } else {
54062        if ('cueOut' in segment) {
54063          cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut);
54064          cue.adStartTime = mediaTime; // Assumes tag format to be
54065          // #EXT-X-CUE-OUT:30
54066
54067          cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
54068          track.addCue(cue);
54069        }
54070
54071        if ('cueOutCont' in segment) {
54072          // Entered into the middle of an ad cue
54073          var adOffset = void 0;
54074          var adTotal = void 0; // Assumes tag formate to be
54075          // #EXT-X-CUE-OUT-CONT:10/30
54076
54077          var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat);
54078
54079          var _segment$cueOutCont$s2 = slicedToArray(_segment$cueOutCont$s, 2);
54080
54081          adOffset = _segment$cueOutCont$s2[0];
54082          adTotal = _segment$cueOutCont$s2[1];
54083          cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, '');
54084          cue.adStartTime = mediaTime - adOffset;
54085          cue.adEndTime = cue.adStartTime + adTotal;
54086          track.addCue(cue);
54087        }
54088      }
54089
54090      mediaTime += segment.duration;
54091    }
54092  };
54093  /**
54094   * @file sync-controller.js
54095   */
54096
54097
54098  var tsprobe = tsInspector.inspect;
54099  var syncPointStrategies = [// Stategy "VOD": Handle the VOD-case where the sync-point is *always*
54100  //                the equivalence display-time 0 === segment-index 0
54101  {
54102    name: 'VOD',
54103    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
54104      if (duration$$1 !== Infinity) {
54105        var syncPoint = {
54106          time: 0,
54107          segmentIndex: 0
54108        };
54109        return syncPoint;
54110      }
54111
54112      return null;
54113    }
54114  }, // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
54115  {
54116    name: 'ProgramDateTime',
54117    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
54118      if (!syncController.datetimeToDisplayTime) {
54119        return null;
54120      }
54121
54122      var segments = playlist.segments || [];
54123      var syncPoint = null;
54124      var lastDistance = null;
54125      currentTime = currentTime || 0;
54126
54127      for (var i = 0; i < segments.length; i++) {
54128        var segment = segments[i];
54129
54130        if (segment.dateTimeObject) {
54131          var segmentTime = segment.dateTimeObject.getTime() / 1000;
54132          var segmentStart = segmentTime + syncController.datetimeToDisplayTime;
54133          var distance = Math.abs(currentTime - segmentStart); // Once the distance begins to increase, we have passed
54134          // currentTime and can stop looking for better candidates
54135
54136          if (lastDistance !== null && lastDistance < distance) {
54137            break;
54138          }
54139
54140          lastDistance = distance;
54141          syncPoint = {
54142            time: segmentStart,
54143            segmentIndex: i
54144          };
54145        }
54146      }
54147
54148      return syncPoint;
54149    }
54150  }, // Stategy "Segment": We have a known time mapping for a timeline and a
54151  //                    segment in the current timeline with timing data
54152  {
54153    name: 'Segment',
54154    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
54155      var segments = playlist.segments || [];
54156      var syncPoint = null;
54157      var lastDistance = null;
54158      currentTime = currentTime || 0;
54159
54160      for (var i = 0; i < segments.length; i++) {
54161        var segment = segments[i];
54162
54163        if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') {
54164          var distance = Math.abs(currentTime - segment.start); // Once the distance begins to increase, we have passed
54165          // currentTime and can stop looking for better candidates
54166
54167          if (lastDistance !== null && lastDistance < distance) {
54168            break;
54169          }
54170
54171          if (!syncPoint || lastDistance === null || lastDistance >= distance) {
54172            lastDistance = distance;
54173            syncPoint = {
54174              time: segment.start,
54175              segmentIndex: i
54176            };
54177          }
54178        }
54179      }
54180
54181      return syncPoint;
54182    }
54183  }, // Stategy "Discontinuity": We have a discontinuity with a known
54184  //                          display-time
54185  {
54186    name: 'Discontinuity',
54187    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
54188      var syncPoint = null;
54189      currentTime = currentTime || 0;
54190
54191      if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
54192        var lastDistance = null;
54193
54194        for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
54195          var segmentIndex = playlist.discontinuityStarts[i];
54196          var discontinuity = playlist.discontinuitySequence + i + 1;
54197          var discontinuitySync = syncController.discontinuities[discontinuity];
54198
54199          if (discontinuitySync) {
54200            var distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed
54201            // currentTime and can stop looking for better candidates
54202
54203            if (lastDistance !== null && lastDistance < distance) {
54204              break;
54205            }
54206
54207            if (!syncPoint || lastDistance === null || lastDistance >= distance) {
54208              lastDistance = distance;
54209              syncPoint = {
54210                time: discontinuitySync.time,
54211                segmentIndex: segmentIndex
54212              };
54213            }
54214          }
54215        }
54216      }
54217
54218      return syncPoint;
54219    }
54220  }, // Stategy "Playlist": We have a playlist with a known mapping of
54221  //                     segment index to display time
54222  {
54223    name: 'Playlist',
54224    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
54225      if (playlist.syncInfo) {
54226        var syncPoint = {
54227          time: playlist.syncInfo.time,
54228          segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence
54229        };
54230        return syncPoint;
54231      }
54232
54233      return null;
54234    }
54235  }];
54236
54237  var SyncController = function (_videojs$EventTarget) {
54238    inherits$1(SyncController, _videojs$EventTarget);
54239
54240    function SyncController() {
54241      classCallCheck$1(this, SyncController); // Segment Loader state variables...
54242      // ...for synching across variants
54243
54244      var _this = possibleConstructorReturn$1(this, (SyncController.__proto__ || Object.getPrototypeOf(SyncController)).call(this));
54245
54246      _this.inspectCache_ = undefined; // ...for synching across variants
54247
54248      _this.timelines = [];
54249      _this.discontinuities = [];
54250      _this.datetimeToDisplayTime = null;
54251      _this.logger_ = logger('SyncController');
54252      return _this;
54253    }
54254    /**
54255     * Find a sync-point for the playlist specified
54256     *
54257     * A sync-point is defined as a known mapping from display-time to
54258     * a segment-index in the current playlist.
54259     *
54260     * @param {Playlist} playlist
54261     *        The playlist that needs a sync-point
54262     * @param {Number} duration
54263     *        Duration of the MediaSource (Infinite if playing a live source)
54264     * @param {Number} currentTimeline
54265     *        The last timeline from which a segment was loaded
54266     * @returns {Object}
54267     *          A sync-point object
54268     */
54269
54270
54271    createClass$1(SyncController, [{
54272      key: 'getSyncPoint',
54273      value: function getSyncPoint(playlist, duration$$1, currentTimeline, currentTime) {
54274        var syncPoints = this.runStrategies_(playlist, duration$$1, currentTimeline, currentTime);
54275
54276        if (!syncPoints.length) {
54277          // Signal that we need to attempt to get a sync-point manually
54278          // by fetching a segment in the playlist and constructing
54279          // a sync-point from that information
54280          return null;
54281        } // Now find the sync-point that is closest to the currentTime because
54282        // that should result in the most accurate guess about which segment
54283        // to fetch
54284
54285
54286        return this.selectSyncPoint_(syncPoints, {
54287          key: 'time',
54288          value: currentTime
54289        });
54290      }
54291      /**
54292       * Calculate the amount of time that has expired off the playlist during playback
54293       *
54294       * @param {Playlist} playlist
54295       *        Playlist object to calculate expired from
54296       * @param {Number} duration
54297       *        Duration of the MediaSource (Infinity if playling a live source)
54298       * @returns {Number|null}
54299       *          The amount of time that has expired off the playlist during playback. Null
54300       *          if no sync-points for the playlist can be found.
54301       */
54302
54303    }, {
54304      key: 'getExpiredTime',
54305      value: function getExpiredTime(playlist, duration$$1) {
54306        if (!playlist || !playlist.segments) {
54307          return null;
54308        }
54309
54310        var syncPoints = this.runStrategies_(playlist, duration$$1, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time
54311
54312        if (!syncPoints.length) {
54313          return null;
54314        }
54315
54316        var syncPoint = this.selectSyncPoint_(syncPoints, {
54317          key: 'segmentIndex',
54318          value: 0
54319        }); // If the sync-point is beyond the start of the playlist, we want to subtract the
54320        // duration from index 0 to syncPoint.segmentIndex instead of adding.
54321
54322        if (syncPoint.segmentIndex > 0) {
54323          syncPoint.time *= -1;
54324        }
54325
54326        return Math.abs(syncPoint.time + sumDurations(playlist, syncPoint.segmentIndex, 0));
54327      }
54328      /**
54329       * Runs each sync-point strategy and returns a list of sync-points returned by the
54330       * strategies
54331       *
54332       * @private
54333       * @param {Playlist} playlist
54334       *        The playlist that needs a sync-point
54335       * @param {Number} duration
54336       *        Duration of the MediaSource (Infinity if playing a live source)
54337       * @param {Number} currentTimeline
54338       *        The last timeline from which a segment was loaded
54339       * @returns {Array}
54340       *          A list of sync-point objects
54341       */
54342
54343    }, {
54344      key: 'runStrategies_',
54345      value: function runStrategies_(playlist, duration$$1, currentTimeline, currentTime) {
54346        var syncPoints = []; // Try to find a sync-point in by utilizing various strategies...
54347
54348        for (var i = 0; i < syncPointStrategies.length; i++) {
54349          var strategy = syncPointStrategies[i];
54350          var syncPoint = strategy.run(this, playlist, duration$$1, currentTimeline, currentTime);
54351
54352          if (syncPoint) {
54353            syncPoint.strategy = strategy.name;
54354            syncPoints.push({
54355              strategy: strategy.name,
54356              syncPoint: syncPoint
54357            });
54358          }
54359        }
54360
54361        return syncPoints;
54362      }
54363      /**
54364       * Selects the sync-point nearest the specified target
54365       *
54366       * @private
54367       * @param {Array} syncPoints
54368       *        List of sync-points to select from
54369       * @param {Object} target
54370       *        Object specifying the property and value we are targeting
54371       * @param {String} target.key
54372       *        Specifies the property to target. Must be either 'time' or 'segmentIndex'
54373       * @param {Number} target.value
54374       *        The value to target for the specified key.
54375       * @returns {Object}
54376       *          The sync-point nearest the target
54377       */
54378
54379    }, {
54380      key: 'selectSyncPoint_',
54381      value: function selectSyncPoint_(syncPoints, target) {
54382        var bestSyncPoint = syncPoints[0].syncPoint;
54383        var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
54384        var bestStrategy = syncPoints[0].strategy;
54385
54386        for (var i = 1; i < syncPoints.length; i++) {
54387          var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
54388
54389          if (newDistance < bestDistance) {
54390            bestDistance = newDistance;
54391            bestSyncPoint = syncPoints[i].syncPoint;
54392            bestStrategy = syncPoints[i].strategy;
54393          }
54394        }
54395
54396        this.logger_('syncPoint for [' + target.key + ': ' + target.value + '] chosen with strategy' + (' [' + bestStrategy + ']: [time:' + bestSyncPoint.time + ',') + (' segmentIndex:' + bestSyncPoint.segmentIndex + ']'));
54397        return bestSyncPoint;
54398      }
54399      /**
54400       * Save any meta-data present on the segments when segments leave
54401       * the live window to the playlist to allow for synchronization at the
54402       * playlist level later.
54403       *
54404       * @param {Playlist} oldPlaylist - The previous active playlist
54405       * @param {Playlist} newPlaylist - The updated and most current playlist
54406       */
54407
54408    }, {
54409      key: 'saveExpiredSegmentInfo',
54410      value: function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
54411        var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // When a segment expires from the playlist and it has a start time
54412        // save that information as a possible sync-point reference in future
54413
54414        for (var i = mediaSequenceDiff - 1; i >= 0; i--) {
54415          var lastRemovedSegment = oldPlaylist.segments[i];
54416
54417          if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
54418            newPlaylist.syncInfo = {
54419              mediaSequence: oldPlaylist.mediaSequence + i,
54420              time: lastRemovedSegment.start
54421            };
54422            this.logger_('playlist refresh sync: [time:' + newPlaylist.syncInfo.time + ',' + (' mediaSequence: ' + newPlaylist.syncInfo.mediaSequence + ']'));
54423            this.trigger('syncinfoupdate');
54424            break;
54425          }
54426        }
54427      }
54428      /**
54429       * Save the mapping from playlist's ProgramDateTime to display. This should
54430       * only ever happen once at the start of playback.
54431       *
54432       * @param {Playlist} playlist - The currently active playlist
54433       */
54434
54435    }, {
54436      key: 'setDateTimeMapping',
54437      value: function setDateTimeMapping(playlist) {
54438        if (!this.datetimeToDisplayTime && playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
54439          var playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000;
54440          this.datetimeToDisplayTime = -playlistTimestamp;
54441        }
54442      }
54443      /**
54444       * Reset the state of the inspection cache when we do a rendition
54445       * switch
54446       */
54447
54448    }, {
54449      key: 'reset',
54450      value: function reset() {
54451        this.inspectCache_ = undefined;
54452      }
54453      /**
54454       * Probe or inspect a fmp4 or an mpeg2-ts segment to determine the start
54455       * and end of the segment in it's internal "media time". Used to generate
54456       * mappings from that internal "media time" to the display time that is
54457       * shown on the player.
54458       *
54459       * @param {SegmentInfo} segmentInfo - The current active request information
54460       */
54461
54462    }, {
54463      key: 'probeSegmentInfo',
54464      value: function probeSegmentInfo(segmentInfo) {
54465        var segment = segmentInfo.segment;
54466        var playlist = segmentInfo.playlist;
54467        var timingInfo = void 0;
54468
54469        if (segment.map) {
54470          timingInfo = this.probeMp4Segment_(segmentInfo);
54471        } else {
54472          timingInfo = this.probeTsSegment_(segmentInfo);
54473        }
54474
54475        if (timingInfo) {
54476          if (this.calculateSegmentTimeMapping_(segmentInfo, timingInfo)) {
54477            this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information
54478            // now with segment timing information
54479
54480            if (!playlist.syncInfo) {
54481              playlist.syncInfo = {
54482                mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
54483                time: segment.start
54484              };
54485            }
54486          }
54487        }
54488
54489        return timingInfo;
54490      }
54491      /**
54492       * Probe an fmp4 or an mpeg2-ts segment to determine the start of the segment
54493       * in it's internal "media time".
54494       *
54495       * @private
54496       * @param {SegmentInfo} segmentInfo - The current active request information
54497       * @return {object} The start and end time of the current segment in "media time"
54498       */
54499
54500    }, {
54501      key: 'probeMp4Segment_',
54502      value: function probeMp4Segment_(segmentInfo) {
54503        var segment = segmentInfo.segment;
54504        var timescales = probe.timescale(segment.map.bytes);
54505        var startTime = probe.startTime(timescales, segmentInfo.bytes);
54506
54507        if (segmentInfo.timestampOffset !== null) {
54508          segmentInfo.timestampOffset -= startTime;
54509        }
54510
54511        return {
54512          start: startTime,
54513          end: startTime + segment.duration
54514        };
54515      }
54516      /**
54517       * Probe an mpeg2-ts segment to determine the start and end of the segment
54518       * in it's internal "media time".
54519       *
54520       * @private
54521       * @param {SegmentInfo} segmentInfo - The current active request information
54522       * @return {object} The start and end time of the current segment in "media time"
54523       */
54524
54525    }, {
54526      key: 'probeTsSegment_',
54527      value: function probeTsSegment_(segmentInfo) {
54528        var timeInfo = tsprobe(segmentInfo.bytes, this.inspectCache_);
54529        var segmentStartTime = void 0;
54530        var segmentEndTime = void 0;
54531
54532        if (!timeInfo) {
54533          return null;
54534        }
54535
54536        if (timeInfo.video && timeInfo.video.length === 2) {
54537          this.inspectCache_ = timeInfo.video[1].dts;
54538          segmentStartTime = timeInfo.video[0].dtsTime;
54539          segmentEndTime = timeInfo.video[1].dtsTime;
54540        } else if (timeInfo.audio && timeInfo.audio.length === 2) {
54541          this.inspectCache_ = timeInfo.audio[1].dts;
54542          segmentStartTime = timeInfo.audio[0].dtsTime;
54543          segmentEndTime = timeInfo.audio[1].dtsTime;
54544        }
54545
54546        var probedInfo = {
54547          start: segmentStartTime,
54548          end: segmentEndTime,
54549          containsVideo: timeInfo.video && timeInfo.video.length === 2,
54550          containsAudio: timeInfo.audio && timeInfo.audio.length === 2
54551        };
54552        return probedInfo;
54553      }
54554    }, {
54555      key: 'timestampOffsetForTimeline',
54556      value: function timestampOffsetForTimeline(timeline) {
54557        if (typeof this.timelines[timeline] === 'undefined') {
54558          return null;
54559        }
54560
54561        return this.timelines[timeline].time;
54562      }
54563    }, {
54564      key: 'mappingForTimeline',
54565      value: function mappingForTimeline(timeline) {
54566        if (typeof this.timelines[timeline] === 'undefined') {
54567          return null;
54568        }
54569
54570        return this.timelines[timeline].mapping;
54571      }
54572      /**
54573       * Use the "media time" for a segment to generate a mapping to "display time" and
54574       * save that display time to the segment.
54575       *
54576       * @private
54577       * @param {SegmentInfo} segmentInfo
54578       *        The current active request information
54579       * @param {object} timingInfo
54580       *        The start and end time of the current segment in "media time"
54581       * @returns {Boolean}
54582       *          Returns false if segment time mapping could not be calculated
54583       */
54584
54585    }, {
54586      key: 'calculateSegmentTimeMapping_',
54587      value: function calculateSegmentTimeMapping_(segmentInfo, timingInfo) {
54588        var segment = segmentInfo.segment;
54589        var mappingObj = this.timelines[segmentInfo.timeline];
54590
54591        if (segmentInfo.timestampOffset !== null) {
54592          mappingObj = {
54593            time: segmentInfo.startOfSegment,
54594            mapping: segmentInfo.startOfSegment - timingInfo.start
54595          };
54596          this.timelines[segmentInfo.timeline] = mappingObj;
54597          this.trigger('timestampoffset');
54598          this.logger_('time mapping for timeline ' + segmentInfo.timeline + ': ' + ('[time: ' + mappingObj.time + '] [mapping: ' + mappingObj.mapping + ']'));
54599          segment.start = segmentInfo.startOfSegment;
54600          segment.end = timingInfo.end + mappingObj.mapping;
54601        } else if (mappingObj) {
54602          segment.start = timingInfo.start + mappingObj.mapping;
54603          segment.end = timingInfo.end + mappingObj.mapping;
54604        } else {
54605          return false;
54606        }
54607
54608        return true;
54609      }
54610      /**
54611       * Each time we have discontinuity in the playlist, attempt to calculate the location
54612       * in display of the start of the discontinuity and save that. We also save an accuracy
54613       * value so that we save values with the most accuracy (closest to 0.)
54614       *
54615       * @private
54616       * @param {SegmentInfo} segmentInfo - The current active request information
54617       */
54618
54619    }, {
54620      key: 'saveDiscontinuitySyncInfo_',
54621      value: function saveDiscontinuitySyncInfo_(segmentInfo) {
54622        var playlist = segmentInfo.playlist;
54623        var segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where
54624        // the start of the range and it's accuracy is 0 (greater accuracy values
54625        // mean more approximation)
54626
54627        if (segment.discontinuity) {
54628          this.discontinuities[segment.timeline] = {
54629            time: segment.start,
54630            accuracy: 0
54631          };
54632        } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
54633          // Search for future discontinuities that we can provide better timing
54634          // information for and save that information for sync purposes
54635          for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
54636            var segmentIndex = playlist.discontinuityStarts[i];
54637            var discontinuity = playlist.discontinuitySequence + i + 1;
54638            var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
54639            var accuracy = Math.abs(mediaIndexDiff);
54640
54641            if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
54642              var time = void 0;
54643
54644              if (mediaIndexDiff < 0) {
54645                time = segment.start - sumDurations(playlist, segmentInfo.mediaIndex, segmentIndex);
54646              } else {
54647                time = segment.end + sumDurations(playlist, segmentInfo.mediaIndex + 1, segmentIndex);
54648              }
54649
54650              this.discontinuities[discontinuity] = {
54651                time: time,
54652                accuracy: accuracy
54653              };
54654            }
54655          }
54656        }
54657      }
54658    }]);
54659    return SyncController;
54660  }(videojs$1.EventTarget);
54661
54662  var Decrypter$1 = new shimWorker("./decrypter-worker.worker.js", function (window, document$$1) {
54663    var self = this;
54664
54665    var decrypterWorker = function () {
54666      /*
54667       * pkcs7.pad
54668       * https://github.com/brightcove/pkcs7
54669       *
54670       * Copyright (c) 2014 Brightcove
54671       * Licensed under the apache2 license.
54672       */
54673
54674      /**
54675       * Returns the subarray of a Uint8Array without PKCS#7 padding.
54676       * @param padded {Uint8Array} unencrypted bytes that have been padded
54677       * @return {Uint8Array} the unpadded bytes
54678       * @see http://tools.ietf.org/html/rfc5652
54679       */
54680      function unpad(padded) {
54681        return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
54682      }
54683
54684      var classCallCheck = function classCallCheck(instance, Constructor) {
54685        if (!(instance instanceof Constructor)) {
54686          throw new TypeError("Cannot call a class as a function");
54687        }
54688      };
54689
54690      var createClass = function () {
54691        function defineProperties(target, props) {
54692          for (var i = 0; i < props.length; i++) {
54693            var descriptor = props[i];
54694            descriptor.enumerable = descriptor.enumerable || false;
54695            descriptor.configurable = true;
54696            if ("value" in descriptor) descriptor.writable = true;
54697            Object.defineProperty(target, descriptor.key, descriptor);
54698          }
54699        }
54700
54701        return function (Constructor, protoProps, staticProps) {
54702          if (protoProps) defineProperties(Constructor.prototype, protoProps);
54703          if (staticProps) defineProperties(Constructor, staticProps);
54704          return Constructor;
54705        };
54706      }();
54707
54708      var inherits = function inherits(subClass, superClass) {
54709        if (typeof superClass !== "function" && superClass !== null) {
54710          throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
54711        }
54712
54713        subClass.prototype = Object.create(superClass && superClass.prototype, {
54714          constructor: {
54715            value: subClass,
54716            enumerable: false,
54717            writable: true,
54718            configurable: true
54719          }
54720        });
54721        if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
54722      };
54723
54724      var possibleConstructorReturn = function possibleConstructorReturn(self, call) {
54725        if (!self) {
54726          throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
54727        }
54728
54729        return call && (typeof call === "object" || typeof call === "function") ? call : self;
54730      };
54731      /**
54732       * @file aes.js
54733       *
54734       * This file contains an adaptation of the AES decryption algorithm
54735       * from the Standford Javascript Cryptography Library. That work is
54736       * covered by the following copyright and permissions notice:
54737       *
54738       * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
54739       * All rights reserved.
54740       *
54741       * Redistribution and use in source and binary forms, with or without
54742       * modification, are permitted provided that the following conditions are
54743       * met:
54744       *
54745       * 1. Redistributions of source code must retain the above copyright
54746       *    notice, this list of conditions and the following disclaimer.
54747       *
54748       * 2. Redistributions in binary form must reproduce the above
54749       *    copyright notice, this list of conditions and the following
54750       *    disclaimer in the documentation and/or other materials provided
54751       *    with the distribution.
54752       *
54753       * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
54754       * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
54755       * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
54756       * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
54757       * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
54758       * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
54759       * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
54760       * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
54761       * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
54762       * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
54763       * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
54764       *
54765       * The views and conclusions contained in the software and documentation
54766       * are those of the authors and should not be interpreted as representing
54767       * official policies, either expressed or implied, of the authors.
54768       */
54769
54770      /**
54771       * Expand the S-box tables.
54772       *
54773       * @private
54774       */
54775
54776
54777      var precompute = function precompute() {
54778        var tables = [[[], [], [], [], []], [[], [], [], [], []]];
54779        var encTable = tables[0];
54780        var decTable = tables[1];
54781        var sbox = encTable[4];
54782        var sboxInv = decTable[4];
54783        var i = void 0;
54784        var x = void 0;
54785        var xInv = void 0;
54786        var d = [];
54787        var th = [];
54788        var x2 = void 0;
54789        var x4 = void 0;
54790        var x8 = void 0;
54791        var s = void 0;
54792        var tEnc = void 0;
54793        var tDec = void 0; // Compute double and third tables
54794
54795        for (i = 0; i < 256; i++) {
54796          th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
54797        }
54798
54799        for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
54800          // Compute sbox
54801          s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
54802          s = s >> 8 ^ s & 255 ^ 99;
54803          sbox[x] = s;
54804          sboxInv[s] = x; // Compute MixColumns
54805
54806          x8 = d[x4 = d[x2 = d[x]]];
54807          tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
54808          tEnc = d[s] * 0x101 ^ s * 0x1010100;
54809
54810          for (i = 0; i < 4; i++) {
54811            encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
54812            decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
54813          }
54814        } // Compactify. Considerable speedup on Firefox.
54815
54816
54817        for (i = 0; i < 5; i++) {
54818          encTable[i] = encTable[i].slice(0);
54819          decTable[i] = decTable[i].slice(0);
54820        }
54821
54822        return tables;
54823      };
54824
54825      var aesTables = null;
54826      /**
54827       * Schedule out an AES key for both encryption and decryption. This
54828       * is a low-level class. Use a cipher mode to do bulk encryption.
54829       *
54830       * @class AES
54831       * @param key {Array} The key as an array of 4, 6 or 8 words.
54832       */
54833
54834      var AES = function () {
54835        function AES(key) {
54836          classCallCheck(this, AES);
54837          /**
54838           * The expanded S-box and inverse S-box tables. These will be computed
54839           * on the client so that we don't have to send them down the wire.
54840           *
54841           * There are two tables, _tables[0] is for encryption and
54842           * _tables[1] is for decryption.
54843           *
54844           * The first 4 sub-tables are the expanded S-box with MixColumns. The
54845           * last (_tables[01][4]) is the S-box itself.
54846           *
54847           * @private
54848           */
54849          // if we have yet to precompute the S-box tables
54850          // do so now
54851
54852          if (!aesTables) {
54853            aesTables = precompute();
54854          } // then make a copy of that object for use
54855
54856
54857          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()]];
54858          var i = void 0;
54859          var j = void 0;
54860          var tmp = void 0;
54861          var encKey = void 0;
54862          var decKey = void 0;
54863          var sbox = this._tables[0][4];
54864          var decTable = this._tables[1];
54865          var keyLen = key.length;
54866          var rcon = 1;
54867
54868          if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
54869            throw new Error('Invalid aes key size');
54870          }
54871
54872          encKey = key.slice(0);
54873          decKey = [];
54874          this._key = [encKey, decKey]; // schedule encryption keys
54875
54876          for (i = keyLen; i < 4 * keyLen + 28; i++) {
54877            tmp = encKey[i - 1]; // apply sbox
54878
54879            if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
54880              tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
54881
54882              if (i % keyLen === 0) {
54883                tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
54884                rcon = rcon << 1 ^ (rcon >> 7) * 283;
54885              }
54886            }
54887
54888            encKey[i] = encKey[i - keyLen] ^ tmp;
54889          } // schedule decryption keys
54890
54891
54892          for (j = 0; i; j++, i--) {
54893            tmp = encKey[j & 3 ? i : i - 4];
54894
54895            if (i <= 4 || j < 4) {
54896              decKey[j] = tmp;
54897            } else {
54898              decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
54899            }
54900          }
54901        }
54902        /**
54903         * Decrypt 16 bytes, specified as four 32-bit words.
54904         *
54905         * @param {Number} encrypted0 the first word to decrypt
54906         * @param {Number} encrypted1 the second word to decrypt
54907         * @param {Number} encrypted2 the third word to decrypt
54908         * @param {Number} encrypted3 the fourth word to decrypt
54909         * @param {Int32Array} out the array to write the decrypted words
54910         * into
54911         * @param {Number} offset the offset into the output array to start
54912         * writing results
54913         * @return {Array} The plaintext.
54914         */
54915
54916
54917        AES.prototype.decrypt = function decrypt$$1(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
54918          var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
54919
54920          var a = encrypted0 ^ key[0];
54921          var b = encrypted3 ^ key[1];
54922          var c = encrypted2 ^ key[2];
54923          var d = encrypted1 ^ key[3];
54924          var a2 = void 0;
54925          var b2 = void 0;
54926          var c2 = void 0; // key.length === 2 ?
54927
54928          var nInnerRounds = key.length / 4 - 2;
54929          var i = void 0;
54930          var kIndex = 4;
54931          var table = this._tables[1]; // load up the tables
54932
54933          var table0 = table[0];
54934          var table1 = table[1];
54935          var table2 = table[2];
54936          var table3 = table[3];
54937          var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
54938
54939          for (i = 0; i < nInnerRounds; i++) {
54940            a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
54941            b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
54942            c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
54943            d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
54944            kIndex += 4;
54945            a = a2;
54946            b = b2;
54947            c = c2;
54948          } // Last round.
54949
54950
54951          for (i = 0; i < 4; i++) {
54952            out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
54953            a2 = a;
54954            a = b;
54955            b = c;
54956            c = d;
54957            d = a2;
54958          }
54959        };
54960
54961        return AES;
54962      }();
54963      /**
54964       * @file stream.js
54965       */
54966
54967      /**
54968       * A lightweight readable stream implemention that handles event dispatching.
54969       *
54970       * @class Stream
54971       */
54972
54973
54974      var Stream = function () {
54975        function Stream() {
54976          classCallCheck(this, Stream);
54977          this.listeners = {};
54978        }
54979        /**
54980         * Add a listener for a specified event type.
54981         *
54982         * @param {String} type the event name
54983         * @param {Function} listener the callback to be invoked when an event of
54984         * the specified type occurs
54985         */
54986
54987
54988        Stream.prototype.on = function on(type, listener) {
54989          if (!this.listeners[type]) {
54990            this.listeners[type] = [];
54991          }
54992
54993          this.listeners[type].push(listener);
54994        };
54995        /**
54996         * Remove a listener for a specified event type.
54997         *
54998         * @param {String} type the event name
54999         * @param {Function} listener  a function previously registered for this
55000         * type of event through `on`
55001         * @return {Boolean} if we could turn it off or not
55002         */
55003
55004
55005        Stream.prototype.off = function off(type, listener) {
55006          if (!this.listeners[type]) {
55007            return false;
55008          }
55009
55010          var index = this.listeners[type].indexOf(listener);
55011          this.listeners[type].splice(index, 1);
55012          return index > -1;
55013        };
55014        /**
55015         * Trigger an event of the specified type on this stream. Any additional
55016         * arguments to this function are passed as parameters to event listeners.
55017         *
55018         * @param {String} type the event name
55019         */
55020
55021
55022        Stream.prototype.trigger = function trigger(type) {
55023          var callbacks = this.listeners[type];
55024
55025          if (!callbacks) {
55026            return;
55027          } // Slicing the arguments on every invocation of this method
55028          // can add a significant amount of overhead. Avoid the
55029          // intermediate object creation for the common case of a
55030          // single callback argument
55031
55032
55033          if (arguments.length === 2) {
55034            var length = callbacks.length;
55035
55036            for (var i = 0; i < length; ++i) {
55037              callbacks[i].call(this, arguments[1]);
55038            }
55039          } else {
55040            var args = Array.prototype.slice.call(arguments, 1);
55041            var _length = callbacks.length;
55042
55043            for (var _i = 0; _i < _length; ++_i) {
55044              callbacks[_i].apply(this, args);
55045            }
55046          }
55047        };
55048        /**
55049         * Destroys the stream and cleans up.
55050         */
55051
55052
55053        Stream.prototype.dispose = function dispose() {
55054          this.listeners = {};
55055        };
55056        /**
55057         * Forwards all `data` events on this stream to the destination stream. The
55058         * destination stream should provide a method `push` to receive the data
55059         * events as they arrive.
55060         *
55061         * @param {Stream} destination the stream that will receive all `data` events
55062         * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
55063         */
55064
55065
55066        Stream.prototype.pipe = function pipe(destination) {
55067          this.on('data', function (data) {
55068            destination.push(data);
55069          });
55070        };
55071
55072        return Stream;
55073      }();
55074      /**
55075       * @file async-stream.js
55076       */
55077
55078      /**
55079       * A wrapper around the Stream class to use setTiemout
55080       * and run stream "jobs" Asynchronously
55081       *
55082       * @class AsyncStream
55083       * @extends Stream
55084       */
55085
55086
55087      var AsyncStream$$1 = function (_Stream) {
55088        inherits(AsyncStream$$1, _Stream);
55089
55090        function AsyncStream$$1() {
55091          classCallCheck(this, AsyncStream$$1);
55092
55093          var _this = possibleConstructorReturn(this, _Stream.call(this, Stream));
55094
55095          _this.jobs = [];
55096          _this.delay = 1;
55097          _this.timeout_ = null;
55098          return _this;
55099        }
55100        /**
55101         * process an async job
55102         *
55103         * @private
55104         */
55105
55106
55107        AsyncStream$$1.prototype.processJob_ = function processJob_() {
55108          this.jobs.shift()();
55109
55110          if (this.jobs.length) {
55111            this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
55112          } else {
55113            this.timeout_ = null;
55114          }
55115        };
55116        /**
55117         * push a job into the stream
55118         *
55119         * @param {Function} job the job to push into the stream
55120         */
55121
55122
55123        AsyncStream$$1.prototype.push = function push(job) {
55124          this.jobs.push(job);
55125
55126          if (!this.timeout_) {
55127            this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
55128          }
55129        };
55130
55131        return AsyncStream$$1;
55132      }(Stream);
55133      /**
55134       * @file decrypter.js
55135       *
55136       * An asynchronous implementation of AES-128 CBC decryption with
55137       * PKCS#7 padding.
55138       */
55139
55140      /**
55141       * Convert network-order (big-endian) bytes into their little-endian
55142       * representation.
55143       */
55144
55145
55146      var ntoh = function ntoh(word) {
55147        return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
55148      };
55149      /**
55150       * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
55151       *
55152       * @param {Uint8Array} encrypted the encrypted bytes
55153       * @param {Uint32Array} key the bytes of the decryption key
55154       * @param {Uint32Array} initVector the initialization vector (IV) to
55155       * use for the first round of CBC.
55156       * @return {Uint8Array} the decrypted bytes
55157       *
55158       * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
55159       * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
55160       * @see https://tools.ietf.org/html/rfc2315
55161       */
55162
55163
55164      var decrypt$$1 = function decrypt$$1(encrypted, key, initVector) {
55165        // word-level access to the encrypted bytes
55166        var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
55167        var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
55168
55169        var decrypted = new Uint8Array(encrypted.byteLength);
55170        var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
55171        // decrypted data
55172
55173        var init0 = void 0;
55174        var init1 = void 0;
55175        var init2 = void 0;
55176        var init3 = void 0;
55177        var encrypted0 = void 0;
55178        var encrypted1 = void 0;
55179        var encrypted2 = void 0;
55180        var encrypted3 = void 0; // iteration variable
55181
55182        var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the
55183        // passed-in reference and easier access
55184
55185        init0 = initVector[0];
55186        init1 = initVector[1];
55187        init2 = initVector[2];
55188        init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
55189        // to each decrypted block
55190
55191        for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
55192          // convert big-endian (network order) words into little-endian
55193          // (javascript order)
55194          encrypted0 = ntoh(encrypted32[wordIx]);
55195          encrypted1 = ntoh(encrypted32[wordIx + 1]);
55196          encrypted2 = ntoh(encrypted32[wordIx + 2]);
55197          encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
55198
55199          decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
55200          // plaintext
55201
55202          decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
55203          decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
55204          decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
55205          decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
55206
55207          init0 = encrypted0;
55208          init1 = encrypted1;
55209          init2 = encrypted2;
55210          init3 = encrypted3;
55211        }
55212
55213        return decrypted;
55214      };
55215      /**
55216       * The `Decrypter` class that manages decryption of AES
55217       * data through `AsyncStream` objects and the `decrypt`
55218       * function
55219       *
55220       * @param {Uint8Array} encrypted the encrypted bytes
55221       * @param {Uint32Array} key the bytes of the decryption key
55222       * @param {Uint32Array} initVector the initialization vector (IV) to
55223       * @param {Function} done the function to run when done
55224       * @class Decrypter
55225       */
55226
55227
55228      var Decrypter$$1 = function () {
55229        function Decrypter$$1(encrypted, key, initVector, done) {
55230          classCallCheck(this, Decrypter$$1);
55231          var step = Decrypter$$1.STEP;
55232          var encrypted32 = new Int32Array(encrypted.buffer);
55233          var decrypted = new Uint8Array(encrypted.byteLength);
55234          var i = 0;
55235          this.asyncStream_ = new AsyncStream$$1(); // split up the encryption job and do the individual chunks asynchronously
55236
55237          this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
55238
55239          for (i = step; i < encrypted32.length; i += step) {
55240            initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
55241            this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
55242          } // invoke the done() callback when everything is finished
55243
55244
55245          this.asyncStream_.push(function () {
55246            // remove pkcs#7 padding from the decrypted bytes
55247            done(null, unpad(decrypted));
55248          });
55249        }
55250        /**
55251         * a getter for step the maximum number of bytes to process at one time
55252         *
55253         * @return {Number} the value of step 32000
55254         */
55255
55256        /**
55257         * @private
55258         */
55259
55260
55261        Decrypter$$1.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
55262          return function () {
55263            var bytes = decrypt$$1(encrypted, key, initVector);
55264            decrypted.set(bytes, encrypted.byteOffset);
55265          };
55266        };
55267
55268        createClass(Decrypter$$1, null, [{
55269          key: 'STEP',
55270          get: function get$$1() {
55271            // 4 * 8000;
55272            return 32000;
55273          }
55274        }]);
55275        return Decrypter$$1;
55276      }();
55277      /**
55278       * @file bin-utils.js
55279       */
55280
55281      /**
55282       * Creates an object for sending to a web worker modifying properties that are TypedArrays
55283       * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
55284       *
55285       * @param {Object} message
55286       *        Object of properties and values to send to the web worker
55287       * @return {Object}
55288       *         Modified message with TypedArray values expanded
55289       * @function createTransferableMessage
55290       */
55291
55292
55293      var createTransferableMessage = function createTransferableMessage(message) {
55294        var transferable = {};
55295        Object.keys(message).forEach(function (key) {
55296          var value = message[key];
55297
55298          if (ArrayBuffer.isView(value)) {
55299            transferable[key] = {
55300              bytes: value.buffer,
55301              byteOffset: value.byteOffset,
55302              byteLength: value.byteLength
55303            };
55304          } else {
55305            transferable[key] = value;
55306          }
55307        });
55308        return transferable;
55309      };
55310      /**
55311       * Our web worker interface so that things can talk to aes-decrypter
55312       * that will be running in a web worker. the scope is passed to this by
55313       * webworkify.
55314       *
55315       * @param {Object} self
55316       *        the scope for the web worker
55317       */
55318
55319
55320      var DecrypterWorker = function DecrypterWorker(self) {
55321        self.onmessage = function (event) {
55322          var data = event.data;
55323          var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength);
55324          var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
55325          var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
55326          /* eslint-disable no-new, handle-callback-err */
55327
55328          new Decrypter$$1(encrypted, key, iv, function (err, bytes) {
55329            self.postMessage(createTransferableMessage({
55330              source: data.source,
55331              decrypted: bytes
55332            }), [bytes.buffer]);
55333          });
55334          /* eslint-enable */
55335        };
55336      };
55337
55338      var decrypterWorker = new DecrypterWorker(self);
55339      return decrypterWorker;
55340    }();
55341  });
55342  /**
55343   * Convert the properties of an HLS track into an audioTrackKind.
55344   *
55345   * @private
55346   */
55347
55348  var audioTrackKind_ = function audioTrackKind_(properties) {
55349    var kind = properties.default ? 'main' : 'alternative';
55350
55351    if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
55352      kind = 'main-desc';
55353    }
55354
55355    return kind;
55356  };
55357  /**
55358   * Pause provided segment loader and playlist loader if active
55359   *
55360   * @param {SegmentLoader} segmentLoader
55361   *        SegmentLoader to pause
55362   * @param {Object} mediaType
55363   *        Active media type
55364   * @function stopLoaders
55365   */
55366
55367
55368  var stopLoaders = function stopLoaders(segmentLoader, mediaType) {
55369    segmentLoader.abort();
55370    segmentLoader.pause();
55371
55372    if (mediaType && mediaType.activePlaylistLoader) {
55373      mediaType.activePlaylistLoader.pause();
55374      mediaType.activePlaylistLoader = null;
55375    }
55376  };
55377  /**
55378   * Start loading provided segment loader and playlist loader
55379   *
55380   * @param {PlaylistLoader} playlistLoader
55381   *        PlaylistLoader to start loading
55382   * @param {Object} mediaType
55383   *        Active media type
55384   * @function startLoaders
55385   */
55386
55387
55388  var startLoaders = function startLoaders(playlistLoader, mediaType) {
55389    // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
55390    // playlist loader
55391    mediaType.activePlaylistLoader = playlistLoader;
55392    playlistLoader.load();
55393  };
55394  /**
55395   * Returns a function to be called when the media group changes. It performs a
55396   * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
55397   * change of group is merely a rendition switch of the same content at another encoding,
55398   * rather than a change of content, such as switching audio from English to Spanish.
55399   *
55400   * @param {String} type
55401   *        MediaGroup type
55402   * @param {Object} settings
55403   *        Object containing required information for media groups
55404   * @return {Function}
55405   *         Handler for a non-destructive resync of SegmentLoader when the active media
55406   *         group changes.
55407   * @function onGroupChanged
55408   */
55409
55410
55411  var onGroupChanged = function onGroupChanged(type, settings) {
55412    return function () {
55413      var _settings$segmentLoad = settings.segmentLoaders,
55414          segmentLoader = _settings$segmentLoad[type],
55415          mainSegmentLoader = _settings$segmentLoad.main,
55416          mediaType = settings.mediaTypes[type];
55417      var activeTrack = mediaType.activeTrack();
55418      var activeGroup = mediaType.activeGroup(activeTrack);
55419      var previousActiveLoader = mediaType.activePlaylistLoader;
55420      stopLoaders(segmentLoader, mediaType);
55421
55422      if (!activeGroup) {
55423        // there is no group active
55424        return;
55425      }
55426
55427      if (!activeGroup.playlistLoader) {
55428        if (previousActiveLoader) {
55429          // The previous group had a playlist loader but the new active group does not
55430          // this means we are switching from demuxed to muxed audio. In this case we want to
55431          // do a destructive reset of the main segment loader and not restart the audio
55432          // loaders.
55433          mainSegmentLoader.resetEverything();
55434        }
55435
55436        return;
55437      } // Non-destructive resync
55438
55439
55440      segmentLoader.resyncLoader();
55441      startLoaders(activeGroup.playlistLoader, mediaType);
55442    };
55443  };
55444  /**
55445   * Returns a function to be called when the media track changes. It performs a
55446   * destructive reset of the SegmentLoader to ensure we start loading as close to
55447   * currentTime as possible.
55448   *
55449   * @param {String} type
55450   *        MediaGroup type
55451   * @param {Object} settings
55452   *        Object containing required information for media groups
55453   * @return {Function}
55454   *         Handler for a destructive reset of SegmentLoader when the active media
55455   *         track changes.
55456   * @function onTrackChanged
55457   */
55458
55459
55460  var onTrackChanged = function onTrackChanged(type, settings) {
55461    return function () {
55462      var _settings$segmentLoad2 = settings.segmentLoaders,
55463          segmentLoader = _settings$segmentLoad2[type],
55464          mainSegmentLoader = _settings$segmentLoad2.main,
55465          mediaType = settings.mediaTypes[type];
55466      var activeTrack = mediaType.activeTrack();
55467      var activeGroup = mediaType.activeGroup(activeTrack);
55468      var previousActiveLoader = mediaType.activePlaylistLoader;
55469      stopLoaders(segmentLoader, mediaType);
55470
55471      if (!activeGroup) {
55472        // there is no group active so we do not want to restart loaders
55473        return;
55474      }
55475
55476      if (!activeGroup.playlistLoader) {
55477        // when switching from demuxed audio/video to muxed audio/video (noted by no playlist
55478        // loader for the audio group), we want to do a destructive reset of the main segment
55479        // loader and not restart the audio loaders
55480        mainSegmentLoader.resetEverything();
55481        return;
55482      }
55483
55484      if (previousActiveLoader === activeGroup.playlistLoader) {
55485        // Nothing has actually changed. This can happen because track change events can fire
55486        // multiple times for a "single" change. One for enabling the new active track, and
55487        // one for disabling the track that was active
55488        startLoaders(activeGroup.playlistLoader, mediaType);
55489        return;
55490      }
55491
55492      if (segmentLoader.track) {
55493        // For WebVTT, set the new text track in the segmentloader
55494        segmentLoader.track(activeTrack);
55495      } // destructive reset
55496
55497
55498      segmentLoader.resetEverything();
55499      startLoaders(activeGroup.playlistLoader, mediaType);
55500    };
55501  };
55502
55503  var onError = {
55504    /**
55505     * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
55506     * an error.
55507     *
55508     * @param {String} type
55509     *        MediaGroup type
55510     * @param {Object} settings
55511     *        Object containing required information for media groups
55512     * @return {Function}
55513     *         Error handler. Logs warning (or error if the playlist is blacklisted) to
55514     *         console and switches back to default audio track.
55515     * @function onError.AUDIO
55516     */
55517    AUDIO: function AUDIO(type, settings) {
55518      return function () {
55519        var segmentLoader = settings.segmentLoaders[type],
55520            mediaType = settings.mediaTypes[type],
55521            blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist;
55522        stopLoaders(segmentLoader, mediaType); // switch back to default audio track
55523
55524        var activeTrack = mediaType.activeTrack();
55525        var activeGroup = mediaType.activeGroup();
55526        var id = (activeGroup.filter(function (group) {
55527          return group.default;
55528        })[0] || activeGroup[0]).id;
55529        var defaultTrack = mediaType.tracks[id];
55530
55531        if (activeTrack === defaultTrack) {
55532          // Default track encountered an error. All we can do now is blacklist the current
55533          // rendition and hope another will switch audio groups
55534          blacklistCurrentPlaylist({
55535            message: 'Problem encountered loading the default audio track.'
55536          });
55537          return;
55538        }
55539
55540        videojs$1.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
55541
55542        for (var trackId in mediaType.tracks) {
55543          mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
55544        }
55545
55546        mediaType.onTrackChanged();
55547      };
55548    },
55549
55550    /**
55551     * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
55552     * an error.
55553     *
55554     * @param {String} type
55555     *        MediaGroup type
55556     * @param {Object} settings
55557     *        Object containing required information for media groups
55558     * @return {Function}
55559     *         Error handler. Logs warning to console and disables the active subtitle track
55560     * @function onError.SUBTITLES
55561     */
55562    SUBTITLES: function SUBTITLES(type, settings) {
55563      return function () {
55564        var segmentLoader = settings.segmentLoaders[type],
55565            mediaType = settings.mediaTypes[type];
55566        videojs$1.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
55567        stopLoaders(segmentLoader, mediaType);
55568        var track = mediaType.activeTrack();
55569
55570        if (track) {
55571          track.mode = 'disabled';
55572        }
55573
55574        mediaType.onTrackChanged();
55575      };
55576    }
55577  };
55578  var setupListeners = {
55579    /**
55580     * Setup event listeners for audio playlist loader
55581     *
55582     * @param {String} type
55583     *        MediaGroup type
55584     * @param {PlaylistLoader|null} playlistLoader
55585     *        PlaylistLoader to register listeners on
55586     * @param {Object} settings
55587     *        Object containing required information for media groups
55588     * @function setupListeners.AUDIO
55589     */
55590    AUDIO: function AUDIO(type, playlistLoader, settings) {
55591      if (!playlistLoader) {
55592        // no playlist loader means audio will be muxed with the video
55593        return;
55594      }
55595
55596      var tech = settings.tech,
55597          requestOptions = settings.requestOptions,
55598          segmentLoader = settings.segmentLoaders[type];
55599      playlistLoader.on('loadedmetadata', function () {
55600        var media = playlistLoader.media();
55601        segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload
55602        // permits, start downloading segments
55603
55604        if (!tech.paused() || media.endList && tech.preload() !== 'none') {
55605          segmentLoader.load();
55606        }
55607      });
55608      playlistLoader.on('loadedplaylist', function () {
55609        segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
55610
55611        if (!tech.paused()) {
55612          segmentLoader.load();
55613        }
55614      });
55615      playlistLoader.on('error', onError[type](type, settings));
55616    },
55617
55618    /**
55619     * Setup event listeners for subtitle playlist loader
55620     *
55621     * @param {String} type
55622     *        MediaGroup type
55623     * @param {PlaylistLoader|null} playlistLoader
55624     *        PlaylistLoader to register listeners on
55625     * @param {Object} settings
55626     *        Object containing required information for media groups
55627     * @function setupListeners.SUBTITLES
55628     */
55629    SUBTITLES: function SUBTITLES(type, playlistLoader, settings) {
55630      var tech = settings.tech,
55631          requestOptions = settings.requestOptions,
55632          segmentLoader = settings.segmentLoaders[type],
55633          mediaType = settings.mediaTypes[type];
55634      playlistLoader.on('loadedmetadata', function () {
55635        var media = playlistLoader.media();
55636        segmentLoader.playlist(media, requestOptions);
55637        segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload
55638        // permits, start downloading segments
55639
55640        if (!tech.paused() || media.endList && tech.preload() !== 'none') {
55641          segmentLoader.load();
55642        }
55643      });
55644      playlistLoader.on('loadedplaylist', function () {
55645        segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
55646
55647        if (!tech.paused()) {
55648          segmentLoader.load();
55649        }
55650      });
55651      playlistLoader.on('error', onError[type](type, settings));
55652    }
55653  };
55654
55655  var byGroupId = function byGroupId(type, groupId) {
55656    return function (playlist) {
55657      return playlist.attributes[type] === groupId;
55658    };
55659  };
55660
55661  var byResolvedUri = function byResolvedUri(resolvedUri) {
55662    return function (playlist) {
55663      return playlist.resolvedUri === resolvedUri;
55664    };
55665  };
55666
55667  var initialize = {
55668    /**
55669     * Setup PlaylistLoaders and AudioTracks for the audio groups
55670     *
55671     * @param {String} type
55672     *        MediaGroup type
55673     * @param {Object} settings
55674     *        Object containing required information for media groups
55675     * @function initialize.AUDIO
55676     */
55677    'AUDIO': function AUDIO(type, settings) {
55678      var hls = settings.hls,
55679          sourceType = settings.sourceType,
55680          segmentLoader = settings.segmentLoaders[type],
55681          requestOptions = settings.requestOptions,
55682          _settings$master = settings.master,
55683          mediaGroups = _settings$master.mediaGroups,
55684          playlists = _settings$master.playlists,
55685          _settings$mediaTypes$ = settings.mediaTypes[type],
55686          groups = _settings$mediaTypes$.groups,
55687          tracks = _settings$mediaTypes$.tracks,
55688          masterPlaylistLoader = settings.masterPlaylistLoader; // force a default if we have none
55689
55690      if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
55691        mediaGroups[type] = {
55692          main: {
55693            default: {
55694              default: true
55695            }
55696          }
55697        };
55698      }
55699
55700      for (var groupId in mediaGroups[type]) {
55701        if (!groups[groupId]) {
55702          groups[groupId] = [];
55703        } // List of playlists that have an AUDIO attribute value matching the current
55704        // group ID
55705
55706
55707        var groupPlaylists = playlists.filter(byGroupId(type, groupId));
55708
55709        for (var variantLabel in mediaGroups[type][groupId]) {
55710          var properties = mediaGroups[type][groupId][variantLabel]; // List of playlists for the current group ID that have a matching uri with
55711          // this alternate audio variant
55712
55713          var matchingPlaylists = groupPlaylists.filter(byResolvedUri(properties.resolvedUri));
55714
55715          if (matchingPlaylists.length) {
55716            // If there is a playlist that has the same uri as this audio variant, assume
55717            // that the playlist is audio only. We delete the resolvedUri property here
55718            // to prevent a playlist loader from being created so that we don't have
55719            // both the main and audio segment loaders loading the same audio segments
55720            // from the same playlist.
55721            delete properties.resolvedUri;
55722          }
55723
55724          var playlistLoader = void 0;
55725
55726          if (properties.resolvedUri) {
55727            playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions);
55728          } else if (properties.playlists && sourceType === 'dash') {
55729            playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader);
55730          } else {
55731            // no resolvedUri means the audio is muxed with the video when using this
55732            // audio track
55733            playlistLoader = null;
55734          }
55735
55736          properties = videojs$1.mergeOptions({
55737            id: variantLabel,
55738            playlistLoader: playlistLoader
55739          }, properties);
55740          setupListeners[type](type, properties.playlistLoader, settings);
55741          groups[groupId].push(properties);
55742
55743          if (typeof tracks[variantLabel] === 'undefined') {
55744            var track = new videojs$1.AudioTrack({
55745              id: variantLabel,
55746              kind: audioTrackKind_(properties),
55747              enabled: false,
55748              language: properties.language,
55749              default: properties.default,
55750              label: variantLabel
55751            });
55752            tracks[variantLabel] = track;
55753          }
55754        }
55755      } // setup single error event handler for the segment loader
55756
55757
55758      segmentLoader.on('error', onError[type](type, settings));
55759    },
55760
55761    /**
55762     * Setup PlaylistLoaders and TextTracks for the subtitle groups
55763     *
55764     * @param {String} type
55765     *        MediaGroup type
55766     * @param {Object} settings
55767     *        Object containing required information for media groups
55768     * @function initialize.SUBTITLES
55769     */
55770    'SUBTITLES': function SUBTITLES(type, settings) {
55771      var tech = settings.tech,
55772          hls = settings.hls,
55773          sourceType = settings.sourceType,
55774          segmentLoader = settings.segmentLoaders[type],
55775          requestOptions = settings.requestOptions,
55776          mediaGroups = settings.master.mediaGroups,
55777          _settings$mediaTypes$2 = settings.mediaTypes[type],
55778          groups = _settings$mediaTypes$2.groups,
55779          tracks = _settings$mediaTypes$2.tracks,
55780          masterPlaylistLoader = settings.masterPlaylistLoader;
55781
55782      for (var groupId in mediaGroups[type]) {
55783        if (!groups[groupId]) {
55784          groups[groupId] = [];
55785        }
55786
55787        for (var variantLabel in mediaGroups[type][groupId]) {
55788          if (mediaGroups[type][groupId][variantLabel].forced) {
55789            // Subtitle playlists with the forced attribute are not selectable in Safari.
55790            // According to Apple's HLS Authoring Specification:
55791            //   If content has forced subtitles and regular subtitles in a given language,
55792            //   the regular subtitles track in that language MUST contain both the forced
55793            //   subtitles and the regular subtitles for that language.
55794            // Because of this requirement and that Safari does not add forced subtitles,
55795            // forced subtitles are skipped here to maintain consistent experience across
55796            // all platforms
55797            continue;
55798          }
55799
55800          var properties = mediaGroups[type][groupId][variantLabel];
55801          var playlistLoader = void 0;
55802
55803          if (sourceType === 'hls') {
55804            playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions);
55805          } else if (sourceType === 'dash') {
55806            playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader);
55807          }
55808
55809          properties = videojs$1.mergeOptions({
55810            id: variantLabel,
55811            playlistLoader: playlistLoader
55812          }, properties);
55813          setupListeners[type](type, properties.playlistLoader, settings);
55814          groups[groupId].push(properties);
55815
55816          if (typeof tracks[variantLabel] === 'undefined') {
55817            var track = tech.addRemoteTextTrack({
55818              id: variantLabel,
55819              kind: 'subtitles',
55820              default: properties.default && properties.autoselect,
55821              language: properties.language,
55822              label: variantLabel
55823            }, false).track;
55824            tracks[variantLabel] = track;
55825          }
55826        }
55827      } // setup single error event handler for the segment loader
55828
55829
55830      segmentLoader.on('error', onError[type](type, settings));
55831    },
55832
55833    /**
55834     * Setup TextTracks for the closed-caption groups
55835     *
55836     * @param {String} type
55837     *        MediaGroup type
55838     * @param {Object} settings
55839     *        Object containing required information for media groups
55840     * @function initialize['CLOSED-CAPTIONS']
55841     */
55842    'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) {
55843      var tech = settings.tech,
55844          mediaGroups = settings.master.mediaGroups,
55845          _settings$mediaTypes$3 = settings.mediaTypes[type],
55846          groups = _settings$mediaTypes$3.groups,
55847          tracks = _settings$mediaTypes$3.tracks;
55848
55849      for (var groupId in mediaGroups[type]) {
55850        if (!groups[groupId]) {
55851          groups[groupId] = [];
55852        }
55853
55854        for (var variantLabel in mediaGroups[type][groupId]) {
55855          var properties = mediaGroups[type][groupId][variantLabel]; // We only support CEA608 captions for now, so ignore anything that
55856          // doesn't use a CCx INSTREAM-ID
55857
55858          if (!properties.instreamId.match(/CC\d/)) {
55859            continue;
55860          } // No PlaylistLoader is required for Closed-Captions because the captions are
55861          // embedded within the video stream
55862
55863
55864          groups[groupId].push(videojs$1.mergeOptions({
55865            id: variantLabel
55866          }, properties));
55867
55868          if (typeof tracks[variantLabel] === 'undefined') {
55869            var track = tech.addRemoteTextTrack({
55870              id: properties.instreamId,
55871              kind: 'captions',
55872              default: properties.default && properties.autoselect,
55873              language: properties.language,
55874              label: variantLabel
55875            }, false).track;
55876            tracks[variantLabel] = track;
55877          }
55878        }
55879      }
55880    }
55881  };
55882  /**
55883   * Returns a function used to get the active group of the provided type
55884   *
55885   * @param {String} type
55886   *        MediaGroup type
55887   * @param {Object} settings
55888   *        Object containing required information for media groups
55889   * @return {Function}
55890   *         Function that returns the active media group for the provided type. Takes an
55891   *         optional parameter {TextTrack} track. If no track is provided, a list of all
55892   *         variants in the group, otherwise the variant corresponding to the provided
55893   *         track is returned.
55894   * @function activeGroup
55895   */
55896
55897  var activeGroup = function activeGroup(type, settings) {
55898    return function (track) {
55899      var masterPlaylistLoader = settings.masterPlaylistLoader,
55900          groups = settings.mediaTypes[type].groups;
55901      var media = masterPlaylistLoader.media();
55902
55903      if (!media) {
55904        return null;
55905      }
55906
55907      var variants = null;
55908
55909      if (media.attributes[type]) {
55910        variants = groups[media.attributes[type]];
55911      }
55912
55913      variants = variants || groups.main;
55914
55915      if (typeof track === 'undefined') {
55916        return variants;
55917      }
55918
55919      if (track === null) {
55920        // An active track was specified so a corresponding group is expected. track === null
55921        // means no track is currently active so there is no corresponding group
55922        return null;
55923      }
55924
55925      return variants.filter(function (props) {
55926        return props.id === track.id;
55927      })[0] || null;
55928    };
55929  };
55930
55931  var activeTrack = {
55932    /**
55933     * Returns a function used to get the active track of type provided
55934     *
55935     * @param {String} type
55936     *        MediaGroup type
55937     * @param {Object} settings
55938     *        Object containing required information for media groups
55939     * @return {Function}
55940     *         Function that returns the active media track for the provided type. Returns
55941     *         null if no track is active
55942     * @function activeTrack.AUDIO
55943     */
55944    AUDIO: function AUDIO(type, settings) {
55945      return function () {
55946        var tracks = settings.mediaTypes[type].tracks;
55947
55948        for (var id in tracks) {
55949          if (tracks[id].enabled) {
55950            return tracks[id];
55951          }
55952        }
55953
55954        return null;
55955      };
55956    },
55957
55958    /**
55959     * Returns a function used to get the active track of type provided
55960     *
55961     * @param {String} type
55962     *        MediaGroup type
55963     * @param {Object} settings
55964     *        Object containing required information for media groups
55965     * @return {Function}
55966     *         Function that returns the active media track for the provided type. Returns
55967     *         null if no track is active
55968     * @function activeTrack.SUBTITLES
55969     */
55970    SUBTITLES: function SUBTITLES(type, settings) {
55971      return function () {
55972        var tracks = settings.mediaTypes[type].tracks;
55973
55974        for (var id in tracks) {
55975          if (tracks[id].mode === 'showing') {
55976            return tracks[id];
55977          }
55978        }
55979
55980        return null;
55981      };
55982    }
55983  };
55984  /**
55985   * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
55986   * Closed-Captions) specified in the master manifest.
55987   *
55988   * @param {Object} settings
55989   *        Object containing required information for setting up the media groups
55990   * @param {SegmentLoader} settings.segmentLoaders.AUDIO
55991   *        Audio segment loader
55992   * @param {SegmentLoader} settings.segmentLoaders.SUBTITLES
55993   *        Subtitle segment loader
55994   * @param {SegmentLoader} settings.segmentLoaders.main
55995   *        Main segment loader
55996   * @param {Tech} settings.tech
55997   *        The tech of the player
55998   * @param {Object} settings.requestOptions
55999   *        XHR request options used by the segment loaders
56000   * @param {PlaylistLoader} settings.masterPlaylistLoader
56001   *        PlaylistLoader for the master source
56002   * @param {HlsHandler} settings.hls
56003   *        HLS SourceHandler
56004   * @param {Object} settings.master
56005   *        The parsed master manifest
56006   * @param {Object} settings.mediaTypes
56007   *        Object to store the loaders, tracks, and utility methods for each media type
56008   * @param {Function} settings.blacklistCurrentPlaylist
56009   *        Blacklists the current rendition and forces a rendition switch.
56010   * @function setupMediaGroups
56011   */
56012
56013  var setupMediaGroups = function setupMediaGroups(settings) {
56014    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
56015      initialize[type](type, settings);
56016    });
56017    var mediaTypes = settings.mediaTypes,
56018        masterPlaylistLoader = settings.masterPlaylistLoader,
56019        tech = settings.tech,
56020        hls = settings.hls; // setup active group and track getters and change event handlers
56021
56022    ['AUDIO', 'SUBTITLES'].forEach(function (type) {
56023      mediaTypes[type].activeGroup = activeGroup(type, settings);
56024      mediaTypes[type].activeTrack = activeTrack[type](type, settings);
56025      mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
56026      mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
56027    }); // DO NOT enable the default subtitle or caption track.
56028    // DO enable the default audio track
56029
56030    var audioGroup = mediaTypes.AUDIO.activeGroup();
56031    var groupId = (audioGroup.filter(function (group) {
56032      return group.default;
56033    })[0] || audioGroup[0]).id;
56034    mediaTypes.AUDIO.tracks[groupId].enabled = true;
56035    mediaTypes.AUDIO.onTrackChanged();
56036    masterPlaylistLoader.on('mediachange', function () {
56037      ['AUDIO', 'SUBTITLES'].forEach(function (type) {
56038        return mediaTypes[type].onGroupChanged();
56039      });
56040    }); // custom audio track change event handler for usage event
56041
56042    var onAudioTrackChanged = function onAudioTrackChanged() {
56043      mediaTypes.AUDIO.onTrackChanged();
56044      tech.trigger({
56045        type: 'usage',
56046        name: 'hls-audio-change'
56047      });
56048    };
56049
56050    tech.audioTracks().addEventListener('change', onAudioTrackChanged);
56051    tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
56052    hls.on('dispose', function () {
56053      tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
56054      tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
56055    }); // clear existing audio tracks and add the ones we just created
56056
56057    tech.clearTracks('audio');
56058
56059    for (var id in mediaTypes.AUDIO.tracks) {
56060      tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
56061    }
56062  };
56063  /**
56064   * Creates skeleton object used to store the loaders, tracks, and utility methods for each
56065   * media type
56066   *
56067   * @return {Object}
56068   *         Object to store the loaders, tracks, and utility methods for each media type
56069   * @function createMediaTypes
56070   */
56071
56072
56073  var createMediaTypes = function createMediaTypes() {
56074    var mediaTypes = {};
56075    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
56076      mediaTypes[type] = {
56077        groups: {},
56078        tracks: {},
56079        activePlaylistLoader: null,
56080        activeGroup: noop$1,
56081        activeTrack: noop$1,
56082        onGroupChanged: noop$1,
56083        onTrackChanged: noop$1
56084      };
56085    });
56086    return mediaTypes;
56087  };
56088  /**
56089   * @file master-playlist-controller.js
56090   */
56091
56092
56093  var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2;
56094  var Hls = void 0; // SegmentLoader stats that need to have each loader's
56095  // values summed to calculate the final value
56096
56097  var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred'];
56098
56099  var sumLoaderStat = function sumLoaderStat(stat) {
56100    return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
56101  };
56102  /**
56103   * the master playlist controller controller all interactons
56104   * between playlists and segmentloaders. At this time this mainly
56105   * involves a master playlist and a series of audio playlists
56106   * if they are available
56107   *
56108   * @class MasterPlaylistController
56109   * @extends videojs.EventTarget
56110   */
56111
56112
56113  var MasterPlaylistController = function (_videojs$EventTarget) {
56114    inherits$1(MasterPlaylistController, _videojs$EventTarget);
56115
56116    function MasterPlaylistController(options) {
56117      classCallCheck$1(this, MasterPlaylistController);
56118
56119      var _this = possibleConstructorReturn$1(this, (MasterPlaylistController.__proto__ || Object.getPrototypeOf(MasterPlaylistController)).call(this));
56120
56121      var url = options.url,
56122          handleManifestRedirects = options.handleManifestRedirects,
56123          withCredentials = options.withCredentials,
56124          tech = options.tech,
56125          bandwidth = options.bandwidth,
56126          externHls = options.externHls,
56127          useCueTags = options.useCueTags,
56128          blacklistDuration = options.blacklistDuration,
56129          enableLowInitialPlaylist = options.enableLowInitialPlaylist,
56130          sourceType = options.sourceType,
56131          seekTo = options.seekTo;
56132
56133      if (!url) {
56134        throw new Error('A non-empty playlist URL is required');
56135      }
56136
56137      Hls = externHls;
56138      _this.withCredentials = withCredentials;
56139      _this.tech_ = tech;
56140      _this.hls_ = tech.hls;
56141      _this.seekTo_ = seekTo;
56142      _this.sourceType_ = sourceType;
56143      _this.useCueTags_ = useCueTags;
56144      _this.blacklistDuration = blacklistDuration;
56145      _this.enableLowInitialPlaylist = enableLowInitialPlaylist;
56146
56147      if (_this.useCueTags_) {
56148        _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues');
56149        _this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
56150      }
56151
56152      _this.requestOptions_ = {
56153        withCredentials: withCredentials,
56154        handleManifestRedirects: handleManifestRedirects,
56155        timeout: null
56156      };
56157      _this.mediaTypes_ = createMediaTypes();
56158      _this.mediaSource = new videojs$1.MediaSource(); // load the media source into the player
56159
56160      _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_.bind(_this));
56161
56162      _this.seekable_ = videojs$1.createTimeRanges();
56163
56164      _this.hasPlayed_ = function () {
56165        return false;
56166      };
56167
56168      _this.syncController_ = new SyncController(options);
56169      _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
56170        kind: 'metadata',
56171        label: 'segment-metadata'
56172      }, false).track;
56173      _this.decrypter_ = new Decrypter$1();
56174      _this.inbandTextTracks_ = {};
56175      var segmentLoaderSettings = {
56176        hls: _this.hls_,
56177        mediaSource: _this.mediaSource,
56178        currentTime: _this.tech_.currentTime.bind(_this.tech_),
56179        seekable: function seekable$$1() {
56180          return _this.seekable();
56181        },
56182        seeking: function seeking() {
56183          return _this.tech_.seeking();
56184        },
56185        duration: function duration$$1() {
56186          return _this.mediaSource.duration;
56187        },
56188        hasPlayed: function hasPlayed() {
56189          return _this.hasPlayed_();
56190        },
56191        goalBufferLength: function goalBufferLength() {
56192          return _this.goalBufferLength();
56193        },
56194        bandwidth: bandwidth,
56195        syncController: _this.syncController_,
56196        decrypter: _this.decrypter_,
56197        sourceType: _this.sourceType_,
56198        inbandTextTracks: _this.inbandTextTracks_
56199      };
56200      _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(url, _this.hls_, _this.requestOptions_) : new PlaylistLoader(url, _this.hls_, _this.requestOptions_);
56201
56202      _this.setupMasterPlaylistLoaderListeners_(); // setup segment loaders
56203      // combined audio/video or just video when alternate audio track is selected
56204
56205
56206      _this.mainSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
56207        segmentMetadataTrack: _this.segmentMetadataTrack_,
56208        loaderType: 'main'
56209      }), options); // alternate audio track
56210
56211      _this.audioSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
56212        loaderType: 'audio'
56213      }), options);
56214      _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
56215        loaderType: 'vtt'
56216      }), options);
56217
56218      _this.setupSegmentLoaderListeners_(); // Create SegmentLoader stat-getters
56219
56220
56221      loaderStats.forEach(function (stat) {
56222        _this[stat + '_'] = sumLoaderStat.bind(_this, stat);
56223      });
56224      _this.logger_ = logger('MPC');
56225
56226      _this.masterPlaylistLoader_.load();
56227
56228      return _this;
56229    }
56230    /**
56231     * Register event handlers on the master playlist loader. A helper
56232     * function for construction time.
56233     *
56234     * @private
56235     */
56236
56237
56238    createClass$1(MasterPlaylistController, [{
56239      key: 'setupMasterPlaylistLoaderListeners_',
56240      value: function setupMasterPlaylistLoaderListeners_() {
56241        var _this2 = this;
56242
56243        this.masterPlaylistLoader_.on('loadedmetadata', function () {
56244          var media = _this2.masterPlaylistLoader_.media();
56245
56246          var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
56247          // timeout the request.
56248
56249          if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
56250            _this2.requestOptions_.timeout = 0;
56251          } else {
56252            _this2.requestOptions_.timeout = requestTimeout;
56253          } // if this isn't a live video and preload permits, start
56254          // downloading segments
56255
56256
56257          if (media.endList && _this2.tech_.preload() !== 'none') {
56258            _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
56259
56260            _this2.mainSegmentLoader_.load();
56261          }
56262
56263          setupMediaGroups({
56264            sourceType: _this2.sourceType_,
56265            segmentLoaders: {
56266              AUDIO: _this2.audioSegmentLoader_,
56267              SUBTITLES: _this2.subtitleSegmentLoader_,
56268              main: _this2.mainSegmentLoader_
56269            },
56270            tech: _this2.tech_,
56271            requestOptions: _this2.requestOptions_,
56272            masterPlaylistLoader: _this2.masterPlaylistLoader_,
56273            hls: _this2.hls_,
56274            master: _this2.master(),
56275            mediaTypes: _this2.mediaTypes_,
56276            blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2)
56277          });
56278
56279          _this2.triggerPresenceUsage_(_this2.master(), media);
56280
56281          try {
56282            _this2.setupSourceBuffers_();
56283          } catch (e) {
56284            videojs$1.log.warn('Failed to create SourceBuffers', e);
56285            return _this2.mediaSource.endOfStream('decode');
56286          }
56287
56288          _this2.setupFirstPlay();
56289
56290          if (!_this2.mediaTypes_.AUDIO.activePlaylistLoader || _this2.mediaTypes_.AUDIO.activePlaylistLoader.media()) {
56291            _this2.trigger('selectedinitialmedia');
56292          } else {
56293            // We must wait for the active audio playlist loader to
56294            // finish setting up before triggering this event so the
56295            // representations API and EME setup is correct
56296            _this2.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', function () {
56297              _this2.trigger('selectedinitialmedia');
56298            });
56299          }
56300        });
56301        this.masterPlaylistLoader_.on('loadedplaylist', function () {
56302          var updatedPlaylist = _this2.masterPlaylistLoader_.media();
56303
56304          if (!updatedPlaylist) {
56305            // blacklist any variants that are not supported by the browser before selecting
56306            // an initial media as the playlist selectors do not consider browser support
56307            _this2.excludeUnsupportedVariants_();
56308
56309            var selectedMedia = void 0;
56310
56311            if (_this2.enableLowInitialPlaylist) {
56312              selectedMedia = _this2.selectInitialPlaylist();
56313            }
56314
56315            if (!selectedMedia) {
56316              selectedMedia = _this2.selectPlaylist();
56317            }
56318
56319            _this2.initialMedia_ = selectedMedia;
56320
56321            _this2.masterPlaylistLoader_.media(_this2.initialMedia_);
56322
56323            return;
56324          }
56325
56326          if (_this2.useCueTags_) {
56327            _this2.updateAdCues_(updatedPlaylist);
56328          } // TODO: Create a new event on the PlaylistLoader that signals
56329          // that the segments have changed in some way and use that to
56330          // update the SegmentLoader instead of doing it twice here and
56331          // on `mediachange`
56332
56333
56334          _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_);
56335
56336          _this2.updateDuration(); // If the player isn't paused, ensure that the segment loader is running,
56337          // as it is possible that it was temporarily stopped while waiting for
56338          // a playlist (e.g., in case the playlist errored and we re-requested it).
56339
56340
56341          if (!_this2.tech_.paused()) {
56342            _this2.mainSegmentLoader_.load();
56343
56344            if (_this2.audioSegmentLoader_) {
56345              _this2.audioSegmentLoader_.load();
56346            }
56347          }
56348
56349          if (!updatedPlaylist.endList) {
56350            var addSeekableRange = function addSeekableRange() {
56351              var seekable$$1 = _this2.seekable();
56352
56353              if (seekable$$1.length !== 0) {
56354                _this2.mediaSource.addSeekableRange_(seekable$$1.start(0), seekable$$1.end(0));
56355              }
56356            };
56357
56358            if (_this2.duration() !== Infinity) {
56359              var onDurationchange = function onDurationchange() {
56360                if (_this2.duration() === Infinity) {
56361                  addSeekableRange();
56362                } else {
56363                  _this2.tech_.one('durationchange', onDurationchange);
56364                }
56365              };
56366
56367              _this2.tech_.one('durationchange', onDurationchange);
56368            } else {
56369              addSeekableRange();
56370            }
56371          }
56372        });
56373        this.masterPlaylistLoader_.on('error', function () {
56374          _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error);
56375        });
56376        this.masterPlaylistLoader_.on('mediachanging', function () {
56377          _this2.mainSegmentLoader_.abort();
56378
56379          _this2.mainSegmentLoader_.pause();
56380        });
56381        this.masterPlaylistLoader_.on('mediachange', function () {
56382          var media = _this2.masterPlaylistLoader_.media();
56383
56384          var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
56385          // timeout the request.
56386
56387          if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
56388            _this2.requestOptions_.timeout = 0;
56389          } else {
56390            _this2.requestOptions_.timeout = requestTimeout;
56391          } // TODO: Create a new event on the PlaylistLoader that signals
56392          // that the segments have changed in some way and use that to
56393          // update the SegmentLoader instead of doing it twice here and
56394          // on `loadedplaylist`
56395
56396
56397          _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
56398
56399          _this2.mainSegmentLoader_.load();
56400
56401          _this2.tech_.trigger({
56402            type: 'mediachange',
56403            bubbles: true
56404          });
56405        });
56406        this.masterPlaylistLoader_.on('playlistunchanged', function () {
56407          var updatedPlaylist = _this2.masterPlaylistLoader_.media();
56408
56409          var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist);
56410
56411          if (playlistOutdated) {
56412            // Playlist has stopped updating and we're stuck at its end. Try to
56413            // blacklist it and switch to another playlist in the hope that that
56414            // one is updating (and give the player a chance to re-adjust to the
56415            // safe live point).
56416            _this2.blacklistCurrentPlaylist({
56417              message: 'Playlist no longer updating.'
56418            }); // useful for monitoring QoS
56419
56420
56421            _this2.tech_.trigger('playliststuck');
56422          }
56423        });
56424        this.masterPlaylistLoader_.on('renditiondisabled', function () {
56425          _this2.tech_.trigger({
56426            type: 'usage',
56427            name: 'hls-rendition-disabled'
56428          });
56429        });
56430        this.masterPlaylistLoader_.on('renditionenabled', function () {
56431          _this2.tech_.trigger({
56432            type: 'usage',
56433            name: 'hls-rendition-enabled'
56434          });
56435        });
56436      }
56437      /**
56438       * A helper function for triggerring presence usage events once per source
56439       *
56440       * @private
56441       */
56442
56443    }, {
56444      key: 'triggerPresenceUsage_',
56445      value: function triggerPresenceUsage_(master, media) {
56446        var mediaGroups = master.mediaGroups || {};
56447        var defaultDemuxed = true;
56448        var audioGroupKeys = Object.keys(mediaGroups.AUDIO);
56449
56450        for (var mediaGroup in mediaGroups.AUDIO) {
56451          for (var label in mediaGroups.AUDIO[mediaGroup]) {
56452            var properties = mediaGroups.AUDIO[mediaGroup][label];
56453
56454            if (!properties.uri) {
56455              defaultDemuxed = false;
56456            }
56457          }
56458        }
56459
56460        if (defaultDemuxed) {
56461          this.tech_.trigger({
56462            type: 'usage',
56463            name: 'hls-demuxed'
56464          });
56465        }
56466
56467        if (Object.keys(mediaGroups.SUBTITLES).length) {
56468          this.tech_.trigger({
56469            type: 'usage',
56470            name: 'hls-webvtt'
56471          });
56472        }
56473
56474        if (Hls.Playlist.isAes(media)) {
56475          this.tech_.trigger({
56476            type: 'usage',
56477            name: 'hls-aes'
56478          });
56479        }
56480
56481        if (Hls.Playlist.isFmp4(media)) {
56482          this.tech_.trigger({
56483            type: 'usage',
56484            name: 'hls-fmp4'
56485          });
56486        }
56487
56488        if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
56489          this.tech_.trigger({
56490            type: 'usage',
56491            name: 'hls-alternate-audio'
56492          });
56493        }
56494
56495        if (this.useCueTags_) {
56496          this.tech_.trigger({
56497            type: 'usage',
56498            name: 'hls-playlist-cue-tags'
56499          });
56500        }
56501      }
56502      /**
56503       * Register event handlers on the segment loaders. A helper function
56504       * for construction time.
56505       *
56506       * @private
56507       */
56508
56509    }, {
56510      key: 'setupSegmentLoaderListeners_',
56511      value: function setupSegmentLoaderListeners_() {
56512        var _this3 = this;
56513
56514        this.mainSegmentLoader_.on('bandwidthupdate', function () {
56515          var nextPlaylist = _this3.selectPlaylist();
56516
56517          var currentPlaylist = _this3.masterPlaylistLoader_.media();
56518
56519          var buffered = _this3.tech_.buffered();
56520
56521          var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0;
56522
56523          var bufferLowWaterLine = _this3.bufferLowWaterLine(); // If the playlist is live, then we want to not take low water line into account.
56524          // This is because in LIVE, the player plays 3 segments from the end of the
56525          // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
56526          // in those segments, a viewer will never experience a rendition upswitch.
56527
56528
56529          if (!currentPlaylist.endList || // For the same reason as LIVE, we ignore the low water line when the VOD
56530          // duration is below the max potential low water line
56531          _this3.duration() < Config.MAX_BUFFER_LOW_WATER_LINE || // we want to switch down to lower resolutions quickly to continue playback, but
56532          nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH || // ensure we have some buffer before we switch up to prevent us running out of
56533          // buffer while loading a higher rendition.
56534          forwardBuffer >= bufferLowWaterLine) {
56535            _this3.masterPlaylistLoader_.media(nextPlaylist);
56536          }
56537
56538          _this3.tech_.trigger('bandwidthupdate');
56539        });
56540        this.mainSegmentLoader_.on('progress', function () {
56541          _this3.trigger('progress');
56542        });
56543        this.mainSegmentLoader_.on('error', function () {
56544          _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error());
56545        });
56546        this.mainSegmentLoader_.on('syncinfoupdate', function () {
56547          _this3.onSyncInfoUpdate_();
56548        });
56549        this.mainSegmentLoader_.on('timestampoffset', function () {
56550          _this3.tech_.trigger({
56551            type: 'usage',
56552            name: 'hls-timestamp-offset'
56553          });
56554        });
56555        this.audioSegmentLoader_.on('syncinfoupdate', function () {
56556          _this3.onSyncInfoUpdate_();
56557        });
56558        this.mainSegmentLoader_.on('ended', function () {
56559          _this3.onEndOfStream();
56560        });
56561        this.mainSegmentLoader_.on('earlyabort', function () {
56562          _this3.blacklistCurrentPlaylist({
56563            message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.'
56564          }, ABORT_EARLY_BLACKLIST_SECONDS);
56565        });
56566        this.mainSegmentLoader_.on('reseteverything', function () {
56567          // If playing an MTS stream, a videojs.MediaSource is listening for
56568          // hls-reset to reset caption parsing state in the transmuxer
56569          _this3.tech_.trigger('hls-reset');
56570        });
56571        this.mainSegmentLoader_.on('segmenttimemapping', function (event) {
56572          // If playing an MTS stream in html, a videojs.MediaSource is listening for
56573          // hls-segment-time-mapping update its internal mapping of stream to display time
56574          _this3.tech_.trigger({
56575            type: 'hls-segment-time-mapping',
56576            mapping: event.mapping
56577          });
56578        });
56579        this.audioSegmentLoader_.on('ended', function () {
56580          _this3.onEndOfStream();
56581        });
56582      }
56583    }, {
56584      key: 'mediaSecondsLoaded_',
56585      value: function mediaSecondsLoaded_() {
56586        return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
56587      }
56588      /**
56589       * Call load on our SegmentLoaders
56590       */
56591
56592    }, {
56593      key: 'load',
56594      value: function load() {
56595        this.mainSegmentLoader_.load();
56596
56597        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
56598          this.audioSegmentLoader_.load();
56599        }
56600
56601        if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
56602          this.subtitleSegmentLoader_.load();
56603        }
56604      }
56605      /**
56606       * Re-tune playback quality level for the current player
56607       * conditions without performing destructive actions, like
56608       * removing already buffered content
56609       *
56610       * @private
56611       */
56612
56613    }, {
56614      key: 'smoothQualityChange_',
56615      value: function smoothQualityChange_() {
56616        var media = this.selectPlaylist();
56617
56618        if (media !== this.masterPlaylistLoader_.media()) {
56619          this.masterPlaylistLoader_.media(media);
56620          this.mainSegmentLoader_.resetLoader(); // don't need to reset audio as it is reset when media changes
56621        }
56622      }
56623      /**
56624       * Re-tune playback quality level for the current player
56625       * conditions. This method will perform destructive actions like removing
56626       * already buffered content in order to readjust the currently active
56627       * playlist quickly. This is good for manual quality changes
56628       *
56629       * @private
56630       */
56631
56632    }, {
56633      key: 'fastQualityChange_',
56634      value: function fastQualityChange_() {
56635        var _this4 = this;
56636
56637        var media = this.selectPlaylist();
56638
56639        if (media === this.masterPlaylistLoader_.media()) {
56640          return;
56641        }
56642
56643        this.masterPlaylistLoader_.media(media); // Delete all buffered data to allow an immediate quality switch, then seek to give
56644        // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds
56645        // ahead is roughly the minimum that will accomplish this across a variety of content
56646        // in IE and Edge, but seeking in place is sufficient on all other browsers)
56647        // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/
56648        // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904
56649
56650        this.mainSegmentLoader_.resetEverything(function () {
56651          // Since this is not a typical seek, we avoid the seekTo method which can cause segments
56652          // from the previously enabled rendition to load before the new playlist has finished loading
56653          if (videojs$1.browser.IE_VERSION || videojs$1.browser.IS_EDGE) {
56654            _this4.tech_.setCurrentTime(_this4.tech_.currentTime() + 0.04);
56655          } else {
56656            _this4.tech_.setCurrentTime(_this4.tech_.currentTime());
56657          }
56658        }); // don't need to reset audio as it is reset when media changes
56659      }
56660      /**
56661       * Begin playback.
56662       */
56663
56664    }, {
56665      key: 'play',
56666      value: function play() {
56667        if (this.setupFirstPlay()) {
56668          return;
56669        }
56670
56671        if (this.tech_.ended()) {
56672          this.seekTo_(0);
56673        }
56674
56675        if (this.hasPlayed_()) {
56676          this.load();
56677        }
56678
56679        var seekable$$1 = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window,
56680        // seek forward to the live point
56681
56682        if (this.tech_.duration() === Infinity) {
56683          if (this.tech_.currentTime() < seekable$$1.start(0)) {
56684            return this.seekTo_(seekable$$1.end(seekable$$1.length - 1));
56685          }
56686        }
56687      }
56688      /**
56689       * Seek to the latest media position if this is a live video and the
56690       * player and video are loaded and initialized.
56691       */
56692
56693    }, {
56694      key: 'setupFirstPlay',
56695      value: function setupFirstPlay() {
56696        var _this5 = this;
56697
56698        var media = this.masterPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play
56699        //  If 1) there is no active media
56700        //     2) the player is paused
56701        //     3) the first play has already been setup
56702        // then exit early
56703
56704        if (!media || this.tech_.paused() || this.hasPlayed_()) {
56705          return false;
56706        } // when the video is a live stream
56707
56708
56709        if (!media.endList) {
56710          var seekable$$1 = this.seekable();
56711
56712          if (!seekable$$1.length) {
56713            // without a seekable range, the player cannot seek to begin buffering at the live
56714            // point
56715            return false;
56716          }
56717
56718          if (videojs$1.browser.IE_VERSION && this.tech_.readyState() === 0) {
56719            // IE11 throws an InvalidStateError if you try to set currentTime while the
56720            // readyState is 0, so it must be delayed until the tech fires loadedmetadata.
56721            this.tech_.one('loadedmetadata', function () {
56722              _this5.trigger('firstplay');
56723
56724              _this5.seekTo_(seekable$$1.end(0));
56725
56726              _this5.hasPlayed_ = function () {
56727                return true;
56728              };
56729            });
56730            return false;
56731          } // trigger firstplay to inform the source handler to ignore the next seek event
56732
56733
56734          this.trigger('firstplay'); // seek to the live point
56735
56736          this.seekTo_(seekable$$1.end(0));
56737        }
56738
56739        this.hasPlayed_ = function () {
56740          return true;
56741        }; // we can begin loading now that everything is ready
56742
56743
56744        this.load();
56745        return true;
56746      }
56747      /**
56748       * handle the sourceopen event on the MediaSource
56749       *
56750       * @private
56751       */
56752
56753    }, {
56754      key: 'handleSourceOpen_',
56755      value: function handleSourceOpen_() {
56756        // Only attempt to create the source buffer if none already exist.
56757        // handleSourceOpen is also called when we are "re-opening" a source buffer
56758        // after `endOfStream` has been called (in response to a seek for instance)
56759        try {
56760          this.setupSourceBuffers_();
56761        } catch (e) {
56762          videojs$1.log.warn('Failed to create Source Buffers', e);
56763          return this.mediaSource.endOfStream('decode');
56764        } // if autoplay is enabled, begin playback. This is duplicative of
56765        // code in video.js but is required because play() must be invoked
56766        // *after* the media source has opened.
56767
56768
56769        if (this.tech_.autoplay()) {
56770          var playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request
56771          // on browsers which return a promise
56772
56773          if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
56774            playPromise.then(null, function (e) {});
56775          }
56776        }
56777
56778        this.trigger('sourceopen');
56779      }
56780      /**
56781       * Calls endOfStream on the media source when all active stream types have called
56782       * endOfStream
56783       *
56784       * @param {string} streamType
56785       *        Stream type of the segment loader that called endOfStream
56786       * @private
56787       */
56788
56789    }, {
56790      key: 'onEndOfStream',
56791      value: function onEndOfStream() {
56792        var isEndOfStream = this.mainSegmentLoader_.ended_;
56793
56794        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
56795          // if the audio playlist loader exists, then alternate audio is active
56796          if (!this.mainSegmentLoader_.startingMedia_ || this.mainSegmentLoader_.startingMedia_.containsVideo) {
56797            // if we do not know if the main segment loader contains video yet or if we
56798            // definitively know the main segment loader contains video, then we need to wait
56799            // for both main and audio segment loaders to call endOfStream
56800            isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
56801          } else {
56802            // otherwise just rely on the audio loader
56803            isEndOfStream = this.audioSegmentLoader_.ended_;
56804          }
56805        }
56806
56807        if (!isEndOfStream) {
56808          return;
56809        }
56810
56811        this.logger_('calling mediaSource.endOfStream()'); // on chrome calling endOfStream can sometimes cause an exception,
56812        // even when the media source is in a valid state.
56813
56814        try {
56815          this.mediaSource.endOfStream();
56816        } catch (e) {
56817          videojs$1.log.warn('Failed to call media source endOfStream', e);
56818        }
56819      }
56820      /**
56821       * Check if a playlist has stopped being updated
56822       * @param {Object} playlist the media playlist object
56823       * @return {boolean} whether the playlist has stopped being updated or not
56824       */
56825
56826    }, {
56827      key: 'stuckAtPlaylistEnd_',
56828      value: function stuckAtPlaylistEnd_(playlist) {
56829        var seekable$$1 = this.seekable();
56830
56831        if (!seekable$$1.length) {
56832          // playlist doesn't have enough information to determine whether we are stuck
56833          return false;
56834        }
56835
56836        var expired = this.syncController_.getExpiredTime(playlist, this.mediaSource.duration);
56837
56838        if (expired === null) {
56839          return false;
56840        } // does not use the safe live end to calculate playlist end, since we
56841        // don't want to say we are stuck while there is still content
56842
56843
56844        var absolutePlaylistEnd = Hls.Playlist.playlistEnd(playlist, expired);
56845        var currentTime = this.tech_.currentTime();
56846        var buffered = this.tech_.buffered();
56847
56848        if (!buffered.length) {
56849          // return true if the playhead reached the absolute end of the playlist
56850          return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
56851        }
56852
56853        var bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute
56854        // end of playlist
56855
56856        return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA;
56857      }
56858      /**
56859       * Blacklists a playlist when an error occurs for a set amount of time
56860       * making it unavailable for selection by the rendition selection algorithm
56861       * and then forces a new playlist (rendition) selection.
56862       *
56863       * @param {Object=} error an optional error that may include the playlist
56864       * to blacklist
56865       * @param {Number=} blacklistDuration an optional number of seconds to blacklist the
56866       * playlist
56867       */
56868
56869    }, {
56870      key: 'blacklistCurrentPlaylist',
56871      value: function blacklistCurrentPlaylist() {
56872        var error = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
56873        var blacklistDuration = arguments[1];
56874        var currentPlaylist = void 0;
56875        var nextPlaylist = void 0; // If the `error` was generated by the playlist loader, it will contain
56876        // the playlist we were trying to load (but failed) and that should be
56877        // blacklisted instead of the currently selected playlist which is likely
56878        // out-of-date in this scenario
56879
56880        currentPlaylist = error.playlist || this.masterPlaylistLoader_.media();
56881        blacklistDuration = blacklistDuration || error.blacklistDuration || this.blacklistDuration; // If there is no current playlist, then an error occurred while we were
56882        // trying to load the master OR while we were disposing of the tech
56883
56884        if (!currentPlaylist) {
56885          this.error = error;
56886
56887          try {
56888            return this.mediaSource.endOfStream('network');
56889          } catch (e) {
56890            return this.trigger('error');
56891          }
56892        }
56893
56894        var isFinalRendition = this.masterPlaylistLoader_.master.playlists.filter(isEnabled).length === 1;
56895
56896        if (isFinalRendition) {
56897          // Never blacklisting this playlist because it's final rendition
56898          videojs$1.log.warn('Problem encountered with the current ' + 'HLS playlist. Trying again since it is the final playlist.');
56899          this.tech_.trigger('retryplaylist');
56900          return this.masterPlaylistLoader_.load(isFinalRendition);
56901        } // Blacklist this playlist
56902
56903
56904        currentPlaylist.excludeUntil = Date.now() + blacklistDuration * 1000;
56905        this.tech_.trigger('blacklistplaylist');
56906        this.tech_.trigger({
56907          type: 'usage',
56908          name: 'hls-rendition-blacklisted'
56909        }); // Select a new playlist
56910
56911        nextPlaylist = this.selectPlaylist();
56912        videojs$1.log.warn('Problem encountered with the current HLS playlist.' + (error.message ? ' ' + error.message : '') + ' Switching to another playlist.');
56913        return this.masterPlaylistLoader_.media(nextPlaylist);
56914      }
56915      /**
56916       * Pause all segment loaders
56917       */
56918
56919    }, {
56920      key: 'pauseLoading',
56921      value: function pauseLoading() {
56922        this.mainSegmentLoader_.pause();
56923
56924        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
56925          this.audioSegmentLoader_.pause();
56926        }
56927
56928        if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
56929          this.subtitleSegmentLoader_.pause();
56930        }
56931      }
56932      /**
56933       * set the current time on all segment loaders
56934       *
56935       * @param {TimeRange} currentTime the current time to set
56936       * @return {TimeRange} the current time
56937       */
56938
56939    }, {
56940      key: 'setCurrentTime',
56941      value: function setCurrentTime(currentTime) {
56942        var buffered = findRange(this.tech_.buffered(), currentTime);
56943
56944        if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) {
56945          // return immediately if the metadata is not ready yet
56946          return 0;
56947        } // it's clearly an edge-case but don't thrown an error if asked to
56948        // seek within an empty playlist
56949
56950
56951        if (!this.masterPlaylistLoader_.media().segments) {
56952          return 0;
56953        } // In flash playback, the segment loaders should be reset on every seek, even
56954        // in buffer seeks. If the seek location is already buffered, continue buffering as
56955        // usual
56956        // TODO: redo this comment
56957
56958
56959        if (buffered && buffered.length) {
56960          return currentTime;
56961        } // cancel outstanding requests so we begin buffering at the new
56962        // location
56963
56964
56965        this.mainSegmentLoader_.resetEverything();
56966        this.mainSegmentLoader_.abort();
56967
56968        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
56969          this.audioSegmentLoader_.resetEverything();
56970          this.audioSegmentLoader_.abort();
56971        }
56972
56973        if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
56974          this.subtitleSegmentLoader_.resetEverything();
56975          this.subtitleSegmentLoader_.abort();
56976        } // start segment loader loading in case they are paused
56977
56978
56979        this.load();
56980      }
56981      /**
56982       * get the current duration
56983       *
56984       * @return {TimeRange} the duration
56985       */
56986
56987    }, {
56988      key: 'duration',
56989      value: function duration$$1() {
56990        if (!this.masterPlaylistLoader_) {
56991          return 0;
56992        }
56993
56994        if (this.mediaSource) {
56995          return this.mediaSource.duration;
56996        }
56997
56998        return Hls.Playlist.duration(this.masterPlaylistLoader_.media());
56999      }
57000      /**
57001       * check the seekable range
57002       *
57003       * @return {TimeRange} the seekable range
57004       */
57005
57006    }, {
57007      key: 'seekable',
57008      value: function seekable$$1() {
57009        return this.seekable_;
57010      }
57011    }, {
57012      key: 'onSyncInfoUpdate_',
57013      value: function onSyncInfoUpdate_() {
57014        var mainSeekable = void 0;
57015        var audioSeekable = void 0;
57016
57017        if (!this.masterPlaylistLoader_) {
57018          return;
57019        }
57020
57021        var media = this.masterPlaylistLoader_.media();
57022
57023        if (!media) {
57024          return;
57025        }
57026
57027        var expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
57028
57029        if (expired === null) {
57030          // not enough information to update seekable
57031          return;
57032        }
57033
57034        mainSeekable = Hls.Playlist.seekable(media, expired);
57035
57036        if (mainSeekable.length === 0) {
57037          return;
57038        }
57039
57040        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
57041          media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
57042          expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
57043
57044          if (expired === null) {
57045            return;
57046          }
57047
57048          audioSeekable = Hls.Playlist.seekable(media, expired);
57049
57050          if (audioSeekable.length === 0) {
57051            return;
57052          }
57053        }
57054
57055        var oldEnd = void 0;
57056        var oldStart = void 0;
57057
57058        if (this.seekable_ && this.seekable_.length) {
57059          oldEnd = this.seekable_.end(0);
57060          oldStart = this.seekable_.start(0);
57061        }
57062
57063        if (!audioSeekable) {
57064          // seekable has been calculated based on buffering video data so it
57065          // can be returned directly
57066          this.seekable_ = mainSeekable;
57067        } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) {
57068          // seekables are pretty far off, rely on main
57069          this.seekable_ = mainSeekable;
57070        } else {
57071          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)]]);
57072        } // seekable is the same as last time
57073
57074
57075        if (this.seekable_ && this.seekable_.length) {
57076          if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) {
57077            return;
57078          }
57079        }
57080
57081        this.logger_('seekable updated [' + printableRange(this.seekable_) + ']');
57082        this.tech_.trigger('seekablechanged');
57083      }
57084      /**
57085       * Update the player duration
57086       */
57087
57088    }, {
57089      key: 'updateDuration',
57090      value: function updateDuration() {
57091        var _this6 = this;
57092
57093        var oldDuration = this.mediaSource.duration;
57094        var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media());
57095        var buffered = this.tech_.buffered();
57096
57097        var setDuration = function setDuration() {
57098          // on firefox setting the duration may sometimes cause an exception
57099          // even if the media source is open and source buffers are not
57100          // updating, something about the media source being in an invalid state.
57101          _this6.logger_('Setting duration from ' + _this6.mediaSource.duration + ' => ' + newDuration);
57102
57103          try {
57104            _this6.mediaSource.duration = newDuration;
57105          } catch (e) {
57106            videojs$1.log.warn('Failed to set media source duration', e);
57107          }
57108
57109          _this6.tech_.trigger('durationchange');
57110
57111          _this6.mediaSource.removeEventListener('sourceopen', setDuration);
57112        };
57113
57114        if (buffered.length > 0) {
57115          newDuration = Math.max(newDuration, buffered.end(buffered.length - 1));
57116        } // if the duration has changed, invalidate the cached value
57117
57118
57119        if (oldDuration !== newDuration) {
57120          // update the duration
57121          if (this.mediaSource.readyState !== 'open') {
57122            this.mediaSource.addEventListener('sourceopen', setDuration);
57123          } else {
57124            setDuration();
57125          }
57126        }
57127      }
57128      /**
57129       * dispose of the MasterPlaylistController and everything
57130       * that it controls
57131       */
57132
57133    }, {
57134      key: 'dispose',
57135      value: function dispose() {
57136        var _this7 = this;
57137
57138        this.decrypter_.terminate();
57139        this.masterPlaylistLoader_.dispose();
57140        this.mainSegmentLoader_.dispose();
57141        ['AUDIO', 'SUBTITLES'].forEach(function (type) {
57142          var groups = _this7.mediaTypes_[type].groups;
57143
57144          for (var id in groups) {
57145            groups[id].forEach(function (group) {
57146              if (group.playlistLoader) {
57147                group.playlistLoader.dispose();
57148              }
57149            });
57150          }
57151        });
57152        this.audioSegmentLoader_.dispose();
57153        this.subtitleSegmentLoader_.dispose();
57154      }
57155      /**
57156       * return the master playlist object if we have one
57157       *
57158       * @return {Object} the master playlist object that we parsed
57159       */
57160
57161    }, {
57162      key: 'master',
57163      value: function master() {
57164        return this.masterPlaylistLoader_.master;
57165      }
57166      /**
57167       * return the currently selected playlist
57168       *
57169       * @return {Object} the currently selected playlist object that we parsed
57170       */
57171
57172    }, {
57173      key: 'media',
57174      value: function media() {
57175        // playlist loader will not return media if it has not been fully loaded
57176        return this.masterPlaylistLoader_.media() || this.initialMedia_;
57177      }
57178      /**
57179       * setup our internal source buffers on our segment Loaders
57180       *
57181       * @private
57182       */
57183
57184    }, {
57185      key: 'setupSourceBuffers_',
57186      value: function setupSourceBuffers_() {
57187        var media = this.masterPlaylistLoader_.media();
57188        var mimeTypes = void 0; // wait until a media playlist is available and the Media Source is
57189        // attached
57190
57191        if (!media || this.mediaSource.readyState !== 'open') {
57192          return;
57193        }
57194
57195        mimeTypes = mimeTypesForPlaylist(this.masterPlaylistLoader_.master, media);
57196
57197        if (mimeTypes.length < 1) {
57198          this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri;
57199          return this.mediaSource.endOfStream('decode');
57200        }
57201
57202        this.configureLoaderMimeTypes_(mimeTypes); // exclude any incompatible variant streams from future playlist
57203        // selection
57204
57205        this.excludeIncompatibleVariants_(media);
57206      }
57207    }, {
57208      key: 'configureLoaderMimeTypes_',
57209      value: function configureLoaderMimeTypes_(mimeTypes) {
57210        // If the content is demuxed, we can't start appending segments to a source buffer
57211        // until both source buffers are set up, or else the browser may not let us add the
57212        // second source buffer (it will assume we are playing either audio only or video
57213        // only).
57214        var sourceBufferEmitter = // If there is more than one mime type
57215        mimeTypes.length > 1 && // and the first mime type does not have muxed video and audio
57216        mimeTypes[0].indexOf(',') === -1 && // and the two mime types are different (they can be the same in the case of audio
57217        // only with alternate audio)
57218        mimeTypes[0] !== mimeTypes[1] ? // then we want to wait on the second source buffer
57219        new videojs$1.EventTarget() : // otherwise there is no need to wait as the content is either audio only,
57220        // video only, or muxed content.
57221        null;
57222        this.mainSegmentLoader_.mimeType(mimeTypes[0], sourceBufferEmitter);
57223
57224        if (mimeTypes[1]) {
57225          this.audioSegmentLoader_.mimeType(mimeTypes[1], sourceBufferEmitter);
57226        }
57227      }
57228      /**
57229       * Blacklists playlists with codecs that are unsupported by the browser.
57230       */
57231
57232    }, {
57233      key: 'excludeUnsupportedVariants_',
57234      value: function excludeUnsupportedVariants_() {
57235        this.master().playlists.forEach(function (variant) {
57236          if (variant.attributes.CODECS && window$1.MediaSource && window$1.MediaSource.isTypeSupported && !window$1.MediaSource.isTypeSupported('video/mp4; codecs="' + mapLegacyAvcCodecs(variant.attributes.CODECS) + '"')) {
57237            variant.excludeUntil = Infinity;
57238          }
57239        });
57240      }
57241      /**
57242       * Blacklist playlists that are known to be codec or
57243       * stream-incompatible with the SourceBuffer configuration. For
57244       * instance, Media Source Extensions would cause the video element to
57245       * stall waiting for video data if you switched from a variant with
57246       * video and audio to an audio-only one.
57247       *
57248       * @param {Object} media a media playlist compatible with the current
57249       * set of SourceBuffers. Variants in the current master playlist that
57250       * do not appear to have compatible codec or stream configurations
57251       * will be excluded from the default playlist selection algorithm
57252       * indefinitely.
57253       * @private
57254       */
57255
57256    }, {
57257      key: 'excludeIncompatibleVariants_',
57258      value: function excludeIncompatibleVariants_(media) {
57259        var codecCount = 2;
57260        var videoCodec = null;
57261        var codecs = void 0;
57262
57263        if (media.attributes.CODECS) {
57264          codecs = parseCodecs(media.attributes.CODECS);
57265          videoCodec = codecs.videoCodec;
57266          codecCount = codecs.codecCount;
57267        }
57268
vendor: 32,178 bytes, lines 57269-58192
57269        this.master().playlists.forEach(function (variant) {
57270          var variantCodecs = {
57271            codecCount: 2,
57272            videoCodec: null
57273          };
57274
57275          if (variant.attributes.CODECS) {
57276            variantCodecs = parseCodecs(variant.attributes.CODECS);
57277          } // if the streams differ in the presence or absence of audio or
57278          // video, they are incompatible
57279
57280
57281          if (variantCodecs.codecCount !== codecCount) {
57282            variant.excludeUntil = Infinity;
57283          } // if h.264 is specified on the current playlist, some flavor of
57284          // it must be specified on all compatible variants
57285
57286
57287          if (variantCodecs.videoCodec !== videoCodec) {
57288            variant.excludeUntil = Infinity;
57289          }
57290        });
57291      }
57292    }, {
57293      key: 'updateAdCues_',
57294      value: function updateAdCues_(media) {
57295        var offset = 0;
57296        var seekable$$1 = this.seekable();
57297
57298        if (seekable$$1.length) {
57299          offset = seekable$$1.start(0);
57300        }
57301
57302        updateAdCues(media, this.cueTagsTrack_, offset);
57303      }
57304      /**
57305       * Calculates the desired forward buffer length based on current time
57306       *
57307       * @return {Number} Desired forward buffer length in seconds
57308       */
57309
57310    }, {
57311      key: 'goalBufferLength',
57312      value: function goalBufferLength() {
57313        var currentTime = this.tech_.currentTime();
57314        var initial = Config.GOAL_BUFFER_LENGTH;
57315        var rate = Config.GOAL_BUFFER_LENGTH_RATE;
57316        var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
57317        return Math.min(initial + currentTime * rate, max);
57318      }
57319      /**
57320       * Calculates the desired buffer low water line based on current time
57321       *
57322       * @return {Number} Desired buffer low water line in seconds
57323       */
57324
57325    }, {
57326      key: 'bufferLowWaterLine',
57327      value: function bufferLowWaterLine() {
57328        var currentTime = this.tech_.currentTime();
57329        var initial = Config.BUFFER_LOW_WATER_LINE;
57330        var rate = Config.BUFFER_LOW_WATER_LINE_RATE;
57331        var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
57332        return Math.min(initial + currentTime * rate, max);
57333      }
57334    }]);
57335    return MasterPlaylistController;
57336  }(videojs$1.EventTarget);
57337  /**
57338   * Returns a function that acts as the Enable/disable playlist function.
57339   *
57340   * @param {PlaylistLoader} loader - The master playlist loader
57341   * @param {String} playlistUri - uri of the playlist
57342   * @param {Function} changePlaylistFn - A function to be called after a
57343   * playlist's enabled-state has been changed. Will NOT be called if a
57344   * playlist's enabled-state is unchanged
57345   * @param {Boolean=} enable - Value to set the playlist enabled-state to
57346   * or if undefined returns the current enabled-state for the playlist
57347   * @return {Function} Function for setting/getting enabled
57348   */
57349
57350
57351  var enableFunction = function enableFunction(loader, playlistUri, changePlaylistFn) {
57352    return function (enable) {
57353      var playlist = loader.master.playlists[playlistUri];
57354      var incompatible = isIncompatible(playlist);
57355      var currentlyEnabled = isEnabled(playlist);
57356
57357      if (typeof enable === 'undefined') {
57358        return currentlyEnabled;
57359      }
57360
57361      if (enable) {
57362        delete playlist.disabled;
57363      } else {
57364        playlist.disabled = true;
57365      }
57366
57367      if (enable !== currentlyEnabled && !incompatible) {
57368        // Ensure the outside world knows about our changes
57369        changePlaylistFn();
57370
57371        if (enable) {
57372          loader.trigger('renditionenabled');
57373        } else {
57374          loader.trigger('renditiondisabled');
57375        }
57376      }
57377
57378      return enable;
57379    };
57380  };
57381  /**
57382   * The representation object encapsulates the publicly visible information
57383   * in a media playlist along with a setter/getter-type function (enabled)
57384   * for changing the enabled-state of a particular playlist entry
57385   *
57386   * @class Representation
57387   */
57388
57389
57390  var Representation = function Representation(hlsHandler, playlist, id) {
57391    classCallCheck$1(this, Representation);
57392    var mpc = hlsHandler.masterPlaylistController_,
57393        smoothQualityChange = hlsHandler.options_.smoothQualityChange; // Get a reference to a bound version of the quality change function
57394
57395    var changeType = smoothQualityChange ? 'smooth' : 'fast';
57396    var qualityChangeFunction = mpc[changeType + 'QualityChange_'].bind(mpc); // some playlist attributes are optional
57397
57398    if (playlist.attributes.RESOLUTION) {
57399      var resolution = playlist.attributes.RESOLUTION;
57400      this.width = resolution.width;
57401      this.height = resolution.height;
57402    }
57403
57404    this.bandwidth = playlist.attributes.BANDWIDTH; // The id is simply the ordinality of the media playlist
57405    // within the master playlist
57406
57407    this.id = id; // Partially-apply the enableFunction to create a playlist-
57408    // specific variant
57409
57410    this.enabled = enableFunction(hlsHandler.playlists, playlist.uri, qualityChangeFunction);
57411  };
57412  /**
57413   * A mixin function that adds the `representations` api to an instance
57414   * of the HlsHandler class
57415   * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the
57416   * representation API into
57417   */
57418
57419
57420  var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) {
57421    var playlists = hlsHandler.playlists; // Add a single API-specific function to the HlsHandler instance
57422
57423    hlsHandler.representations = function () {
57424      return playlists.master.playlists.filter(function (media) {
57425        return !isIncompatible(media);
57426      }).map(function (e, i) {
57427        return new Representation(hlsHandler, e, e.uri);
57428      });
57429    };
57430  };
57431  /**
57432   * @file playback-watcher.js
57433   *
57434   * Playback starts, and now my watch begins. It shall not end until my death. I shall
57435   * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
57436   * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
57437   * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
57438   * my life and honor to the Playback Watch, for this Player and all the Players to come.
57439   */
57440  // Set of events that reset the playback-watcher time check logic and clear the timeout
57441
57442
57443  var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
57444  /**
57445   * @class PlaybackWatcher
57446   */
57447
57448  var PlaybackWatcher = function () {
57449    /**
57450     * Represents an PlaybackWatcher object.
57451     * @constructor
57452     * @param {object} options an object that includes the tech and settings
57453     */
57454    function PlaybackWatcher(options) {
57455      var _this = this;
57456
57457      classCallCheck$1(this, PlaybackWatcher);
57458      this.tech_ = options.tech;
57459      this.seekable = options.seekable;
57460      this.seekTo = options.seekTo;
57461      this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow;
57462      this.media = options.media;
57463      this.consecutiveUpdates = 0;
57464      this.lastRecordedTime = null;
57465      this.timer_ = null;
57466      this.checkCurrentTimeTimeout_ = null;
57467      this.logger_ = logger('PlaybackWatcher');
57468      this.logger_('initialize');
57469
57470      var canPlayHandler = function canPlayHandler() {
57471        return _this.monitorCurrentTime_();
57472      };
57473
57474      var waitingHandler = function waitingHandler() {
57475        return _this.techWaiting_();
57476      };
57477
57478      var cancelTimerHandler = function cancelTimerHandler() {
57479        return _this.cancelTimer_();
57480      };
57481
57482      var fixesBadSeeksHandler = function fixesBadSeeksHandler() {
57483        return _this.fixesBadSeeks_();
57484      };
57485
57486      this.tech_.on('seekablechanged', fixesBadSeeksHandler);
57487      this.tech_.on('waiting', waitingHandler);
57488      this.tech_.on(timerCancelEvents, cancelTimerHandler);
57489      this.tech_.on('canplay', canPlayHandler); // Define the dispose function to clean up our events
57490
57491      this.dispose = function () {
57492        _this.logger_('dispose');
57493
57494        _this.tech_.off('seekablechanged', fixesBadSeeksHandler);
57495
57496        _this.tech_.off('waiting', waitingHandler);
57497
57498        _this.tech_.off(timerCancelEvents, cancelTimerHandler);
57499
57500        _this.tech_.off('canplay', canPlayHandler);
57501
57502        if (_this.checkCurrentTimeTimeout_) {
57503          window$1.clearTimeout(_this.checkCurrentTimeTimeout_);
57504        }
57505
57506        _this.cancelTimer_();
57507      };
57508    }
57509    /**
57510     * Periodically check current time to see if playback stopped
57511     *
57512     * @private
57513     */
57514
57515
57516    createClass$1(PlaybackWatcher, [{
57517      key: 'monitorCurrentTime_',
57518      value: function monitorCurrentTime_() {
57519        this.checkCurrentTime_();
57520
57521        if (this.checkCurrentTimeTimeout_) {
57522          window$1.clearTimeout(this.checkCurrentTimeTimeout_);
57523        } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
57524
57525
57526        this.checkCurrentTimeTimeout_ = window$1.setTimeout(this.monitorCurrentTime_.bind(this), 250);
57527      }
57528      /**
57529       * The purpose of this function is to emulate the "waiting" event on
57530       * browsers that do not emit it when they are waiting for more
57531       * data to continue playback
57532       *
57533       * @private
57534       */
57535
57536    }, {
57537      key: 'checkCurrentTime_',
57538      value: function checkCurrentTime_() {
57539        if (this.tech_.seeking() && this.fixesBadSeeks_()) {
57540          this.consecutiveUpdates = 0;
57541          this.lastRecordedTime = this.tech_.currentTime();
57542          return;
57543        }
57544
57545        if (this.tech_.paused() || this.tech_.seeking()) {
57546          return;
57547        }
57548
57549        var currentTime = this.tech_.currentTime();
57550        var buffered = this.tech_.buffered();
57551
57552        if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) {
57553          // If current time is at the end of the final buffered region, then any playback
57554          // stall is most likely caused by buffering in a low bandwidth environment. The tech
57555          // should fire a `waiting` event in this scenario, but due to browser and tech
57556          // inconsistencies. Calling `techWaiting_` here allows us to simulate
57557          // responding to a native `waiting` event when the tech fails to emit one.
57558          return this.techWaiting_();
57559        }
57560
57561        if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
57562          this.consecutiveUpdates++;
57563          this.waiting_();
57564        } else if (currentTime === this.lastRecordedTime) {
57565          this.consecutiveUpdates++;
57566        } else {
57567          this.consecutiveUpdates = 0;
57568          this.lastRecordedTime = currentTime;
57569        }
57570      }
57571      /**
57572       * Cancels any pending timers and resets the 'timeupdate' mechanism
57573       * designed to detect that we are stalled
57574       *
57575       * @private
57576       */
57577
57578    }, {
57579      key: 'cancelTimer_',
57580      value: function cancelTimer_() {
57581        this.consecutiveUpdates = 0;
57582
57583        if (this.timer_) {
57584          this.logger_('cancelTimer_');
57585          clearTimeout(this.timer_);
57586        }
57587
57588        this.timer_ = null;
57589      }
57590      /**
57591       * Fixes situations where there's a bad seek
57592       *
57593       * @return {Boolean} whether an action was taken to fix the seek
57594       * @private
57595       */
57596
57597    }, {
57598      key: 'fixesBadSeeks_',
57599      value: function fixesBadSeeks_() {
57600        var seeking = this.tech_.seeking();
57601
57602        if (!seeking) {
57603          return false;
57604        }
57605
57606        var seekable = this.seekable();
57607        var currentTime = this.tech_.currentTime();
57608        var isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow);
57609        var seekTo = void 0;
57610
57611        if (isAfterSeekableRange) {
57612          var seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
57613
57614          seekTo = seekableEnd;
57615        }
57616
57617        if (this.beforeSeekableWindow_(seekable, currentTime)) {
57618          var seekableStart = seekable.start(0); // sync to the beginning of the live window
57619          // provide a buffer of .1 seconds to handle rounding/imprecise numbers
57620
57621          seekTo = seekableStart + SAFE_TIME_DELTA;
57622        }
57623
57624        if (typeof seekTo !== 'undefined') {
57625          this.logger_('Trying to seek outside of seekable at time ' + currentTime + ' with ' + ('seekable range ' + printableRange(seekable) + '. Seeking to ') + (seekTo + '.'));
57626          this.seekTo(seekTo);
57627          return true;
57628        }
57629
57630        return false;
57631      }
57632      /**
57633       * Handler for situations when we determine the player is waiting.
57634       *
57635       * @private
57636       */
57637
57638    }, {
57639      key: 'waiting_',
57640      value: function waiting_() {
57641        if (this.techWaiting_()) {
57642          return;
57643        } // All tech waiting checks failed. Use last resort correction
57644
57645
57646        var currentTime = this.tech_.currentTime();
57647        var buffered = this.tech_.buffered();
57648        var currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered
57649        // region with no indication that anything is amiss (seen in Firefox). Seeking to
57650        // currentTime is usually enough to kickstart the player. This checks that the player
57651        // is currently within a buffered region before attempting a corrective seek.
57652        // Chrome does not appear to continue `timeupdate` events after a `waiting` event
57653        // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
57654        // make sure there is ~3 seconds of forward buffer before taking any corrective action
57655        // to avoid triggering an `unknownwaiting` event when the network is slow.
57656
57657        if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
57658          this.cancelTimer_();
57659          this.seekTo(currentTime);
57660          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
57661
57662          this.tech_.trigger({
57663            type: 'usage',
57664            name: 'hls-unknown-waiting'
57665          });
57666          return;
57667        }
57668      }
57669      /**
57670       * Handler for situations when the tech fires a `waiting` event
57671       *
57672       * @return {Boolean}
57673       *         True if an action (or none) was needed to correct the waiting. False if no
57674       *         checks passed
57675       * @private
57676       */
57677
57678    }, {
57679      key: 'techWaiting_',
57680      value: function techWaiting_() {
57681        var seekable = this.seekable();
57682        var currentTime = this.tech_.currentTime();
57683
57684        if (this.tech_.seeking() && this.fixesBadSeeks_()) {
57685          // Tech is seeking or bad seek fixed, no action needed
57686          return true;
57687        }
57688
57689        if (this.tech_.seeking() || this.timer_ !== null) {
57690          // Tech is seeking or already waiting on another action, no action needed
57691          return true;
57692        }
57693
57694        if (this.beforeSeekableWindow_(seekable, currentTime)) {
57695          var livePoint = seekable.end(seekable.length - 1);
57696          this.logger_('Fell out of live window at time ' + currentTime + '. Seeking to ' + ('live point (seekable end) ' + livePoint));
57697          this.cancelTimer_();
57698          this.seekTo(livePoint); // live window resyncs may be useful for monitoring QoS
57699
57700          this.tech_.trigger({
57701            type: 'usage',
57702            name: 'hls-live-resync'
57703          });
57704          return true;
57705        }
57706
57707        var buffered = this.tech_.buffered();
57708        var nextRange = findNextRange(buffered, currentTime);
57709
57710        if (this.videoUnderflow_(nextRange, buffered, currentTime)) {
57711          // Even though the video underflowed and was stuck in a gap, the audio overplayed
57712          // the gap, leading currentTime into a buffered range. Seeking to currentTime
57713          // allows the video to catch up to the audio position without losing any audio
57714          // (only suffering ~3 seconds of frozen video and a pause in audio playback).
57715          this.cancelTimer_();
57716          this.seekTo(currentTime); // video underflow may be useful for monitoring QoS
57717
57718          this.tech_.trigger({
57719            type: 'usage',
57720            name: 'hls-video-underflow'
57721          });
57722          return true;
57723        } // check for gap
57724
57725
57726        if (nextRange.length > 0) {
57727          var difference = nextRange.start(0) - currentTime;
57728          this.logger_('Stopped at ' + currentTime + ', setting timer for ' + difference + ', seeking ' + ('to ' + nextRange.start(0)));
57729          this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime);
57730          return true;
57731        } // All checks failed. Returning false to indicate failure to correct waiting
57732
57733
57734        return false;
57735      }
57736    }, {
57737      key: 'afterSeekableWindow_',
57738      value: function afterSeekableWindow_(seekable, currentTime, playlist) {
57739        var allowSeeksWithinUnsafeLiveWindow = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
57740
57741        if (!seekable.length) {
57742          // we can't make a solid case if there's no seekable, default to false
57743          return false;
57744        }
57745
57746        var allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA;
57747        var isLive = !playlist.endList;
57748
57749        if (isLive && allowSeeksWithinUnsafeLiveWindow) {
57750          allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3;
57751        }
57752
57753        if (currentTime > allowedEnd) {
57754          return true;
57755        }
57756
57757        return false;
57758      }
57759    }, {
57760      key: 'beforeSeekableWindow_',
57761      value: function beforeSeekableWindow_(seekable, currentTime) {
57762        if (seekable.length && // can't fall before 0 and 0 seekable start identifies VOD stream
57763        seekable.start(0) > 0 && currentTime < seekable.start(0) - SAFE_TIME_DELTA) {
57764          return true;
57765        }
57766
57767        return false;
57768      }
57769    }, {
57770      key: 'videoUnderflow_',
57771      value: function videoUnderflow_(nextRange, buffered, currentTime) {
57772        if (nextRange.length === 0) {
57773          // Even if there is no available next range, there is still a possibility we are
57774          // stuck in a gap due to video underflow.
57775          var gap = this.gapFromVideoUnderflow_(buffered, currentTime);
57776
57777          if (gap) {
57778            this.logger_('Encountered a gap in video from ' + gap.start + ' to ' + gap.end + '. ' + ('Seeking to current time ' + currentTime));
57779            return true;
57780          }
57781        }
57782
57783        return false;
57784      }
57785      /**
57786       * Timer callback. If playback still has not proceeded, then we seek
57787       * to the start of the next buffered region.
57788       *
57789       * @private
57790       */
57791
57792    }, {
57793      key: 'skipTheGap_',
57794      value: function skipTheGap_(scheduledCurrentTime) {
57795        var buffered = this.tech_.buffered();
57796        var currentTime = this.tech_.currentTime();
57797        var nextRange = findNextRange(buffered, currentTime);
57798        this.cancelTimer_();
57799
57800        if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
57801          return;
57802        }
57803
57804        this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played
57805
57806        this.seekTo(nextRange.start(0) + TIME_FUDGE_FACTOR);
57807        this.tech_.trigger({
57808          type: 'usage',
57809          name: 'hls-gap-skip'
57810        });
57811      }
57812    }, {
57813      key: 'gapFromVideoUnderflow_',
57814      value: function gapFromVideoUnderflow_(buffered, currentTime) {
57815        // At least in Chrome, if there is a gap in the video buffer, the audio will continue
57816        // playing for ~3 seconds after the video gap starts. This is done to account for
57817        // video buffer underflow/underrun (note that this is not done when there is audio
57818        // buffer underflow/underrun -- in that case the video will stop as soon as it
57819        // encounters the gap, as audio stalls are more noticeable/jarring to a user than
57820        // video stalls). The player's time will reflect the playthrough of audio, so the
57821        // time will appear as if we are in a buffered region, even if we are stuck in a
57822        // "gap."
57823        //
57824        // Example:
57825        // video buffer:   0 => 10.1, 10.2 => 20
57826        // audio buffer:   0 => 20
57827        // overall buffer: 0 => 10.1, 10.2 => 20
57828        // current time: 13
57829        //
57830        // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
57831        // however, the audio continued playing until it reached ~3 seconds past the gap
57832        // (13 seconds), at which point it stops as well. Since current time is past the
57833        // gap, findNextRange will return no ranges.
57834        //
57835        // To check for this issue, we see if there is a gap that starts somewhere within
57836        // a 3 second range (3 seconds +/- 1 second) back from our current time.
57837        var gaps = findGaps(buffered);
57838
57839        for (var i = 0; i < gaps.length; i++) {
57840          var start = gaps.start(i);
57841          var end = gaps.end(i); // gap is starts no more than 4 seconds back
57842
57843          if (currentTime - start < 4 && currentTime - start > 2) {
57844            return {
57845              start: start,
57846              end: end
57847            };
57848          }
57849        }
57850
57851        return null;
57852      }
57853    }]);
57854    return PlaybackWatcher;
57855  }();
57856
57857  var defaultOptions = {
57858    errorInterval: 30,
57859    getSource: function getSource(next) {
57860      var tech = this.tech({
57861        IWillNotUseThisInPlugins: true
57862      });
57863      var sourceObj = tech.currentSource_;
57864      return next(sourceObj);
57865    }
57866  };
57867  /**
57868   * Main entry point for the plugin
57869   *
57870   * @param {Player} player a reference to a videojs Player instance
57871   * @param {Object} [options] an object with plugin options
57872   * @private
57873   */
57874
57875  var initPlugin = function initPlugin(player, options) {
57876    var lastCalled = 0;
57877    var seekTo = 0;
57878    var localOptions = videojs$1.mergeOptions(defaultOptions, options);
57879    player.ready(function () {
57880      player.trigger({
57881        type: 'usage',
57882        name: 'hls-error-reload-initialized'
57883      });
57884    });
57885    /**
57886     * Player modifications to perform that must wait until `loadedmetadata`
57887     * has been triggered
57888     *
57889     * @private
57890     */
57891
57892    var loadedMetadataHandler = function loadedMetadataHandler() {
57893      if (seekTo) {
57894        player.currentTime(seekTo);
57895      }
57896    };
57897    /**
57898     * Set the source on the player element, play, and seek if necessary
57899     *
57900     * @param {Object} sourceObj An object specifying the source url and mime-type to play
57901     * @private
57902     */
57903
57904
57905    var setSource = function setSource(sourceObj) {
57906      if (sourceObj === null || sourceObj === undefined) {
57907        return;
57908      }
57909
57910      seekTo = player.duration() !== Infinity && player.currentTime() || 0;
57911      player.one('loadedmetadata', loadedMetadataHandler);
57912      player.src(sourceObj);
57913      player.trigger({
57914        type: 'usage',
57915        name: 'hls-error-reload'
57916      });
57917      player.play();
57918    };
57919    /**
57920     * Attempt to get a source from either the built-in getSource function
57921     * or a custom function provided via the options
57922     *
57923     * @private
57924     */
57925
57926
57927    var errorHandler = function errorHandler() {
57928      // Do not attempt to reload the source if a source-reload occurred before
57929      // 'errorInterval' time has elapsed since the last source-reload
57930      if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
57931        player.trigger({
57932          type: 'usage',
57933          name: 'hls-error-reload-canceled'
57934        });
57935        return;
57936      }
57937
57938      if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
57939        videojs$1.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
57940        return;
57941      }
57942
57943      lastCalled = Date.now();
57944      return localOptions.getSource.call(player, setSource);
57945    };
57946    /**
57947     * Unbind any event handlers that were bound by the plugin
57948     *
57949     * @private
57950     */
57951
57952
57953    var cleanupEvents = function cleanupEvents() {
57954      player.off('loadedmetadata', loadedMetadataHandler);
57955      player.off('error', errorHandler);
57956      player.off('dispose', cleanupEvents);
57957    };
57958    /**
57959     * Cleanup before re-initializing the plugin
57960     *
57961     * @param {Object} [newOptions] an object with plugin options
57962     * @private
57963     */
57964
57965
57966    var reinitPlugin = function reinitPlugin(newOptions) {
57967      cleanupEvents();
57968      initPlugin(player, newOptions);
57969    };
57970
57971    player.on('error', errorHandler);
57972    player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before
57973    // initializing the plugin
57974
57975    player.reloadSourceOnError = reinitPlugin;
57976  };
57977  /**
57978   * Reload the source when an error is detected as long as there
57979   * wasn't an error previously within the last 30 seconds
57980   *
57981   * @param {Object} [options] an object with plugin options
57982   */
57983
57984
57985  var reloadSourceOnError = function reloadSourceOnError(options) {
57986    initPlugin(this, options);
57987  };
57988
57989  var version$3 = "1.9.3"; // since VHS handles HLS and DASH (and in the future, more types), use * to capture all
57990
57991  videojs$1.use('*', function (player) {
57992    return {
57993      setSource: function setSource(srcObj, next) {
57994        // pass null as the first argument to indicate that the source is not rejected
57995        next(null, srcObj);
57996      },
57997      // VHS needs to know when seeks happen. For external seeks (generated at the player
57998      // level), this middleware will capture the action. For internal seeks (generated at
57999      // the tech level), we use a wrapped function so that we can handle it on our own
58000      // (specified elsewhere).
58001      setCurrentTime: function setCurrentTime(time) {
58002        if (player.vhs && player.currentSource().src === player.vhs.source_.src) {
58003          player.vhs.setCurrentTime(time);
58004        }
58005
58006        return time;
58007      },
58008      // Sync VHS after play requests.
58009      // This specifically handles replay where the order of actions is
58010      // play, video element will seek to 0 (skipping the setCurrentTime middleware)
58011      // then triggers a play event.
58012      play: function play() {
58013        if (player.vhs && player.currentSource().src === player.vhs.source_.src) {
58014          player.vhs.setCurrentTime(player.tech_.currentTime());
58015        }
58016      }
58017    };
58018  });
58019  /**
58020   * @file videojs-http-streaming.js
58021   *
58022   * The main file for the HLS project.
58023   * License: https://github.com/videojs/videojs-http-streaming/blob/master/LICENSE
58024   */
58025
58026  var Hls$1 = {
58027    PlaylistLoader: PlaylistLoader,
58028    Playlist: Playlist,
58029    Decrypter: Decrypter,
58030    AsyncStream: AsyncStream,
58031    decrypt: decrypt,
58032    utils: utils$1,
58033    STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
58034    INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
58035    comparePlaylistBandwidth: comparePlaylistBandwidth,
58036    comparePlaylistResolution: comparePlaylistResolution,
58037    xhr: xhrFactory()
58038  }; // Define getter/setters for config properites
58039
58040  ['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) {
58041    Object.defineProperty(Hls$1, prop, {
58042      get: function get$$1() {
58043        videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
58044        return Config[prop];
58045      },
58046      set: function set$$1(value) {
58047        videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
58048
58049        if (typeof value !== 'number' || value < 0) {
58050          videojs$1.log.warn('value of Hls.' + prop + ' must be greater than or equal to 0');
58051          return;
58052        }
58053
58054        Config[prop] = value;
58055      }
58056    });
58057  });
58058  var LOCAL_STORAGE_KEY$1 = 'videojs-vhs';
58059
58060  var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
58061    var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
58062
58063    if (mpegurlRE.test(type)) {
58064      return 'hls';
58065    }
58066
58067    var dashRE = /^application\/dash\+xml/i;
58068
58069    if (dashRE.test(type)) {
58070      return 'dash';
58071    }
58072
58073    return null;
58074  };
58075  /**
58076   * Updates the selectedIndex of the QualityLevelList when a mediachange happens in hls.
58077   *
58078   * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
58079   * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
58080   * @function handleHlsMediaChange
58081   */
58082
58083
58084  var handleHlsMediaChange = function handleHlsMediaChange(qualityLevels, playlistLoader) {
58085    var newPlaylist = playlistLoader.media();
58086    var selectedIndex = -1;
58087
58088    for (var i = 0; i < qualityLevels.length; i++) {
58089      if (qualityLevels[i].id === newPlaylist.uri) {
58090        selectedIndex = i;
58091        break;
58092      }
58093    }
58094
58095    qualityLevels.selectedIndex_ = selectedIndex;
58096    qualityLevels.trigger({
58097      selectedIndex: selectedIndex,
58098      type: 'change'
58099    });
58100  };
58101  /**
58102   * Adds quality levels to list once playlist metadata is available
58103   *
58104   * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
58105   * @param {Object} hls Hls object to listen to for media events.
58106   * @function handleHlsLoadedMetadata
58107   */
58108
58109
58110  var handleHlsLoadedMetadata = function handleHlsLoadedMetadata(qualityLevels, hls) {
58111    hls.representations().forEach(function (rep) {
58112      qualityLevels.addQualityLevel(rep);
58113    });
58114    handleHlsMediaChange(qualityLevels, hls.playlists);
58115  }; // HLS is a source handler, not a tech. Make sure attempts to use it
58116  // as one do not cause exceptions.
58117
58118
58119  Hls$1.canPlaySource = function () {
58120    return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
58121  };
58122
58123  var emeKeySystems = function emeKeySystems(keySystemOptions, videoPlaylist, audioPlaylist) {
58124    if (!keySystemOptions) {
58125      return keySystemOptions;
58126    } // upsert the content types based on the selected playlist
58127
58128
58129    var keySystemContentTypes = {};
58130
58131    for (var keySystem in keySystemOptions) {
58132      keySystemContentTypes[keySystem] = {
58133        audioContentType: 'audio/mp4; codecs="' + audioPlaylist.attributes.CODECS + '"',
58134        videoContentType: 'video/mp4; codecs="' + videoPlaylist.attributes.CODECS + '"'
58135      };
58136
58137      if (videoPlaylist.contentProtection && videoPlaylist.contentProtection[keySystem] && videoPlaylist.contentProtection[keySystem].pssh) {
58138        keySystemContentTypes[keySystem].pssh = videoPlaylist.contentProtection[keySystem].pssh;
58139      } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
58140      // so we need to prevent overwriting the URL entirely
58141
58142
58143      if (typeof keySystemOptions[keySystem] === 'string') {
58144        keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
58145      }
58146    }
58147
58148    return videojs$1.mergeOptions(keySystemOptions, keySystemContentTypes);
58149  };
58150
58151  var setupEmeOptions = function setupEmeOptions(hlsHandler) {
58152    if (hlsHandler.options_.sourceType !== 'dash') {
58153      return;
58154    }
58155
58156    var player = videojs$1.players[hlsHandler.tech_.options_.playerId];
58157
58158    if (player.eme) {
58159      var sourceOptions = emeKeySystems(hlsHandler.source_.keySystems, hlsHandler.playlists.media(), hlsHandler.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader.media());
58160
58161      if (sourceOptions) {
58162        player.currentSource().keySystems = sourceOptions; // works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449
58163
58164        if (player.eme.initializeMediaKeys) {
58165          player.eme.initializeMediaKeys();
58166        }
58167      }
58168    }
58169  };
58170
58171  var getVhsLocalStorage = function getVhsLocalStorage() {
58172    if (!window.localStorage) {
58173      return null;
58174    }
58175
58176    var storedObject = window.localStorage.getItem(LOCAL_STORAGE_KEY$1);
58177
58178    if (!storedObject) {
58179      return null;
58180    }
58181
58182    try {
58183      return JSON.parse(storedObject);
58184    } catch (e) {
58185      // someone may have tampered with the value
58186      return null;
58187    }
58188  };
58189
58190  var updateVhsLocalStorage = function updateVhsLocalStorage(options) {
58191    if (!window.localStorage) {
58192      return false;
58193    }
58194
58195    var objectToStore = getVhsLocalStorage();
58196    objectToStore = objectToStore ? videojs$1.mergeOptions(objectToStore, options) : options;
58197
58198    try {
58199      window.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(objectToStore));
58200    } catch (e) {
58201      // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where
58202      // storage is set to 0).
58203      // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions
58204      // No need to perform any operation.
58205      return false;
58206    }
58207
58208    return objectToStore;
58209  };
58210  /**
58211   * Whether the browser has built-in HLS support.
58212   */
58213
58214
58215  Hls$1.supportsNativeHls = function () {
58216    var video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't
58217
58218    if (!videojs$1.getTech('Html5').isSupported()) {
58219      return false;
58220    } // HLS manifests can go by many mime-types
58221
58222
58223    var canPlay = [// Apple santioned
58224    'application/vnd.apple.mpegurl', // Apple sanctioned for backwards compatibility
58225    'audio/mpegurl', // Very common
58226    'audio/x-mpegurl', // Very common
vendor: 7,654 bytes, lines 58227-58418
58227    'application/x-mpegurl', // Included for completeness
58228    'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
58229    return canPlay.some(function (canItPlay) {
58230      return /maybe|probably/i.test(video.canPlayType(canItPlay));
58231    });
58232  }();
58233
58234  Hls$1.supportsNativeDash = function () {
58235    if (!videojs$1.getTech('Html5').isSupported()) {
58236      return false;
58237    }
58238
58239    return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml'));
58240  }();
58241
58242  Hls$1.supportsTypeNatively = function (type) {
58243    if (type === 'hls') {
58244      return Hls$1.supportsNativeHls;
58245    }
58246
58247    if (type === 'dash') {
58248      return Hls$1.supportsNativeDash;
58249    }
58250
58251    return false;
58252  };
58253  /**
58254   * HLS is a source handler, not a tech. Make sure attempts to use it
58255   * as one do not cause exceptions.
58256   */
58257
58258
58259  Hls$1.isSupported = function () {
58260    return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
58261  };
58262
58263  var Component$1 = videojs$1.getComponent('Component');
58264  /**
58265   * The Hls Handler object, where we orchestrate all of the parts
58266   * of HLS to interact with video.js
58267   *
58268   * @class HlsHandler
58269   * @extends videojs.Component
58270   * @param {Object} source the soruce object
58271   * @param {Tech} tech the parent tech object
58272   * @param {Object} options optional and required options
58273   */
58274
58275  var HlsHandler = function (_Component) {
58276    inherits$1(HlsHandler, _Component);
58277
58278    function HlsHandler(source, tech, options) {
58279      classCallCheck$1(this, HlsHandler); // tech.player() is deprecated but setup a reference to HLS for
58280      // backwards-compatibility
58281
58282      var _this = possibleConstructorReturn$1(this, (HlsHandler.__proto__ || Object.getPrototypeOf(HlsHandler)).call(this, tech, options.hls));
58283
58284      if (tech.options_ && tech.options_.playerId) {
58285        var _player = videojs$1(tech.options_.playerId);
58286
58287        if (!_player.hasOwnProperty('hls')) {
58288          Object.defineProperty(_player, 'hls', {
58289            get: function get$$1() {
58290              videojs$1.log.warn('player.hls is deprecated. Use player.tech().hls instead.');
58291              tech.trigger({
58292                type: 'usage',
58293                name: 'hls-player-access'
58294              });
58295              return _this;
58296            },
58297            configurable: true
58298          });
58299        } // Set up a reference to the HlsHandler from player.vhs. This allows users to start
58300        // migrating from player.tech_.hls... to player.vhs... for API access. Although this
58301        // isn't the most appropriate form of reference for video.js (since all APIs should
58302        // be provided through core video.js), it is a common pattern for plugins, and vhs
58303        // will act accordingly.
58304
58305
58306        _player.vhs = _this; // deprecated, for backwards compatibility
58307
58308        _player.dash = _this;
58309        _this.player_ = _player;
58310      }
58311
58312      _this.tech_ = tech;
58313      _this.source_ = source;
58314      _this.stats = {};
58315
58316      _this.setOptions_();
58317
58318      if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) {
58319        tech.overrideNativeAudioTracks(true);
58320        tech.overrideNativeVideoTracks(true);
58321      } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
58322        // overriding native HLS only works if audio tracks have been emulated
58323        // error early if we're misconfigured
58324        throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB');
58325      } // listen for fullscreenchange events for this player so that we
58326      // can adjust our quality selection quickly
58327
58328
58329      _this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) {
58330        var fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
58331
58332        if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) {
58333          _this.masterPlaylistController_.smoothQualityChange_();
58334        }
58335      }); // Handle seeking when looping - middleware doesn't handle this seek event from the tech
58336
58337
58338      _this.on(_this.tech_, 'seeking', function () {
58339        if (this.tech_.currentTime() === 0 && this.tech_.player_.loop()) {
58340          this.setCurrentTime(0);
58341        }
58342      });
58343
58344      _this.on(_this.tech_, 'error', function () {
58345        if (this.masterPlaylistController_) {
58346          this.masterPlaylistController_.pauseLoading();
58347        }
58348      });
58349
58350      _this.on(_this.tech_, 'play', _this.play);
58351
58352      return _this;
58353    }
58354
58355    createClass$1(HlsHandler, [{
58356      key: 'setOptions_',
58357      value: function setOptions_() {
58358        var _this2 = this; // defaults
58359
58360
58361        this.options_.withCredentials = this.options_.withCredentials || false;
58362        this.options_.handleManifestRedirects = this.options_.handleManifestRedirects || false;
58363        this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
58364        this.options_.smoothQualityChange = this.options_.smoothQualityChange || false;
58365        this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false;
58366        this.options_.customTagParsers = this.options_.customTagParsers || [];
58367        this.options_.customTagMappers = this.options_.customTagMappers || [];
58368
58369        if (typeof this.options_.blacklistDuration !== 'number') {
58370          this.options_.blacklistDuration = 5 * 60;
58371        }
58372
58373        if (typeof this.options_.bandwidth !== 'number') {
58374          if (this.options_.useBandwidthFromLocalStorage) {
58375            var storedObject = getVhsLocalStorage();
58376
58377            if (storedObject && storedObject.bandwidth) {
58378              this.options_.bandwidth = storedObject.bandwidth;
58379              this.tech_.trigger({
58380                type: 'usage',
58381                name: 'hls-bandwidth-from-local-storage'
58382              });
58383            }
58384
58385            if (storedObject && storedObject.throughput) {
58386              this.options_.throughput = storedObject.throughput;
58387              this.tech_.trigger({
58388                type: 'usage',
58389                name: 'hls-throughput-from-local-storage'
58390              });
58391            }
58392          }
58393        } // if bandwidth was not set by options or pulled from local storage, start playlist
58394        // selection at a reasonable bandwidth
58395
58396
58397        if (typeof this.options_.bandwidth !== 'number') {
58398          this.options_.bandwidth = Config.INITIAL_BANDWIDTH;
58399        } // If the bandwidth number is unchanged from the initial setting
58400        // then this takes precedence over the enableLowInitialPlaylist option
58401
58402
58403        this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src
58404
58405        ['withCredentials', 'limitRenditionByPlayerDimensions', 'bandwidth', 'smoothQualityChange', 'customTagParsers', 'customTagMappers', 'handleManifestRedirects'].forEach(function (option) {
58406          if (typeof _this2.source_[option] !== 'undefined') {
58407            _this2.options_[option] = _this2.source_[option];
58408          }
58409        });
58410        this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
58411      }
58412      /**
58413       * called when player.src gets called, handle a new source
58414       *
58415       * @param {Object} src the source object to handle
58416       */
58417
58418    }, {
vendor: 9,926 bytes, lines 58419-58686
58419      key: 'src',
58420      value: function src(_src, type) {
58421        var _this3 = this; // do nothing if the src is falsey
58422
58423
58424        if (!_src) {
58425          return;
58426        }
58427
58428        this.setOptions_(); // add master playlist controller options
58429
58430        this.options_.url = this.source_.src;
58431        this.options_.tech = this.tech_;
58432        this.options_.externHls = Hls$1;
58433        this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update both the tech and call our own
58434        // setCurrentTime function. This is needed because "seeking" events aren't always
58435        // reliable. External seeks (via the player object) are handled via middleware.
58436
58437        this.options_.seekTo = function (time) {
58438          _this3.tech_.setCurrentTime(time);
58439
58440          _this3.setCurrentTime(time);
58441        };
58442
58443        this.masterPlaylistController_ = new MasterPlaylistController(this.options_);
58444        this.playbackWatcher_ = new PlaybackWatcher(videojs$1.mergeOptions(this.options_, {
58445          seekable: function seekable$$1() {
58446            return _this3.seekable();
58447          },
58448          media: function media() {
58449            return _this3.masterPlaylistController_.media();
58450          }
58451        }));
58452        this.masterPlaylistController_.on('error', function () {
58453          var player = videojs$1.players[_this3.tech_.options_.playerId];
58454          player.error(_this3.masterPlaylistController_.error);
58455        }); // `this` in selectPlaylist should be the HlsHandler for backwards
58456        // compatibility with < v2
58457
58458        this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Hls$1.STANDARD_PLAYLIST_SELECTOR.bind(this);
58459        this.masterPlaylistController_.selectInitialPlaylist = Hls$1.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2
58460
58461        this.playlists = this.masterPlaylistController_.masterPlaylistLoader_;
58462        this.mediaSource = this.masterPlaylistController_.mediaSource; // Proxy assignment of some properties to the master playlist
58463        // controller. Using a custom property for backwards compatibility
58464        // with < v2
58465
58466        Object.defineProperties(this, {
58467          selectPlaylist: {
58468            get: function get$$1() {
58469              return this.masterPlaylistController_.selectPlaylist;
58470            },
58471            set: function set$$1(selectPlaylist) {
58472              this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this);
58473            }
58474          },
58475          throughput: {
58476            get: function get$$1() {
58477              return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate;
58478            },
58479            set: function set$$1(throughput) {
58480              this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value
58481              // for the cumulative average
58482
58483              this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1;
58484            }
58485          },
58486          bandwidth: {
58487            get: function get$$1() {
58488              return this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
58489            },
58490            set: function set$$1(bandwidth) {
58491              this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter
58492              // `count` is set to zero that current value of `rate` isn't included
58493              // in the cumulative average
58494
58495              this.masterPlaylistController_.mainSegmentLoader_.throughput = {
58496                rate: 0,
58497                count: 0
58498              };
58499            }
58500          },
58501
58502          /**
58503           * `systemBandwidth` is a combination of two serial processes bit-rates. The first
58504           * is the network bitrate provided by `bandwidth` and the second is the bitrate of
58505           * the entire process after that - decryption, transmuxing, and appending - provided
58506           * by `throughput`.
58507           *
58508           * Since the two process are serial, the overall system bandwidth is given by:
58509           *   sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
58510           */
58511          systemBandwidth: {
58512            get: function get$$1() {
58513              var invBandwidth = 1 / (this.bandwidth || 1);
58514              var invThroughput = void 0;
58515
58516              if (this.throughput > 0) {
58517                invThroughput = 1 / this.throughput;
58518              } else {
58519                invThroughput = 0;
58520              }
58521
58522              var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
58523              return systemBitrate;
58524            },
58525            set: function set$$1() {
58526              videojs$1.log.error('The "systemBandwidth" property is read-only');
58527            }
58528          }
58529        });
58530
58531        if (this.options_.bandwidth) {
58532          this.bandwidth = this.options_.bandwidth;
58533        }
58534
58535        if (this.options_.throughput) {
58536          this.throughput = this.options_.throughput;
58537        }
58538
58539        Object.defineProperties(this.stats, {
58540          bandwidth: {
58541            get: function get$$1() {
58542              return _this3.bandwidth || 0;
58543            },
58544            enumerable: true
58545          },
58546          mediaRequests: {
58547            get: function get$$1() {
58548              return _this3.masterPlaylistController_.mediaRequests_() || 0;
58549            },
58550            enumerable: true
58551          },
58552          mediaRequestsAborted: {
58553            get: function get$$1() {
58554              return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0;
58555            },
58556            enumerable: true
58557          },
58558          mediaRequestsTimedout: {
58559            get: function get$$1() {
58560              return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0;
58561            },
58562            enumerable: true
58563          },
58564          mediaRequestsErrored: {
58565            get: function get$$1() {
58566              return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0;
58567            },
58568            enumerable: true
58569          },
58570          mediaTransferDuration: {
58571            get: function get$$1() {
58572              return _this3.masterPlaylistController_.mediaTransferDuration_() || 0;
58573            },
58574            enumerable: true
58575          },
58576          mediaBytesTransferred: {
58577            get: function get$$1() {
58578              return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0;
58579            },
58580            enumerable: true
58581          },
58582          mediaSecondsLoaded: {
58583            get: function get$$1() {
58584              return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0;
58585            },
58586            enumerable: true
58587          },
58588          buffered: {
58589            get: function get$$1() {
58590              return timeRangesToArray(_this3.tech_.buffered());
58591            },
58592            enumerable: true
58593          },
58594          currentTime: {
58595            get: function get$$1() {
58596              return _this3.tech_.currentTime();
58597            },
58598            enumerable: true
58599          },
58600          currentSource: {
58601            get: function get$$1() {
58602              return _this3.tech_.currentSource_;
58603            },
58604            enumerable: true
58605          },
58606          currentTech: {
58607            get: function get$$1() {
58608              return _this3.tech_.name_;
58609            },
58610            enumerable: true
58611          },
58612          duration: {
58613            get: function get$$1() {
58614              return _this3.tech_.duration();
58615            },
58616            enumerable: true
58617          },
58618          master: {
58619            get: function get$$1() {
58620              return _this3.playlists.master;
58621            },
58622            enumerable: true
58623          },
58624          playerDimensions: {
58625            get: function get$$1() {
58626              return _this3.tech_.currentDimensions();
58627            },
58628            enumerable: true
58629          },
58630          seekable: {
58631            get: function get$$1() {
58632              return timeRangesToArray(_this3.tech_.seekable());
58633            },
58634            enumerable: true
58635          },
58636          timestamp: {
58637            get: function get$$1() {
58638              return Date.now();
58639            },
58640            enumerable: true
58641          },
58642          videoPlaybackQuality: {
58643            get: function get$$1() {
58644              return _this3.tech_.getVideoPlaybackQuality();
58645            },
58646            enumerable: true
58647          }
58648        });
58649        this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_));
58650        this.tech_.on('bandwidthupdate', function () {
58651          if (_this3.options_.useBandwidthFromLocalStorage) {
58652            updateVhsLocalStorage({
58653              bandwidth: _this3.bandwidth,
58654              throughput: Math.round(_this3.throughput)
58655            });
58656          }
58657        });
58658        this.masterPlaylistController_.on('selectedinitialmedia', function () {
58659          // Add the manual rendition mix-in to HlsHandler
58660          renditionSelectionMixin(_this3);
58661          setupEmeOptions(_this3);
58662        }); // the bandwidth of the primary segment loader is our best
58663        // estimate of overall bandwidth
58664
58665        this.on(this.masterPlaylistController_, 'progress', function () {
58666          this.tech_.trigger('progress');
58667        });
58668        this.tech_.ready(function () {
58669          return _this3.setupQualityLevels_();
58670        }); // do nothing if the tech has been disposed already
58671        // this can occur if someone sets the src in player.ready(), for instance
58672
58673        if (!this.tech_.el()) {
58674          return;
58675        }
58676
58677        this.tech_.src(videojs$1.URL.createObjectURL(this.masterPlaylistController_.mediaSource));
58678      }
58679      /**
58680       * Initializes the quality levels and sets listeners to update them.
58681       *
58682       * @method setupQualityLevels_
58683       * @private
58684       */
58685
58686    }, {
58687      key: 'setupQualityLevels_',
58688      value: function setupQualityLevels_() {
58689        var _this4 = this;
58690
58691        var player = videojs$1.players[this.tech_.options_.playerId];
58692
58693        if (player && player.qualityLevels) {
58694          this.qualityLevels_ = player.qualityLevels();
58695          this.masterPlaylistController_.on('selectedinitialmedia', function () {
58696            handleHlsLoadedMetadata(_this4.qualityLevels_, _this4);
58697          });
58698          this.playlists.on('mediachange', function () {
58699            handleHlsMediaChange(_this4.qualityLevels_, _this4.playlists);
58700          });
58701        }
58702      }
58703      /**
58704       * Begin playing the video.
58705       */
58706
58707    }, {
58708      key: 'play',
58709      value: function play() {
58710        this.masterPlaylistController_.play();
58711      }
58712      /**
58713       * a wrapper around the function in MasterPlaylistController
58714       */
58715
58716    }, {
58717      key: 'setCurrentTime',
58718      value: function setCurrentTime(currentTime) {
58719        this.masterPlaylistController_.setCurrentTime(currentTime);
58720      }
58721      /**
58722       * a wrapper around the function in MasterPlaylistController
58723       */
58724
58725    }, {
58726      key: 'duration',
58727      value: function duration$$1() {
58728        return this.masterPlaylistController_.duration();
58729      }
58730      /**
58731       * a wrapper around the function in MasterPlaylistController
58732       */
58733
58734    }, {
58735      key: 'seekable',
58736      value: function seekable$$1() {
58737        return this.masterPlaylistController_.seekable();
58738      }
58739      /**
58740       * Abort all outstanding work and cleanup.
58741       */
58742
58743    }, {
58744      key: 'dispose',
58745      value: function dispose() {
58746        if (this.playbackWatcher_) {
58747          this.playbackWatcher_.dispose();
58748        }
58749
58750        if (this.masterPlaylistController_) {
58751          this.masterPlaylistController_.dispose();
58752        }
58753
58754        if (this.qualityLevels_) {
58755          this.qualityLevels_.dispose();
58756        }
58757
58758        if (this.player_) {
58759          delete this.player_.vhs;
58760          delete this.player_.dash;
58761          delete this.player_.hls;
58762        }
58763
58764        if (this.tech_ && this.tech_.hls) {
58765          delete this.tech_.hls;
58766        }
58767
58768        get$1(HlsHandler.prototype.__proto__ || Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this);
58769      }
58770    }, {
58771      key: 'convertToProgramTime',
58772      value: function convertToProgramTime(time, callback) {
58773        return getProgramTime({
58774          playlist: this.masterPlaylistController_.media(),
58775          time: time,
58776          callback: callback
58777        });
58778      } // the player must be playing before calling this
58779
58780    }, {
58781      key: 'seekToProgramTime',
58782      value: function seekToProgramTime$$1(programTime, callback) {
58783        var pauseAfterSeek = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
58784        var retryCount = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 2;
58785        return seekToProgramTime({
58786          programTime: programTime,
58787          playlist: this.masterPlaylistController_.media(),
58788          retryCount: retryCount,
58789          pauseAfterSeek: pauseAfterSeek,
58790          seekTo: this.options_.seekTo,
58791          tech: this.options_.tech,
58792          callback: callback
58793        });
58794      }
58795    }]);
58796    return HlsHandler;
58797  }(Component$1);
58798  /**
58799   * The Source Handler object, which informs video.js what additional
58800   * MIME types are supported and sets up playback. It is registered
58801   * automatically to the appropriate tech based on the capabilities of
58802   * the browser it is running in. It is not necessary to use or modify
58803   * this object in normal usage.
58804   */
58805
58806
58807  var HlsSourceHandler = {
58808    name: 'videojs-http-streaming',
58809    VERSION: version$3,
58810    canHandleSource: function canHandleSource(srcObj) {
58811      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
58812      var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
58813      return HlsSourceHandler.canPlayType(srcObj.type, localOptions);
58814    },
58815    handleSource: function handleSource(source, tech) {
58816      var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
58817      var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
58818      tech.hls = new HlsHandler(source, tech, localOptions);
58819      tech.hls.xhr = xhrFactory();
58820      tech.hls.src(source.src, source.type);
58821      return tech.hls;
58822    },
58823    canPlayType: function canPlayType(type) {
58824      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
58825
58826      var _videojs$mergeOptions = videojs$1.mergeOptions(videojs$1.options, options),
58827          overrideNative = _videojs$mergeOptions.hls.overrideNative;
58828
58829      var supportedType = simpleTypeFromSourceType(type);
58830      var canUseMsePlayback = supportedType && (!Hls$1.supportsTypeNatively(supportedType) || overrideNative);
58831      return canUseMsePlayback ? 'maybe' : '';
58832    }
58833  };
58834
58835  if (typeof videojs$1.MediaSource === 'undefined' || typeof videojs$1.URL === 'undefined') {
58836    videojs$1.MediaSource = MediaSource;
58837    videojs$1.URL = URL$1;
58838  } // register source handlers with the appropriate techs
58839
58840
58841  if (MediaSource.supportsNativeMediaSources()) {
58842    videojs$1.getTech('Html5').registerSourceHandler(HlsSourceHandler, 0);
58843  }
58844
58845  videojs$1.HlsHandler = HlsHandler;
58846  videojs$1.HlsSourceHandler = HlsSourceHandler;
58847  videojs$1.Hls = Hls$1;
58848
58849  if (!videojs$1.use) {
58850    videojs$1.registerComponent('Hls', Hls$1);
58851  }
58852
58853  videojs$1.options.hls = videojs$1.options.hls || {};
58854
58855  if (videojs$1.registerPlugin) {
58856    videojs$1.registerPlugin('reloadSourceOnError', reloadSourceOnError);
58857  } else {
58858    videojs$1.plugin('reloadSourceOnError', reloadSourceOnError);
58859  }
58860
58861  return videojs$1;
58862
58863}));
58864
58865!function(){!function(a){var b=a&&a.videojs;b&&(b.CDN_VERSION="7.5.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.