Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
/**
2
 * @license
3
 * Video.js 8.10.0 <http://videojs.com/>
4
 * Copyright Brightcove, Inc. <https://www.brightcove.com/>
5
 * Available under Apache License Version 2.0
6
 * <https://github.com/videojs/video.js/blob/main/LICENSE>
7
 *
8
 * Includes vtt.js <https://github.com/mozilla/vtt.js>
9
 * Available under Apache License Version 2.0
10
 * <https://github.com/mozilla/vtt.js/blob/main/LICENSE>
11
 */
12
 
13
(function (global, factory) {
14
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
15
        typeof define === 'function' && define.amd ? define(factory) :
16
            (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojs = factory());
17
})(this, (function () { 'use strict';
18
 
19
    var version$5 = "8.10.0";
20
 
21
    /**
22
     * An Object that contains lifecycle hooks as keys which point to an array
23
     * of functions that are run when a lifecycle is triggered
24
     *
25
     * @private
26
     */
27
    const hooks_ = {};
28
 
29
    /**
30
     * Get a list of hooks for a specific lifecycle
31
     *
32
     * @param  {string} type
33
     *         the lifecycle to get hooks from
34
     *
35
     * @param  {Function|Function[]} [fn]
36
     *         Optionally add a hook (or hooks) to the lifecycle that your are getting.
37
     *
38
     * @return {Array}
39
     *         an array of hooks, or an empty array if there are none.
40
     */
41
    const hooks = function (type, fn) {
42
        hooks_[type] = hooks_[type] || [];
43
        if (fn) {
44
            hooks_[type] = hooks_[type].concat(fn);
45
        }
46
        return hooks_[type];
47
    };
48
 
49
    /**
50
     * Add a function hook to a specific videojs lifecycle.
51
     *
52
     * @param {string} type
53
     *        the lifecycle to hook the function to.
54
     *
55
     * @param {Function|Function[]}
56
     *        The function or array of functions to attach.
57
     */
58
    const hook = function (type, fn) {
59
        hooks(type, fn);
60
    };
61
 
62
    /**
63
     * Remove a hook from a specific videojs lifecycle.
64
     *
65
     * @param  {string} type
66
     *         the lifecycle that the function hooked to
67
     *
68
     * @param  {Function} fn
69
     *         The hooked function to remove
70
     *
71
     * @return {boolean}
72
     *         The function that was removed or undef
73
     */
74
    const removeHook = function (type, fn) {
75
        const index = hooks(type).indexOf(fn);
76
        if (index <= -1) {
77
            return false;
78
        }
79
        hooks_[type] = hooks_[type].slice();
80
        hooks_[type].splice(index, 1);
81
        return true;
82
    };
83
 
84
    /**
85
     * Add a function hook that will only run once to a specific videojs lifecycle.
86
     *
87
     * @param {string} type
88
     *        the lifecycle to hook the function to.
89
     *
90
     * @param {Function|Function[]}
91
     *        The function or array of functions to attach.
92
     */
93
    const hookOnce = function (type, fn) {
94
        hooks(type, [].concat(fn).map(original => {
95
            const wrapper = (...args) => {
96
                removeHook(type, wrapper);
97
                return original(...args);
98
            };
99
            return wrapper;
100
        }));
101
    };
102
 
103
    /**
104
     * @file fullscreen-api.js
105
     * @module fullscreen-api
106
     */
107
 
108
    /**
109
     * Store the browser-specific methods for the fullscreen API.
110
     *
111
     * @type {Object}
112
     * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
113
     * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
114
     */
115
    const FullscreenApi = {
116
        prefixed: true
117
    };
118
 
119
    // browser API methods
120
    const apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'],
121
        // WebKit
122
        ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen']];
123
    const specApi = apiMap[0];
124
    let browserApi;
125
 
126
    // determine the supported set of functions
127
    for (let i = 0; i < apiMap.length; i++) {
128
        // check for exitFullscreen function
129
        if (apiMap[i][1] in document) {
130
            browserApi = apiMap[i];
131
            break;
132
        }
133
    }
134
 
135
    // map the browser API names to the spec API names
136
    if (browserApi) {
137
        for (let i = 0; i < browserApi.length; i++) {
138
            FullscreenApi[specApi[i]] = browserApi[i];
139
        }
140
        FullscreenApi.prefixed = browserApi[0] !== specApi[0];
141
    }
142
 
143
    /**
144
     * @file create-logger.js
145
     * @module create-logger
146
     */
147
 
148
        // This is the private tracking variable for the logging history.
149
    let history = [];
150
 
151
    /**
152
     * Log messages to the console and history based on the type of message
153
     *
154
     * @private
155
     * @param  {string} name
156
     *         The name of the console method to use.
157
     *
158
     * @param  {Object} log
159
     *         The arguments to be passed to the matching console method.
160
     *
161
     * @param {string} [styles]
162
     *        styles for name
163
     */
164
    const LogByTypeFactory = (name, log, styles) => (type, level, args) => {
165
        const lvl = log.levels[level];
166
        const lvlRegExp = new RegExp(`^(${lvl})$`);
167
        let resultName = name;
168
        if (type !== 'log') {
169
            // Add the type to the front of the message when it's not "log".
170
            args.unshift(type.toUpperCase() + ':');
171
        }
172
        if (styles) {
173
            resultName = `%c${name}`;
174
            args.unshift(styles);
175
        }
176
 
177
        // Add console prefix after adding to history.
178
        args.unshift(resultName + ':');
179
 
180
        // Add a clone of the args at this point to history.
181
        if (history) {
182
            history.push([].concat(args));
183
 
184
            // only store 1000 history entries
185
            const splice = history.length - 1000;
186
            history.splice(0, splice > 0 ? splice : 0);
187
        }
188
 
189
        // If there's no console then don't try to output messages, but they will
190
        // still be stored in history.
191
        if (!window.console) {
192
            return;
193
        }
194
 
195
        // Was setting these once outside of this function, but containing them
196
        // in the function makes it easier to test cases where console doesn't exist
197
        // when the module is executed.
198
        let fn = window.console[type];
199
        if (!fn && type === 'debug') {
200
            // Certain browsers don't have support for console.debug. For those, we
201
            // should default to the closest comparable log.
202
            fn = window.console.info || window.console.log;
203
        }
204
 
205
        // Bail out if there's no console or if this type is not allowed by the
206
        // current logging level.
207
        if (!fn || !lvl || !lvlRegExp.test(type)) {
208
            return;
209
        }
210
        fn[Array.isArray(args) ? 'apply' : 'call'](window.console, args);
211
    };
212
    function createLogger$1(name, delimiter = ':', styles = '') {
213
        // This is the private tracking variable for logging level.
214
        let level = 'info';
215
 
216
        // the curried logByType bound to the specific log and history
217
        let logByType;
218
 
219
        /**
220
         * Logs plain debug messages. Similar to `console.log`.
221
         *
222
         * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
223
         * of our JSDoc template, we cannot properly document this as both a function
224
         * and a namespace, so its function signature is documented here.
225
         *
226
         * #### Arguments
227
         * ##### *args
228
         * *[]
229
         *
230
         * Any combination of values that could be passed to `console.log()`.
231
         *
232
         * #### Return Value
233
         *
234
         * `undefined`
235
         *
236
         * @namespace
237
         * @param    {...*} args
238
         *           One or more messages or objects that should be logged.
239
         */
240
        const log = function (...args) {
241
            logByType('log', level, args);
242
        };
243
 
244
        // This is the logByType helper that the logging methods below use
245
        logByType = LogByTypeFactory(name, log, styles);
246
 
247
        /**
248
         * Create a new subLogger which chains the old name to the new name.
249
         *
250
         * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following:
251
         * ```js
252
         *  mylogger('foo');
253
         *  // > VIDEOJS: player: foo
254
         * ```
255
         *
256
         * @param {string} subName
257
         *        The name to add call the new logger
258
         * @param {string} [subDelimiter]
259
         *        Optional delimiter
260
         * @param {string} [subStyles]
261
         *        Optional styles
262
         * @return {Object}
263
         */
264
        log.createLogger = (subName, subDelimiter, subStyles) => {
265
            const resultDelimiter = subDelimiter !== undefined ? subDelimiter : delimiter;
266
            const resultStyles = subStyles !== undefined ? subStyles : styles;
267
            const resultName = `${name} ${resultDelimiter} ${subName}`;
268
            return createLogger$1(resultName, resultDelimiter, resultStyles);
269
        };
270
 
271
        /**
272
         * Create a new logger.
273
         *
274
         * @param {string} newName
275
         *        The name for the new logger
276
         * @param {string} [newDelimiter]
277
         *        Optional delimiter
278
         * @param {string} [newStyles]
279
         *        Optional styles
280
         * @return {Object}
281
         */
282
        log.createNewLogger = (newName, newDelimiter, newStyles) => {
283
            return createLogger$1(newName, newDelimiter, newStyles);
284
        };
285
 
286
        /**
287
         * Enumeration of available logging levels, where the keys are the level names
288
         * and the values are `|`-separated strings containing logging methods allowed
289
         * in that logging level. These strings are used to create a regular expression
290
         * matching the function name being called.
291
         *
292
         * Levels provided by Video.js are:
293
         *
294
         * - `off`: Matches no calls. Any value that can be cast to `false` will have
295
         *   this effect. The most restrictive.
296
         * - `all`: Matches only Video.js-provided functions (`debug`, `log`,
297
         *   `log.warn`, and `log.error`).
298
         * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
299
         * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
300
         * - `warn`: Matches `log.warn` and `log.error` calls.
301
         * - `error`: Matches only `log.error` calls.
302
         *
303
         * @type {Object}
304
         */
305
        log.levels = {
306
            all: 'debug|log|warn|error',
307
            off: '',
308
            debug: 'debug|log|warn|error',
309
            info: 'log|warn|error',
310
            warn: 'warn|error',
311
            error: 'error',
312
            DEFAULT: level
313
        };
314
 
315
        /**
316
         * Get or set the current logging level.
317
         *
318
         * If a string matching a key from {@link module:log.levels} is provided, acts
319
         * as a setter.
320
         *
321
         * @param  {'all'|'debug'|'info'|'warn'|'error'|'off'} [lvl]
322
         *         Pass a valid level to set a new logging level.
323
         *
324
         * @return {string}
325
         *         The current logging level.
326
         */
327
        log.level = lvl => {
328
            if (typeof lvl === 'string') {
329
                if (!log.levels.hasOwnProperty(lvl)) {
330
                    throw new Error(`"${lvl}" in not a valid log level`);
331
                }
332
                level = lvl;
333
            }
334
            return level;
335
        };
336
 
337
        /**
338
         * Returns an array containing everything that has been logged to the history.
339
         *
340
         * This array is a shallow clone of the internal history record. However, its
341
         * contents are _not_ cloned; so, mutating objects inside this array will
342
         * mutate them in history.
343
         *
344
         * @return {Array}
345
         */
346
        log.history = () => history ? [].concat(history) : [];
347
 
348
        /**
349
         * Allows you to filter the history by the given logger name
350
         *
351
         * @param {string} fname
352
         *        The name to filter by
353
         *
354
         * @return {Array}
355
         *         The filtered list to return
356
         */
357
        log.history.filter = fname => {
358
            return (history || []).filter(historyItem => {
359
                // if the first item in each historyItem includes `fname`, then it's a match
360
                return new RegExp(`.*${fname}.*`).test(historyItem[0]);
361
            });
362
        };
363
 
364
        /**
365
         * Clears the internal history tracking, but does not prevent further history
366
         * tracking.
367
         */
368
        log.history.clear = () => {
369
            if (history) {
370
                history.length = 0;
371
            }
372
        };
373
 
374
        /**
375
         * Disable history tracking if it is currently enabled.
376
         */
377
        log.history.disable = () => {
378
            if (history !== null) {
379
                history.length = 0;
380
                history = null;
381
            }
382
        };
383
 
384
        /**
385
         * Enable history tracking if it is currently disabled.
386
         */
387
        log.history.enable = () => {
388
            if (history === null) {
389
                history = [];
390
            }
391
        };
392
 
393
        /**
394
         * Logs error messages. Similar to `console.error`.
395
         *
396
         * @param {...*} args
397
         *        One or more messages or objects that should be logged as an error
398
         */
399
        log.error = (...args) => logByType('error', level, args);
400
 
401
        /**
402
         * Logs warning messages. Similar to `console.warn`.
403
         *
404
         * @param {...*} args
405
         *        One or more messages or objects that should be logged as a warning.
406
         */
407
        log.warn = (...args) => logByType('warn', level, args);
408
 
409
        /**
410
         * Logs debug messages. Similar to `console.debug`, but may also act as a comparable
411
         * log if `console.debug` is not available
412
         *
413
         * @param {...*} args
414
         *        One or more messages or objects that should be logged as debug.
415
         */
416
        log.debug = (...args) => logByType('debug', level, args);
417
        return log;
418
    }
419
 
420
    /**
421
     * @file log.js
422
     * @module log
423
     */
424
    const log$1 = createLogger$1('VIDEOJS');
425
    const createLogger = log$1.createLogger;
426
 
427
    /**
428
     * @file obj.js
429
     * @module obj
430
     */
431
 
432
    /**
433
     * @callback obj:EachCallback
434
     *
435
     * @param {*} value
436
     *        The current key for the object that is being iterated over.
437
     *
438
     * @param {string} key
439
     *        The current key-value for object that is being iterated over
440
     */
441
 
442
    /**
443
     * @callback obj:ReduceCallback
444
     *
445
     * @param {*} accum
446
     *        The value that is accumulating over the reduce loop.
447
     *
448
     * @param {*} value
449
     *        The current key for the object that is being iterated over.
450
     *
451
     * @param {string} key
452
     *        The current key-value for object that is being iterated over
453
     *
454
     * @return {*}
455
     *         The new accumulated value.
456
     */
457
    const toString$1 = Object.prototype.toString;
458
 
459
    /**
460
     * Get the keys of an Object
461
     *
462
     * @param {Object}
463
     *        The Object to get the keys from
464
     *
465
     * @return {string[]}
466
     *         An array of the keys from the object. Returns an empty array if the
467
     *         object passed in was invalid or had no keys.
468
     *
469
     * @private
470
     */
471
    const keys = function (object) {
472
        return isObject$1(object) ? Object.keys(object) : [];
473
    };
474
 
475
    /**
476
     * Array-like iteration for objects.
477
     *
478
     * @param {Object} object
479
     *        The object to iterate over
480
     *
481
     * @param {obj:EachCallback} fn
482
     *        The callback function which is called for each key in the object.
483
     */
484
    function each(object, fn) {
485
        keys(object).forEach(key => fn(object[key], key));
486
    }
487
 
488
    /**
489
     * Array-like reduce for objects.
490
     *
491
     * @param {Object} object
492
     *        The Object that you want to reduce.
493
     *
494
     * @param {Function} fn
495
     *         A callback function which is called for each key in the object. It
496
     *         receives the accumulated value and the per-iteration value and key
497
     *         as arguments.
498
     *
499
     * @param {*} [initial = 0]
500
     *        Starting value
501
     *
502
     * @return {*}
503
     *         The final accumulated value.
504
     */
505
    function reduce(object, fn, initial = 0) {
506
        return keys(object).reduce((accum, key) => fn(accum, object[key], key), initial);
507
    }
508
 
509
    /**
510
     * Returns whether a value is an object of any kind - including DOM nodes,
511
     * arrays, regular expressions, etc. Not functions, though.
512
     *
513
     * This avoids the gotcha where using `typeof` on a `null` value
514
     * results in `'object'`.
515
     *
516
     * @param  {Object} value
517
     * @return {boolean}
518
     */
519
    function isObject$1(value) {
520
        return !!value && typeof value === 'object';
521
    }
522
 
523
    /**
524
     * Returns whether an object appears to be a "plain" object - that is, a
525
     * direct instance of `Object`.
526
     *
527
     * @param  {Object} value
528
     * @return {boolean}
529
     */
530
    function isPlain(value) {
531
        return isObject$1(value) && toString$1.call(value) === '[object Object]' && value.constructor === Object;
532
    }
533
 
534
    /**
535
     * Merge two objects recursively.
536
     *
537
     * Performs a deep merge like
538
     * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges
539
     * plain objects (not arrays, elements, or anything else).
540
     *
541
     * Non-plain object values will be copied directly from the right-most
542
     * argument.
543
     *
544
     * @param   {Object[]} sources
545
     *          One or more objects to merge into a new object.
546
     *
547
     * @return {Object}
548
     *          A new object that is the merged result of all sources.
549
     */
550
    function merge$2(...sources) {
551
        const result = {};
552
        sources.forEach(source => {
553
            if (!source) {
554
                return;
555
            }
556
            each(source, (value, key) => {
557
                if (!isPlain(value)) {
558
                    result[key] = value;
559
                    return;
560
                }
561
                if (!isPlain(result[key])) {
562
                    result[key] = {};
563
                }
564
                result[key] = merge$2(result[key], value);
565
            });
566
        });
567
        return result;
568
    }
569
 
570
    /**
571
     * Returns an array of values for a given object
572
     *
573
     * @param  {Object} source - target object
574
     * @return {Array<unknown>} - object values
575
     */
576
    function values$1(source = {}) {
577
        const result = [];
578
        for (const key in source) {
579
            if (source.hasOwnProperty(key)) {
580
                const value = source[key];
581
                result.push(value);
582
            }
583
        }
584
        return result;
585
    }
586
 
587
    /**
588
     * Object.defineProperty but "lazy", which means that the value is only set after
589
     * it is retrieved the first time, rather than being set right away.
590
     *
591
     * @param {Object} obj the object to set the property on
592
     * @param {string} key the key for the property to set
593
     * @param {Function} getValue the function used to get the value when it is needed.
594
     * @param {boolean} setter whether a setter should be allowed or not
595
     */
596
    function defineLazyProperty(obj, key, getValue, setter = true) {
597
        const set = value => Object.defineProperty(obj, key, {
598
            value,
599
            enumerable: true,
600
            writable: true
601
        });
602
        const options = {
603
            configurable: true,
604
            enumerable: true,
605
            get() {
606
                const value = getValue();
607
                set(value);
608
                return value;
609
            }
610
        };
611
        if (setter) {
612
            options.set = set;
613
        }
614
        return Object.defineProperty(obj, key, options);
615
    }
616
 
617
    var Obj = /*#__PURE__*/Object.freeze({
618
        __proto__: null,
619
        each: each,
620
        reduce: reduce,
621
        isObject: isObject$1,
622
        isPlain: isPlain,
623
        merge: merge$2,
624
        values: values$1,
625
        defineLazyProperty: defineLazyProperty
626
    });
627
 
628
    /**
629
     * @file browser.js
630
     * @module browser
631
     */
632
 
633
    /**
634
     * Whether or not this device is an iPod.
635
     *
636
     * @static
637
     * @type {Boolean}
638
     */
639
    let IS_IPOD = false;
640
 
641
    /**
642
     * The detected iOS version - or `null`.
643
     *
644
     * @static
645
     * @type {string|null}
646
     */
647
    let IOS_VERSION = null;
648
 
649
    /**
650
     * Whether or not this is an Android device.
651
     *
652
     * @static
653
     * @type {Boolean}
654
     */
655
    let IS_ANDROID = false;
656
 
657
    /**
658
     * The detected Android version - or `null` if not Android or indeterminable.
659
     *
660
     * @static
661
     * @type {number|string|null}
662
     */
663
    let ANDROID_VERSION;
664
 
665
    /**
666
     * Whether or not this is Mozilla Firefox.
667
     *
668
     * @static
669
     * @type {Boolean}
670
     */
671
    let IS_FIREFOX = false;
672
 
673
    /**
674
     * Whether or not this is Microsoft Edge.
675
     *
676
     * @static
677
     * @type {Boolean}
678
     */
679
    let IS_EDGE = false;
680
 
681
    /**
682
     * Whether or not this is any Chromium Browser
683
     *
684
     * @static
685
     * @type {Boolean}
686
     */
687
    let IS_CHROMIUM = false;
688
 
689
    /**
690
     * Whether or not this is any Chromium browser that is not Edge.
691
     *
692
     * This will also be `true` for Chrome on iOS, which will have different support
693
     * as it is actually Safari under the hood.
694
     *
695
     * Deprecated, as the behaviour to not match Edge was to prevent Legacy Edge's UA matching.
696
     * IS_CHROMIUM should be used instead.
697
     * "Chromium but not Edge" could be explicitly tested with IS_CHROMIUM && !IS_EDGE
698
     *
699
     * @static
700
     * @deprecated
701
     * @type {Boolean}
702
     */
703
    let IS_CHROME = false;
704
 
705
    /**
706
     * The detected Chromium version - or `null`.
707
     *
708
     * @static
709
     * @type {number|null}
710
     */
711
    let CHROMIUM_VERSION = null;
712
 
713
    /**
714
     * The detected Google Chrome version - or `null`.
715
     * This has always been the _Chromium_ version, i.e. would return on Chromium Edge.
716
     * Deprecated, use CHROMIUM_VERSION instead.
717
     *
718
     * @static
719
     * @deprecated
720
     * @type {number|null}
721
     */
722
    let CHROME_VERSION = null;
723
 
724
    /**
725
     * The detected Internet Explorer version - or `null`.
726
     *
727
     * @static
728
     * @deprecated
729
     * @type {number|null}
730
     */
731
    let IE_VERSION = null;
732
 
733
    /**
734
     * Whether or not this is desktop Safari.
735
     *
736
     * @static
737
     * @type {Boolean}
738
     */
739
    let IS_SAFARI = false;
740
 
741
    /**
742
     * Whether or not this is a Windows machine.
743
     *
744
     * @static
745
     * @type {Boolean}
746
     */
747
    let IS_WINDOWS = false;
748
 
749
    /**
750
     * Whether or not this device is an iPad.
751
     *
752
     * @static
753
     * @type {Boolean}
754
     */
755
    let IS_IPAD = false;
756
 
757
    /**
758
     * Whether or not this device is an iPhone.
759
     *
760
     * @static
761
     * @type {Boolean}
762
     */
763
        // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
764
        // to identify iPhones, we need to exclude iPads.
765
        // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
766
    let IS_IPHONE = false;
767
 
768
    /**
769
     * Whether or not this device is touch-enabled.
770
     *
771
     * @static
772
     * @const
773
     * @type {Boolean}
774
     */
775
    const TOUCH_ENABLED = Boolean(isReal() && ('ontouchstart' in window || window.navigator.maxTouchPoints || window.DocumentTouch && window.document instanceof window.DocumentTouch));
776
    const UAD = window.navigator && window.navigator.userAgentData;
777
    if (UAD && UAD.platform && UAD.brands) {
778
        // If userAgentData is present, use it instead of userAgent to avoid warnings
779
        // Currently only implemented on Chromium
780
        // userAgentData does not expose Android version, so ANDROID_VERSION remains `null`
781
 
782
        IS_ANDROID = UAD.platform === 'Android';
783
        IS_EDGE = Boolean(UAD.brands.find(b => b.brand === 'Microsoft Edge'));
784
        IS_CHROMIUM = Boolean(UAD.brands.find(b => b.brand === 'Chromium'));
785
        IS_CHROME = !IS_EDGE && IS_CHROMIUM;
786
        CHROMIUM_VERSION = CHROME_VERSION = (UAD.brands.find(b => b.brand === 'Chromium') || {}).version || null;
787
        IS_WINDOWS = UAD.platform === 'Windows';
788
    }
789
 
790
    // If the browser is not Chromium, either userAgentData is not present which could be an old Chromium browser,
791
    //  or it's a browser that has added userAgentData since that we don't have tests for yet. In either case,
792
    // the checks need to be made agiainst the regular userAgent string.
793
    if (!IS_CHROMIUM) {
794
        const USER_AGENT = window.navigator && window.navigator.userAgent || '';
795
        IS_IPOD = /iPod/i.test(USER_AGENT);
796
        IOS_VERSION = function () {
797
            const match = USER_AGENT.match(/OS (\d+)_/i);
798
            if (match && match[1]) {
799
                return match[1];
800
            }
801
            return null;
802
        }();
803
        IS_ANDROID = /Android/i.test(USER_AGENT);
804
        ANDROID_VERSION = function () {
805
            // This matches Android Major.Minor.Patch versions
806
            // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
807
            const match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
808
            if (!match) {
809
                return null;
810
            }
811
            const major = match[1] && parseFloat(match[1]);
812
            const minor = match[2] && parseFloat(match[2]);
813
            if (major && minor) {
814
                return parseFloat(match[1] + '.' + match[2]);
815
            } else if (major) {
816
                return major;
817
            }
818
            return null;
819
        }();
820
        IS_FIREFOX = /Firefox/i.test(USER_AGENT);
821
        IS_EDGE = /Edg/i.test(USER_AGENT);
822
        IS_CHROMIUM = /Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT);
823
        IS_CHROME = !IS_EDGE && IS_CHROMIUM;
824
        CHROMIUM_VERSION = CHROME_VERSION = function () {
825
            const match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
826
            if (match && match[2]) {
827
                return parseFloat(match[2]);
828
            }
829
            return null;
830
        }();
831
        IE_VERSION = function () {
832
            const result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
833
            let version = result && parseFloat(result[1]);
834
            if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
835
                // IE 11 has a different user agent string than other IE versions
836
                version = 11.0;
837
            }
838
            return version;
839
        }();
840
        IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
841
        IS_WINDOWS = /Windows/i.test(USER_AGENT);
842
        IS_IPAD = /iPad/i.test(USER_AGENT) || IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT);
843
        IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
844
    }
845
 
846
    /**
847
     * Whether or not this is an iOS device.
848
     *
849
     * @static
850
     * @const
851
     * @type {Boolean}
852
     */
853
    const IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
854
 
855
    /**
856
     * Whether or not this is any flavor of Safari - including iOS.
857
     *
858
     * @static
859
     * @const
860
     * @type {Boolean}
861
     */
862
    const IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
863
 
864
    var browser = /*#__PURE__*/Object.freeze({
865
        __proto__: null,
866
        get IS_IPOD () { return IS_IPOD; },
867
        get IOS_VERSION () { return IOS_VERSION; },
868
        get IS_ANDROID () { return IS_ANDROID; },
869
        get ANDROID_VERSION () { return ANDROID_VERSION; },
870
        get IS_FIREFOX () { return IS_FIREFOX; },
871
        get IS_EDGE () { return IS_EDGE; },
872
        get IS_CHROMIUM () { return IS_CHROMIUM; },
873
        get IS_CHROME () { return IS_CHROME; },
874
        get CHROMIUM_VERSION () { return CHROMIUM_VERSION; },
875
        get CHROME_VERSION () { return CHROME_VERSION; },
876
        get IE_VERSION () { return IE_VERSION; },
877
        get IS_SAFARI () { return IS_SAFARI; },
878
        get IS_WINDOWS () { return IS_WINDOWS; },
879
        get IS_IPAD () { return IS_IPAD; },
880
        get IS_IPHONE () { return IS_IPHONE; },
881
        TOUCH_ENABLED: TOUCH_ENABLED,
882
        IS_IOS: IS_IOS,
883
        IS_ANY_SAFARI: IS_ANY_SAFARI
884
    });
885
 
886
    /**
887
     * @file dom.js
888
     * @module dom
889
     */
890
 
891
    /**
892
     * Detect if a value is a string with any non-whitespace characters.
893
     *
894
     * @private
895
     * @param  {string} str
896
     *         The string to check
897
     *
898
     * @return {boolean}
899
     *         Will be `true` if the string is non-blank, `false` otherwise.
900
     *
901
     */
902
    function isNonBlankString(str) {
903
        // we use str.trim as it will trim any whitespace characters
904
        // from the front or back of non-whitespace characters. aka
905
        // Any string that contains non-whitespace characters will
906
        // still contain them after `trim` but whitespace only strings
907
        // will have a length of 0, failing this check.
908
        return typeof str === 'string' && Boolean(str.trim());
909
    }
910
 
911
    /**
912
     * Throws an error if the passed string has whitespace. This is used by
913
     * class methods to be relatively consistent with the classList API.
914
     *
915
     * @private
916
     * @param  {string} str
917
     *         The string to check for whitespace.
918
     *
919
     * @throws {Error}
920
     *         Throws an error if there is whitespace in the string.
921
     */
922
    function throwIfWhitespace(str) {
923
        // str.indexOf instead of regex because str.indexOf is faster performance wise.
924
        if (str.indexOf(' ') >= 0) {
925
            throw new Error('class has illegal whitespace characters');
926
        }
927
    }
928
 
929
    /**
930
     * Whether the current DOM interface appears to be real (i.e. not simulated).
931
     *
932
     * @return {boolean}
933
     *         Will be `true` if the DOM appears to be real, `false` otherwise.
934
     */
935
    function isReal() {
936
        // Both document and window will never be undefined thanks to `global`.
937
        return document === window.document;
938
    }
939
 
940
    /**
941
     * Determines, via duck typing, whether or not a value is a DOM element.
942
     *
943
     * @param  {*} value
944
     *         The value to check.
945
     *
946
     * @return {boolean}
947
     *         Will be `true` if the value is a DOM element, `false` otherwise.
948
     */
949
    function isEl(value) {
950
        return isObject$1(value) && value.nodeType === 1;
951
    }
952
 
953
    /**
954
     * Determines if the current DOM is embedded in an iframe.
955
     *
956
     * @return {boolean}
957
     *         Will be `true` if the DOM is embedded in an iframe, `false`
958
     *         otherwise.
959
     */
960
    function isInFrame() {
961
        // We need a try/catch here because Safari will throw errors when attempting
962
        // to get either `parent` or `self`
963
        try {
964
            return window.parent !== window.self;
965
        } catch (x) {
966
            return true;
967
        }
968
    }
969
 
970
    /**
971
     * Creates functions to query the DOM using a given method.
972
     *
973
     * @private
974
     * @param   {string} method
975
     *          The method to create the query with.
976
     *
977
     * @return  {Function}
978
     *          The query method
979
     */
980
    function createQuerier(method) {
981
        return function (selector, context) {
982
            if (!isNonBlankString(selector)) {
983
                return document[method](null);
984
            }
985
            if (isNonBlankString(context)) {
986
                context = document.querySelector(context);
987
            }
988
            const ctx = isEl(context) ? context : document;
989
            return ctx[method] && ctx[method](selector);
990
        };
991
    }
992
 
993
    /**
994
     * Creates an element and applies properties, attributes, and inserts content.
995
     *
996
     * @param  {string} [tagName='div']
997
     *         Name of tag to be created.
998
     *
999
     * @param  {Object} [properties={}]
1000
     *         Element properties to be applied.
1001
     *
1002
     * @param  {Object} [attributes={}]
1003
     *         Element attributes to be applied.
1004
     *
1005
     * @param {ContentDescriptor} [content]
1006
     *        A content descriptor object.
1007
     *
1008
     * @return {Element}
1009
     *         The element that was created.
1010
     */
1011
    function createEl(tagName = 'div', properties = {}, attributes = {}, content) {
1012
        const el = document.createElement(tagName);
1013
        Object.getOwnPropertyNames(properties).forEach(function (propName) {
1014
            const val = properties[propName];
1015
 
1016
            // Handle textContent since it's not supported everywhere and we have a
1017
            // method for it.
1018
            if (propName === 'textContent') {
1019
                textContent(el, val);
1020
            } else if (el[propName] !== val || propName === 'tabIndex') {
1021
                el[propName] = val;
1022
            }
1023
        });
1024
        Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
1025
            el.setAttribute(attrName, attributes[attrName]);
1026
        });
1027
        if (content) {
1028
            appendContent(el, content);
1029
        }
1030
        return el;
1031
    }
1032
 
1033
    /**
1034
     * Injects text into an element, replacing any existing contents entirely.
1035
     *
1036
     * @param  {HTMLElement} el
1037
     *         The element to add text content into
1038
     *
1039
     * @param  {string} text
1040
     *         The text content to add.
1041
     *
1042
     * @return {Element}
1043
     *         The element with added text content.
1044
     */
1045
    function textContent(el, text) {
1046
        if (typeof el.textContent === 'undefined') {
1047
            el.innerText = text;
1048
        } else {
1049
            el.textContent = text;
1050
        }
1051
        return el;
1052
    }
1053
 
1054
    /**
1055
     * Insert an element as the first child node of another
1056
     *
1057
     * @param {Element} child
1058
     *        Element to insert
1059
     *
1060
     * @param {Element} parent
1061
     *        Element to insert child into
1062
     */
1063
    function prependTo(child, parent) {
1064
        if (parent.firstChild) {
1065
            parent.insertBefore(child, parent.firstChild);
1066
        } else {
1067
            parent.appendChild(child);
1068
        }
1069
    }
1070
 
1071
    /**
1072
     * Check if an element has a class name.
1073
     *
1074
     * @param  {Element} element
1075
     *         Element to check
1076
     *
1077
     * @param  {string} classToCheck
1078
     *         Class name to check for
1079
     *
1080
     * @return {boolean}
1081
     *         Will be `true` if the element has a class, `false` otherwise.
1082
     *
1083
     * @throws {Error}
1084
     *         Throws an error if `classToCheck` has white space.
1085
     */
1086
    function hasClass(element, classToCheck) {
1087
        throwIfWhitespace(classToCheck);
1088
        return element.classList.contains(classToCheck);
1089
    }
1090
 
1091
    /**
1092
     * Add a class name to an element.
1093
     *
1094
     * @param  {Element} element
1095
     *         Element to add class name to.
1096
     *
1097
     * @param  {...string} classesToAdd
1098
     *         One or more class name to add.
1099
     *
1100
     * @return {Element}
1101
     *         The DOM element with the added class name.
1102
     */
1103
    function addClass(element, ...classesToAdd) {
1104
        element.classList.add(...classesToAdd.reduce((prev, current) => prev.concat(current.split(/\s+/)), []));
1105
        return element;
1106
    }
1107
 
1108
    /**
1109
     * Remove a class name from an element.
1110
     *
1111
     * @param  {Element} element
1112
     *         Element to remove a class name from.
1113
     *
1114
     * @param  {...string} classesToRemove
1115
     *         One or more class name to remove.
1116
     *
1117
     * @return {Element}
1118
     *         The DOM element with class name removed.
1119
     */
1120
    function removeClass(element, ...classesToRemove) {
1121
        // Protect in case the player gets disposed
1122
        if (!element) {
1123
            log$1.warn("removeClass was called with an element that doesn't exist");
1124
            return null;
1125
        }
1126
        element.classList.remove(...classesToRemove.reduce((prev, current) => prev.concat(current.split(/\s+/)), []));
1127
        return element;
1128
    }
1129
 
1130
    /**
1131
     * The callback definition for toggleClass.
1132
     *
1133
     * @callback module:dom~PredicateCallback
1134
     * @param    {Element} element
1135
     *           The DOM element of the Component.
1136
     *
1137
     * @param    {string} classToToggle
1138
     *           The `className` that wants to be toggled
1139
     *
1140
     * @return   {boolean|undefined}
1141
     *           If `true` is returned, the `classToToggle` will be added to the
1142
     *           `element`. If `false`, the `classToToggle` will be removed from
1143
     *           the `element`. If `undefined`, the callback will be ignored.
1144
     */
1145
 
1146
    /**
1147
     * Adds or removes a class name to/from an element depending on an optional
1148
     * condition or the presence/absence of the class name.
1149
     *
1150
     * @param  {Element} element
1151
     *         The element to toggle a class name on.
1152
     *
1153
     * @param  {string} classToToggle
1154
     *         The class that should be toggled.
1155
     *
1156
     * @param  {boolean|module:dom~PredicateCallback} [predicate]
1157
     *         See the return value for {@link module:dom~PredicateCallback}
1158
     *
1159
     * @return {Element}
1160
     *         The element with a class that has been toggled.
1161
     */
1162
    function toggleClass(element, classToToggle, predicate) {
1163
        if (typeof predicate === 'function') {
1164
            predicate = predicate(element, classToToggle);
1165
        }
1166
        if (typeof predicate !== 'boolean') {
1167
            predicate = undefined;
1168
        }
1169
        classToToggle.split(/\s+/).forEach(className => element.classList.toggle(className, predicate));
1170
        return element;
1171
    }
1172
 
1173
    /**
1174
     * Apply attributes to an HTML element.
1175
     *
1176
     * @param {Element} el
1177
     *        Element to add attributes to.
1178
     *
1179
     * @param {Object} [attributes]
1180
     *        Attributes to be applied.
1181
     */
1182
    function setAttributes(el, attributes) {
1183
        Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
1184
            const attrValue = attributes[attrName];
1185
            if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
1186
                el.removeAttribute(attrName);
1187
            } else {
1188
                el.setAttribute(attrName, attrValue === true ? '' : attrValue);
1189
            }
1190
        });
1191
    }
1192
 
1193
    /**
1194
     * Get an element's attribute values, as defined on the HTML tag.
1195
     *
1196
     * Attributes are not the same as properties. They're defined on the tag
1197
     * or with setAttribute.
1198
     *
1199
     * @param  {Element} tag
1200
     *         Element from which to get tag attributes.
1201
     *
1202
     * @return {Object}
1203
     *         All attributes of the element. Boolean attributes will be `true` or
1204
     *         `false`, others will be strings.
1205
     */
1206
    function getAttributes(tag) {
1207
        const obj = {};
1208
 
1209
        // known boolean attributes
1210
        // we can check for matching boolean properties, but not all browsers
1211
        // and not all tags know about these attributes, so, we still want to check them manually
1212
        const knownBooleans = ['autoplay', 'controls', 'playsinline', 'loop', 'muted', 'default', 'defaultMuted'];
1213
        if (tag && tag.attributes && tag.attributes.length > 0) {
1214
            const attrs = tag.attributes;
1215
            for (let i = attrs.length - 1; i >= 0; i--) {
1216
                const attrName = attrs[i].name;
1217
                /** @type {boolean|string} */
1218
                let attrVal = attrs[i].value;
1219
 
1220
                // check for known booleans
1221
                // the matching element property will return a value for typeof
1222
                if (knownBooleans.includes(attrName)) {
1223
                    // the value of an included boolean attribute is typically an empty
1224
                    // string ('') which would equal false if we just check for a false value.
1225
                    // we also don't want support bad code like autoplay='false'
1226
                    attrVal = attrVal !== null ? true : false;
1227
                }
1228
                obj[attrName] = attrVal;
1229
            }
1230
        }
1231
        return obj;
1232
    }
1233
 
1234
    /**
1235
     * Get the value of an element's attribute.
1236
     *
1237
     * @param {Element} el
1238
     *        A DOM element.
1239
     *
1240
     * @param {string} attribute
1241
     *        Attribute to get the value of.
1242
     *
1243
     * @return {string}
1244
     *         The value of the attribute.
1245
     */
1246
    function getAttribute(el, attribute) {
1247
        return el.getAttribute(attribute);
1248
    }
1249
 
1250
    /**
1251
     * Set the value of an element's attribute.
1252
     *
1253
     * @param {Element} el
1254
     *        A DOM element.
1255
     *
1256
     * @param {string} attribute
1257
     *        Attribute to set.
1258
     *
1259
     * @param {string} value
1260
     *        Value to set the attribute to.
1261
     */
1262
    function setAttribute(el, attribute, value) {
1263
        el.setAttribute(attribute, value);
1264
    }
1265
 
1266
    /**
1267
     * Remove an element's attribute.
1268
     *
1269
     * @param {Element} el
1270
     *        A DOM element.
1271
     *
1272
     * @param {string} attribute
1273
     *        Attribute to remove.
1274
     */
1275
    function removeAttribute(el, attribute) {
1276
        el.removeAttribute(attribute);
1277
    }
1278
 
1279
    /**
1280
     * Attempt to block the ability to select text.
1281
     */
1282
    function blockTextSelection() {
1283
        document.body.focus();
1284
        document.onselectstart = function () {
1285
            return false;
1286
        };
1287
    }
1288
 
1289
    /**
1290
     * Turn off text selection blocking.
1291
     */
1292
    function unblockTextSelection() {
1293
        document.onselectstart = function () {
1294
            return true;
1295
        };
1296
    }
1297
 
1298
    /**
1299
     * Identical to the native `getBoundingClientRect` function, but ensures that
1300
     * the method is supported at all (it is in all browsers we claim to support)
1301
     * and that the element is in the DOM before continuing.
1302
     *
1303
     * This wrapper function also shims properties which are not provided by some
1304
     * older browsers (namely, IE8).
1305
     *
1306
     * Additionally, some browsers do not support adding properties to a
1307
     * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
1308
     * properties (except `x` and `y` which are not widely supported). This helps
1309
     * avoid implementations where keys are non-enumerable.
1310
     *
1311
     * @param  {Element} el
1312
     *         Element whose `ClientRect` we want to calculate.
1313
     *
1314
     * @return {Object|undefined}
1315
     *         Always returns a plain object - or `undefined` if it cannot.
1316
     */
1317
    function getBoundingClientRect(el) {
1318
        if (el && el.getBoundingClientRect && el.parentNode) {
1319
            const rect = el.getBoundingClientRect();
1320
            const result = {};
1321
            ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(k => {
1322
                if (rect[k] !== undefined) {
1323
                    result[k] = rect[k];
1324
                }
1325
            });
1326
            if (!result.height) {
1327
                result.height = parseFloat(computedStyle(el, 'height'));
1328
            }
1329
            if (!result.width) {
1330
                result.width = parseFloat(computedStyle(el, 'width'));
1331
            }
1332
            return result;
1333
        }
1334
    }
1335
 
1336
    /**
1337
     * Represents the position of a DOM element on the page.
1338
     *
1339
     * @typedef  {Object} module:dom~Position
1340
     *
1341
     * @property {number} left
1342
     *           Pixels to the left.
1343
     *
1344
     * @property {number} top
1345
     *           Pixels from the top.
1346
     */
1347
 
1348
    /**
1349
     * Get the position of an element in the DOM.
1350
     *
1351
     * Uses `getBoundingClientRect` technique from John Resig.
1352
     *
1353
     * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
1354
     *
1355
     * @param  {Element} el
1356
     *         Element from which to get offset.
1357
     *
1358
     * @return {module:dom~Position}
1359
     *         The position of the element that was passed in.
1360
     */
1361
    function findPosition(el) {
1362
        if (!el || el && !el.offsetParent) {
1363
            return {
1364
                left: 0,
1365
                top: 0,
1366
                width: 0,
1367
                height: 0
1368
            };
1369
        }
1370
        const width = el.offsetWidth;
1371
        const height = el.offsetHeight;
1372
        let left = 0;
1373
        let top = 0;
1374
        while (el.offsetParent && el !== document[FullscreenApi.fullscreenElement]) {
1375
            left += el.offsetLeft;
1376
            top += el.offsetTop;
1377
            el = el.offsetParent;
1378
        }
1379
        return {
1380
            left,
1381
            top,
1382
            width,
1383
            height
1384
        };
1385
    }
1386
 
1387
    /**
1388
     * Represents x and y coordinates for a DOM element or mouse pointer.
1389
     *
1390
     * @typedef  {Object} module:dom~Coordinates
1391
     *
1392
     * @property {number} x
1393
     *           x coordinate in pixels
1394
     *
1395
     * @property {number} y
1396
     *           y coordinate in pixels
1397
     */
1398
 
1399
    /**
1400
     * Get the pointer position within an element.
1401
     *
1402
     * The base on the coordinates are the bottom left of the element.
1403
     *
1404
     * @param  {Element} el
1405
     *         Element on which to get the pointer position on.
1406
     *
1407
     * @param  {Event} event
1408
     *         Event object.
1409
     *
1410
     * @return {module:dom~Coordinates}
1411
     *         A coordinates object corresponding to the mouse position.
1412
     *
1413
     */
1414
    function getPointerPosition(el, event) {
1415
        const translated = {
1416
            x: 0,
1417
            y: 0
1418
        };
1419
        if (IS_IOS) {
1420
            let item = el;
1421
            while (item && item.nodeName.toLowerCase() !== 'html') {
1422
                const transform = computedStyle(item, 'transform');
1423
                if (/^matrix/.test(transform)) {
1424
                    const values = transform.slice(7, -1).split(/,\s/).map(Number);
1425
                    translated.x += values[4];
1426
                    translated.y += values[5];
1427
                } else if (/^matrix3d/.test(transform)) {
1428
                    const values = transform.slice(9, -1).split(/,\s/).map(Number);
1429
                    translated.x += values[12];
1430
                    translated.y += values[13];
1431
                }
1432
                item = item.parentNode;
1433
            }
1434
        }
1435
        const position = {};
1436
        const boxTarget = findPosition(event.target);
1437
        const box = findPosition(el);
1438
        const boxW = box.width;
1439
        const boxH = box.height;
1440
        let offsetY = event.offsetY - (box.top - boxTarget.top);
1441
        let offsetX = event.offsetX - (box.left - boxTarget.left);
1442
        if (event.changedTouches) {
1443
            offsetX = event.changedTouches[0].pageX - box.left;
1444
            offsetY = event.changedTouches[0].pageY + box.top;
1445
            if (IS_IOS) {
1446
                offsetX -= translated.x;
1447
                offsetY -= translated.y;
1448
            }
1449
        }
1450
        position.y = 1 - Math.max(0, Math.min(1, offsetY / boxH));
1451
        position.x = Math.max(0, Math.min(1, offsetX / boxW));
1452
        return position;
1453
    }
1454
 
1455
    /**
1456
     * Determines, via duck typing, whether or not a value is a text node.
1457
     *
1458
     * @param  {*} value
1459
     *         Check if this value is a text node.
1460
     *
1461
     * @return {boolean}
1462
     *         Will be `true` if the value is a text node, `false` otherwise.
1463
     */
1464
    function isTextNode$1(value) {
1465
        return isObject$1(value) && value.nodeType === 3;
1466
    }
1467
 
1468
    /**
1469
     * Empties the contents of an element.
1470
     *
1471
     * @param  {Element} el
1472
     *         The element to empty children from
1473
     *
1474
     * @return {Element}
1475
     *         The element with no children
1476
     */
1477
    function emptyEl(el) {
1478
        while (el.firstChild) {
1479
            el.removeChild(el.firstChild);
1480
        }
1481
        return el;
1482
    }
1483
 
1484
    /**
1485
     * This is a mixed value that describes content to be injected into the DOM
1486
     * via some method. It can be of the following types:
1487
     *
1488
     * Type       | Description
1489
     * -----------|-------------
1490
     * `string`   | The value will be normalized into a text node.
1491
     * `Element`  | The value will be accepted as-is.
1492
     * `Text`     | A TextNode. The value will be accepted as-is.
1493
     * `Array`    | A one-dimensional array of strings, elements, text nodes, or functions. These functions should return a string, element, or text node (any other return value, like an array, will be ignored).
1494
     * `Function` | A function, which is expected to return a string, element, text node, or array - any of the other possible values described above. This means that a content descriptor could be a function that returns an array of functions, but those second-level functions must return strings, elements, or text nodes.
1495
     *
1496
     * @typedef {string|Element|Text|Array|Function} ContentDescriptor
1497
     */
1498
 
1499
    /**
1500
     * Normalizes content for eventual insertion into the DOM.
1501
     *
1502
     * This allows a wide range of content definition methods, but helps protect
1503
     * from falling into the trap of simply writing to `innerHTML`, which could
1504
     * be an XSS concern.
1505
     *
1506
     * The content for an element can be passed in multiple types and
1507
     * combinations, whose behavior is as follows:
1508
     *
1509
     * @param {ContentDescriptor} content
1510
     *        A content descriptor value.
1511
     *
1512
     * @return {Array}
1513
     *         All of the content that was passed in, normalized to an array of
1514
     *         elements or text nodes.
1515
     */
1516
    function normalizeContent(content) {
1517
        // First, invoke content if it is a function. If it produces an array,
1518
        // that needs to happen before normalization.
1519
        if (typeof content === 'function') {
1520
            content = content();
1521
        }
1522
 
1523
        // Next up, normalize to an array, so one or many items can be normalized,
1524
        // filtered, and returned.
1525
        return (Array.isArray(content) ? content : [content]).map(value => {
1526
            // First, invoke value if it is a function to produce a new value,
1527
            // which will be subsequently normalized to a Node of some kind.
1528
            if (typeof value === 'function') {
1529
                value = value();
1530
            }
1531
            if (isEl(value) || isTextNode$1(value)) {
1532
                return value;
1533
            }
1534
            if (typeof value === 'string' && /\S/.test(value)) {
1535
                return document.createTextNode(value);
1536
            }
1537
        }).filter(value => value);
1538
    }
1539
 
1540
    /**
1541
     * Normalizes and appends content to an element.
1542
     *
1543
     * @param  {Element} el
1544
     *         Element to append normalized content to.
1545
     *
1546
     * @param {ContentDescriptor} content
1547
     *        A content descriptor value.
1548
     *
1549
     * @return {Element}
1550
     *         The element with appended normalized content.
1551
     */
1552
    function appendContent(el, content) {
1553
        normalizeContent(content).forEach(node => el.appendChild(node));
1554
        return el;
1555
    }
1556
 
1557
    /**
1558
     * Normalizes and inserts content into an element; this is identical to
1559
     * `appendContent()`, except it empties the element first.
1560
     *
1561
     * @param {Element} el
1562
     *        Element to insert normalized content into.
1563
     *
1564
     * @param {ContentDescriptor} content
1565
     *        A content descriptor value.
1566
     *
1567
     * @return {Element}
1568
     *         The element with inserted normalized content.
1569
     */
1570
    function insertContent(el, content) {
1571
        return appendContent(emptyEl(el), content);
1572
    }
1573
 
1574
    /**
1575
     * Check if an event was a single left click.
1576
     *
1577
     * @param  {MouseEvent} event
1578
     *         Event object.
1579
     *
1580
     * @return {boolean}
1581
     *         Will be `true` if a single left click, `false` otherwise.
1582
     */
1583
    function isSingleLeftClick(event) {
1584
        // Note: if you create something draggable, be sure to
1585
        // call it on both `mousedown` and `mousemove` event,
1586
        // otherwise `mousedown` should be enough for a button
1587
 
1588
        if (event.button === undefined && event.buttons === undefined) {
1589
            // Why do we need `buttons` ?
1590
            // Because, middle mouse sometimes have this:
1591
            // e.button === 0 and e.buttons === 4
1592
            // Furthermore, we want to prevent combination click, something like
1593
            // HOLD middlemouse then left click, that would be
1594
            // e.button === 0, e.buttons === 5
1595
            // just `button` is not gonna work
1596
 
1597
            // Alright, then what this block does ?
1598
            // this is for chrome `simulate mobile devices`
1599
            // I want to support this as well
1600
 
1601
            return true;
1602
        }
1603
        if (event.button === 0 && event.buttons === undefined) {
1604
            // Touch screen, sometimes on some specific device, `buttons`
1605
            // doesn't have anything (safari on ios, blackberry...)
1606
 
1607
            return true;
1608
        }
1609
 
1610
        // `mouseup` event on a single left click has
1611
        // `button` and `buttons` equal to 0
1612
        if (event.type === 'mouseup' && event.button === 0 && event.buttons === 0) {
1613
            return true;
1614
        }
1615
        if (event.button !== 0 || event.buttons !== 1) {
1616
            // This is the reason we have those if else block above
1617
            // if any special case we can catch and let it slide
1618
            // we do it above, when get to here, this definitely
1619
            // is-not-left-click
1620
 
1621
            return false;
1622
        }
1623
        return true;
1624
    }
1625
 
1626
    /**
1627
     * Finds a single DOM element matching `selector` within the optional
1628
     * `context` of another DOM element (defaulting to `document`).
1629
     *
1630
     * @param  {string} selector
1631
     *         A valid CSS selector, which will be passed to `querySelector`.
1632
     *
1633
     * @param  {Element|String} [context=document]
1634
     *         A DOM element within which to query. Can also be a selector
1635
     *         string in which case the first matching element will be used
1636
     *         as context. If missing (or no element matches selector), falls
1637
     *         back to `document`.
1638
     *
1639
     * @return {Element|null}
1640
     *         The element that was found or null.
1641
     */
1642
    const $ = createQuerier('querySelector');
1643
 
1644
    /**
1645
     * Finds a all DOM elements matching `selector` within the optional
1646
     * `context` of another DOM element (defaulting to `document`).
1647
     *
1648
     * @param  {string} selector
1649
     *         A valid CSS selector, which will be passed to `querySelectorAll`.
1650
     *
1651
     * @param  {Element|String} [context=document]
1652
     *         A DOM element within which to query. Can also be a selector
1653
     *         string in which case the first matching element will be used
1654
     *         as context. If missing (or no element matches selector), falls
1655
     *         back to `document`.
1656
     *
1657
     * @return {NodeList}
1658
     *         A element list of elements that were found. Will be empty if none
1659
     *         were found.
1660
     *
1661
     */
1662
    const $$ = createQuerier('querySelectorAll');
1663
 
1664
    /**
1665
     * A safe getComputedStyle.
1666
     *
1667
     * This is needed because in Firefox, if the player is loaded in an iframe with
1668
     * `display:none`, then `getComputedStyle` returns `null`, so, we do a
1669
     * null-check to make sure that the player doesn't break in these cases.
1670
     *
1671
     * @param    {Element} el
1672
     *           The element you want the computed style of
1673
     *
1674
     * @param    {string} prop
1675
     *           The property name you want
1676
     *
1677
     * @see      https://bugzilla.mozilla.org/show_bug.cgi?id=548397
1678
     */
1679
    function computedStyle(el, prop) {
1680
        if (!el || !prop) {
1681
            return '';
1682
        }
1683
        if (typeof window.getComputedStyle === 'function') {
1684
            let computedStyleValue;
1685
            try {
1686
                computedStyleValue = window.getComputedStyle(el);
1687
            } catch (e) {
1688
                return '';
1689
            }
1690
            return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : '';
1691
        }
1692
        return '';
1693
    }
1694
 
1695
    /**
1696
     * Copy document style sheets to another window.
1697
     *
1698
     * @param    {Window} win
1699
     *           The window element you want to copy the document style sheets to.
1700
     *
1701
     */
1702
    function copyStyleSheetsToWindow(win) {
1703
        [...document.styleSheets].forEach(styleSheet => {
1704
            try {
1705
                const cssRules = [...styleSheet.cssRules].map(rule => rule.cssText).join('');
1706
                const style = document.createElement('style');
1707
                style.textContent = cssRules;
1708
                win.document.head.appendChild(style);
1709
            } catch (e) {
1710
                const link = document.createElement('link');
1711
                link.rel = 'stylesheet';
1712
                link.type = styleSheet.type;
1713
                // For older Safari this has to be the string; on other browsers setting the MediaList works
1714
                link.media = styleSheet.media.mediaText;
1715
                link.href = styleSheet.href;
1716
                win.document.head.appendChild(link);
1717
            }
1718
        });
1719
    }
1720
 
1721
    var Dom = /*#__PURE__*/Object.freeze({
1722
        __proto__: null,
1723
        isReal: isReal,
1724
        isEl: isEl,
1725
        isInFrame: isInFrame,
1726
        createEl: createEl,
1727
        textContent: textContent,
1728
        prependTo: prependTo,
1729
        hasClass: hasClass,
1730
        addClass: addClass,
1731
        removeClass: removeClass,
1732
        toggleClass: toggleClass,
1733
        setAttributes: setAttributes,
1734
        getAttributes: getAttributes,
1735
        getAttribute: getAttribute,
1736
        setAttribute: setAttribute,
1737
        removeAttribute: removeAttribute,
1738
        blockTextSelection: blockTextSelection,
1739
        unblockTextSelection: unblockTextSelection,
1740
        getBoundingClientRect: getBoundingClientRect,
1741
        findPosition: findPosition,
1742
        getPointerPosition: getPointerPosition,
1743
        isTextNode: isTextNode$1,
1744
        emptyEl: emptyEl,
1745
        normalizeContent: normalizeContent,
1746
        appendContent: appendContent,
1747
        insertContent: insertContent,
1748
        isSingleLeftClick: isSingleLeftClick,
1749
        $: $,
1750
        $$: $$,
1751
        computedStyle: computedStyle,
1752
        copyStyleSheetsToWindow: copyStyleSheetsToWindow
1753
    });
1754
 
1755
    /**
1756
     * @file setup.js - Functions for setting up a player without
1757
     * user interaction based on the data-setup `attribute` of the video tag.
1758
     *
1759
     * @module setup
1760
     */
1761
    let _windowLoaded = false;
1762
    let videojs$1;
1763
 
1764
    /**
1765
     * Set up any tags that have a data-setup `attribute` when the player is started.
1766
     */
1767
    const autoSetup = function () {
1768
        if (videojs$1.options.autoSetup === false) {
1769
            return;
1770
        }
1771
        const vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
1772
        const audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
1773
        const divs = Array.prototype.slice.call(document.getElementsByTagName('video-js'));
1774
        const mediaEls = vids.concat(audios, divs);
1775
 
1776
        // Check if any media elements exist
1777
        if (mediaEls && mediaEls.length > 0) {
1778
            for (let i = 0, e = mediaEls.length; i < e; i++) {
1779
                const mediaEl = mediaEls[i];
1780
 
1781
                // Check if element exists, has getAttribute func.
1782
                if (mediaEl && mediaEl.getAttribute) {
1783
                    // Make sure this player hasn't already been set up.
1784
                    if (mediaEl.player === undefined) {
1785
                        const options = mediaEl.getAttribute('data-setup');
1786
 
1787
                        // Check if data-setup attr exists.
1788
                        // We only auto-setup if they've added the data-setup attr.
1789
                        if (options !== null) {
1790
                            // Create new video.js instance.
1791
                            videojs$1(mediaEl);
1792
                        }
1793
                    }
1794
 
1795
                    // If getAttribute isn't defined, we need to wait for the DOM.
1796
                } else {
1797
                    autoSetupTimeout(1);
1798
                    break;
1799
                }
1800
            }
1801
 
1802
            // No videos were found, so keep looping unless page is finished loading.
1803
        } else if (!_windowLoaded) {
1804
            autoSetupTimeout(1);
1805
        }
1806
    };
1807
 
1808
    /**
1809
     * Wait until the page is loaded before running autoSetup. This will be called in
1810
     * autoSetup if `hasLoaded` returns false.
1811
     *
1812
     * @param {number} wait
1813
     *        How long to wait in ms
1814
     *
1815
     * @param {module:videojs} [vjs]
1816
     *        The videojs library function
1817
     */
1818
    function autoSetupTimeout(wait, vjs) {
1819
        // Protect against breakage in non-browser environments
1820
        if (!isReal()) {
1821
            return;
1822
        }
1823
        if (vjs) {
1824
            videojs$1 = vjs;
1825
        }
1826
        window.setTimeout(autoSetup, wait);
1827
    }
1828
 
1829
    /**
1830
     * Used to set the internal tracking of window loaded state to true.
1831
     *
1832
     * @private
1833
     */
1834
    function setWindowLoaded() {
1835
        _windowLoaded = true;
1836
        window.removeEventListener('load', setWindowLoaded);
1837
    }
1838
    if (isReal()) {
1839
        if (document.readyState === 'complete') {
1840
            setWindowLoaded();
1841
        } else {
1842
            /**
1843
             * Listen for the load event on window, and set _windowLoaded to true.
1844
             *
1845
             * We use a standard event listener here to avoid incrementing the GUID
1846
             * before any players are created.
1847
             *
1848
             * @listens load
1849
             */
1850
            window.addEventListener('load', setWindowLoaded);
1851
        }
1852
    }
1853
 
1854
    /**
1855
     * @file stylesheet.js
1856
     * @module stylesheet
1857
     */
1858
 
1859
    /**
1860
     * Create a DOM style element given a className for it.
1861
     *
1862
     * @param {string} className
1863
     *        The className to add to the created style element.
1864
     *
1865
     * @return {Element}
1866
     *         The element that was created.
1867
     */
1868
    const createStyleElement = function (className) {
1869
        const style = document.createElement('style');
1870
        style.className = className;
1871
        return style;
1872
    };
1873
 
1874
    /**
1875
     * Add text to a DOM element.
1876
     *
1877
     * @param {Element} el
1878
     *        The Element to add text content to.
1879
     *
1880
     * @param {string} content
1881
     *        The text to add to the element.
1882
     */
1883
    const setTextContent = function (el, content) {
1884
        if (el.styleSheet) {
1885
            el.styleSheet.cssText = content;
1886
        } else {
1887
            el.textContent = content;
1888
        }
1889
    };
1890
 
1891
    /**
1892
     * @file dom-data.js
1893
     * @module dom-data
1894
     */
1895
 
1896
    /**
1897
     * Element Data Store.
1898
     *
1899
     * Allows for binding data to an element without putting it directly on the
1900
     * element. Ex. Event listeners are stored here.
1901
     * (also from jsninja.com, slightly modified and updated for closure compiler)
1902
     *
1903
     * @type {Object}
1904
     * @private
1905
     */
1906
    var DomData = new WeakMap();
1907
 
1908
    /**
1909
     * @file guid.js
1910
     * @module guid
1911
     */
1912
 
1913
        // Default value for GUIDs. This allows us to reset the GUID counter in tests.
1914
        //
1915
        // The initial GUID is 3 because some users have come to rely on the first
1916
        // default player ID ending up as `vjs_video_3`.
1917
        //
1918
        // See: https://github.com/videojs/video.js/pull/6216
1919
    const _initialGuid = 3;
1920
 
1921
    /**
1922
     * Unique ID for an element or function
1923
     *
1924
     * @type {Number}
1925
     */
1926
    let _guid = _initialGuid;
1927
 
1928
    /**
1929
     * Get a unique auto-incrementing ID by number that has not been returned before.
1930
     *
1931
     * @return {number}
1932
     *         A new unique ID.
1933
     */
1934
    function newGUID() {
1935
        return _guid++;
1936
    }
1937
 
1938
    /**
1939
     * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
1940
     * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
1941
     * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
1942
     * robust as jquery's, so there's probably some differences.
1943
     *
1944
     * @file events.js
1945
     * @module events
1946
     */
1947
 
1948
    /**
1949
     * Clean up the listener cache and dispatchers
1950
     *
1951
     * @param {Element|Object} elem
1952
     *        Element to clean up
1953
     *
1954
     * @param {string} type
1955
     *        Type of event to clean up
1956
     */
1957
    function _cleanUpEvents(elem, type) {
1958
        if (!DomData.has(elem)) {
1959
            return;
1960
        }
1961
        const data = DomData.get(elem);
1962
 
1963
        // Remove the events of a particular type if there are none left
1964
        if (data.handlers[type].length === 0) {
1965
            delete data.handlers[type];
1966
            // data.handlers[type] = null;
1967
            // Setting to null was causing an error with data.handlers
1968
 
1969
            // Remove the meta-handler from the element
1970
            if (elem.removeEventListener) {
1971
                elem.removeEventListener(type, data.dispatcher, false);
1972
            } else if (elem.detachEvent) {
1973
                elem.detachEvent('on' + type, data.dispatcher);
1974
            }
1975
        }
1976
 
1977
        // Remove the events object if there are no types left
1978
        if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
1979
            delete data.handlers;
1980
            delete data.dispatcher;
1981
            delete data.disabled;
1982
        }
1983
 
1984
        // Finally remove the element data if there is no data left
1985
        if (Object.getOwnPropertyNames(data).length === 0) {
1986
            DomData.delete(elem);
1987
        }
1988
    }
1989
 
1990
    /**
1991
     * Loops through an array of event types and calls the requested method for each type.
1992
     *
1993
     * @param {Function} fn
1994
     *        The event method we want to use.
1995
     *
1996
     * @param {Element|Object} elem
1997
     *        Element or object to bind listeners to
1998
     *
1999
     * @param {string[]} types
2000
     *        Type of event to bind to.
2001
     *
2002
     * @param {Function} callback
2003
     *        Event listener.
2004
     */
2005
    function _handleMultipleEvents(fn, elem, types, callback) {
2006
        types.forEach(function (type) {
2007
            // Call the event method for each one of the types
2008
            fn(elem, type, callback);
2009
        });
2010
    }
2011
 
2012
    /**
2013
     * Fix a native event to have standard property values
2014
     *
2015
     * @param {Object} event
2016
     *        Event object to fix.
2017
     *
2018
     * @return {Object}
2019
     *         Fixed event object.
2020
     */
2021
    function fixEvent(event) {
2022
        if (event.fixed_) {
2023
            return event;
2024
        }
2025
        function returnTrue() {
2026
            return true;
2027
        }
2028
        function returnFalse() {
2029
            return false;
2030
        }
2031
 
2032
        // Test if fixing up is needed
2033
        // Used to check if !event.stopPropagation instead of isPropagationStopped
2034
        // But native events return true for stopPropagation, but don't have
2035
        // other expected methods like isPropagationStopped. Seems to be a problem
2036
        // with the Javascript Ninja code. So we're just overriding all events now.
2037
        if (!event || !event.isPropagationStopped || !event.isImmediatePropagationStopped) {
2038
            const old = event || window.event;
2039
            event = {};
2040
            // Clone the old object so that we can modify the values event = {};
2041
            // IE8 Doesn't like when you mess with native event properties
2042
            // Firefox returns false for event.hasOwnProperty('type') and other props
2043
            //  which makes copying more difficult.
2044
            // TODO: Probably best to create a whitelist of event props
2045
            for (const key in old) {
2046
                // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
2047
                // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
2048
                // and webkitMovementX/Y
2049
                // Lighthouse complains if Event.path is copied
2050
                if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY' && key !== 'path') {
2051
                    // Chrome 32+ warns if you try to copy deprecated returnValue, but
2052
                    // we still want to if preventDefault isn't supported (IE8).
2053
                    if (!(key === 'returnValue' && old.preventDefault)) {
2054
                        event[key] = old[key];
2055
                    }
2056
                }
2057
            }
2058
 
2059
            // The event occurred on this element
2060
            if (!event.target) {
2061
                event.target = event.srcElement || document;
2062
            }
2063
 
2064
            // Handle which other element the event is related to
2065
            if (!event.relatedTarget) {
2066
                event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
2067
            }
2068
 
2069
            // Stop the default browser action
2070
            event.preventDefault = function () {
2071
                if (old.preventDefault) {
2072
                    old.preventDefault();
2073
                }
2074
                event.returnValue = false;
2075
                old.returnValue = false;
2076
                event.defaultPrevented = true;
2077
            };
2078
            event.defaultPrevented = false;
2079
 
2080
            // Stop the event from bubbling
2081
            event.stopPropagation = function () {
2082
                if (old.stopPropagation) {
2083
                    old.stopPropagation();
2084
                }
2085
                event.cancelBubble = true;
2086
                old.cancelBubble = true;
2087
                event.isPropagationStopped = returnTrue;
2088
            };
2089
            event.isPropagationStopped = returnFalse;
2090
 
2091
            // Stop the event from bubbling and executing other handlers
2092
            event.stopImmediatePropagation = function () {
2093
                if (old.stopImmediatePropagation) {
2094
                    old.stopImmediatePropagation();
2095
                }
2096
                event.isImmediatePropagationStopped = returnTrue;
2097
                event.stopPropagation();
2098
            };
2099
            event.isImmediatePropagationStopped = returnFalse;
2100
 
2101
            // Handle mouse position
2102
            if (event.clientX !== null && event.clientX !== undefined) {
2103
                const doc = document.documentElement;
2104
                const body = document.body;
2105
                event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
2106
                event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
2107
            }
2108
 
2109
            // Handle key presses
2110
            event.which = event.charCode || event.keyCode;
2111
 
2112
            // Fix button for mouse clicks:
2113
            // 0 == left; 1 == middle; 2 == right
2114
            if (event.button !== null && event.button !== undefined) {
2115
                // The following is disabled because it does not pass videojs-standard
2116
                // and... yikes.
2117
                /* eslint-disable */
2118
                event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
2119
                /* eslint-enable */
2120
            }
2121
        }
2122
 
2123
        event.fixed_ = true;
2124
        // Returns fixed-up instance
2125
        return event;
2126
    }
2127
 
2128
    /**
2129
     * Whether passive event listeners are supported
2130
     */
2131
    let _supportsPassive;
2132
    const supportsPassive = function () {
2133
        if (typeof _supportsPassive !== 'boolean') {
2134
            _supportsPassive = false;
2135
            try {
2136
                const opts = Object.defineProperty({}, 'passive', {
2137
                    get() {
2138
                        _supportsPassive = true;
2139
                    }
2140
                });
2141
                window.addEventListener('test', null, opts);
2142
                window.removeEventListener('test', null, opts);
2143
            } catch (e) {
2144
                // disregard
2145
            }
2146
        }
2147
        return _supportsPassive;
2148
    };
2149
 
2150
    /**
2151
     * Touch events Chrome expects to be passive
2152
     */
2153
    const passiveEvents = ['touchstart', 'touchmove'];
2154
 
2155
    /**
2156
     * Add an event listener to element
2157
     * It stores the handler function in a separate cache object
2158
     * and adds a generic handler to the element's event,
2159
     * along with a unique id (guid) to the element.
2160
     *
2161
     * @param {Element|Object} elem
2162
     *        Element or object to bind listeners to
2163
     *
2164
     * @param {string|string[]} type
2165
     *        Type of event to bind to.
2166
     *
2167
     * @param {Function} fn
2168
     *        Event listener.
2169
     */
2170
    function on(elem, type, fn) {
2171
        if (Array.isArray(type)) {
2172
            return _handleMultipleEvents(on, elem, type, fn);
2173
        }
2174
        if (!DomData.has(elem)) {
2175
            DomData.set(elem, {});
2176
        }
2177
        const data = DomData.get(elem);
2178
 
2179
        // We need a place to store all our handler data
2180
        if (!data.handlers) {
2181
            data.handlers = {};
2182
        }
2183
        if (!data.handlers[type]) {
2184
            data.handlers[type] = [];
2185
        }
2186
        if (!fn.guid) {
2187
            fn.guid = newGUID();
2188
        }
2189
        data.handlers[type].push(fn);
2190
        if (!data.dispatcher) {
2191
            data.disabled = false;
2192
            data.dispatcher = function (event, hash) {
2193
                if (data.disabled) {
2194
                    return;
2195
                }
2196
                event = fixEvent(event);
2197
                const handlers = data.handlers[event.type];
2198
                if (handlers) {
2199
                    // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
2200
                    const handlersCopy = handlers.slice(0);
2201
                    for (let m = 0, n = handlersCopy.length; m < n; m++) {
2202
                        if (event.isImmediatePropagationStopped()) {
2203
                            break;
2204
                        } else {
2205
                            try {
2206
                                handlersCopy[m].call(elem, event, hash);
2207
                            } catch (e) {
2208
                                log$1.error(e);
2209
                            }
2210
                        }
2211
                    }
2212
                }
2213
            };
2214
        }
2215
        if (data.handlers[type].length === 1) {
2216
            if (elem.addEventListener) {
2217
                let options = false;
2218
                if (supportsPassive() && passiveEvents.indexOf(type) > -1) {
2219
                    options = {
2220
                        passive: true
2221
                    };
2222
                }
2223
                elem.addEventListener(type, data.dispatcher, options);
2224
            } else if (elem.attachEvent) {
2225
                elem.attachEvent('on' + type, data.dispatcher);
2226
            }
2227
        }
2228
    }
2229
 
2230
    /**
2231
     * Removes event listeners from an element
2232
     *
2233
     * @param {Element|Object} elem
2234
     *        Object to remove listeners from.
2235
     *
2236
     * @param {string|string[]} [type]
2237
     *        Type of listener to remove. Don't include to remove all events from element.
2238
     *
2239
     * @param {Function} [fn]
2240
     *        Specific listener to remove. Don't include to remove listeners for an event
2241
     *        type.
2242
     */
2243
    function off(elem, type, fn) {
2244
        // Don't want to add a cache object through getElData if not needed
2245
        if (!DomData.has(elem)) {
2246
            return;
2247
        }
2248
        const data = DomData.get(elem);
2249
 
2250
        // If no events exist, nothing to unbind
2251
        if (!data.handlers) {
2252
            return;
2253
        }
2254
        if (Array.isArray(type)) {
2255
            return _handleMultipleEvents(off, elem, type, fn);
2256
        }
2257
 
2258
        // Utility function
2259
        const removeType = function (el, t) {
2260
            data.handlers[t] = [];
2261
            _cleanUpEvents(el, t);
2262
        };
2263
 
2264
        // Are we removing all bound events?
2265
        if (type === undefined) {
2266
            for (const t in data.handlers) {
2267
                if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
2268
                    removeType(elem, t);
2269
                }
2270
            }
2271
            return;
2272
        }
2273
        const handlers = data.handlers[type];
2274
 
2275
        // If no handlers exist, nothing to unbind
2276
        if (!handlers) {
2277
            return;
2278
        }
2279
 
2280
        // If no listener was provided, remove all listeners for type
2281
        if (!fn) {
2282
            removeType(elem, type);
2283
            return;
2284
        }
2285
 
2286
        // We're only removing a single handler
2287
        if (fn.guid) {
2288
            for (let n = 0; n < handlers.length; n++) {
2289
                if (handlers[n].guid === fn.guid) {
2290
                    handlers.splice(n--, 1);
2291
                }
2292
            }
2293
        }
2294
        _cleanUpEvents(elem, type);
2295
    }
2296
 
2297
    /**
2298
     * Trigger an event for an element
2299
     *
2300
     * @param {Element|Object} elem
2301
     *        Element to trigger an event on
2302
     *
2303
     * @param {EventTarget~Event|string} event
2304
     *        A string (the type) or an event object with a type attribute
2305
     *
2306
     * @param {Object} [hash]
2307
     *        data hash to pass along with the event
2308
     *
2309
     * @return {boolean|undefined}
2310
     *         Returns the opposite of `defaultPrevented` if default was
2311
     *         prevented. Otherwise, returns `undefined`
2312
     */
2313
    function trigger(elem, event, hash) {
2314
        // Fetches element data and a reference to the parent (for bubbling).
2315
        // Don't want to add a data object to cache for every parent,
2316
        // so checking hasElData first.
2317
        const elemData = DomData.has(elem) ? DomData.get(elem) : {};
2318
        const parent = elem.parentNode || elem.ownerDocument;
2319
        // type = event.type || event,
2320
        // handler;
2321
 
2322
        // If an event name was passed as a string, creates an event out of it
2323
        if (typeof event === 'string') {
2324
            event = {
2325
                type: event,
2326
                target: elem
2327
            };
2328
        } else if (!event.target) {
2329
            event.target = elem;
2330
        }
2331
 
2332
        // Normalizes the event properties.
2333
        event = fixEvent(event);
2334
 
2335
        // If the passed element has a dispatcher, executes the established handlers.
2336
        if (elemData.dispatcher) {
2337
            elemData.dispatcher.call(elem, event, hash);
2338
        }
2339
 
2340
        // Unless explicitly stopped or the event does not bubble (e.g. media events)
2341
        // recursively calls this function to bubble the event up the DOM.
2342
        if (parent && !event.isPropagationStopped() && event.bubbles === true) {
2343
            trigger.call(null, parent, event, hash);
2344
 
2345
            // If at the top of the DOM, triggers the default action unless disabled.
2346
        } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
2347
            if (!DomData.has(event.target)) {
2348
                DomData.set(event.target, {});
2349
            }
2350
            const targetData = DomData.get(event.target);
2351
 
2352
            // Checks if the target has a default action for this event.
2353
            if (event.target[event.type]) {
2354
                // Temporarily disables event dispatching on the target as we have already executed the handler.
2355
                targetData.disabled = true;
2356
                // Executes the default action.
2357
                if (typeof event.target[event.type] === 'function') {
2358
                    event.target[event.type]();
2359
                }
2360
                // Re-enables event dispatching.
2361
                targetData.disabled = false;
2362
            }
2363
        }
2364
 
2365
        // Inform the triggerer if the default was prevented by returning false
2366
        return !event.defaultPrevented;
2367
    }
2368
 
2369
    /**
2370
     * Trigger a listener only once for an event.
2371
     *
2372
     * @param {Element|Object} elem
2373
     *        Element or object to bind to.
2374
     *
2375
     * @param {string|string[]} type
2376
     *        Name/type of event
2377
     *
2378
     * @param {Event~EventListener} fn
2379
     *        Event listener function
2380
     */
2381
    function one(elem, type, fn) {
2382
        if (Array.isArray(type)) {
2383
            return _handleMultipleEvents(one, elem, type, fn);
2384
        }
2385
        const func = function () {
2386
            off(elem, type, func);
2387
            fn.apply(this, arguments);
2388
        };
2389
 
2390
        // copy the guid to the new function so it can removed using the original function's ID
2391
        func.guid = fn.guid = fn.guid || newGUID();
2392
        on(elem, type, func);
2393
    }
2394
 
2395
    /**
2396
     * Trigger a listener only once and then turn if off for all
2397
     * configured events
2398
     *
2399
     * @param {Element|Object} elem
2400
     *        Element or object to bind to.
2401
     *
2402
     * @param {string|string[]} type
2403
     *        Name/type of event
2404
     *
2405
     * @param {Event~EventListener} fn
2406
     *        Event listener function
2407
     */
2408
    function any(elem, type, fn) {
2409
        const func = function () {
2410
            off(elem, type, func);
2411
            fn.apply(this, arguments);
2412
        };
2413
 
2414
        // copy the guid to the new function so it can removed using the original function's ID
2415
        func.guid = fn.guid = fn.guid || newGUID();
2416
 
2417
        // multiple ons, but one off for everything
2418
        on(elem, type, func);
2419
    }
2420
 
2421
    var Events = /*#__PURE__*/Object.freeze({
2422
        __proto__: null,
2423
        fixEvent: fixEvent,
2424
        on: on,
2425
        off: off,
2426
        trigger: trigger,
2427
        one: one,
2428
        any: any
2429
    });
2430
 
2431
    /**
2432
     * @file fn.js
2433
     * @module fn
2434
     */
2435
    const UPDATE_REFRESH_INTERVAL = 30;
2436
 
2437
    /**
2438
     * A private, internal-only function for changing the context of a function.
2439
     *
2440
     * It also stores a unique id on the function so it can be easily removed from
2441
     * events.
2442
     *
2443
     * @private
2444
     * @function
2445
     * @param    {*} context
2446
     *           The object to bind as scope.
2447
     *
2448
     * @param    {Function} fn
2449
     *           The function to be bound to a scope.
2450
     *
2451
     * @param    {number} [uid]
2452
     *           An optional unique ID for the function to be set
2453
     *
2454
     * @return   {Function}
2455
     *           The new function that will be bound into the context given
2456
     */
2457
    const bind_ = function (context, fn, uid) {
2458
        // Make sure the function has a unique ID
2459
        if (!fn.guid) {
2460
            fn.guid = newGUID();
2461
        }
2462
 
2463
        // Create the new function that changes the context
2464
        const bound = fn.bind(context);
2465
 
2466
        // Allow for the ability to individualize this function
2467
        // Needed in the case where multiple objects might share the same prototype
2468
        // IF both items add an event listener with the same function, then you try to remove just one
2469
        // it will remove both because they both have the same guid.
2470
        // when using this, you need to use the bind method when you remove the listener as well.
2471
        // currently used in text tracks
2472
        bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
2473
        return bound;
2474
    };
2475
 
2476
    /**
2477
     * Wraps the given function, `fn`, with a new function that only invokes `fn`
2478
     * at most once per every `wait` milliseconds.
2479
     *
2480
     * @function
2481
     * @param    {Function} fn
2482
     *           The function to be throttled.
2483
     *
2484
     * @param    {number}   wait
2485
     *           The number of milliseconds by which to throttle.
2486
     *
2487
     * @return   {Function}
2488
     */
2489
    const throttle = function (fn, wait) {
2490
        let last = window.performance.now();
2491
        const throttled = function (...args) {
2492
            const now = window.performance.now();
2493
            if (now - last >= wait) {
2494
                fn(...args);
2495
                last = now;
2496
            }
2497
        };
2498
        return throttled;
2499
    };
2500
 
2501
    /**
2502
     * Creates a debounced function that delays invoking `func` until after `wait`
2503
     * milliseconds have elapsed since the last time the debounced function was
2504
     * invoked.
2505
     *
2506
     * Inspired by lodash and underscore implementations.
2507
     *
2508
     * @function
2509
     * @param    {Function} func
2510
     *           The function to wrap with debounce behavior.
2511
     *
2512
     * @param    {number} wait
2513
     *           The number of milliseconds to wait after the last invocation.
2514
     *
2515
     * @param    {boolean} [immediate]
2516
     *           Whether or not to invoke the function immediately upon creation.
2517
     *
2518
     * @param    {Object} [context=window]
2519
     *           The "context" in which the debounced function should debounce. For
2520
     *           example, if this function should be tied to a Video.js player,
2521
     *           the player can be passed here. Alternatively, defaults to the
2522
     *           global `window` object.
2523
     *
2524
     * @return   {Function}
2525
     *           A debounced function.
2526
     */
2527
    const debounce = function (func, wait, immediate, context = window) {
2528
        let timeout;
2529
        const cancel = () => {
2530
            context.clearTimeout(timeout);
2531
            timeout = null;
2532
        };
2533
 
2534
        /* eslint-disable consistent-this */
2535
        const debounced = function () {
2536
            const self = this;
2537
            const args = arguments;
2538
            let later = function () {
2539
                timeout = null;
2540
                later = null;
2541
                if (!immediate) {
2542
                    func.apply(self, args);
2543
                }
2544
            };
2545
            if (!timeout && immediate) {
2546
                func.apply(self, args);
2547
            }
2548
            context.clearTimeout(timeout);
2549
            timeout = context.setTimeout(later, wait);
2550
        };
2551
        /* eslint-enable consistent-this */
2552
 
2553
        debounced.cancel = cancel;
2554
        return debounced;
2555
    };
2556
 
2557
    var Fn = /*#__PURE__*/Object.freeze({
2558
        __proto__: null,
2559
        UPDATE_REFRESH_INTERVAL: UPDATE_REFRESH_INTERVAL,
2560
        bind_: bind_,
2561
        throttle: throttle,
2562
        debounce: debounce
2563
    });
2564
 
2565
    /**
2566
     * @file src/js/event-target.js
2567
     */
2568
    let EVENT_MAP;
2569
 
2570
    /**
2571
     * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
2572
     * adds shorthand functions that wrap around lengthy functions. For example:
2573
     * the `on` function is a wrapper around `addEventListener`.
2574
     *
2575
     * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
2576
     * @class EventTarget
2577
     */
2578
    class EventTarget$2 {
2579
        /**
2580
         * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
2581
         * function that will get called when an event with a certain name gets triggered.
2582
         *
2583
         * @param {string|string[]} type
2584
         *        An event name or an array of event names.
2585
         *
2586
         * @param {Function} fn
2587
         *        The function to call with `EventTarget`s
2588
         */
2589
        on(type, fn) {
2590
            // Remove the addEventListener alias before calling Events.on
2591
            // so we don't get into an infinite type loop
2592
            const ael = this.addEventListener;
2593
            this.addEventListener = () => {};
2594
            on(this, type, fn);
2595
            this.addEventListener = ael;
2596
        }
2597
        /**
2598
         * Removes an `event listener` for a specific event from an instance of `EventTarget`.
2599
         * This makes it so that the `event listener` will no longer get called when the
2600
         * named event happens.
2601
         *
2602
         * @param {string|string[]} type
2603
         *        An event name or an array of event names.
2604
         *
2605
         * @param {Function} fn
2606
         *        The function to remove.
2607
         */
2608
        off(type, fn) {
2609
            off(this, type, fn);
2610
        }
2611
        /**
2612
         * This function will add an `event listener` that gets triggered only once. After the
2613
         * first trigger it will get removed. This is like adding an `event listener`
2614
         * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
2615
         *
2616
         * @param {string|string[]} type
2617
         *        An event name or an array of event names.
2618
         *
2619
         * @param {Function} fn
2620
         *        The function to be called once for each event name.
2621
         */
2622
        one(type, fn) {
2623
            // Remove the addEventListener aliasing Events.on
2624
            // so we don't get into an infinite type loop
2625
            const ael = this.addEventListener;
2626
            this.addEventListener = () => {};
2627
            one(this, type, fn);
2628
            this.addEventListener = ael;
2629
        }
2630
        /**
2631
         * This function will add an `event listener` that gets triggered only once and is
2632
         * removed from all events. This is like adding an array of `event listener`s
2633
         * with {@link EventTarget#on} that calls {@link EventTarget#off} on all events the
2634
         * first time it is triggered.
2635
         *
2636
         * @param {string|string[]} type
2637
         *        An event name or an array of event names.
2638
         *
2639
         * @param {Function} fn
2640
         *        The function to be called once for each event name.
2641
         */
2642
        any(type, fn) {
2643
            // Remove the addEventListener aliasing Events.on
2644
            // so we don't get into an infinite type loop
2645
            const ael = this.addEventListener;
2646
            this.addEventListener = () => {};
2647
            any(this, type, fn);
2648
            this.addEventListener = ael;
2649
        }
2650
        /**
2651
         * This function causes an event to happen. This will then cause any `event listeners`
2652
         * that are waiting for that event, to get called. If there are no `event listeners`
2653
         * for an event then nothing will happen.
2654
         *
2655
         * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
2656
         * Trigger will also call the `on` + `uppercaseEventName` function.
2657
         *
2658
         * Example:
2659
         * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
2660
         * `onClick` if it exists.
2661
         *
2662
         * @param {string|EventTarget~Event|Object} event
2663
         *        The name of the event, an `Event`, or an object with a key of type set to
2664
         *        an event name.
2665
         */
2666
        trigger(event) {
2667
            const type = event.type || event;
2668
 
2669
            // deprecation
2670
            // In a future version we should default target to `this`
2671
            // similar to how we default the target to `elem` in
2672
            // `Events.trigger`. Right now the default `target` will be
2673
            // `document` due to the `Event.fixEvent` call.
2674
            if (typeof event === 'string') {
2675
                event = {
2676
                    type
2677
                };
2678
            }
2679
            event = fixEvent(event);
2680
            if (this.allowedEvents_[type] && this['on' + type]) {
2681
                this['on' + type](event);
2682
            }
2683
            trigger(this, event);
2684
        }
2685
        queueTrigger(event) {
2686
            // only set up EVENT_MAP if it'll be used
2687
            if (!EVENT_MAP) {
2688
                EVENT_MAP = new Map();
2689
            }
2690
            const type = event.type || event;
2691
            let map = EVENT_MAP.get(this);
2692
            if (!map) {
2693
                map = new Map();
2694
                EVENT_MAP.set(this, map);
2695
            }
2696
            const oldTimeout = map.get(type);
2697
            map.delete(type);
2698
            window.clearTimeout(oldTimeout);
2699
            const timeout = window.setTimeout(() => {
2700
                map.delete(type);
2701
                // if we cleared out all timeouts for the current target, delete its map
2702
                if (map.size === 0) {
2703
                    map = null;
2704
                    EVENT_MAP.delete(this);
2705
                }
2706
                this.trigger(event);
2707
            }, 0);
2708
            map.set(type, timeout);
2709
        }
2710
    }
2711
 
2712
    /**
2713
     * A Custom DOM event.
2714
     *
2715
     * @typedef {CustomEvent} Event
2716
     * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
2717
     */
2718
 
2719
    /**
2720
     * All event listeners should follow the following format.
2721
     *
2722
     * @callback EventListener
2723
     * @this {EventTarget}
2724
     *
2725
     * @param {Event} event
2726
     *        the event that triggered this function
2727
     *
2728
     * @param {Object} [hash]
2729
     *        hash of data sent during the event
2730
     */
2731
 
2732
    /**
2733
     * An object containing event names as keys and booleans as values.
2734
     *
2735
     * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
2736
     *         will have extra functionality. See that function for more information.
2737
     *
2738
     * @property EventTarget.prototype.allowedEvents_
2739
     * @protected
2740
     */
2741
    EventTarget$2.prototype.allowedEvents_ = {};
2742
 
2743
    /**
2744
     * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
2745
     * the standard DOM API.
2746
     *
2747
     * @function
2748
     * @see {@link EventTarget#on}
2749
     */
2750
    EventTarget$2.prototype.addEventListener = EventTarget$2.prototype.on;
2751
 
2752
    /**
2753
     * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
2754
     * the standard DOM API.
2755
     *
2756
     * @function
2757
     * @see {@link EventTarget#off}
2758
     */
2759
    EventTarget$2.prototype.removeEventListener = EventTarget$2.prototype.off;
2760
 
2761
    /**
2762
     * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
2763
     * the standard DOM API.
2764
     *
2765
     * @function
2766
     * @see {@link EventTarget#trigger}
2767
     */
2768
    EventTarget$2.prototype.dispatchEvent = EventTarget$2.prototype.trigger;
2769
 
2770
    /**
2771
     * @file mixins/evented.js
2772
     * @module evented
2773
     */
2774
    const objName = obj => {
2775
        if (typeof obj.name === 'function') {
2776
            return obj.name();
2777
        }
2778
        if (typeof obj.name === 'string') {
2779
            return obj.name;
2780
        }
2781
        if (obj.name_) {
2782
            return obj.name_;
2783
        }
2784
        if (obj.constructor && obj.constructor.name) {
2785
            return obj.constructor.name;
2786
        }
2787
        return typeof obj;
2788
    };
2789
 
2790
    /**
2791
     * Returns whether or not an object has had the evented mixin applied.
2792
     *
2793
     * @param  {Object} object
2794
     *         An object to test.
2795
     *
2796
     * @return {boolean}
2797
     *         Whether or not the object appears to be evented.
2798
     */
2799
    const isEvented = object => object instanceof EventTarget$2 || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(k => typeof object[k] === 'function');
2800
 
2801
    /**
2802
     * Adds a callback to run after the evented mixin applied.
2803
     *
2804
     * @param  {Object} target
2805
     *         An object to Add
2806
     * @param  {Function} callback
2807
     *         The callback to run.
2808
     */
2809
    const addEventedCallback = (target, callback) => {
2810
        if (isEvented(target)) {
2811
            callback();
2812
        } else {
2813
            if (!target.eventedCallbacks) {
2814
                target.eventedCallbacks = [];
2815
            }
2816
            target.eventedCallbacks.push(callback);
2817
        }
2818
    };
2819
 
2820
    /**
2821
     * Whether a value is a valid event type - non-empty string or array.
2822
     *
2823
     * @private
2824
     * @param  {string|Array} type
2825
     *         The type value to test.
2826
     *
2827
     * @return {boolean}
2828
     *         Whether or not the type is a valid event type.
2829
     */
2830
    const isValidEventType = type =>
2831
        // The regex here verifies that the `type` contains at least one non-
2832
        // whitespace character.
2833
        typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length;
2834
 
2835
    /**
2836
     * Validates a value to determine if it is a valid event target. Throws if not.
2837
     *
2838
     * @private
2839
     * @throws {Error}
2840
     *         If the target does not appear to be a valid event target.
2841
     *
2842
     * @param  {Object} target
2843
     *         The object to test.
2844
     *
2845
     * @param  {Object} obj
2846
     *         The evented object we are validating for
2847
     *
2848
     * @param  {string} fnName
2849
     *         The name of the evented mixin function that called this.
2850
     */
2851
    const validateTarget = (target, obj, fnName) => {
2852
        if (!target || !target.nodeName && !isEvented(target)) {
2853
            throw new Error(`Invalid target for ${objName(obj)}#${fnName}; must be a DOM node or evented object.`);
2854
        }
2855
    };
2856
 
2857
    /**
2858
     * Validates a value to determine if it is a valid event target. Throws if not.
2859
     *
2860
     * @private
2861
     * @throws {Error}
2862
     *         If the type does not appear to be a valid event type.
2863
     *
2864
     * @param  {string|Array} type
2865
     *         The type to test.
2866
     *
2867
     * @param  {Object} obj
2868
     *         The evented object we are validating for
2869
     *
2870
     * @param  {string} fnName
2871
     *         The name of the evented mixin function that called this.
2872
     */
2873
    const validateEventType = (type, obj, fnName) => {
2874
        if (!isValidEventType(type)) {
2875
            throw new Error(`Invalid event type for ${objName(obj)}#${fnName}; must be a non-empty string or array.`);
2876
        }
2877
    };
2878
 
2879
    /**
2880
     * Validates a value to determine if it is a valid listener. Throws if not.
2881
     *
2882
     * @private
2883
     * @throws {Error}
2884
     *         If the listener is not a function.
2885
     *
2886
     * @param  {Function} listener
2887
     *         The listener to test.
2888
     *
2889
     * @param  {Object} obj
2890
     *         The evented object we are validating for
2891
     *
2892
     * @param  {string} fnName
2893
     *         The name of the evented mixin function that called this.
2894
     */
2895
    const validateListener = (listener, obj, fnName) => {
2896
        if (typeof listener !== 'function') {
2897
            throw new Error(`Invalid listener for ${objName(obj)}#${fnName}; must be a function.`);
2898
        }
2899
    };
2900
 
2901
    /**
2902
     * Takes an array of arguments given to `on()` or `one()`, validates them, and
2903
     * normalizes them into an object.
2904
     *
2905
     * @private
2906
     * @param  {Object} self
2907
     *         The evented object on which `on()` or `one()` was called. This
2908
     *         object will be bound as the `this` value for the listener.
2909
     *
2910
     * @param  {Array} args
2911
     *         An array of arguments passed to `on()` or `one()`.
2912
     *
2913
     * @param  {string} fnName
2914
     *         The name of the evented mixin function that called this.
2915
     *
2916
     * @return {Object}
2917
     *         An object containing useful values for `on()` or `one()` calls.
2918
     */
2919
    const normalizeListenArgs = (self, args, fnName) => {
2920
        // If the number of arguments is less than 3, the target is always the
2921
        // evented object itself.
2922
        const isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
2923
        let target;
2924
        let type;
2925
        let listener;
2926
        if (isTargetingSelf) {
2927
            target = self.eventBusEl_;
2928
 
2929
            // Deal with cases where we got 3 arguments, but we are still listening to
2930
            // the evented object itself.
2931
            if (args.length >= 3) {
2932
                args.shift();
2933
            }
2934
            [type, listener] = args;
2935
        } else {
2936
            [target, type, listener] = args;
2937
        }
2938
        validateTarget(target, self, fnName);
2939
        validateEventType(type, self, fnName);
2940
        validateListener(listener, self, fnName);
2941
        listener = bind_(self, listener);
2942
        return {
2943
            isTargetingSelf,
2944
            target,
2945
            type,
2946
            listener
2947
        };
2948
    };
2949
 
2950
    /**
2951
     * Adds the listener to the event type(s) on the target, normalizing for
2952
     * the type of target.
2953
     *
2954
     * @private
2955
     * @param  {Element|Object} target
2956
     *         A DOM node or evented object.
2957
     *
2958
     * @param  {string} method
2959
     *         The event binding method to use ("on" or "one").
2960
     *
2961
     * @param  {string|Array} type
2962
     *         One or more event type(s).
2963
     *
2964
     * @param  {Function} listener
2965
     *         A listener function.
2966
     */
2967
    const listen = (target, method, type, listener) => {
2968
        validateTarget(target, target, method);
2969
        if (target.nodeName) {
2970
            Events[method](target, type, listener);
2971
        } else {
2972
            target[method](type, listener);
2973
        }
2974
    };
2975
 
2976
    /**
2977
     * Contains methods that provide event capabilities to an object which is passed
2978
     * to {@link module:evented|evented}.
2979
     *
2980
     * @mixin EventedMixin
2981
     */
2982
    const EventedMixin = {
2983
        /**
2984
         * Add a listener to an event (or events) on this object or another evented
2985
         * object.
2986
         *
2987
         * @param  {string|Array|Element|Object} targetOrType
2988
         *         If this is a string or array, it represents the event type(s)
2989
         *         that will trigger the listener.
2990
         *
2991
         *         Another evented object can be passed here instead, which will
2992
         *         cause the listener to listen for events on _that_ object.
2993
         *
2994
         *         In either case, the listener's `this` value will be bound to
2995
         *         this object.
2996
         *
2997
         * @param  {string|Array|Function} typeOrListener
2998
         *         If the first argument was a string or array, this should be the
2999
         *         listener function. Otherwise, this is a string or array of event
3000
         *         type(s).
3001
         *
3002
         * @param  {Function} [listener]
3003
         *         If the first argument was another evented object, this will be
3004
         *         the listener function.
3005
         */
3006
        on(...args) {
3007
            const {
3008
                isTargetingSelf,
3009
                target,
3010
                type,
3011
                listener
3012
            } = normalizeListenArgs(this, args, 'on');
3013
            listen(target, 'on', type, listener);
3014
 
3015
            // If this object is listening to another evented object.
3016
            if (!isTargetingSelf) {
3017
                // If this object is disposed, remove the listener.
3018
                const removeListenerOnDispose = () => this.off(target, type, listener);
3019
 
3020
                // Use the same function ID as the listener so we can remove it later it
3021
                // using the ID of the original listener.
3022
                removeListenerOnDispose.guid = listener.guid;
3023
 
3024
                // Add a listener to the target's dispose event as well. This ensures
3025
                // that if the target is disposed BEFORE this object, we remove the
3026
                // removal listener that was just added. Otherwise, we create a memory leak.
3027
                const removeRemoverOnTargetDispose = () => this.off('dispose', removeListenerOnDispose);
3028
 
3029
                // Use the same function ID as the listener so we can remove it later
3030
                // it using the ID of the original listener.
3031
                removeRemoverOnTargetDispose.guid = listener.guid;
3032
                listen(this, 'on', 'dispose', removeListenerOnDispose);
3033
                listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
3034
            }
3035
        },
3036
        /**
3037
         * Add a listener to an event (or events) on this object or another evented
3038
         * object. The listener will be called once per event and then removed.
3039
         *
3040
         * @param  {string|Array|Element|Object} targetOrType
3041
         *         If this is a string or array, it represents the event type(s)
3042
         *         that will trigger the listener.
3043
         *
3044
         *         Another evented object can be passed here instead, which will
3045
         *         cause the listener to listen for events on _that_ object.
3046
         *
3047
         *         In either case, the listener's `this` value will be bound to
3048
         *         this object.
3049
         *
3050
         * @param  {string|Array|Function} typeOrListener
3051
         *         If the first argument was a string or array, this should be the
3052
         *         listener function. Otherwise, this is a string or array of event
3053
         *         type(s).
3054
         *
3055
         * @param  {Function} [listener]
3056
         *         If the first argument was another evented object, this will be
3057
         *         the listener function.
3058
         */
3059
        one(...args) {
3060
            const {
3061
                isTargetingSelf,
3062
                target,
3063
                type,
3064
                listener
3065
            } = normalizeListenArgs(this, args, 'one');
3066
 
3067
            // Targeting this evented object.
3068
            if (isTargetingSelf) {
3069
                listen(target, 'one', type, listener);
3070
 
3071
                // Targeting another evented object.
3072
            } else {
3073
                // TODO: This wrapper is incorrect! It should only
3074
                //       remove the wrapper for the event type that called it.
3075
                //       Instead all listeners are removed on the first trigger!
3076
                //       see https://github.com/videojs/video.js/issues/5962
3077
                const wrapper = (...largs) => {
3078
                    this.off(target, type, wrapper);
3079
                    listener.apply(null, largs);
3080
                };
3081
 
3082
                // Use the same function ID as the listener so we can remove it later
3083
                // it using the ID of the original listener.
3084
                wrapper.guid = listener.guid;
3085
                listen(target, 'one', type, wrapper);
3086
            }
3087
        },
3088
        /**
3089
         * Add a listener to an event (or events) on this object or another evented
3090
         * object. The listener will only be called once for the first event that is triggered
3091
         * then removed.
3092
         *
3093
         * @param  {string|Array|Element|Object} targetOrType
3094
         *         If this is a string or array, it represents the event type(s)
3095
         *         that will trigger the listener.
3096
         *
3097
         *         Another evented object can be passed here instead, which will
3098
         *         cause the listener to listen for events on _that_ object.
3099
         *
3100
         *         In either case, the listener's `this` value will be bound to
3101
         *         this object.
3102
         *
3103
         * @param  {string|Array|Function} typeOrListener
3104
         *         If the first argument was a string or array, this should be the
3105
         *         listener function. Otherwise, this is a string or array of event
3106
         *         type(s).
3107
         *
3108
         * @param  {Function} [listener]
3109
         *         If the first argument was another evented object, this will be
3110
         *         the listener function.
3111
         */
3112
        any(...args) {
3113
            const {
3114
                isTargetingSelf,
3115
                target,
3116
                type,
3117
                listener
3118
            } = normalizeListenArgs(this, args, 'any');
3119
 
3120
            // Targeting this evented object.
3121
            if (isTargetingSelf) {
3122
                listen(target, 'any', type, listener);
3123
 
3124
                // Targeting another evented object.
3125
            } else {
3126
                const wrapper = (...largs) => {
3127
                    this.off(target, type, wrapper);
3128
                    listener.apply(null, largs);
3129
                };
3130
 
3131
                // Use the same function ID as the listener so we can remove it later
3132
                // it using the ID of the original listener.
3133
                wrapper.guid = listener.guid;
3134
                listen(target, 'any', type, wrapper);
3135
            }
3136
        },
3137
        /**
3138
         * Removes listener(s) from event(s) on an evented object.
3139
         *
3140
         * @param  {string|Array|Element|Object} [targetOrType]
3141
         *         If this is a string or array, it represents the event type(s).
3142
         *
3143
         *         Another evented object can be passed here instead, in which case
3144
         *         ALL 3 arguments are _required_.
3145
         *
3146
         * @param  {string|Array|Function} [typeOrListener]
3147
         *         If the first argument was a string or array, this may be the
3148
         *         listener function. Otherwise, this is a string or array of event
3149
         *         type(s).
3150
         *
3151
         * @param  {Function} [listener]
3152
         *         If the first argument was another evented object, this will be
3153
         *         the listener function; otherwise, _all_ listeners bound to the
3154
         *         event type(s) will be removed.
3155
         */
3156
        off(targetOrType, typeOrListener, listener) {
3157
            // Targeting this evented object.
3158
            if (!targetOrType || isValidEventType(targetOrType)) {
3159
                off(this.eventBusEl_, targetOrType, typeOrListener);
3160
 
3161
                // Targeting another evented object.
3162
            } else {
3163
                const target = targetOrType;
3164
                const type = typeOrListener;
3165
 
3166
                // Fail fast and in a meaningful way!
3167
                validateTarget(target, this, 'off');
3168
                validateEventType(type, this, 'off');
3169
                validateListener(listener, this, 'off');
3170
 
3171
                // Ensure there's at least a guid, even if the function hasn't been used
3172
                listener = bind_(this, listener);
3173
 
3174
                // Remove the dispose listener on this evented object, which was given
3175
                // the same guid as the event listener in on().
3176
                this.off('dispose', listener);
3177
                if (target.nodeName) {
3178
                    off(target, type, listener);
3179
                    off(target, 'dispose', listener);
3180
                } else if (isEvented(target)) {
3181
                    target.off(type, listener);
3182
                    target.off('dispose', listener);
3183
                }
3184
            }
3185
        },
3186
        /**
3187
         * Fire an event on this evented object, causing its listeners to be called.
3188
         *
3189
         * @param   {string|Object} event
3190
         *          An event type or an object with a type property.
3191
         *
3192
         * @param   {Object} [hash]
3193
         *          An additional object to pass along to listeners.
3194
         *
3195
         * @return {boolean}
3196
         *          Whether or not the default behavior was prevented.
3197
         */
3198
        trigger(event, hash) {
3199
            validateTarget(this.eventBusEl_, this, 'trigger');
3200
            const type = event && typeof event !== 'string' ? event.type : event;
3201
            if (!isValidEventType(type)) {
3202
                throw new Error(`Invalid event type for ${objName(this)}#trigger; ` + 'must be a non-empty string or object with a type key that has a non-empty value.');
3203
            }
3204
            return trigger(this.eventBusEl_, event, hash);
3205
        }
3206
    };
3207
 
3208
    /**
3209
     * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
3210
     *
3211
     * @param  {Object} target
3212
     *         The object to which to add event methods.
3213
     *
3214
     * @param  {Object} [options={}]
3215
     *         Options for customizing the mixin behavior.
3216
     *
3217
     * @param  {string} [options.eventBusKey]
3218
     *         By default, adds a `eventBusEl_` DOM element to the target object,
3219
     *         which is used as an event bus. If the target object already has a
3220
     *         DOM element that should be used, pass its key here.
3221
     *
3222
     * @return {Object}
3223
     *         The target object.
3224
     */
3225
    function evented(target, options = {}) {
3226
        const {
3227
            eventBusKey
3228
        } = options;
3229
 
3230
        // Set or create the eventBusEl_.
3231
        if (eventBusKey) {
3232
            if (!target[eventBusKey].nodeName) {
3233
                throw new Error(`The eventBusKey "${eventBusKey}" does not refer to an element.`);
3234
            }
3235
            target.eventBusEl_ = target[eventBusKey];
3236
        } else {
3237
            target.eventBusEl_ = createEl('span', {
3238
                className: 'vjs-event-bus'
3239
            });
3240
        }
3241
        Object.assign(target, EventedMixin);
3242
        if (target.eventedCallbacks) {
3243
            target.eventedCallbacks.forEach(callback => {
3244
                callback();
3245
            });
3246
        }
3247
 
3248
        // When any evented object is disposed, it removes all its listeners.
3249
        target.on('dispose', () => {
3250
            target.off();
3251
            [target, target.el_, target.eventBusEl_].forEach(function (val) {
3252
                if (val && DomData.has(val)) {
3253
                    DomData.delete(val);
3254
                }
3255
            });
3256
            window.setTimeout(() => {
3257
                target.eventBusEl_ = null;
3258
            }, 0);
3259
        });
3260
        return target;
3261
    }
3262
 
3263
    /**
3264
     * @file mixins/stateful.js
3265
     * @module stateful
3266
     */
3267
 
3268
    /**
3269
     * Contains methods that provide statefulness to an object which is passed
3270
     * to {@link module:stateful}.
3271
     *
3272
     * @mixin StatefulMixin
3273
     */
3274
    const StatefulMixin = {
3275
        /**
3276
         * A hash containing arbitrary keys and values representing the state of
3277
         * the object.
3278
         *
3279
         * @type {Object}
3280
         */
3281
        state: {},
3282
        /**
3283
         * Set the state of an object by mutating its
3284
         * {@link module:stateful~StatefulMixin.state|state} object in place.
3285
         *
3286
         * @fires   module:stateful~StatefulMixin#statechanged
3287
         * @param   {Object|Function} stateUpdates
3288
         *          A new set of properties to shallow-merge into the plugin state.
3289
         *          Can be a plain object or a function returning a plain object.
3290
         *
3291
         * @return {Object|undefined}
3292
         *          An object containing changes that occurred. If no changes
3293
         *          occurred, returns `undefined`.
3294
         */
3295
        setState(stateUpdates) {
3296
            // Support providing the `stateUpdates` state as a function.
3297
            if (typeof stateUpdates === 'function') {
3298
                stateUpdates = stateUpdates();
3299
            }
3300
            let changes;
3301
            each(stateUpdates, (value, key) => {
3302
                // Record the change if the value is different from what's in the
3303
                // current state.
3304
                if (this.state[key] !== value) {
3305
                    changes = changes || {};
3306
                    changes[key] = {
3307
                        from: this.state[key],
3308
                        to: value
3309
                    };
3310
                }
3311
                this.state[key] = value;
3312
            });
3313
 
3314
            // Only trigger "statechange" if there were changes AND we have a trigger
3315
            // function. This allows us to not require that the target object be an
3316
            // evented object.
3317
            if (changes && isEvented(this)) {
3318
                /**
3319
                 * An event triggered on an object that is both
3320
                 * {@link module:stateful|stateful} and {@link module:evented|evented}
3321
                 * indicating that its state has changed.
3322
                 *
3323
                 * @event    module:stateful~StatefulMixin#statechanged
3324
                 * @type     {Object}
3325
                 * @property {Object} changes
3326
                 *           A hash containing the properties that were changed and
3327
                 *           the values they were changed `from` and `to`.
3328
                 */
3329
                this.trigger({
3330
                    changes,
3331
                    type: 'statechanged'
3332
                });
3333
            }
3334
            return changes;
3335
        }
3336
    };
3337
 
3338
    /**
3339
     * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
3340
     * object.
3341
     *
3342
     * If the target object is {@link module:evented|evented} and has a
3343
     * `handleStateChanged` method, that method will be automatically bound to the
3344
     * `statechanged` event on itself.
3345
     *
3346
     * @param   {Object} target
3347
     *          The object to be made stateful.
3348
     *
3349
     * @param   {Object} [defaultState]
3350
     *          A default set of properties to populate the newly-stateful object's
3351
     *          `state` property.
3352
     *
3353
     * @return {Object}
3354
     *          Returns the `target`.
3355
     */
3356
    function stateful(target, defaultState) {
3357
        Object.assign(target, StatefulMixin);
3358
 
3359
        // This happens after the mixing-in because we need to replace the `state`
3360
        // added in that step.
3361
        target.state = Object.assign({}, target.state, defaultState);
3362
 
3363
        // Auto-bind the `handleStateChanged` method of the target object if it exists.
3364
        if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
3365
            target.on('statechanged', target.handleStateChanged);
3366
        }
3367
        return target;
3368
    }
3369
 
3370
    /**
3371
     * @file str.js
3372
     * @module to-lower-case
3373
     */
3374
 
3375
    /**
3376
     * Lowercase the first letter of a string.
3377
     *
3378
     * @param {string} string
3379
     *        String to be lowercased
3380
     *
3381
     * @return {string}
3382
     *         The string with a lowercased first letter
3383
     */
3384
    const toLowerCase = function (string) {
3385
        if (typeof string !== 'string') {
3386
            return string;
3387
        }
3388
        return string.replace(/./, w => w.toLowerCase());
3389
    };
3390
 
3391
    /**
3392
     * Uppercase the first letter of a string.
3393
     *
3394
     * @param {string} string
3395
     *        String to be uppercased
3396
     *
3397
     * @return {string}
3398
     *         The string with an uppercased first letter
3399
     */
3400
    const toTitleCase$1 = function (string) {
3401
        if (typeof string !== 'string') {
3402
            return string;
3403
        }
3404
        return string.replace(/./, w => w.toUpperCase());
3405
    };
3406
 
3407
    /**
3408
     * Compares the TitleCase versions of the two strings for equality.
3409
     *
3410
     * @param {string} str1
3411
     *        The first string to compare
3412
     *
3413
     * @param {string} str2
3414
     *        The second string to compare
3415
     *
3416
     * @return {boolean}
3417
     *         Whether the TitleCase versions of the strings are equal
3418
     */
3419
    const titleCaseEquals = function (str1, str2) {
3420
        return toTitleCase$1(str1) === toTitleCase$1(str2);
3421
    };
3422
 
3423
    var Str = /*#__PURE__*/Object.freeze({
3424
        __proto__: null,
3425
        toLowerCase: toLowerCase,
3426
        toTitleCase: toTitleCase$1,
3427
        titleCaseEquals: titleCaseEquals
3428
    });
3429
 
3430
    var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
3431
 
3432
    function unwrapExports (x) {
3433
        return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
3434
    }
3435
 
3436
    function createCommonjsModule(fn, module) {
3437
        return module = { exports: {} }, fn(module, module.exports), module.exports;
3438
    }
3439
 
3440
    var keycode = createCommonjsModule(function (module, exports) {
3441
        // Source: http://jsfiddle.net/vWx8V/
3442
        // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes
3443
 
3444
        /**
3445
         * Conenience method returns corresponding value for given keyName or keyCode.
3446
         *
3447
         * @param {Mixed} keyCode {Number} or keyName {String}
3448
         * @return {Mixed}
3449
         * @api public
3450
         */
3451
 
3452
        function keyCode(searchInput) {
3453
            // Keyboard Events
3454
            if (searchInput && 'object' === typeof searchInput) {
3455
                var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode;
3456
                if (hasKeyCode) searchInput = hasKeyCode;
3457
            }
3458
 
3459
            // Numbers
3460
            if ('number' === typeof searchInput) return names[searchInput];
3461
 
3462
            // Everything else (cast to string)
3463
            var search = String(searchInput);
3464
 
3465
            // check codes
3466
            var foundNamedKey = codes[search.toLowerCase()];
3467
            if (foundNamedKey) return foundNamedKey;
3468
 
3469
            // check aliases
3470
            var foundNamedKey = aliases[search.toLowerCase()];
3471
            if (foundNamedKey) return foundNamedKey;
3472
 
3473
            // weird character?
3474
            if (search.length === 1) return search.charCodeAt(0);
3475
            return undefined;
3476
        }
3477
 
3478
        /**
3479
         * Compares a keyboard event with a given keyCode or keyName.
3480
         *
3481
         * @param {Event} event Keyboard event that should be tested
3482
         * @param {Mixed} keyCode {Number} or keyName {String}
3483
         * @return {Boolean}
3484
         * @api public
3485
         */
3486
        keyCode.isEventKey = function isEventKey(event, nameOrCode) {
3487
            if (event && 'object' === typeof event) {
3488
                var keyCode = event.which || event.keyCode || event.charCode;
3489
                if (keyCode === null || keyCode === undefined) {
3490
                    return false;
3491
                }
3492
                if (typeof nameOrCode === 'string') {
3493
                    // check codes
3494
                    var foundNamedKey = codes[nameOrCode.toLowerCase()];
3495
                    if (foundNamedKey) {
3496
                        return foundNamedKey === keyCode;
3497
                    }
3498
 
3499
                    // check aliases
3500
                    var foundNamedKey = aliases[nameOrCode.toLowerCase()];
3501
                    if (foundNamedKey) {
3502
                        return foundNamedKey === keyCode;
3503
                    }
3504
                } else if (typeof nameOrCode === 'number') {
3505
                    return nameOrCode === keyCode;
3506
                }
3507
                return false;
3508
            }
3509
        };
3510
        exports = module.exports = keyCode;
3511
 
3512
        /**
3513
         * Get by name
3514
         *
3515
         *   exports.code['enter'] // => 13
3516
         */
3517
 
3518
        var codes = exports.code = exports.codes = {
3519
            'backspace': 8,
3520
            'tab': 9,
3521
            'enter': 13,
3522
            'shift': 16,
3523
            'ctrl': 17,
3524
            'alt': 18,
3525
            'pause/break': 19,
3526
            'caps lock': 20,
3527
            'esc': 27,
3528
            'space': 32,
3529
            'page up': 33,
3530
            'page down': 34,
3531
            'end': 35,
3532
            'home': 36,
3533
            'left': 37,
3534
            'up': 38,
3535
            'right': 39,
3536
            'down': 40,
3537
            'insert': 45,
3538
            'delete': 46,
3539
            'command': 91,
3540
            'left command': 91,
3541
            'right command': 93,
3542
            'numpad *': 106,
3543
            'numpad +': 107,
3544
            'numpad -': 109,
3545
            'numpad .': 110,
3546
            'numpad /': 111,
3547
            'num lock': 144,
3548
            'scroll lock': 145,
3549
            'my computer': 182,
3550
            'my calculator': 183,
3551
            ';': 186,
3552
            '=': 187,
3553
            ',': 188,
3554
            '-': 189,
3555
            '.': 190,
3556
            '/': 191,
3557
            '`': 192,
3558
            '[': 219,
3559
            '\\': 220,
3560
            ']': 221,
3561
            "'": 222
3562
        };
3563
 
3564
        // Helper aliases
3565
 
3566
        var aliases = exports.aliases = {
3567
            'windows': 91,
3568
            '⇧': 16,
3569
            '⌥': 18,
3570
            '⌃': 17,
3571
            '⌘': 91,
3572
            'ctl': 17,
3573
            'control': 17,
3574
            'option': 18,
3575
            'pause': 19,
3576
            'break': 19,
3577
            'caps': 20,
3578
            'return': 13,
3579
            'escape': 27,
3580
            'spc': 32,
3581
            'spacebar': 32,
3582
            'pgup': 33,
3583
            'pgdn': 34,
3584
            'ins': 45,
3585
            'del': 46,
3586
            'cmd': 91
3587
        };
3588
 
3589
        /*!
3590
     * Programatically add the following
3591
     */
3592
 
3593
        // lower case chars
3594
        for (i = 97; i < 123; i++) codes[String.fromCharCode(i)] = i - 32;
3595
 
3596
        // numbers
3597
        for (var i = 48; i < 58; i++) codes[i - 48] = i;
3598
 
3599
        // function keys
3600
        for (i = 1; i < 13; i++) codes['f' + i] = i + 111;
3601
 
3602
        // numpad keys
3603
        for (i = 0; i < 10; i++) codes['numpad ' + i] = i + 96;
3604
 
3605
        /**
3606
         * Get by code
3607
         *
3608
         *   exports.name[13] // => 'Enter'
3609
         */
3610
 
3611
        var names = exports.names = exports.title = {}; // title for backward compat
3612
 
3613
        // Create reverse mapping
3614
        for (i in codes) names[codes[i]] = i;
3615
 
3616
        // Add aliases
3617
        for (var alias in aliases) {
3618
            codes[alias] = aliases[alias];
3619
        }
3620
    });
3621
    keycode.code;
3622
    keycode.codes;
3623
    keycode.aliases;
3624
    keycode.names;
3625
    keycode.title;
3626
 
3627
    /**
3628
     * Player Component - Base class for all UI objects
3629
     *
3630
     * @file component.js
3631
     */
3632
 
3633
    /**
3634
     * Base class for all UI Components.
3635
     * Components are UI objects which represent both a javascript object and an element
3636
     * in the DOM. They can be children of other components, and can have
3637
     * children themselves.
3638
     *
3639
     * Components can also use methods from {@link EventTarget}
3640
     */
3641
    class Component$1 {
3642
        /**
3643
         * A callback that is called when a component is ready. Does not have any
3644
         * parameters and any callback value will be ignored.
3645
         *
3646
         * @callback ReadyCallback
3647
         * @this Component
3648
         */
3649
 
3650
        /**
3651
         * Creates an instance of this class.
3652
         *
3653
         * @param { import('./player').default } player
3654
         *        The `Player` that this class should be attached to.
3655
         *
3656
         * @param {Object} [options]
3657
         *        The key/value store of component options.
3658
         *
3659
         * @param {Object[]} [options.children]
3660
         *        An array of children objects to initialize this component with. Children objects have
3661
         *        a name property that will be used if more than one component of the same type needs to be
3662
         *        added.
3663
         *
3664
         * @param  {string} [options.className]
3665
         *         A class or space separated list of classes to add the component
3666
         *
3667
         * @param {ReadyCallback} [ready]
3668
         *        Function that gets called when the `Component` is ready.
3669
         */
3670
        constructor(player, options, ready) {
3671
            // The component might be the player itself and we can't pass `this` to super
3672
            if (!player && this.play) {
3673
                this.player_ = player = this; // eslint-disable-line
3674
            } else {
3675
                this.player_ = player;
3676
            }
3677
            this.isDisposed_ = false;
3678
 
3679
            // Hold the reference to the parent component via `addChild` method
3680
            this.parentComponent_ = null;
3681
 
3682
            // Make a copy of prototype.options_ to protect against overriding defaults
3683
            this.options_ = merge$2({}, this.options_);
3684
 
3685
            // Updated options with supplied options
3686
            options = this.options_ = merge$2(this.options_, options);
3687
 
3688
            // Get ID from options or options element if one is supplied
3689
            this.id_ = options.id || options.el && options.el.id;
3690
 
3691
            // If there was no ID from the options, generate one
3692
            if (!this.id_) {
3693
                // Don't require the player ID function in the case of mock players
3694
                const id = player && player.id && player.id() || 'no_player';
3695
                this.id_ = `${id}_component_${newGUID()}`;
3696
            }
3697
            this.name_ = options.name || null;
3698
 
3699
            // Create element if one wasn't provided in options
3700
            if (options.el) {
3701
                this.el_ = options.el;
3702
            } else if (options.createEl !== false) {
3703
                this.el_ = this.createEl();
3704
            }
3705
            if (options.className && this.el_) {
3706
                options.className.split(' ').forEach(c => this.addClass(c));
3707
            }
3708
 
3709
            // Remove the placeholder event methods. If the component is evented, the
3710
            // real methods are added next
3711
            ['on', 'off', 'one', 'any', 'trigger'].forEach(fn => {
3712
                this[fn] = undefined;
3713
            });
3714
 
3715
            // if evented is anything except false, we want to mixin in evented
3716
            if (options.evented !== false) {
3717
                // Make this an evented object and use `el_`, if available, as its event bus
3718
                evented(this, {
3719
                    eventBusKey: this.el_ ? 'el_' : null
3720
                });
3721
                this.handleLanguagechange = this.handleLanguagechange.bind(this);
3722
                this.on(this.player_, 'languagechange', this.handleLanguagechange);
3723
            }
3724
            stateful(this, this.constructor.defaultState);
3725
            this.children_ = [];
3726
            this.childIndex_ = {};
3727
            this.childNameIndex_ = {};
3728
            this.setTimeoutIds_ = new Set();
3729
            this.setIntervalIds_ = new Set();
3730
            this.rafIds_ = new Set();
3731
            this.namedRafs_ = new Map();
3732
            this.clearingTimersOnDispose_ = false;
3733
 
3734
            // Add any child components in options
3735
            if (options.initChildren !== false) {
3736
                this.initChildren();
3737
            }
3738
 
3739
            // Don't want to trigger ready here or it will go before init is actually
3740
            // finished for all children that run this constructor
3741
            this.ready(ready);
3742
            if (options.reportTouchActivity !== false) {
3743
                this.enableTouchActivity();
3744
            }
3745
        }
3746
 
3747
        // `on`, `off`, `one`, `any` and `trigger` are here so tsc includes them in definitions.
3748
        // They are replaced or removed in the constructor
3749
 
3750
        /**
3751
         * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
3752
         * function that will get called when an event with a certain name gets triggered.
3753
         *
3754
         * @param {string|string[]} type
3755
         *        An event name or an array of event names.
3756
         *
3757
         * @param {Function} fn
3758
         *        The function to call with `EventTarget`s
3759
         */
3760
        on(type, fn) {}
3761
 
3762
        /**
3763
         * Removes an `event listener` for a specific event from an instance of `EventTarget`.
3764
         * This makes it so that the `event listener` will no longer get called when the
3765
         * named event happens.
3766
         *
3767
         * @param {string|string[]} type
3768
         *        An event name or an array of event names.
3769
         *
3770
         * @param {Function} [fn]
3771
         *        The function to remove. If not specified, all listeners managed by Video.js will be removed.
3772
         */
3773
        off(type, fn) {}
3774
 
3775
        /**
3776
         * This function will add an `event listener` that gets triggered only once. After the
3777
         * first trigger it will get removed. This is like adding an `event listener`
3778
         * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
3779
         *
3780
         * @param {string|string[]} type
3781
         *        An event name or an array of event names.
3782
         *
3783
         * @param {Function} fn
3784
         *        The function to be called once for each event name.
3785
         */
3786
        one(type, fn) {}
3787
 
3788
        /**
3789
         * This function will add an `event listener` that gets triggered only once and is
3790
         * removed from all events. This is like adding an array of `event listener`s
3791
         * with {@link EventTarget#on} that calls {@link EventTarget#off} on all events the
3792
         * first time it is triggered.
3793
         *
3794
         * @param {string|string[]} type
3795
         *        An event name or an array of event names.
3796
         *
3797
         * @param {Function} fn
3798
         *        The function to be called once for each event name.
3799
         */
3800
        any(type, fn) {}
3801
 
3802
        /**
3803
         * This function causes an event to happen. This will then cause any `event listeners`
3804
         * that are waiting for that event, to get called. If there are no `event listeners`
3805
         * for an event then nothing will happen.
3806
         *
3807
         * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
3808
         * Trigger will also call the `on` + `uppercaseEventName` function.
3809
         *
3810
         * Example:
3811
         * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
3812
         * `onClick` if it exists.
3813
         *
3814
         * @param {string|Event|Object} event
3815
         *        The name of the event, an `Event`, or an object with a key of type set to
3816
         *        an event name.
3817
         *
3818
         * @param {Object} [hash]
3819
         *        Optionally extra argument to pass through to an event listener
3820
         */
3821
        trigger(event, hash) {}
3822
 
3823
        /**
3824
         * Dispose of the `Component` and all child components.
3825
         *
3826
         * @fires Component#dispose
3827
         *
3828
         * @param {Object} options
3829
         * @param {Element} options.originalEl element with which to replace player element
3830
         */
3831
        dispose(options = {}) {
3832
            // Bail out if the component has already been disposed.
3833
            if (this.isDisposed_) {
3834
                return;
3835
            }
3836
            if (this.readyQueue_) {
3837
                this.readyQueue_.length = 0;
3838
            }
3839
 
3840
            /**
3841
             * Triggered when a `Component` is disposed.
3842
             *
3843
             * @event Component#dispose
3844
             * @type {Event}
3845
             *
3846
             * @property {boolean} [bubbles=false]
3847
             *           set to false so that the dispose event does not
3848
             *           bubble up
3849
             */
3850
            this.trigger({
3851
                type: 'dispose',
3852
                bubbles: false
3853
            });
3854
            this.isDisposed_ = true;
3855
 
3856
            // Dispose all children.
3857
            if (this.children_) {
3858
                for (let i = this.children_.length - 1; i >= 0; i--) {
3859
                    if (this.children_[i].dispose) {
3860
                        this.children_[i].dispose();
3861
                    }
3862
                }
3863
            }
3864
 
3865
            // Delete child references
3866
            this.children_ = null;
3867
            this.childIndex_ = null;
3868
            this.childNameIndex_ = null;
3869
            this.parentComponent_ = null;
3870
            if (this.el_) {
3871
                // Remove element from DOM
3872
                if (this.el_.parentNode) {
3873
                    if (options.restoreEl) {
3874
                        this.el_.parentNode.replaceChild(options.restoreEl, this.el_);
3875
                    } else {
3876
                        this.el_.parentNode.removeChild(this.el_);
3877
                    }
3878
                }
3879
                this.el_ = null;
3880
            }
3881
 
3882
            // remove reference to the player after disposing of the element
3883
            this.player_ = null;
3884
        }
3885
 
3886
        /**
3887
         * Determine whether or not this component has been disposed.
3888
         *
3889
         * @return {boolean}
3890
         *         If the component has been disposed, will be `true`. Otherwise, `false`.
3891
         */
3892
        isDisposed() {
3893
            return Boolean(this.isDisposed_);
3894
        }
3895
 
3896
        /**
3897
         * Return the {@link Player} that the `Component` has attached to.
3898
         *
3899
         * @return { import('./player').default }
3900
         *         The player that this `Component` has attached to.
3901
         */
3902
        player() {
3903
            return this.player_;
3904
        }
3905
 
3906
        /**
3907
         * Deep merge of options objects with new options.
3908
         * > Note: When both `obj` and `options` contain properties whose values are objects.
3909
         *         The two properties get merged using {@link module:obj.merge}
3910
         *
3911
         * @param {Object} obj
3912
         *        The object that contains new options.
3913
         *
3914
         * @return {Object}
3915
         *         A new object of `this.options_` and `obj` merged together.
3916
         */
3917
        options(obj) {
3918
            if (!obj) {
3919
                return this.options_;
3920
            }
3921
            this.options_ = merge$2(this.options_, obj);
3922
            return this.options_;
3923
        }
3924
 
3925
        /**
3926
         * Get the `Component`s DOM element
3927
         *
3928
         * @return {Element}
3929
         *         The DOM element for this `Component`.
3930
         */
3931
        el() {
3932
            return this.el_;
3933
        }
3934
 
3935
        /**
3936
         * Create the `Component`s DOM element.
3937
         *
3938
         * @param {string} [tagName]
3939
         *        Element's DOM node type. e.g. 'div'
3940
         *
3941
         * @param {Object} [properties]
3942
         *        An object of properties that should be set.
3943
         *
3944
         * @param {Object} [attributes]
3945
         *        An object of attributes that should be set.
3946
         *
3947
         * @return {Element}
3948
         *         The element that gets created.
3949
         */
3950
        createEl(tagName, properties, attributes) {
3951
            return createEl(tagName, properties, attributes);
3952
        }
3953
 
3954
        /**
3955
         * Localize a string given the string in english.
3956
         *
3957
         * If tokens are provided, it'll try and run a simple token replacement on the provided string.
3958
         * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
3959
         *
3960
         * If a `defaultValue` is provided, it'll use that over `string`,
3961
         * if a value isn't found in provided language files.
3962
         * This is useful if you want to have a descriptive key for token replacement
3963
         * but have a succinct localized string and not require `en.json` to be included.
3964
         *
3965
         * Currently, it is used for the progress bar timing.
3966
         * ```js
3967
         * {
3968
         *   "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
3969
         * }
3970
         * ```
3971
         * It is then used like so:
3972
         * ```js
3973
         * this.localize('progress bar timing: currentTime={1} duration{2}',
3974
         *               [this.player_.currentTime(), this.player_.duration()],
3975
         *               '{1} of {2}');
3976
         * ```
3977
         *
3978
         * Which outputs something like: `01:23 of 24:56`.
3979
         *
3980
         *
3981
         * @param {string} string
3982
         *        The string to localize and the key to lookup in the language files.
3983
         * @param {string[]} [tokens]
3984
         *        If the current item has token replacements, provide the tokens here.
3985
         * @param {string} [defaultValue]
3986
         *        Defaults to `string`. Can be a default value to use for token replacement
3987
         *        if the lookup key is needed to be separate.
3988
         *
3989
         * @return {string}
3990
         *         The localized string or if no localization exists the english string.
3991
         */
3992
        localize(string, tokens, defaultValue = string) {
3993
            const code = this.player_.language && this.player_.language();
3994
            const languages = this.player_.languages && this.player_.languages();
3995
            const language = languages && languages[code];
3996
            const primaryCode = code && code.split('-')[0];
3997
            const primaryLang = languages && languages[primaryCode];
3998
            let localizedString = defaultValue;
3999
            if (language && language[string]) {
4000
                localizedString = language[string];
4001
            } else if (primaryLang && primaryLang[string]) {
4002
                localizedString = primaryLang[string];
4003
            }
4004
            if (tokens) {
4005
                localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
4006
                    const value = tokens[index - 1];
4007
                    let ret = value;
4008
                    if (typeof value === 'undefined') {
4009
                        ret = match;
4010
                    }
4011
                    return ret;
4012
                });
4013
            }
4014
            return localizedString;
4015
        }
4016
 
4017
        /**
4018
         * Handles language change for the player in components. Should be overridden by sub-components.
4019
         *
4020
         * @abstract
4021
         */
4022
        handleLanguagechange() {}
4023
 
4024
        /**
4025
         * Return the `Component`s DOM element. This is where children get inserted.
4026
         * This will usually be the the same as the element returned in {@link Component#el}.
4027
         *
4028
         * @return {Element}
4029
         *         The content element for this `Component`.
4030
         */
4031
        contentEl() {
4032
            return this.contentEl_ || this.el_;
4033
        }
4034
 
4035
        /**
4036
         * Get this `Component`s ID
4037
         *
4038
         * @return {string}
4039
         *         The id of this `Component`
4040
         */
4041
        id() {
4042
            return this.id_;
4043
        }
4044
 
4045
        /**
4046
         * Get the `Component`s name. The name gets used to reference the `Component`
4047
         * and is set during registration.
4048
         *
4049
         * @return {string}
4050
         *         The name of this `Component`.
4051
         */
4052
        name() {
4053
            return this.name_;
4054
        }
4055
 
4056
        /**
4057
         * Get an array of all child components
4058
         *
4059
         * @return {Array}
4060
         *         The children
4061
         */
4062
        children() {
4063
            return this.children_;
4064
        }
4065
 
4066
        /**
4067
         * Returns the child `Component` with the given `id`.
4068
         *
4069
         * @param {string} id
4070
         *        The id of the child `Component` to get.
4071
         *
4072
         * @return {Component|undefined}
4073
         *         The child `Component` with the given `id` or undefined.
4074
         */
4075
        getChildById(id) {
4076
            return this.childIndex_[id];
4077
        }
4078
 
4079
        /**
4080
         * Returns the child `Component` with the given `name`.
4081
         *
4082
         * @param {string} name
4083
         *        The name of the child `Component` to get.
4084
         *
4085
         * @return {Component|undefined}
4086
         *         The child `Component` with the given `name` or undefined.
4087
         */
4088
        getChild(name) {
4089
            if (!name) {
4090
                return;
4091
            }
4092
            return this.childNameIndex_[name];
4093
        }
4094
 
4095
        /**
4096
         * Returns the descendant `Component` following the givent
4097
         * descendant `names`. For instance ['foo', 'bar', 'baz'] would
4098
         * try to get 'foo' on the current component, 'bar' on the 'foo'
4099
         * component and 'baz' on the 'bar' component and return undefined
4100
         * if any of those don't exist.
4101
         *
4102
         * @param {...string[]|...string} names
4103
         *        The name of the child `Component` to get.
4104
         *
4105
         * @return {Component|undefined}
4106
         *         The descendant `Component` following the given descendant
4107
         *         `names` or undefined.
4108
         */
4109
        getDescendant(...names) {
4110
            // flatten array argument into the main array
4111
            names = names.reduce((acc, n) => acc.concat(n), []);
4112
            let currentChild = this;
4113
            for (let i = 0; i < names.length; i++) {
4114
                currentChild = currentChild.getChild(names[i]);
4115
                if (!currentChild || !currentChild.getChild) {
4116
                    return;
4117
                }
4118
            }
4119
            return currentChild;
4120
        }
4121
 
4122
        /**
4123
         * Adds an SVG icon element to another element or component.
4124
         *
4125
         * @param {string} iconName
4126
         *        The name of icon. A list of all the icon names can be found at 'sandbox/svg-icons.html'
4127
         *
4128
         * @param {Element} [el=this.el()]
4129
         *        Element to set the title on. Defaults to the current Component's element.
4130
         *
4131
         * @return {Element}
4132
         *        The newly created icon element.
4133
         */
4134
        setIcon(iconName, el = this.el()) {
4135
            // TODO: In v9 of video.js, we will want to remove font icons entirely.
4136
            // This means this check, as well as the others throughout the code, and
4137
            // the unecessary CSS for font icons, will need to be removed.
4138
            // See https://github.com/videojs/video.js/pull/8260 as to which components
4139
            // need updating.
4140
            if (!this.player_.options_.experimentalSvgIcons) {
4141
                return;
4142
            }
4143
            const xmlnsURL = 'http://www.w3.org/2000/svg';
4144
 
4145
            // The below creates an element in the format of:
4146
            // <span><svg><use>....</use></svg></span>
4147
            const iconContainer = createEl('span', {
4148
                className: 'vjs-icon-placeholder vjs-svg-icon'
4149
            }, {
4150
                'aria-hidden': 'true'
4151
            });
4152
            const svgEl = document.createElementNS(xmlnsURL, 'svg');
4153
            svgEl.setAttributeNS(null, 'viewBox', '0 0 512 512');
4154
            const useEl = document.createElementNS(xmlnsURL, 'use');
4155
            svgEl.appendChild(useEl);
4156
            useEl.setAttributeNS(null, 'href', `#vjs-icon-${iconName}`);
4157
            iconContainer.appendChild(svgEl);
4158
 
4159
            // Replace a pre-existing icon if one exists.
4160
            if (this.iconIsSet_) {
4161
                el.replaceChild(iconContainer, el.querySelector('.vjs-icon-placeholder'));
4162
            } else {
4163
                el.appendChild(iconContainer);
4164
            }
4165
            this.iconIsSet_ = true;
4166
            return iconContainer;
4167
        }
4168
 
4169
        /**
4170
         * Add a child `Component` inside the current `Component`.
4171
         *
4172
         * @param {string|Component} child
4173
         *        The name or instance of a child to add.
4174
         *
4175
         * @param {Object} [options={}]
4176
         *        The key/value store of options that will get passed to children of
4177
         *        the child.
4178
         *
4179
         * @param {number} [index=this.children_.length]
4180
         *        The index to attempt to add a child into.
4181
         *
4182
         *
4183
         * @return {Component}
4184
         *         The `Component` that gets added as a child. When using a string the
4185
         *         `Component` will get created by this process.
4186
         */
4187
        addChild(child, options = {}, index = this.children_.length) {
4188
            let component;
4189
            let componentName;
4190
 
4191
            // If child is a string, create component with options
4192
            if (typeof child === 'string') {
4193
                componentName = toTitleCase$1(child);
4194
                const componentClassName = options.componentClass || componentName;
4195
 
4196
                // Set name through options
4197
                options.name = componentName;
4198
 
4199
                // Create a new object & element for this controls set
4200
                // If there's no .player_, this is a player
4201
                const ComponentClass = Component$1.getComponent(componentClassName);
4202
                if (!ComponentClass) {
4203
                    throw new Error(`Component ${componentClassName} does not exist`);
4204
                }
4205
 
4206
                // data stored directly on the videojs object may be
4207
                // misidentified as a component to retain
4208
                // backwards-compatibility with 4.x. check to make sure the
4209
                // component class can be instantiated.
4210
                if (typeof ComponentClass !== 'function') {
4211
                    return null;
4212
                }
4213
                component = new ComponentClass(this.player_ || this, options);
4214
 
4215
                // child is a component instance
4216
            } else {
4217
                component = child;
4218
            }
4219
            if (component.parentComponent_) {
4220
                component.parentComponent_.removeChild(component);
4221
            }
4222
            this.children_.splice(index, 0, component);
4223
            component.parentComponent_ = this;
4224
            if (typeof component.id === 'function') {
4225
                this.childIndex_[component.id()] = component;
4226
            }
4227
 
4228
            // If a name wasn't used to create the component, check if we can use the
4229
            // name function of the component
4230
            componentName = componentName || component.name && toTitleCase$1(component.name());
4231
            if (componentName) {
4232
                this.childNameIndex_[componentName] = component;
4233
                this.childNameIndex_[toLowerCase(componentName)] = component;
4234
            }
4235
 
4236
            // Add the UI object's element to the container div (box)
4237
            // Having an element is not required
4238
            if (typeof component.el === 'function' && component.el()) {
4239
                // If inserting before a component, insert before that component's element
4240
                let refNode = null;
4241
                if (this.children_[index + 1]) {
4242
                    // Most children are components, but the video tech is an HTML element
4243
                    if (this.children_[index + 1].el_) {
4244
                        refNode = this.children_[index + 1].el_;
4245
                    } else if (isEl(this.children_[index + 1])) {
4246
                        refNode = this.children_[index + 1];
4247
                    }
4248
                }
4249
                this.contentEl().insertBefore(component.el(), refNode);
4250
            }
4251
 
4252
            // Return so it can stored on parent object if desired.
4253
            return component;
4254
        }
4255
 
4256
        /**
4257
         * Remove a child `Component` from this `Component`s list of children. Also removes
4258
         * the child `Component`s element from this `Component`s element.
4259
         *
4260
         * @param {Component} component
4261
         *        The child `Component` to remove.
4262
         */
4263
        removeChild(component) {
4264
            if (typeof component === 'string') {
4265
                component = this.getChild(component);
4266
            }
4267
            if (!component || !this.children_) {
4268
                return;
4269
            }
4270
            let childFound = false;
4271
            for (let i = this.children_.length - 1; i >= 0; i--) {
4272
                if (this.children_[i] === component) {
4273
                    childFound = true;
4274
                    this.children_.splice(i, 1);
4275
                    break;
4276
                }
4277
            }
4278
            if (!childFound) {
4279
                return;
4280
            }
4281
            component.parentComponent_ = null;
4282
            this.childIndex_[component.id()] = null;
4283
            this.childNameIndex_[toTitleCase$1(component.name())] = null;
4284
            this.childNameIndex_[toLowerCase(component.name())] = null;
4285
            const compEl = component.el();
4286
            if (compEl && compEl.parentNode === this.contentEl()) {
4287
                this.contentEl().removeChild(component.el());
4288
            }
4289
        }
4290
 
4291
        /**
4292
         * Add and initialize default child `Component`s based upon options.
4293
         */
4294
        initChildren() {
4295
            const children = this.options_.children;
4296
            if (children) {
4297
                // `this` is `parent`
4298
                const parentOptions = this.options_;
4299
                const handleAdd = child => {
4300
                    const name = child.name;
4301
                    let opts = child.opts;
4302
 
4303
                    // Allow options for children to be set at the parent options
4304
                    // e.g. videojs(id, { controlBar: false });
4305
                    // instead of videojs(id, { children: { controlBar: false });
4306
                    if (parentOptions[name] !== undefined) {
4307
                        opts = parentOptions[name];
4308
                    }
4309
 
4310
                    // Allow for disabling default components
4311
                    // e.g. options['children']['posterImage'] = false
4312
                    if (opts === false) {
4313
                        return;
4314
                    }
4315
 
4316
                    // Allow options to be passed as a simple boolean if no configuration
4317
                    // is necessary.
4318
                    if (opts === true) {
4319
                        opts = {};
4320
                    }
4321
 
4322
                    // We also want to pass the original player options
4323
                    // to each component as well so they don't need to
4324
                    // reach back into the player for options later.
4325
                    opts.playerOptions = this.options_.playerOptions;
4326
 
4327
                    // Create and add the child component.
4328
                    // Add a direct reference to the child by name on the parent instance.
4329
                    // If two of the same component are used, different names should be supplied
4330
                    // for each
4331
                    const newChild = this.addChild(name, opts);
4332
                    if (newChild) {
4333
                        this[name] = newChild;
4334
                    }
4335
                };
4336
 
4337
                // Allow for an array of children details to passed in the options
4338
                let workingChildren;
4339
                const Tech = Component$1.getComponent('Tech');
4340
                if (Array.isArray(children)) {
4341
                    workingChildren = children;
4342
                } else {
4343
                    workingChildren = Object.keys(children);
4344
                }
4345
                workingChildren
4346
                    // children that are in this.options_ but also in workingChildren  would
4347
                    // give us extra children we do not want. So, we want to filter them out.
4348
                    .concat(Object.keys(this.options_).filter(function (child) {
4349
                        return !workingChildren.some(function (wchild) {
4350
                            if (typeof wchild === 'string') {
4351
                                return child === wchild;
4352
                            }
4353
                            return child === wchild.name;
4354
                        });
4355
                    })).map(child => {
4356
                    let name;
4357
                    let opts;
4358
                    if (typeof child === 'string') {
4359
                        name = child;
4360
                        opts = children[name] || this.options_[name] || {};
4361
                    } else {
4362
                        name = child.name;
4363
                        opts = child;
4364
                    }
4365
                    return {
4366
                        name,
4367
                        opts
4368
                    };
4369
                }).filter(child => {
4370
                    // we have to make sure that child.name isn't in the techOrder since
4371
                    // techs are registered as Components but can't aren't compatible
4372
                    // See https://github.com/videojs/video.js/issues/2772
4373
                    const c = Component$1.getComponent(child.opts.componentClass || toTitleCase$1(child.name));
4374
                    return c && !Tech.isTech(c);
4375
                }).forEach(handleAdd);
4376
            }
4377
        }
4378
 
4379
        /**
4380
         * Builds the default DOM class name. Should be overridden by sub-components.
4381
         *
4382
         * @return {string}
4383
         *         The DOM class name for this object.
4384
         *
4385
         * @abstract
4386
         */
4387
        buildCSSClass() {
4388
            // Child classes can include a function that does:
4389
            // return 'CLASS NAME' + this._super();
4390
            return '';
4391
        }
4392
 
4393
        /**
4394
         * Bind a listener to the component's ready state.
4395
         * Different from event listeners in that if the ready event has already happened
4396
         * it will trigger the function immediately.
4397
         *
4398
         * @param {ReadyCallback} fn
4399
         *        Function that gets called when the `Component` is ready.
4400
         *
4401
         * @return {Component}
4402
         *         Returns itself; method can be chained.
4403
         */
4404
        ready(fn, sync = false) {
4405
            if (!fn) {
4406
                return;
4407
            }
4408
            if (!this.isReady_) {
4409
                this.readyQueue_ = this.readyQueue_ || [];
4410
                this.readyQueue_.push(fn);
4411
                return;
4412
            }
4413
            if (sync) {
4414
                fn.call(this);
4415
            } else {
4416
                // Call the function asynchronously by default for consistency
4417
                this.setTimeout(fn, 1);
4418
            }
4419
        }
4420
 
4421
        /**
4422
         * Trigger all the ready listeners for this `Component`.
4423
         *
4424
         * @fires Component#ready
4425
         */
4426
        triggerReady() {
4427
            this.isReady_ = true;
4428
 
4429
            // Ensure ready is triggered asynchronously
4430
            this.setTimeout(function () {
4431
                const readyQueue = this.readyQueue_;
4432
 
4433
                // Reset Ready Queue
4434
                this.readyQueue_ = [];
4435
                if (readyQueue && readyQueue.length > 0) {
4436
                    readyQueue.forEach(function (fn) {
4437
                        fn.call(this);
4438
                    }, this);
4439
                }
4440
 
4441
                // Allow for using event listeners also
4442
                /**
4443
                 * Triggered when a `Component` is ready.
4444
                 *
4445
                 * @event Component#ready
4446
                 * @type {Event}
4447
                 */
4448
                this.trigger('ready');
4449
            }, 1);
4450
        }
4451
 
4452
        /**
4453
         * Find a single DOM element matching a `selector`. This can be within the `Component`s
4454
         * `contentEl()` or another custom context.
4455
         *
4456
         * @param {string} selector
4457
         *        A valid CSS selector, which will be passed to `querySelector`.
4458
         *
4459
         * @param {Element|string} [context=this.contentEl()]
4460
         *        A DOM element within which to query. Can also be a selector string in
4461
         *        which case the first matching element will get used as context. If
4462
         *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
4463
         *        nothing it falls back to `document`.
4464
         *
4465
         * @return {Element|null}
4466
         *         the dom element that was found, or null
4467
         *
4468
         * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
4469
         */
4470
        $(selector, context) {
4471
            return $(selector, context || this.contentEl());
4472
        }
4473
 
4474
        /**
4475
         * Finds all DOM element matching a `selector`. This can be within the `Component`s
4476
         * `contentEl()` or another custom context.
4477
         *
4478
         * @param {string} selector
4479
         *        A valid CSS selector, which will be passed to `querySelectorAll`.
4480
         *
4481
         * @param {Element|string} [context=this.contentEl()]
4482
         *        A DOM element within which to query. Can also be a selector string in
4483
         *        which case the first matching element will get used as context. If
4484
         *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
4485
         *        nothing it falls back to `document`.
4486
         *
4487
         * @return {NodeList}
4488
         *         a list of dom elements that were found
4489
         *
4490
         * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
4491
         */
4492
        $$(selector, context) {
4493
            return $$(selector, context || this.contentEl());
4494
        }
4495
 
4496
        /**
4497
         * Check if a component's element has a CSS class name.
4498
         *
4499
         * @param {string} classToCheck
4500
         *        CSS class name to check.
4501
         *
4502
         * @return {boolean}
4503
         *         - True if the `Component` has the class.
4504
         *         - False if the `Component` does not have the class`
4505
         */
4506
        hasClass(classToCheck) {
4507
            return hasClass(this.el_, classToCheck);
4508
        }
4509
 
4510
        /**
4511
         * Add a CSS class name to the `Component`s element.
4512
         *
4513
         * @param {...string} classesToAdd
4514
         *        One or more CSS class name to add.
4515
         */
4516
        addClass(...classesToAdd) {
4517
            addClass(this.el_, ...classesToAdd);
4518
        }
4519
 
4520
        /**
4521
         * Remove a CSS class name from the `Component`s element.
4522
         *
4523
         * @param {...string} classesToRemove
4524
         *        One or more CSS class name to remove.
4525
         */
4526
        removeClass(...classesToRemove) {
4527
            removeClass(this.el_, ...classesToRemove);
4528
        }
4529
 
4530
        /**
4531
         * Add or remove a CSS class name from the component's element.
4532
         * - `classToToggle` gets added when {@link Component#hasClass} would return false.
4533
         * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
4534
         *
4535
         * @param  {string} classToToggle
4536
         *         The class to add or remove based on (@link Component#hasClass}
4537
         *
4538
         * @param  {boolean|Dom~predicate} [predicate]
4539
         *         An {@link Dom~predicate} function or a boolean
4540
         */
4541
        toggleClass(classToToggle, predicate) {
4542
            toggleClass(this.el_, classToToggle, predicate);
4543
        }
4544
 
4545
        /**
4546
         * Show the `Component`s element if it is hidden by removing the
4547
         * 'vjs-hidden' class name from it.
4548
         */
4549
        show() {
4550
            this.removeClass('vjs-hidden');
4551
        }
4552
 
4553
        /**
4554
         * Hide the `Component`s element if it is currently showing by adding the
4555
         * 'vjs-hidden` class name to it.
4556
         */
4557
        hide() {
4558
            this.addClass('vjs-hidden');
4559
        }
4560
 
4561
        /**
4562
         * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
4563
         * class name to it. Used during fadeIn/fadeOut.
4564
         *
4565
         * @private
4566
         */
4567
        lockShowing() {
4568
            this.addClass('vjs-lock-showing');
4569
        }
4570
 
4571
        /**
4572
         * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
4573
         * class name from it. Used during fadeIn/fadeOut.
4574
         *
4575
         * @private
4576
         */
4577
        unlockShowing() {
4578
            this.removeClass('vjs-lock-showing');
4579
        }
4580
 
4581
        /**
4582
         * Get the value of an attribute on the `Component`s element.
4583
         *
4584
         * @param {string} attribute
4585
         *        Name of the attribute to get the value from.
4586
         *
4587
         * @return {string|null}
4588
         *         - The value of the attribute that was asked for.
4589
         *         - Can be an empty string on some browsers if the attribute does not exist
4590
         *           or has no value
4591
         *         - Most browsers will return null if the attribute does not exist or has
4592
         *           no value.
4593
         *
4594
         * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
4595
         */
4596
        getAttribute(attribute) {
4597
            return getAttribute(this.el_, attribute);
4598
        }
4599
 
4600
        /**
4601
         * Set the value of an attribute on the `Component`'s element
4602
         *
4603
         * @param {string} attribute
4604
         *        Name of the attribute to set.
4605
         *
4606
         * @param {string} value
4607
         *        Value to set the attribute to.
4608
         *
4609
         * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
4610
         */
4611
        setAttribute(attribute, value) {
4612
            setAttribute(this.el_, attribute, value);
4613
        }
4614
 
4615
        /**
4616
         * Remove an attribute from the `Component`s element.
4617
         *
4618
         * @param {string} attribute
4619
         *        Name of the attribute to remove.
4620
         *
4621
         * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
4622
         */
4623
        removeAttribute(attribute) {
4624
            removeAttribute(this.el_, attribute);
4625
        }
4626
 
4627
        /**
4628
         * Get or set the width of the component based upon the CSS styles.
4629
         * See {@link Component#dimension} for more detailed information.
4630
         *
4631
         * @param {number|string} [num]
4632
         *        The width that you want to set postfixed with '%', 'px' or nothing.
4633
         *
4634
         * @param {boolean} [skipListeners]
4635
         *        Skip the componentresize event trigger
4636
         *
4637
         * @return {number|undefined}
4638
         *         The width when getting, zero if there is no width
4639
         */
4640
        width(num, skipListeners) {
4641
            return this.dimension('width', num, skipListeners);
4642
        }
4643
 
4644
        /**
4645
         * Get or set the height of the component based upon the CSS styles.
4646
         * See {@link Component#dimension} for more detailed information.
4647
         *
4648
         * @param {number|string} [num]
4649
         *        The height that you want to set postfixed with '%', 'px' or nothing.
4650
         *
4651
         * @param {boolean} [skipListeners]
4652
         *        Skip the componentresize event trigger
4653
         *
4654
         * @return {number|undefined}
4655
         *         The height when getting, zero if there is no height
4656
         */
4657
        height(num, skipListeners) {
4658
            return this.dimension('height', num, skipListeners);
4659
        }
4660
 
4661
        /**
4662
         * Set both the width and height of the `Component` element at the same time.
4663
         *
4664
         * @param  {number|string} width
4665
         *         Width to set the `Component`s element to.
4666
         *
4667
         * @param  {number|string} height
4668
         *         Height to set the `Component`s element to.
4669
         */
4670
        dimensions(width, height) {
4671
            // Skip componentresize listeners on width for optimization
4672
            this.width(width, true);
4673
            this.height(height);
4674
        }
4675
 
4676
        /**
4677
         * Get or set width or height of the `Component` element. This is the shared code
4678
         * for the {@link Component#width} and {@link Component#height}.
4679
         *
4680
         * Things to know:
4681
         * - If the width or height in an number this will return the number postfixed with 'px'.
4682
         * - If the width/height is a percent this will return the percent postfixed with '%'
4683
         * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
4684
         *   defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
4685
         *   See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/}
4686
         *   for more information
4687
         * - If you want the computed style of the component, use {@link Component#currentWidth}
4688
         *   and {@link {Component#currentHeight}
4689
         *
4690
         * @fires Component#componentresize
4691
         *
4692
         * @param {string} widthOrHeight
4693
         8        'width' or 'height'
4694
         *
4695
         * @param  {number|string} [num]
4696
         8         New dimension
4697
         *
4698
         * @param  {boolean} [skipListeners]
4699
         *         Skip componentresize event trigger
4700
         *
4701
         * @return {number|undefined}
4702
         *         The dimension when getting or 0 if unset
4703
         */
4704
        dimension(widthOrHeight, num, skipListeners) {
4705
            if (num !== undefined) {
4706
                // Set to zero if null or literally NaN (NaN !== NaN)
4707
                if (num === null || num !== num) {
4708
                    num = 0;
4709
                }
4710
 
4711
                // Check if using css width/height (% or px) and adjust
4712
                if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
4713
                    this.el_.style[widthOrHeight] = num;
4714
                } else if (num === 'auto') {
4715
                    this.el_.style[widthOrHeight] = '';
4716
                } else {
4717
                    this.el_.style[widthOrHeight] = num + 'px';
4718
                }
4719
 
4720
                // skipListeners allows us to avoid triggering the resize event when setting both width and height
4721
                if (!skipListeners) {
4722
                    /**
4723
                     * Triggered when a component is resized.
4724
                     *
4725
                     * @event Component#componentresize
4726
                     * @type {Event}
4727
                     */
4728
                    this.trigger('componentresize');
4729
                }
4730
                return;
4731
            }
4732
 
4733
            // Not setting a value, so getting it
4734
            // Make sure element exists
4735
            if (!this.el_) {
4736
                return 0;
4737
            }
4738
 
4739
            // Get dimension value from style
4740
            const val = this.el_.style[widthOrHeight];
4741
            const pxIndex = val.indexOf('px');
4742
            if (pxIndex !== -1) {
4743
                // Return the pixel value with no 'px'
4744
                return parseInt(val.slice(0, pxIndex), 10);
4745
            }
4746
 
4747
            // No px so using % or no style was set, so falling back to offsetWidth/height
4748
            // If component has display:none, offset will return 0
4749
            // TODO: handle display:none and no dimension style using px
4750
            return parseInt(this.el_['offset' + toTitleCase$1(widthOrHeight)], 10);
4751
        }
4752
 
4753
        /**
4754
         * Get the computed width or the height of the component's element.
4755
         *
4756
         * Uses `window.getComputedStyle`.
4757
         *
4758
         * @param {string} widthOrHeight
4759
         *        A string containing 'width' or 'height'. Whichever one you want to get.
4760
         *
4761
         * @return {number}
4762
         *         The dimension that gets asked for or 0 if nothing was set
4763
         *         for that dimension.
4764
         */
4765
        currentDimension(widthOrHeight) {
4766
            let computedWidthOrHeight = 0;
4767
            if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
4768
                throw new Error('currentDimension only accepts width or height value');
4769
            }
4770
            computedWidthOrHeight = computedStyle(this.el_, widthOrHeight);
4771
 
4772
            // remove 'px' from variable and parse as integer
4773
            computedWidthOrHeight = parseFloat(computedWidthOrHeight);
4774
 
4775
            // if the computed value is still 0, it's possible that the browser is lying
4776
            // and we want to check the offset values.
4777
            // This code also runs wherever getComputedStyle doesn't exist.
4778
            if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) {
4779
                const rule = `offset${toTitleCase$1(widthOrHeight)}`;
4780
                computedWidthOrHeight = this.el_[rule];
4781
            }
4782
            return computedWidthOrHeight;
4783
        }
4784
 
4785
        /**
4786
         * An object that contains width and height values of the `Component`s
4787
         * computed style. Uses `window.getComputedStyle`.
4788
         *
4789
         * @typedef {Object} Component~DimensionObject
4790
         *
4791
         * @property {number} width
4792
         *           The width of the `Component`s computed style.
4793
         *
4794
         * @property {number} height
4795
         *           The height of the `Component`s computed style.
4796
         */
4797
 
4798
        /**
4799
         * Get an object that contains computed width and height values of the
4800
         * component's element.
4801
         *
4802
         * Uses `window.getComputedStyle`.
4803
         *
4804
         * @return {Component~DimensionObject}
4805
         *         The computed dimensions of the component's element.
4806
         */
4807
        currentDimensions() {
4808
            return {
4809
                width: this.currentDimension('width'),
4810
                height: this.currentDimension('height')
4811
            };
4812
        }
4813
 
4814
        /**
4815
         * Get the computed width of the component's element.
4816
         *
4817
         * Uses `window.getComputedStyle`.
4818
         *
4819
         * @return {number}
4820
         *         The computed width of the component's element.
4821
         */
4822
        currentWidth() {
4823
            return this.currentDimension('width');
4824
        }
4825
 
4826
        /**
4827
         * Get the computed height of the component's element.
4828
         *
4829
         * Uses `window.getComputedStyle`.
4830
         *
4831
         * @return {number}
4832
         *         The computed height of the component's element.
4833
         */
4834
        currentHeight() {
4835
            return this.currentDimension('height');
4836
        }
4837
 
4838
        /**
4839
         * Set the focus to this component
4840
         */
4841
        focus() {
4842
            this.el_.focus();
4843
        }
4844
 
4845
        /**
4846
         * Remove the focus from this component
4847
         */
4848
        blur() {
4849
            this.el_.blur();
4850
        }
4851
 
4852
        /**
4853
         * When this Component receives a `keydown` event which it does not process,
4854
         *  it passes the event to the Player for handling.
4855
         *
4856
         * @param {KeyboardEvent} event
4857
         *        The `keydown` event that caused this function to be called.
4858
         */
4859
        handleKeyDown(event) {
4860
            if (this.player_) {
4861
                // We only stop propagation here because we want unhandled events to fall
4862
                // back to the browser. Exclude Tab for focus trapping.
4863
                if (!keycode.isEventKey(event, 'Tab')) {
4864
                    event.stopPropagation();
4865
                }
4866
                this.player_.handleKeyDown(event);
4867
            }
4868
        }
4869
 
4870
        /**
4871
         * Many components used to have a `handleKeyPress` method, which was poorly
4872
         * named because it listened to a `keydown` event. This method name now
4873
         * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress`
4874
         * will not see their method calls stop working.
4875
         *
4876
         * @param {KeyboardEvent} event
4877
         *        The event that caused this function to be called.
4878
         */
4879
        handleKeyPress(event) {
4880
            this.handleKeyDown(event);
4881
        }
4882
 
4883
        /**
4884
         * Emit a 'tap' events when touch event support gets detected. This gets used to
4885
         * support toggling the controls through a tap on the video. They get enabled
4886
         * because every sub-component would have extra overhead otherwise.
4887
         *
4888
         * @protected
4889
         * @fires Component#tap
4890
         * @listens Component#touchstart
4891
         * @listens Component#touchmove
4892
         * @listens Component#touchleave
4893
         * @listens Component#touchcancel
4894
         * @listens Component#touchend
4895
         */
4896
        emitTapEvents() {
4897
            // Track the start time so we can determine how long the touch lasted
4898
            let touchStart = 0;
4899
            let firstTouch = null;
4900
 
4901
            // Maximum movement allowed during a touch event to still be considered a tap
4902
            // Other popular libs use anywhere from 2 (hammer.js) to 15,
4903
            // so 10 seems like a nice, round number.
4904
            const tapMovementThreshold = 10;
4905
 
4906
            // The maximum length a touch can be while still being considered a tap
4907
            const touchTimeThreshold = 200;
4908
            let couldBeTap;
4909
            this.on('touchstart', function (event) {
4910
                // If more than one finger, don't consider treating this as a click
4911
                if (event.touches.length === 1) {
4912
                    // Copy pageX/pageY from the object
4913
                    firstTouch = {
4914
                        pageX: event.touches[0].pageX,
4915
                        pageY: event.touches[0].pageY
4916
                    };
4917
                    // Record start time so we can detect a tap vs. "touch and hold"
4918
                    touchStart = window.performance.now();
4919
                    // Reset couldBeTap tracking
4920
                    couldBeTap = true;
4921
                }
4922
            });
4923
            this.on('touchmove', function (event) {
4924
                // If more than one finger, don't consider treating this as a click
4925
                if (event.touches.length > 1) {
4926
                    couldBeTap = false;
4927
                } else if (firstTouch) {
4928
                    // Some devices will throw touchmoves for all but the slightest of taps.
4929
                    // So, if we moved only a small distance, this could still be a tap
4930
                    const xdiff = event.touches[0].pageX - firstTouch.pageX;
4931
                    const ydiff = event.touches[0].pageY - firstTouch.pageY;
4932
                    const touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
4933
                    if (touchDistance > tapMovementThreshold) {
4934
                        couldBeTap = false;
4935
                    }
4936
                }
4937
            });
4938
            const noTap = function () {
4939
                couldBeTap = false;
4940
            };
4941
 
4942
            // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
4943
            this.on('touchleave', noTap);
4944
            this.on('touchcancel', noTap);
4945
 
4946
            // When the touch ends, measure how long it took and trigger the appropriate
4947
            // event
4948
            this.on('touchend', function (event) {
4949
                firstTouch = null;
4950
                // Proceed only if the touchmove/leave/cancel event didn't happen
4951
                if (couldBeTap === true) {
4952
                    // Measure how long the touch lasted
4953
                    const touchTime = window.performance.now() - touchStart;
4954
 
4955
                    // Make sure the touch was less than the threshold to be considered a tap
4956
                    if (touchTime < touchTimeThreshold) {
4957
                        // Don't let browser turn this into a click
4958
                        event.preventDefault();
4959
                        /**
4960
                         * Triggered when a `Component` is tapped.
4961
                         *
4962
                         * @event Component#tap
4963
                         * @type {MouseEvent}
4964
                         */
4965
                        this.trigger('tap');
4966
                        // It may be good to copy the touchend event object and change the
4967
                        // type to tap, if the other event properties aren't exact after
4968
                        // Events.fixEvent runs (e.g. event.target)
4969
                    }
4970
                }
4971
            });
4972
        }
4973
 
4974
        /**
4975
         * This function reports user activity whenever touch events happen. This can get
4976
         * turned off by any sub-components that wants touch events to act another way.
4977
         *
4978
         * Report user touch activity when touch events occur. User activity gets used to
4979
         * determine when controls should show/hide. It is simple when it comes to mouse
4980
         * events, because any mouse event should show the controls. So we capture mouse
4981
         * events that bubble up to the player and report activity when that happens.
4982
         * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
4983
         * controls. So touch events can't help us at the player level either.
4984
         *
4985
         * User activity gets checked asynchronously. So what could happen is a tap event
4986
         * on the video turns the controls off. Then the `touchend` event bubbles up to
4987
         * the player. Which, if it reported user activity, would turn the controls right
4988
         * back on. We also don't want to completely block touch events from bubbling up.
4989
         * Furthermore a `touchmove` event and anything other than a tap, should not turn
4990
         * controls back on.
4991
         *
4992
         * @listens Component#touchstart
4993
         * @listens Component#touchmove
4994
         * @listens Component#touchend
4995
         * @listens Component#touchcancel
4996
         */
4997
        enableTouchActivity() {
4998
            // Don't continue if the root player doesn't support reporting user activity
4999
            if (!this.player() || !this.player().reportUserActivity) {
5000
                return;
5001
            }
5002
 
5003
            // listener for reporting that the user is active
5004
            const report = bind_(this.player(), this.player().reportUserActivity);
5005
            let touchHolding;
5006
            this.on('touchstart', function () {
5007
                report();
5008
                // For as long as the they are touching the device or have their mouse down,
5009
                // we consider them active even if they're not moving their finger or mouse.
5010
                // So we want to continue to update that they are active
5011
                this.clearInterval(touchHolding);
5012
                // report at the same interval as activityCheck
5013
                touchHolding = this.setInterval(report, 250);
5014
            });
5015
            const touchEnd = function (event) {
5016
                report();
5017
                // stop the interval that maintains activity if the touch is holding
5018
                this.clearInterval(touchHolding);
5019
            };
5020
            this.on('touchmove', report);
5021
            this.on('touchend', touchEnd);
5022
            this.on('touchcancel', touchEnd);
5023
        }
5024
 
5025
        /**
5026
         * A callback that has no parameters and is bound into `Component`s context.
5027
         *
5028
         * @callback Component~GenericCallback
5029
         * @this Component
5030
         */
5031
 
5032
        /**
5033
         * Creates a function that runs after an `x` millisecond timeout. This function is a
5034
         * wrapper around `window.setTimeout`. There are a few reasons to use this one
5035
         * instead though:
5036
         * 1. It gets cleared via  {@link Component#clearTimeout} when
5037
         *    {@link Component#dispose} gets called.
5038
         * 2. The function callback will gets turned into a {@link Component~GenericCallback}
5039
         *
5040
         * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
5041
         *         will cause its dispose listener not to get cleaned up! Please use
5042
         *         {@link Component#clearTimeout} or {@link Component#dispose} instead.
5043
         *
5044
         * @param {Component~GenericCallback} fn
5045
         *        The function that will be run after `timeout`.
5046
         *
5047
         * @param {number} timeout
5048
         *        Timeout in milliseconds to delay before executing the specified function.
5049
         *
5050
         * @return {number}
5051
         *         Returns a timeout ID that gets used to identify the timeout. It can also
5052
         *         get used in {@link Component#clearTimeout} to clear the timeout that
5053
         *         was set.
5054
         *
5055
         * @listens Component#dispose
5056
         * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
5057
         */
5058
        setTimeout(fn, timeout) {
5059
            // declare as variables so they are properly available in timeout function
5060
            // eslint-disable-next-line
5061
            var timeoutId;
5062
            fn = bind_(this, fn);
5063
            this.clearTimersOnDispose_();
5064
            timeoutId = window.setTimeout(() => {
5065
                if (this.setTimeoutIds_.has(timeoutId)) {
5066
                    this.setTimeoutIds_.delete(timeoutId);
5067
                }
5068
                fn();
5069
            }, timeout);
5070
            this.setTimeoutIds_.add(timeoutId);
5071
            return timeoutId;
5072
        }
5073
 
5074
        /**
5075
         * Clears a timeout that gets created via `window.setTimeout` or
5076
         * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
5077
         * use this function instead of `window.clearTimout`. If you don't your dispose
5078
         * listener will not get cleaned up until {@link Component#dispose}!
5079
         *
5080
         * @param {number} timeoutId
5081
         *        The id of the timeout to clear. The return value of
5082
         *        {@link Component#setTimeout} or `window.setTimeout`.
5083
         *
5084
         * @return {number}
5085
         *         Returns the timeout id that was cleared.
5086
         *
5087
         * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
5088
         */
5089
        clearTimeout(timeoutId) {
5090
            if (this.setTimeoutIds_.has(timeoutId)) {
5091
                this.setTimeoutIds_.delete(timeoutId);
5092
                window.clearTimeout(timeoutId);
5093
            }
5094
            return timeoutId;
5095
        }
5096
 
5097
        /**
5098
         * Creates a function that gets run every `x` milliseconds. This function is a wrapper
5099
         * around `window.setInterval`. There are a few reasons to use this one instead though.
5100
         * 1. It gets cleared via  {@link Component#clearInterval} when
5101
         *    {@link Component#dispose} gets called.
5102
         * 2. The function callback will be a {@link Component~GenericCallback}
5103
         *
5104
         * @param {Component~GenericCallback} fn
5105
         *        The function to run every `x` seconds.
5106
         *
5107
         * @param {number} interval
5108
         *        Execute the specified function every `x` milliseconds.
5109
         *
5110
         * @return {number}
5111
         *         Returns an id that can be used to identify the interval. It can also be be used in
5112
         *         {@link Component#clearInterval} to clear the interval.
5113
         *
5114
         * @listens Component#dispose
5115
         * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
5116
         */
5117
        setInterval(fn, interval) {
5118
            fn = bind_(this, fn);
5119
            this.clearTimersOnDispose_();
5120
            const intervalId = window.setInterval(fn, interval);
5121
            this.setIntervalIds_.add(intervalId);
5122
            return intervalId;
5123
        }
5124
 
5125
        /**
5126
         * Clears an interval that gets created via `window.setInterval` or
5127
         * {@link Component#setInterval}. If you set an interval via {@link Component#setInterval}
5128
         * use this function instead of `window.clearInterval`. If you don't your dispose
5129
         * listener will not get cleaned up until {@link Component#dispose}!
5130
         *
5131
         * @param {number} intervalId
5132
         *        The id of the interval to clear. The return value of
5133
         *        {@link Component#setInterval} or `window.setInterval`.
5134
         *
5135
         * @return {number}
5136
         *         Returns the interval id that was cleared.
5137
         *
5138
         * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
5139
         */
5140
        clearInterval(intervalId) {
5141
            if (this.setIntervalIds_.has(intervalId)) {
5142
                this.setIntervalIds_.delete(intervalId);
5143
                window.clearInterval(intervalId);
5144
            }
5145
            return intervalId;
5146
        }
5147
 
5148
        /**
5149
         * Queues up a callback to be passed to requestAnimationFrame (rAF), but
5150
         * with a few extra bonuses:
5151
         *
5152
         * - Supports browsers that do not support rAF by falling back to
5153
         *   {@link Component#setTimeout}.
5154
         *
5155
         * - The callback is turned into a {@link Component~GenericCallback} (i.e.
5156
         *   bound to the component).
5157
         *
5158
         * - Automatic cancellation of the rAF callback is handled if the component
5159
         *   is disposed before it is called.
5160
         *
5161
         * @param  {Component~GenericCallback} fn
5162
         *         A function that will be bound to this component and executed just
5163
         *         before the browser's next repaint.
5164
         *
5165
         * @return {number}
5166
         *         Returns an rAF ID that gets used to identify the timeout. It can
5167
         *         also be used in {@link Component#cancelAnimationFrame} to cancel
5168
         *         the animation frame callback.
5169
         *
5170
         * @listens Component#dispose
5171
         * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
5172
         */
5173
        requestAnimationFrame(fn) {
5174
            this.clearTimersOnDispose_();
5175
 
5176
            // declare as variables so they are properly available in rAF function
5177
            // eslint-disable-next-line
5178
            var id;
5179
            fn = bind_(this, fn);
5180
            id = window.requestAnimationFrame(() => {
5181
                if (this.rafIds_.has(id)) {
5182
                    this.rafIds_.delete(id);
5183
                }
5184
                fn();
5185
            });
5186
            this.rafIds_.add(id);
5187
            return id;
5188
        }
5189
 
5190
        /**
5191
         * Request an animation frame, but only one named animation
5192
         * frame will be queued. Another will never be added until
5193
         * the previous one finishes.
5194
         *
5195
         * @param {string} name
5196
         *        The name to give this requestAnimationFrame
5197
         *
5198
         * @param  {Component~GenericCallback} fn
5199
         *         A function that will be bound to this component and executed just
5200
         *         before the browser's next repaint.
5201
         */
5202
        requestNamedAnimationFrame(name, fn) {
5203
            if (this.namedRafs_.has(name)) {
5204
                return;
5205
            }
5206
            this.clearTimersOnDispose_();
5207
            fn = bind_(this, fn);
5208
            const id = this.requestAnimationFrame(() => {
5209
                fn();
5210
                if (this.namedRafs_.has(name)) {
5211
                    this.namedRafs_.delete(name);
5212
                }
5213
            });
5214
            this.namedRafs_.set(name, id);
5215
            return name;
5216
        }
5217
 
5218
        /**
5219
         * Cancels a current named animation frame if it exists.
5220
         *
5221
         * @param {string} name
5222
         *        The name of the requestAnimationFrame to cancel.
5223
         */
5224
        cancelNamedAnimationFrame(name) {
5225
            if (!this.namedRafs_.has(name)) {
5226
                return;
5227
            }
5228
            this.cancelAnimationFrame(this.namedRafs_.get(name));
5229
            this.namedRafs_.delete(name);
5230
        }
5231
 
5232
        /**
5233
         * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
5234
         * (rAF).
5235
         *
5236
         * If you queue an rAF callback via {@link Component#requestAnimationFrame},
5237
         * use this function instead of `window.cancelAnimationFrame`. If you don't,
5238
         * your dispose listener will not get cleaned up until {@link Component#dispose}!
5239
         *
5240
         * @param {number} id
5241
         *        The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
5242
         *
5243
         * @return {number}
5244
         *         Returns the rAF ID that was cleared.
5245
         *
5246
         * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
5247
         */
5248
        cancelAnimationFrame(id) {
5249
            if (this.rafIds_.has(id)) {
5250
                this.rafIds_.delete(id);
5251
                window.cancelAnimationFrame(id);
5252
            }
5253
            return id;
5254
        }
5255
 
5256
        /**
5257
         * A function to setup `requestAnimationFrame`, `setTimeout`,
5258
         * and `setInterval`, clearing on dispose.
5259
         *
5260
         * > Previously each timer added and removed dispose listeners on it's own.
5261
         * For better performance it was decided to batch them all, and use `Set`s
5262
         * to track outstanding timer ids.
5263
         *
5264
         * @private
5265
         */
5266
        clearTimersOnDispose_() {
5267
            if (this.clearingTimersOnDispose_) {
5268
                return;
5269
            }
5270
            this.clearingTimersOnDispose_ = true;
5271
            this.one('dispose', () => {
5272
                [['namedRafs_', 'cancelNamedAnimationFrame'], ['rafIds_', 'cancelAnimationFrame'], ['setTimeoutIds_', 'clearTimeout'], ['setIntervalIds_', 'clearInterval']].forEach(([idName, cancelName]) => {
5273
                    // for a `Set` key will actually be the value again
5274
                    // so forEach((val, val) =>` but for maps we want to use
5275
                    // the key.
5276
                    this[idName].forEach((val, key) => this[cancelName](key));
5277
                });
5278
                this.clearingTimersOnDispose_ = false;
5279
            });
5280
        }
5281
 
5282
        /**
5283
         * Register a `Component` with `videojs` given the name and the component.
5284
         *
5285
         * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
5286
         *         should be registered using {@link Tech.registerTech} or
5287
         *         {@link videojs:videojs.registerTech}.
5288
         *
5289
         * > NOTE: This function can also be seen on videojs as
5290
         *         {@link videojs:videojs.registerComponent}.
5291
         *
5292
         * @param {string} name
5293
         *        The name of the `Component` to register.
5294
         *
5295
         * @param {Component} ComponentToRegister
5296
         *        The `Component` class to register.
5297
         *
5298
         * @return {Component}
5299
         *         The `Component` that was registered.
5300
         */
5301
        static registerComponent(name, ComponentToRegister) {
5302
            if (typeof name !== 'string' || !name) {
5303
                throw new Error(`Illegal component name, "${name}"; must be a non-empty string.`);
5304
            }
5305
            const Tech = Component$1.getComponent('Tech');
5306
 
5307
            // We need to make sure this check is only done if Tech has been registered.
5308
            const isTech = Tech && Tech.isTech(ComponentToRegister);
5309
            const isComp = Component$1 === ComponentToRegister || Component$1.prototype.isPrototypeOf(ComponentToRegister.prototype);
5310
            if (isTech || !isComp) {
5311
                let reason;
5312
                if (isTech) {
5313
                    reason = 'techs must be registered using Tech.registerTech()';
5314
                } else {
5315
                    reason = 'must be a Component subclass';
5316
                }
5317
                throw new Error(`Illegal component, "${name}"; ${reason}.`);
5318
            }
5319
            name = toTitleCase$1(name);
5320
            if (!Component$1.components_) {
5321
                Component$1.components_ = {};
5322
            }
5323
            const Player = Component$1.getComponent('Player');
5324
            if (name === 'Player' && Player && Player.players) {
5325
                const players = Player.players;
5326
                const playerNames = Object.keys(players);
5327
 
5328
                // If we have players that were disposed, then their name will still be
5329
                // in Players.players. So, we must loop through and verify that the value
5330
                // for each item is not null. This allows registration of the Player component
5331
                // after all players have been disposed or before any were created.
5332
                if (players && playerNames.length > 0 && playerNames.map(pname => players[pname]).every(Boolean)) {
5333
                    throw new Error('Can not register Player component after player has been created.');
5334
                }
5335
            }
5336
            Component$1.components_[name] = ComponentToRegister;
5337
            Component$1.components_[toLowerCase(name)] = ComponentToRegister;
5338
            return ComponentToRegister;
5339
        }
5340
 
5341
        /**
5342
         * Get a `Component` based on the name it was registered with.
5343
         *
5344
         * @param {string} name
5345
         *        The Name of the component to get.
5346
         *
5347
         * @return {typeof Component}
5348
         *         The `Component` that got registered under the given name.
5349
         */
5350
        static getComponent(name) {
5351
            if (!name || !Component$1.components_) {
5352
                return;
5353
            }
5354
            return Component$1.components_[name];
5355
        }
5356
    }
5357
    Component$1.registerComponent('Component', Component$1);
5358
 
5359
    /**
5360
     * @file time.js
5361
     * @module time
5362
     */
5363
 
5364
    /**
5365
     * Returns the time for the specified index at the start or end
5366
     * of a TimeRange object.
5367
     *
5368
     * @typedef    {Function} TimeRangeIndex
5369
     *
5370
     * @param      {number} [index=0]
5371
     *             The range number to return the time for.
5372
     *
5373
     * @return     {number}
5374
     *             The time offset at the specified index.
5375
     *
5376
     * @deprecated The index argument must be provided.
5377
     *             In the future, leaving it out will throw an error.
5378
     */
5379
 
5380
    /**
5381
     * An object that contains ranges of time, which mimics {@link TimeRanges}.
5382
     *
5383
     * @typedef  {Object} TimeRange
5384
     *
5385
     * @property {number} length
5386
     *           The number of time ranges represented by this object.
5387
     *
5388
     * @property {module:time~TimeRangeIndex} start
5389
     *           Returns the time offset at which a specified time range begins.
5390
     *
5391
     * @property {module:time~TimeRangeIndex} end
5392
     *           Returns the time offset at which a specified time range ends.
5393
     *
5394
     * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
5395
     */
5396
 
5397
    /**
5398
     * Check if any of the time ranges are over the maximum index.
5399
     *
5400
     * @private
5401
     * @param   {string} fnName
5402
     *          The function name to use for logging
5403
     *
5404
     * @param   {number} index
5405
     *          The index to check
5406
     *
5407
     * @param   {number} maxIndex
5408
     *          The maximum possible index
5409
     *
5410
     * @throws  {Error} if the timeRanges provided are over the maxIndex
5411
     */
5412
    function rangeCheck(fnName, index, maxIndex) {
5413
        if (typeof index !== 'number' || index < 0 || index > maxIndex) {
5414
            throw new Error(`Failed to execute '${fnName}' on 'TimeRanges': The index provided (${index}) is non-numeric or out of bounds (0-${maxIndex}).`);
5415
        }
5416
    }
5417
 
5418
    /**
5419
     * Get the time for the specified index at the start or end
5420
     * of a TimeRange object.
5421
     *
5422
     * @private
5423
     * @param      {string} fnName
5424
     *             The function name to use for logging
5425
     *
5426
     * @param      {string} valueIndex
5427
     *             The property that should be used to get the time. should be
5428
     *             'start' or 'end'
5429
     *
5430
     * @param      {Array} ranges
5431
     *             An array of time ranges
5432
     *
5433
     * @param      {Array} [rangeIndex=0]
5434
     *             The index to start the search at
5435
     *
5436
     * @return     {number}
5437
     *             The time that offset at the specified index.
5438
     *
5439
     * @deprecated rangeIndex must be set to a value, in the future this will throw an error.
5440
     * @throws     {Error} if rangeIndex is more than the length of ranges
5441
     */
5442
    function getRange(fnName, valueIndex, ranges, rangeIndex) {
5443
        rangeCheck(fnName, rangeIndex, ranges.length - 1);
5444
        return ranges[rangeIndex][valueIndex];
5445
    }
5446
 
5447
    /**
5448
     * Create a time range object given ranges of time.
5449
     *
5450
     * @private
5451
     * @param   {Array} [ranges]
5452
     *          An array of time ranges.
5453
     *
5454
     * @return  {TimeRange}
5455
     */
5456
    function createTimeRangesObj(ranges) {
5457
        let timeRangesObj;
5458
        if (ranges === undefined || ranges.length === 0) {
5459
            timeRangesObj = {
5460
                length: 0,
5461
                start() {
5462
                    throw new Error('This TimeRanges object is empty');
5463
                },
5464
                end() {
5465
                    throw new Error('This TimeRanges object is empty');
5466
                }
5467
            };
5468
        } else {
5469
            timeRangesObj = {
5470
                length: ranges.length,
5471
                start: getRange.bind(null, 'start', 0, ranges),
5472
                end: getRange.bind(null, 'end', 1, ranges)
5473
            };
5474
        }
5475
        if (window.Symbol && window.Symbol.iterator) {
5476
            timeRangesObj[window.Symbol.iterator] = () => (ranges || []).values();
5477
        }
5478
        return timeRangesObj;
5479
    }
5480
 
5481
    /**
5482
     * Create a `TimeRange` object which mimics an
5483
     * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}.
5484
     *
5485
     * @param {number|Array[]} start
5486
     *        The start of a single range (a number) or an array of ranges (an
5487
     *        array of arrays of two numbers each).
5488
     *
5489
     * @param {number} end
5490
     *        The end of a single range. Cannot be used with the array form of
5491
     *        the `start` argument.
5492
     *
5493
     * @return {TimeRange}
5494
     */
5495
    function createTimeRanges$1(start, end) {
5496
        if (Array.isArray(start)) {
5497
            return createTimeRangesObj(start);
5498
        } else if (start === undefined || end === undefined) {
5499
            return createTimeRangesObj();
5500
        }
5501
        return createTimeRangesObj([[start, end]]);
5502
    }
5503
 
5504
    /**
5505
     * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
5506
     * seconds) will force a number of leading zeros to cover the length of the
5507
     * guide.
5508
     *
5509
     * @private
5510
     * @param  {number} seconds
5511
     *         Number of seconds to be turned into a string
5512
     *
5513
     * @param  {number} guide
5514
     *         Number (in seconds) to model the string after
5515
     *
5516
     * @return {string}
5517
     *         Time formatted as H:MM:SS or M:SS
5518
     */
5519
    const defaultImplementation = function (seconds, guide) {
5520
        seconds = seconds < 0 ? 0 : seconds;
5521
        let s = Math.floor(seconds % 60);
5522
        let m = Math.floor(seconds / 60 % 60);
5523
        let h = Math.floor(seconds / 3600);
5524
        const gm = Math.floor(guide / 60 % 60);
5525
        const gh = Math.floor(guide / 3600);
5526
 
5527
        // handle invalid times
5528
        if (isNaN(seconds) || seconds === Infinity) {
5529
            // '-' is false for all relational operators (e.g. <, >=) so this setting
5530
            // will add the minimum number of fields specified by the guide
5531
            h = m = s = '-';
5532
        }
5533
 
5534
        // Check if we need to show hours
5535
        h = h > 0 || gh > 0 ? h + ':' : '';
5536
 
5537
        // If hours are showing, we may need to add a leading zero.
5538
        // Always show at least one digit of minutes.
5539
        m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':';
5540
 
5541
        // Check if leading zero is need for seconds
5542
        s = s < 10 ? '0' + s : s;
5543
        return h + m + s;
5544
    };
5545
 
5546
    // Internal pointer to the current implementation.
5547
    let implementation = defaultImplementation;
5548
 
5549
    /**
5550
     * Replaces the default formatTime implementation with a custom implementation.
5551
     *
5552
     * @param {Function} customImplementation
5553
     *        A function which will be used in place of the default formatTime
5554
     *        implementation. Will receive the current time in seconds and the
5555
     *        guide (in seconds) as arguments.
5556
     */
5557
    function setFormatTime(customImplementation) {
5558
        implementation = customImplementation;
5559
    }
5560
 
5561
    /**
5562
     * Resets formatTime to the default implementation.
5563
     */
5564
    function resetFormatTime() {
5565
        implementation = defaultImplementation;
5566
    }
5567
 
5568
    /**
5569
     * Delegates to either the default time formatting function or a custom
5570
     * function supplied via `setFormatTime`.
5571
     *
5572
     * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
5573
     * guide (in seconds) will force a number of leading zeros to cover the
5574
     * length of the guide.
5575
     *
5576
     * @example  formatTime(125, 600) === "02:05"
5577
     * @param    {number} seconds
5578
     *           Number of seconds to be turned into a string
5579
     *
5580
     * @param    {number} guide
5581
     *           Number (in seconds) to model the string after
5582
     *
5583
     * @return   {string}
5584
     *           Time formatted as H:MM:SS or M:SS
5585
     */
5586
    function formatTime(seconds, guide = seconds) {
5587
        return implementation(seconds, guide);
5588
    }
5589
 
5590
    var Time = /*#__PURE__*/Object.freeze({
5591
        __proto__: null,
5592
        createTimeRanges: createTimeRanges$1,
5593
        createTimeRange: createTimeRanges$1,
5594
        setFormatTime: setFormatTime,
5595
        resetFormatTime: resetFormatTime,
5596
        formatTime: formatTime
5597
    });
5598
 
5599
    /**
5600
     * @file buffer.js
5601
     * @module buffer
5602
     */
5603
 
5604
    /**
5605
     * Compute the percentage of the media that has been buffered.
5606
     *
5607
     * @param { import('./time').TimeRange } buffered
5608
     *        The current `TimeRanges` object representing buffered time ranges
5609
     *
5610
     * @param {number} duration
5611
     *        Total duration of the media
5612
     *
5613
     * @return {number}
5614
     *         Percent buffered of the total duration in decimal form.
5615
     */
5616
    function bufferedPercent(buffered, duration) {
5617
        let bufferedDuration = 0;
5618
        let start;
5619
        let end;
5620
        if (!duration) {
5621
            return 0;
5622
        }
5623
        if (!buffered || !buffered.length) {
5624
            buffered = createTimeRanges$1(0, 0);
5625
        }
5626
        for (let i = 0; i < buffered.length; i++) {
5627
            start = buffered.start(i);
5628
            end = buffered.end(i);
5629
 
5630
            // buffered end can be bigger than duration by a very small fraction
5631
            if (end > duration) {
5632
                end = duration;
5633
            }
5634
            bufferedDuration += end - start;
5635
        }
5636
        return bufferedDuration / duration;
5637
    }
5638
 
5639
    /**
5640
     * @file media-error.js
5641
     */
5642
 
5643
    /**
5644
     * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
5645
     *
5646
     * @param {number|string|Object|MediaError} value
5647
     *        This can be of multiple types:
5648
     *        - number: should be a standard error code
5649
     *        - string: an error message (the code will be 0)
5650
     *        - Object: arbitrary properties
5651
     *        - `MediaError` (native): used to populate a video.js `MediaError` object
5652
     *        - `MediaError` (video.js): will return itself if it's already a
5653
     *          video.js `MediaError` object.
5654
     *
5655
     * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
5656
     * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
5657
     *
5658
     * @class MediaError
5659
     */
5660
    function MediaError(value) {
5661
        // Allow redundant calls to this constructor to avoid having `instanceof`
5662
        // checks peppered around the code.
5663
        if (value instanceof MediaError) {
5664
            return value;
5665
        }
5666
        if (typeof value === 'number') {
5667
            this.code = value;
5668
        } else if (typeof value === 'string') {
5669
            // default code is zero, so this is a custom error
5670
            this.message = value;
5671
        } else if (isObject$1(value)) {
5672
            // We assign the `code` property manually because native `MediaError` objects
5673
            // do not expose it as an own/enumerable property of the object.
5674
            if (typeof value.code === 'number') {
5675
                this.code = value.code;
5676
            }
5677
            Object.assign(this, value);
5678
        }
5679
        if (!this.message) {
5680
            this.message = MediaError.defaultMessages[this.code] || '';
5681
        }
5682
    }
5683
 
5684
    /**
5685
     * The error code that refers two one of the defined `MediaError` types
5686
     *
5687
     * @type {Number}
5688
     */
5689
    MediaError.prototype.code = 0;
5690
 
5691
    /**
5692
     * An optional message that to show with the error. Message is not part of the HTML5
5693
     * video spec but allows for more informative custom errors.
5694
     *
5695
     * @type {String}
5696
     */
5697
    MediaError.prototype.message = '';
5698
 
5699
    /**
5700
     * An optional status code that can be set by plugins to allow even more detail about
5701
     * the error. For example a plugin might provide a specific HTTP status code and an
5702
     * error message for that code. Then when the plugin gets that error this class will
5703
     * know how to display an error message for it. This allows a custom message to show
5704
     * up on the `Player` error overlay.
5705
     *
5706
     * @type {Array}
5707
     */
5708
    MediaError.prototype.status = null;
5709
 
5710
    /**
5711
     * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
5712
     * specification listed under {@link MediaError} for more information.
5713
     *
5714
     * @enum {array}
5715
     * @readonly
5716
     * @property {string} 0 - MEDIA_ERR_CUSTOM
5717
     * @property {string} 1 - MEDIA_ERR_ABORTED
5718
     * @property {string} 2 - MEDIA_ERR_NETWORK
5719
     * @property {string} 3 - MEDIA_ERR_DECODE
5720
     * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
5721
     * @property {string} 5 - MEDIA_ERR_ENCRYPTED
5722
     */
5723
    MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
5724
 
5725
    /**
5726
     * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
5727
     *
5728
     * @type {Array}
5729
     * @constant
5730
     */
5731
    MediaError.defaultMessages = {
5732
        1: 'You aborted the media playback',
5733
        2: 'A network error caused the media download to fail part-way.',
5734
        3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
5735
        4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
5736
        5: 'The media is encrypted and we do not have the keys to decrypt it.'
5737
    };
5738
 
5739
    // Add types as properties on MediaError
5740
    // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
5741
    for (let errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
5742
        MediaError[MediaError.errorTypes[errNum]] = errNum;
5743
        // values should be accessible on both the class and instance
5744
        MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
5745
    }
5746
 
5747
    var tuple = SafeParseTuple;
5748
    function SafeParseTuple(obj, reviver) {
5749
        var json;
5750
        var error = null;
5751
        try {
5752
            json = JSON.parse(obj, reviver);
5753
        } catch (err) {
5754
            error = err;
5755
        }
5756
        return [error, json];
5757
    }
5758
 
5759
    /**
5760
     * Returns whether an object is `Promise`-like (i.e. has a `then` method).
5761
     *
5762
     * @param  {Object}  value
5763
     *         An object that may or may not be `Promise`-like.
5764
     *
5765
     * @return {boolean}
5766
     *         Whether or not the object is `Promise`-like.
5767
     */
5768
    function isPromise(value) {
5769
        return value !== undefined && value !== null && typeof value.then === 'function';
5770
    }
5771
 
5772
    /**
5773
     * Silence a Promise-like object.
5774
     *
5775
     * This is useful for avoiding non-harmful, but potentially confusing "uncaught
5776
     * play promise" rejection error messages.
5777
     *
5778
     * @param  {Object} value
5779
     *         An object that may or may not be `Promise`-like.
5780
     */
5781
    function silencePromise(value) {
5782
        if (isPromise(value)) {
5783
            value.then(null, e => {});
5784
        }
5785
    }
5786
 
5787
    /**
5788
     * @file text-track-list-converter.js Utilities for capturing text track state and
5789
     * re-creating tracks based on a capture.
5790
     *
5791
     * @module text-track-list-converter
5792
     */
5793
 
5794
    /**
5795
     * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
5796
     * represents the {@link TextTrack}'s state.
5797
     *
5798
     * @param {TextTrack} track
5799
     *        The text track to query.
5800
     *
5801
     * @return {Object}
5802
     *         A serializable javascript representation of the TextTrack.
5803
     * @private
5804
     */
5805
    const trackToJson_ = function (track) {
5806
        const ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce((acc, prop, i) => {
5807
            if (track[prop]) {
5808
                acc[prop] = track[prop];
5809
            }
5810
            return acc;
5811
        }, {
5812
            cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
5813
                return {
5814
                    startTime: cue.startTime,
5815
                    endTime: cue.endTime,
5816
                    text: cue.text,
5817
                    id: cue.id
5818
                };
5819
            })
5820
        });
5821
        return ret;
5822
    };
5823
 
5824
    /**
5825
     * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
5826
     * state of all {@link TextTrack}s currently configured. The return array is compatible with
5827
     * {@link text-track-list-converter:jsonToTextTracks}.
5828
     *
5829
     * @param { import('../tech/tech').default } tech
5830
     *        The tech object to query
5831
     *
5832
     * @return {Array}
5833
     *         A serializable javascript representation of the {@link Tech}s
5834
     *         {@link TextTrackList}.
5835
     */
5836
    const textTracksToJson = function (tech) {
5837
        const trackEls = tech.$$('track');
5838
        const trackObjs = Array.prototype.map.call(trackEls, t => t.track);
5839
        const tracks = Array.prototype.map.call(trackEls, function (trackEl) {
5840
            const json = trackToJson_(trackEl.track);
5841
            if (trackEl.src) {
5842
                json.src = trackEl.src;
5843
            }
5844
            return json;
5845
        });
5846
        return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
5847
            return trackObjs.indexOf(track) === -1;
5848
        }).map(trackToJson_));
5849
    };
5850
 
5851
    /**
5852
     * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
5853
     * object {@link TextTrack} representations.
5854
     *
5855
     * @param {Array} json
5856
     *        An array of `TextTrack` representation objects, like those that would be
5857
     *        produced by `textTracksToJson`.
5858
     *
5859
     * @param {Tech} tech
5860
     *        The `Tech` to create the `TextTrack`s on.
5861
     */
5862
    const jsonToTextTracks = function (json, tech) {
5863
        json.forEach(function (track) {
5864
            const addedTrack = tech.addRemoteTextTrack(track).track;
5865
            if (!track.src && track.cues) {
5866
                track.cues.forEach(cue => addedTrack.addCue(cue));
5867
            }
5868
        });
5869
        return tech.textTracks();
5870
    };
5871
    var textTrackConverter = {
5872
        textTracksToJson,
5873
        jsonToTextTracks,
5874
        trackToJson_
5875
    };
5876
 
5877
    /**
5878
     * @file modal-dialog.js
5879
     */
5880
    const MODAL_CLASS_NAME = 'vjs-modal-dialog';
5881
 
5882
    /**
5883
     * The `ModalDialog` displays over the video and its controls, which blocks
5884
     * interaction with the player until it is closed.
5885
     *
5886
     * Modal dialogs include a "Close" button and will close when that button
5887
     * is activated - or when ESC is pressed anywhere.
5888
     *
5889
     * @extends Component
5890
     */
5891
    class ModalDialog extends Component$1 {
5892
        /**
5893
         * Create an instance of this class.
5894
         *
5895
         * @param { import('./player').default } player
5896
         *        The `Player` that this class should be attached to.
5897
         *
5898
         * @param {Object} [options]
5899
         *        The key/value store of player options.
5900
         *
5901
         * @param { import('./utils/dom').ContentDescriptor} [options.content=undefined]
5902
         *        Provide customized content for this modal.
5903
         *
5904
         * @param {string} [options.description]
5905
         *        A text description for the modal, primarily for accessibility.
5906
         *
5907
         * @param {boolean} [options.fillAlways=false]
5908
         *        Normally, modals are automatically filled only the first time
5909
         *        they open. This tells the modal to refresh its content
5910
         *        every time it opens.
5911
         *
5912
         * @param {string} [options.label]
5913
         *        A text label for the modal, primarily for accessibility.
5914
         *
5915
         * @param {boolean} [options.pauseOnOpen=true]
5916
         *        If `true`, playback will will be paused if playing when
5917
         *        the modal opens, and resumed when it closes.
5918
         *
5919
         * @param {boolean} [options.temporary=true]
5920
         *        If `true`, the modal can only be opened once; it will be
5921
         *        disposed as soon as it's closed.
5922
         *
5923
         * @param {boolean} [options.uncloseable=false]
5924
         *        If `true`, the user will not be able to close the modal
5925
         *        through the UI in the normal ways. Programmatic closing is
5926
         *        still possible.
5927
         */
5928
        constructor(player, options) {
5929
            super(player, options);
5930
            this.handleKeyDown_ = e => this.handleKeyDown(e);
5931
            this.close_ = e => this.close(e);
5932
            this.opened_ = this.hasBeenOpened_ = this.hasBeenFilled_ = false;
5933
            this.closeable(!this.options_.uncloseable);
5934
            this.content(this.options_.content);
5935
 
5936
            // Make sure the contentEl is defined AFTER any children are initialized
5937
            // because we only want the contents of the modal in the contentEl
5938
            // (not the UI elements like the close button).
5939
            this.contentEl_ = createEl('div', {
5940
                className: `${MODAL_CLASS_NAME}-content`
5941
            }, {
5942
                role: 'document'
5943
            });
5944
            this.descEl_ = createEl('p', {
5945
                className: `${MODAL_CLASS_NAME}-description vjs-control-text`,
5946
                id: this.el().getAttribute('aria-describedby')
5947
            });
5948
            textContent(this.descEl_, this.description());
5949
            this.el_.appendChild(this.descEl_);
5950
            this.el_.appendChild(this.contentEl_);
5951
        }
5952
 
5953
        /**
5954
         * Create the `ModalDialog`'s DOM element
5955
         *
5956
         * @return {Element}
5957
         *         The DOM element that gets created.
5958
         */
5959
        createEl() {
5960
            return super.createEl('div', {
5961
                className: this.buildCSSClass(),
5962
                tabIndex: -1
5963
            }, {
5964
                'aria-describedby': `${this.id()}_description`,
5965
                'aria-hidden': 'true',
5966
                'aria-label': this.label(),
5967
                'role': 'dialog'
5968
            });
5969
        }
5970
        dispose() {
5971
            this.contentEl_ = null;
5972
            this.descEl_ = null;
5973
            this.previouslyActiveEl_ = null;
5974
            super.dispose();
5975
        }
5976
 
5977
        /**
5978
         * Builds the default DOM `className`.
5979
         *
5980
         * @return {string}
5981
         *         The DOM `className` for this object.
5982
         */
5983
        buildCSSClass() {
5984
            return `${MODAL_CLASS_NAME} vjs-hidden ${super.buildCSSClass()}`;
5985
        }
5986
 
5987
        /**
5988
         * Returns the label string for this modal. Primarily used for accessibility.
5989
         *
5990
         * @return {string}
5991
         *         the localized or raw label of this modal.
5992
         */
5993
        label() {
5994
            return this.localize(this.options_.label || 'Modal Window');
5995
        }
5996
 
5997
        /**
5998
         * Returns the description string for this modal. Primarily used for
5999
         * accessibility.
6000
         *
6001
         * @return {string}
6002
         *         The localized or raw description of this modal.
6003
         */
6004
        description() {
6005
            let desc = this.options_.description || this.localize('This is a modal window.');
6006
 
6007
            // Append a universal closeability message if the modal is closeable.
6008
            if (this.closeable()) {
6009
                desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
6010
            }
6011
            return desc;
6012
        }
6013
 
6014
        /**
6015
         * Opens the modal.
6016
         *
6017
         * @fires ModalDialog#beforemodalopen
6018
         * @fires ModalDialog#modalopen
6019
         */
6020
        open() {
6021
            if (!this.opened_) {
6022
                const player = this.player();
6023
 
6024
                /**
6025
                 * Fired just before a `ModalDialog` is opened.
6026
                 *
6027
                 * @event ModalDialog#beforemodalopen
6028
                 * @type {Event}
6029
                 */
6030
                this.trigger('beforemodalopen');
6031
                this.opened_ = true;
6032
 
6033
                // Fill content if the modal has never opened before and
6034
                // never been filled.
6035
                if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
6036
                    this.fill();
6037
                }
6038
 
6039
                // If the player was playing, pause it and take note of its previously
6040
                // playing state.
6041
                this.wasPlaying_ = !player.paused();
6042
                if (this.options_.pauseOnOpen && this.wasPlaying_) {
6043
                    player.pause();
6044
                }
6045
                this.on('keydown', this.handleKeyDown_);
6046
 
6047
                // Hide controls and note if they were enabled.
6048
                this.hadControls_ = player.controls();
6049
                player.controls(false);
6050
                this.show();
6051
                this.conditionalFocus_();
6052
                this.el().setAttribute('aria-hidden', 'false');
6053
 
6054
                /**
6055
                 * Fired just after a `ModalDialog` is opened.
6056
                 *
6057
                 * @event ModalDialog#modalopen
6058
                 * @type {Event}
6059
                 */
6060
                this.trigger('modalopen');
6061
                this.hasBeenOpened_ = true;
6062
            }
6063
        }
6064
 
6065
        /**
6066
         * If the `ModalDialog` is currently open or closed.
6067
         *
6068
         * @param  {boolean} [value]
6069
         *         If given, it will open (`true`) or close (`false`) the modal.
6070
         *
6071
         * @return {boolean}
6072
         *         the current open state of the modaldialog
6073
         */
6074
        opened(value) {
6075
            if (typeof value === 'boolean') {
6076
                this[value ? 'open' : 'close']();
6077
            }
6078
            return this.opened_;
6079
        }
6080
 
6081
        /**
6082
         * Closes the modal, does nothing if the `ModalDialog` is
6083
         * not open.
6084
         *
6085
         * @fires ModalDialog#beforemodalclose
6086
         * @fires ModalDialog#modalclose
6087
         */
6088
        close() {
6089
            if (!this.opened_) {
6090
                return;
6091
            }
6092
            const player = this.player();
6093
 
6094
            /**
6095
             * Fired just before a `ModalDialog` is closed.
6096
             *
6097
             * @event ModalDialog#beforemodalclose
6098
             * @type {Event}
6099
             */
6100
            this.trigger('beforemodalclose');
6101
            this.opened_ = false;
6102
            if (this.wasPlaying_ && this.options_.pauseOnOpen) {
6103
                player.play();
6104
            }
6105
            this.off('keydown', this.handleKeyDown_);
6106
            if (this.hadControls_) {
6107
                player.controls(true);
6108
            }
6109
            this.hide();
6110
            this.el().setAttribute('aria-hidden', 'true');
6111
 
6112
            /**
6113
             * Fired just after a `ModalDialog` is closed.
6114
             *
6115
             * @event ModalDialog#modalclose
6116
             * @type {Event}
6117
             */
6118
            this.trigger('modalclose');
6119
            this.conditionalBlur_();
6120
            if (this.options_.temporary) {
6121
                this.dispose();
6122
            }
6123
        }
6124
 
6125
        /**
6126
         * Check to see if the `ModalDialog` is closeable via the UI.
6127
         *
6128
         * @param  {boolean} [value]
6129
         *         If given as a boolean, it will set the `closeable` option.
6130
         *
6131
         * @return {boolean}
6132
         *         Returns the final value of the closable option.
6133
         */
6134
        closeable(value) {
6135
            if (typeof value === 'boolean') {
6136
                const closeable = this.closeable_ = !!value;
6137
                let close = this.getChild('closeButton');
6138
 
6139
                // If this is being made closeable and has no close button, add one.
6140
                if (closeable && !close) {
6141
                    // The close button should be a child of the modal - not its
6142
                    // content element, so temporarily change the content element.
6143
                    const temp = this.contentEl_;
6144
                    this.contentEl_ = this.el_;
6145
                    close = this.addChild('closeButton', {
6146
                        controlText: 'Close Modal Dialog'
6147
                    });
6148
                    this.contentEl_ = temp;
6149
                    this.on(close, 'close', this.close_);
6150
                }
6151
 
6152
                // If this is being made uncloseable and has a close button, remove it.
6153
                if (!closeable && close) {
6154
                    this.off(close, 'close', this.close_);
6155
                    this.removeChild(close);
6156
                    close.dispose();
6157
                }
6158
            }
6159
            return this.closeable_;
6160
        }
6161
 
6162
        /**
6163
         * Fill the modal's content element with the modal's "content" option.
6164
         * The content element will be emptied before this change takes place.
6165
         */
6166
        fill() {
6167
            this.fillWith(this.content());
6168
        }
6169
 
6170
        /**
6171
         * Fill the modal's content element with arbitrary content.
6172
         * The content element will be emptied before this change takes place.
6173
         *
6174
         * @fires ModalDialog#beforemodalfill
6175
         * @fires ModalDialog#modalfill
6176
         *
6177
         * @param { import('./utils/dom').ContentDescriptor} [content]
6178
         *        The same rules apply to this as apply to the `content` option.
6179
         */
6180
        fillWith(content) {
6181
            const contentEl = this.contentEl();
6182
            const parentEl = contentEl.parentNode;
6183
            const nextSiblingEl = contentEl.nextSibling;
6184
 
6185
            /**
6186
             * Fired just before a `ModalDialog` is filled with content.
6187
             *
6188
             * @event ModalDialog#beforemodalfill
6189
             * @type {Event}
6190
             */
6191
            this.trigger('beforemodalfill');
6192
            this.hasBeenFilled_ = true;
6193
 
6194
            // Detach the content element from the DOM before performing
6195
            // manipulation to avoid modifying the live DOM multiple times.
6196
            parentEl.removeChild(contentEl);
6197
            this.empty();
6198
            insertContent(contentEl, content);
6199
            /**
6200
             * Fired just after a `ModalDialog` is filled with content.
6201
             *
6202
             * @event ModalDialog#modalfill
6203
             * @type {Event}
6204
             */
6205
            this.trigger('modalfill');
6206
 
6207
            // Re-inject the re-filled content element.
6208
            if (nextSiblingEl) {
6209
                parentEl.insertBefore(contentEl, nextSiblingEl);
6210
            } else {
6211
                parentEl.appendChild(contentEl);
6212
            }
6213
 
6214
            // make sure that the close button is last in the dialog DOM
6215
            const closeButton = this.getChild('closeButton');
6216
            if (closeButton) {
6217
                parentEl.appendChild(closeButton.el_);
6218
            }
6219
        }
6220
 
6221
        /**
6222
         * Empties the content element. This happens anytime the modal is filled.
6223
         *
6224
         * @fires ModalDialog#beforemodalempty
6225
         * @fires ModalDialog#modalempty
6226
         */
6227
        empty() {
6228
            /**
6229
             * Fired just before a `ModalDialog` is emptied.
6230
             *
6231
             * @event ModalDialog#beforemodalempty
6232
             * @type {Event}
6233
             */
6234
            this.trigger('beforemodalempty');
6235
            emptyEl(this.contentEl());
6236
 
6237
            /**
6238
             * Fired just after a `ModalDialog` is emptied.
6239
             *
6240
             * @event ModalDialog#modalempty
6241
             * @type {Event}
6242
             */
6243
            this.trigger('modalempty');
6244
        }
6245
 
6246
        /**
6247
         * Gets or sets the modal content, which gets normalized before being
6248
         * rendered into the DOM.
6249
         *
6250
         * This does not update the DOM or fill the modal, but it is called during
6251
         * that process.
6252
         *
6253
         * @param  { import('./utils/dom').ContentDescriptor} [value]
6254
         *         If defined, sets the internal content value to be used on the
6255
         *         next call(s) to `fill`. This value is normalized before being
6256
         *         inserted. To "clear" the internal content value, pass `null`.
6257
         *
6258
         * @return { import('./utils/dom').ContentDescriptor}
6259
         *         The current content of the modal dialog
6260
         */
6261
        content(value) {
6262
            if (typeof value !== 'undefined') {
6263
                this.content_ = value;
6264
            }
6265
            return this.content_;
6266
        }
6267
 
6268
        /**
6269
         * conditionally focus the modal dialog if focus was previously on the player.
6270
         *
6271
         * @private
6272
         */
6273
        conditionalFocus_() {
6274
            const activeEl = document.activeElement;
6275
            const playerEl = this.player_.el_;
6276
            this.previouslyActiveEl_ = null;
6277
            if (playerEl.contains(activeEl) || playerEl === activeEl) {
6278
                this.previouslyActiveEl_ = activeEl;
6279
                this.focus();
6280
            }
6281
        }
6282
 
6283
        /**
6284
         * conditionally blur the element and refocus the last focused element
6285
         *
6286
         * @private
6287
         */
6288
        conditionalBlur_() {
6289
            if (this.previouslyActiveEl_) {
6290
                this.previouslyActiveEl_.focus();
6291
                this.previouslyActiveEl_ = null;
6292
            }
6293
        }
6294
 
6295
        /**
6296
         * Keydown handler. Attached when modal is focused.
6297
         *
6298
         * @listens keydown
6299
         */
6300
        handleKeyDown(event) {
6301
            // Do not allow keydowns to reach out of the modal dialog.
6302
            event.stopPropagation();
6303
            if (keycode.isEventKey(event, 'Escape') && this.closeable()) {
6304
                event.preventDefault();
6305
                this.close();
6306
                return;
6307
            }
6308
 
6309
            // exit early if it isn't a tab key
6310
            if (!keycode.isEventKey(event, 'Tab')) {
6311
                return;
6312
            }
6313
            const focusableEls = this.focusableEls_();
6314
            const activeEl = this.el_.querySelector(':focus');
6315
            let focusIndex;
6316
            for (let i = 0; i < focusableEls.length; i++) {
6317
                if (activeEl === focusableEls[i]) {
6318
                    focusIndex = i;
6319
                    break;
6320
                }
6321
            }
6322
            if (document.activeElement === this.el_) {
6323
                focusIndex = 0;
6324
            }
6325
            if (event.shiftKey && focusIndex === 0) {
6326
                focusableEls[focusableEls.length - 1].focus();
6327
                event.preventDefault();
6328
            } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
6329
                focusableEls[0].focus();
6330
                event.preventDefault();
6331
            }
6332
        }
6333
 
6334
        /**
6335
         * get all focusable elements
6336
         *
6337
         * @private
6338
         */
6339
        focusableEls_() {
6340
            const allChildren = this.el_.querySelectorAll('*');
6341
            return Array.prototype.filter.call(allChildren, child => {
6342
                return (child instanceof window.HTMLAnchorElement || child instanceof window.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window.HTMLInputElement || child instanceof window.HTMLSelectElement || child instanceof window.HTMLTextAreaElement || child instanceof window.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window.HTMLIFrameElement || child instanceof window.HTMLObjectElement || child instanceof window.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable');
6343
            });
6344
        }
6345
    }
6346
 
6347
    /**
6348
     * Default options for `ModalDialog` default options.
6349
     *
6350
     * @type {Object}
6351
     * @private
6352
     */
6353
    ModalDialog.prototype.options_ = {
6354
        pauseOnOpen: true,
6355
        temporary: true
6356
    };
6357
    Component$1.registerComponent('ModalDialog', ModalDialog);
6358
 
6359
    /**
6360
     * @file track-list.js
6361
     */
6362
 
6363
    /**
6364
     * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
6365
     * {@link VideoTrackList}
6366
     *
6367
     * @extends EventTarget
6368
     */
6369
    class TrackList extends EventTarget$2 {
6370
        /**
6371
         * Create an instance of this class
6372
         *
6373
         * @param { import('./track').default[] } tracks
6374
         *        A list of tracks to initialize the list with.
6375
         *
6376
         * @abstract
6377
         */
6378
        constructor(tracks = []) {
6379
            super();
6380
            this.tracks_ = [];
6381
 
6382
            /**
6383
             * @memberof TrackList
6384
             * @member {number} length
6385
             *         The current number of `Track`s in the this Trackist.
6386
             * @instance
6387
             */
6388
            Object.defineProperty(this, 'length', {
6389
                get() {
6390
                    return this.tracks_.length;
6391
                }
6392
            });
6393
            for (let i = 0; i < tracks.length; i++) {
6394
                this.addTrack(tracks[i]);
6395
            }
6396
        }
6397
 
6398
        /**
6399
         * Add a {@link Track} to the `TrackList`
6400
         *
6401
         * @param { import('./track').default } track
6402
         *        The audio, video, or text track to add to the list.
6403
         *
6404
         * @fires TrackList#addtrack
6405
         */
6406
        addTrack(track) {
6407
            const index = this.tracks_.length;
6408
            if (!('' + index in this)) {
6409
                Object.defineProperty(this, index, {
6410
                    get() {
6411
                        return this.tracks_[index];
6412
                    }
6413
                });
6414
            }
6415
 
6416
            // Do not add duplicate tracks
6417
            if (this.tracks_.indexOf(track) === -1) {
6418
                this.tracks_.push(track);
6419
                /**
6420
                 * Triggered when a track is added to a track list.
6421
                 *
6422
                 * @event TrackList#addtrack
6423
                 * @type {Event}
6424
                 * @property {Track} track
6425
                 *           A reference to track that was added.
6426
                 */
6427
                this.trigger({
6428
                    track,
6429
                    type: 'addtrack',
6430
                    target: this
6431
                });
6432
            }
6433
 
6434
            /**
6435
             * Triggered when a track label is changed.
6436
             *
6437
             * @event TrackList#addtrack
6438
             * @type {Event}
6439
             * @property {Track} track
6440
             *           A reference to track that was added.
6441
             */
6442
            track.labelchange_ = () => {
6443
                this.trigger({
6444
                    track,
6445
                    type: 'labelchange',
6446
                    target: this
6447
                });
6448
            };
6449
            if (isEvented(track)) {
6450
                track.addEventListener('labelchange', track.labelchange_);
6451
            }
6452
        }
6453
 
6454
        /**
6455
         * Remove a {@link Track} from the `TrackList`
6456
         *
6457
         * @param { import('./track').default } rtrack
6458
         *        The audio, video, or text track to remove from the list.
6459
         *
6460
         * @fires TrackList#removetrack
6461
         */
6462
        removeTrack(rtrack) {
6463
            let track;
6464
            for (let i = 0, l = this.length; i < l; i++) {
6465
                if (this[i] === rtrack) {
6466
                    track = this[i];
6467
                    if (track.off) {
6468
                        track.off();
6469
                    }
6470
                    this.tracks_.splice(i, 1);
6471
                    break;
6472
                }
6473
            }
6474
            if (!track) {
6475
                return;
6476
            }
6477
 
6478
            /**
6479
             * Triggered when a track is removed from track list.
6480
             *
6481
             * @event TrackList#removetrack
6482
             * @type {Event}
6483
             * @property {Track} track
6484
             *           A reference to track that was removed.
6485
             */
6486
            this.trigger({
6487
                track,
6488
                type: 'removetrack',
6489
                target: this
6490
            });
6491
        }
6492
 
6493
        /**
6494
         * Get a Track from the TrackList by a tracks id
6495
         *
6496
         * @param {string} id - the id of the track to get
6497
         * @method getTrackById
6498
         * @return { import('./track').default }
6499
         * @private
6500
         */
6501
        getTrackById(id) {
6502
            let result = null;
6503
            for (let i = 0, l = this.length; i < l; i++) {
6504
                const track = this[i];
6505
                if (track.id === id) {
6506
                    result = track;
6507
                    break;
6508
                }
6509
            }
6510
            return result;
6511
        }
6512
    }
6513
 
6514
    /**
6515
     * Triggered when a different track is selected/enabled.
6516
     *
6517
     * @event TrackList#change
6518
     * @type {Event}
6519
     */
6520
 
6521
    /**
6522
     * Events that can be called with on + eventName. See {@link EventHandler}.
6523
     *
6524
     * @property {Object} TrackList#allowedEvents_
6525
     * @protected
6526
     */
6527
    TrackList.prototype.allowedEvents_ = {
6528
        change: 'change',
6529
        addtrack: 'addtrack',
6530
        removetrack: 'removetrack',
6531
        labelchange: 'labelchange'
6532
    };
6533
 
6534
    // emulate attribute EventHandler support to allow for feature detection
6535
    for (const event in TrackList.prototype.allowedEvents_) {
6536
        TrackList.prototype['on' + event] = null;
6537
    }
6538
 
6539
    /**
6540
     * @file audio-track-list.js
6541
     */
6542
 
6543
    /**
6544
     * Anywhere we call this function we diverge from the spec
6545
     * as we only support one enabled audiotrack at a time
6546
     *
6547
     * @param {AudioTrackList} list
6548
     *        list to work on
6549
     *
6550
     * @param { import('./audio-track').default } track
6551
     *        The track to skip
6552
     *
6553
     * @private
6554
     */
6555
    const disableOthers$1 = function (list, track) {
6556
        for (let i = 0; i < list.length; i++) {
6557
            if (!Object.keys(list[i]).length || track.id === list[i].id) {
6558
                continue;
6559
            }
6560
            // another audio track is enabled, disable it
6561
            list[i].enabled = false;
6562
        }
6563
    };
6564
 
6565
    /**
6566
     * The current list of {@link AudioTrack} for a media file.
6567
     *
6568
     * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
6569
     * @extends TrackList
6570
     */
6571
    class AudioTrackList extends TrackList {
6572
        /**
6573
         * Create an instance of this class.
6574
         *
6575
         * @param { import('./audio-track').default[] } [tracks=[]]
6576
         *        A list of `AudioTrack` to instantiate the list with.
6577
         */
6578
        constructor(tracks = []) {
6579
            // make sure only 1 track is enabled
6580
            // sorted from last index to first index
6581
            for (let i = tracks.length - 1; i >= 0; i--) {
6582
                if (tracks[i].enabled) {
6583
                    disableOthers$1(tracks, tracks[i]);
6584
                    break;
6585
                }
6586
            }
6587
            super(tracks);
6588
            this.changing_ = false;
6589
        }
6590
 
6591
        /**
6592
         * Add an {@link AudioTrack} to the `AudioTrackList`.
6593
         *
6594
         * @param { import('./audio-track').default } track
6595
         *        The AudioTrack to add to the list
6596
         *
6597
         * @fires TrackList#addtrack
6598
         */
6599
        addTrack(track) {
6600
            if (track.enabled) {
6601
                disableOthers$1(this, track);
6602
            }
6603
            super.addTrack(track);
6604
            // native tracks don't have this
6605
            if (!track.addEventListener) {
6606
                return;
6607
            }
6608
            track.enabledChange_ = () => {
6609
                // when we are disabling other tracks (since we don't support
6610
                // more than one track at a time) we will set changing_
6611
                // to true so that we don't trigger additional change events
6612
                if (this.changing_) {
6613
                    return;
6614
                }
6615
                this.changing_ = true;
6616
                disableOthers$1(this, track);
6617
                this.changing_ = false;
6618
                this.trigger('change');
6619
            };
6620
 
6621
            /**
6622
             * @listens AudioTrack#enabledchange
6623
             * @fires TrackList#change
6624
             */
6625
            track.addEventListener('enabledchange', track.enabledChange_);
6626
        }
6627
        removeTrack(rtrack) {
6628
            super.removeTrack(rtrack);
6629
            if (rtrack.removeEventListener && rtrack.enabledChange_) {
6630
                rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
6631
                rtrack.enabledChange_ = null;
6632
            }
6633
        }
6634
    }
6635
 
6636
    /**
6637
     * @file video-track-list.js
6638
     */
6639
 
6640
    /**
6641
     * Un-select all other {@link VideoTrack}s that are selected.
6642
     *
6643
     * @param {VideoTrackList} list
6644
     *        list to work on
6645
     *
6646
     * @param { import('./video-track').default } track
6647
     *        The track to skip
6648
     *
6649
     * @private
6650
     */
6651
    const disableOthers = function (list, track) {
6652
        for (let i = 0; i < list.length; i++) {
6653
            if (!Object.keys(list[i]).length || track.id === list[i].id) {
6654
                continue;
6655
            }
6656
            // another video track is enabled, disable it
6657
            list[i].selected = false;
6658
        }
6659
    };
6660
 
6661
    /**
6662
     * The current list of {@link VideoTrack} for a video.
6663
     *
6664
     * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
6665
     * @extends TrackList
6666
     */
6667
    class VideoTrackList extends TrackList {
6668
        /**
6669
         * Create an instance of this class.
6670
         *
6671
         * @param {VideoTrack[]} [tracks=[]]
6672
         *        A list of `VideoTrack` to instantiate the list with.
6673
         */
6674
        constructor(tracks = []) {
6675
            // make sure only 1 track is enabled
6676
            // sorted from last index to first index
6677
            for (let i = tracks.length - 1; i >= 0; i--) {
6678
                if (tracks[i].selected) {
6679
                    disableOthers(tracks, tracks[i]);
6680
                    break;
6681
                }
6682
            }
6683
            super(tracks);
6684
            this.changing_ = false;
6685
 
6686
            /**
6687
             * @member {number} VideoTrackList#selectedIndex
6688
             *         The current index of the selected {@link VideoTrack`}.
6689
             */
6690
            Object.defineProperty(this, 'selectedIndex', {
6691
                get() {
6692
                    for (let i = 0; i < this.length; i++) {
6693
                        if (this[i].selected) {
6694
                            return i;
6695
                        }
6696
                    }
6697
                    return -1;
6698
                },
6699
                set() {}
6700
            });
6701
        }
6702
 
6703
        /**
6704
         * Add a {@link VideoTrack} to the `VideoTrackList`.
6705
         *
6706
         * @param { import('./video-track').default } track
6707
         *        The VideoTrack to add to the list
6708
         *
6709
         * @fires TrackList#addtrack
6710
         */
6711
        addTrack(track) {
6712
            if (track.selected) {
6713
                disableOthers(this, track);
6714
            }
6715
            super.addTrack(track);
6716
            // native tracks don't have this
6717
            if (!track.addEventListener) {
6718
                return;
6719
            }
6720
            track.selectedChange_ = () => {
6721
                if (this.changing_) {
6722
                    return;
6723
                }
6724
                this.changing_ = true;
6725
                disableOthers(this, track);
6726
                this.changing_ = false;
6727
                this.trigger('change');
6728
            };
6729
 
6730
            /**
6731
             * @listens VideoTrack#selectedchange
6732
             * @fires TrackList#change
6733
             */
6734
            track.addEventListener('selectedchange', track.selectedChange_);
6735
        }
6736
        removeTrack(rtrack) {
6737
            super.removeTrack(rtrack);
6738
            if (rtrack.removeEventListener && rtrack.selectedChange_) {
6739
                rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
6740
                rtrack.selectedChange_ = null;
6741
            }
6742
        }
6743
    }
6744
 
6745
    /**
6746
     * @file text-track-list.js
6747
     */
6748
 
6749
    /**
6750
     * The current list of {@link TextTrack} for a media file.
6751
     *
6752
     * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
6753
     * @extends TrackList
6754
     */
6755
    class TextTrackList extends TrackList {
6756
        /**
6757
         * Add a {@link TextTrack} to the `TextTrackList`
6758
         *
6759
         * @param { import('./text-track').default } track
6760
         *        The text track to add to the list.
6761
         *
6762
         * @fires TrackList#addtrack
6763
         */
6764
        addTrack(track) {
6765
            super.addTrack(track);
6766
            if (!this.queueChange_) {
6767
                this.queueChange_ = () => this.queueTrigger('change');
6768
            }
6769
            if (!this.triggerSelectedlanguagechange) {
6770
                this.triggerSelectedlanguagechange_ = () => this.trigger('selectedlanguagechange');
6771
            }
6772
 
6773
            /**
6774
             * @listens TextTrack#modechange
6775
             * @fires TrackList#change
6776
             */
6777
            track.addEventListener('modechange', this.queueChange_);
6778
            const nonLanguageTextTrackKind = ['metadata', 'chapters'];
6779
            if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
6780
                track.addEventListener('modechange', this.triggerSelectedlanguagechange_);
6781
            }
6782
        }
6783
        removeTrack(rtrack) {
6784
            super.removeTrack(rtrack);
6785
 
6786
            // manually remove the event handlers we added
6787
            if (rtrack.removeEventListener) {
6788
                if (this.queueChange_) {
6789
                    rtrack.removeEventListener('modechange', this.queueChange_);
6790
                }
6791
                if (this.selectedlanguagechange_) {
6792
                    rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_);
6793
                }
6794
            }
6795
        }
6796
    }
6797
 
6798
    /**
6799
     * @file html-track-element-list.js
6800
     */
6801
 
6802
    /**
6803
     * The current list of {@link HtmlTrackElement}s.
6804
     */
6805
    class HtmlTrackElementList {
6806
        /**
6807
         * Create an instance of this class.
6808
         *
6809
         * @param {HtmlTrackElement[]} [tracks=[]]
6810
         *        A list of `HtmlTrackElement` to instantiate the list with.
6811
         */
6812
        constructor(trackElements = []) {
6813
            this.trackElements_ = [];
6814
 
6815
            /**
6816
             * @memberof HtmlTrackElementList
6817
             * @member {number} length
6818
             *         The current number of `Track`s in the this Trackist.
6819
             * @instance
6820
             */
6821
            Object.defineProperty(this, 'length', {
6822
                get() {
6823
                    return this.trackElements_.length;
6824
                }
6825
            });
6826
            for (let i = 0, length = trackElements.length; i < length; i++) {
6827
                this.addTrackElement_(trackElements[i]);
6828
            }
6829
        }
6830
 
6831
        /**
6832
         * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
6833
         *
6834
         * @param {HtmlTrackElement} trackElement
6835
         *        The track element to add to the list.
6836
         *
6837
         * @private
6838
         */
6839
        addTrackElement_(trackElement) {
6840
            const index = this.trackElements_.length;
6841
            if (!('' + index in this)) {
6842
                Object.defineProperty(this, index, {
6843
                    get() {
6844
                        return this.trackElements_[index];
6845
                    }
6846
                });
6847
            }
6848
 
6849
            // Do not add duplicate elements
6850
            if (this.trackElements_.indexOf(trackElement) === -1) {
6851
                this.trackElements_.push(trackElement);
6852
            }
6853
        }
6854
 
6855
        /**
6856
         * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
6857
         * {@link TextTrack}.
6858
         *
6859
         * @param {TextTrack} track
6860
         *        The track associated with a track element.
6861
         *
6862
         * @return {HtmlTrackElement|undefined}
6863
         *         The track element that was found or undefined.
6864
         *
6865
         * @private
6866
         */
6867
        getTrackElementByTrack_(track) {
6868
            let trackElement_;
6869
            for (let i = 0, length = this.trackElements_.length; i < length; i++) {
6870
                if (track === this.trackElements_[i].track) {
6871
                    trackElement_ = this.trackElements_[i];
6872
                    break;
6873
                }
6874
            }
6875
            return trackElement_;
6876
        }
6877
 
6878
        /**
6879
         * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
6880
         *
6881
         * @param {HtmlTrackElement} trackElement
6882
         *        The track element to remove from the list.
6883
         *
6884
         * @private
6885
         */
6886
        removeTrackElement_(trackElement) {
6887
            for (let i = 0, length = this.trackElements_.length; i < length; i++) {
6888
                if (trackElement === this.trackElements_[i]) {
6889
                    if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') {
6890
                        this.trackElements_[i].track.off();
6891
                    }
6892
                    if (typeof this.trackElements_[i].off === 'function') {
6893
                        this.trackElements_[i].off();
6894
                    }
6895
                    this.trackElements_.splice(i, 1);
6896
                    break;
6897
                }
6898
            }
6899
        }
6900
    }
6901
 
6902
    /**
6903
     * @file text-track-cue-list.js
6904
     */
6905
 
6906
    /**
6907
     * @typedef {Object} TextTrackCueList~TextTrackCue
6908
     *
6909
     * @property {string} id
6910
     *           The unique id for this text track cue
6911
     *
6912
     * @property {number} startTime
6913
     *           The start time for this text track cue
6914
     *
6915
     * @property {number} endTime
6916
     *           The end time for this text track cue
6917
     *
6918
     * @property {boolean} pauseOnExit
6919
     *           Pause when the end time is reached if true.
6920
     *
6921
     * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
6922
     */
6923
 
6924
    /**
6925
     * A List of TextTrackCues.
6926
     *
6927
     * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
6928
     */
6929
    class TextTrackCueList {
6930
        /**
6931
         * Create an instance of this class..
6932
         *
6933
         * @param {Array} cues
6934
         *        A list of cues to be initialized with
6935
         */
6936
        constructor(cues) {
6937
            TextTrackCueList.prototype.setCues_.call(this, cues);
6938
 
6939
            /**
6940
             * @memberof TextTrackCueList
6941
             * @member {number} length
6942
             *         The current number of `TextTrackCue`s in the TextTrackCueList.
6943
             * @instance
6944
             */
6945
            Object.defineProperty(this, 'length', {
6946
                get() {
6947
                    return this.length_;
6948
                }
6949
            });
6950
        }
6951
 
6952
        /**
6953
         * A setter for cues in this list. Creates getters
6954
         * an an index for the cues.
6955
         *
6956
         * @param {Array} cues
6957
         *        An array of cues to set
6958
         *
6959
         * @private
6960
         */
6961
        setCues_(cues) {
6962
            const oldLength = this.length || 0;
6963
            let i = 0;
6964
            const l = cues.length;
6965
            this.cues_ = cues;
6966
            this.length_ = cues.length;
6967
            const defineProp = function (index) {
6968
                if (!('' + index in this)) {
6969
                    Object.defineProperty(this, '' + index, {
6970
                        get() {
6971
                            return this.cues_[index];
6972
                        }
6973
                    });
6974
                }
6975
            };
6976
            if (oldLength < l) {
6977
                i = oldLength;
6978
                for (; i < l; i++) {
6979
                    defineProp.call(this, i);
6980
                }
6981
            }
6982
        }
6983
 
6984
        /**
6985
         * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
6986
         *
6987
         * @param {string} id
6988
         *        The id of the cue that should be searched for.
6989
         *
6990
         * @return {TextTrackCueList~TextTrackCue|null}
6991
         *         A single cue or null if none was found.
6992
         */
6993
        getCueById(id) {
6994
            let result = null;
6995
            for (let i = 0, l = this.length; i < l; i++) {
6996
                const cue = this[i];
6997
                if (cue.id === id) {
6998
                    result = cue;
6999
                    break;
7000
                }
7001
            }
7002
            return result;
7003
        }
7004
    }
7005
 
7006
    /**
7007
     * @file track-kinds.js
7008
     */
7009
 
7010
    /**
7011
     * All possible `VideoTrackKind`s
7012
     *
7013
     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
7014
     * @typedef VideoTrack~Kind
7015
     * @enum
7016
     */
7017
    const VideoTrackKind = {
7018
        alternative: 'alternative',
7019
        captions: 'captions',
7020
        main: 'main',
7021
        sign: 'sign',
7022
        subtitles: 'subtitles',
7023
        commentary: 'commentary'
7024
    };
7025
 
7026
    /**
7027
     * All possible `AudioTrackKind`s
7028
     *
7029
     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
7030
     * @typedef AudioTrack~Kind
7031
     * @enum
7032
     */
7033
    const AudioTrackKind = {
7034
        'alternative': 'alternative',
7035
        'descriptions': 'descriptions',
7036
        'main': 'main',
7037
        'main-desc': 'main-desc',
7038
        'translation': 'translation',
7039
        'commentary': 'commentary'
7040
    };
7041
 
7042
    /**
7043
     * All possible `TextTrackKind`s
7044
     *
7045
     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
7046
     * @typedef TextTrack~Kind
7047
     * @enum
7048
     */
7049
    const TextTrackKind = {
7050
        subtitles: 'subtitles',
7051
        captions: 'captions',
7052
        descriptions: 'descriptions',
7053
        chapters: 'chapters',
7054
        metadata: 'metadata'
7055
    };
7056
 
7057
    /**
7058
     * All possible `TextTrackMode`s
7059
     *
7060
     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
7061
     * @typedef TextTrack~Mode
7062
     * @enum
7063
     */
7064
    const TextTrackMode = {
7065
        disabled: 'disabled',
7066
        hidden: 'hidden',
7067
        showing: 'showing'
7068
    };
7069
 
7070
    /**
7071
     * @file track.js
7072
     */
7073
 
7074
    /**
7075
     * A Track class that contains all of the common functionality for {@link AudioTrack},
7076
     * {@link VideoTrack}, and {@link TextTrack}.
7077
     *
7078
     * > Note: This class should not be used directly
7079
     *
7080
     * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
7081
     * @extends EventTarget
7082
     * @abstract
7083
     */
7084
    class Track extends EventTarget$2 {
7085
        /**
7086
         * Create an instance of this class.
7087
         *
7088
         * @param {Object} [options={}]
7089
         *        Object of option names and values
7090
         *
7091
         * @param {string} [options.kind='']
7092
         *        A valid kind for the track type you are creating.
7093
         *
7094
         * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7095
         *        A unique id for this AudioTrack.
7096
         *
7097
         * @param {string} [options.label='']
7098
         *        The menu label for this track.
7099
         *
7100
         * @param {string} [options.language='']
7101
         *        A valid two character language code.
7102
         *
7103
         * @abstract
7104
         */
7105
        constructor(options = {}) {
7106
            super();
7107
            const trackProps = {
7108
                id: options.id || 'vjs_track_' + newGUID(),
7109
                kind: options.kind || '',
7110
                language: options.language || ''
7111
            };
7112
            let label = options.label || '';
7113
 
7114
            /**
7115
             * @memberof Track
7116
             * @member {string} id
7117
             *         The id of this track. Cannot be changed after creation.
7118
             * @instance
7119
             *
7120
             * @readonly
7121
             */
7122
 
7123
            /**
7124
             * @memberof Track
7125
             * @member {string} kind
7126
             *         The kind of track that this is. Cannot be changed after creation.
7127
             * @instance
7128
             *
7129
             * @readonly
7130
             */
7131
 
7132
            /**
7133
             * @memberof Track
7134
             * @member {string} language
7135
             *         The two letter language code for this track. Cannot be changed after
7136
             *         creation.
7137
             * @instance
7138
             *
7139
             * @readonly
7140
             */
7141
 
7142
            for (const key in trackProps) {
7143
                Object.defineProperty(this, key, {
7144
                    get() {
7145
                        return trackProps[key];
7146
                    },
7147
                    set() {}
7148
                });
7149
            }
7150
 
7151
            /**
7152
             * @memberof Track
7153
             * @member {string} label
7154
             *         The label of this track. Cannot be changed after creation.
7155
             * @instance
7156
             *
7157
             * @fires Track#labelchange
7158
             */
7159
            Object.defineProperty(this, 'label', {
7160
                get() {
7161
                    return label;
7162
                },
7163
                set(newLabel) {
7164
                    if (newLabel !== label) {
7165
                        label = newLabel;
7166
 
7167
                        /**
7168
                         * An event that fires when label changes on this track.
7169
                         *
7170
                         * > Note: This is not part of the spec!
7171
                         *
7172
                         * @event Track#labelchange
7173
                         * @type {Event}
7174
                         */
7175
                        this.trigger('labelchange');
7176
                    }
7177
                }
7178
            });
7179
        }
7180
    }
7181
 
7182
    /**
7183
     * @file url.js
7184
     * @module url
7185
     */
7186
 
7187
    /**
7188
     * @typedef {Object} url:URLObject
7189
     *
7190
     * @property {string} protocol
7191
     *           The protocol of the url that was parsed.
7192
     *
7193
     * @property {string} hostname
7194
     *           The hostname of the url that was parsed.
7195
     *
7196
     * @property {string} port
7197
     *           The port of the url that was parsed.
7198
     *
7199
     * @property {string} pathname
7200
     *           The pathname of the url that was parsed.
7201
     *
7202
     * @property {string} search
7203
     *           The search query of the url that was parsed.
7204
     *
7205
     * @property {string} hash
7206
     *           The hash of the url that was parsed.
7207
     *
7208
     * @property {string} host
7209
     *           The host of the url that was parsed.
7210
     */
7211
 
7212
    /**
7213
     * Resolve and parse the elements of a URL.
7214
     *
7215
     * @function
7216
     * @param    {String} url
7217
     *           The url to parse
7218
     *
7219
     * @return   {url:URLObject}
7220
     *           An object of url details
7221
     */
7222
    const parseUrl = function (url) {
7223
        // This entire method can be replace with URL once we are able to drop IE11
7224
 
7225
        const props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host'];
7226
 
7227
        // add the url to an anchor and let the browser parse the URL
7228
        const a = document.createElement('a');
7229
        a.href = url;
7230
 
7231
        // Copy the specific URL properties to a new object
7232
        // This is also needed for IE because the anchor loses its
7233
        // properties when it's removed from the dom
7234
        const details = {};
7235
        for (let i = 0; i < props.length; i++) {
7236
            details[props[i]] = a[props[i]];
7237
        }
7238
 
7239
        // IE adds the port to the host property unlike everyone else. If
7240
        // a port identifier is added for standard ports, strip it.
7241
        if (details.protocol === 'http:') {
7242
            details.host = details.host.replace(/:80$/, '');
7243
        }
7244
        if (details.protocol === 'https:') {
7245
            details.host = details.host.replace(/:443$/, '');
7246
        }
7247
        if (!details.protocol) {
7248
            details.protocol = window.location.protocol;
7249
        }
7250
 
7251
        /* istanbul ignore if */
7252
        if (!details.host) {
7253
            details.host = window.location.host;
7254
        }
7255
        return details;
7256
    };
7257
 
7258
    /**
7259
     * Get absolute version of relative URL.
7260
     *
7261
     * @function
7262
     * @param    {string} url
7263
     *           URL to make absolute
7264
     *
7265
     * @return   {string}
7266
     *           Absolute URL
7267
     *
7268
     * @see      http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
7269
     */
7270
    const getAbsoluteURL = function (url) {
7271
        // Check if absolute URL
7272
        if (!url.match(/^https?:\/\//)) {
7273
            // Add the url to an anchor and let the browser parse it to convert to an absolute url
7274
            const a = document.createElement('a');
7275
            a.href = url;
7276
            url = a.href;
7277
        }
7278
        return url;
7279
    };
7280
 
7281
    /**
7282
     * Returns the extension of the passed file name. It will return an empty string
7283
     * if passed an invalid path.
7284
     *
7285
     * @function
7286
     * @param    {string} path
7287
     *           The fileName path like '/path/to/file.mp4'
7288
     *
7289
     * @return  {string}
7290
     *           The extension in lower case or an empty string if no
7291
     *           extension could be found.
7292
     */
7293
    const getFileExtension = function (path) {
7294
        if (typeof path === 'string') {
7295
            const splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/;
7296
            const pathParts = splitPathRe.exec(path);
7297
            if (pathParts) {
7298
                return pathParts.pop().toLowerCase();
7299
            }
7300
        }
7301
        return '';
7302
    };
7303
 
7304
    /**
7305
     * Returns whether the url passed is a cross domain request or not.
7306
     *
7307
     * @function
7308
     * @param    {string} url
7309
     *           The url to check.
7310
     *
7311
     * @param    {Object} [winLoc]
7312
     *           the domain to check the url against, defaults to window.location
7313
     *
7314
     * @param    {string} [winLoc.protocol]
7315
     *           The window location protocol defaults to window.location.protocol
7316
     *
7317
     * @param    {string} [winLoc.host]
7318
     *           The window location host defaults to window.location.host
7319
     *
7320
     * @return   {boolean}
7321
     *           Whether it is a cross domain request or not.
7322
     */
7323
    const isCrossOrigin = function (url, winLoc = window.location) {
7324
        const urlInfo = parseUrl(url);
7325
 
7326
        // IE8 protocol relative urls will return ':' for protocol
7327
        const srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol;
7328
 
7329
        // Check if url is for another domain/origin
7330
        // IE8 doesn't know location.origin, so we won't rely on it here
7331
        const crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
7332
        return crossOrigin;
7333
    };
7334
 
7335
    var Url = /*#__PURE__*/Object.freeze({
7336
        __proto__: null,
7337
        parseUrl: parseUrl,
7338
        getAbsoluteURL: getAbsoluteURL,
7339
        getFileExtension: getFileExtension,
7340
        isCrossOrigin: isCrossOrigin
7341
    });
7342
 
7343
    var win;
7344
    if (typeof window !== "undefined") {
7345
        win = window;
7346
    } else if (typeof commonjsGlobal !== "undefined") {
7347
        win = commonjsGlobal;
7348
    } else if (typeof self !== "undefined") {
7349
        win = self;
7350
    } else {
7351
        win = {};
7352
    }
7353
    var window_1 = win;
7354
 
7355
    var _extends_1 = createCommonjsModule(function (module) {
7356
        function _extends() {
7357
            module.exports = _extends = Object.assign ? Object.assign.bind() : function (target) {
7358
                for (var i = 1; i < arguments.length; i++) {
7359
                    var source = arguments[i];
7360
                    for (var key in source) {
7361
                        if (Object.prototype.hasOwnProperty.call(source, key)) {
7362
                            target[key] = source[key];
7363
                        }
7364
                    }
7365
                }
7366
                return target;
7367
            }, module.exports.__esModule = true, module.exports["default"] = module.exports;
7368
            return _extends.apply(this, arguments);
7369
        }
7370
        module.exports = _extends, module.exports.__esModule = true, module.exports["default"] = module.exports;
7371
    });
7372
    var _extends$1 = unwrapExports(_extends_1);
7373
 
7374
    var isFunction_1 = isFunction;
7375
    var toString = Object.prototype.toString;
7376
    function isFunction(fn) {
7377
        if (!fn) {
7378
            return false;
7379
        }
7380
        var string = toString.call(fn);
7381
        return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && (
7382
            // IE8 and below
7383
            fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt);
7384
    }
7385
 
7386
    var httpResponseHandler = function httpResponseHandler(callback, decodeResponseBody) {
7387
        if (decodeResponseBody === void 0) {
7388
            decodeResponseBody = false;
7389
        }
7390
        return function (err, response, responseBody) {
7391
            // if the XHR failed, return that error
7392
            if (err) {
7393
                callback(err);
7394
                return;
7395
            } // if the HTTP status code is 4xx or 5xx, the request also failed
7396
 
7397
            if (response.statusCode >= 400 && response.statusCode <= 599) {
7398
                var cause = responseBody;
7399
                if (decodeResponseBody) {
7400
                    if (window_1.TextDecoder) {
7401
                        var charset = getCharset(response.headers && response.headers['content-type']);
7402
                        try {
7403
                            cause = new TextDecoder(charset).decode(responseBody);
7404
                        } catch (e) {}
7405
                    } else {
7406
                        cause = String.fromCharCode.apply(null, new Uint8Array(responseBody));
7407
                    }
7408
                }
7409
                callback({
7410
                    cause: cause
7411
                });
7412
                return;
7413
            } // otherwise, request succeeded
7414
 
7415
            callback(null, responseBody);
7416
        };
7417
    };
7418
    function getCharset(contentTypeHeader) {
7419
        if (contentTypeHeader === void 0) {
7420
            contentTypeHeader = '';
7421
        }
7422
        return contentTypeHeader.toLowerCase().split(';').reduce(function (charset, contentType) {
7423
            var _contentType$split = contentType.split('='),
7424
                type = _contentType$split[0],
7425
                value = _contentType$split[1];
7426
            if (type.trim() === 'charset') {
7427
                return value.trim();
7428
            }
7429
            return charset;
7430
        }, 'utf-8');
7431
    }
7432
    var httpHandler = httpResponseHandler;
7433
 
7434
    createXHR.httpHandler = httpHandler;
7435
    /**
7436
     * @license
7437
     * slighly modified parse-headers 2.0.2 <https://github.com/kesla/parse-headers/>
7438
     * Copyright (c) 2014 David Björklund
7439
     * Available under the MIT license
7440
     * <https://github.com/kesla/parse-headers/blob/master/LICENCE>
7441
     */
7442
 
7443
    var parseHeaders = function parseHeaders(headers) {
7444
        var result = {};
7445
        if (!headers) {
7446
            return result;
7447
        }
7448
        headers.trim().split('\n').forEach(function (row) {
7449
            var index = row.indexOf(':');
7450
            var key = row.slice(0, index).trim().toLowerCase();
7451
            var value = row.slice(index + 1).trim();
7452
            if (typeof result[key] === 'undefined') {
7453
                result[key] = value;
7454
            } else if (Array.isArray(result[key])) {
7455
                result[key].push(value);
7456
            } else {
7457
                result[key] = [result[key], value];
7458
            }
7459
        });
7460
        return result;
7461
    };
7462
    var lib = createXHR; // Allow use of default import syntax in TypeScript
7463
 
7464
    var default_1 = createXHR;
7465
    createXHR.XMLHttpRequest = window_1.XMLHttpRequest || noop$1;
7466
    createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window_1.XDomainRequest;
7467
    forEachArray(["get", "put", "post", "patch", "head", "delete"], function (method) {
7468
        createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
7469
            options = initParams(uri, options, callback);
7470
            options.method = method.toUpperCase();
7471
            return _createXHR(options);
7472
        };
7473
    });
7474
    function forEachArray(array, iterator) {
7475
        for (var i = 0; i < array.length; i++) {
7476
            iterator(array[i]);
7477
        }
7478
    }
7479
    function isEmpty(obj) {
7480
        for (var i in obj) {
7481
            if (obj.hasOwnProperty(i)) return false;
7482
        }
7483
        return true;
7484
    }
7485
    function initParams(uri, options, callback) {
7486
        var params = uri;
7487
        if (isFunction_1(options)) {
7488
            callback = options;
7489
            if (typeof uri === "string") {
7490
                params = {
7491
                    uri: uri
7492
                };
7493
            }
7494
        } else {
7495
            params = _extends_1({}, options, {
7496
                uri: uri
7497
            });
7498
        }
7499
        params.callback = callback;
7500
        return params;
7501
    }
7502
    function createXHR(uri, options, callback) {
7503
        options = initParams(uri, options, callback);
7504
        return _createXHR(options);
7505
    }
7506
    function _createXHR(options) {
7507
        if (typeof options.callback === "undefined") {
7508
            throw new Error("callback argument missing");
7509
        }
7510
        var called = false;
7511
        var callback = function cbOnce(err, response, body) {
7512
            if (!called) {
7513
                called = true;
7514
                options.callback(err, response, body);
7515
            }
7516
        };
7517
        function readystatechange() {
7518
            if (xhr.readyState === 4) {
7519
                setTimeout(loadFunc, 0);
7520
            }
7521
        }
7522
        function getBody() {
7523
            // Chrome with requestType=blob throws errors arround when even testing access to responseText
7524
            var body = undefined;
7525
            if (xhr.response) {
7526
                body = xhr.response;
7527
            } else {
7528
                body = xhr.responseText || getXml(xhr);
7529
            }
7530
            if (isJson) {
7531
                try {
7532
                    body = JSON.parse(body);
7533
                } catch (e) {}
7534
            }
7535
            return body;
7536
        }
7537
        function errorFunc(evt) {
7538
            clearTimeout(timeoutTimer);
7539
            if (!(evt instanceof Error)) {
7540
                evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
7541
            }
7542
            evt.statusCode = 0;
7543
            return callback(evt, failureResponse);
7544
        } // will load the data & process the response in a special response object
7545
 
7546
        function loadFunc() {
7547
            if (aborted) return;
7548
            var status;
7549
            clearTimeout(timeoutTimer);
7550
            if (options.useXDR && xhr.status === undefined) {
7551
                //IE8 CORS GET successful response doesn't have a status field, but body is fine
7552
                status = 200;
7553
            } else {
7554
                status = xhr.status === 1223 ? 204 : xhr.status;
7555
            }
7556
            var response = failureResponse;
7557
            var err = null;
7558
            if (status !== 0) {
7559
                response = {
7560
                    body: getBody(),
7561
                    statusCode: status,
7562
                    method: method,
7563
                    headers: {},
7564
                    url: uri,
7565
                    rawRequest: xhr
7566
                };
7567
                if (xhr.getAllResponseHeaders) {
7568
                    //remember xhr can in fact be XDR for CORS in IE
7569
                    response.headers = parseHeaders(xhr.getAllResponseHeaders());
7570
                }
7571
            } else {
7572
                err = new Error("Internal XMLHttpRequest Error");
7573
            }
7574
            return callback(err, response, response.body);
7575
        }
7576
        var xhr = options.xhr || null;
7577
        if (!xhr) {
7578
            if (options.cors || options.useXDR) {
7579
                xhr = new createXHR.XDomainRequest();
7580
            } else {
7581
                xhr = new createXHR.XMLHttpRequest();
7582
            }
7583
        }
7584
        var key;
7585
        var aborted;
7586
        var uri = xhr.url = options.uri || options.url;
7587
        var method = xhr.method = options.method || "GET";
7588
        var body = options.body || options.data;
7589
        var headers = xhr.headers = options.headers || {};
7590
        var sync = !!options.sync;
7591
        var isJson = false;
7592
        var timeoutTimer;
7593
        var failureResponse = {
7594
            body: undefined,
7595
            headers: {},
7596
            statusCode: 0,
7597
            method: method,
7598
            url: uri,
7599
            rawRequest: xhr
7600
        };
7601
        if ("json" in options && options.json !== false) {
7602
            isJson = true;
7603
            headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
7604
 
7605
            if (method !== "GET" && method !== "HEAD") {
7606
                headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
7607
 
7608
                body = JSON.stringify(options.json === true ? body : options.json);
7609
            }
7610
        }
7611
        xhr.onreadystatechange = readystatechange;
7612
        xhr.onload = loadFunc;
7613
        xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function.
7614
 
7615
        xhr.onprogress = function () {// IE must die
7616
        };
7617
        xhr.onabort = function () {
7618
            aborted = true;
7619
        };
7620
        xhr.ontimeout = errorFunc;
7621
        xhr.open(method, uri, !sync, options.username, options.password); //has to be after open
7622
 
7623
        if (!sync) {
7624
            xhr.withCredentials = !!options.withCredentials;
7625
        } // Cannot set timeout with sync request
7626
        // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
7627
        // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
7628
 
7629
        if (!sync && options.timeout > 0) {
7630
            timeoutTimer = setTimeout(function () {
7631
                if (aborted) return;
7632
                aborted = true; //IE9 may still call readystatechange
7633
 
7634
                xhr.abort("timeout");
7635
                var e = new Error("XMLHttpRequest timeout");
7636
                e.code = "ETIMEDOUT";
7637
                errorFunc(e);
7638
            }, options.timeout);
7639
        }
7640
        if (xhr.setRequestHeader) {
7641
            for (key in headers) {
7642
                if (headers.hasOwnProperty(key)) {
7643
                    xhr.setRequestHeader(key, headers[key]);
7644
                }
7645
            }
7646
        } else if (options.headers && !isEmpty(options.headers)) {
7647
            throw new Error("Headers cannot be set on an XDomainRequest object");
7648
        }
7649
        if ("responseType" in options) {
7650
            xhr.responseType = options.responseType;
7651
        }
7652
        if ("beforeSend" in options && typeof options.beforeSend === "function") {
7653
            options.beforeSend(xhr);
7654
        } // Microsoft Edge browser sends "undefined" when send is called with undefined value.
7655
        // XMLHttpRequest spec says to pass null as body to indicate no body
7656
        // See https://github.com/naugtur/xhr/issues/100.
7657
 
7658
        xhr.send(body || null);
7659
        return xhr;
7660
    }
7661
    function getXml(xhr) {
7662
        // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException"
7663
        // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML.
7664
        try {
7665
            if (xhr.responseType === "document") {
7666
                return xhr.responseXML;
7667
            }
7668
            var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
7669
            if (xhr.responseType === "" && !firefoxBugTakenEffect) {
7670
                return xhr.responseXML;
7671
            }
7672
        } catch (e) {}
7673
        return null;
7674
    }
7675
    function noop$1() {}
7676
    lib.default = default_1;
7677
 
7678
    /**
7679
     * @file text-track.js
7680
     */
7681
 
7682
    /**
7683
     * Takes a webvtt file contents and parses it into cues
7684
     *
7685
     * @param {string} srcContent
7686
     *        webVTT file contents
7687
     *
7688
     * @param {TextTrack} track
7689
     *        TextTrack to add cues to. Cues come from the srcContent.
7690
     *
7691
     * @private
7692
     */
7693
    const parseCues = function (srcContent, track) {
7694
        const parser = new window.WebVTT.Parser(window, window.vttjs, window.WebVTT.StringDecoder());
7695
        const errors = [];
7696
        parser.oncue = function (cue) {
7697
            track.addCue(cue);
7698
        };
7699
        parser.onparsingerror = function (error) {
7700
            errors.push(error);
7701
        };
7702
        parser.onflush = function () {
7703
            track.trigger({
7704
                type: 'loadeddata',
7705
                target: track
7706
            });
7707
        };
7708
        parser.parse(srcContent);
7709
        if (errors.length > 0) {
7710
            if (window.console && window.console.groupCollapsed) {
7711
                window.console.groupCollapsed(`Text Track parsing errors for ${track.src}`);
7712
            }
7713
            errors.forEach(error => log$1.error(error));
7714
            if (window.console && window.console.groupEnd) {
7715
                window.console.groupEnd();
7716
            }
7717
        }
7718
        parser.flush();
7719
    };
7720
 
7721
    /**
7722
     * Load a `TextTrack` from a specified url.
7723
     *
7724
     * @param {string} src
7725
     *        Url to load track from.
7726
     *
7727
     * @param {TextTrack} track
7728
     *        Track to add cues to. Comes from the content at the end of `url`.
7729
     *
7730
     * @private
7731
     */
7732
    const loadTrack = function (src, track) {
7733
        const opts = {
7734
            uri: src
7735
        };
7736
        const crossOrigin = isCrossOrigin(src);
7737
        if (crossOrigin) {
7738
            opts.cors = crossOrigin;
7739
        }
7740
        const withCredentials = track.tech_.crossOrigin() === 'use-credentials';
7741
        if (withCredentials) {
7742
            opts.withCredentials = withCredentials;
7743
        }
7744
        lib(opts, bind_(this, function (err, response, responseBody) {
7745
            if (err) {
7746
                return log$1.error(err, response);
7747
            }
7748
            track.loaded_ = true;
7749
 
7750
            // Make sure that vttjs has loaded, otherwise, wait till it finished loading
7751
            // NOTE: this is only used for the alt/video.novtt.js build
7752
            if (typeof window.WebVTT !== 'function') {
7753
                if (track.tech_) {
7754
                    // to prevent use before define eslint error, we define loadHandler
7755
                    // as a let here
7756
                    track.tech_.any(['vttjsloaded', 'vttjserror'], event => {
7757
                        if (event.type === 'vttjserror') {
7758
                            log$1.error(`vttjs failed to load, stopping trying to process ${track.src}`);
7759
                            return;
7760
                        }
7761
                        return parseCues(responseBody, track);
7762
                    });
7763
                }
7764
            } else {
7765
                parseCues(responseBody, track);
7766
            }
7767
        }));
7768
    };
7769
 
7770
    /**
7771
     * A representation of a single `TextTrack`.
7772
     *
7773
     * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
7774
     * @extends Track
7775
     */
7776
    class TextTrack extends Track {
7777
        /**
7778
         * Create an instance of this class.
7779
         *
7780
         * @param {Object} options={}
7781
         *        Object of option names and values
7782
         *
7783
         * @param { import('../tech/tech').default } options.tech
7784
         *        A reference to the tech that owns this TextTrack.
7785
         *
7786
         * @param {TextTrack~Kind} [options.kind='subtitles']
7787
         *        A valid text track kind.
7788
         *
7789
         * @param {TextTrack~Mode} [options.mode='disabled']
7790
         *        A valid text track mode.
7791
         *
7792
         * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7793
         *        A unique id for this TextTrack.
7794
         *
7795
         * @param {string} [options.label='']
7796
         *        The menu label for this track.
7797
         *
7798
         * @param {string} [options.language='']
7799
         *        A valid two character language code.
7800
         *
7801
         * @param {string} [options.srclang='']
7802
         *        A valid two character language code. An alternative, but deprioritized
7803
         *        version of `options.language`
7804
         *
7805
         * @param {string} [options.src]
7806
         *        A url to TextTrack cues.
7807
         *
7808
         * @param {boolean} [options.default]
7809
         *        If this track should default to on or off.
7810
         */
7811
        constructor(options = {}) {
7812
            if (!options.tech) {
7813
                throw new Error('A tech was not provided.');
7814
            }
7815
            const settings = merge$2(options, {
7816
                kind: TextTrackKind[options.kind] || 'subtitles',
7817
                language: options.language || options.srclang || ''
7818
            });
7819
            let mode = TextTrackMode[settings.mode] || 'disabled';
7820
            const default_ = settings.default;
7821
            if (settings.kind === 'metadata' || settings.kind === 'chapters') {
7822
                mode = 'hidden';
7823
            }
7824
            super(settings);
7825
            this.tech_ = settings.tech;
7826
            this.cues_ = [];
7827
            this.activeCues_ = [];
7828
            this.preload_ = this.tech_.preloadTextTracks !== false;
7829
            const cues = new TextTrackCueList(this.cues_);
7830
            const activeCues = new TextTrackCueList(this.activeCues_);
7831
            let changed = false;
7832
            this.timeupdateHandler = bind_(this, function (event = {}) {
7833
                if (this.tech_.isDisposed()) {
7834
                    return;
7835
                }
7836
                if (!this.tech_.isReady_) {
7837
                    if (event.type !== 'timeupdate') {
7838
                        this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
7839
                    }
7840
                    return;
7841
                }
7842
 
7843
                // Accessing this.activeCues for the side-effects of updating itself
7844
                // due to its nature as a getter function. Do not remove or cues will
7845
                // stop updating!
7846
                // Use the setter to prevent deletion from uglify (pure_getters rule)
7847
                this.activeCues = this.activeCues;
7848
                if (changed) {
7849
                    this.trigger('cuechange');
7850
                    changed = false;
7851
                }
7852
                if (event.type !== 'timeupdate') {
7853
                    this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
7854
                }
7855
            });
7856
            const disposeHandler = () => {
7857
                this.stopTracking();
7858
            };
7859
            this.tech_.one('dispose', disposeHandler);
7860
            if (mode !== 'disabled') {
7861
                this.startTracking();
7862
            }
7863
            Object.defineProperties(this, {
7864
                /**
7865
                 * @memberof TextTrack
7866
                 * @member {boolean} default
7867
                 *         If this track was set to be on or off by default. Cannot be changed after
7868
                 *         creation.
7869
                 * @instance
7870
                 *
7871
                 * @readonly
7872
                 */
7873
                default: {
7874
                    get() {
7875
                        return default_;
7876
                    },
7877
                    set() {}
7878
                },
7879
                /**
7880
                 * @memberof TextTrack
7881
                 * @member {string} mode
7882
                 *         Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
7883
                 *         not be set if setting to an invalid mode.
7884
                 * @instance
7885
                 *
7886
                 * @fires TextTrack#modechange
7887
                 */
7888
                mode: {
7889
                    get() {
7890
                        return mode;
7891
                    },
7892
                    set(newMode) {
7893
                        if (!TextTrackMode[newMode]) {
7894
                            return;
7895
                        }
7896
                        if (mode === newMode) {
7897
                            return;
7898
                        }
7899
                        mode = newMode;
7900
                        if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) {
7901
                            // On-demand load.
7902
                            loadTrack(this.src, this);
7903
                        }
7904
                        this.stopTracking();
7905
                        if (mode !== 'disabled') {
7906
                            this.startTracking();
7907
                        }
7908
                        /**
7909
                         * An event that fires when mode changes on this track. This allows
7910
                         * the TextTrackList that holds this track to act accordingly.
7911
                         *
7912
                         * > Note: This is not part of the spec!
7913
                         *
7914
                         * @event TextTrack#modechange
7915
                         * @type {Event}
7916
                         */
7917
                        this.trigger('modechange');
7918
                    }
7919
                },
7920
                /**
7921
                 * @memberof TextTrack
7922
                 * @member {TextTrackCueList} cues
7923
                 *         The text track cue list for this TextTrack.
7924
                 * @instance
7925
                 */
7926
                cues: {
7927
                    get() {
7928
                        if (!this.loaded_) {
7929
                            return null;
7930
                        }
7931
                        return cues;
7932
                    },
7933
                    set() {}
7934
                },
7935
                /**
7936
                 * @memberof TextTrack
7937
                 * @member {TextTrackCueList} activeCues
7938
                 *         The list text track cues that are currently active for this TextTrack.
7939
                 * @instance
7940
                 */
7941
                activeCues: {
7942
                    get() {
7943
                        if (!this.loaded_) {
7944
                            return null;
7945
                        }
7946
 
7947
                        // nothing to do
7948
                        if (this.cues.length === 0) {
7949
                            return activeCues;
7950
                        }
7951
                        const ct = this.tech_.currentTime();
7952
                        const active = [];
7953
                        for (let i = 0, l = this.cues.length; i < l; i++) {
7954
                            const cue = this.cues[i];
7955
                            if (cue.startTime <= ct && cue.endTime >= ct) {
7956
                                active.push(cue);
7957
                            }
7958
                        }
7959
                        changed = false;
7960
                        if (active.length !== this.activeCues_.length) {
7961
                            changed = true;
7962
                        } else {
7963
                            for (let i = 0; i < active.length; i++) {
7964
                                if (this.activeCues_.indexOf(active[i]) === -1) {
7965
                                    changed = true;
7966
                                }
7967
                            }
7968
                        }
7969
                        this.activeCues_ = active;
7970
                        activeCues.setCues_(this.activeCues_);
7971
                        return activeCues;
7972
                    },
7973
                    // /!\ Keep this setter empty (see the timeupdate handler above)
7974
                    set() {}
7975
                }
7976
            });
7977
            if (settings.src) {
7978
                this.src = settings.src;
7979
                if (!this.preload_) {
7980
                    // Tracks will load on-demand.
7981
                    // Act like we're loaded for other purposes.
7982
                    this.loaded_ = true;
7983
                }
7984
                if (this.preload_ || settings.kind !== 'subtitles' && settings.kind !== 'captions') {
7985
                    loadTrack(this.src, this);
7986
                }
7987
            } else {
7988
                this.loaded_ = true;
7989
            }
7990
        }
7991
        startTracking() {
7992
            // More precise cues based on requestVideoFrameCallback with a requestAnimationFram fallback
7993
            this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
7994
            // Also listen to timeupdate in case rVFC/rAF stops (window in background, audio in video el)
7995
            this.tech_.on('timeupdate', this.timeupdateHandler);
7996
        }
7997
        stopTracking() {
7998
            if (this.rvf_) {
7999
                this.tech_.cancelVideoFrameCallback(this.rvf_);
8000
                this.rvf_ = undefined;
8001
            }
8002
            this.tech_.off('timeupdate', this.timeupdateHandler);
8003
        }
8004
 
8005
        /**
8006
         * Add a cue to the internal list of cues.
8007
         *
8008
         * @param {TextTrack~Cue} cue
8009
         *        The cue to add to our internal list
8010
         */
8011
        addCue(originalCue) {
8012
            let cue = originalCue;
8013
 
8014
            // Testing if the cue is a VTTCue in a way that survives minification
8015
            if (!('getCueAsHTML' in cue)) {
8016
                cue = new window.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
8017
                for (const prop in originalCue) {
8018
                    if (!(prop in cue)) {
8019
                        cue[prop] = originalCue[prop];
8020
                    }
8021
                }
8022
 
8023
                // make sure that `id` is copied over
8024
                cue.id = originalCue.id;
8025
                cue.originalCue_ = originalCue;
8026
            }
8027
            const tracks = this.tech_.textTracks();
8028
            for (let i = 0; i < tracks.length; i++) {
8029
                if (tracks[i] !== this) {
8030
                    tracks[i].removeCue(cue);
8031
                }
8032
            }
8033
            this.cues_.push(cue);
8034
            this.cues.setCues_(this.cues_);
8035
        }
8036
 
8037
        /**
8038
         * Remove a cue from our internal list
8039
         *
8040
         * @param {TextTrack~Cue} removeCue
8041
         *        The cue to remove from our internal list
8042
         */
8043
        removeCue(removeCue) {
8044
            let i = this.cues_.length;
8045
            while (i--) {
8046
                const cue = this.cues_[i];
8047
                if (cue === removeCue || cue.originalCue_ && cue.originalCue_ === removeCue) {
8048
                    this.cues_.splice(i, 1);
8049
                    this.cues.setCues_(this.cues_);
8050
                    break;
8051
                }
8052
            }
8053
        }
8054
    }
8055
 
8056
    /**
8057
     * cuechange - One or more cues in the track have become active or stopped being active.
8058
     * @protected
8059
     */
8060
    TextTrack.prototype.allowedEvents_ = {
8061
        cuechange: 'cuechange'
8062
    };
8063
 
8064
    /**
8065
     * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
8066
     * only one `AudioTrack` in the list will be enabled at a time.
8067
     *
8068
     * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
8069
     * @extends Track
8070
     */
8071
    class AudioTrack extends Track {
8072
        /**
8073
         * Create an instance of this class.
8074
         *
8075
         * @param {Object} [options={}]
8076
         *        Object of option names and values
8077
         *
8078
         * @param {AudioTrack~Kind} [options.kind='']
8079
         *        A valid audio track kind
8080
         *
8081
         * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8082
         *        A unique id for this AudioTrack.
8083
         *
8084
         * @param {string} [options.label='']
8085
         *        The menu label for this track.
8086
         *
8087
         * @param {string} [options.language='']
8088
         *        A valid two character language code.
8089
         *
8090
         * @param {boolean} [options.enabled]
8091
         *        If this track is the one that is currently playing. If this track is part of
8092
         *        an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
8093
         */
8094
        constructor(options = {}) {
8095
            const settings = merge$2(options, {
8096
                kind: AudioTrackKind[options.kind] || ''
8097
            });
8098
            super(settings);
8099
            let enabled = false;
8100
 
8101
            /**
8102
             * @memberof AudioTrack
8103
             * @member {boolean} enabled
8104
             *         If this `AudioTrack` is enabled or not. When setting this will
8105
             *         fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
8106
             * @instance
8107
             *
8108
             * @fires VideoTrack#selectedchange
8109
             */
8110
            Object.defineProperty(this, 'enabled', {
8111
                get() {
8112
                    return enabled;
8113
                },
8114
                set(newEnabled) {
8115
                    // an invalid or unchanged value
8116
                    if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
8117
                        return;
8118
                    }
8119
                    enabled = newEnabled;
8120
 
8121
                    /**
8122
                     * An event that fires when enabled changes on this track. This allows
8123
                     * the AudioTrackList that holds this track to act accordingly.
8124
                     *
8125
                     * > Note: This is not part of the spec! Native tracks will do
8126
                     *         this internally without an event.
8127
                     *
8128
                     * @event AudioTrack#enabledchange
8129
                     * @type {Event}
8130
                     */
8131
                    this.trigger('enabledchange');
8132
                }
8133
            });
8134
 
8135
            // if the user sets this track to selected then
8136
            // set selected to that true value otherwise
8137
            // we keep it false
8138
            if (settings.enabled) {
8139
                this.enabled = settings.enabled;
8140
            }
8141
            this.loaded_ = true;
8142
        }
8143
    }
8144
 
8145
    /**
8146
     * A representation of a single `VideoTrack`.
8147
     *
8148
     * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
8149
     * @extends Track
8150
     */
8151
    class VideoTrack extends Track {
8152
        /**
8153
         * Create an instance of this class.
8154
         *
8155
         * @param {Object} [options={}]
8156
         *        Object of option names and values
8157
         *
8158
         * @param {string} [options.kind='']
8159
         *        A valid {@link VideoTrack~Kind}
8160
         *
8161
         * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8162
         *        A unique id for this AudioTrack.
8163
         *
8164
         * @param {string} [options.label='']
8165
         *        The menu label for this track.
8166
         *
8167
         * @param {string} [options.language='']
8168
         *        A valid two character language code.
8169
         *
8170
         * @param {boolean} [options.selected]
8171
         *        If this track is the one that is currently playing.
8172
         */
8173
        constructor(options = {}) {
8174
            const settings = merge$2(options, {
8175
                kind: VideoTrackKind[options.kind] || ''
8176
            });
8177
            super(settings);
8178
            let selected = false;
8179
 
8180
            /**
8181
             * @memberof VideoTrack
8182
             * @member {boolean} selected
8183
             *         If this `VideoTrack` is selected or not. When setting this will
8184
             *         fire {@link VideoTrack#selectedchange} if the state of selected changed.
8185
             * @instance
8186
             *
8187
             * @fires VideoTrack#selectedchange
8188
             */
8189
            Object.defineProperty(this, 'selected', {
8190
                get() {
8191
                    return selected;
8192
                },
8193
                set(newSelected) {
8194
                    // an invalid or unchanged value
8195
                    if (typeof newSelected !== 'boolean' || newSelected === selected) {
8196
                        return;
8197
                    }
8198
                    selected = newSelected;
8199
 
8200
                    /**
8201
                     * An event that fires when selected changes on this track. This allows
8202
                     * the VideoTrackList that holds this track to act accordingly.
8203
                     *
8204
                     * > Note: This is not part of the spec! Native tracks will do
8205
                     *         this internally without an event.
8206
                     *
8207
                     * @event VideoTrack#selectedchange
8208
                     * @type {Event}
8209
                     */
8210
                    this.trigger('selectedchange');
8211
                }
8212
            });
8213
 
8214
            // if the user sets this track to selected then
8215
            // set selected to that true value otherwise
8216
            // we keep it false
8217
            if (settings.selected) {
8218
                this.selected = settings.selected;
8219
            }
8220
        }
8221
    }
8222
 
8223
    /**
8224
     * @file html-track-element.js
8225
     */
8226
 
8227
    /**
8228
     * A single track represented in the DOM.
8229
     *
8230
     * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
8231
     * @extends EventTarget
8232
     */
8233
    class HTMLTrackElement extends EventTarget$2 {
8234
        /**
8235
         * Create an instance of this class.
8236
         *
8237
         * @param {Object} options={}
8238
         *        Object of option names and values
8239
         *
8240
         * @param { import('../tech/tech').default } options.tech
8241
         *        A reference to the tech that owns this HTMLTrackElement.
8242
         *
8243
         * @param {TextTrack~Kind} [options.kind='subtitles']
8244
         *        A valid text track kind.
8245
         *
8246
         * @param {TextTrack~Mode} [options.mode='disabled']
8247
         *        A valid text track mode.
8248
         *
8249
         * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
8250
         *        A unique id for this TextTrack.
8251
         *
8252
         * @param {string} [options.label='']
8253
         *        The menu label for this track.
8254
         *
8255
         * @param {string} [options.language='']
8256
         *        A valid two character language code.
8257
         *
8258
         * @param {string} [options.srclang='']
8259
         *        A valid two character language code. An alternative, but deprioritized
8260
         *        version of `options.language`
8261
         *
8262
         * @param {string} [options.src]
8263
         *        A url to TextTrack cues.
8264
         *
8265
         * @param {boolean} [options.default]
8266
         *        If this track should default to on or off.
8267
         */
8268
        constructor(options = {}) {
8269
            super();
8270
            let readyState;
8271
            const track = new TextTrack(options);
8272
            this.kind = track.kind;
8273
            this.src = track.src;
8274
            this.srclang = track.language;
8275
            this.label = track.label;
8276
            this.default = track.default;
8277
            Object.defineProperties(this, {
8278
                /**
8279
                 * @memberof HTMLTrackElement
8280
                 * @member {HTMLTrackElement~ReadyState} readyState
8281
                 *         The current ready state of the track element.
8282
                 * @instance
8283
                 */
8284
                readyState: {
8285
                    get() {
8286
                        return readyState;
8287
                    }
8288
                },
8289
                /**
8290
                 * @memberof HTMLTrackElement
8291
                 * @member {TextTrack} track
8292
                 *         The underlying TextTrack object.
8293
                 * @instance
8294
                 *
8295
                 */
8296
                track: {
8297
                    get() {
8298
                        return track;
8299
                    }
8300
                }
8301
            });
8302
            readyState = HTMLTrackElement.NONE;
8303
 
8304
            /**
8305
             * @listens TextTrack#loadeddata
8306
             * @fires HTMLTrackElement#load
8307
             */
8308
            track.addEventListener('loadeddata', () => {
8309
                readyState = HTMLTrackElement.LOADED;
8310
                this.trigger({
8311
                    type: 'load',
8312
                    target: this
8313
                });
8314
            });
8315
        }
8316
    }
8317
 
8318
    /**
8319
     * @protected
8320
     */
8321
    HTMLTrackElement.prototype.allowedEvents_ = {
8322
        load: 'load'
8323
    };
8324
 
8325
    /**
8326
     * The text track not loaded state.
8327
     *
8328
     * @type {number}
8329
     * @static
8330
     */
8331
    HTMLTrackElement.NONE = 0;
8332
 
8333
    /**
8334
     * The text track loading state.
8335
     *
8336
     * @type {number}
8337
     * @static
8338
     */
8339
    HTMLTrackElement.LOADING = 1;
8340
 
8341
    /**
8342
     * The text track loaded state.
8343
     *
8344
     * @type {number}
8345
     * @static
8346
     */
8347
    HTMLTrackElement.LOADED = 2;
8348
 
8349
    /**
8350
     * The text track failed to load state.
8351
     *
8352
     * @type {number}
8353
     * @static
8354
     */
8355
    HTMLTrackElement.ERROR = 3;
8356
 
8357
    /*
8358
   * This file contains all track properties that are used in
8359
   * player.js, tech.js, html5.js and possibly other techs in the future.
8360
   */
8361
 
8362
    const NORMAL = {
8363
        audio: {
8364
            ListClass: AudioTrackList,
8365
            TrackClass: AudioTrack,
8366
            capitalName: 'Audio'
8367
        },
8368
        video: {
8369
            ListClass: VideoTrackList,
8370
            TrackClass: VideoTrack,
8371
            capitalName: 'Video'
8372
        },
8373
        text: {
8374
            ListClass: TextTrackList,
8375
            TrackClass: TextTrack,
8376
            capitalName: 'Text'
8377
        }
8378
    };
8379
    Object.keys(NORMAL).forEach(function (type) {
8380
        NORMAL[type].getterName = `${type}Tracks`;
8381
        NORMAL[type].privateName = `${type}Tracks_`;
8382
    });
8383
    const REMOTE = {
8384
        remoteText: {
8385
            ListClass: TextTrackList,
8386
            TrackClass: TextTrack,
8387
            capitalName: 'RemoteText',
8388
            getterName: 'remoteTextTracks',
8389
            privateName: 'remoteTextTracks_'
8390
        },
8391
        remoteTextEl: {
8392
            ListClass: HtmlTrackElementList,
8393
            TrackClass: HTMLTrackElement,
8394
            capitalName: 'RemoteTextTrackEls',
8395
            getterName: 'remoteTextTrackEls',
8396
            privateName: 'remoteTextTrackEls_'
8397
        }
8398
    };
8399
    const ALL = Object.assign({}, NORMAL, REMOTE);
8400
    REMOTE.names = Object.keys(REMOTE);
8401
    NORMAL.names = Object.keys(NORMAL);
8402
    ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
8403
 
8404
    var minDoc = {};
8405
 
8406
    var topLevel = typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof window !== 'undefined' ? window : {};
8407
    var doccy;
8408
    if (typeof document !== 'undefined') {
8409
        doccy = document;
8410
    } else {
8411
        doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
8412
        if (!doccy) {
8413
            doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
8414
        }
8415
    }
8416
    var document_1 = doccy;
8417
 
8418
    /**
8419
     * Copyright 2013 vtt.js Contributors
8420
     *
8421
     * Licensed under the Apache License, Version 2.0 (the "License");
8422
     * you may not use this file except in compliance with the License.
8423
     * You may obtain a copy of the License at
8424
     *
8425
     *   http://www.apache.org/licenses/LICENSE-2.0
8426
     *
8427
     * Unless required by applicable law or agreed to in writing, software
8428
     * distributed under the License is distributed on an "AS IS" BASIS,
8429
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8430
     * See the License for the specific language governing permissions and
8431
     * limitations under the License.
8432
     */
8433
 
8434
    /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
8435
    /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
8436
 
8437
    var _objCreate = Object.create || function () {
8438
        function F() {}
8439
        return function (o) {
8440
            if (arguments.length !== 1) {
8441
                throw new Error('Object.create shim only accepts one parameter.');
8442
            }
8443
            F.prototype = o;
8444
            return new F();
8445
        };
8446
    }();
8447
 
8448
    // Creates a new ParserError object from an errorData object. The errorData
8449
    // object should have default code and message properties. The default message
8450
    // property can be overriden by passing in a message parameter.
8451
    // See ParsingError.Errors below for acceptable errors.
8452
    function ParsingError(errorData, message) {
8453
        this.name = "ParsingError";
8454
        this.code = errorData.code;
8455
        this.message = message || errorData.message;
8456
    }
8457
    ParsingError.prototype = _objCreate(Error.prototype);
8458
    ParsingError.prototype.constructor = ParsingError;
8459
 
8460
    // ParsingError metadata for acceptable ParsingErrors.
8461
    ParsingError.Errors = {
8462
        BadSignature: {
8463
            code: 0,
8464
            message: "Malformed WebVTT signature."
8465
        },
8466
        BadTimeStamp: {
8467
            code: 1,
8468
            message: "Malformed time stamp."
8469
        }
8470
    };
8471
 
8472
    // Try to parse input as a time stamp.
8473
    function parseTimeStamp(input) {
8474
        function computeSeconds(h, m, s, f) {
8475
            return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
8476
        }
8477
        var m = input.match(/^(\d+):(\d{1,2})(:\d{1,2})?\.(\d{3})/);
8478
        if (!m) {
8479
            return null;
8480
        }
8481
        if (m[3]) {
8482
            // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
8483
            return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
8484
        } else if (m[1] > 59) {
8485
            // Timestamp takes the form of [hours]:[minutes].[milliseconds]
8486
            // First position is hours as it's over 59.
8487
            return computeSeconds(m[1], m[2], 0, m[4]);
8488
        } else {
8489
            // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
8490
            return computeSeconds(0, m[1], m[2], m[4]);
8491
        }
8492
    }
8493
 
8494
    // A settings object holds key/value pairs and will ignore anything but the first
8495
    // assignment to a specific key.
8496
    function Settings() {
8497
        this.values = _objCreate(null);
8498
    }
8499
    Settings.prototype = {
8500
        // Only accept the first assignment to any key.
8501
        set: function (k, v) {
8502
            if (!this.get(k) && v !== "") {
8503
                this.values[k] = v;
8504
            }
8505
        },
8506
        // Return the value for a key, or a default value.
8507
        // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
8508
        // a number of possible default values as properties where 'defaultKey' is
8509
        // the key of the property that will be chosen; otherwise it's assumed to be
8510
        // a single value.
8511
        get: function (k, dflt, defaultKey) {
8512
            if (defaultKey) {
8513
                return this.has(k) ? this.values[k] : dflt[defaultKey];
8514
            }
8515
            return this.has(k) ? this.values[k] : dflt;
8516
        },
8517
        // Check whether we have a value for a key.
8518
        has: function (k) {
8519
            return k in this.values;
8520
        },
8521
        // Accept a setting if its one of the given alternatives.
8522
        alt: function (k, v, a) {
8523
            for (var n = 0; n < a.length; ++n) {
8524
                if (v === a[n]) {
8525
                    this.set(k, v);
8526
                    break;
8527
                }
8528
            }
8529
        },
8530
        // Accept a setting if its a valid (signed) integer.
8531
        integer: function (k, v) {
8532
            if (/^-?\d+$/.test(v)) {
8533
                // integer
8534
                this.set(k, parseInt(v, 10));
8535
            }
8536
        },
8537
        // Accept a setting if its a valid percentage.
8538
        percent: function (k, v) {
8539
            if (v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) {
8540
                v = parseFloat(v);
8541
                if (v >= 0 && v <= 100) {
8542
                    this.set(k, v);
8543
                    return true;
8544
                }
8545
            }
8546
            return false;
8547
        }
8548
    };
8549
 
8550
    // Helper function to parse input into groups separated by 'groupDelim', and
8551
    // interprete each group as a key/value pair separated by 'keyValueDelim'.
8552
    function parseOptions(input, callback, keyValueDelim, groupDelim) {
8553
        var groups = groupDelim ? input.split(groupDelim) : [input];
8554
        for (var i in groups) {
8555
            if (typeof groups[i] !== "string") {
8556
                continue;
8557
            }
8558
            var kv = groups[i].split(keyValueDelim);
8559
            if (kv.length !== 2) {
8560
                continue;
8561
            }
8562
            var k = kv[0].trim();
8563
            var v = kv[1].trim();
8564
            callback(k, v);
8565
        }
8566
    }
8567
    function parseCue(input, cue, regionList) {
8568
        // Remember the original input if we need to throw an error.
8569
        var oInput = input;
8570
        // 4.1 WebVTT timestamp
8571
        function consumeTimeStamp() {
8572
            var ts = parseTimeStamp(input);
8573
            if (ts === null) {
8574
                throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput);
8575
            }
8576
            // Remove time stamp from input.
8577
            input = input.replace(/^[^\sa-zA-Z-]+/, "");
8578
            return ts;
8579
        }
8580
 
8581
        // 4.4.2 WebVTT cue settings
8582
        function consumeCueSettings(input, cue) {
8583
            var settings = new Settings();
8584
            parseOptions(input, function (k, v) {
8585
                switch (k) {
8586
                    case "region":
8587
                        // Find the last region we parsed with the same region id.
8588
                        for (var i = regionList.length - 1; i >= 0; i--) {
8589
                            if (regionList[i].id === v) {
8590
                                settings.set(k, regionList[i].region);
8591
                                break;
8592
                            }
8593
                        }
8594
                        break;
8595
                    case "vertical":
8596
                        settings.alt(k, v, ["rl", "lr"]);
8597
                        break;
8598
                    case "line":
8599
                        var vals = v.split(","),
8600
                            vals0 = vals[0];
8601
                        settings.integer(k, vals0);
8602
                        settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
8603
                        settings.alt(k, vals0, ["auto"]);
8604
                        if (vals.length === 2) {
8605
                            settings.alt("lineAlign", vals[1], ["start", "center", "end"]);
8606
                        }
8607
                        break;
8608
                    case "position":
8609
                        vals = v.split(",");
8610
                        settings.percent(k, vals[0]);
8611
                        if (vals.length === 2) {
8612
                            settings.alt("positionAlign", vals[1], ["start", "center", "end"]);
8613
                        }
8614
                        break;
8615
                    case "size":
8616
                        settings.percent(k, v);
8617
                        break;
8618
                    case "align":
8619
                        settings.alt(k, v, ["start", "center", "end", "left", "right"]);
8620
                        break;
8621
                }
8622
            }, /:/, /\s/);
8623
 
8624
            // Apply default values for any missing fields.
8625
            cue.region = settings.get("region", null);
8626
            cue.vertical = settings.get("vertical", "");
8627
            try {
8628
                cue.line = settings.get("line", "auto");
8629
            } catch (e) {}
8630
            cue.lineAlign = settings.get("lineAlign", "start");
8631
            cue.snapToLines = settings.get("snapToLines", true);
8632
            cue.size = settings.get("size", 100);
8633
            // Safari still uses the old middle value and won't accept center
8634
            try {
8635
                cue.align = settings.get("align", "center");
8636
            } catch (e) {
8637
                cue.align = settings.get("align", "middle");
8638
            }
8639
            try {
8640
                cue.position = settings.get("position", "auto");
8641
            } catch (e) {
8642
                cue.position = settings.get("position", {
8643
                    start: 0,
8644
                    left: 0,
8645
                    center: 50,
8646
                    middle: 50,
8647
                    end: 100,
8648
                    right: 100
8649
                }, cue.align);
8650
            }
8651
            cue.positionAlign = settings.get("positionAlign", {
8652
                start: "start",
8653
                left: "start",
8654
                center: "center",
8655
                middle: "center",
8656
                end: "end",
8657
                right: "end"
8658
            }, cue.align);
8659
        }
8660
        function skipWhitespace() {
8661
            input = input.replace(/^\s+/, "");
8662
        }
8663
 
8664
        // 4.1 WebVTT cue timings.
8665
        skipWhitespace();
8666
        cue.startTime = consumeTimeStamp(); // (1) collect cue start time
8667
        skipWhitespace();
8668
        if (input.substr(0, 3) !== "-->") {
8669
            // (3) next characters must match "-->"
8670
            throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput);
8671
        }
8672
        input = input.substr(3);
8673
        skipWhitespace();
8674
        cue.endTime = consumeTimeStamp(); // (5) collect cue end time
8675
 
8676
        // 4.1 WebVTT cue settings list.
8677
        skipWhitespace();
8678
        consumeCueSettings(input, cue);
8679
    }
8680
 
8681
    // When evaluating this file as part of a Webpack bundle for server
8682
    // side rendering, `document` is an empty object.
8683
    var TEXTAREA_ELEMENT = document_1.createElement && document_1.createElement("textarea");
8684
    var TAG_NAME = {
8685
        c: "span",
8686
        i: "i",
8687
        b: "b",
8688
        u: "u",
8689
        ruby: "ruby",
8690
        rt: "rt",
8691
        v: "span",
8692
        lang: "span"
8693
    };
8694
 
8695
    // 5.1 default text color
8696
    // 5.2 default text background color is equivalent to text color with bg_ prefix
8697
    var DEFAULT_COLOR_CLASS = {
8698
        white: 'rgba(255,255,255,1)',
8699
        lime: 'rgba(0,255,0,1)',
8700
        cyan: 'rgba(0,255,255,1)',
8701
        red: 'rgba(255,0,0,1)',
8702
        yellow: 'rgba(255,255,0,1)',
8703
        magenta: 'rgba(255,0,255,1)',
8704
        blue: 'rgba(0,0,255,1)',
8705
        black: 'rgba(0,0,0,1)'
8706
    };
8707
    var TAG_ANNOTATION = {
8708
        v: "title",
8709
        lang: "lang"
8710
    };
8711
    var NEEDS_PARENT = {
8712
        rt: "ruby"
8713
    };
8714
 
8715
    // Parse content into a document fragment.
8716
    function parseContent(window, input) {
8717
        function nextToken() {
8718
            // Check for end-of-string.
8719
            if (!input) {
8720
                return null;
8721
            }
8722
 
8723
            // Consume 'n' characters from the input.
8724
            function consume(result) {
8725
                input = input.substr(result.length);
8726
                return result;
8727
            }
8728
            var m = input.match(/^([^<]*)(<[^>]*>?)?/);
8729
            // If there is some text before the next tag, return it, otherwise return
8730
            // the tag.
8731
            return consume(m[1] ? m[1] : m[2]);
8732
        }
8733
        function unescape(s) {
8734
            TEXTAREA_ELEMENT.innerHTML = s;
8735
            s = TEXTAREA_ELEMENT.textContent;
8736
            TEXTAREA_ELEMENT.textContent = "";
8737
            return s;
8738
        }
8739
        function shouldAdd(current, element) {
8740
            return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName;
8741
        }
8742
 
8743
        // Create an element for this tag.
8744
        function createElement(type, annotation) {
8745
            var tagName = TAG_NAME[type];
8746
            if (!tagName) {
8747
                return null;
8748
            }
8749
            var element = window.document.createElement(tagName);
8750
            var name = TAG_ANNOTATION[type];
8751
            if (name && annotation) {
8752
                element[name] = annotation.trim();
8753
            }
8754
            return element;
8755
        }
8756
        var rootDiv = window.document.createElement("div"),
8757
            current = rootDiv,
8758
            t,
8759
            tagStack = [];
8760
        while ((t = nextToken()) !== null) {
8761
            if (t[0] === '<') {
8762
                if (t[1] === "/") {
8763
                    // If the closing tag matches, move back up to the parent node.
8764
                    if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
8765
                        tagStack.pop();
8766
                        current = current.parentNode;
8767
                    }
8768
                    // Otherwise just ignore the end tag.
8769
                    continue;
8770
                }
8771
                var ts = parseTimeStamp(t.substr(1, t.length - 2));
8772
                var node;
8773
                if (ts) {
8774
                    // Timestamps are lead nodes as well.
8775
                    node = window.document.createProcessingInstruction("timestamp", ts);
8776
                    current.appendChild(node);
8777
                    continue;
8778
                }
8779
                var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);
8780
                // If we can't parse the tag, skip to the next tag.
8781
                if (!m) {
8782
                    continue;
8783
                }
8784
                // Try to construct an element, and ignore the tag if we couldn't.
8785
                node = createElement(m[1], m[3]);
8786
                if (!node) {
8787
                    continue;
8788
                }
8789
                // Determine if the tag should be added based on the context of where it
8790
                // is placed in the cuetext.
8791
                if (!shouldAdd(current, node)) {
8792
                    continue;
8793
                }
8794
                // Set the class list (as a list of classes, separated by space).
8795
                if (m[2]) {
8796
                    var classes = m[2].split('.');
8797
                    classes.forEach(function (cl) {
8798
                        var bgColor = /^bg_/.test(cl);
8799
                        // slice out `bg_` if it's a background color
8800
                        var colorName = bgColor ? cl.slice(3) : cl;
8801
                        if (DEFAULT_COLOR_CLASS.hasOwnProperty(colorName)) {
8802
                            var propName = bgColor ? 'background-color' : 'color';
8803
                            var propValue = DEFAULT_COLOR_CLASS[colorName];
8804
                            node.style[propName] = propValue;
8805
                        }
8806
                    });
8807
                    node.className = classes.join(' ');
8808
                }
8809
                // Append the node to the current node, and enter the scope of the new
8810
                // node.
8811
                tagStack.push(m[1]);
8812
                current.appendChild(node);
8813
                current = node;
8814
                continue;
8815
            }
8816
 
8817
            // Text nodes are leaf nodes.
8818
            current.appendChild(window.document.createTextNode(unescape(t)));
8819
        }
8820
        return rootDiv;
8821
    }
8822
 
8823
    // This is a list of all the Unicode characters that have a strong
8824
    // right-to-left category. What this means is that these characters are
8825
    // written right-to-left for sure. It was generated by pulling all the strong
8826
    // right-to-left characters out of the Unicode data table. That table can
8827
    // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
8828
    var strongRTLRanges = [[0x5be, 0x5be], [0x5c0, 0x5c0], [0x5c3, 0x5c3], [0x5c6, 0x5c6], [0x5d0, 0x5ea], [0x5f0, 0x5f4], [0x608, 0x608], [0x60b, 0x60b], [0x60d, 0x60d], [0x61b, 0x61b], [0x61e, 0x64a], [0x66d, 0x66f], [0x671, 0x6d5], [0x6e5, 0x6e6], [0x6ee, 0x6ef], [0x6fa, 0x70d], [0x70f, 0x710], [0x712, 0x72f], [0x74d, 0x7a5], [0x7b1, 0x7b1], [0x7c0, 0x7ea], [0x7f4, 0x7f5], [0x7fa, 0x7fa], [0x800, 0x815], [0x81a, 0x81a], [0x824, 0x824], [0x828, 0x828], [0x830, 0x83e], [0x840, 0x858], [0x85e, 0x85e], [0x8a0, 0x8a0], [0x8a2, 0x8ac], [0x200f, 0x200f], [0xfb1d, 0xfb1d], [0xfb1f, 0xfb28], [0xfb2a, 0xfb36], [0xfb38, 0xfb3c], [0xfb3e, 0xfb3e], [0xfb40, 0xfb41], [0xfb43, 0xfb44], [0xfb46, 0xfbc1], [0xfbd3, 0xfd3d], [0xfd50, 0xfd8f], [0xfd92, 0xfdc7], [0xfdf0, 0xfdfc], [0xfe70, 0xfe74], [0xfe76, 0xfefc], [0x10800, 0x10805], [0x10808, 0x10808], [0x1080a, 0x10835], [0x10837, 0x10838], [0x1083c, 0x1083c], [0x1083f, 0x10855], [0x10857, 0x1085f], [0x10900, 0x1091b], [0x10920, 0x10939], [0x1093f, 0x1093f], [0x10980, 0x109b7], [0x109be, 0x109bf], [0x10a00, 0x10a00], [0x10a10, 0x10a13], [0x10a15, 0x10a17], [0x10a19, 0x10a33], [0x10a40, 0x10a47], [0x10a50, 0x10a58], [0x10a60, 0x10a7f], [0x10b00, 0x10b35], [0x10b40, 0x10b55], [0x10b58, 0x10b72], [0x10b78, 0x10b7f], [0x10c00, 0x10c48], [0x1ee00, 0x1ee03], [0x1ee05, 0x1ee1f], [0x1ee21, 0x1ee22], [0x1ee24, 0x1ee24], [0x1ee27, 0x1ee27], [0x1ee29, 0x1ee32], [0x1ee34, 0x1ee37], [0x1ee39, 0x1ee39], [0x1ee3b, 0x1ee3b], [0x1ee42, 0x1ee42], [0x1ee47, 0x1ee47], [0x1ee49, 0x1ee49], [0x1ee4b, 0x1ee4b], [0x1ee4d, 0x1ee4f], [0x1ee51, 0x1ee52], [0x1ee54, 0x1ee54], [0x1ee57, 0x1ee57], [0x1ee59, 0x1ee59], [0x1ee5b, 0x1ee5b], [0x1ee5d, 0x1ee5d], [0x1ee5f, 0x1ee5f], [0x1ee61, 0x1ee62], [0x1ee64, 0x1ee64], [0x1ee67, 0x1ee6a], [0x1ee6c, 0x1ee72], [0x1ee74, 0x1ee77], [0x1ee79, 0x1ee7c], [0x1ee7e, 0x1ee7e], [0x1ee80, 0x1ee89], [0x1ee8b, 0x1ee9b], [0x1eea1, 0x1eea3], [0x1eea5, 0x1eea9], [0x1eeab, 0x1eebb], [0x10fffd, 0x10fffd]];
8829
    function isStrongRTLChar(charCode) {
8830
        for (var i = 0; i < strongRTLRanges.length; i++) {
8831
            var currentRange = strongRTLRanges[i];
8832
            if (charCode >= currentRange[0] && charCode <= currentRange[1]) {
8833
                return true;
8834
            }
8835
        }
8836
        return false;
8837
    }
8838
    function determineBidi(cueDiv) {
8839
        var nodeStack = [],
8840
            text = "",
8841
            charCode;
8842
        if (!cueDiv || !cueDiv.childNodes) {
8843
            return "ltr";
8844
        }
8845
        function pushNodes(nodeStack, node) {
8846
            for (var i = node.childNodes.length - 1; i >= 0; i--) {
8847
                nodeStack.push(node.childNodes[i]);
8848
            }
8849
        }
8850
        function nextTextNode(nodeStack) {
8851
            if (!nodeStack || !nodeStack.length) {
8852
                return null;
8853
            }
8854
            var node = nodeStack.pop(),
8855
                text = node.textContent || node.innerText;
8856
            if (text) {
8857
                // TODO: This should match all unicode type B characters (paragraph
8858
                // separator characters). See issue #115.
8859
                var m = text.match(/^.*(\n|\r)/);
8860
                if (m) {
8861
                    nodeStack.length = 0;
8862
                    return m[0];
8863
                }
8864
                return text;
8865
            }
8866
            if (node.tagName === "ruby") {
8867
                return nextTextNode(nodeStack);
8868
            }
8869
            if (node.childNodes) {
8870
                pushNodes(nodeStack, node);
8871
                return nextTextNode(nodeStack);
8872
            }
8873
        }
8874
        pushNodes(nodeStack, cueDiv);
8875
        while (text = nextTextNode(nodeStack)) {
8876
            for (var i = 0; i < text.length; i++) {
8877
                charCode = text.charCodeAt(i);
8878
                if (isStrongRTLChar(charCode)) {
8879
                    return "rtl";
8880
                }
8881
            }
8882
        }
8883
        return "ltr";
8884
    }
8885
    function computeLinePos(cue) {
8886
        if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) {
8887
            return cue.line;
8888
        }
8889
        if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) {
8890
            return -1;
8891
        }
8892
        var track = cue.track,
8893
            trackList = track.textTrackList,
8894
            count = 0;
8895
        for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
8896
            if (trackList[i].mode === "showing") {
8897
                count++;
8898
            }
8899
        }
8900
        return ++count * -1;
8901
    }
8902
    function StyleBox() {}
8903
 
8904
    // Apply styles to a div. If there is no div passed then it defaults to the
8905
    // div on 'this'.
8906
    StyleBox.prototype.applyStyles = function (styles, div) {
8907
        div = div || this.div;
8908
        for (var prop in styles) {
8909
            if (styles.hasOwnProperty(prop)) {
8910
                div.style[prop] = styles[prop];
8911
            }
8912
        }
8913
    };
8914
    StyleBox.prototype.formatStyle = function (val, unit) {
8915
        return val === 0 ? 0 : val + unit;
8916
    };
8917
 
8918
    // Constructs the computed display state of the cue (a div). Places the div
8919
    // into the overlay which should be a block level element (usually a div).
8920
    function CueStyleBox(window, cue, styleOptions) {
8921
        StyleBox.call(this);
8922
        this.cue = cue;
8923
 
8924
        // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
8925
        // have inline positioning and will function as the cue background box.
8926
        this.cueDiv = parseContent(window, cue.text);
8927
        var styles = {
8928
            color: "rgba(255, 255, 255, 1)",
8929
            backgroundColor: "rgba(0, 0, 0, 0.8)",
8930
            position: "relative",
8931
            left: 0,
8932
            right: 0,
8933
            top: 0,
8934
            bottom: 0,
8935
            display: "inline",
8936
            writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
8937
            unicodeBidi: "plaintext"
8938
        };
8939
        this.applyStyles(styles, this.cueDiv);
8940
 
8941
        // Create an absolutely positioned div that will be used to position the cue
8942
        // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
8943
        // mirrors of them except middle instead of center on Safari.
8944
        this.div = window.document.createElement("div");
8945
        styles = {
8946
            direction: determineBidi(this.cueDiv),
8947
            writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
8948
            unicodeBidi: "plaintext",
8949
            textAlign: cue.align === "middle" ? "center" : cue.align,
8950
            font: styleOptions.font,
8951
            whiteSpace: "pre-line",
8952
            position: "absolute"
8953
        };
8954
        this.applyStyles(styles);
8955
        this.div.appendChild(this.cueDiv);
8956
 
8957
        // Calculate the distance from the reference edge of the viewport to the text
8958
        // position of the cue box. The reference edge will be resolved later when
8959
        // the box orientation styles are applied.
8960
        var textPos = 0;
8961
        switch (cue.positionAlign) {
8962
            case "start":
8963
            case "line-left":
8964
                textPos = cue.position;
8965
                break;
8966
            case "center":
8967
                textPos = cue.position - cue.size / 2;
8968
                break;
8969
            case "end":
8970
            case "line-right":
8971
                textPos = cue.position - cue.size;
8972
                break;
8973
        }
8974
 
8975
        // Horizontal box orientation; textPos is the distance from the left edge of the
8976
        // area to the left edge of the box and cue.size is the distance extending to
8977
        // the right from there.
8978
        if (cue.vertical === "") {
8979
            this.applyStyles({
8980
                left: this.formatStyle(textPos, "%"),
8981
                width: this.formatStyle(cue.size, "%")
8982
            });
8983
            // Vertical box orientation; textPos is the distance from the top edge of the
8984
            // area to the top edge of the box and cue.size is the height extending
8985
            // downwards from there.
8986
        } else {
8987
            this.applyStyles({
8988
                top: this.formatStyle(textPos, "%"),
8989
                height: this.formatStyle(cue.size, "%")
8990
            });
8991
        }
8992
        this.move = function (box) {
8993
            this.applyStyles({
8994
                top: this.formatStyle(box.top, "px"),
8995
                bottom: this.formatStyle(box.bottom, "px"),
8996
                left: this.formatStyle(box.left, "px"),
8997
                right: this.formatStyle(box.right, "px"),
8998
                height: this.formatStyle(box.height, "px"),
8999
                width: this.formatStyle(box.width, "px")
9000
            });
9001
        };
9002
    }
9003
    CueStyleBox.prototype = _objCreate(StyleBox.prototype);
9004
    CueStyleBox.prototype.constructor = CueStyleBox;
9005
 
9006
    // Represents the co-ordinates of an Element in a way that we can easily
9007
    // compute things with such as if it overlaps or intersects with another Element.
9008
    // Can initialize it with either a StyleBox or another BoxPosition.
9009
    function BoxPosition(obj) {
9010
        // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
9011
        // was passed in and we need to copy the results of 'getBoundingClientRect'
9012
        // as the object returned is readonly. All co-ordinate values are in reference
9013
        // to the viewport origin (top left).
9014
        var lh, height, width, top;
9015
        if (obj.div) {
9016
            height = obj.div.offsetHeight;
9017
            width = obj.div.offsetWidth;
9018
            top = obj.div.offsetTop;
9019
            var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects();
9020
            obj = obj.div.getBoundingClientRect();
9021
            // In certain cases the outter div will be slightly larger then the sum of
9022
            // the inner div's lines. This could be due to bold text, etc, on some platforms.
9023
            // In this case we should get the average line height and use that. This will
9024
            // result in the desired behaviour.
9025
            lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0;
9026
        }
9027
        this.left = obj.left;
9028
        this.right = obj.right;
9029
        this.top = obj.top || top;
9030
        this.height = obj.height || height;
9031
        this.bottom = obj.bottom || top + (obj.height || height);
9032
        this.width = obj.width || width;
9033
        this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
9034
    }
9035
 
9036
    // Move the box along a particular axis. Optionally pass in an amount to move
9037
    // the box. If no amount is passed then the default is the line height of the
9038
    // box.
9039
    BoxPosition.prototype.move = function (axis, toMove) {
9040
        toMove = toMove !== undefined ? toMove : this.lineHeight;
9041
        switch (axis) {
9042
            case "+x":
9043
                this.left += toMove;
9044
                this.right += toMove;
9045
                break;
9046
            case "-x":
9047
                this.left -= toMove;
9048
                this.right -= toMove;
9049
                break;
9050
            case "+y":
9051
                this.top += toMove;
9052
                this.bottom += toMove;
9053
                break;
9054
            case "-y":
9055
                this.top -= toMove;
9056
                this.bottom -= toMove;
9057
                break;
9058
        }
9059
    };
9060
 
9061
    // Check if this box overlaps another box, b2.
9062
    BoxPosition.prototype.overlaps = function (b2) {
9063
        return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top;
9064
    };
9065
 
9066
    // Check if this box overlaps any other boxes in boxes.
9067
    BoxPosition.prototype.overlapsAny = function (boxes) {
9068
        for (var i = 0; i < boxes.length; i++) {
9069
            if (this.overlaps(boxes[i])) {
9070
                return true;
9071
            }
9072
        }
9073
        return false;
9074
    };
9075
 
9076
    // Check if this box is within another box.
9077
    BoxPosition.prototype.within = function (container) {
9078
        return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right;
9079
    };
9080
 
9081
    // Check if this box is entirely within the container or it is overlapping
9082
    // on the edge opposite of the axis direction passed. For example, if "+x" is
9083
    // passed and the box is overlapping on the left edge of the container, then
9084
    // return true.
9085
    BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) {
9086
        switch (axis) {
9087
            case "+x":
9088
                return this.left < container.left;
9089
            case "-x":
9090
                return this.right > container.right;
9091
            case "+y":
9092
                return this.top < container.top;
9093
            case "-y":
9094
                return this.bottom > container.bottom;
9095
        }
9096
    };
9097
 
9098
    // Find the percentage of the area that this box is overlapping with another
9099
    // box.
9100
    BoxPosition.prototype.intersectPercentage = function (b2) {
9101
        var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
9102
            y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
9103
            intersectArea = x * y;
9104
        return intersectArea / (this.height * this.width);
9105
    };
9106
 
9107
    // Convert the positions from this box to CSS compatible positions using
9108
    // the reference container's positions. This has to be done because this
9109
    // box's positions are in reference to the viewport origin, whereas, CSS
9110
    // values are in referecne to their respective edges.
9111
    BoxPosition.prototype.toCSSCompatValues = function (reference) {
9112
        return {
9113
            top: this.top - reference.top,
9114
            bottom: reference.bottom - this.bottom,
9115
            left: this.left - reference.left,
9116
            right: reference.right - this.right,
9117
            height: this.height,
9118
            width: this.width
9119
        };
9120
    };
9121
 
9122
    // Get an object that represents the box's position without anything extra.
9123
    // Can pass a StyleBox, HTMLElement, or another BoxPositon.
9124
    BoxPosition.getSimpleBoxPosition = function (obj) {
9125
        var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
9126
        var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
9127
        var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;
9128
        obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj;
9129
        var ret = {
9130
            left: obj.left,
9131
            right: obj.right,
9132
            top: obj.top || top,
9133
            height: obj.height || height,
9134
            bottom: obj.bottom || top + (obj.height || height),
9135
            width: obj.width || width
9136
        };
9137
        return ret;
9138
    };
9139
 
9140
    // Move a StyleBox to its specified, or next best, position. The containerBox
9141
    // is the box that contains the StyleBox, such as a div. boxPositions are
9142
    // a list of other boxes that the styleBox can't overlap with.
9143
    function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
9144
        // Find the best position for a cue box, b, on the video. The axis parameter
9145
        // is a list of axis, the order of which, it will move the box along. For example:
9146
        // Passing ["+x", "-x"] will move the box first along the x axis in the positive
9147
        // direction. If it doesn't find a good position for it there it will then move
9148
        // it along the x axis in the negative direction.
9149
        function findBestPosition(b, axis) {
9150
            var bestPosition,
9151
                specifiedPosition = new BoxPosition(b),
9152
                percentage = 1; // Highest possible so the first thing we get is better.
9153
 
9154
            for (var i = 0; i < axis.length; i++) {
9155
                while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) {
9156
                    b.move(axis[i]);
9157
                }
9158
                // We found a spot where we aren't overlapping anything. This is our
9159
                // best position.
9160
                if (b.within(containerBox)) {
9161
                    return b;
9162
                }
9163
                var p = b.intersectPercentage(containerBox);
9164
                // If we're outside the container box less then we were on our last try
9165
                // then remember this position as the best position.
9166
                if (percentage > p) {
9167
                    bestPosition = new BoxPosition(b);
9168
                    percentage = p;
9169
                }
9170
                // Reset the box position to the specified position.
9171
                b = new BoxPosition(specifiedPosition);
9172
            }
9173
            return bestPosition || specifiedPosition;
9174
        }
9175
        var boxPosition = new BoxPosition(styleBox),
9176
            cue = styleBox.cue,
9177
            linePos = computeLinePos(cue),
9178
            axis = [];
9179
 
9180
        // If we have a line number to align the cue to.
9181
        if (cue.snapToLines) {
9182
            var size;
9183
            switch (cue.vertical) {
9184
                case "":
9185
                    axis = ["+y", "-y"];
9186
                    size = "height";
9187
                    break;
9188
                case "rl":
9189
                    axis = ["+x", "-x"];
9190
                    size = "width";
9191
                    break;
9192
                case "lr":
9193
                    axis = ["-x", "+x"];
9194
                    size = "width";
9195
                    break;
9196
            }
9197
            var step = boxPosition.lineHeight,
9198
                position = step * Math.round(linePos),
9199
                maxPosition = containerBox[size] + step,
9200
                initialAxis = axis[0];
9201
 
9202
            // If the specified intial position is greater then the max position then
9203
            // clamp the box to the amount of steps it would take for the box to
9204
            // reach the max position.
9205
            if (Math.abs(position) > maxPosition) {
9206
                position = position < 0 ? -1 : 1;
9207
                position *= Math.ceil(maxPosition / step) * step;
9208
            }
9209
 
9210
            // If computed line position returns negative then line numbers are
9211
            // relative to the bottom of the video instead of the top. Therefore, we
9212
            // need to increase our initial position by the length or width of the
9213
            // video, depending on the writing direction, and reverse our axis directions.
9214
            if (linePos < 0) {
9215
                position += cue.vertical === "" ? containerBox.height : containerBox.width;
9216
                axis = axis.reverse();
9217
            }
9218
 
9219
            // Move the box to the specified position. This may not be its best
9220
            // position.
9221
            boxPosition.move(initialAxis, position);
9222
        } else {
9223
            // If we have a percentage line value for the cue.
9224
            var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100;
9225
            switch (cue.lineAlign) {
9226
                case "center":
9227
                    linePos -= calculatedPercentage / 2;
9228
                    break;
9229
                case "end":
9230
                    linePos -= calculatedPercentage;
9231
                    break;
9232
            }
9233
 
9234
            // Apply initial line position to the cue box.
9235
            switch (cue.vertical) {
9236
                case "":
9237
                    styleBox.applyStyles({
9238
                        top: styleBox.formatStyle(linePos, "%")
9239
                    });
9240
                    break;
9241
                case "rl":
9242
                    styleBox.applyStyles({
9243
                        left: styleBox.formatStyle(linePos, "%")
9244
                    });
9245
                    break;
9246
                case "lr":
9247
                    styleBox.applyStyles({
9248
                        right: styleBox.formatStyle(linePos, "%")
9249
                    });
9250
                    break;
9251
            }
9252
            axis = ["+y", "-x", "+x", "-y"];
9253
 
9254
            // Get the box position again after we've applied the specified positioning
9255
            // to it.
9256
            boxPosition = new BoxPosition(styleBox);
9257
        }
9258
        var bestPosition = findBestPosition(boxPosition, axis);
9259
        styleBox.move(bestPosition.toCSSCompatValues(containerBox));
9260
    }
9261
    function WebVTT$1() {
9262
        // Nothing
9263
    }
9264
 
9265
    // Helper to allow strings to be decoded instead of the default binary utf8 data.
9266
    WebVTT$1.StringDecoder = function () {
9267
        return {
9268
            decode: function (data) {
9269
                if (!data) {
9270
                    return "";
9271
                }
9272
                if (typeof data !== "string") {
9273
                    throw new Error("Error - expected string data.");
9274
                }
9275
                return decodeURIComponent(encodeURIComponent(data));
9276
            }
9277
        };
9278
    };
9279
    WebVTT$1.convertCueToDOMTree = function (window, cuetext) {
9280
        if (!window || !cuetext) {
9281
            return null;
9282
        }
9283
        return parseContent(window, cuetext);
9284
    };
9285
    var FONT_SIZE_PERCENT = 0.05;
9286
    var FONT_STYLE = "sans-serif";
9287
    var CUE_BACKGROUND_PADDING = "1.5%";
9288
 
9289
    // Runs the processing model over the cues and regions passed to it.
9290
    // @param overlay A block level element (usually a div) that the computed cues
9291
    //                and regions will be placed into.
9292
    WebVTT$1.processCues = function (window, cues, overlay) {
9293
        if (!window || !cues || !overlay) {
9294
            return null;
9295
        }
9296
 
9297
        // Remove all previous children.
9298
        while (overlay.firstChild) {
9299
            overlay.removeChild(overlay.firstChild);
9300
        }
9301
        var paddedOverlay = window.document.createElement("div");
9302
        paddedOverlay.style.position = "absolute";
9303
        paddedOverlay.style.left = "0";
9304
        paddedOverlay.style.right = "0";
9305
        paddedOverlay.style.top = "0";
9306
        paddedOverlay.style.bottom = "0";
9307
        paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
9308
        overlay.appendChild(paddedOverlay);
9309
 
9310
        // Determine if we need to compute the display states of the cues. This could
9311
        // be the case if a cue's state has been changed since the last computation or
9312
        // if it has not been computed yet.
9313
        function shouldCompute(cues) {
9314
            for (var i = 0; i < cues.length; i++) {
9315
                if (cues[i].hasBeenReset || !cues[i].displayState) {
9316
                    return true;
9317
                }
9318
            }
9319
            return false;
9320
        }
9321
 
9322
        // We don't need to recompute the cues' display states. Just reuse them.
9323
        if (!shouldCompute(cues)) {
9324
            for (var i = 0; i < cues.length; i++) {
9325
                paddedOverlay.appendChild(cues[i].displayState);
9326
            }
9327
            return;
9328
        }
9329
        var boxPositions = [],
9330
            containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
9331
            fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
9332
        var styleOptions = {
9333
            font: fontSize + "px " + FONT_STYLE
9334
        };
9335
        (function () {
9336
            var styleBox, cue;
9337
            for (var i = 0; i < cues.length; i++) {
9338
                cue = cues[i];
9339
 
9340
                // Compute the intial position and styles of the cue div.
9341
                styleBox = new CueStyleBox(window, cue, styleOptions);
9342
                paddedOverlay.appendChild(styleBox.div);
9343
 
9344
                // Move the cue div to it's correct line position.
9345
                moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);
9346
 
9347
                // Remember the computed div so that we don't have to recompute it later
9348
                // if we don't have too.
9349
                cue.displayState = styleBox.div;
9350
                boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
9351
            }
9352
        })();
9353
    };
9354
    WebVTT$1.Parser = function (window, vttjs, decoder) {
9355
        if (!decoder) {
9356
            decoder = vttjs;
9357
            vttjs = {};
9358
        }
9359
        if (!vttjs) {
9360
            vttjs = {};
9361
        }
9362
        this.window = window;
9363
        this.vttjs = vttjs;
9364
        this.state = "INITIAL";
9365
        this.buffer = "";
9366
        this.decoder = decoder || new TextDecoder("utf8");
9367
        this.regionList = [];
9368
    };
9369
    WebVTT$1.Parser.prototype = {
9370
        // If the error is a ParsingError then report it to the consumer if
9371
        // possible. If it's not a ParsingError then throw it like normal.
9372
        reportOrThrowError: function (e) {
9373
            if (e instanceof ParsingError) {
9374
                this.onparsingerror && this.onparsingerror(e);
9375
            } else {
9376
                throw e;
9377
            }
9378
        },
9379
        parse: function (data) {
9380
            var self = this;
9381
 
9382
            // If there is no data then we won't decode it, but will just try to parse
9383
            // whatever is in buffer already. This may occur in circumstances, for
9384
            // example when flush() is called.
9385
            if (data) {
9386
                // Try to decode the data that we received.
9387
                self.buffer += self.decoder.decode(data, {
9388
                    stream: true
9389
                });
9390
            }
9391
            function collectNextLine() {
9392
                var buffer = self.buffer;
9393
                var pos = 0;
9394
                while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
9395
                    ++pos;
9396
                }
9397
                var line = buffer.substr(0, pos);
9398
                // Advance the buffer early in case we fail below.
9399
                if (buffer[pos] === '\r') {
9400
                    ++pos;
9401
                }
9402
                if (buffer[pos] === '\n') {
9403
                    ++pos;
9404
                }
9405
                self.buffer = buffer.substr(pos);
9406
                return line;
9407
            }
9408
 
9409
            // 3.4 WebVTT region and WebVTT region settings syntax
9410
            function parseRegion(input) {
9411
                var settings = new Settings();
9412
                parseOptions(input, function (k, v) {
9413
                    switch (k) {
9414
                        case "id":
9415
                            settings.set(k, v);
9416
                            break;
9417
                        case "width":
9418
                            settings.percent(k, v);
9419
                            break;
9420
                        case "lines":
9421
                            settings.integer(k, v);
9422
                            break;
9423
                        case "regionanchor":
9424
                        case "viewportanchor":
9425
                            var xy = v.split(',');
9426
                            if (xy.length !== 2) {
9427
                                break;
9428
                            }
9429
                            // We have to make sure both x and y parse, so use a temporary
9430
                            // settings object here.
9431
                            var anchor = new Settings();
9432
                            anchor.percent("x", xy[0]);
9433
                            anchor.percent("y", xy[1]);
9434
                            if (!anchor.has("x") || !anchor.has("y")) {
9435
                                break;
9436
                            }
9437
                            settings.set(k + "X", anchor.get("x"));
9438
                            settings.set(k + "Y", anchor.get("y"));
9439
                            break;
9440
                        case "scroll":
9441
                            settings.alt(k, v, ["up"]);
9442
                            break;
9443
                    }
9444
                }, /=/, /\s/);
9445
 
9446
                // Create the region, using default values for any values that were not
9447
                // specified.
9448
                if (settings.has("id")) {
9449
                    var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
9450
                    region.width = settings.get("width", 100);
9451
                    region.lines = settings.get("lines", 3);
9452
                    region.regionAnchorX = settings.get("regionanchorX", 0);
9453
                    region.regionAnchorY = settings.get("regionanchorY", 100);
9454
                    region.viewportAnchorX = settings.get("viewportanchorX", 0);
9455
                    region.viewportAnchorY = settings.get("viewportanchorY", 100);
9456
                    region.scroll = settings.get("scroll", "");
9457
                    // Register the region.
9458
                    self.onregion && self.onregion(region);
9459
                    // Remember the VTTRegion for later in case we parse any VTTCues that
9460
                    // reference it.
9461
                    self.regionList.push({
9462
                        id: settings.get("id"),
9463
                        region: region
9464
                    });
9465
                }
9466
            }
9467
 
9468
            // draft-pantos-http-live-streaming-20
9469
            // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5
9470
            // 3.5 WebVTT
9471
            function parseTimestampMap(input) {
9472
                var settings = new Settings();
9473
                parseOptions(input, function (k, v) {
9474
                    switch (k) {
9475
                        case "MPEGT":
9476
                            settings.integer(k + 'S', v);
9477
                            break;
9478
                        case "LOCA":
9479
                            settings.set(k + 'L', parseTimeStamp(v));
9480
                            break;
9481
                    }
9482
                }, /[^\d]:/, /,/);
9483
                self.ontimestampmap && self.ontimestampmap({
9484
                    "MPEGTS": settings.get("MPEGTS"),
9485
                    "LOCAL": settings.get("LOCAL")
9486
                });
9487
            }
9488
 
9489
            // 3.2 WebVTT metadata header syntax
9490
            function parseHeader(input) {
9491
                if (input.match(/X-TIMESTAMP-MAP/)) {
9492
                    // This line contains HLS X-TIMESTAMP-MAP metadata
9493
                    parseOptions(input, function (k, v) {
9494
                        switch (k) {
9495
                            case "X-TIMESTAMP-MAP":
9496
                                parseTimestampMap(v);
9497
                                break;
9498
                        }
9499
                    }, /=/);
9500
                } else {
9501
                    parseOptions(input, function (k, v) {
9502
                        switch (k) {
9503
                            case "Region":
9504
                                // 3.3 WebVTT region metadata header syntax
9505
                                parseRegion(v);
9506
                                break;
9507
                        }
9508
                    }, /:/);
9509
                }
9510
            }
9511
 
9512
            // 5.1 WebVTT file parsing.
9513
            try {
9514
                var line;
9515
                if (self.state === "INITIAL") {
9516
                    // We can't start parsing until we have the first line.
9517
                    if (!/\r\n|\n/.test(self.buffer)) {
9518
                        return this;
9519
                    }
9520
                    line = collectNextLine();
9521
                    var m = line.match(/^WEBVTT([ \t].*)?$/);
9522
                    if (!m || !m[0]) {
9523
                        throw new ParsingError(ParsingError.Errors.BadSignature);
9524
                    }
9525
                    self.state = "HEADER";
9526
                }
9527
                var alreadyCollectedLine = false;
9528
                while (self.buffer) {
9529
                    // We can't parse a line until we have the full line.
9530
                    if (!/\r\n|\n/.test(self.buffer)) {
9531
                        return this;
9532
                    }
9533
                    if (!alreadyCollectedLine) {
9534
                        line = collectNextLine();
9535
                    } else {
9536
                        alreadyCollectedLine = false;
9537
                    }
9538
                    switch (self.state) {
9539
                        case "HEADER":
9540
                            // 13-18 - Allow a header (metadata) under the WEBVTT line.
9541
                            if (/:/.test(line)) {
9542
                                parseHeader(line);
9543
                            } else if (!line) {
9544
                                // An empty line terminates the header and starts the body (cues).
9545
                                self.state = "ID";
9546
                            }
9547
                            continue;
9548
                        case "NOTE":
9549
                            // Ignore NOTE blocks.
9550
                            if (!line) {
9551
                                self.state = "ID";
9552
                            }
9553
                            continue;
9554
                        case "ID":
9555
                            // Check for the start of NOTE blocks.
9556
                            if (/^NOTE($|[ \t])/.test(line)) {
9557
                                self.state = "NOTE";
9558
                                break;
9559
                            }
9560
                            // 19-29 - Allow any number of line terminators, then initialize new cue values.
9561
                            if (!line) {
9562
                                continue;
9563
                            }
9564
                            self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, "");
9565
                            // Safari still uses the old middle value and won't accept center
9566
                            try {
9567
                                self.cue.align = "center";
9568
                            } catch (e) {
9569
                                self.cue.align = "middle";
9570
                            }
9571
                            self.state = "CUE";
9572
                            // 30-39 - Check if self line contains an optional identifier or timing data.
9573
                            if (line.indexOf("-->") === -1) {
9574
                                self.cue.id = line;
9575
                                continue;
9576
                            }
9577
                        // Process line as start of a cue.
9578
                        /*falls through*/
9579
                        case "CUE":
9580
                            // 40 - Collect cue timings and settings.
9581
                            try {
9582
                                parseCue(line, self.cue, self.regionList);
9583
                            } catch (e) {
9584
                                self.reportOrThrowError(e);
9585
                                // In case of an error ignore rest of the cue.
9586
                                self.cue = null;
9587
                                self.state = "BADCUE";
9588
                                continue;
9589
                            }
9590
                            self.state = "CUETEXT";
9591
                            continue;
9592
                        case "CUETEXT":
9593
                            var hasSubstring = line.indexOf("-->") !== -1;
9594
                            // 34 - If we have an empty line then report the cue.
9595
                            // 35 - If we have the special substring '-->' then report the cue,
9596
                            // but do not collect the line as we need to process the current
9597
                            // one as a new cue.
9598
                            if (!line || hasSubstring && (alreadyCollectedLine = true)) {
9599
                                // We are done parsing self cue.
9600
                                self.oncue && self.oncue(self.cue);
9601
                                self.cue = null;
9602
                                self.state = "ID";
9603
                                continue;
9604
                            }
9605
                            if (self.cue.text) {
9606
                                self.cue.text += "\n";
9607
                            }
9608
                            self.cue.text += line.replace(/\u2028/g, '\n').replace(/u2029/g, '\n');
9609
                            continue;
9610
                        case "BADCUE":
9611
                            // BADCUE
9612
                            // 54-62 - Collect and discard the remaining cue.
9613
                            if (!line) {
9614
                                self.state = "ID";
9615
                            }
9616
                            continue;
9617
                    }
9618
                }
9619
            } catch (e) {
9620
                self.reportOrThrowError(e);
9621
 
9622
                // If we are currently parsing a cue, report what we have.
9623
                if (self.state === "CUETEXT" && self.cue && self.oncue) {
9624
                    self.oncue(self.cue);
9625
                }
9626
                self.cue = null;
9627
                // Enter BADWEBVTT state if header was not parsed correctly otherwise
9628
                // another exception occurred so enter BADCUE state.
9629
                self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
9630
            }
9631
            return this;
9632
        },
9633
        flush: function () {
9634
            var self = this;
9635
            try {
9636
                // Finish decoding the stream.
9637
                self.buffer += self.decoder.decode();
9638
                // Synthesize the end of the current cue or region.
9639
                if (self.cue || self.state === "HEADER") {
9640
                    self.buffer += "\n\n";
9641
                    self.parse();
9642
                }
9643
                // If we've flushed, parsed, and we're still on the INITIAL state then
9644
                // that means we don't have enough of the stream to parse the first
9645
                // line.
9646
                if (self.state === "INITIAL") {
9647
                    throw new ParsingError(ParsingError.Errors.BadSignature);
9648
                }
9649
            } catch (e) {
9650
                self.reportOrThrowError(e);
9651
            }
9652
            self.onflush && self.onflush();
9653
            return this;
9654
        }
9655
    };
9656
    var vtt = WebVTT$1;
9657
 
9658
    /**
9659
     * Copyright 2013 vtt.js Contributors
9660
     *
9661
     * Licensed under the Apache License, Version 2.0 (the "License");
9662
     * you may not use this file except in compliance with the License.
9663
     * You may obtain a copy of the License at
9664
     *
9665
     *   http://www.apache.org/licenses/LICENSE-2.0
9666
     *
9667
     * Unless required by applicable law or agreed to in writing, software
9668
     * distributed under the License is distributed on an "AS IS" BASIS,
9669
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9670
     * See the License for the specific language governing permissions and
9671
     * limitations under the License.
9672
     */
9673
 
9674
    var autoKeyword = "auto";
9675
    var directionSetting = {
9676
        "": 1,
9677
        "lr": 1,
9678
        "rl": 1
9679
    };
9680
    var alignSetting = {
9681
        "start": 1,
9682
        "center": 1,
9683
        "end": 1,
9684
        "left": 1,
9685
        "right": 1,
9686
        "auto": 1,
9687
        "line-left": 1,
9688
        "line-right": 1
9689
    };
9690
    function findDirectionSetting(value) {
9691
        if (typeof value !== "string") {
9692
            return false;
9693
        }
9694
        var dir = directionSetting[value.toLowerCase()];
9695
        return dir ? value.toLowerCase() : false;
9696
    }
9697
    function findAlignSetting(value) {
9698
        if (typeof value !== "string") {
9699
            return false;
9700
        }
9701
        var align = alignSetting[value.toLowerCase()];
9702
        return align ? value.toLowerCase() : false;
9703
    }
9704
    function VTTCue(startTime, endTime, text) {
9705
        /**
9706
         * Shim implementation specific properties. These properties are not in
9707
         * the spec.
9708
         */
9709
 
9710
        // Lets us know when the VTTCue's data has changed in such a way that we need
9711
        // to recompute its display state. This lets us compute its display state
9712
        // lazily.
9713
        this.hasBeenReset = false;
9714
 
9715
        /**
9716
         * VTTCue and TextTrackCue properties
9717
         * http://dev.w3.org/html5/webvtt/#vttcue-interface
9718
         */
9719
 
9720
        var _id = "";
9721
        var _pauseOnExit = false;
9722
        var _startTime = startTime;
9723
        var _endTime = endTime;
9724
        var _text = text;
9725
        var _region = null;
9726
        var _vertical = "";
9727
        var _snapToLines = true;
9728
        var _line = "auto";
9729
        var _lineAlign = "start";
9730
        var _position = "auto";
9731
        var _positionAlign = "auto";
9732
        var _size = 100;
9733
        var _align = "center";
9734
        Object.defineProperties(this, {
9735
            "id": {
9736
                enumerable: true,
9737
                get: function () {
9738
                    return _id;
9739
                },
9740
                set: function (value) {
9741
                    _id = "" + value;
9742
                }
9743
            },
9744
            "pauseOnExit": {
9745
                enumerable: true,
9746
                get: function () {
9747
                    return _pauseOnExit;
9748
                },
9749
                set: function (value) {
9750
                    _pauseOnExit = !!value;
9751
                }
9752
            },
9753
            "startTime": {
9754
                enumerable: true,
9755
                get: function () {
9756
                    return _startTime;
9757
                },
9758
                set: function (value) {
9759
                    if (typeof value !== "number") {
9760
                        throw new TypeError("Start time must be set to a number.");
9761
                    }
9762
                    _startTime = value;
9763
                    this.hasBeenReset = true;
9764
                }
9765
            },
9766
            "endTime": {
9767
                enumerable: true,
9768
                get: function () {
9769
                    return _endTime;
9770
                },
9771
                set: function (value) {
9772
                    if (typeof value !== "number") {
9773
                        throw new TypeError("End time must be set to a number.");
9774
                    }
9775
                    _endTime = value;
9776
                    this.hasBeenReset = true;
9777
                }
9778
            },
9779
            "text": {
9780
                enumerable: true,
9781
                get: function () {
9782
                    return _text;
9783
                },
9784
                set: function (value) {
9785
                    _text = "" + value;
9786
                    this.hasBeenReset = true;
9787
                }
9788
            },
9789
            "region": {
9790
                enumerable: true,
9791
                get: function () {
9792
                    return _region;
9793
                },
9794
                set: function (value) {
9795
                    _region = value;
9796
                    this.hasBeenReset = true;
9797
                }
9798
            },
9799
            "vertical": {
9800
                enumerable: true,
9801
                get: function () {
9802
                    return _vertical;
9803
                },
9804
                set: function (value) {
9805
                    var setting = findDirectionSetting(value);
9806
                    // Have to check for false because the setting an be an empty string.
9807
                    if (setting === false) {
9808
                        throw new SyntaxError("Vertical: an invalid or illegal direction string was specified.");
9809
                    }
9810
                    _vertical = setting;
9811
                    this.hasBeenReset = true;
9812
                }
9813
            },
9814
            "snapToLines": {
9815
                enumerable: true,
9816
                get: function () {
9817
                    return _snapToLines;
9818
                },
9819
                set: function (value) {
9820
                    _snapToLines = !!value;
9821
                    this.hasBeenReset = true;
9822
                }
9823
            },
9824
            "line": {
9825
                enumerable: true,
9826
                get: function () {
9827
                    return _line;
9828
                },
9829
                set: function (value) {
9830
                    if (typeof value !== "number" && value !== autoKeyword) {
9831
                        throw new SyntaxError("Line: an invalid number or illegal string was specified.");
9832
                    }
9833
                    _line = value;
9834
                    this.hasBeenReset = true;
9835
                }
9836
            },
9837
            "lineAlign": {
9838
                enumerable: true,
9839
                get: function () {
9840
                    return _lineAlign;
9841
                },
9842
                set: function (value) {
9843
                    var setting = findAlignSetting(value);
9844
                    if (!setting) {
9845
                        console.warn("lineAlign: an invalid or illegal string was specified.");
9846
                    } else {
9847
                        _lineAlign = setting;
9848
                        this.hasBeenReset = true;
9849
                    }
9850
                }
9851
            },
9852
            "position": {
9853
                enumerable: true,
9854
                get: function () {
9855
                    return _position;
9856
                },
9857
                set: function (value) {
9858
                    if (value < 0 || value > 100) {
9859
                        throw new Error("Position must be between 0 and 100.");
9860
                    }
9861
                    _position = value;
9862
                    this.hasBeenReset = true;
9863
                }
9864
            },
9865
            "positionAlign": {
9866
                enumerable: true,
9867
                get: function () {
9868
                    return _positionAlign;
9869
                },
9870
                set: function (value) {
9871
                    var setting = findAlignSetting(value);
9872
                    if (!setting) {
9873
                        console.warn("positionAlign: an invalid or illegal string was specified.");
9874
                    } else {
9875
                        _positionAlign = setting;
9876
                        this.hasBeenReset = true;
9877
                    }
9878
                }
9879
            },
9880
            "size": {
9881
                enumerable: true,
9882
                get: function () {
9883
                    return _size;
9884
                },
9885
                set: function (value) {
9886
                    if (value < 0 || value > 100) {
9887
                        throw new Error("Size must be between 0 and 100.");
9888
                    }
9889
                    _size = value;
9890
                    this.hasBeenReset = true;
9891
                }
9892
            },
9893
            "align": {
9894
                enumerable: true,
9895
                get: function () {
9896
                    return _align;
9897
                },
9898
                set: function (value) {
9899
                    var setting = findAlignSetting(value);
9900
                    if (!setting) {
9901
                        throw new SyntaxError("align: an invalid or illegal alignment string was specified.");
9902
                    }
9903
                    _align = setting;
9904
                    this.hasBeenReset = true;
9905
                }
9906
            }
9907
        });
9908
 
9909
        /**
9910
         * Other <track> spec defined properties
9911
         */
9912
 
9913
        // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
9914
        this.displayState = undefined;
9915
    }
9916
 
9917
    /**
9918
     * VTTCue methods
9919
     */
9920
 
9921
    VTTCue.prototype.getCueAsHTML = function () {
9922
        // Assume WebVTT.convertCueToDOMTree is on the global.
9923
        return WebVTT.convertCueToDOMTree(window, this.text);
9924
    };
9925
    var vttcue = VTTCue;
9926
 
9927
    /**
9928
     * Copyright 2013 vtt.js Contributors
9929
     *
9930
     * Licensed under the Apache License, Version 2.0 (the "License");
9931
     * you may not use this file except in compliance with the License.
9932
     * You may obtain a copy of the License at
9933
     *
9934
     *   http://www.apache.org/licenses/LICENSE-2.0
9935
     *
9936
     * Unless required by applicable law or agreed to in writing, software
9937
     * distributed under the License is distributed on an "AS IS" BASIS,
9938
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9939
     * See the License for the specific language governing permissions and
9940
     * limitations under the License.
9941
     */
9942
 
9943
    var scrollSetting = {
9944
        "": true,
9945
        "up": true
9946
    };
9947
    function findScrollSetting(value) {
9948
        if (typeof value !== "string") {
9949
            return false;
9950
        }
9951
        var scroll = scrollSetting[value.toLowerCase()];
9952
        return scroll ? value.toLowerCase() : false;
9953
    }
9954
    function isValidPercentValue(value) {
9955
        return typeof value === "number" && value >= 0 && value <= 100;
9956
    }
9957
 
9958
    // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface
9959
    function VTTRegion() {
9960
        var _width = 100;
9961
        var _lines = 3;
9962
        var _regionAnchorX = 0;
9963
        var _regionAnchorY = 100;
9964
        var _viewportAnchorX = 0;
9965
        var _viewportAnchorY = 100;
9966
        var _scroll = "";
9967
        Object.defineProperties(this, {
9968
            "width": {
9969
                enumerable: true,
9970
                get: function () {
9971
                    return _width;
9972
                },
9973
                set: function (value) {
9974
                    if (!isValidPercentValue(value)) {
9975
                        throw new Error("Width must be between 0 and 100.");
9976
                    }
9977
                    _width = value;
9978
                }
9979
            },
9980
            "lines": {
9981
                enumerable: true,
9982
                get: function () {
9983
                    return _lines;
9984
                },
9985
                set: function (value) {
9986
                    if (typeof value !== "number") {
9987
                        throw new TypeError("Lines must be set to a number.");
9988
                    }
9989
                    _lines = value;
9990
                }
9991
            },
9992
            "regionAnchorY": {
9993
                enumerable: true,
9994
                get: function () {
9995
                    return _regionAnchorY;
9996
                },
9997
                set: function (value) {
9998
                    if (!isValidPercentValue(value)) {
9999
                        throw new Error("RegionAnchorX must be between 0 and 100.");
10000
                    }
10001
                    _regionAnchorY = value;
10002
                }
10003
            },
10004
            "regionAnchorX": {
10005
                enumerable: true,
10006
                get: function () {
10007
                    return _regionAnchorX;
10008
                },
10009
                set: function (value) {
10010
                    if (!isValidPercentValue(value)) {
10011
                        throw new Error("RegionAnchorY must be between 0 and 100.");
10012
                    }
10013
                    _regionAnchorX = value;
10014
                }
10015
            },
10016
            "viewportAnchorY": {
10017
                enumerable: true,
10018
                get: function () {
10019
                    return _viewportAnchorY;
10020
                },
10021
                set: function (value) {
10022
                    if (!isValidPercentValue(value)) {
10023
                        throw new Error("ViewportAnchorY must be between 0 and 100.");
10024
                    }
10025
                    _viewportAnchorY = value;
10026
                }
10027
            },
10028
            "viewportAnchorX": {
10029
                enumerable: true,
10030
                get: function () {
10031
                    return _viewportAnchorX;
10032
                },
10033
                set: function (value) {
10034
                    if (!isValidPercentValue(value)) {
10035
                        throw new Error("ViewportAnchorX must be between 0 and 100.");
10036
                    }
10037
                    _viewportAnchorX = value;
10038
                }
10039
            },
10040
            "scroll": {
10041
                enumerable: true,
10042
                get: function () {
10043
                    return _scroll;
10044
                },
10045
                set: function (value) {
10046
                    var setting = findScrollSetting(value);
10047
                    // Have to check for false as an empty string is a legal value.
10048
                    if (setting === false) {
10049
                        console.warn("Scroll: an invalid or illegal string was specified.");
10050
                    } else {
10051
                        _scroll = setting;
10052
                    }
10053
                }
10054
            }
10055
        });
10056
    }
10057
    var vttregion = VTTRegion;
10058
 
10059
    var browserIndex = createCommonjsModule(function (module) {
10060
        /**
10061
         * Copyright 2013 vtt.js Contributors
10062
         *
10063
         * Licensed under the Apache License, Version 2.0 (the "License");
10064
         * you may not use this file except in compliance with the License.
10065
         * You may obtain a copy of the License at
10066
         *
10067
         *   http://www.apache.org/licenses/LICENSE-2.0
10068
         *
10069
         * Unless required by applicable law or agreed to in writing, software
10070
         * distributed under the License is distributed on an "AS IS" BASIS,
10071
         * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10072
         * See the License for the specific language governing permissions and
10073
         * limitations under the License.
10074
         */
10075
 
10076
            // Default exports for Node. Export the extended versions of VTTCue and
10077
            // VTTRegion in Node since we likely want the capability to convert back and
10078
            // forth between JSON. If we don't then it's not that big of a deal since we're
10079
            // off browser.
10080
 
10081
        var vttjs = module.exports = {
10082
                WebVTT: vtt,
10083
                VTTCue: vttcue,
10084
                VTTRegion: vttregion
10085
            };
10086
        window_1.vttjs = vttjs;
10087
        window_1.WebVTT = vttjs.WebVTT;
10088
        var cueShim = vttjs.VTTCue;
10089
        var regionShim = vttjs.VTTRegion;
10090
        var nativeVTTCue = window_1.VTTCue;
10091
        var nativeVTTRegion = window_1.VTTRegion;
10092
        vttjs.shim = function () {
10093
            window_1.VTTCue = cueShim;
10094
            window_1.VTTRegion = regionShim;
10095
        };
10096
        vttjs.restore = function () {
10097
            window_1.VTTCue = nativeVTTCue;
10098
            window_1.VTTRegion = nativeVTTRegion;
10099
        };
10100
        if (!window_1.VTTCue) {
10101
            vttjs.shim();
10102
        }
10103
    });
10104
    browserIndex.WebVTT;
10105
    browserIndex.VTTCue;
10106
    browserIndex.VTTRegion;
10107
 
10108
    /**
10109
     * @file tech.js
10110
     */
10111
 
10112
    /**
10113
     * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
10114
     * that just contains the src url alone.
10115
     * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
10116
     * `var SourceString = 'http://example.com/some-video.mp4';`
10117
     *
10118
     * @typedef {Object|string} SourceObject
10119
     *
10120
     * @property {string} src
10121
     *           The url to the source
10122
     *
10123
     * @property {string} type
10124
     *           The mime type of the source
10125
     */
10126
 
10127
    /**
10128
     * A function used by {@link Tech} to create a new {@link TextTrack}.
10129
     *
10130
     * @private
10131
     *
10132
     * @param {Tech} self
10133
     *        An instance of the Tech class.
10134
     *
10135
     * @param {string} kind
10136
     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
10137
     *
10138
     * @param {string} [label]
10139
     *        Label to identify the text track
10140
     *
10141
     * @param {string} [language]
10142
     *        Two letter language abbreviation
10143
     *
10144
     * @param {Object} [options={}]
10145
     *        An object with additional text track options
10146
     *
10147
     * @return {TextTrack}
10148
     *          The text track that was created.
10149
     */
10150
    function createTrackHelper(self, kind, label, language, options = {}) {
10151
        const tracks = self.textTracks();
10152
        options.kind = kind;
10153
        if (label) {
10154
            options.label = label;
10155
        }
10156
        if (language) {
10157
            options.language = language;
10158
        }
10159
        options.tech = self;
10160
        const track = new ALL.text.TrackClass(options);
10161
        tracks.addTrack(track);
10162
        return track;
10163
    }
10164
 
10165
    /**
10166
     * This is the base class for media playback technology controllers, such as
10167
     * {@link HTML5}
10168
     *
10169
     * @extends Component
10170
     */
10171
    class Tech extends Component$1 {
10172
        /**
10173
         * Create an instance of this Tech.
10174
         *
10175
         * @param {Object} [options]
10176
         *        The key/value store of player options.
10177
         *
10178
         * @param {Function} [ready]
10179
         *        Callback function to call when the `HTML5` Tech is ready.
10180
         */
10181
        constructor(options = {}, ready = function () {}) {
10182
            // we don't want the tech to report user activity automatically.
10183
            // This is done manually in addControlsListeners
10184
            options.reportTouchActivity = false;
10185
            super(null, options, ready);
10186
            this.onDurationChange_ = e => this.onDurationChange(e);
10187
            this.trackProgress_ = e => this.trackProgress(e);
10188
            this.trackCurrentTime_ = e => this.trackCurrentTime(e);
10189
            this.stopTrackingCurrentTime_ = e => this.stopTrackingCurrentTime(e);
10190
            this.disposeSourceHandler_ = e => this.disposeSourceHandler(e);
10191
            this.queuedHanders_ = new Set();
10192
 
10193
            // keep track of whether the current source has played at all to
10194
            // implement a very limited played()
10195
            this.hasStarted_ = false;
10196
            this.on('playing', function () {
10197
                this.hasStarted_ = true;
10198
            });
10199
            this.on('loadstart', function () {
10200
                this.hasStarted_ = false;
10201
            });
10202
            ALL.names.forEach(name => {
10203
                const props = ALL[name];
10204
                if (options && options[props.getterName]) {
10205
                    this[props.privateName] = options[props.getterName];
10206
                }
10207
            });
10208
 
10209
            // Manually track progress in cases where the browser/tech doesn't report it.
10210
            if (!this.featuresProgressEvents) {
10211
                this.manualProgressOn();
10212
            }
10213
 
10214
            // Manually track timeupdates in cases where the browser/tech doesn't report it.
10215
            if (!this.featuresTimeupdateEvents) {
10216
                this.manualTimeUpdatesOn();
10217
            }
10218
            ['Text', 'Audio', 'Video'].forEach(track => {
10219
                if (options[`native${track}Tracks`] === false) {
10220
                    this[`featuresNative${track}Tracks`] = false;
10221
                }
10222
            });
10223
            if (options.nativeCaptions === false || options.nativeTextTracks === false) {
10224
                this.featuresNativeTextTracks = false;
10225
            } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
10226
                this.featuresNativeTextTracks = true;
10227
            }
10228
            if (!this.featuresNativeTextTracks) {
10229
                this.emulateTextTracks();
10230
            }
10231
            this.preloadTextTracks = options.preloadTextTracks !== false;
10232
            this.autoRemoteTextTracks_ = new ALL.text.ListClass();
10233
            this.initTrackListeners();
10234
 
10235
            // Turn on component tap events only if not using native controls
10236
            if (!options.nativeControlsForTouch) {
10237
                this.emitTapEvents();
10238
            }
10239
            if (this.constructor) {
10240
                this.name_ = this.constructor.name || 'Unknown Tech';
10241
            }
10242
        }
10243
 
10244
        /**
10245
         * A special function to trigger source set in a way that will allow player
10246
         * to re-trigger if the player or tech are not ready yet.
10247
         *
10248
         * @fires Tech#sourceset
10249
         * @param {string} src The source string at the time of the source changing.
10250
         */
10251
        triggerSourceset(src) {
10252
            if (!this.isReady_) {
10253
                // on initial ready we have to trigger source set
10254
                // 1ms after ready so that player can watch for it.
10255
                this.one('ready', () => this.setTimeout(() => this.triggerSourceset(src), 1));
10256
            }
10257
 
10258
            /**
10259
             * Fired when the source is set on the tech causing the media element
10260
             * to reload.
10261
             *
10262
             * @see {@link Player#event:sourceset}
10263
             * @event Tech#sourceset
10264
             * @type {Event}
10265
             */
10266
            this.trigger({
10267
                src,
10268
                type: 'sourceset'
10269
            });
10270
        }
10271
 
10272
        /* Fallbacks for unsupported event types
10273
    ================================================================================ */
10274
 
10275
        /**
10276
         * Polyfill the `progress` event for browsers that don't support it natively.
10277
         *
10278
         * @see {@link Tech#trackProgress}
10279
         */
10280
        manualProgressOn() {
10281
            this.on('durationchange', this.onDurationChange_);
10282
            this.manualProgress = true;
10283
 
10284
            // Trigger progress watching when a source begins loading
10285
            this.one('ready', this.trackProgress_);
10286
        }
10287
 
10288
        /**
10289
         * Turn off the polyfill for `progress` events that was created in
10290
         * {@link Tech#manualProgressOn}
10291
         */
10292
        manualProgressOff() {
10293
            this.manualProgress = false;
10294
            this.stopTrackingProgress();
10295
            this.off('durationchange', this.onDurationChange_);
10296
        }
10297
 
10298
        /**
10299
         * This is used to trigger a `progress` event when the buffered percent changes. It
10300
         * sets an interval function that will be called every 500 milliseconds to check if the
10301
         * buffer end percent has changed.
10302
         *
10303
         * > This function is called by {@link Tech#manualProgressOn}
10304
         *
10305
         * @param {Event} event
10306
         *        The `ready` event that caused this to run.
10307
         *
10308
         * @listens Tech#ready
10309
         * @fires Tech#progress
10310
         */
10311
        trackProgress(event) {
10312
            this.stopTrackingProgress();
10313
            this.progressInterval = this.setInterval(bind_(this, function () {
10314
                // Don't trigger unless buffered amount is greater than last time
10315
 
10316
                const numBufferedPercent = this.bufferedPercent();
10317
                if (this.bufferedPercent_ !== numBufferedPercent) {
10318
                    /**
10319
                     * See {@link Player#progress}
10320
                     *
10321
                     * @event Tech#progress
10322
                     * @type {Event}
10323
                     */
10324
                    this.trigger('progress');
10325
                }
10326
                this.bufferedPercent_ = numBufferedPercent;
10327
                if (numBufferedPercent === 1) {
10328
                    this.stopTrackingProgress();
10329
                }
10330
            }), 500);
10331
        }
10332
 
10333
        /**
10334
         * Update our internal duration on a `durationchange` event by calling
10335
         * {@link Tech#duration}.
10336
         *
10337
         * @param {Event} event
10338
         *        The `durationchange` event that caused this to run.
10339
         *
10340
         * @listens Tech#durationchange
10341
         */
10342
        onDurationChange(event) {
10343
            this.duration_ = this.duration();
10344
        }
10345
 
10346
        /**
10347
         * Get and create a `TimeRange` object for buffering.
10348
         *
10349
         * @return { import('../utils/time').TimeRange }
10350
         *         The time range object that was created.
10351
         */
10352
        buffered() {
10353
            return createTimeRanges$1(0, 0);
10354
        }
10355
 
10356
        /**
10357
         * Get the percentage of the current video that is currently buffered.
10358
         *
10359
         * @return {number}
10360
         *         A number from 0 to 1 that represents the decimal percentage of the
10361
         *         video that is buffered.
10362
         *
10363
         */
10364
        bufferedPercent() {
10365
            return bufferedPercent(this.buffered(), this.duration_);
10366
        }
10367
 
10368
        /**
10369
         * Turn off the polyfill for `progress` events that was created in
10370
         * {@link Tech#manualProgressOn}
10371
         * Stop manually tracking progress events by clearing the interval that was set in
10372
         * {@link Tech#trackProgress}.
10373
         */
10374
        stopTrackingProgress() {
10375
            this.clearInterval(this.progressInterval);
10376
        }
10377
 
10378
        /**
10379
         * Polyfill the `timeupdate` event for browsers that don't support it.
10380
         *
10381
         * @see {@link Tech#trackCurrentTime}
10382
         */
10383
        manualTimeUpdatesOn() {
10384
            this.manualTimeUpdates = true;
10385
            this.on('play', this.trackCurrentTime_);
10386
            this.on('pause', this.stopTrackingCurrentTime_);
10387
        }
10388
 
10389
        /**
10390
         * Turn off the polyfill for `timeupdate` events that was created in
10391
         * {@link Tech#manualTimeUpdatesOn}
10392
         */
10393
        manualTimeUpdatesOff() {
10394
            this.manualTimeUpdates = false;
10395
            this.stopTrackingCurrentTime();
10396
            this.off('play', this.trackCurrentTime_);
10397
            this.off('pause', this.stopTrackingCurrentTime_);
10398
        }
10399
 
10400
        /**
10401
         * Sets up an interval function to track current time and trigger `timeupdate` every
10402
         * 250 milliseconds.
10403
         *
10404
         * @listens Tech#play
10405
         * @triggers Tech#timeupdate
10406
         */
10407
        trackCurrentTime() {
10408
            if (this.currentTimeInterval) {
10409
                this.stopTrackingCurrentTime();
10410
            }
10411
            this.currentTimeInterval = this.setInterval(function () {
10412
                /**
10413
                 * Triggered at an interval of 250ms to indicated that time is passing in the video.
10414
                 *
10415
                 * @event Tech#timeupdate
10416
                 * @type {Event}
10417
                 */
10418
                this.trigger({
10419
                    type: 'timeupdate',
10420
                    target: this,
10421
                    manuallyTriggered: true
10422
                });
10423
 
10424
                // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
10425
            }, 250);
10426
        }
10427
 
10428
        /**
10429
         * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
10430
         * `timeupdate` event is no longer triggered.
10431
         *
10432
         * @listens {Tech#pause}
10433
         */
10434
        stopTrackingCurrentTime() {
10435
            this.clearInterval(this.currentTimeInterval);
10436
 
10437
            // #1002 - if the video ends right before the next timeupdate would happen,
10438
            // the progress bar won't make it all the way to the end
10439
            this.trigger({
10440
                type: 'timeupdate',
10441
                target: this,
10442
                manuallyTriggered: true
10443
            });
10444
        }
10445
 
10446
        /**
10447
         * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
10448
         * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
10449
         *
10450
         * @fires Component#dispose
10451
         */
10452
        dispose() {
10453
            // clear out all tracks because we can't reuse them between techs
10454
            this.clearTracks(NORMAL.names);
10455
 
10456
            // Turn off any manual progress or timeupdate tracking
10457
            if (this.manualProgress) {
10458
                this.manualProgressOff();
10459
            }
10460
            if (this.manualTimeUpdates) {
10461
                this.manualTimeUpdatesOff();
10462
            }
10463
            super.dispose();
10464
        }
10465
 
10466
        /**
10467
         * Clear out a single `TrackList` or an array of `TrackLists` given their names.
10468
         *
10469
         * > Note: Techs without source handlers should call this between sources for `video`
10470
         *         & `audio` tracks. You don't want to use them between tracks!
10471
         *
10472
         * @param {string[]|string} types
10473
         *        TrackList names to clear, valid names are `video`, `audio`, and
10474
         *        `text`.
10475
         */
10476
        clearTracks(types) {
10477
            types = [].concat(types);
10478
            // clear out all tracks because we can't reuse them between techs
10479
            types.forEach(type => {
10480
                const list = this[`${type}Tracks`]() || [];
10481
                let i = list.length;
10482
                while (i--) {
10483
                    const track = list[i];
10484
                    if (type === 'text') {
10485
                        this.removeRemoteTextTrack(track);
10486
                    }
10487
                    list.removeTrack(track);
10488
                }
10489
            });
10490
        }
10491
 
10492
        /**
10493
         * Remove any TextTracks added via addRemoteTextTrack that are
10494
         * flagged for automatic garbage collection
10495
         */
10496
        cleanupAutoTextTracks() {
10497
            const list = this.autoRemoteTextTracks_ || [];
10498
            let i = list.length;
10499
            while (i--) {
10500
                const track = list[i];
10501
                this.removeRemoteTextTrack(track);
10502
            }
10503
        }
10504
 
10505
        /**
10506
         * Reset the tech, which will removes all sources and reset the internal readyState.
10507
         *
10508
         * @abstract
10509
         */
10510
        reset() {}
10511
 
10512
        /**
10513
         * Get the value of `crossOrigin` from the tech.
10514
         *
10515
         * @abstract
10516
         *
10517
         * @see {Html5#crossOrigin}
10518
         */
10519
        crossOrigin() {}
10520
 
10521
        /**
10522
         * Set the value of `crossOrigin` on the tech.
10523
         *
10524
         * @abstract
10525
         *
10526
         * @param {string} crossOrigin the crossOrigin value
10527
         * @see {Html5#setCrossOrigin}
10528
         */
10529
        setCrossOrigin() {}
10530
 
10531
        /**
10532
         * Get or set an error on the Tech.
10533
         *
10534
         * @param {MediaError} [err]
10535
         *        Error to set on the Tech
10536
         *
10537
         * @return {MediaError|null}
10538
         *         The current error object on the tech, or null if there isn't one.
10539
         */
10540
        error(err) {
10541
            if (err !== undefined) {
10542
                this.error_ = new MediaError(err);
10543
                this.trigger('error');
10544
            }
10545
            return this.error_;
10546
        }
10547
 
10548
        /**
10549
         * Returns the `TimeRange`s that have been played through for the current source.
10550
         *
10551
         * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
10552
         *         It only checks whether the source has played at all or not.
10553
         *
10554
         * @return { import('../utils/time').TimeRange }
10555
         *         - A single time range if this video has played
10556
         *         - An empty set of ranges if not.
10557
         */
10558
        played() {
10559
            if (this.hasStarted_) {
10560
                return createTimeRanges$1(0, 0);
10561
            }
10562
            return createTimeRanges$1();
10563
        }
10564
 
10565
        /**
10566
         * Start playback
10567
         *
10568
         * @abstract
10569
         *
10570
         * @see {Html5#play}
10571
         */
10572
        play() {}
10573
 
10574
        /**
10575
         * Set whether we are scrubbing or not
10576
         *
10577
         * @abstract
10578
         * @param {boolean} _isScrubbing
10579
         *                  - true for we are currently scrubbing
10580
         *                  - false for we are no longer scrubbing
10581
         *
10582
         * @see {Html5#setScrubbing}
10583
         */
10584
        setScrubbing(_isScrubbing) {}
10585
 
10586
        /**
10587
         * Get whether we are scrubbing or not
10588
         *
10589
         * @abstract
10590
         *
10591
         * @see {Html5#scrubbing}
10592
         */
10593
        scrubbing() {}
10594
 
10595
        /**
10596
         * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
10597
         * previously called.
10598
         *
10599
         * @param {number} _seconds
10600
         *        Set the current time of the media to this.
10601
         * @fires Tech#timeupdate
10602
         */
10603
        setCurrentTime(_seconds) {
10604
            // improve the accuracy of manual timeupdates
10605
            if (this.manualTimeUpdates) {
10606
                /**
10607
                 * A manual `timeupdate` event.
10608
                 *
10609
                 * @event Tech#timeupdate
10610
                 * @type {Event}
10611
                 */
10612
                this.trigger({
10613
                    type: 'timeupdate',
10614
                    target: this,
10615
                    manuallyTriggered: true
10616
                });
10617
            }
10618
        }
10619
 
10620
        /**
10621
         * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
10622
         * {@link TextTrackList} events.
10623
         *
10624
         * This adds {@link EventTarget~EventListeners} for `addtrack`, and  `removetrack`.
10625
         *
10626
         * @fires Tech#audiotrackchange
10627
         * @fires Tech#videotrackchange
10628
         * @fires Tech#texttrackchange
10629
         */
10630
        initTrackListeners() {
10631
            /**
10632
             * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
10633
             *
10634
             * @event Tech#audiotrackchange
10635
             * @type {Event}
10636
             */
10637
 
10638
            /**
10639
             * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
10640
             *
10641
             * @event Tech#videotrackchange
10642
             * @type {Event}
10643
             */
10644
 
10645
            /**
10646
             * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
10647
             *
10648
             * @event Tech#texttrackchange
10649
             * @type {Event}
10650
             */
10651
            NORMAL.names.forEach(name => {
10652
                const props = NORMAL[name];
10653
                const trackListChanges = () => {
10654
                    this.trigger(`${name}trackchange`);
10655
                };
10656
                const tracks = this[props.getterName]();
10657
                tracks.addEventListener('removetrack', trackListChanges);
10658
                tracks.addEventListener('addtrack', trackListChanges);
10659
                this.on('dispose', () => {
10660
                    tracks.removeEventListener('removetrack', trackListChanges);
10661
                    tracks.removeEventListener('addtrack', trackListChanges);
10662
                });
10663
            });
10664
        }
10665
 
10666
        /**
10667
         * Emulate TextTracks using vtt.js if necessary
10668
         *
10669
         * @fires Tech#vttjsloaded
10670
         * @fires Tech#vttjserror
10671
         */
10672
        addWebVttScript_() {
10673
            if (window.WebVTT) {
10674
                return;
10675
            }
10676
 
10677
            // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
10678
            // signals that the Tech is ready at which point Tech.el_ is part of the DOM
10679
            // before inserting the WebVTT script
10680
            if (document.body.contains(this.el())) {
10681
                // load via require if available and vtt.js script location was not passed in
10682
                // as an option. novtt builds will turn the above require call into an empty object
10683
                // which will cause this if check to always fail.
10684
                if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) {
10685
                    this.trigger('vttjsloaded');
10686
                    return;
10687
                }
10688
 
10689
                // load vtt.js via the script location option or the cdn of no location was
10690
                // passed in
10691
                const script = document.createElement('script');
10692
                script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
10693
                script.onload = () => {
10694
                    /**
10695
                     * Fired when vtt.js is loaded.
10696
                     *
10697
                     * @event Tech#vttjsloaded
10698
                     * @type {Event}
10699
                     */
10700
                    this.trigger('vttjsloaded');
10701
                };
10702
                script.onerror = () => {
10703
                    /**
10704
                     * Fired when vtt.js was not loaded due to an error
10705
                     *
10706
                     * @event Tech#vttjsloaded
10707
                     * @type {Event}
10708
                     */
10709
                    this.trigger('vttjserror');
10710
                };
10711
                this.on('dispose', () => {
10712
                    script.onload = null;
10713
                    script.onerror = null;
10714
                });
10715
                // but have not loaded yet and we set it to true before the inject so that
10716
                // we don't overwrite the injected window.WebVTT if it loads right away
10717
                window.WebVTT = true;
10718
                this.el().parentNode.appendChild(script);
10719
            } else {
10720
                this.ready(this.addWebVttScript_);
10721
            }
10722
        }
10723
 
10724
        /**
10725
         * Emulate texttracks
10726
         *
10727
         */
10728
        emulateTextTracks() {
10729
            const tracks = this.textTracks();
10730
            const remoteTracks = this.remoteTextTracks();
10731
            const handleAddTrack = e => tracks.addTrack(e.track);
10732
            const handleRemoveTrack = e => tracks.removeTrack(e.track);
10733
            remoteTracks.on('addtrack', handleAddTrack);
10734
            remoteTracks.on('removetrack', handleRemoveTrack);
10735
            this.addWebVttScript_();
10736
            const updateDisplay = () => this.trigger('texttrackchange');
10737
            const textTracksChanges = () => {
10738
                updateDisplay();
10739
                for (let i = 0; i < tracks.length; i++) {
10740
                    const track = tracks[i];
10741
                    track.removeEventListener('cuechange', updateDisplay);
10742
                    if (track.mode === 'showing') {
10743
                        track.addEventListener('cuechange', updateDisplay);
10744
                    }
10745
                }
10746
            };
10747
            textTracksChanges();
10748
            tracks.addEventListener('change', textTracksChanges);
10749
            tracks.addEventListener('addtrack', textTracksChanges);
10750
            tracks.addEventListener('removetrack', textTracksChanges);
10751
            this.on('dispose', function () {
10752
                remoteTracks.off('addtrack', handleAddTrack);
10753
                remoteTracks.off('removetrack', handleRemoveTrack);
10754
                tracks.removeEventListener('change', textTracksChanges);
10755
                tracks.removeEventListener('addtrack', textTracksChanges);
10756
                tracks.removeEventListener('removetrack', textTracksChanges);
10757
                for (let i = 0; i < tracks.length; i++) {
10758
                    const track = tracks[i];
10759
                    track.removeEventListener('cuechange', updateDisplay);
10760
                }
10761
            });
10762
        }
10763
 
10764
        /**
10765
         * Create and returns a remote {@link TextTrack} object.
10766
         *
10767
         * @param {string} kind
10768
         *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
10769
         *
10770
         * @param {string} [label]
10771
         *        Label to identify the text track
10772
         *
10773
         * @param {string} [language]
10774
         *        Two letter language abbreviation
10775
         *
10776
         * @return {TextTrack}
10777
         *         The TextTrack that gets created.
10778
         */
10779
        addTextTrack(kind, label, language) {
10780
            if (!kind) {
10781
                throw new Error('TextTrack kind is required but was not provided');
10782
            }
10783
            return createTrackHelper(this, kind, label, language);
10784
        }
10785
 
10786
        /**
10787
         * Create an emulated TextTrack for use by addRemoteTextTrack
10788
         *
10789
         * This is intended to be overridden by classes that inherit from
10790
         * Tech in order to create native or custom TextTracks.
10791
         *
10792
         * @param {Object} options
10793
         *        The object should contain the options to initialize the TextTrack with.
10794
         *
10795
         * @param {string} [options.kind]
10796
         *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
10797
         *
10798
         * @param {string} [options.label].
10799
         *        Label to identify the text track
10800
         *
10801
         * @param {string} [options.language]
10802
         *        Two letter language abbreviation.
10803
         *
10804
         * @return {HTMLTrackElement}
10805
         *         The track element that gets created.
10806
         */
10807
        createRemoteTextTrack(options) {
10808
            const track = merge$2(options, {
10809
                tech: this
10810
            });
10811
            return new REMOTE.remoteTextEl.TrackClass(track);
10812
        }
10813
 
10814
        /**
10815
         * Creates a remote text track object and returns an html track element.
10816
         *
10817
         * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
10818
         *
10819
         * @param {Object} options
10820
         *        See {@link Tech#createRemoteTextTrack} for more detailed properties.
10821
         *
10822
         * @param {boolean} [manualCleanup=false]
10823
         *        - When false: the TextTrack will be automatically removed from the video
10824
         *          element whenever the source changes
10825
         *        - When True: The TextTrack will have to be cleaned up manually
10826
         *
10827
         * @return {HTMLTrackElement}
10828
         *         An Html Track Element.
10829
         *
10830
         */
10831
        addRemoteTextTrack(options = {}, manualCleanup) {
10832
            const htmlTrackElement = this.createRemoteTextTrack(options);
10833
            if (typeof manualCleanup !== 'boolean') {
10834
                manualCleanup = false;
10835
            }
10836
 
10837
            // store HTMLTrackElement and TextTrack to remote list
10838
            this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
10839
            this.remoteTextTracks().addTrack(htmlTrackElement.track);
10840
            if (manualCleanup === false) {
10841
                // create the TextTrackList if it doesn't exist
10842
                this.ready(() => this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track));
10843
            }
10844
            return htmlTrackElement;
10845
        }
10846
 
10847
        /**
10848
         * Remove a remote text track from the remote `TextTrackList`.
10849
         *
10850
         * @param {TextTrack} track
10851
         *        `TextTrack` to remove from the `TextTrackList`
10852
         */
10853
        removeRemoteTextTrack(track) {
10854
            const trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
10855
 
10856
            // remove HTMLTrackElement and TextTrack from remote list
10857
            this.remoteTextTrackEls().removeTrackElement_(trackElement);
10858
            this.remoteTextTracks().removeTrack(track);
10859
            this.autoRemoteTextTracks_.removeTrack(track);
10860
        }
10861
 
10862
        /**
10863
         * Gets available media playback quality metrics as specified by the W3C's Media
10864
         * Playback Quality API.
10865
         *
10866
         * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
10867
         *
10868
         * @return {Object}
10869
         *         An object with supported media playback quality metrics
10870
         *
10871
         * @abstract
10872
         */
10873
        getVideoPlaybackQuality() {
10874
            return {};
10875
        }
10876
 
10877
        /**
10878
         * Attempt to create a floating video window always on top of other windows
10879
         * so that users may continue consuming media while they interact with other
10880
         * content sites, or applications on their device.
10881
         *
10882
         * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
10883
         *
10884
         * @return {Promise|undefined}
10885
         *         A promise with a Picture-in-Picture window if the browser supports
10886
         *         Promises (or one was passed in as an option). It returns undefined
10887
         *         otherwise.
10888
         *
10889
         * @abstract
10890
         */
10891
        requestPictureInPicture() {
10892
            return Promise.reject();
10893
        }
10894
 
10895
        /**
10896
         * A method to check for the value of the 'disablePictureInPicture' <video> property.
10897
         * Defaults to true, as it should be considered disabled if the tech does not support pip
10898
         *
10899
         * @abstract
10900
         */
10901
        disablePictureInPicture() {
10902
            return true;
10903
        }
10904
 
10905
        /**
10906
         * A method to set or unset the 'disablePictureInPicture' <video> property.
10907
         *
10908
         * @abstract
10909
         */
10910
        setDisablePictureInPicture() {}
10911
 
10912
        /**
10913
         * A fallback implementation of requestVideoFrameCallback using requestAnimationFrame
10914
         *
10915
         * @param {function} cb
10916
         * @return {number} request id
10917
         */
10918
        requestVideoFrameCallback(cb) {
10919
            const id = newGUID();
10920
            if (!this.isReady_ || this.paused()) {
10921
                this.queuedHanders_.add(id);
10922
                this.one('playing', () => {
10923
                    if (this.queuedHanders_.has(id)) {
10924
                        this.queuedHanders_.delete(id);
10925
                        cb();
10926
                    }
10927
                });
10928
            } else {
10929
                this.requestNamedAnimationFrame(id, cb);
10930
            }
10931
            return id;
10932
        }
10933
 
10934
        /**
10935
         * A fallback implementation of cancelVideoFrameCallback
10936
         *
10937
         * @param {number} id id of callback to be cancelled
10938
         */
10939
        cancelVideoFrameCallback(id) {
10940
            if (this.queuedHanders_.has(id)) {
10941
                this.queuedHanders_.delete(id);
10942
            } else {
10943
                this.cancelNamedAnimationFrame(id);
10944
            }
10945
        }
10946
 
10947
        /**
10948
         * A method to set a poster from a `Tech`.
10949
         *
10950
         * @abstract
10951
         */
10952
        setPoster() {}
10953
 
10954
        /**
10955
         * A method to check for the presence of the 'playsinline' <video> attribute.
10956
         *
10957
         * @abstract
10958
         */
10959
        playsinline() {}
10960
 
10961
        /**
10962
         * A method to set or unset the 'playsinline' <video> attribute.
10963
         *
10964
         * @abstract
10965
         */
10966
        setPlaysinline() {}
10967
 
10968
        /**
10969
         * Attempt to force override of native audio tracks.
10970
         *
10971
         * @param {boolean} override - If set to true native audio will be overridden,
10972
         * otherwise native audio will potentially be used.
10973
         *
10974
         * @abstract
10975
         */
10976
        overrideNativeAudioTracks(override) {}
10977
 
10978
        /**
10979
         * Attempt to force override of native video tracks.
10980
         *
10981
         * @param {boolean} override - If set to true native video will be overridden,
10982
         * otherwise native video will potentially be used.
10983
         *
10984
         * @abstract
10985
         */
10986
        overrideNativeVideoTracks(override) {}
10987
 
10988
        /**
10989
         * Check if the tech can support the given mime-type.
10990
         *
10991
         * The base tech does not support any type, but source handlers might
10992
         * overwrite this.
10993
         *
10994
         * @param  {string} _type
10995
         *         The mimetype to check for support
10996
         *
10997
         * @return {string}
10998
         *         'probably', 'maybe', or empty string
10999
         *
11000
         * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
11001
         *
11002
         * @abstract
11003
         */
11004
        canPlayType(_type) {
11005
            return '';
11006
        }
11007
 
11008
        /**
11009
         * Check if the type is supported by this tech.
11010
         *
11011
         * The base tech does not support any type, but source handlers might
11012
         * overwrite this.
11013
         *
11014
         * @param {string} _type
11015
         *        The media type to check
11016
         * @return {string} Returns the native video element's response
11017
         */
11018
        static canPlayType(_type) {
11019
            return '';
11020
        }
11021
 
11022
        /**
11023
         * Check if the tech can support the given source
11024
         *
11025
         * @param {Object} srcObj
11026
         *        The source object
11027
         * @param {Object} options
11028
         *        The options passed to the tech
11029
         * @return {string} 'probably', 'maybe', or '' (empty string)
11030
         */
11031
        static canPlaySource(srcObj, options) {
11032
            return Tech.canPlayType(srcObj.type);
11033
        }
11034
 
11035
        /*
11036
     * Return whether the argument is a Tech or not.
11037
     * Can be passed either a Class like `Html5` or a instance like `player.tech_`
11038
     *
11039
     * @param {Object} component
11040
     *        The item to check
11041
     *
11042
     * @return {boolean}
11043
     *         Whether it is a tech or not
11044
     *         - True if it is a tech
11045
     *         - False if it is not
11046
     */
11047
        static isTech(component) {
11048
            return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
11049
        }
11050
 
11051
        /**
11052
         * Registers a `Tech` into a shared list for videojs.
11053
         *
11054
         * @param {string} name
11055
         *        Name of the `Tech` to register.
11056
         *
11057
         * @param {Object} tech
11058
         *        The `Tech` class to register.
11059
         */
11060
        static registerTech(name, tech) {
11061
            if (!Tech.techs_) {
11062
                Tech.techs_ = {};
11063
            }
11064
            if (!Tech.isTech(tech)) {
11065
                throw new Error(`Tech ${name} must be a Tech`);
11066
            }
11067
            if (!Tech.canPlayType) {
11068
                throw new Error('Techs must have a static canPlayType method on them');
11069
            }
11070
            if (!Tech.canPlaySource) {
11071
                throw new Error('Techs must have a static canPlaySource method on them');
11072
            }
11073
            name = toTitleCase$1(name);
11074
            Tech.techs_[name] = tech;
11075
            Tech.techs_[toLowerCase(name)] = tech;
11076
            if (name !== 'Tech') {
11077
                // camel case the techName for use in techOrder
11078
                Tech.defaultTechOrder_.push(name);
11079
            }
11080
            return tech;
11081
        }
11082
 
11083
        /**
11084
         * Get a `Tech` from the shared list by name.
11085
         *
11086
         * @param {string} name
11087
         *        `camelCase` or `TitleCase` name of the Tech to get
11088
         *
11089
         * @return {Tech|undefined}
11090
         *         The `Tech` or undefined if there was no tech with the name requested.
11091
         */
11092
        static getTech(name) {
11093
            if (!name) {
11094
                return;
11095
            }
11096
            if (Tech.techs_ && Tech.techs_[name]) {
11097
                return Tech.techs_[name];
11098
            }
11099
            name = toTitleCase$1(name);
11100
            if (window && window.videojs && window.videojs[name]) {
11101
                log$1.warn(`The ${name} tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)`);
11102
                return window.videojs[name];
11103
            }
11104
        }
11105
    }
11106
 
11107
    /**
11108
     * Get the {@link VideoTrackList}
11109
     *
11110
     * @returns {VideoTrackList}
11111
     * @method Tech.prototype.videoTracks
11112
     */
11113
 
11114
    /**
11115
     * Get the {@link AudioTrackList}
11116
     *
11117
     * @returns {AudioTrackList}
11118
     * @method Tech.prototype.audioTracks
11119
     */
11120
 
11121
    /**
11122
     * Get the {@link TextTrackList}
11123
     *
11124
     * @returns {TextTrackList}
11125
     * @method Tech.prototype.textTracks
11126
     */
11127
 
11128
    /**
11129
     * Get the remote element {@link TextTrackList}
11130
     *
11131
     * @returns {TextTrackList}
11132
     * @method Tech.prototype.remoteTextTracks
11133
     */
11134
 
11135
    /**
11136
     * Get the remote element {@link HtmlTrackElementList}
11137
     *
11138
     * @returns {HtmlTrackElementList}
11139
     * @method Tech.prototype.remoteTextTrackEls
11140
     */
11141
 
11142
    ALL.names.forEach(function (name) {
11143
        const props = ALL[name];
11144
        Tech.prototype[props.getterName] = function () {
11145
            this[props.privateName] = this[props.privateName] || new props.ListClass();
11146
            return this[props.privateName];
11147
        };
11148
    });
11149
 
11150
    /**
11151
     * List of associated text tracks
11152
     *
11153
     * @type {TextTrackList}
11154
     * @private
11155
     * @property Tech#textTracks_
11156
     */
11157
 
11158
    /**
11159
     * List of associated audio tracks.
11160
     *
11161
     * @type {AudioTrackList}
11162
     * @private
11163
     * @property Tech#audioTracks_
11164
     */
11165
 
11166
    /**
11167
     * List of associated video tracks.
11168
     *
11169
     * @type {VideoTrackList}
11170
     * @private
11171
     * @property Tech#videoTracks_
11172
     */
11173
 
11174
    /**
11175
     * Boolean indicating whether the `Tech` supports volume control.
11176
     *
11177
     * @type {boolean}
11178
     * @default
11179
     */
11180
    Tech.prototype.featuresVolumeControl = true;
11181
 
11182
    /**
11183
     * Boolean indicating whether the `Tech` supports muting volume.
11184
     *
11185
     * @type {boolean}
11186
     * @default
11187
     */
11188
    Tech.prototype.featuresMuteControl = true;
11189
 
11190
    /**
11191
     * Boolean indicating whether the `Tech` supports fullscreen resize control.
11192
     * Resizing plugins using request fullscreen reloads the plugin
11193
     *
11194
     * @type {boolean}
11195
     * @default
11196
     */
11197
    Tech.prototype.featuresFullscreenResize = false;
11198
 
11199
    /**
11200
     * Boolean indicating whether the `Tech` supports changing the speed at which the video
11201
     * plays. Examples:
11202
     *   - Set player to play 2x (twice) as fast
11203
     *   - Set player to play 0.5x (half) as fast
11204
     *
11205
     * @type {boolean}
11206
     * @default
11207
     */
11208
    Tech.prototype.featuresPlaybackRate = false;
11209
 
11210
    /**
11211
     * Boolean indicating whether the `Tech` supports the `progress` event.
11212
     * This will be used to determine if {@link Tech#manualProgressOn} should be called.
11213
     *
11214
     * @type {boolean}
11215
     * @default
11216
     */
11217
    Tech.prototype.featuresProgressEvents = false;
11218
 
11219
    /**
11220
     * Boolean indicating whether the `Tech` supports the `sourceset` event.
11221
     *
11222
     * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
11223
     * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
11224
     * a new source.
11225
     *
11226
     * @type {boolean}
11227
     * @default
11228
     */
11229
    Tech.prototype.featuresSourceset = false;
11230
 
11231
    /**
11232
     * Boolean indicating whether the `Tech` supports the `timeupdate` event.
11233
     * This will be used to determine if {@link Tech#manualTimeUpdates} should be called.
11234
     *
11235
     * @type {boolean}
11236
     * @default
11237
     */
11238
    Tech.prototype.featuresTimeupdateEvents = false;
11239
 
11240
    /**
11241
     * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
11242
     * This will help us integrate with native `TextTrack`s if the browser supports them.
11243
     *
11244
     * @type {boolean}
11245
     * @default
11246
     */
11247
    Tech.prototype.featuresNativeTextTracks = false;
11248
 
11249
    /**
11250
     * Boolean indicating whether the `Tech` supports `requestVideoFrameCallback`.
11251
     *
11252
     * @type {boolean}
11253
     * @default
11254
     */
11255
    Tech.prototype.featuresVideoFrameCallback = false;
11256
 
11257
    /**
11258
     * A functional mixin for techs that want to use the Source Handler pattern.
11259
     * Source handlers are scripts for handling specific formats.
11260
     * The source handler pattern is used for adaptive formats (HLS, DASH) that
11261
     * manually load video data and feed it into a Source Buffer (Media Source Extensions)
11262
     * Example: `Tech.withSourceHandlers.call(MyTech);`
11263
     *
11264
     * @param {Tech} _Tech
11265
     *        The tech to add source handler functions to.
11266
     *
11267
     * @mixes Tech~SourceHandlerAdditions
11268
     */
11269
    Tech.withSourceHandlers = function (_Tech) {
11270
        /**
11271
         * Register a source handler
11272
         *
11273
         * @param {Function} handler
11274
         *        The source handler class
11275
         *
11276
         * @param {number} [index]
11277
         *        Register it at the following index
11278
         */
11279
        _Tech.registerSourceHandler = function (handler, index) {
11280
            let handlers = _Tech.sourceHandlers;
11281
            if (!handlers) {
11282
                handlers = _Tech.sourceHandlers = [];
11283
            }
11284
            if (index === undefined) {
11285
                // add to the end of the list
11286
                index = handlers.length;
11287
            }
11288
            handlers.splice(index, 0, handler);
11289
        };
11290
 
11291
        /**
11292
         * Check if the tech can support the given type. Also checks the
11293
         * Techs sourceHandlers.
11294
         *
11295
         * @param {string} type
11296
         *         The mimetype to check.
11297
         *
11298
         * @return {string}
11299
         *         'probably', 'maybe', or '' (empty string)
11300
         */
11301
        _Tech.canPlayType = function (type) {
11302
            const handlers = _Tech.sourceHandlers || [];
11303
            let can;
11304
            for (let i = 0; i < handlers.length; i++) {
11305
                can = handlers[i].canPlayType(type);
11306
                if (can) {
11307
                    return can;
11308
                }
11309
            }
11310
            return '';
11311
        };
11312
 
11313
        /**
11314
         * Returns the first source handler that supports the source.
11315
         *
11316
         * TODO: Answer question: should 'probably' be prioritized over 'maybe'
11317
         *
11318
         * @param {SourceObject} source
11319
         *        The source object
11320
         *
11321
         * @param {Object} options
11322
         *        The options passed to the tech
11323
         *
11324
         * @return {SourceHandler|null}
11325
         *          The first source handler that supports the source or null if
11326
         *          no SourceHandler supports the source
11327
         */
11328
        _Tech.selectSourceHandler = function (source, options) {
11329
            const handlers = _Tech.sourceHandlers || [];
11330
            let can;
11331
            for (let i = 0; i < handlers.length; i++) {
11332
                can = handlers[i].canHandleSource(source, options);
11333
                if (can) {
11334
                    return handlers[i];
11335
                }
11336
            }
11337
            return null;
11338
        };
11339
 
11340
        /**
11341
         * Check if the tech can support the given source.
11342
         *
11343
         * @param {SourceObject} srcObj
11344
         *        The source object
11345
         *
11346
         * @param {Object} options
11347
         *        The options passed to the tech
11348
         *
11349
         * @return {string}
11350
         *         'probably', 'maybe', or '' (empty string)
11351
         */
11352
        _Tech.canPlaySource = function (srcObj, options) {
11353
            const sh = _Tech.selectSourceHandler(srcObj, options);
11354
            if (sh) {
11355
                return sh.canHandleSource(srcObj, options);
11356
            }
11357
            return '';
11358
        };
11359
 
11360
        /**
11361
         * When using a source handler, prefer its implementation of
11362
         * any function normally provided by the tech.
11363
         */
11364
        const deferrable = ['seekable', 'seeking', 'duration'];
11365
 
11366
        /**
11367
         * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
11368
         * function if it exists, with a fallback to the Techs seekable function.
11369
         *
11370
         * @method _Tech.seekable
11371
         */
11372
 
11373
        /**
11374
         * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
11375
         * function if it exists, otherwise it will fallback to the techs duration function.
11376
         *
11377
         * @method _Tech.duration
11378
         */
11379
 
11380
        deferrable.forEach(function (fnName) {
11381
            const originalFn = this[fnName];
11382
            if (typeof originalFn !== 'function') {
11383
                return;
11384
            }
11385
            this[fnName] = function () {
11386
                if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
11387
                    return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
11388
                }
11389
                return originalFn.apply(this, arguments);
11390
            };
11391
        }, _Tech.prototype);
11392
 
11393
        /**
11394
         * Create a function for setting the source using a source object
11395
         * and source handlers.
11396
         * Should never be called unless a source handler was found.
11397
         *
11398
         * @param {SourceObject} source
11399
         *        A source object with src and type keys
11400
         */
11401
        _Tech.prototype.setSource = function (source) {
11402
            let sh = _Tech.selectSourceHandler(source, this.options_);
11403
            if (!sh) {
11404
                // Fall back to a native source handler when unsupported sources are
11405
                // deliberately set
11406
                if (_Tech.nativeSourceHandler) {
11407
                    sh = _Tech.nativeSourceHandler;
11408
                } else {
11409
                    log$1.error('No source handler found for the current source.');
11410
                }
11411
            }
11412
 
11413
            // Dispose any existing source handler
11414
            this.disposeSourceHandler();
11415
            this.off('dispose', this.disposeSourceHandler_);
11416
            if (sh !== _Tech.nativeSourceHandler) {
11417
                this.currentSource_ = source;
11418
            }
11419
            this.sourceHandler_ = sh.handleSource(source, this, this.options_);
11420
            this.one('dispose', this.disposeSourceHandler_);
11421
        };
11422
 
11423
        /**
11424
         * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
11425
         *
11426
         * @listens Tech#dispose
11427
         */
11428
        _Tech.prototype.disposeSourceHandler = function () {
11429
            // if we have a source and get another one
11430
            // then we are loading something new
11431
            // than clear all of our current tracks
11432
            if (this.currentSource_) {
11433
                this.clearTracks(['audio', 'video']);
11434
                this.currentSource_ = null;
11435
            }
11436
 
11437
            // always clean up auto-text tracks
11438
            this.cleanupAutoTextTracks();
11439
            if (this.sourceHandler_) {
11440
                if (this.sourceHandler_.dispose) {
11441
                    this.sourceHandler_.dispose();
11442
                }
11443
                this.sourceHandler_ = null;
11444
            }
11445
        };
11446
    };
11447
 
11448
    // The base Tech class needs to be registered as a Component. It is the only
11449
    // Tech that can be registered as a Component.
11450
    Component$1.registerComponent('Tech', Tech);
11451
    Tech.registerTech('Tech', Tech);
11452
 
11453
    /**
11454
     * A list of techs that should be added to techOrder on Players
11455
     *
11456
     * @private
11457
     */
11458
    Tech.defaultTechOrder_ = [];
11459
 
11460
    /**
11461
     * @file middleware.js
11462
     * @module middleware
11463
     */
11464
    const middlewares = {};
11465
    const middlewareInstances = {};
11466
    const TERMINATOR = {};
11467
 
11468
    /**
11469
     * A middleware object is a plain JavaScript object that has methods that
11470
     * match the {@link Tech} methods found in the lists of allowed
11471
     * {@link module:middleware.allowedGetters|getters},
11472
     * {@link module:middleware.allowedSetters|setters}, and
11473
     * {@link module:middleware.allowedMediators|mediators}.
11474
     *
11475
     * @typedef {Object} MiddlewareObject
11476
     */
11477
 
11478
    /**
11479
     * A middleware factory function that should return a
11480
     * {@link module:middleware~MiddlewareObject|MiddlewareObject}.
11481
     *
11482
     * This factory will be called for each player when needed, with the player
11483
     * passed in as an argument.
11484
     *
11485
     * @callback MiddlewareFactory
11486
     * @param { import('../player').default } player
11487
     *        A Video.js player.
11488
     */
11489
 
11490
    /**
11491
     * Define a middleware that the player should use by way of a factory function
11492
     * that returns a middleware object.
11493
     *
11494
     * @param  {string} type
11495
     *         The MIME type to match or `"*"` for all MIME types.
11496
     *
11497
     * @param  {MiddlewareFactory} middleware
11498
     *         A middleware factory function that will be executed for
11499
     *         matching types.
11500
     */
11501
    function use(type, middleware) {
11502
        middlewares[type] = middlewares[type] || [];
11503
        middlewares[type].push(middleware);
11504
    }
11505
 
11506
    /**
11507
     * Asynchronously sets a source using middleware by recursing through any
11508
     * matching middlewares and calling `setSource` on each, passing along the
11509
     * previous returned value each time.
11510
     *
11511
     * @param  { import('../player').default } player
11512
     *         A {@link Player} instance.
11513
     *
11514
     * @param  {Tech~SourceObject} src
11515
     *         A source object.
11516
     *
11517
     * @param  {Function}
11518
     *         The next middleware to run.
11519
     */
11520
    function setSource(player, src, next) {
11521
        player.setTimeout(() => setSourceHelper(src, middlewares[src.type], next, player), 1);
11522
    }
11523
 
11524
    /**
11525
     * When the tech is set, passes the tech to each middleware's `setTech` method.
11526
     *
11527
     * @param {Object[]} middleware
11528
     *        An array of middleware instances.
11529
     *
11530
     * @param { import('../tech/tech').default } tech
11531
     *        A Video.js tech.
11532
     */
11533
    function setTech(middleware, tech) {
11534
        middleware.forEach(mw => mw.setTech && mw.setTech(tech));
11535
    }
11536
 
11537
    /**
11538
     * Calls a getter on the tech first, through each middleware
11539
     * from right to left to the player.
11540
     *
11541
     * @param  {Object[]} middleware
11542
     *         An array of middleware instances.
11543
     *
11544
     * @param  { import('../tech/tech').default } tech
11545
     *         The current tech.
11546
     *
11547
     * @param  {string} method
11548
     *         A method name.
11549
     *
11550
     * @return {*}
11551
     *         The final value from the tech after middleware has intercepted it.
11552
     */
11553
    function get(middleware, tech, method) {
11554
        return middleware.reduceRight(middlewareIterator(method), tech[method]());
11555
    }
11556
 
11557
    /**
11558
     * Takes the argument given to the player and calls the setter method on each
11559
     * middleware from left to right to the tech.
11560
     *
11561
     * @param  {Object[]} middleware
11562
     *         An array of middleware instances.
11563
     *
11564
     * @param  { import('../tech/tech').default } tech
11565
     *         The current tech.
11566
     *
11567
     * @param  {string} method
11568
     *         A method name.
11569
     *
11570
     * @param  {*} arg
11571
     *         The value to set on the tech.
11572
     *
11573
     * @return {*}
11574
     *         The return value of the `method` of the `tech`.
11575
     */
11576
    function set(middleware, tech, method, arg) {
11577
        return tech[method](middleware.reduce(middlewareIterator(method), arg));
11578
    }
11579
 
11580
    /**
11581
     * Takes the argument given to the player and calls the `call` version of the
11582
     * method on each middleware from left to right.
11583
     *
11584
     * Then, call the passed in method on the tech and return the result unchanged
11585
     * back to the player, through middleware, this time from right to left.
11586
     *
11587
     * @param  {Object[]} middleware
11588
     *         An array of middleware instances.
11589
     *
11590
     * @param  { import('../tech/tech').default } tech
11591
     *         The current tech.
11592
     *
11593
     * @param  {string} method
11594
     *         A method name.
11595
     *
11596
     * @param  {*} arg
11597
     *         The value to set on the tech.
11598
     *
11599
     * @return {*}
11600
     *         The return value of the `method` of the `tech`, regardless of the
11601
     *         return values of middlewares.
11602
     */
11603
    function mediate(middleware, tech, method, arg = null) {
11604
        const callMethod = 'call' + toTitleCase$1(method);
11605
        const middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
11606
        const terminated = middlewareValue === TERMINATOR;
11607
        // deprecated. The `null` return value should instead return TERMINATOR to
11608
        // prevent confusion if a techs method actually returns null.
11609
        const returnValue = terminated ? null : tech[method](middlewareValue);
11610
        executeRight(middleware, method, returnValue, terminated);
11611
        return returnValue;
11612
    }
11613
 
11614
    /**
11615
     * Enumeration of allowed getters where the keys are method names.
11616
     *
11617
     * @type {Object}
11618
     */
11619
    const allowedGetters = {
11620
        buffered: 1,
11621
        currentTime: 1,
11622
        duration: 1,
11623
        muted: 1,
11624
        played: 1,
11625
        paused: 1,
11626
        seekable: 1,
11627
        volume: 1,
11628
        ended: 1
11629
    };
11630
 
11631
    /**
11632
     * Enumeration of allowed setters where the keys are method names.
11633
     *
11634
     * @type {Object}
11635
     */
11636
    const allowedSetters = {
11637
        setCurrentTime: 1,
11638
        setMuted: 1,
11639
        setVolume: 1
11640
    };
11641
 
11642
    /**
11643
     * Enumeration of allowed mediators where the keys are method names.
11644
     *
11645
     * @type {Object}
11646
     */
11647
    const allowedMediators = {
11648
        play: 1,
11649
        pause: 1
11650
    };
11651
    function middlewareIterator(method) {
11652
        return (value, mw) => {
11653
            // if the previous middleware terminated, pass along the termination
11654
            if (value === TERMINATOR) {
11655
                return TERMINATOR;
11656
            }
11657
            if (mw[method]) {
11658
                return mw[method](value);
11659
            }
11660
            return value;
11661
        };
11662
    }
11663
    function executeRight(mws, method, value, terminated) {
11664
        for (let i = mws.length - 1; i >= 0; i--) {
11665
            const mw = mws[i];
11666
            if (mw[method]) {
11667
                mw[method](terminated, value);
11668
            }
11669
        }
11670
    }
11671
 
11672
    /**
11673
     * Clear the middleware cache for a player.
11674
     *
11675
     * @param  { import('../player').default } player
11676
     *         A {@link Player} instance.
11677
     */
11678
    function clearCacheForPlayer(player) {
11679
        middlewareInstances[player.id()] = null;
11680
    }
11681
 
11682
    /**
11683
     * {
11684
     *  [playerId]: [[mwFactory, mwInstance], ...]
11685
     * }
11686
     *
11687
     * @private
11688
     */
11689
    function getOrCreateFactory(player, mwFactory) {
11690
        const mws = middlewareInstances[player.id()];
11691
        let mw = null;
11692
        if (mws === undefined || mws === null) {
11693
            mw = mwFactory(player);
11694
            middlewareInstances[player.id()] = [[mwFactory, mw]];
11695
            return mw;
11696
        }
11697
        for (let i = 0; i < mws.length; i++) {
11698
            const [mwf, mwi] = mws[i];
11699
            if (mwf !== mwFactory) {
11700
                continue;
11701
            }
11702
            mw = mwi;
11703
        }
11704
        if (mw === null) {
11705
            mw = mwFactory(player);
11706
            mws.push([mwFactory, mw]);
11707
        }
11708
        return mw;
11709
    }
11710
    function setSourceHelper(src = {}, middleware = [], next, player, acc = [], lastRun = false) {
11711
        const [mwFactory, ...mwrest] = middleware;
11712
 
11713
        // if mwFactory is a string, then we're at a fork in the road
11714
        if (typeof mwFactory === 'string') {
11715
            setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun);
11716
 
11717
            // if we have an mwFactory, call it with the player to get the mw,
11718
            // then call the mw's setSource method
11719
        } else if (mwFactory) {
11720
            const mw = getOrCreateFactory(player, mwFactory);
11721
 
11722
            // if setSource isn't present, implicitly select this middleware
11723
            if (!mw.setSource) {
11724
                acc.push(mw);
11725
                return setSourceHelper(src, mwrest, next, player, acc, lastRun);
11726
            }
11727
            mw.setSource(Object.assign({}, src), function (err, _src) {
11728
                // something happened, try the next middleware on the current level
11729
                // make sure to use the old src
11730
                if (err) {
11731
                    return setSourceHelper(src, mwrest, next, player, acc, lastRun);
11732
                }
11733
 
11734
                // we've succeeded, now we need to go deeper
11735
                acc.push(mw);
11736
 
11737
                // if it's the same type, continue down the current chain
11738
                // otherwise, we want to go down the new chain
11739
                setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
11740
            });
11741
        } else if (mwrest.length) {
11742
            setSourceHelper(src, mwrest, next, player, acc, lastRun);
11743
        } else if (lastRun) {
11744
            next(src, acc);
11745
        } else {
11746
            setSourceHelper(src, middlewares['*'], next, player, acc, true);
11747
        }
11748
    }
11749
 
11750
    /**
11751
     * Mimetypes
11752
     *
11753
     * @see https://www.iana.org/assignments/media-types/media-types.xhtml
11754
     * @typedef Mimetypes~Kind
11755
     * @enum
11756
     */
11757
    const MimetypesKind = {
11758
        opus: 'video/ogg',
11759
        ogv: 'video/ogg',
11760
        mp4: 'video/mp4',
11761
        mov: 'video/mp4',
11762
        m4v: 'video/mp4',
11763
        mkv: 'video/x-matroska',
11764
        m4a: 'audio/mp4',
11765
        mp3: 'audio/mpeg',
11766
        aac: 'audio/aac',
11767
        caf: 'audio/x-caf',
11768
        flac: 'audio/flac',
11769
        oga: 'audio/ogg',
11770
        wav: 'audio/wav',
11771
        m3u8: 'application/x-mpegURL',
11772
        mpd: 'application/dash+xml',
11773
        jpg: 'image/jpeg',
11774
        jpeg: 'image/jpeg',
11775
        gif: 'image/gif',
11776
        png: 'image/png',
11777
        svg: 'image/svg+xml',
11778
        webp: 'image/webp'
11779
    };
11780
 
11781
    /**
11782
     * Get the mimetype of a given src url if possible
11783
     *
11784
     * @param {string} src
11785
     *        The url to the src
11786
     *
11787
     * @return {string}
11788
     *         return the mimetype if it was known or empty string otherwise
11789
     */
11790
    const getMimetype = function (src = '') {
11791
        const ext = getFileExtension(src);
11792
        const mimetype = MimetypesKind[ext.toLowerCase()];
11793
        return mimetype || '';
11794
    };
11795
 
11796
    /**
11797
     * Find the mime type of a given source string if possible. Uses the player
11798
     * source cache.
11799
     *
11800
     * @param { import('../player').default } player
11801
     *        The player object
11802
     *
11803
     * @param {string} src
11804
     *        The source string
11805
     *
11806
     * @return {string}
11807
     *         The type that was found
11808
     */
11809
    const findMimetype = (player, src) => {
11810
        if (!src) {
11811
            return '';
11812
        }
11813
 
11814
        // 1. check for the type in the `source` cache
11815
        if (player.cache_.source.src === src && player.cache_.source.type) {
11816
            return player.cache_.source.type;
11817
        }
11818
 
11819
        // 2. see if we have this source in our `currentSources` cache
11820
        const matchingSources = player.cache_.sources.filter(s => s.src === src);
11821
        if (matchingSources.length) {
11822
            return matchingSources[0].type;
11823
        }
11824
 
11825
        // 3. look for the src url in source elements and use the type there
11826
        const sources = player.$$('source');
11827
        for (let i = 0; i < sources.length; i++) {
11828
            const s = sources[i];
11829
            if (s.type && s.src && s.src === src) {
11830
                return s.type;
11831
            }
11832
        }
11833
 
11834
        // 4. finally fallback to our list of mime types based on src url extension
11835
        return getMimetype(src);
11836
    };
11837
 
11838
    /**
11839
     * @module filter-source
11840
     */
11841
 
11842
    /**
11843
     * Filter out single bad source objects or multiple source objects in an
11844
     * array. Also flattens nested source object arrays into a 1 dimensional
11845
     * array of source objects.
11846
     *
11847
     * @param {Tech~SourceObject|Tech~SourceObject[]} src
11848
     *        The src object to filter
11849
     *
11850
     * @return {Tech~SourceObject[]}
11851
     *         An array of sourceobjects containing only valid sources
11852
     *
11853
     * @private
11854
     */
11855
    const filterSource = function (src) {
11856
        // traverse array
11857
        if (Array.isArray(src)) {
11858
            let newsrc = [];
11859
            src.forEach(function (srcobj) {
11860
                srcobj = filterSource(srcobj);
11861
                if (Array.isArray(srcobj)) {
11862
                    newsrc = newsrc.concat(srcobj);
11863
                } else if (isObject$1(srcobj)) {
11864
                    newsrc.push(srcobj);
11865
                }
11866
            });
11867
            src = newsrc;
11868
        } else if (typeof src === 'string' && src.trim()) {
11869
            // convert string into object
11870
            src = [fixSource({
11871
                src
11872
            })];
11873
        } else if (isObject$1(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
11874
            // src is already valid
11875
            src = [fixSource(src)];
11876
        } else {
11877
            // invalid source, turn it into an empty array
11878
            src = [];
11879
        }
11880
        return src;
11881
    };
11882
 
11883
    /**
11884
     * Checks src mimetype, adding it when possible
11885
     *
11886
     * @param {Tech~SourceObject} src
11887
     *        The src object to check
11888
     * @return {Tech~SourceObject}
11889
     *        src Object with known type
11890
     */
11891
    function fixSource(src) {
11892
        if (!src.type) {
11893
            const mimetype = getMimetype(src.src);
11894
            if (mimetype) {
11895
                src.type = mimetype;
11896
            }
11897
        }
11898
        return src;
11899
    }
11900
 
11901
    var icons = "<svg xmlns=\"http://www.w3.org/2000/svg\">\n  <defs>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-play\">\n      <path d=\"M16 10v28l22-14z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-pause\">\n      <path d=\"M12 38h8V10h-8v28zm16-28v28h8V10h-8z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-audio\">\n      <path d=\"M24 2C14.06 2 6 10.06 6 20v14c0 3.31 2.69 6 6 6h6V24h-8v-4c0-7.73 6.27-14 14-14s14 6.27 14 14v4h-8v16h6c3.31 0 6-2.69 6-6V20c0-9.94-8.06-18-18-18z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-captions\">\n      <path d=\"M38 8H10c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h28c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zM22 22h-3v-1h-4v6h4v-1h3v2a2 2 0 0 1-2 2h-6a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v2zm14 0h-3v-1h-4v6h4v-1h3v2a2 2 0 0 1-2 2h-6a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v2z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-subtitles\">\n      <path d=\"M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zM8 24h8v4H8v-4zm20 12H8v-4h20v4zm12 0h-8v-4h8v4zm0-8H20v-4h20v4z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-fullscreen-enter\">\n      <path d=\"M14 28h-4v10h10v-4h-6v-6zm-4-8h4v-6h6v-4H10v10zm24 14h-6v4h10V28h-4v6zm-6-24v4h6v6h4V10H28z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-fullscreen-exit\">\n      <path d=\"M10 32h6v6h4V28H10v4zm6-16h-6v4h10V10h-4v6zm12 22h4v-6h6v-4H28v10zm4-22v-6h-4v10h10v-4h-6z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-play-circle\">\n      <path d=\"M20 33l12-9-12-9v18zm4-29C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm0 36c-8.82 0-16-7.18-16-16S15.18 8 24 8s16 7.18 16 16-7.18 16-16 16z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-volume-mute\">\n      <path d=\"M33 24c0-3.53-2.04-6.58-5-8.05v4.42l4.91 4.91c.06-.42.09-.85.09-1.28zm5 0c0 1.88-.41 3.65-1.08 5.28l3.03 3.03C41.25 29.82 42 27 42 24c0-8.56-5.99-15.72-14-17.54v4.13c5.78 1.72 10 7.07 10 13.41zM8.55 6L6 8.55 15.45 18H6v12h8l10 10V26.55l8.51 8.51c-1.34 1.03-2.85 1.86-4.51 2.36v4.13a17.94 17.94 0 0 0 7.37-3.62L39.45 42 42 39.45l-18-18L8.55 6zM24 8l-4.18 4.18L24 16.36V8z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-volume-low\">\n      <path d=\"M14 18v12h8l10 10V8L22 18h-8z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-volume-medium\">\n      <path d=\"M37 24c0-3.53-2.04-6.58-5-8.05v16.11c2.96-1.48 5-4.53 5-8.06zm-27-6v12h8l10 10V8L18 18h-8z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-volume-high\">\n      <path d=\"M6 18v12h8l10 10V8L14 18H6zm27 6c0-3.53-2.04-6.58-5-8.05v16.11c2.96-1.48 5-4.53 5-8.06zM28 6.46v4.13c5.78 1.72 10 7.07 10 13.41s-4.22 11.69-10 13.41v4.13c8.01-1.82 14-8.97 14-17.54S36.01 8.28 28 6.46z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-spinner\">\n      <path d=\"M18.8 21l9.53-16.51C26.94 4.18 25.49 4 24 4c-4.8 0-9.19 1.69-12.64 4.51l7.33 12.69.11-.2zm24.28-3c-1.84-5.85-6.3-10.52-11.99-12.68L23.77 18h19.31zm.52 2H28.62l.58 1 9.53 16.5C41.99 33.94 44 29.21 44 24c0-1.37-.14-2.71-.4-4zm-26.53 4l-7.8-13.5C6.01 14.06 4 18.79 4 24c0 1.37.14 2.71.4 4h14.98l-2.31-4zM4.92 30c1.84 5.85 6.3 10.52 11.99 12.68L24.23 30H4.92zm22.54 0l-7.8 13.51c1.4.31 2.85.49 4.34.49 4.8 0 9.19-1.69 12.64-4.51L29.31 26.8 27.46 30z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 24 24\" id=\"vjs-icon-hd\">\n      <path d=\"M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-8 12H9.5v-2h-2v2H6V9h1.5v2.5h2V9H11v6zm2-6h4c.55 0 1 .45 1 1v4c0 .55-.45 1-1 1h-4V9zm1.5 4.5h2v-3h-2v3z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-chapters\">\n      <path d=\"M6 26h4v-4H6v4zm0 8h4v-4H6v4zm0-16h4v-4H6v4zm8 8h28v-4H14v4zm0 8h28v-4H14v4zm0-20v4h28v-4H14z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 40 40\" id=\"vjs-icon-downloading\">\n      <path d=\"M18.208 36.875q-3.208-.292-5.979-1.729-2.771-1.438-4.812-3.729-2.042-2.292-3.188-5.229-1.146-2.938-1.146-6.23 0-6.583 4.334-11.416 4.333-4.834 10.833-5.5v3.166q-5.167.75-8.583 4.646Q6.25 14.75 6.25 19.958q0 5.209 3.396 9.104 3.396 3.896 8.562 4.646zM20 28.417L11.542 20l2.083-2.083 4.917 4.916v-11.25h2.916v11.25l4.875-4.916L28.417 20zm1.792 8.458v-3.167q1.833-.25 3.541-.958 1.709-.708 3.167-1.875l2.333 2.292q-1.958 1.583-4.25 2.541-2.291.959-4.791 1.167zm6.791-27.792q-1.541-1.125-3.25-1.854-1.708-.729-3.541-1.021V3.042q2.5.25 4.77 1.208 2.271.958 4.271 2.5zm4.584 21.584l-2.25-2.25q1.166-1.5 1.854-3.209.687-1.708.937-3.541h3.209q-.292 2.5-1.229 4.791-.938 2.292-2.521 4.209zm.541-12.417q-.291-1.833-.958-3.562-.667-1.73-1.833-3.188l2.375-2.208q1.541 1.916 2.458 4.208.917 2.292 1.167 4.75z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-file-download\">\n      <path d=\"M10.8 40.55q-1.35 0-2.375-1T7.4 37.15v-7.7h3.4v7.7h26.35v-7.7h3.4v7.7q0 1.4-1 2.4t-2.4 1zM24 32.1L13.9 22.05l2.45-2.45 5.95 5.95V7.15h3.4v18.4l5.95-5.95 2.45 2.45z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-file-download-done\">\n      <path d=\"M9.8 40.5v-3.45h28.4v3.45zm9.2-9.05L7.4 19.85l2.45-2.35L19 26.65l19.2-19.2 2.4 2.4z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-file-download-off\">\n      <path d=\"M4.9 4.75L43.25 43.1 41 45.3l-4.75-4.75q-.05.05-.075.025-.025-.025-.075-.025H10.8q-1.35 0-2.375-1T7.4 37.15v-7.7h3.4v7.7h22.05l-7-7-1.85 1.8L13.9 21.9l1.85-1.85L2.7 7zm26.75 14.7l2.45 2.45-3.75 3.8-2.45-2.5zM25.7 7.15V21.1l-3.4-3.45V7.15z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-share\">\n      <path d=\"M36 32.17c-1.52 0-2.89.59-3.93 1.54L17.82 25.4c.11-.45.18-.92.18-1.4s-.07-.95-.18-1.4l14.1-8.23c1.07 1 2.5 1.62 4.08 1.62 3.31 0 6-2.69 6-6s-2.69-6-6-6-6 2.69-6 6c0 .48.07.95.18 1.4l-14.1 8.23c-1.07-1-2.5-1.62-4.08-1.62-3.31 0-6 2.69-6 6s2.69 6 6 6c1.58 0 3.01-.62 4.08-1.62l14.25 8.31c-.1.42-.16.86-.16 1.31A5.83 5.83 0 1 0 36 32.17z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-cog\">\n      <path d=\"M38.86 25.95c.08-.64.14-1.29.14-1.95s-.06-1.31-.14-1.95l4.23-3.31c.38-.3.49-.84.24-1.28l-4-6.93c-.25-.43-.77-.61-1.22-.43l-4.98 2.01c-1.03-.79-2.16-1.46-3.38-1.97L29 4.84c-.09-.47-.5-.84-1-.84h-8c-.5 0-.91.37-.99.84l-.75 5.3a14.8 14.8 0 0 0-3.38 1.97L9.9 10.1a1 1 0 0 0-1.22.43l-4 6.93c-.25.43-.14.97.24 1.28l4.22 3.31C9.06 22.69 9 23.34 9 24s.06 1.31.14 1.95l-4.22 3.31c-.38.3-.49.84-.24 1.28l4 6.93c.25.43.77.61 1.22.43l4.98-2.01c1.03.79 2.16 1.46 3.38 1.97l.75 5.3c.08.47.49.84.99.84h8c.5 0 .91-.37.99-.84l.75-5.3a14.8 14.8 0 0 0 3.38-1.97l4.98 2.01a1 1 0 0 0 1.22-.43l4-6.93c.25-.43.14-.97-.24-1.28l-4.22-3.31zM24 31c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-square\">\n      <path d=\"M36 8H12c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h24c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zm0 28H12V12h24v24z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-circle\">\n      <circle cx=\"24\" cy=\"24\" r=\"20\"></circle>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-circle-outline\">\n      <path d=\"M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm0 36c-8.82 0-16-7.18-16-16S15.18 8 24 8s16 7.18 16 16-7.18 16-16 16z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-circle-inner-circle\">\n      <path d=\"M24 4C12.97 4 4 12.97 4 24s8.97 20 20 20 20-8.97 20-20S35.03 4 24 4zm0 36c-8.82 0-16-7.18-16-16S15.18 8 24 8s16 7.18 16 16-7.18 16-16 16zm6-16c0 3.31-2.69 6-6 6s-6-2.69-6-6 2.69-6 6-6 6 2.69 6 6z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-cancel\">\n      <path d=\"M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm10 27.17L31.17 34 24 26.83 16.83 34 14 31.17 21.17 24 14 16.83 16.83 14 24 21.17 31.17 14 34 16.83 26.83 24 34 31.17z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-replay\">\n      <path d=\"M24 10V2L14 12l10 10v-8c6.63 0 12 5.37 12 12s-5.37 12-12 12-12-5.37-12-12H8c0 8.84 7.16 16 16 16s16-7.16 16-16-7.16-16-16-16z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-repeat\">\n      <path d=\"M14 14h20v6l8-8-8-8v6H10v12h4v-8zm20 20H14v-6l-8 8 8 8v-6h24V26h-4v8z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 96 48 48\" id=\"vjs-icon-replay-5\">\n      <path d=\"M17.689 98l-8.697 8.696 8.697 8.697 2.486-2.485-4.32-4.319h1.302c4.93 0 9.071 1.722 12.424 5.165 3.352 3.443 5.029 7.638 5.029 12.584h3.55c0-2.958-.553-5.73-1.658-8.313-1.104-2.583-2.622-4.841-4.555-6.774-1.932-1.932-4.19-3.45-6.773-4.555-2.584-1.104-5.355-1.657-8.313-1.657H15.5l4.615-4.615zm-8.08 21.659v13.861h11.357v5.008H9.609V143h12.7c.834 0 1.55-.298 2.146-.894.596-.597.895-1.31.895-2.145v-7.781c0-.835-.299-1.55-.895-2.147a2.929 2.929 0 0 0-2.147-.894h-8.227v-5.096H25.35v-4.384z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 96 48 48\" id=\"vjs-icon-replay-10\">\n      <path d=\"M42.315 125.63c0-4.997-1.694-9.235-5.08-12.713-3.388-3.479-7.571-5.218-12.552-5.218h-1.315l4.363 4.363-2.51 2.51-8.787-8.786L25.221 97l2.45 2.45-4.662 4.663h1.375c2.988 0 5.788.557 8.397 1.673 2.61 1.116 4.892 2.65 6.844 4.602 1.953 1.953 3.487 4.234 4.602 6.844 1.116 2.61 1.674 5.41 1.674 8.398zM8.183 142v-19.657H3.176V117.8h9.643V142zm13.63 0c-1.156 0-2.127-.393-2.912-1.178-.778-.778-1.168-1.746-1.168-2.902v-16.04c0-1.156.393-2.127 1.178-2.912.779-.779 1.746-1.168 2.902-1.168h7.696c1.156 0 2.126.392 2.911 1.177.779.78 1.168 1.747 1.168 2.903v16.04c0 1.156-.392 2.127-1.177 2.912-.779.779-1.746 1.168-2.902 1.168zm.556-4.636h6.583v-15.02H22.37z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 96 48 48\" id=\"vjs-icon-replay-30\">\n      <path d=\"M26.047 97l-8.733 8.732 8.733 8.733 2.496-2.494-4.336-4.338h1.307c4.95 0 9.108 1.73 12.474 5.187 3.367 3.458 5.051 7.668 5.051 12.635h3.565c0-2.97-.556-5.751-1.665-8.346-1.109-2.594-2.633-4.862-4.574-6.802-1.94-1.941-4.208-3.466-6.803-4.575-2.594-1.109-5.375-1.664-8.345-1.664H23.85l4.634-4.634zM2.555 117.531v4.688h10.297v5.25H5.873v4.687h6.979v5.156H2.555V142H13.36c1.061 0 1.95-.395 2.668-1.186.718-.79 1.076-1.772 1.076-2.94v-16.218c0-1.168-.358-2.149-1.076-2.94-.717-.79-1.607-1.185-2.668-1.185zm22.482.14c-1.149 0-2.11.39-2.885 1.165-.78.78-1.172 1.744-1.172 2.893v15.943c0 1.149.388 2.11 1.163 2.885.78.78 1.745 1.172 2.894 1.172h7.649c1.148 0 2.11-.388 2.884-1.163.78-.78 1.17-1.745 1.17-2.894v-15.943c0-1.15-.386-2.111-1.16-2.885-.78-.78-1.746-1.172-2.894-1.172zm.553 4.518h6.545v14.93H25.59z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 96 48 48\" id=\"vjs-icon-forward-5\">\n      <path d=\"M29.508 97l-2.431 2.43 4.625 4.625h-1.364c-2.965 0-5.742.554-8.332 1.66-2.589 1.107-4.851 2.629-6.788 4.566-1.937 1.937-3.458 4.2-4.565 6.788-1.107 2.59-1.66 5.367-1.66 8.331h3.557c0-4.957 1.68-9.16 5.04-12.611 3.36-3.45 7.51-5.177 12.451-5.177h1.304l-4.326 4.33 2.49 2.49 8.715-8.716zm-9.783 21.61v13.89h11.382v5.018H19.725V142h12.727a2.93 2.93 0 0 0 2.15-.896 2.93 2.93 0 0 0 .896-2.15v-7.798c0-.837-.299-1.554-.896-2.152a2.93 2.93 0 0 0-2.15-.896h-8.245V123h11.29v-4.392z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 96 48 48\" id=\"vjs-icon-forward-10\">\n      <path d=\"M23.119 97l-2.386 2.383 4.538 4.538h-1.339c-2.908 0-5.633.543-8.173 1.63-2.54 1.085-4.76 2.577-6.66 4.478-1.9 1.9-3.392 4.12-4.478 6.66-1.085 2.54-1.629 5.264-1.629 8.172h3.49c0-4.863 1.648-8.986 4.944-12.372 3.297-3.385 7.368-5.078 12.216-5.078h1.279l-4.245 4.247 2.443 2.442 8.55-8.55zm-9.52 21.45v4.42h4.871V142h4.513v-23.55zm18.136 0c-1.125 0-2.066.377-2.824 1.135-.764.764-1.148 1.709-1.148 2.834v15.612c0 1.124.38 2.066 1.139 2.824.764.764 1.708 1.145 2.833 1.145h7.489c1.125 0 2.066-.378 2.824-1.136.764-.764 1.145-1.709 1.145-2.833v-15.612c0-1.125-.378-2.067-1.136-2.825-.764-.764-1.708-1.145-2.833-1.145zm.54 4.42h6.408v14.617h-6.407z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 96 48 48\" id=\"vjs-icon-forward-30\">\n      <path d=\"M25.549 97l-2.437 2.434 4.634 4.635H26.38c-2.97 0-5.753.555-8.347 1.664-2.594 1.109-4.861 2.633-6.802 4.574-1.94 1.94-3.465 4.207-4.574 6.802-1.109 2.594-1.664 5.377-1.664 8.347h3.565c0-4.967 1.683-9.178 5.05-12.636 3.366-3.458 7.525-5.187 12.475-5.187h1.307l-4.335 4.338 2.495 2.494 8.732-8.732zm-11.553 20.53v4.689h10.297v5.249h-6.978v4.688h6.978v5.156H13.996V142h10.808c1.06 0 1.948-.395 2.666-1.186.718-.79 1.077-1.771 1.077-2.94v-16.217c0-1.169-.36-2.15-1.077-2.94-.718-.79-1.605-1.186-2.666-1.186zm21.174.168c-1.149 0-2.11.389-2.884 1.163-.78.78-1.172 1.745-1.172 2.894v15.942c0 1.15.388 2.11 1.162 2.885.78.78 1.745 1.17 2.894 1.17h7.649c1.149 0 2.11-.386 2.885-1.16.78-.78 1.17-1.746 1.17-2.895v-15.942c0-1.15-.387-2.11-1.161-2.885-.78-.78-1.745-1.172-2.894-1.172zm.552 4.516h6.542v14.931h-6.542z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 512 512\" id=\"vjs-icon-audio-description\">\n      <g fill-rule=\"evenodd\"><path d=\"M227.29 381.351V162.993c50.38-1.017 89.108-3.028 117.631 17.126 27.374 19.342 48.734 56.965 44.89 105.325-4.067 51.155-41.335 94.139-89.776 98.475-24.085 2.155-71.972 0-71.972 0s-.84-1.352-.773-2.568m48.755-54.804c31.43 1.26 53.208-16.633 56.495-45.386 4.403-38.51-21.188-63.552-58.041-60.796v103.612c-.036 1.466.575 2.22 1.546 2.57\"></path><path d=\"M383.78 381.328c13.336 3.71 17.387-11.06 23.215-21.408 12.722-22.571 22.294-51.594 22.445-84.774.221-47.594-18.343-82.517-35.6-106.182h-8.51c-.587 3.874 2.226 7.315 3.865 10.276 13.166 23.762 25.367 56.553 25.54 94.194.2 43.176-14.162 79.278-30.955 107.894\"></path><path d=\"M425.154 381.328c13.336 3.71 17.384-11.061 23.215-21.408 12.721-22.571 22.291-51.594 22.445-84.774.221-47.594-18.343-82.517-35.6-106.182h-8.511c-.586 3.874 2.226 7.315 3.866 10.276 13.166 23.762 25.367 56.553 25.54 94.194.2 43.176-14.162 79.278-30.955 107.894\"></path><path d=\"M466.26 381.328c13.337 3.71 17.385-11.061 23.216-21.408 12.722-22.571 22.292-51.594 22.445-84.774.221-47.594-18.343-82.517-35.6-106.182h-8.51c-.587 3.874 2.225 7.315 3.865 10.276 13.166 23.762 25.367 56.553 25.54 94.194.2 43.176-14.162 79.278-30.955 107.894M4.477 383.005H72.58l18.573-28.484 64.169-.135s.065 19.413.065 28.62h48.756V160.307h-58.816c-5.653 9.537-140.85 222.697-140.85 222.697zm152.667-145.282v71.158l-40.453-.27 40.453-70.888z\"></path></g>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-next-item\">\n      <path d=\"M12 36l17-12-17-12v24zm20-24v24h4V12h-4z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-previous-item\">\n      <path d=\"M12 12h4v24h-4zm7 12l17 12V12z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-shuffle\">\n      <path d=\"M21.17 18.34L10.83 8 8 10.83l10.34 10.34 2.83-2.83zM29 8l4.09 4.09L8 37.17 10.83 40l25.09-25.09L40 19V8H29zm.66 18.83l-2.83 2.83 6.26 6.26L29 40h11V29l-4.09 4.09-6.25-6.26z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-cast\">\n      <path d=\"M42 6H6c-2.21 0-4 1.79-4 4v6h4v-6h36v28H28v4h14c2.21 0 4-1.79 4-4V10c0-2.21-1.79-4-4-4zM2 36v6h6c0-3.31-2.69-6-6-6zm0-8v4c5.52 0 10 4.48 10 10h4c0-7.73-6.27-14-14-14zm0-8v4c9.94 0 18 8.06 18 18h4c0-12.15-9.85-22-22-22z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 48 48\" id=\"vjs-icon-picture-in-picture-enter\">\n      <path d=\"M38 22H22v11.99h16V22zm8 16V9.96C46 7.76 44.2 6 42 6H6C3.8 6 2 7.76 2 9.96V38c0 2.2 1.8 4 4 4h36c2.2 0 4-1.8 4-4zm-4 .04H6V9.94h36v28.1z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 22 18\" id=\"vjs-icon-picture-in-picture-exit\">\n      <path d=\"M18 4H4v10h14V4zm4 12V1.98C22 .88 21.1 0 20 0H2C.9 0 0 .88 0 1.98V16c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2zm-2 .02H2V1.97h18v14.05z\"></path>\n      <path fill=\"none\" d=\"M-1-3h24v24H-1z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 1792 1792\" id=\"vjs-icon-facebook\">\n      <path d=\"M1343 12v264h-157q-86 0-116 36t-30 108v189h293l-39 296h-254v759H734V905H479V609h255V391q0-186 104-288.5T1115 0q147 0 228 12z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 1792 1792\" id=\"vjs-icon-linkedin\">\n      <path d=\"M477 625v991H147V625h330zm21-306q1 73-50.5 122T312 490h-2q-82 0-132-49t-50-122q0-74 51.5-122.5T314 148t133 48.5T498 319zm1166 729v568h-329v-530q0-105-40.5-164.5T1168 862q-63 0-105.5 34.5T999 982q-11 30-11 81v553H659q2-399 2-647t-1-296l-1-48h329v144h-2q20-32 41-56t56.5-52 87-43.5T1285 602q171 0 275 113.5t104 332.5z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 1792 1792\" id=\"vjs-icon-twitter\">\n      <path d=\"M1684 408q-67 98-162 167 1 14 1 42 0 130-38 259.5T1369.5 1125 1185 1335.5t-258 146-323 54.5q-271 0-496-145 35 4 78 4 225 0 401-138-105-2-188-64.5T285 1033q33 5 61 5 43 0 85-11-112-23-185.5-111.5T172 710v-4q68 38 146 41-66-44-105-115t-39-154q0-88 44-163 121 149 294.5 238.5T884 653q-8-38-8-74 0-134 94.5-228.5T1199 256q140 0 236 102 109-21 205-78-37 115-142 178 93-10 186-50z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 1792 1792\" id=\"vjs-icon-tumblr\">\n      <path d=\"M1328 1329l80 237q-23 35-111 66t-177 32q-104 2-190.5-26T787 1564t-95-106-55.5-120-16.5-118V676H452V461q72-26 129-69.5t91-90 58-102 34-99T779 12q1-5 4.5-8.5T791 0h244v424h333v252h-334v518q0 30 6.5 56t22.5 52.5 49.5 41.5 81.5 14q78-2 134-29z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 1792 1792\" id=\"vjs-icon-pinterest\">\n      <path d=\"M1664 896q0 209-103 385.5T1281.5 1561 896 1664q-111 0-218-32 59-93 78-164 9-34 54-211 20 39 73 67.5t114 28.5q121 0 216-68.5t147-188.5 52-270q0-114-59.5-214T1180 449t-255-63q-105 0-196 29t-154.5 77-109 110.5-67 129.5T377 866q0 104 40 183t117 111q30 12 38-20 2-7 8-31t8-30q6-23-11-43-51-61-51-151 0-151 104.5-259.5T904 517q151 0 235.5 82t84.5 213q0 170-68.5 289T980 1220q-61 0-98-43.5T859 1072q8-35 26.5-93.5t30-103T927 800q0-50-27-83t-77-33q-62 0-105 57t-43 142q0 73 25 122l-99 418q-17 70-13 177-206-91-333-281T128 896q0-209 103-385.5T510.5 231 896 128t385.5 103T1561 510.5 1664 896z\"></path>\n    </symbol>\n  </defs>\n</svg>";
11902
 
11903
    /**
11904
     * @file loader.js
11905
     */
11906
 
11907
    /**
11908
     * The `MediaLoader` is the `Component` that decides which playback technology to load
11909
     * when a player is initialized.
11910
     *
11911
     * @extends Component
11912
     */
11913
    class MediaLoader extends Component$1 {
11914
        /**
11915
         * Create an instance of this class.
11916
         *
11917
         * @param { import('../player').default } player
11918
         *        The `Player` that this class should attach to.
11919
         *
11920
         * @param {Object} [options]
11921
         *        The key/value store of player options.
11922
         *
11923
         * @param {Function} [ready]
11924
         *        The function that is run when this component is ready.
11925
         */
11926
        constructor(player, options, ready) {
11927
            // MediaLoader has no element
11928
            const options_ = merge$2({
11929
                createEl: false
11930
            }, options);
11931
            super(player, options_, ready);
11932
 
11933
            // If there are no sources when the player is initialized,
11934
            // load the first supported playback technology.
11935
 
11936
            if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
11937
                for (let i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
11938
                    const techName = toTitleCase$1(j[i]);
11939
                    let tech = Tech.getTech(techName);
11940
 
11941
                    // Support old behavior of techs being registered as components.
11942
                    // Remove once that deprecated behavior is removed.
11943
                    if (!techName) {
11944
                        tech = Component$1.getComponent(techName);
11945
                    }
11946
 
11947
                    // Check if the browser supports this technology
11948
                    if (tech && tech.isSupported()) {
11949
                        player.loadTech_(techName);
11950
                        break;
11951
                    }
11952
                }
11953
            } else {
11954
                // Loop through playback technologies (e.g. HTML5) and check for support.
11955
                // Then load the best source.
11956
                // A few assumptions here:
11957
                //   All playback technologies respect preload false.
11958
                player.src(options.playerOptions.sources);
11959
            }
11960
        }
11961
    }
11962
    Component$1.registerComponent('MediaLoader', MediaLoader);
11963
 
11964
    /**
11965
     * @file clickable-component.js
11966
     */
11967
 
11968
    /**
11969
     * Component which is clickable or keyboard actionable, but is not a
11970
     * native HTML button.
11971
     *
11972
     * @extends Component
11973
     */
11974
    class ClickableComponent extends Component$1 {
11975
        /**
11976
         * Creates an instance of this class.
11977
         *
11978
         * @param  { import('./player').default } player
11979
         *         The `Player` that this class should be attached to.
11980
         *
11981
         * @param  {Object} [options]
11982
         *         The key/value store of component options.
11983
         *
11984
         * @param  {function} [options.clickHandler]
11985
         *         The function to call when the button is clicked / activated
11986
         *
11987
         * @param  {string} [options.controlText]
11988
         *         The text to set on the button
11989
         *
11990
         * @param  {string} [options.className]
11991
         *         A class or space separated list of classes to add the component
11992
         *
11993
         */
11994
        constructor(player, options) {
11995
            super(player, options);
11996
            if (this.options_.controlText) {
11997
                this.controlText(this.options_.controlText);
11998
            }
11999
            this.handleMouseOver_ = e => this.handleMouseOver(e);
12000
            this.handleMouseOut_ = e => this.handleMouseOut(e);
12001
            this.handleClick_ = e => this.handleClick(e);
12002
            this.handleKeyDown_ = e => this.handleKeyDown(e);
12003
            this.emitTapEvents();
12004
            this.enable();
12005
        }
12006
 
12007
        /**
12008
         * Create the `ClickableComponent`s DOM element.
12009
         *
12010
         * @param {string} [tag=div]
12011
         *        The element's node type.
12012
         *
12013
         * @param {Object} [props={}]
12014
         *        An object of properties that should be set on the element.
12015
         *
12016
         * @param {Object} [attributes={}]
12017
         *        An object of attributes that should be set on the element.
12018
         *
12019
         * @return {Element}
12020
         *         The element that gets created.
12021
         */
12022
        createEl(tag = 'div', props = {}, attributes = {}) {
12023
            props = Object.assign({
12024
                className: this.buildCSSClass(),
12025
                tabIndex: 0
12026
            }, props);
12027
            if (tag === 'button') {
12028
                log$1.error(`Creating a ClickableComponent with an HTML element of ${tag} is not supported; use a Button instead.`);
12029
            }
12030
 
12031
            // Add ARIA attributes for clickable element which is not a native HTML button
12032
            attributes = Object.assign({
12033
                role: 'button'
12034
            }, attributes);
12035
            this.tabIndex_ = props.tabIndex;
12036
            const el = createEl(tag, props, attributes);
12037
            if (!this.player_.options_.experimentalSvgIcons) {
12038
                el.appendChild(createEl('span', {
12039
                    className: 'vjs-icon-placeholder'
12040
                }, {
12041
                    'aria-hidden': true
12042
                }));
12043
            }
12044
            this.createControlTextEl(el);
12045
            return el;
12046
        }
12047
        dispose() {
12048
            // remove controlTextEl_ on dispose
12049
            this.controlTextEl_ = null;
12050
            super.dispose();
12051
        }
12052
 
12053
        /**
12054
         * Create a control text element on this `ClickableComponent`
12055
         *
12056
         * @param {Element} [el]
12057
         *        Parent element for the control text.
12058
         *
12059
         * @return {Element}
12060
         *         The control text element that gets created.
12061
         */
12062
        createControlTextEl(el) {
12063
            this.controlTextEl_ = createEl('span', {
12064
                className: 'vjs-control-text'
12065
            }, {
12066
                // let the screen reader user know that the text of the element may change
12067
                'aria-live': 'polite'
12068
            });
12069
            if (el) {
12070
                el.appendChild(this.controlTextEl_);
12071
            }
12072
            this.controlText(this.controlText_, el);
12073
            return this.controlTextEl_;
12074
        }
12075
 
12076
        /**
12077
         * Get or set the localize text to use for the controls on the `ClickableComponent`.
12078
         *
12079
         * @param {string} [text]
12080
         *        Control text for element.
12081
         *
12082
         * @param {Element} [el=this.el()]
12083
         *        Element to set the title on.
12084
         *
12085
         * @return {string}
12086
         *         - The control text when getting
12087
         */
12088
        controlText(text, el = this.el()) {
12089
            if (text === undefined) {
12090
                return this.controlText_ || 'Need Text';
12091
            }
12092
            const localizedText = this.localize(text);
12093
 
12094
            /** @protected */
12095
            this.controlText_ = text;
12096
            textContent(this.controlTextEl_, localizedText);
12097
            if (!this.nonIconControl && !this.player_.options_.noUITitleAttributes) {
12098
                // Set title attribute if only an icon is shown
12099
                el.setAttribute('title', localizedText);
12100
            }
12101
        }
12102
 
12103
        /**
12104
         * Builds the default DOM `className`.
12105
         *
12106
         * @return {string}
12107
         *         The DOM `className` for this object.
12108
         */
12109
        buildCSSClass() {
12110
            return `vjs-control vjs-button ${super.buildCSSClass()}`;
12111
        }
12112
 
12113
        /**
12114
         * Enable this `ClickableComponent`
12115
         */
12116
        enable() {
12117
            if (!this.enabled_) {
12118
                this.enabled_ = true;
12119
                this.removeClass('vjs-disabled');
12120
                this.el_.setAttribute('aria-disabled', 'false');
12121
                if (typeof this.tabIndex_ !== 'undefined') {
12122
                    this.el_.setAttribute('tabIndex', this.tabIndex_);
12123
                }
12124
                this.on(['tap', 'click'], this.handleClick_);
12125
                this.on('keydown', this.handleKeyDown_);
12126
            }
12127
        }
12128
 
12129
        /**
12130
         * Disable this `ClickableComponent`
12131
         */
12132
        disable() {
12133
            this.enabled_ = false;
12134
            this.addClass('vjs-disabled');
12135
            this.el_.setAttribute('aria-disabled', 'true');
12136
            if (typeof this.tabIndex_ !== 'undefined') {
12137
                this.el_.removeAttribute('tabIndex');
12138
            }
12139
            this.off('mouseover', this.handleMouseOver_);
12140
            this.off('mouseout', this.handleMouseOut_);
12141
            this.off(['tap', 'click'], this.handleClick_);
12142
            this.off('keydown', this.handleKeyDown_);
12143
        }
12144
 
12145
        /**
12146
         * Handles language change in ClickableComponent for the player in components
12147
         *
12148
         *
12149
         */
12150
        handleLanguagechange() {
12151
            this.controlText(this.controlText_);
12152
        }
12153
 
12154
        /**
12155
         * Event handler that is called when a `ClickableComponent` receives a
12156
         * `click` or `tap` event.
12157
         *
12158
         * @param {Event} event
12159
         *        The `tap` or `click` event that caused this function to be called.
12160
         *
12161
         * @listens tap
12162
         * @listens click
12163
         * @abstract
12164
         */
12165
        handleClick(event) {
12166
            if (this.options_.clickHandler) {
12167
                this.options_.clickHandler.call(this, arguments);
12168
            }
12169
        }
12170
 
12171
        /**
12172
         * Event handler that is called when a `ClickableComponent` receives a
12173
         * `keydown` event.
12174
         *
12175
         * By default, if the key is Space or Enter, it will trigger a `click` event.
12176
         *
12177
         * @param {KeyboardEvent} event
12178
         *        The `keydown` event that caused this function to be called.
12179
         *
12180
         * @listens keydown
12181
         */
12182
        handleKeyDown(event) {
12183
            // Support Space or Enter key operation to fire a click event. Also,
12184
            // prevent the event from propagating through the DOM and triggering
12185
            // Player hotkeys.
12186
            if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
12187
                event.preventDefault();
12188
                event.stopPropagation();
12189
                this.trigger('click');
12190
            } else {
12191
                // Pass keypress handling up for unsupported keys
12192
                super.handleKeyDown(event);
12193
            }
12194
        }
12195
    }
12196
    Component$1.registerComponent('ClickableComponent', ClickableComponent);
12197
 
12198
    /**
12199
     * @file poster-image.js
12200
     */
12201
 
12202
    /**
12203
     * A `ClickableComponent` that handles showing the poster image for the player.
12204
     *
12205
     * @extends ClickableComponent
12206
     */
12207
    class PosterImage extends ClickableComponent {
12208
        /**
12209
         * Create an instance of this class.
12210
         *
12211
         * @param { import('./player').default } player
12212
         *        The `Player` that this class should attach to.
12213
         *
12214
         * @param {Object} [options]
12215
         *        The key/value store of player options.
12216
         */
12217
        constructor(player, options) {
12218
            super(player, options);
12219
            this.update();
12220
            this.update_ = e => this.update(e);
12221
            player.on('posterchange', this.update_);
12222
        }
12223
 
12224
        /**
12225
         * Clean up and dispose of the `PosterImage`.
12226
         */
12227
        dispose() {
12228
            this.player().off('posterchange', this.update_);
12229
            super.dispose();
12230
        }
12231
 
12232
        /**
12233
         * Create the `PosterImage`s DOM element.
12234
         *
12235
         * @return {Element}
12236
         *         The element that gets created.
12237
         */
12238
        createEl() {
12239
            // The el is an empty div to keep position in the DOM
12240
            // A picture and img el will be inserted when a source is set
12241
            return createEl('div', {
12242
                className: 'vjs-poster'
12243
            });
12244
        }
12245
 
12246
        /**
12247
         * Get or set the `PosterImage`'s crossOrigin option.
12248
         *
12249
         * @param {string|null} [value]
12250
         *        The value to set the crossOrigin to. If an argument is
12251
         *        given, must be one of `'anonymous'` or `'use-credentials'`, or 'null'.
12252
         *
12253
         * @return {string|null}
12254
         *         - The current crossOrigin value of the `Player` when getting.
12255
         *         - undefined when setting
12256
         */
12257
        crossOrigin(value) {
12258
            // `null` can be set to unset a value
12259
            if (typeof value === 'undefined') {
12260
                if (this.$('img')) {
12261
                    // If the poster's element exists, give its value
12262
                    return this.$('img').crossOrigin;
12263
                } else if (this.player_.tech_ && this.player_.tech_.isReady_) {
12264
                    // If not but the tech is ready, query the tech
12265
                    return this.player_.crossOrigin();
12266
                }
12267
                // Otherwise check options as the  poster is usually set before the state of crossorigin
12268
                // can be retrieved by the getter
12269
                return this.player_.options_.crossOrigin || this.player_.options_.crossorigin || null;
12270
            }
12271
            if (value !== null && value !== 'anonymous' && value !== 'use-credentials') {
12272
                this.player_.log.warn(`crossOrigin must be null,  "anonymous" or "use-credentials", given "${value}"`);
12273
                return;
12274
            }
12275
            if (this.$('img')) {
12276
                this.$('img').crossOrigin = value;
12277
            }
12278
            return;
12279
        }
12280
 
12281
        /**
12282
         * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
12283
         *
12284
         * @listens Player#posterchange
12285
         *
12286
         * @param {Event} [event]
12287
         *        The `Player#posterchange` event that triggered this function.
12288
         */
12289
        update(event) {
12290
            const url = this.player().poster();
12291
            this.setSrc(url);
12292
 
12293
            // If there's no poster source we should display:none on this component
12294
            // so it's not still clickable or right-clickable
12295
            if (url) {
12296
                this.show();
12297
            } else {
12298
                this.hide();
12299
            }
12300
        }
12301
 
12302
        /**
12303
         * Set the source of the `PosterImage` depending on the display method. (Re)creates
12304
         * the inner picture and img elementss when needed.
12305
         *
12306
         * @param {string} [url]
12307
         *        The URL to the source for the `PosterImage`. If not specified or falsy,
12308
         *        any source and ant inner picture/img are removed.
12309
         */
12310
        setSrc(url) {
12311
            if (!url) {
12312
                this.el_.textContent = '';
12313
                return;
12314
            }
12315
            if (!this.$('img')) {
12316
                this.el_.appendChild(createEl('picture', {
12317
                    className: 'vjs-poster',
12318
                    // Don't want poster to be tabbable.
12319
                    tabIndex: -1
12320
                }, {}, createEl('img', {
12321
                    loading: 'lazy',
12322
                    crossOrigin: this.crossOrigin()
12323
                }, {
12324
                    alt: ''
12325
                })));
12326
            }
12327
            this.$('img').src = url;
12328
        }
12329
 
12330
        /**
12331
         * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
12332
         * {@link ClickableComponent#handleClick} for instances where this will be triggered.
12333
         *
12334
         * @listens tap
12335
         * @listens click
12336
         * @listens keydown
12337
         *
12338
         * @param {Event} event
12339
         +        The `click`, `tap` or `keydown` event that caused this function to be called.
12340
         */
12341
        handleClick(event) {
12342
            // We don't want a click to trigger playback when controls are disabled
12343
            if (!this.player_.controls()) {
12344
                return;
12345
            }
12346
            if (this.player_.tech(true)) {
12347
                this.player_.tech(true).focus();
12348
            }
12349
            if (this.player_.paused()) {
12350
                silencePromise(this.player_.play());
12351
            } else {
12352
                this.player_.pause();
12353
            }
12354
        }
12355
    }
12356
 
12357
    /**
12358
     * Get or set the `PosterImage`'s crossorigin option. For the HTML5 player, this
12359
     * sets the `crossOrigin` property on the `<img>` tag to control the CORS
12360
     * behavior.
12361
     *
12362
     * @param {string|null} [value]
12363
     *        The value to set the `PosterImages`'s crossorigin to. If an argument is
12364
     *        given, must be one of `anonymous` or `use-credentials`.
12365
     *
12366
     * @return {string|null|undefined}
12367
     *         - The current crossorigin value of the `Player` when getting.
12368
     *         - undefined when setting
12369
     */
12370
    PosterImage.prototype.crossorigin = PosterImage.prototype.crossOrigin;
12371
    Component$1.registerComponent('PosterImage', PosterImage);
12372
 
12373
    /**
12374
     * @file text-track-display.js
12375
     */
12376
    const darkGray = '#222';
12377
    const lightGray = '#ccc';
12378
    const fontMap = {
12379
        monospace: 'monospace',
12380
        sansSerif: 'sans-serif',
12381
        serif: 'serif',
12382
        monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
12383
        monospaceSerif: '"Courier New", monospace',
12384
        proportionalSansSerif: 'sans-serif',
12385
        proportionalSerif: 'serif',
12386
        casual: '"Comic Sans MS", Impact, fantasy',
12387
        script: '"Monotype Corsiva", cursive',
12388
        smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
12389
    };
12390
 
12391
    /**
12392
     * Construct an rgba color from a given hex color code.
12393
     *
12394
     * @param {number} color
12395
     *        Hex number for color, like #f0e or #f604e2.
12396
     *
12397
     * @param {number} opacity
12398
     *        Value for opacity, 0.0 - 1.0.
12399
     *
12400
     * @return {string}
12401
     *         The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
12402
     */
12403
    function constructColor(color, opacity) {
12404
        let hex;
12405
        if (color.length === 4) {
12406
            // color looks like "#f0e"
12407
            hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
12408
        } else if (color.length === 7) {
12409
            // color looks like "#f604e2"
12410
            hex = color.slice(1);
12411
        } else {
12412
            throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
12413
        }
12414
        return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
12415
    }
12416
 
12417
    /**
12418
     * Try to update the style of a DOM element. Some style changes will throw an error,
12419
     * particularly in IE8. Those should be noops.
12420
     *
12421
     * @param {Element} el
12422
     *        The DOM element to be styled.
12423
     *
12424
     * @param {string} style
12425
     *        The CSS property on the element that should be styled.
12426
     *
12427
     * @param {string} rule
12428
     *        The style rule that should be applied to the property.
12429
     *
12430
     * @private
12431
     */
12432
    function tryUpdateStyle(el, style, rule) {
12433
        try {
12434
            el.style[style] = rule;
12435
        } catch (e) {
12436
            // Satisfies linter.
12437
            return;
12438
        }
12439
    }
12440
 
12441
    /**
12442
     * Converts the CSS top/right/bottom/left property numeric value to string in pixels.
12443
     *
12444
     * @param {number} position
12445
     *        The CSS top/right/bottom/left property value.
12446
     *
12447
     * @return {string}
12448
     *          The CSS property value that was created, like '10px'.
12449
     *
12450
     * @private
12451
     */
12452
    function getCSSPositionValue(position) {
12453
        return position ? `${position}px` : '';
12454
    }
12455
 
12456
    /**
12457
     * The component for displaying text track cues.
12458
     *
12459
     * @extends Component
12460
     */
12461
    class TextTrackDisplay extends Component$1 {
12462
        /**
12463
         * Creates an instance of this class.
12464
         *
12465
         * @param { import('../player').default } player
12466
         *        The `Player` that this class should be attached to.
12467
         *
12468
         * @param {Object} [options]
12469
         *        The key/value store of player options.
12470
         *
12471
         * @param {Function} [ready]
12472
         *        The function to call when `TextTrackDisplay` is ready.
12473
         */
12474
        constructor(player, options, ready) {
12475
            super(player, options, ready);
12476
            const updateDisplayTextHandler = e => this.updateDisplay(e);
12477
            const updateDisplayHandler = e => {
12478
                this.updateDisplayOverlay();
12479
                this.updateDisplay(e);
12480
            };
12481
            player.on('loadstart', e => this.toggleDisplay(e));
12482
            player.on('texttrackchange', updateDisplayTextHandler);
12483
            player.on('loadedmetadata', e => {
12484
                this.updateDisplayOverlay();
12485
                this.preselectTrack(e);
12486
            });
12487
 
12488
            // This used to be called during player init, but was causing an error
12489
            // if a track should show by default and the display hadn't loaded yet.
12490
            // Should probably be moved to an external track loader when we support
12491
            // tracks that don't need a display.
12492
            player.ready(bind_(this, function () {
12493
                if (player.tech_ && player.tech_.featuresNativeTextTracks) {
12494
                    this.hide();
12495
                    return;
12496
                }
12497
                player.on('fullscreenchange', updateDisplayHandler);
12498
                player.on('playerresize', updateDisplayHandler);
12499
                const screenOrientation = window.screen.orientation || window;
12500
                const changeOrientationEvent = window.screen.orientation ? 'change' : 'orientationchange';
12501
                screenOrientation.addEventListener(changeOrientationEvent, updateDisplayHandler);
12502
                player.on('dispose', () => screenOrientation.removeEventListener(changeOrientationEvent, updateDisplayHandler));
12503
                const tracks = this.options_.playerOptions.tracks || [];
12504
                for (let i = 0; i < tracks.length; i++) {
12505
                    this.player_.addRemoteTextTrack(tracks[i], true);
12506
                }
12507
                this.preselectTrack();
12508
            }));
12509
        }
12510
 
12511
        /**
12512
         * Preselect a track following this precedence:
12513
         * - matches the previously selected {@link TextTrack}'s language and kind
12514
         * - matches the previously selected {@link TextTrack}'s language only
12515
         * - is the first default captions track
12516
         * - is the first default descriptions track
12517
         *
12518
         * @listens Player#loadstart
12519
         */
12520
        preselectTrack() {
12521
            const modes = {
12522
                captions: 1,
12523
                subtitles: 1
12524
            };
12525
            const trackList = this.player_.textTracks();
12526
            const userPref = this.player_.cache_.selectedLanguage;
12527
            let firstDesc;
12528
            let firstCaptions;
12529
            let preferredTrack;
12530
            for (let i = 0; i < trackList.length; i++) {
12531
                const track = trackList[i];
12532
                if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) {
12533
                    // Always choose the track that matches both language and kind
12534
                    if (track.kind === userPref.kind) {
12535
                        preferredTrack = track;
12536
                        // or choose the first track that matches language
12537
                    } else if (!preferredTrack) {
12538
                        preferredTrack = track;
12539
                    }
12540
 
12541
                    // clear everything if offTextTrackMenuItem was clicked
12542
                } else if (userPref && !userPref.enabled) {
12543
                    preferredTrack = null;
12544
                    firstDesc = null;
12545
                    firstCaptions = null;
12546
                } else if (track.default) {
12547
                    if (track.kind === 'descriptions' && !firstDesc) {
12548
                        firstDesc = track;
12549
                    } else if (track.kind in modes && !firstCaptions) {
12550
                        firstCaptions = track;
12551
                    }
12552
                }
12553
            }
12554
 
12555
            // The preferredTrack matches the user preference and takes
12556
            // precedence over all the other tracks.
12557
            // So, display the preferredTrack before the first default track
12558
            // and the subtitles/captions track before the descriptions track
12559
            if (preferredTrack) {
12560
                preferredTrack.mode = 'showing';
12561
            } else if (firstCaptions) {
12562
                firstCaptions.mode = 'showing';
12563
            } else if (firstDesc) {
12564
                firstDesc.mode = 'showing';
12565
            }
12566
        }
12567
 
12568
        /**
12569
         * Turn display of {@link TextTrack}'s from the current state into the other state.
12570
         * There are only two states:
12571
         * - 'shown'
12572
         * - 'hidden'
12573
         *
12574
         * @listens Player#loadstart
12575
         */
12576
        toggleDisplay() {
12577
            if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
12578
                this.hide();
12579
            } else {
12580
                this.show();
12581
            }
12582
        }
12583
 
12584
        /**
12585
         * Create the {@link Component}'s DOM element.
12586
         *
12587
         * @return {Element}
12588
         *         The element that was created.
12589
         */
12590
        createEl() {
12591
            return super.createEl('div', {
12592
                className: 'vjs-text-track-display'
12593
            }, {
12594
                'translate': 'yes',
12595
                'aria-live': 'off',
12596
                'aria-atomic': 'true'
12597
            });
12598
        }
12599
 
12600
        /**
12601
         * Clear all displayed {@link TextTrack}s.
12602
         */
12603
        clearDisplay() {
12604
            if (typeof window.WebVTT === 'function') {
12605
                window.WebVTT.processCues(window, [], this.el_);
12606
            }
12607
        }
12608
 
12609
        /**
12610
         * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
12611
         * a {@link Player#fullscreenchange} is fired.
12612
         *
12613
         * @listens Player#texttrackchange
12614
         * @listens Player#fullscreenchange
12615
         */
12616
        updateDisplay() {
12617
            const tracks = this.player_.textTracks();
12618
            const allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks;
12619
            this.clearDisplay();
12620
            if (allowMultipleShowingTracks) {
12621
                const showingTracks = [];
12622
                for (let i = 0; i < tracks.length; ++i) {
12623
                    const track = tracks[i];
12624
                    if (track.mode !== 'showing') {
12625
                        continue;
12626
                    }
12627
                    showingTracks.push(track);
12628
                }
12629
                this.updateForTrack(showingTracks);
12630
                return;
12631
            }
12632
 
12633
            //  Track display prioritization model: if multiple tracks are 'showing',
12634
            //  display the first 'subtitles' or 'captions' track which is 'showing',
12635
            //  otherwise display the first 'descriptions' track which is 'showing'
12636
 
12637
            let descriptionsTrack = null;
12638
            let captionsSubtitlesTrack = null;
12639
            let i = tracks.length;
12640
            while (i--) {
12641
                const track = tracks[i];
12642
                if (track.mode === 'showing') {
12643
                    if (track.kind === 'descriptions') {
12644
                        descriptionsTrack = track;
12645
                    } else {
12646
                        captionsSubtitlesTrack = track;
12647
                    }
12648
                }
12649
            }
12650
            if (captionsSubtitlesTrack) {
12651
                if (this.getAttribute('aria-live') !== 'off') {
12652
                    this.setAttribute('aria-live', 'off');
12653
                }
12654
                this.updateForTrack(captionsSubtitlesTrack);
12655
            } else if (descriptionsTrack) {
12656
                if (this.getAttribute('aria-live') !== 'assertive') {
12657
                    this.setAttribute('aria-live', 'assertive');
12658
                }
12659
                this.updateForTrack(descriptionsTrack);
12660
            }
12661
        }
12662
 
12663
        /**
12664
         * Updates the displayed TextTrack to be sure it overlays the video when a either
12665
         * a {@link Player#texttrackchange} or a {@link Player#fullscreenchange} is fired.
12666
         */
12667
        updateDisplayOverlay() {
12668
            // inset-inline and inset-block are not supprted on old chrome, but these are
12669
            // only likely to be used on TV devices
12670
            if (!this.player_.videoHeight() || !window.CSS.supports('inset-inline: 10px')) {
12671
                return;
12672
            }
12673
            const playerWidth = this.player_.currentWidth();
12674
            const playerHeight = this.player_.currentHeight();
12675
            const playerAspectRatio = playerWidth / playerHeight;
12676
            const videoAspectRatio = this.player_.videoWidth() / this.player_.videoHeight();
12677
            let insetInlineMatch = 0;
12678
            let insetBlockMatch = 0;
12679
            if (Math.abs(playerAspectRatio - videoAspectRatio) > 0.1) {
12680
                if (playerAspectRatio > videoAspectRatio) {
12681
                    insetInlineMatch = Math.round((playerWidth - playerHeight * videoAspectRatio) / 2);
12682
                } else {
12683
                    insetBlockMatch = Math.round((playerHeight - playerWidth / videoAspectRatio) / 2);
12684
                }
12685
            }
12686
            tryUpdateStyle(this.el_, 'insetInline', getCSSPositionValue(insetInlineMatch));
12687
            tryUpdateStyle(this.el_, 'insetBlock', getCSSPositionValue(insetBlockMatch));
12688
        }
12689
 
12690
        /**
12691
         * Style {@Link TextTrack} activeCues according to {@Link TextTrackSettings}.
12692
         *
12693
         * @param {TextTrack} track
12694
         *        Text track object containing active cues to style.
12695
         */
12696
        updateDisplayState(track) {
12697
            const overrides = this.player_.textTrackSettings.getValues();
12698
            const cues = track.activeCues;
12699
            let i = cues.length;
12700
            while (i--) {
12701
                const cue = cues[i];
12702
                if (!cue) {
12703
                    continue;
12704
                }
12705
                const cueDiv = cue.displayState;
12706
                if (overrides.color) {
12707
                    cueDiv.firstChild.style.color = overrides.color;
12708
                }
12709
                if (overrides.textOpacity) {
12710
                    tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
12711
                }
12712
                if (overrides.backgroundColor) {
12713
                    cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
12714
                }
12715
                if (overrides.backgroundOpacity) {
12716
                    tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
12717
                }
12718
                if (overrides.windowColor) {
12719
                    if (overrides.windowOpacity) {
12720
                        tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
12721
                    } else {
12722
                        cueDiv.style.backgroundColor = overrides.windowColor;
12723
                    }
12724
                }
12725
                if (overrides.edgeStyle) {
12726
                    if (overrides.edgeStyle === 'dropshadow') {
12727
                        cueDiv.firstChild.style.textShadow = `2px 2px 3px ${darkGray}, 2px 2px 4px ${darkGray}, 2px 2px 5px ${darkGray}`;
12728
                    } else if (overrides.edgeStyle === 'raised') {
12729
                        cueDiv.firstChild.style.textShadow = `1px 1px ${darkGray}, 2px 2px ${darkGray}, 3px 3px ${darkGray}`;
12730
                    } else if (overrides.edgeStyle === 'depressed') {
12731
                        cueDiv.firstChild.style.textShadow = `1px 1px ${lightGray}, 0 1px ${lightGray}, -1px -1px ${darkGray}, 0 -1px ${darkGray}`;
12732
                    } else if (overrides.edgeStyle === 'uniform') {
12733
                        cueDiv.firstChild.style.textShadow = `0 0 4px ${darkGray}, 0 0 4px ${darkGray}, 0 0 4px ${darkGray}, 0 0 4px ${darkGray}`;
12734
                    }
12735
                }
12736
                if (overrides.fontPercent && overrides.fontPercent !== 1) {
12737
                    const fontSize = window.parseFloat(cueDiv.style.fontSize);
12738
                    cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
12739
                    cueDiv.style.height = 'auto';
12740
                    cueDiv.style.top = 'auto';
12741
                }
12742
                if (overrides.fontFamily && overrides.fontFamily !== 'default') {
12743
                    if (overrides.fontFamily === 'small-caps') {
12744
                        cueDiv.firstChild.style.fontVariant = 'small-caps';
12745
                    } else {
12746
                        cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
12747
                    }
12748
                }
12749
            }
12750
        }
12751
 
12752
        /**
12753
         * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
12754
         *
12755
         * @param {TextTrack|TextTrack[]} tracks
12756
         *        Text track object or text track array to be added to the list.
12757
         */
12758
        updateForTrack(tracks) {
12759
            if (!Array.isArray(tracks)) {
12760
                tracks = [tracks];
12761
            }
12762
            if (typeof window.WebVTT !== 'function' || tracks.every(track => {
12763
                return !track.activeCues;
12764
            })) {
12765
                return;
12766
            }
12767
            const cues = [];
12768
 
12769
            // push all active track cues
12770
            for (let i = 0; i < tracks.length; ++i) {
12771
                const track = tracks[i];
12772
                for (let j = 0; j < track.activeCues.length; ++j) {
12773
                    cues.push(track.activeCues[j]);
12774
                }
12775
            }
12776
 
12777
            // removes all cues before it processes new ones
12778
            window.WebVTT.processCues(window, cues, this.el_);
12779
 
12780
            // add unique class to each language text track & add settings styling if necessary
12781
            for (let i = 0; i < tracks.length; ++i) {
12782
                const track = tracks[i];
12783
                for (let j = 0; j < track.activeCues.length; ++j) {
12784
                    const cueEl = track.activeCues[j].displayState;
12785
                    addClass(cueEl, 'vjs-text-track-cue', 'vjs-text-track-cue-' + (track.language ? track.language : i));
12786
                    if (track.language) {
12787
                        setAttribute(cueEl, 'lang', track.language);
12788
                    }
12789
                }
12790
                if (this.player_.textTrackSettings) {
12791
                    this.updateDisplayState(track);
12792
                }
12793
            }
12794
        }
12795
    }
12796
    Component$1.registerComponent('TextTrackDisplay', TextTrackDisplay);
12797
 
12798
    /**
12799
     * @file loading-spinner.js
12800
     */
12801
 
12802
    /**
12803
     * A loading spinner for use during waiting/loading events.
12804
     *
12805
     * @extends Component
12806
     */
12807
    class LoadingSpinner extends Component$1 {
12808
        /**
12809
         * Create the `LoadingSpinner`s DOM element.
12810
         *
12811
         * @return {Element}
12812
         *         The dom element that gets created.
12813
         */
12814
        createEl() {
12815
            const isAudio = this.player_.isAudio();
12816
            const playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
12817
            const controlText = createEl('span', {
12818
                className: 'vjs-control-text',
12819
                textContent: this.localize('{1} is loading.', [playerType])
12820
            });
12821
            const el = super.createEl('div', {
12822
                className: 'vjs-loading-spinner',
12823
                dir: 'ltr'
12824
            });
12825
            el.appendChild(controlText);
12826
            return el;
12827
        }
12828
 
12829
        /**
12830
         * Update control text on languagechange
12831
         */
12832
        handleLanguagechange() {
12833
            this.$('.vjs-control-text').textContent = this.localize('{1} is loading.', [this.player_.isAudio() ? 'Audio Player' : 'Video Player']);
12834
        }
12835
    }
12836
    Component$1.registerComponent('LoadingSpinner', LoadingSpinner);
12837
 
12838
    /**
12839
     * @file button.js
12840
     */
12841
 
12842
    /**
12843
     * Base class for all buttons.
12844
     *
12845
     * @extends ClickableComponent
12846
     */
12847
    class Button extends ClickableComponent {
12848
        /**
12849
         * Create the `Button`s DOM element.
12850
         *
12851
         * @param {string} [tag="button"]
12852
         *        The element's node type. This argument is IGNORED: no matter what
12853
         *        is passed, it will always create a `button` element.
12854
         *
12855
         * @param {Object} [props={}]
12856
         *        An object of properties that should be set on the element.
12857
         *
12858
         * @param {Object} [attributes={}]
12859
         *        An object of attributes that should be set on the element.
12860
         *
12861
         * @return {Element}
12862
         *         The element that gets created.
12863
         */
12864
        createEl(tag, props = {}, attributes = {}) {
12865
            tag = 'button';
12866
            props = Object.assign({
12867
                className: this.buildCSSClass()
12868
            }, props);
12869
 
12870
            // Add attributes for button element
12871
            attributes = Object.assign({
12872
                // Necessary since the default button type is "submit"
12873
                type: 'button'
12874
            }, attributes);
12875
            const el = createEl(tag, props, attributes);
12876
            if (!this.player_.options_.experimentalSvgIcons) {
12877
                el.appendChild(createEl('span', {
12878
                    className: 'vjs-icon-placeholder'
12879
                }, {
12880
                    'aria-hidden': true
12881
                }));
12882
            }
12883
            this.createControlTextEl(el);
12884
            return el;
12885
        }
12886
 
12887
        /**
12888
         * Add a child `Component` inside of this `Button`.
12889
         *
12890
         * @param {string|Component} child
12891
         *        The name or instance of a child to add.
12892
         *
12893
         * @param {Object} [options={}]
12894
         *        The key/value store of options that will get passed to children of
12895
         *        the child.
12896
         *
12897
         * @return {Component}
12898
         *         The `Component` that gets added as a child. When using a string the
12899
         *         `Component` will get created by this process.
12900
         *
12901
         * @deprecated since version 5
12902
         */
12903
        addChild(child, options = {}) {
12904
            const className = this.constructor.name;
12905
            log$1.warn(`Adding an actionable (user controllable) child to a Button (${className}) is not supported; use a ClickableComponent instead.`);
12906
 
12907
            // Avoid the error message generated by ClickableComponent's addChild method
12908
            return Component$1.prototype.addChild.call(this, child, options);
12909
        }
12910
 
12911
        /**
12912
         * Enable the `Button` element so that it can be activated or clicked. Use this with
12913
         * {@link Button#disable}.
12914
         */
12915
        enable() {
12916
            super.enable();
12917
            this.el_.removeAttribute('disabled');
12918
        }
12919
 
12920
        /**
12921
         * Disable the `Button` element so that it cannot be activated or clicked. Use this with
12922
         * {@link Button#enable}.
12923
         */
12924
        disable() {
12925
            super.disable();
12926
            this.el_.setAttribute('disabled', 'disabled');
12927
        }
12928
 
12929
        /**
12930
         * This gets called when a `Button` has focus and `keydown` is triggered via a key
12931
         * press.
12932
         *
12933
         * @param {KeyboardEvent} event
12934
         *        The event that caused this function to get called.
12935
         *
12936
         * @listens keydown
12937
         */
12938
        handleKeyDown(event) {
12939
            // Ignore Space or Enter key operation, which is handled by the browser for
12940
            // a button - though not for its super class, ClickableComponent. Also,
12941
            // prevent the event from propagating through the DOM and triggering Player
12942
            // hotkeys. We do not preventDefault here because we _want_ the browser to
12943
            // handle it.
12944
            if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
12945
                event.stopPropagation();
12946
                return;
12947
            }
12948
 
12949
            // Pass keypress handling up for unsupported keys
12950
            super.handleKeyDown(event);
12951
        }
12952
    }
12953
    Component$1.registerComponent('Button', Button);
12954
 
12955
    /**
12956
     * @file big-play-button.js
12957
     */
12958
 
12959
    /**
12960
     * The initial play button that shows before the video has played. The hiding of the
12961
     * `BigPlayButton` get done via CSS and `Player` states.
12962
     *
12963
     * @extends Button
12964
     */
12965
    class BigPlayButton extends Button {
12966
        constructor(player, options) {
12967
            super(player, options);
12968
            this.mouseused_ = false;
12969
            this.setIcon('play');
12970
            this.on('mousedown', e => this.handleMouseDown(e));
12971
        }
12972
 
12973
        /**
12974
         * Builds the default DOM `className`.
12975
         *
12976
         * @return {string}
12977
         *         The DOM `className` for this object. Always returns 'vjs-big-play-button'.
12978
         */
12979
        buildCSSClass() {
12980
            return 'vjs-big-play-button';
12981
        }
12982
 
12983
        /**
12984
         * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
12985
         * for more detailed information on what a click can be.
12986
         *
12987
         * @param {KeyboardEvent|MouseEvent|TouchEvent} event
12988
         *        The `keydown`, `tap`, or `click` event that caused this function to be
12989
         *        called.
12990
         *
12991
         * @listens tap
12992
         * @listens click
12993
         */
12994
        handleClick(event) {
12995
            const playPromise = this.player_.play();
12996
 
12997
            // exit early if clicked via the mouse
12998
            if (this.mouseused_ && 'clientX' in event && 'clientY' in event) {
12999
                silencePromise(playPromise);
13000
                if (this.player_.tech(true)) {
13001
                    this.player_.tech(true).focus();
13002
                }
13003
                return;
13004
            }
13005
            const cb = this.player_.getChild('controlBar');
13006
            const playToggle = cb && cb.getChild('playToggle');
13007
            if (!playToggle) {
13008
                this.player_.tech(true).focus();
13009
                return;
13010
            }
13011
            const playFocus = () => playToggle.focus();
13012
            if (isPromise(playPromise)) {
13013
                playPromise.then(playFocus, () => {});
13014
            } else {
13015
                this.setTimeout(playFocus, 1);
13016
            }
13017
        }
13018
 
13019
        /**
13020
         * Event handler that is called when a `BigPlayButton` receives a
13021
         * `keydown` event.
13022
         *
13023
         * @param {KeyboardEvent} event
13024
         *        The `keydown` event that caused this function to be called.
13025
         *
13026
         * @listens keydown
13027
         */
13028
        handleKeyDown(event) {
13029
            this.mouseused_ = false;
13030
            super.handleKeyDown(event);
13031
        }
13032
 
13033
        /**
13034
         * Handle `mousedown` events on the `BigPlayButton`.
13035
         *
13036
         * @param {MouseEvent} event
13037
         *        `mousedown` or `touchstart` event that triggered this function
13038
         *
13039
         * @listens mousedown
13040
         */
13041
        handleMouseDown(event) {
13042
            this.mouseused_ = true;
13043
        }
13044
    }
13045
 
13046
    /**
13047
     * The text that should display over the `BigPlayButton`s controls. Added to for localization.
13048
     *
13049
     * @type {string}
13050
     * @protected
13051
     */
13052
    BigPlayButton.prototype.controlText_ = 'Play Video';
13053
    Component$1.registerComponent('BigPlayButton', BigPlayButton);
13054
 
13055
    /**
13056
     * @file close-button.js
13057
     */
13058
 
13059
    /**
13060
     * The `CloseButton` is a `{@link Button}` that fires a `close` event when
13061
     * it gets clicked.
13062
     *
13063
     * @extends Button
13064
     */
13065
    class CloseButton extends Button {
13066
        /**
13067
         * Creates an instance of the this class.
13068
         *
13069
         * @param  { import('./player').default } player
13070
         *         The `Player` that this class should be attached to.
13071
         *
13072
         * @param  {Object} [options]
13073
         *         The key/value store of player options.
13074
         */
13075
        constructor(player, options) {
13076
            super(player, options);
13077
            this.setIcon('cancel');
13078
            this.controlText(options && options.controlText || this.localize('Close'));
13079
        }
13080
 
13081
        /**
13082
         * Builds the default DOM `className`.
13083
         *
13084
         * @return {string}
13085
         *         The DOM `className` for this object.
13086
         */
13087
        buildCSSClass() {
13088
            return `vjs-close-button ${super.buildCSSClass()}`;
13089
        }
13090
 
13091
        /**
13092
         * This gets called when a `CloseButton` gets clicked. See
13093
         * {@link ClickableComponent#handleClick} for more information on when
13094
         * this will be triggered
13095
         *
13096
         * @param {Event} event
13097
         *        The `keydown`, `tap`, or `click` event that caused this function to be
13098
         *        called.
13099
         *
13100
         * @listens tap
13101
         * @listens click
13102
         * @fires CloseButton#close
13103
         */
13104
        handleClick(event) {
13105
            /**
13106
             * Triggered when the a `CloseButton` is clicked.
13107
             *
13108
             * @event CloseButton#close
13109
             * @type {Event}
13110
             *
13111
             * @property {boolean} [bubbles=false]
13112
             *           set to false so that the close event does not
13113
             *           bubble up to parents if there is no listener
13114
             */
13115
            this.trigger({
13116
                type: 'close',
13117
                bubbles: false
13118
            });
13119
        }
13120
        /**
13121
         * Event handler that is called when a `CloseButton` receives a
13122
         * `keydown` event.
13123
         *
13124
         * By default, if the key is Esc, it will trigger a `click` event.
13125
         *
13126
         * @param {KeyboardEvent} event
13127
         *        The `keydown` event that caused this function to be called.
13128
         *
13129
         * @listens keydown
13130
         */
13131
        handleKeyDown(event) {
13132
            // Esc button will trigger `click` event
13133
            if (keycode.isEventKey(event, 'Esc')) {
13134
                event.preventDefault();
13135
                event.stopPropagation();
13136
                this.trigger('click');
13137
            } else {
13138
                // Pass keypress handling up for unsupported keys
13139
                super.handleKeyDown(event);
13140
            }
13141
        }
13142
    }
13143
    Component$1.registerComponent('CloseButton', CloseButton);
13144
 
13145
    /**
13146
     * @file play-toggle.js
13147
     */
13148
 
13149
    /**
13150
     * Button to toggle between play and pause.
13151
     *
13152
     * @extends Button
13153
     */
13154
    class PlayToggle extends Button {
13155
        /**
13156
         * Creates an instance of this class.
13157
         *
13158
         * @param { import('./player').default } player
13159
         *        The `Player` that this class should be attached to.
13160
         *
13161
         * @param {Object} [options={}]
13162
         *        The key/value store of player options.
13163
         */
13164
        constructor(player, options = {}) {
13165
            super(player, options);
13166
 
13167
            // show or hide replay icon
13168
            options.replay = options.replay === undefined || options.replay;
13169
            this.setIcon('play');
13170
            this.on(player, 'play', e => this.handlePlay(e));
13171
            this.on(player, 'pause', e => this.handlePause(e));
13172
            if (options.replay) {
13173
                this.on(player, 'ended', e => this.handleEnded(e));
13174
            }
13175
        }
13176
 
13177
        /**
13178
         * Builds the default DOM `className`.
13179
         *
13180
         * @return {string}
13181
         *         The DOM `className` for this object.
13182
         */
13183
        buildCSSClass() {
13184
            return `vjs-play-control ${super.buildCSSClass()}`;
13185
        }
13186
 
13187
        /**
13188
         * This gets called when an `PlayToggle` is "clicked". See
13189
         * {@link ClickableComponent} for more detailed information on what a click can be.
13190
         *
13191
         * @param {Event} [event]
13192
         *        The `keydown`, `tap`, or `click` event that caused this function to be
13193
         *        called.
13194
         *
13195
         * @listens tap
13196
         * @listens click
13197
         */
13198
        handleClick(event) {
13199
            if (this.player_.paused()) {
13200
                silencePromise(this.player_.play());
13201
            } else {
13202
                this.player_.pause();
13203
            }
13204
        }
13205
 
13206
        /**
13207
         * This gets called once after the video has ended and the user seeks so that
13208
         * we can change the replay button back to a play button.
13209
         *
13210
         * @param {Event} [event]
13211
         *        The event that caused this function to run.
13212
         *
13213
         * @listens Player#seeked
13214
         */
13215
        handleSeeked(event) {
13216
            this.removeClass('vjs-ended');
13217
            if (this.player_.paused()) {
13218
                this.handlePause(event);
13219
            } else {
13220
                this.handlePlay(event);
13221
            }
13222
        }
13223
 
13224
        /**
13225
         * Add the vjs-playing class to the element so it can change appearance.
13226
         *
13227
         * @param {Event} [event]
13228
         *        The event that caused this function to run.
13229
         *
13230
         * @listens Player#play
13231
         */
13232
        handlePlay(event) {
13233
            this.removeClass('vjs-ended', 'vjs-paused');
13234
            this.addClass('vjs-playing');
13235
            // change the button text to "Pause"
13236
            this.setIcon('pause');
13237
            this.controlText('Pause');
13238
        }
13239
 
13240
        /**
13241
         * Add the vjs-paused class to the element so it can change appearance.
13242
         *
13243
         * @param {Event} [event]
13244
         *        The event that caused this function to run.
13245
         *
13246
         * @listens Player#pause
13247
         */
13248
        handlePause(event) {
13249
            this.removeClass('vjs-playing');
13250
            this.addClass('vjs-paused');
13251
            // change the button text to "Play"
13252
            this.setIcon('play');
13253
            this.controlText('Play');
13254
        }
13255
 
13256
        /**
13257
         * Add the vjs-ended class to the element so it can change appearance
13258
         *
13259
         * @param {Event} [event]
13260
         *        The event that caused this function to run.
13261
         *
13262
         * @listens Player#ended
13263
         */
13264
        handleEnded(event) {
13265
            this.removeClass('vjs-playing');
13266
            this.addClass('vjs-ended');
13267
            // change the button text to "Replay"
13268
            this.setIcon('replay');
13269
            this.controlText('Replay');
13270
 
13271
            // on the next seek remove the replay button
13272
            this.one(this.player_, 'seeked', e => this.handleSeeked(e));
13273
        }
13274
    }
13275
 
13276
    /**
13277
     * The text that should display over the `PlayToggle`s controls. Added for localization.
13278
     *
13279
     * @type {string}
13280
     * @protected
13281
     */
13282
    PlayToggle.prototype.controlText_ = 'Play';
13283
    Component$1.registerComponent('PlayToggle', PlayToggle);
13284
 
13285
    /**
13286
     * @file time-display.js
13287
     */
13288
 
13289
    /**
13290
     * Displays time information about the video
13291
     *
13292
     * @extends Component
13293
     */
13294
    class TimeDisplay extends Component$1 {
13295
        /**
13296
         * Creates an instance of this class.
13297
         *
13298
         * @param { import('../../player').default } player
13299
         *        The `Player` that this class should be attached to.
13300
         *
13301
         * @param {Object} [options]
13302
         *        The key/value store of player options.
13303
         */
13304
        constructor(player, options) {
13305
            super(player, options);
13306
            this.on(player, ['timeupdate', 'ended', 'seeking'], e => this.update(e));
13307
            this.updateTextNode_();
13308
        }
13309
 
13310
        /**
13311
         * Create the `Component`'s DOM element
13312
         *
13313
         * @return {Element}
13314
         *         The element that was created.
13315
         */
13316
        createEl() {
13317
            const className = this.buildCSSClass();
13318
            const el = super.createEl('div', {
13319
                className: `${className} vjs-time-control vjs-control`
13320
            });
13321
            const span = createEl('span', {
13322
                className: 'vjs-control-text',
13323
                textContent: `${this.localize(this.labelText_)}\u00a0`
13324
            }, {
13325
                role: 'presentation'
13326
            });
13327
            el.appendChild(span);
13328
            this.contentEl_ = createEl('span', {
13329
                className: `${className}-display`
13330
            }, {
13331
                // span elements have no implicit role, but some screen readers (notably VoiceOver)
13332
                // treat them as a break between items in the DOM when using arrow keys
13333
                // (or left-to-right swipes on iOS) to read contents of a page. Using
13334
                // role='presentation' causes VoiceOver to NOT treat this span as a break.
13335
                role: 'presentation'
13336
            });
13337
            el.appendChild(this.contentEl_);
13338
            return el;
13339
        }
13340
        dispose() {
13341
            this.contentEl_ = null;
13342
            this.textNode_ = null;
13343
            super.dispose();
13344
        }
13345
 
13346
        /**
13347
         * Updates the displayed time according to the `updateContent` function which is defined in the child class.
13348
         *
13349
         * @param {Event} [event]
13350
         *          The `timeupdate`, `ended` or `seeking` (if enableSmoothSeeking is true) event that caused this function to be called.
13351
         */
13352
        update(event) {
13353
            if (!this.player_.options_.enableSmoothSeeking && event.type === 'seeking') {
13354
                return;
13355
            }
13356
            this.updateContent(event);
13357
        }
13358
 
13359
        /**
13360
         * Updates the time display text node with a new time
13361
         *
13362
         * @param {number} [time=0] the time to update to
13363
         *
13364
         * @private
13365
         */
13366
        updateTextNode_(time = 0) {
13367
            time = formatTime(time);
13368
            if (this.formattedTime_ === time) {
13369
                return;
13370
            }
13371
            this.formattedTime_ = time;
13372
            this.requestNamedAnimationFrame('TimeDisplay#updateTextNode_', () => {
13373
                if (!this.contentEl_) {
13374
                    return;
13375
                }
13376
                let oldNode = this.textNode_;
13377
                if (oldNode && this.contentEl_.firstChild !== oldNode) {
13378
                    oldNode = null;
13379
                    log$1.warn('TimeDisplay#updateTextnode_: Prevented replacement of text node element since it was no longer a child of this node. Appending a new node instead.');
13380
                }
13381
                this.textNode_ = document.createTextNode(this.formattedTime_);
13382
                if (!this.textNode_) {
13383
                    return;
13384
                }
13385
                if (oldNode) {
13386
                    this.contentEl_.replaceChild(this.textNode_, oldNode);
13387
                } else {
13388
                    this.contentEl_.appendChild(this.textNode_);
13389
                }
13390
            });
13391
        }
13392
 
13393
        /**
13394
         * To be filled out in the child class, should update the displayed time
13395
         * in accordance with the fact that the current time has changed.
13396
         *
13397
         * @param {Event} [event]
13398
         *        The `timeupdate`  event that caused this to run.
13399
         *
13400
         * @listens Player#timeupdate
13401
         */
13402
        updateContent(event) {}
13403
    }
13404
 
13405
    /**
13406
     * The text that is added to the `TimeDisplay` for screen reader users.
13407
     *
13408
     * @type {string}
13409
     * @private
13410
     */
13411
    TimeDisplay.prototype.labelText_ = 'Time';
13412
 
13413
    /**
13414
     * The text that should display over the `TimeDisplay`s controls. Added to for localization.
13415
     *
13416
     * @type {string}
13417
     * @protected
13418
     *
13419
     * @deprecated in v7; controlText_ is not used in non-active display Components
13420
     */
13421
    TimeDisplay.prototype.controlText_ = 'Time';
13422
    Component$1.registerComponent('TimeDisplay', TimeDisplay);
13423
 
13424
    /**
13425
     * @file current-time-display.js
13426
     */
13427
 
13428
    /**
13429
     * Displays the current time
13430
     *
13431
     * @extends Component
13432
     */
13433
    class CurrentTimeDisplay extends TimeDisplay {
13434
        /**
13435
         * Builds the default DOM `className`.
13436
         *
13437
         * @return {string}
13438
         *         The DOM `className` for this object.
13439
         */
13440
        buildCSSClass() {
13441
            return 'vjs-current-time';
13442
        }
13443
 
13444
        /**
13445
         * Update current time display
13446
         *
13447
         * @param {Event} [event]
13448
         *        The `timeupdate` event that caused this function to run.
13449
         *
13450
         * @listens Player#timeupdate
13451
         */
13452
        updateContent(event) {
13453
            // Allows for smooth scrubbing, when player can't keep up.
13454
            let time;
13455
            if (this.player_.ended()) {
13456
                time = this.player_.duration();
13457
            } else {
13458
                time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
13459
            }
13460
            this.updateTextNode_(time);
13461
        }
13462
    }
13463
 
13464
    /**
13465
     * The text that is added to the `CurrentTimeDisplay` for screen reader users.
13466
     *
13467
     * @type {string}
13468
     * @private
13469
     */
13470
    CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
13471
 
13472
    /**
13473
     * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
13474
     *
13475
     * @type {string}
13476
     * @protected
13477
     *
13478
     * @deprecated in v7; controlText_ is not used in non-active display Components
13479
     */
13480
    CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
13481
    Component$1.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
13482
 
13483
    /**
13484
     * @file duration-display.js
13485
     */
13486
 
13487
    /**
13488
     * Displays the duration
13489
     *
13490
     * @extends Component
13491
     */
13492
    class DurationDisplay extends TimeDisplay {
13493
        /**
13494
         * Creates an instance of this class.
13495
         *
13496
         * @param { import('../../player').default } player
13497
         *        The `Player` that this class should be attached to.
13498
         *
13499
         * @param {Object} [options]
13500
         *        The key/value store of player options.
13501
         */
13502
        constructor(player, options) {
13503
            super(player, options);
13504
            const updateContent = e => this.updateContent(e);
13505
 
13506
            // we do not want to/need to throttle duration changes,
13507
            // as they should always display the changed duration as
13508
            // it has changed
13509
            this.on(player, 'durationchange', updateContent);
13510
 
13511
            // Listen to loadstart because the player duration is reset when a new media element is loaded,
13512
            // but the durationchange on the user agent will not fire.
13513
            // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
13514
            this.on(player, 'loadstart', updateContent);
13515
 
13516
            // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
13517
            // listeners could have broken dependent applications/libraries. These
13518
            // can likely be removed for 7.0.
13519
            this.on(player, 'loadedmetadata', updateContent);
13520
        }
13521
 
13522
        /**
13523
         * Builds the default DOM `className`.
13524
         *
13525
         * @return {string}
13526
         *         The DOM `className` for this object.
13527
         */
13528
        buildCSSClass() {
13529
            return 'vjs-duration';
13530
        }
13531
 
13532
        /**
13533
         * Update duration time display.
13534
         *
13535
         * @param {Event} [event]
13536
         *        The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
13537
         *        this function to be called.
13538
         *
13539
         * @listens Player#durationchange
13540
         * @listens Player#timeupdate
13541
         * @listens Player#loadedmetadata
13542
         */
13543
        updateContent(event) {
13544
            const duration = this.player_.duration();
13545
            this.updateTextNode_(duration);
13546
        }
13547
    }
13548
 
13549
    /**
13550
     * The text that is added to the `DurationDisplay` for screen reader users.
13551
     *
13552
     * @type {string}
13553
     * @private
13554
     */
13555
    DurationDisplay.prototype.labelText_ = 'Duration';
13556
 
13557
    /**
13558
     * The text that should display over the `DurationDisplay`s controls. Added to for localization.
13559
     *
13560
     * @type {string}
13561
     * @protected
13562
     *
13563
     * @deprecated in v7; controlText_ is not used in non-active display Components
13564
     */
13565
    DurationDisplay.prototype.controlText_ = 'Duration';
13566
    Component$1.registerComponent('DurationDisplay', DurationDisplay);
13567
 
13568
    /**
13569
     * @file time-divider.js
13570
     */
13571
 
13572
    /**
13573
     * The separator between the current time and duration.
13574
     * Can be hidden if it's not needed in the design.
13575
     *
13576
     * @extends Component
13577
     */
13578
    class TimeDivider extends Component$1 {
13579
        /**
13580
         * Create the component's DOM element
13581
         *
13582
         * @return {Element}
13583
         *         The element that was created.
13584
         */
13585
        createEl() {
13586
            const el = super.createEl('div', {
13587
                className: 'vjs-time-control vjs-time-divider'
13588
            }, {
13589
                // this element and its contents can be hidden from assistive techs since
13590
                // it is made extraneous by the announcement of the control text
13591
                // for the current time and duration displays
13592
                'aria-hidden': true
13593
            });
13594
            const div = super.createEl('div');
13595
            const span = super.createEl('span', {
13596
                textContent: '/'
13597
            });
13598
            div.appendChild(span);
13599
            el.appendChild(div);
13600
            return el;
13601
        }
13602
    }
13603
    Component$1.registerComponent('TimeDivider', TimeDivider);
13604
 
13605
    /**
13606
     * @file remaining-time-display.js
13607
     */
13608
 
13609
    /**
13610
     * Displays the time left in the video
13611
     *
13612
     * @extends Component
13613
     */
13614
    class RemainingTimeDisplay extends TimeDisplay {
13615
        /**
13616
         * Creates an instance of this class.
13617
         *
13618
         * @param { import('../../player').default } player
13619
         *        The `Player` that this class should be attached to.
13620
         *
13621
         * @param {Object} [options]
13622
         *        The key/value store of player options.
13623
         */
13624
        constructor(player, options) {
13625
            super(player, options);
13626
            this.on(player, 'durationchange', e => this.updateContent(e));
13627
        }
13628
 
13629
        /**
13630
         * Builds the default DOM `className`.
13631
         *
13632
         * @return {string}
13633
         *         The DOM `className` for this object.
13634
         */
13635
        buildCSSClass() {
13636
            return 'vjs-remaining-time';
13637
        }
13638
 
13639
        /**
13640
         * Create the `Component`'s DOM element with the "minus" character prepend to the time
13641
         *
13642
         * @return {Element}
13643
         *         The element that was created.
13644
         */
13645
        createEl() {
13646
            const el = super.createEl();
13647
            if (this.options_.displayNegative !== false) {
13648
                el.insertBefore(createEl('span', {}, {
13649
                    'aria-hidden': true
13650
                }, '-'), this.contentEl_);
13651
            }
13652
            return el;
13653
        }
13654
 
13655
        /**
13656
         * Update remaining time display.
13657
         *
13658
         * @param {Event} [event]
13659
         *        The `timeupdate` or `durationchange` event that caused this to run.
13660
         *
13661
         * @listens Player#timeupdate
13662
         * @listens Player#durationchange
13663
         */
13664
        updateContent(event) {
13665
            if (typeof this.player_.duration() !== 'number') {
13666
                return;
13667
            }
13668
            let time;
13669
 
13670
            // @deprecated We should only use remainingTimeDisplay
13671
            // as of video.js 7
13672
            if (this.player_.ended()) {
13673
                time = 0;
13674
            } else if (this.player_.remainingTimeDisplay) {
13675
                time = this.player_.remainingTimeDisplay();
13676
            } else {
13677
                time = this.player_.remainingTime();
13678
            }
13679
            this.updateTextNode_(time);
13680
        }
13681
    }
13682
 
13683
    /**
13684
     * The text that is added to the `RemainingTimeDisplay` for screen reader users.
13685
     *
13686
     * @type {string}
13687
     * @private
13688
     */
13689
    RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
13690
 
13691
    /**
13692
     * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
13693
     *
13694
     * @type {string}
13695
     * @protected
13696
     *
13697
     * @deprecated in v7; controlText_ is not used in non-active display Components
13698
     */
13699
    RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
13700
    Component$1.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
13701
 
13702
    /**
13703
     * @file live-display.js
13704
     */
13705
 
13706
    // TODO - Future make it click to snap to live
13707
 
13708
    /**
13709
     * Displays the live indicator when duration is Infinity.
13710
     *
13711
     * @extends Component
13712
     */
13713
    class LiveDisplay extends Component$1 {
13714
        /**
13715
         * Creates an instance of this class.
13716
         *
13717
         * @param { import('./player').default } player
13718
         *        The `Player` that this class should be attached to.
13719
         *
13720
         * @param {Object} [options]
13721
         *        The key/value store of player options.
13722
         */
13723
        constructor(player, options) {
13724
            super(player, options);
13725
            this.updateShowing();
13726
            this.on(this.player(), 'durationchange', e => this.updateShowing(e));
13727
        }
13728
 
13729
        /**
13730
         * Create the `Component`'s DOM element
13731
         *
13732
         * @return {Element}
13733
         *         The element that was created.
13734
         */
13735
        createEl() {
13736
            const el = super.createEl('div', {
13737
                className: 'vjs-live-control vjs-control'
13738
            });
13739
            this.contentEl_ = createEl('div', {
13740
                className: 'vjs-live-display'
13741
            }, {
13742
                'aria-live': 'off'
13743
            });
13744
            this.contentEl_.appendChild(createEl('span', {
13745
                className: 'vjs-control-text',
13746
                textContent: `${this.localize('Stream Type')}\u00a0`
13747
            }));
13748
            this.contentEl_.appendChild(document.createTextNode(this.localize('LIVE')));
13749
            el.appendChild(this.contentEl_);
13750
            return el;
13751
        }
13752
        dispose() {
13753
            this.contentEl_ = null;
13754
            super.dispose();
13755
        }
13756
 
13757
        /**
13758
         * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
13759
         * it accordingly
13760
         *
13761
         * @param {Event} [event]
13762
         *        The {@link Player#durationchange} event that caused this function to run.
13763
         *
13764
         * @listens Player#durationchange
13765
         */
13766
        updateShowing(event) {
13767
            if (this.player().duration() === Infinity) {
13768
                this.show();
13769
            } else {
13770
                this.hide();
13771
            }
13772
        }
13773
    }
13774
    Component$1.registerComponent('LiveDisplay', LiveDisplay);
13775
 
13776
    /**
13777
     * @file seek-to-live.js
13778
     */
13779
 
13780
    /**
13781
     * Displays the live indicator when duration is Infinity.
13782
     *
13783
     * @extends Component
13784
     */
13785
    class SeekToLive extends Button {
13786
        /**
13787
         * Creates an instance of this class.
13788
         *
13789
         * @param { import('./player').default } player
13790
         *        The `Player` that this class should be attached to.
13791
         *
13792
         * @param {Object} [options]
13793
         *        The key/value store of player options.
13794
         */
13795
        constructor(player, options) {
13796
            super(player, options);
13797
            this.updateLiveEdgeStatus();
13798
            if (this.player_.liveTracker) {
13799
                this.updateLiveEdgeStatusHandler_ = e => this.updateLiveEdgeStatus(e);
13800
                this.on(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatusHandler_);
13801
            }
13802
        }
13803
 
13804
        /**
13805
         * Create the `Component`'s DOM element
13806
         *
13807
         * @return {Element}
13808
         *         The element that was created.
13809
         */
13810
        createEl() {
13811
            const el = super.createEl('button', {
13812
                className: 'vjs-seek-to-live-control vjs-control'
13813
            });
13814
            this.setIcon('circle', el);
13815
            this.textEl_ = createEl('span', {
13816
                className: 'vjs-seek-to-live-text',
13817
                textContent: this.localize('LIVE')
13818
            }, {
13819
                'aria-hidden': 'true'
13820
            });
13821
            el.appendChild(this.textEl_);
13822
            return el;
13823
        }
13824
 
13825
        /**
13826
         * Update the state of this button if we are at the live edge
13827
         * or not
13828
         */
13829
        updateLiveEdgeStatus() {
13830
            // default to live edge
13831
            if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) {
13832
                this.setAttribute('aria-disabled', true);
13833
                this.addClass('vjs-at-live-edge');
13834
                this.controlText('Seek to live, currently playing live');
13835
            } else {
13836
                this.setAttribute('aria-disabled', false);
13837
                this.removeClass('vjs-at-live-edge');
13838
                this.controlText('Seek to live, currently behind live');
13839
            }
13840
        }
13841
 
13842
        /**
13843
         * On click bring us as near to the live point as possible.
13844
         * This requires that we wait for the next `live-seekable-change`
13845
         * event which will happen every segment length seconds.
13846
         */
13847
        handleClick() {
13848
            this.player_.liveTracker.seekToLiveEdge();
13849
        }
13850
 
13851
        /**
13852
         * Dispose of the element and stop tracking
13853
         */
13854
        dispose() {
13855
            if (this.player_.liveTracker) {
13856
                this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatusHandler_);
13857
            }
13858
            this.textEl_ = null;
13859
            super.dispose();
13860
        }
13861
    }
13862
    /**
13863
     * The text that should display over the `SeekToLive`s control. Added for localization.
13864
     *
13865
     * @type {string}
13866
     * @protected
13867
     */
13868
    SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live';
13869
    Component$1.registerComponent('SeekToLive', SeekToLive);
13870
 
13871
    /**
13872
     * @file num.js
13873
     * @module num
13874
     */
13875
 
13876
    /**
13877
     * Keep a number between a min and a max value
13878
     *
13879
     * @param {number} number
13880
     *        The number to clamp
13881
     *
13882
     * @param {number} min
13883
     *        The minimum value
13884
     * @param {number} max
13885
     *        The maximum value
13886
     *
13887
     * @return {number}
13888
     *         the clamped number
13889
     */
13890
    function clamp(number, min, max) {
13891
        number = Number(number);
13892
        return Math.min(max, Math.max(min, isNaN(number) ? min : number));
13893
    }
13894
 
13895
    var Num = /*#__PURE__*/Object.freeze({
13896
        __proto__: null,
13897
        clamp: clamp
13898
    });
13899
 
13900
    /**
13901
     * @file slider.js
13902
     */
13903
 
13904
    /**
13905
     * The base functionality for a slider. Can be vertical or horizontal.
13906
     * For instance the volume bar or the seek bar on a video is a slider.
13907
     *
13908
     * @extends Component
13909
     */
13910
    class Slider extends Component$1 {
13911
        /**
13912
         * Create an instance of this class
13913
         *
13914
         * @param { import('../player').default } player
13915
         *        The `Player` that this class should be attached to.
13916
         *
13917
         * @param {Object} [options]
13918
         *        The key/value store of player options.
13919
         */
13920
        constructor(player, options) {
13921
            super(player, options);
13922
            this.handleMouseDown_ = e => this.handleMouseDown(e);
13923
            this.handleMouseUp_ = e => this.handleMouseUp(e);
13924
            this.handleKeyDown_ = e => this.handleKeyDown(e);
13925
            this.handleClick_ = e => this.handleClick(e);
13926
            this.handleMouseMove_ = e => this.handleMouseMove(e);
13927
            this.update_ = e => this.update(e);
13928
 
13929
            // Set property names to bar to match with the child Slider class is looking for
13930
            this.bar = this.getChild(this.options_.barName);
13931
 
13932
            // Set a horizontal or vertical class on the slider depending on the slider type
13933
            this.vertical(!!this.options_.vertical);
13934
            this.enable();
13935
        }
13936
 
13937
        /**
13938
         * Are controls are currently enabled for this slider or not.
13939
         *
13940
         * @return {boolean}
13941
         *         true if controls are enabled, false otherwise
13942
         */
13943
        enabled() {
13944
            return this.enabled_;
13945
        }
13946
 
13947
        /**
13948
         * Enable controls for this slider if they are disabled
13949
         */
13950
        enable() {
13951
            if (this.enabled()) {
13952
                return;
13953
            }
13954
            this.on('mousedown', this.handleMouseDown_);
13955
            this.on('touchstart', this.handleMouseDown_);
13956
            this.on('keydown', this.handleKeyDown_);
13957
            this.on('click', this.handleClick_);
13958
 
13959
            // TODO: deprecated, controlsvisible does not seem to be fired
13960
            this.on(this.player_, 'controlsvisible', this.update);
13961
            if (this.playerEvent) {
13962
                this.on(this.player_, this.playerEvent, this.update);
13963
            }
13964
            this.removeClass('disabled');
13965
            this.setAttribute('tabindex', 0);
13966
            this.enabled_ = true;
13967
        }
13968
 
13969
        /**
13970
         * Disable controls for this slider if they are enabled
13971
         */
13972
        disable() {
13973
            if (!this.enabled()) {
13974
                return;
13975
            }
13976
            const doc = this.bar.el_.ownerDocument;
13977
            this.off('mousedown', this.handleMouseDown_);
13978
            this.off('touchstart', this.handleMouseDown_);
13979
            this.off('keydown', this.handleKeyDown_);
13980
            this.off('click', this.handleClick_);
13981
            this.off(this.player_, 'controlsvisible', this.update_);
13982
            this.off(doc, 'mousemove', this.handleMouseMove_);
13983
            this.off(doc, 'mouseup', this.handleMouseUp_);
13984
            this.off(doc, 'touchmove', this.handleMouseMove_);
13985
            this.off(doc, 'touchend', this.handleMouseUp_);
13986
            this.removeAttribute('tabindex');
13987
            this.addClass('disabled');
13988
            if (this.playerEvent) {
13989
                this.off(this.player_, this.playerEvent, this.update);
13990
            }
13991
            this.enabled_ = false;
13992
        }
13993
 
13994
        /**
13995
         * Create the `Slider`s DOM element.
13996
         *
13997
         * @param {string} type
13998
         *        Type of element to create.
13999
         *
14000
         * @param {Object} [props={}]
14001
         *        List of properties in Object form.
14002
         *
14003
         * @param {Object} [attributes={}]
14004
         *        list of attributes in Object form.
14005
         *
14006
         * @return {Element}
14007
         *         The element that gets created.
14008
         */
14009
        createEl(type, props = {}, attributes = {}) {
14010
            // Add the slider element class to all sub classes
14011
            props.className = props.className + ' vjs-slider';
14012
            props = Object.assign({
14013
                tabIndex: 0
14014
            }, props);
14015
            attributes = Object.assign({
14016
                'role': 'slider',
14017
                'aria-valuenow': 0,
14018
                'aria-valuemin': 0,
14019
                'aria-valuemax': 100
14020
            }, attributes);
14021
            return super.createEl(type, props, attributes);
14022
        }
14023
 
14024
        /**
14025
         * Handle `mousedown` or `touchstart` events on the `Slider`.
14026
         *
14027
         * @param {MouseEvent} event
14028
         *        `mousedown` or `touchstart` event that triggered this function
14029
         *
14030
         * @listens mousedown
14031
         * @listens touchstart
14032
         * @fires Slider#slideractive
14033
         */
14034
        handleMouseDown(event) {
14035
            const doc = this.bar.el_.ownerDocument;
14036
            if (event.type === 'mousedown') {
14037
                event.preventDefault();
14038
            }
14039
            // Do not call preventDefault() on touchstart in Chrome
14040
            // to avoid console warnings. Use a 'touch-action: none' style
14041
            // instead to prevent unintended scrolling.
14042
            // https://developers.google.com/web/updates/2017/01/scrolling-intervention
14043
            if (event.type === 'touchstart' && !IS_CHROME) {
14044
                event.preventDefault();
14045
            }
14046
            blockTextSelection();
14047
            this.addClass('vjs-sliding');
14048
            /**
14049
             * Triggered when the slider is in an active state
14050
             *
14051
             * @event Slider#slideractive
14052
             * @type {MouseEvent}
14053
             */
14054
            this.trigger('slideractive');
14055
            this.on(doc, 'mousemove', this.handleMouseMove_);
14056
            this.on(doc, 'mouseup', this.handleMouseUp_);
14057
            this.on(doc, 'touchmove', this.handleMouseMove_);
14058
            this.on(doc, 'touchend', this.handleMouseUp_);
14059
            this.handleMouseMove(event, true);
14060
        }
14061
 
14062
        /**
14063
         * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
14064
         * The `mousemove` and `touchmove` events will only only trigger this function during
14065
         * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
14066
         * {@link Slider#handleMouseUp}.
14067
         *
14068
         * @param {MouseEvent} event
14069
         *        `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
14070
         *        this function
14071
         * @param {boolean} mouseDown this is a flag that should be set to true if `handleMouseMove` is called directly. It allows us to skip things that should not happen if coming from mouse down but should happen on regular mouse move handler. Defaults to false.
14072
         *
14073
         * @listens mousemove
14074
         * @listens touchmove
14075
         */
14076
        handleMouseMove(event) {}
14077
 
14078
        /**
14079
         * Handle `mouseup` or `touchend` events on the `Slider`.
14080
         *
14081
         * @param {MouseEvent} event
14082
         *        `mouseup` or `touchend` event that triggered this function.
14083
         *
14084
         * @listens touchend
14085
         * @listens mouseup
14086
         * @fires Slider#sliderinactive
14087
         */
14088
        handleMouseUp(event) {
14089
            const doc = this.bar.el_.ownerDocument;
14090
            unblockTextSelection();
14091
            this.removeClass('vjs-sliding');
14092
            /**
14093
             * Triggered when the slider is no longer in an active state.
14094
             *
14095
             * @event Slider#sliderinactive
14096
             * @type {Event}
14097
             */
14098
            this.trigger('sliderinactive');
14099
            this.off(doc, 'mousemove', this.handleMouseMove_);
14100
            this.off(doc, 'mouseup', this.handleMouseUp_);
14101
            this.off(doc, 'touchmove', this.handleMouseMove_);
14102
            this.off(doc, 'touchend', this.handleMouseUp_);
14103
            this.update();
14104
        }
14105
 
14106
        /**
14107
         * Update the progress bar of the `Slider`.
14108
         *
14109
         * @return {number}
14110
         *          The percentage of progress the progress bar represents as a
14111
         *          number from 0 to 1.
14112
         */
14113
        update() {
14114
            // In VolumeBar init we have a setTimeout for update that pops and update
14115
            // to the end of the execution stack. The player is destroyed before then
14116
            // update will cause an error
14117
            // If there's no bar...
14118
            if (!this.el_ || !this.bar) {
14119
                return;
14120
            }
14121
 
14122
            // clamp progress between 0 and 1
14123
            // and only round to four decimal places, as we round to two below
14124
            const progress = this.getProgress();
14125
            if (progress === this.progress_) {
14126
                return progress;
14127
            }
14128
            this.progress_ = progress;
14129
            this.requestNamedAnimationFrame('Slider#update', () => {
14130
                // Set the new bar width or height
14131
                const sizeKey = this.vertical() ? 'height' : 'width';
14132
 
14133
                // Convert to a percentage for css value
14134
                this.bar.el().style[sizeKey] = (progress * 100).toFixed(2) + '%';
14135
            });
14136
            return progress;
14137
        }
14138
 
14139
        /**
14140
         * Get the percentage of the bar that should be filled
14141
         * but clamped and rounded.
14142
         *
14143
         * @return {number}
14144
         *         percentage filled that the slider is
14145
         */
14146
        getProgress() {
14147
            return Number(clamp(this.getPercent(), 0, 1).toFixed(4));
14148
        }
14149
 
14150
        /**
14151
         * Calculate distance for slider
14152
         *
14153
         * @param {Event} event
14154
         *        The event that caused this function to run.
14155
         *
14156
         * @return {number}
14157
         *         The current position of the Slider.
14158
         *         - position.x for vertical `Slider`s
14159
         *         - position.y for horizontal `Slider`s
14160
         */
14161
        calculateDistance(event) {
14162
            const position = getPointerPosition(this.el_, event);
14163
            if (this.vertical()) {
14164
                return position.y;
14165
            }
14166
            return position.x;
14167
        }
14168
 
14169
        /**
14170
         * Handle a `keydown` event on the `Slider`. Watches for left, right, up, and down
14171
         * arrow keys. This function will only be called when the slider has focus. See
14172
         * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
14173
         *
14174
         * @param {KeyboardEvent} event
14175
         *        the `keydown` event that caused this function to run.
14176
         *
14177
         * @listens keydown
14178
         */
14179
        handleKeyDown(event) {
14180
            // Left and Down Arrows
14181
            if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
14182
                event.preventDefault();
14183
                event.stopPropagation();
14184
                this.stepBack();
14185
 
14186
                // Up and Right Arrows
14187
            } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
14188
                event.preventDefault();
14189
                event.stopPropagation();
14190
                this.stepForward();
14191
            } else {
14192
                // Pass keydown handling up for unsupported keys
14193
                super.handleKeyDown(event);
14194
            }
14195
        }
14196
 
14197
        /**
14198
         * Listener for click events on slider, used to prevent clicks
14199
         *   from bubbling up to parent elements like button menus.
14200
         *
14201
         * @param {Object} event
14202
         *        Event that caused this object to run
14203
         */
14204
        handleClick(event) {
14205
            event.stopPropagation();
14206
            event.preventDefault();
14207
        }
14208
 
14209
        /**
14210
         * Get/set if slider is horizontal for vertical
14211
         *
14212
         * @param {boolean} [bool]
14213
         *        - true if slider is vertical,
14214
         *        - false is horizontal
14215
         *
14216
         * @return {boolean}
14217
         *         - true if slider is vertical, and getting
14218
         *         - false if the slider is horizontal, and getting
14219
         */
14220
        vertical(bool) {
14221
            if (bool === undefined) {
14222
                return this.vertical_ || false;
14223
            }
14224
            this.vertical_ = !!bool;
14225
            if (this.vertical_) {
14226
                this.addClass('vjs-slider-vertical');
14227
            } else {
14228
                this.addClass('vjs-slider-horizontal');
14229
            }
14230
        }
14231
    }
14232
    Component$1.registerComponent('Slider', Slider);
14233
 
14234
    /**
14235
     * @file load-progress-bar.js
14236
     */
14237
 
14238
        // get the percent width of a time compared to the total end
14239
    const percentify = (time, end) => clamp(time / end * 100, 0, 100).toFixed(2) + '%';
14240
 
14241
    /**
14242
     * Shows loading progress
14243
     *
14244
     * @extends Component
14245
     */
14246
    class LoadProgressBar extends Component$1 {
14247
        /**
14248
         * Creates an instance of this class.
14249
         *
14250
         * @param { import('../../player').default } player
14251
         *        The `Player` that this class should be attached to.
14252
         *
14253
         * @param {Object} [options]
14254
         *        The key/value store of player options.
14255
         */
14256
        constructor(player, options) {
14257
            super(player, options);
14258
            this.partEls_ = [];
14259
            this.on(player, 'progress', e => this.update(e));
14260
        }
14261
 
14262
        /**
14263
         * Create the `Component`'s DOM element
14264
         *
14265
         * @return {Element}
14266
         *         The element that was created.
14267
         */
14268
        createEl() {
14269
            const el = super.createEl('div', {
14270
                className: 'vjs-load-progress'
14271
            });
14272
            const wrapper = createEl('span', {
14273
                className: 'vjs-control-text'
14274
            });
14275
            const loadedText = createEl('span', {
14276
                textContent: this.localize('Loaded')
14277
            });
14278
            const separator = document.createTextNode(': ');
14279
            this.percentageEl_ = createEl('span', {
14280
                className: 'vjs-control-text-loaded-percentage',
14281
                textContent: '0%'
14282
            });
14283
            el.appendChild(wrapper);
14284
            wrapper.appendChild(loadedText);
14285
            wrapper.appendChild(separator);
14286
            wrapper.appendChild(this.percentageEl_);
14287
            return el;
14288
        }
14289
        dispose() {
14290
            this.partEls_ = null;
14291
            this.percentageEl_ = null;
14292
            super.dispose();
14293
        }
14294
 
14295
        /**
14296
         * Update progress bar
14297
         *
14298
         * @param {Event} [event]
14299
         *        The `progress` event that caused this function to run.
14300
         *
14301
         * @listens Player#progress
14302
         */
14303
        update(event) {
14304
            this.requestNamedAnimationFrame('LoadProgressBar#update', () => {
14305
                const liveTracker = this.player_.liveTracker;
14306
                const buffered = this.player_.buffered();
14307
                const duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
14308
                const bufferedEnd = this.player_.bufferedEnd();
14309
                const children = this.partEls_;
14310
                const percent = percentify(bufferedEnd, duration);
14311
                if (this.percent_ !== percent) {
14312
                    // update the width of the progress bar
14313
                    this.el_.style.width = percent;
14314
                    // update the control-text
14315
                    textContent(this.percentageEl_, percent);
14316
                    this.percent_ = percent;
14317
                }
14318
 
14319
                // add child elements to represent the individual buffered time ranges
14320
                for (let i = 0; i < buffered.length; i++) {
14321
                    const start = buffered.start(i);
14322
                    const end = buffered.end(i);
14323
                    let part = children[i];
14324
                    if (!part) {
14325
                        part = this.el_.appendChild(createEl());
14326
                        children[i] = part;
14327
                    }
14328
 
14329
                    //  only update if changed
14330
                    if (part.dataset.start === start && part.dataset.end === end) {
14331
                        continue;
14332
                    }
14333
                    part.dataset.start = start;
14334
                    part.dataset.end = end;
14335
 
14336
                    // set the percent based on the width of the progress bar (bufferedEnd)
14337
                    part.style.left = percentify(start, bufferedEnd);
14338
                    part.style.width = percentify(end - start, bufferedEnd);
14339
                }
14340
 
14341
                // remove unused buffered range elements
14342
                for (let i = children.length; i > buffered.length; i--) {
14343
                    this.el_.removeChild(children[i - 1]);
14344
                }
14345
                children.length = buffered.length;
14346
            });
14347
        }
14348
    }
14349
    Component$1.registerComponent('LoadProgressBar', LoadProgressBar);
14350
 
14351
    /**
14352
     * @file time-tooltip.js
14353
     */
14354
 
14355
    /**
14356
     * Time tooltips display a time above the progress bar.
14357
     *
14358
     * @extends Component
14359
     */
14360
    class TimeTooltip extends Component$1 {
14361
        /**
14362
         * Creates an instance of this class.
14363
         *
14364
         * @param { import('../../player').default } player
14365
         *        The {@link Player} that this class should be attached to.
14366
         *
14367
         * @param {Object} [options]
14368
         *        The key/value store of player options.
14369
         */
14370
        constructor(player, options) {
14371
            super(player, options);
14372
            this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
14373
        }
14374
 
14375
        /**
14376
         * Create the time tooltip DOM element
14377
         *
14378
         * @return {Element}
14379
         *         The element that was created.
14380
         */
14381
        createEl() {
14382
            return super.createEl('div', {
14383
                className: 'vjs-time-tooltip'
14384
            }, {
14385
                'aria-hidden': 'true'
14386
            });
14387
        }
14388
 
14389
        /**
14390
         * Updates the position of the time tooltip relative to the `SeekBar`.
14391
         *
14392
         * @param {Object} seekBarRect
14393
         *        The `ClientRect` for the {@link SeekBar} element.
14394
         *
14395
         * @param {number} seekBarPoint
14396
         *        A number from 0 to 1, representing a horizontal reference point
14397
         *        from the left edge of the {@link SeekBar}
14398
         */
14399
        update(seekBarRect, seekBarPoint, content) {
14400
            const tooltipRect = findPosition(this.el_);
14401
            const playerRect = getBoundingClientRect(this.player_.el());
14402
            const seekBarPointPx = seekBarRect.width * seekBarPoint;
14403
 
14404
            // do nothing if either rect isn't available
14405
            // for example, if the player isn't in the DOM for testing
14406
            if (!playerRect || !tooltipRect) {
14407
                return;
14408
            }
14409
 
14410
            // This is the space left of the `seekBarPoint` available within the bounds
14411
            // of the player. We calculate any gap between the left edge of the player
14412
            // and the left edge of the `SeekBar` and add the number of pixels in the
14413
            // `SeekBar` before hitting the `seekBarPoint`
14414
            const spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx;
14415
 
14416
            // This is the space right of the `seekBarPoint` available within the bounds
14417
            // of the player. We calculate the number of pixels from the `seekBarPoint`
14418
            // to the right edge of the `SeekBar` and add to that any gap between the
14419
            // right edge of the `SeekBar` and the player.
14420
            const spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right);
14421
 
14422
            // This is the number of pixels by which the tooltip will need to be pulled
14423
            // further to the right to center it over the `seekBarPoint`.
14424
            let pullTooltipBy = tooltipRect.width / 2;
14425
 
14426
            // Adjust the `pullTooltipBy` distance to the left or right depending on
14427
            // the results of the space calculations above.
14428
            if (spaceLeftOfPoint < pullTooltipBy) {
14429
                pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
14430
            } else if (spaceRightOfPoint < pullTooltipBy) {
14431
                pullTooltipBy = spaceRightOfPoint;
14432
            }
14433
 
14434
            // Due to the imprecision of decimal/ratio based calculations and varying
14435
            // rounding behaviors, there are cases where the spacing adjustment is off
14436
            // by a pixel or two. This adds insurance to these calculations.
14437
            if (pullTooltipBy < 0) {
14438
                pullTooltipBy = 0;
14439
            } else if (pullTooltipBy > tooltipRect.width) {
14440
                pullTooltipBy = tooltipRect.width;
14441
            }
14442
 
14443
            // prevent small width fluctuations within 0.4px from
14444
            // changing the value below.
14445
            // This really helps for live to prevent the play
14446
            // progress time tooltip from jittering
14447
            pullTooltipBy = Math.round(pullTooltipBy);
14448
            this.el_.style.right = `-${pullTooltipBy}px`;
14449
            this.write(content);
14450
        }
14451
 
14452
        /**
14453
         * Write the time to the tooltip DOM element.
14454
         *
14455
         * @param {string} content
14456
         *        The formatted time for the tooltip.
14457
         */
14458
        write(content) {
14459
            textContent(this.el_, content);
14460
        }
14461
 
14462
        /**
14463
         * Updates the position of the time tooltip relative to the `SeekBar`.
14464
         *
14465
         * @param {Object} seekBarRect
14466
         *        The `ClientRect` for the {@link SeekBar} element.
14467
         *
14468
         * @param {number} seekBarPoint
14469
         *        A number from 0 to 1, representing a horizontal reference point
14470
         *        from the left edge of the {@link SeekBar}
14471
         *
14472
         * @param {number} time
14473
         *        The time to update the tooltip to, not used during live playback
14474
         *
14475
         * @param {Function} cb
14476
         *        A function that will be called during the request animation frame
14477
         *        for tooltips that need to do additional animations from the default
14478
         */
14479
        updateTime(seekBarRect, seekBarPoint, time, cb) {
14480
            this.requestNamedAnimationFrame('TimeTooltip#updateTime', () => {
14481
                let content;
14482
                const duration = this.player_.duration();
14483
                if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
14484
                    const liveWindow = this.player_.liveTracker.liveWindow();
14485
                    const secondsBehind = liveWindow - seekBarPoint * liveWindow;
14486
                    content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
14487
                } else {
14488
                    content = formatTime(time, duration);
14489
                }
14490
                this.update(seekBarRect, seekBarPoint, content);
14491
                if (cb) {
14492
                    cb();
14493
                }
14494
            });
14495
        }
14496
    }
14497
    Component$1.registerComponent('TimeTooltip', TimeTooltip);
14498
 
14499
    /**
14500
     * @file play-progress-bar.js
14501
     */
14502
 
14503
    /**
14504
     * Used by {@link SeekBar} to display media playback progress as part of the
14505
     * {@link ProgressControl}.
14506
     *
14507
     * @extends Component
14508
     */
14509
    class PlayProgressBar extends Component$1 {
14510
        /**
14511
         * Creates an instance of this class.
14512
         *
14513
         * @param { import('../../player').default } player
14514
         *        The {@link Player} that this class should be attached to.
14515
         *
14516
         * @param {Object} [options]
14517
         *        The key/value store of player options.
14518
         */
14519
        constructor(player, options) {
14520
            super(player, options);
14521
            this.setIcon('circle');
14522
            this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
14523
        }
14524
 
14525
        /**
14526
         * Create the the DOM element for this class.
14527
         *
14528
         * @return {Element}
14529
         *         The element that was created.
14530
         */
14531
        createEl() {
14532
            return super.createEl('div', {
14533
                className: 'vjs-play-progress vjs-slider-bar'
14534
            }, {
14535
                'aria-hidden': 'true'
14536
            });
14537
        }
14538
 
14539
        /**
14540
         * Enqueues updates to its own DOM as well as the DOM of its
14541
         * {@link TimeTooltip} child.
14542
         *
14543
         * @param {Object} seekBarRect
14544
         *        The `ClientRect` for the {@link SeekBar} element.
14545
         *
14546
         * @param {number} seekBarPoint
14547
         *        A number from 0 to 1, representing a horizontal reference point
14548
         *        from the left edge of the {@link SeekBar}
14549
         */
14550
        update(seekBarRect, seekBarPoint) {
14551
            const timeTooltip = this.getChild('timeTooltip');
14552
            if (!timeTooltip) {
14553
                return;
14554
            }
14555
            const time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
14556
            timeTooltip.updateTime(seekBarRect, seekBarPoint, time);
14557
        }
14558
    }
14559
 
14560
    /**
14561
     * Default options for {@link PlayProgressBar}.
14562
     *
14563
     * @type {Object}
14564
     * @private
14565
     */
14566
    PlayProgressBar.prototype.options_ = {
14567
        children: []
14568
    };
14569
 
14570
    // Time tooltips should not be added to a player on mobile devices
14571
    if (!IS_IOS && !IS_ANDROID) {
14572
        PlayProgressBar.prototype.options_.children.push('timeTooltip');
14573
    }
14574
    Component$1.registerComponent('PlayProgressBar', PlayProgressBar);
14575
 
14576
    /**
14577
     * @file mouse-time-display.js
14578
     */
14579
 
14580
    /**
14581
     * The {@link MouseTimeDisplay} component tracks mouse movement over the
14582
     * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
14583
     * indicating the time which is represented by a given point in the
14584
     * {@link ProgressControl}.
14585
     *
14586
     * @extends Component
14587
     */
14588
    class MouseTimeDisplay extends Component$1 {
14589
        /**
14590
         * Creates an instance of this class.
14591
         *
14592
         * @param { import('../../player').default } player
14593
         *        The {@link Player} that this class should be attached to.
14594
         *
14595
         * @param {Object} [options]
14596
         *        The key/value store of player options.
14597
         */
14598
        constructor(player, options) {
14599
            super(player, options);
14600
            this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
14601
        }
14602
 
14603
        /**
14604
         * Create the DOM element for this class.
14605
         *
14606
         * @return {Element}
14607
         *         The element that was created.
14608
         */
14609
        createEl() {
14610
            return super.createEl('div', {
14611
                className: 'vjs-mouse-display'
14612
            });
14613
        }
14614
 
14615
        /**
14616
         * Enqueues updates to its own DOM as well as the DOM of its
14617
         * {@link TimeTooltip} child.
14618
         *
14619
         * @param {Object} seekBarRect
14620
         *        The `ClientRect` for the {@link SeekBar} element.
14621
         *
14622
         * @param {number} seekBarPoint
14623
         *        A number from 0 to 1, representing a horizontal reference point
14624
         *        from the left edge of the {@link SeekBar}
14625
         */
14626
        update(seekBarRect, seekBarPoint) {
14627
            const time = seekBarPoint * this.player_.duration();
14628
            this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, () => {
14629
                this.el_.style.left = `${seekBarRect.width * seekBarPoint}px`;
14630
            });
14631
        }
14632
    }
14633
 
14634
    /**
14635
     * Default options for `MouseTimeDisplay`
14636
     *
14637
     * @type {Object}
14638
     * @private
14639
     */
14640
    MouseTimeDisplay.prototype.options_ = {
14641
        children: ['timeTooltip']
14642
    };
14643
    Component$1.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
14644
 
14645
    /**
14646
     * @file seek-bar.js
14647
     */
14648
 
14649
        // The number of seconds the `step*` functions move the timeline.
14650
    const STEP_SECONDS = 5;
14651
 
14652
    // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline.
14653
    const PAGE_KEY_MULTIPLIER = 12;
14654
 
14655
    /**
14656
     * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
14657
     * as its `bar`.
14658
     *
14659
     * @extends Slider
14660
     */
14661
    class SeekBar extends Slider {
14662
        /**
14663
         * Creates an instance of this class.
14664
         *
14665
         * @param { import('../../player').default } player
14666
         *        The `Player` that this class should be attached to.
14667
         *
14668
         * @param {Object} [options]
14669
         *        The key/value store of player options.
14670
         */
14671
        constructor(player, options) {
14672
            super(player, options);
14673
            this.setEventHandlers_();
14674
        }
14675
 
14676
        /**
14677
         * Sets the event handlers
14678
         *
14679
         * @private
14680
         */
14681
        setEventHandlers_() {
14682
            this.update_ = bind_(this, this.update);
14683
            this.update = throttle(this.update_, UPDATE_REFRESH_INTERVAL);
14684
            this.on(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update);
14685
            if (this.player_.liveTracker) {
14686
                this.on(this.player_.liveTracker, 'liveedgechange', this.update);
14687
            }
14688
 
14689
            // when playing, let's ensure we smoothly update the play progress bar
14690
            // via an interval
14691
            this.updateInterval = null;
14692
            this.enableIntervalHandler_ = e => this.enableInterval_(e);
14693
            this.disableIntervalHandler_ = e => this.disableInterval_(e);
14694
            this.on(this.player_, ['playing'], this.enableIntervalHandler_);
14695
            this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_);
14696
 
14697
            // we don't need to update the play progress if the document is hidden,
14698
            // also, this causes the CPU to spike and eventually crash the page on IE11.
14699
            if ('hidden' in document && 'visibilityState' in document) {
14700
                this.on(document, 'visibilitychange', this.toggleVisibility_);
14701
            }
14702
        }
14703
        toggleVisibility_(e) {
14704
            if (document.visibilityState === 'hidden') {
14705
                this.cancelNamedAnimationFrame('SeekBar#update');
14706
                this.cancelNamedAnimationFrame('Slider#update');
14707
                this.disableInterval_(e);
14708
            } else {
14709
                if (!this.player_.ended() && !this.player_.paused()) {
14710
                    this.enableInterval_();
14711
                }
14712
 
14713
                // we just switched back to the page and someone may be looking, so, update ASAP
14714
                this.update();
14715
            }
14716
        }
14717
        enableInterval_() {
14718
            if (this.updateInterval) {
14719
                return;
14720
            }
14721
            this.updateInterval = this.setInterval(this.update, UPDATE_REFRESH_INTERVAL);
14722
        }
14723
        disableInterval_(e) {
14724
            if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e && e.type !== 'ended') {
14725
                return;
14726
            }
14727
            if (!this.updateInterval) {
14728
                return;
14729
            }
14730
            this.clearInterval(this.updateInterval);
14731
            this.updateInterval = null;
14732
        }
14733
 
14734
        /**
14735
         * Create the `Component`'s DOM element
14736
         *
14737
         * @return {Element}
14738
         *         The element that was created.
14739
         */
14740
        createEl() {
14741
            return super.createEl('div', {
14742
                className: 'vjs-progress-holder'
14743
            }, {
14744
                'aria-label': this.localize('Progress Bar')
14745
            });
14746
        }
14747
 
14748
        /**
14749
         * This function updates the play progress bar and accessibility
14750
         * attributes to whatever is passed in.
14751
         *
14752
         * @param {Event} [event]
14753
         *        The `timeupdate` or `ended` event that caused this to run.
14754
         *
14755
         * @listens Player#timeupdate
14756
         *
14757
         * @return {number}
14758
         *          The current percent at a number from 0-1
14759
         */
14760
        update(event) {
14761
            // ignore updates while the tab is hidden
14762
            if (document.visibilityState === 'hidden') {
14763
                return;
14764
            }
14765
            const percent = super.update();
14766
            this.requestNamedAnimationFrame('SeekBar#update', () => {
14767
                const currentTime = this.player_.ended() ? this.player_.duration() : this.getCurrentTime_();
14768
                const liveTracker = this.player_.liveTracker;
14769
                let duration = this.player_.duration();
14770
                if (liveTracker && liveTracker.isLive()) {
14771
                    duration = this.player_.liveTracker.liveCurrentTime();
14772
                }
14773
                if (this.percent_ !== percent) {
14774
                    // machine readable value of progress bar (percentage complete)
14775
                    this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2));
14776
                    this.percent_ = percent;
14777
                }
14778
                if (this.currentTime_ !== currentTime || this.duration_ !== duration) {
14779
                    // human readable value of progress bar (time complete)
14780
                    this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}'));
14781
                    this.currentTime_ = currentTime;
14782
                    this.duration_ = duration;
14783
                }
14784
 
14785
                // update the progress bar time tooltip with the current time
14786
                if (this.bar) {
14787
                    this.bar.update(getBoundingClientRect(this.el()), this.getProgress());
14788
                }
14789
            });
14790
            return percent;
14791
        }
14792
 
14793
        /**
14794
         * Prevent liveThreshold from causing seeks to seem like they
14795
         * are not happening from a user perspective.
14796
         *
14797
         * @param {number} ct
14798
         *        current time to seek to
14799
         */
14800
        userSeek_(ct) {
14801
            if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
14802
                this.player_.liveTracker.nextSeekedFromUser();
14803
            }
14804
            this.player_.currentTime(ct);
14805
        }
14806
 
14807
        /**
14808
         * Get the value of current time but allows for smooth scrubbing,
14809
         * when player can't keep up.
14810
         *
14811
         * @return {number}
14812
         *         The current time value to display
14813
         *
14814
         * @private
14815
         */
14816
        getCurrentTime_() {
14817
            return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
14818
        }
14819
 
14820
        /**
14821
         * Get the percentage of media played so far.
14822
         *
14823
         * @return {number}
14824
         *         The percentage of media played so far (0 to 1).
14825
         */
14826
        getPercent() {
14827
            const currentTime = this.getCurrentTime_();
14828
            let percent;
14829
            const liveTracker = this.player_.liveTracker;
14830
            if (liveTracker && liveTracker.isLive()) {
14831
                percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow();
14832
 
14833
                // prevent the percent from changing at the live edge
14834
                if (liveTracker.atLiveEdge()) {
14835
                    percent = 1;
14836
                }
14837
            } else {
14838
                percent = currentTime / this.player_.duration();
14839
            }
14840
            return percent;
14841
        }
14842
 
14843
        /**
14844
         * Handle mouse down on seek bar
14845
         *
14846
         * @param {MouseEvent} event
14847
         *        The `mousedown` event that caused this to run.
14848
         *
14849
         * @listens mousedown
14850
         */
14851
        handleMouseDown(event) {
14852
            if (!isSingleLeftClick(event)) {
14853
                return;
14854
            }
14855
 
14856
            // Stop event propagation to prevent double fire in progress-control.js
14857
            event.stopPropagation();
14858
            this.videoWasPlaying = !this.player_.paused();
14859
            this.player_.pause();
14860
            super.handleMouseDown(event);
14861
        }
14862
 
14863
        /**
14864
         * Handle mouse move on seek bar
14865
         *
14866
         * @param {MouseEvent} event
14867
         *        The `mousemove` event that caused this to run.
14868
         * @param {boolean} mouseDown this is a flag that should be set to true if `handleMouseMove` is called directly. It allows us to skip things that should not happen if coming from mouse down but should happen on regular mouse move handler. Defaults to false
14869
         *
14870
         * @listens mousemove
14871
         */
14872
        handleMouseMove(event, mouseDown = false) {
14873
            if (!isSingleLeftClick(event) || isNaN(this.player_.duration())) {
14874
                return;
14875
            }
14876
            if (!mouseDown && !this.player_.scrubbing()) {
14877
                this.player_.scrubbing(true);
14878
            }
14879
            let newTime;
14880
            const distance = this.calculateDistance(event);
14881
            const liveTracker = this.player_.liveTracker;
14882
            if (!liveTracker || !liveTracker.isLive()) {
14883
                newTime = distance * this.player_.duration();
14884
 
14885
                // Don't let video end while scrubbing.
14886
                if (newTime === this.player_.duration()) {
14887
                    newTime = newTime - 0.1;
14888
                }
14889
            } else {
14890
                if (distance >= 0.99) {
14891
                    liveTracker.seekToLiveEdge();
14892
                    return;
14893
                }
14894
                const seekableStart = liveTracker.seekableStart();
14895
                const seekableEnd = liveTracker.liveCurrentTime();
14896
                newTime = seekableStart + distance * liveTracker.liveWindow();
14897
 
14898
                // Don't let video end while scrubbing.
14899
                if (newTime >= seekableEnd) {
14900
                    newTime = seekableEnd;
14901
                }
14902
 
14903
                // Compensate for precision differences so that currentTime is not less
14904
                // than seekable start
14905
                if (newTime <= seekableStart) {
14906
                    newTime = seekableStart + 0.1;
14907
                }
14908
 
14909
                // On android seekableEnd can be Infinity sometimes,
14910
                // this will cause newTime to be Infinity, which is
14911
                // not a valid currentTime.
14912
                if (newTime === Infinity) {
14913
                    return;
14914
                }
14915
            }
14916
 
14917
            // Set new time (tell player to seek to new time)
14918
            this.userSeek_(newTime);
14919
            if (this.player_.options_.enableSmoothSeeking) {
14920
                this.update();
14921
            }
14922
        }
14923
        enable() {
14924
            super.enable();
14925
            const mouseTimeDisplay = this.getChild('mouseTimeDisplay');
14926
            if (!mouseTimeDisplay) {
14927
                return;
14928
            }
14929
            mouseTimeDisplay.show();
14930
        }
14931
        disable() {
14932
            super.disable();
14933
            const mouseTimeDisplay = this.getChild('mouseTimeDisplay');
14934
            if (!mouseTimeDisplay) {
14935
                return;
14936
            }
14937
            mouseTimeDisplay.hide();
14938
        }
14939
 
14940
        /**
14941
         * Handle mouse up on seek bar
14942
         *
14943
         * @param {MouseEvent} event
14944
         *        The `mouseup` event that caused this to run.
14945
         *
14946
         * @listens mouseup
14947
         */
14948
        handleMouseUp(event) {
14949
            super.handleMouseUp(event);
14950
 
14951
            // Stop event propagation to prevent double fire in progress-control.js
14952
            if (event) {
14953
                event.stopPropagation();
14954
            }
14955
            this.player_.scrubbing(false);
14956
 
14957
            /**
14958
             * Trigger timeupdate because we're done seeking and the time has changed.
14959
             * This is particularly useful for if the player is paused to time the time displays.
14960
             *
14961
             * @event Tech#timeupdate
14962
             * @type {Event}
14963
             */
14964
            this.player_.trigger({
14965
                type: 'timeupdate',
14966
                target: this,
14967
                manuallyTriggered: true
14968
            });
14969
            if (this.videoWasPlaying) {
14970
                silencePromise(this.player_.play());
14971
            } else {
14972
                // We're done seeking and the time has changed.
14973
                // If the player is paused, make sure we display the correct time on the seek bar.
14974
                this.update_();
14975
            }
14976
        }
14977
 
14978
        /**
14979
         * Move more quickly fast forward for keyboard-only users
14980
         */
14981
        stepForward() {
14982
            this.userSeek_(this.player_.currentTime() + STEP_SECONDS);
14983
        }
14984
 
14985
        /**
14986
         * Move more quickly rewind for keyboard-only users
14987
         */
14988
        stepBack() {
14989
            this.userSeek_(this.player_.currentTime() - STEP_SECONDS);
14990
        }
14991
 
14992
        /**
14993
         * Toggles the playback state of the player
14994
         * This gets called when enter or space is used on the seekbar
14995
         *
14996
         * @param {KeyboardEvent} event
14997
         *        The `keydown` event that caused this function to be called
14998
         *
14999
         */
15000
        handleAction(event) {
15001
            if (this.player_.paused()) {
15002
                this.player_.play();
15003
            } else {
15004
                this.player_.pause();
15005
            }
15006
        }
15007
 
15008
        /**
15009
         * Called when this SeekBar has focus and a key gets pressed down.
15010
         * Supports the following keys:
15011
         *
15012
         *   Space or Enter key fire a click event
15013
         *   Home key moves to start of the timeline
15014
         *   End key moves to end of the timeline
15015
         *   Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline
15016
         *   PageDown key moves back a larger step than ArrowDown
15017
         *   PageUp key moves forward a large step
15018
         *
15019
         * @param {KeyboardEvent} event
15020
         *        The `keydown` event that caused this function to be called.
15021
         *
15022
         * @listens keydown
15023
         */
15024
        handleKeyDown(event) {
15025
            const liveTracker = this.player_.liveTracker;
15026
            if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
15027
                event.preventDefault();
15028
                event.stopPropagation();
15029
                this.handleAction(event);
15030
            } else if (keycode.isEventKey(event, 'Home')) {
15031
                event.preventDefault();
15032
                event.stopPropagation();
15033
                this.userSeek_(0);
15034
            } else if (keycode.isEventKey(event, 'End')) {
15035
                event.preventDefault();
15036
                event.stopPropagation();
15037
                if (liveTracker && liveTracker.isLive()) {
15038
                    this.userSeek_(liveTracker.liveCurrentTime());
15039
                } else {
15040
                    this.userSeek_(this.player_.duration());
15041
                }
15042
            } else if (/^[0-9]$/.test(keycode(event))) {
15043
                event.preventDefault();
15044
                event.stopPropagation();
15045
                const gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0;
15046
                if (liveTracker && liveTracker.isLive()) {
15047
                    this.userSeek_(liveTracker.seekableStart() + liveTracker.liveWindow() * gotoFraction);
15048
                } else {
15049
                    this.userSeek_(this.player_.duration() * gotoFraction);
15050
                }
15051
            } else if (keycode.isEventKey(event, 'PgDn')) {
15052
                event.preventDefault();
15053
                event.stopPropagation();
15054
                this.userSeek_(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER);
15055
            } else if (keycode.isEventKey(event, 'PgUp')) {
15056
                event.preventDefault();
15057
                event.stopPropagation();
15058
                this.userSeek_(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER);
15059
            } else {
15060
                // Pass keydown handling up for unsupported keys
15061
                super.handleKeyDown(event);
15062
            }
15063
        }
15064
        dispose() {
15065
            this.disableInterval_();
15066
            this.off(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update);
15067
            if (this.player_.liveTracker) {
15068
                this.off(this.player_.liveTracker, 'liveedgechange', this.update);
15069
            }
15070
            this.off(this.player_, ['playing'], this.enableIntervalHandler_);
15071
            this.off(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_);
15072
 
15073
            // we don't need to update the play progress if the document is hidden,
15074
            // also, this causes the CPU to spike and eventually crash the page on IE11.
15075
            if ('hidden' in document && 'visibilityState' in document) {
15076
                this.off(document, 'visibilitychange', this.toggleVisibility_);
15077
            }
15078
            super.dispose();
15079
        }
15080
    }
15081
 
15082
    /**
15083
     * Default options for the `SeekBar`
15084
     *
15085
     * @type {Object}
15086
     * @private
15087
     */
15088
    SeekBar.prototype.options_ = {
15089
        children: ['loadProgressBar', 'playProgressBar'],
15090
        barName: 'playProgressBar'
15091
    };
15092
 
15093
    // MouseTimeDisplay tooltips should not be added to a player on mobile devices
15094
    if (!IS_IOS && !IS_ANDROID) {
15095
        SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
15096
    }
15097
    Component$1.registerComponent('SeekBar', SeekBar);
15098
 
15099
    /**
15100
     * @file progress-control.js
15101
     */
15102
 
15103
    /**
15104
     * The Progress Control component contains the seek bar, load progress,
15105
     * and play progress.
15106
     *
15107
     * @extends Component
15108
     */
15109
    class ProgressControl extends Component$1 {
15110
        /**
15111
         * Creates an instance of this class.
15112
         *
15113
         * @param { import('../../player').default } player
15114
         *        The `Player` that this class should be attached to.
15115
         *
15116
         * @param {Object} [options]
15117
         *        The key/value store of player options.
15118
         */
15119
        constructor(player, options) {
15120
            super(player, options);
15121
            this.handleMouseMove = throttle(bind_(this, this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
15122
            this.throttledHandleMouseSeek = throttle(bind_(this, this.handleMouseSeek), UPDATE_REFRESH_INTERVAL);
15123
            this.handleMouseUpHandler_ = e => this.handleMouseUp(e);
15124
            this.handleMouseDownHandler_ = e => this.handleMouseDown(e);
15125
            this.enable();
15126
        }
15127
 
15128
        /**
15129
         * Create the `Component`'s DOM element
15130
         *
15131
         * @return {Element}
15132
         *         The element that was created.
15133
         */
15134
        createEl() {
15135
            return super.createEl('div', {
15136
                className: 'vjs-progress-control vjs-control'
15137
            });
15138
        }
15139
 
15140
        /**
15141
         * When the mouse moves over the `ProgressControl`, the pointer position
15142
         * gets passed down to the `MouseTimeDisplay` component.
15143
         *
15144
         * @param {Event} event
15145
         *        The `mousemove` event that caused this function to run.
15146
         *
15147
         * @listen mousemove
15148
         */
15149
        handleMouseMove(event) {
15150
            const seekBar = this.getChild('seekBar');
15151
            if (!seekBar) {
15152
                return;
15153
            }
15154
            const playProgressBar = seekBar.getChild('playProgressBar');
15155
            const mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
15156
            if (!playProgressBar && !mouseTimeDisplay) {
15157
                return;
15158
            }
15159
            const seekBarEl = seekBar.el();
15160
            const seekBarRect = findPosition(seekBarEl);
15161
            let seekBarPoint = getPointerPosition(seekBarEl, event).x;
15162
 
15163
            // The default skin has a gap on either side of the `SeekBar`. This means
15164
            // that it's possible to trigger this behavior outside the boundaries of
15165
            // the `SeekBar`. This ensures we stay within it at all times.
15166
            seekBarPoint = clamp(seekBarPoint, 0, 1);
15167
            if (mouseTimeDisplay) {
15168
                mouseTimeDisplay.update(seekBarRect, seekBarPoint);
15169
            }
15170
            if (playProgressBar) {
15171
                playProgressBar.update(seekBarRect, seekBar.getProgress());
15172
            }
15173
        }
15174
 
15175
        /**
15176
         * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
15177
         *
15178
         * @method ProgressControl#throttledHandleMouseSeek
15179
         * @param {Event} event
15180
         *        The `mousemove` event that caused this function to run.
15181
         *
15182
         * @listen mousemove
15183
         * @listen touchmove
15184
         */
15185
 
15186
        /**
15187
         * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
15188
         *
15189
         * @param {Event} event
15190
         *        `mousedown` or `touchstart` event that triggered this function
15191
         *
15192
         * @listens mousemove
15193
         * @listens touchmove
15194
         */
15195
        handleMouseSeek(event) {
15196
            const seekBar = this.getChild('seekBar');
15197
            if (seekBar) {
15198
                seekBar.handleMouseMove(event);
15199
            }
15200
        }
15201
 
15202
        /**
15203
         * Are controls are currently enabled for this progress control.
15204
         *
15205
         * @return {boolean}
15206
         *         true if controls are enabled, false otherwise
15207
         */
15208
        enabled() {
15209
            return this.enabled_;
15210
        }
15211
 
15212
        /**
15213
         * Disable all controls on the progress control and its children
15214
         */
15215
        disable() {
15216
            this.children().forEach(child => child.disable && child.disable());
15217
            if (!this.enabled()) {
15218
                return;
15219
            }
15220
            this.off(['mousedown', 'touchstart'], this.handleMouseDownHandler_);
15221
            this.off(this.el_, 'mousemove', this.handleMouseMove);
15222
            this.removeListenersAddedOnMousedownAndTouchstart();
15223
            this.addClass('disabled');
15224
            this.enabled_ = false;
15225
 
15226
            // Restore normal playback state if controls are disabled while scrubbing
15227
            if (this.player_.scrubbing()) {
15228
                const seekBar = this.getChild('seekBar');
15229
                this.player_.scrubbing(false);
15230
                if (seekBar.videoWasPlaying) {
15231
                    silencePromise(this.player_.play());
15232
                }
15233
            }
15234
        }
15235
 
15236
        /**
15237
         * Enable all controls on the progress control and its children
15238
         */
15239
        enable() {
15240
            this.children().forEach(child => child.enable && child.enable());
15241
            if (this.enabled()) {
15242
                return;
15243
            }
15244
            this.on(['mousedown', 'touchstart'], this.handleMouseDownHandler_);
15245
            this.on(this.el_, 'mousemove', this.handleMouseMove);
15246
            this.removeClass('disabled');
15247
            this.enabled_ = true;
15248
        }
15249
 
15250
        /**
15251
         * Cleanup listeners after the user finishes interacting with the progress controls
15252
         */
15253
        removeListenersAddedOnMousedownAndTouchstart() {
15254
            const doc = this.el_.ownerDocument;
15255
            this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
15256
            this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
15257
            this.off(doc, 'mouseup', this.handleMouseUpHandler_);
15258
            this.off(doc, 'touchend', this.handleMouseUpHandler_);
15259
        }
15260
 
15261
        /**
15262
         * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
15263
         *
15264
         * @param {Event} event
15265
         *        `mousedown` or `touchstart` event that triggered this function
15266
         *
15267
         * @listens mousedown
15268
         * @listens touchstart
15269
         */
15270
        handleMouseDown(event) {
15271
            const doc = this.el_.ownerDocument;
15272
            const seekBar = this.getChild('seekBar');
15273
            if (seekBar) {
15274
                seekBar.handleMouseDown(event);
15275
            }
15276
            this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
15277
            this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
15278
            this.on(doc, 'mouseup', this.handleMouseUpHandler_);
15279
            this.on(doc, 'touchend', this.handleMouseUpHandler_);
15280
        }
15281
 
15282
        /**
15283
         * Handle `mouseup` or `touchend` events on the `ProgressControl`.
15284
         *
15285
         * @param {Event} event
15286
         *        `mouseup` or `touchend` event that triggered this function.
15287
         *
15288
         * @listens touchend
15289
         * @listens mouseup
15290
         */
15291
        handleMouseUp(event) {
15292
            const seekBar = this.getChild('seekBar');
15293
            if (seekBar) {
15294
                seekBar.handleMouseUp(event);
15295
            }
15296
            this.removeListenersAddedOnMousedownAndTouchstart();
15297
        }
15298
    }
15299
 
15300
    /**
15301
     * Default options for `ProgressControl`
15302
     *
15303
     * @type {Object}
15304
     * @private
15305
     */
15306
    ProgressControl.prototype.options_ = {
15307
        children: ['seekBar']
15308
    };
15309
    Component$1.registerComponent('ProgressControl', ProgressControl);
15310
 
15311
    /**
15312
     * @file picture-in-picture-toggle.js
15313
     */
15314
 
15315
    /**
15316
     * Toggle Picture-in-Picture mode
15317
     *
15318
     * @extends Button
15319
     */
15320
    class PictureInPictureToggle extends Button {
15321
        /**
15322
         * Creates an instance of this class.
15323
         *
15324
         * @param { import('./player').default } player
15325
         *        The `Player` that this class should be attached to.
15326
         *
15327
         * @param {Object} [options]
15328
         *        The key/value store of player options.
15329
         *
15330
         * @listens Player#enterpictureinpicture
15331
         * @listens Player#leavepictureinpicture
15332
         */
15333
        constructor(player, options) {
15334
            super(player, options);
15335
            this.setIcon('picture-in-picture-enter');
15336
            this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], e => this.handlePictureInPictureChange(e));
15337
            this.on(player, ['disablepictureinpicturechanged', 'loadedmetadata'], e => this.handlePictureInPictureEnabledChange(e));
15338
            this.on(player, ['loadedmetadata', 'audioonlymodechange', 'audiopostermodechange'], () => this.handlePictureInPictureAudioModeChange());
15339
 
15340
            // TODO: Deactivate button on player emptied event.
15341
            this.disable();
15342
        }
15343
 
15344
        /**
15345
         * Builds the default DOM `className`.
15346
         *
15347
         * @return {string}
15348
         *         The DOM `className` for this object.
15349
         */
15350
        buildCSSClass() {
15351
            return `vjs-picture-in-picture-control vjs-hidden ${super.buildCSSClass()}`;
15352
        }
15353
 
15354
        /**
15355
         * Displays or hides the button depending on the audio mode detection.
15356
         * Exits picture-in-picture if it is enabled when switching to audio mode.
15357
         */
15358
        handlePictureInPictureAudioModeChange() {
15359
            // This audio detection will not detect HLS or DASH audio-only streams because there was no reliable way to detect them at the time
15360
            const isSourceAudio = this.player_.currentType().substring(0, 5) === 'audio';
15361
            const isAudioMode = isSourceAudio || this.player_.audioPosterMode() || this.player_.audioOnlyMode();
15362
            if (!isAudioMode) {
15363
                this.show();
15364
                return;
15365
            }
15366
            if (this.player_.isInPictureInPicture()) {
15367
                this.player_.exitPictureInPicture();
15368
            }
15369
            this.hide();
15370
        }
15371
 
15372
        /**
15373
         * Enables or disables button based on availability of a Picture-In-Picture mode.
15374
         *
15375
         * Enabled if
15376
         * - `player.options().enableDocumentPictureInPicture` is true and
15377
         *   window.documentPictureInPicture is available; or
15378
         * - `player.disablePictureInPicture()` is false and
15379
         *   element.requestPictureInPicture is available
15380
         */
15381
        handlePictureInPictureEnabledChange() {
15382
            if (document.pictureInPictureEnabled && this.player_.disablePictureInPicture() === false || this.player_.options_.enableDocumentPictureInPicture && 'documentPictureInPicture' in window) {
15383
                this.enable();
15384
            } else {
15385
                this.disable();
15386
            }
15387
        }
15388
 
15389
        /**
15390
         * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly.
15391
         *
15392
         * @param {Event} [event]
15393
         *        The {@link Player#enterpictureinpicture} or {@link Player#leavepictureinpicture} event that caused this function to be
15394
         *        called.
15395
         *
15396
         * @listens Player#enterpictureinpicture
15397
         * @listens Player#leavepictureinpicture
15398
         */
15399
        handlePictureInPictureChange(event) {
15400
            if (this.player_.isInPictureInPicture()) {
15401
                this.setIcon('picture-in-picture-exit');
15402
                this.controlText('Exit Picture-in-Picture');
15403
            } else {
15404
                this.setIcon('picture-in-picture-enter');
15405
                this.controlText('Picture-in-Picture');
15406
            }
15407
            this.handlePictureInPictureEnabledChange();
15408
        }
15409
 
15410
        /**
15411
         * This gets called when an `PictureInPictureToggle` is "clicked". See
15412
         * {@link ClickableComponent} for more detailed information on what a click can be.
15413
         *
15414
         * @param {Event} [event]
15415
         *        The `keydown`, `tap`, or `click` event that caused this function to be
15416
         *        called.
15417
         *
15418
         * @listens tap
15419
         * @listens click
15420
         */
15421
        handleClick(event) {
15422
            if (!this.player_.isInPictureInPicture()) {
15423
                this.player_.requestPictureInPicture();
15424
            } else {
15425
                this.player_.exitPictureInPicture();
15426
            }
15427
        }
15428
 
15429
        /**
15430
         * Show the `Component`s element if it is hidden by removing the
15431
         * 'vjs-hidden' class name from it only in browsers that support the Picture-in-Picture API.
15432
         */
15433
        show() {
15434
            // Does not allow to display the pictureInPictureToggle in browsers that do not support the Picture-in-Picture API, e.g. Firefox.
15435
            if (typeof document.exitPictureInPicture !== 'function') {
15436
                return;
15437
            }
15438
            super.show();
15439
        }
15440
    }
15441
 
15442
    /**
15443
     * The text that should display over the `PictureInPictureToggle`s controls. Added for localization.
15444
     *
15445
     * @type {string}
15446
     * @protected
15447
     */
15448
    PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture';
15449
    Component$1.registerComponent('PictureInPictureToggle', PictureInPictureToggle);
15450
 
15451
    /**
15452
     * @file fullscreen-toggle.js
15453
     */
15454
 
15455
    /**
15456
     * Toggle fullscreen video
15457
     *
15458
     * @extends Button
15459
     */
15460
    class FullscreenToggle extends Button {
15461
        /**
15462
         * Creates an instance of this class.
15463
         *
15464
         * @param { import('./player').default } player
15465
         *        The `Player` that this class should be attached to.
15466
         *
15467
         * @param {Object} [options]
15468
         *        The key/value store of player options.
15469
         */
15470
        constructor(player, options) {
15471
            super(player, options);
15472
            this.setIcon('fullscreen-enter');
15473
            this.on(player, 'fullscreenchange', e => this.handleFullscreenChange(e));
15474
            if (document[player.fsApi_.fullscreenEnabled] === false) {
15475
                this.disable();
15476
            }
15477
        }
15478
 
15479
        /**
15480
         * Builds the default DOM `className`.
15481
         *
15482
         * @return {string}
15483
         *         The DOM `className` for this object.
15484
         */
15485
        buildCSSClass() {
15486
            return `vjs-fullscreen-control ${super.buildCSSClass()}`;
15487
        }
15488
 
15489
        /**
15490
         * Handles fullscreenchange on the player and change control text accordingly.
15491
         *
15492
         * @param {Event} [event]
15493
         *        The {@link Player#fullscreenchange} event that caused this function to be
15494
         *        called.
15495
         *
15496
         * @listens Player#fullscreenchange
15497
         */
15498
        handleFullscreenChange(event) {
15499
            if (this.player_.isFullscreen()) {
15500
                this.controlText('Exit Fullscreen');
15501
                this.setIcon('fullscreen-exit');
15502
            } else {
15503
                this.controlText('Fullscreen');
15504
                this.setIcon('fullscreen-enter');
15505
            }
15506
        }
15507
 
15508
        /**
15509
         * This gets called when an `FullscreenToggle` is "clicked". See
15510
         * {@link ClickableComponent} for more detailed information on what a click can be.
15511
         *
15512
         * @param {Event} [event]
15513
         *        The `keydown`, `tap`, or `click` event that caused this function to be
15514
         *        called.
15515
         *
15516
         * @listens tap
15517
         * @listens click
15518
         */
15519
        handleClick(event) {
15520
            if (!this.player_.isFullscreen()) {
15521
                this.player_.requestFullscreen();
15522
            } else {
15523
                this.player_.exitFullscreen();
15524
            }
15525
        }
15526
    }
15527
 
15528
    /**
15529
     * The text that should display over the `FullscreenToggle`s controls. Added for localization.
15530
     *
15531
     * @type {string}
15532
     * @protected
15533
     */
15534
    FullscreenToggle.prototype.controlText_ = 'Fullscreen';
15535
    Component$1.registerComponent('FullscreenToggle', FullscreenToggle);
15536
 
15537
    /**
15538
     * Check if volume control is supported and if it isn't hide the
15539
     * `Component` that was passed  using the `vjs-hidden` class.
15540
     *
15541
     * @param { import('../../component').default } self
15542
     *        The component that should be hidden if volume is unsupported
15543
     *
15544
     * @param { import('../../player').default } player
15545
     *        A reference to the player
15546
     *
15547
     * @private
15548
     */
15549
    const checkVolumeSupport = function (self, player) {
15550
        // hide volume controls when they're not supported by the current tech
15551
        if (player.tech_ && !player.tech_.featuresVolumeControl) {
15552
            self.addClass('vjs-hidden');
15553
        }
15554
        self.on(player, 'loadstart', function () {
15555
            if (!player.tech_.featuresVolumeControl) {
15556
                self.addClass('vjs-hidden');
15557
            } else {
15558
                self.removeClass('vjs-hidden');
15559
            }
15560
        });
15561
    };
15562
 
15563
    /**
15564
     * @file volume-level.js
15565
     */
15566
 
15567
    /**
15568
     * Shows volume level
15569
     *
15570
     * @extends Component
15571
     */
15572
    class VolumeLevel extends Component$1 {
15573
        /**
15574
         * Create the `Component`'s DOM element
15575
         *
15576
         * @return {Element}
15577
         *         The element that was created.
15578
         */
15579
        createEl() {
15580
            const el = super.createEl('div', {
15581
                className: 'vjs-volume-level'
15582
            });
15583
            this.setIcon('circle', el);
15584
            el.appendChild(super.createEl('span', {
15585
                className: 'vjs-control-text'
15586
            }));
15587
            return el;
15588
        }
15589
    }
15590
    Component$1.registerComponent('VolumeLevel', VolumeLevel);
15591
 
15592
    /**
15593
     * @file volume-level-tooltip.js
15594
     */
15595
 
15596
    /**
15597
     * Volume level tooltips display a volume above or side by side the volume bar.
15598
     *
15599
     * @extends Component
15600
     */
15601
    class VolumeLevelTooltip extends Component$1 {
15602
        /**
15603
         * Creates an instance of this class.
15604
         *
15605
         * @param { import('../../player').default } player
15606
         *        The {@link Player} that this class should be attached to.
15607
         *
15608
         * @param {Object} [options]
15609
         *        The key/value store of player options.
15610
         */
15611
        constructor(player, options) {
15612
            super(player, options);
15613
            this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
15614
        }
15615
 
15616
        /**
15617
         * Create the volume tooltip DOM element
15618
         *
15619
         * @return {Element}
15620
         *         The element that was created.
15621
         */
15622
        createEl() {
15623
            return super.createEl('div', {
15624
                className: 'vjs-volume-tooltip'
15625
            }, {
15626
                'aria-hidden': 'true'
15627
            });
15628
        }
15629
 
15630
        /**
15631
         * Updates the position of the tooltip relative to the `VolumeBar` and
15632
         * its content text.
15633
         *
15634
         * @param {Object} rangeBarRect
15635
         *        The `ClientRect` for the {@link VolumeBar} element.
15636
         *
15637
         * @param {number} rangeBarPoint
15638
         *        A number from 0 to 1, representing a horizontal/vertical reference point
15639
         *        from the left edge of the {@link VolumeBar}
15640
         *
15641
         * @param {boolean} vertical
15642
         *        Referees to the Volume control position
15643
         *        in the control bar{@link VolumeControl}
15644
         *
15645
         */
15646
        update(rangeBarRect, rangeBarPoint, vertical, content) {
15647
            if (!vertical) {
15648
                const tooltipRect = getBoundingClientRect(this.el_);
15649
                const playerRect = getBoundingClientRect(this.player_.el());
15650
                const volumeBarPointPx = rangeBarRect.width * rangeBarPoint;
15651
                if (!playerRect || !tooltipRect) {
15652
                    return;
15653
                }
15654
                const spaceLeftOfPoint = rangeBarRect.left - playerRect.left + volumeBarPointPx;
15655
                const spaceRightOfPoint = rangeBarRect.width - volumeBarPointPx + (playerRect.right - rangeBarRect.right);
15656
                let pullTooltipBy = tooltipRect.width / 2;
15657
                if (spaceLeftOfPoint < pullTooltipBy) {
15658
                    pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
15659
                } else if (spaceRightOfPoint < pullTooltipBy) {
15660
                    pullTooltipBy = spaceRightOfPoint;
15661
                }
15662
                if (pullTooltipBy < 0) {
15663
                    pullTooltipBy = 0;
15664
                } else if (pullTooltipBy > tooltipRect.width) {
15665
                    pullTooltipBy = tooltipRect.width;
15666
                }
15667
                this.el_.style.right = `-${pullTooltipBy}px`;
15668
            }
15669
            this.write(`${content}%`);
15670
        }
15671
 
15672
        /**
15673
         * Write the volume to the tooltip DOM element.
15674
         *
15675
         * @param {string} content
15676
         *        The formatted volume for the tooltip.
15677
         */
15678
        write(content) {
15679
            textContent(this.el_, content);
15680
        }
15681
 
15682
        /**
15683
         * Updates the position of the volume tooltip relative to the `VolumeBar`.
15684
         *
15685
         * @param {Object} rangeBarRect
15686
         *        The `ClientRect` for the {@link VolumeBar} element.
15687
         *
15688
         * @param {number} rangeBarPoint
15689
         *        A number from 0 to 1, representing a horizontal/vertical reference point
15690
         *        from the left edge of the {@link VolumeBar}
15691
         *
15692
         * @param {boolean} vertical
15693
         *        Referees to the Volume control position
15694
         *        in the control bar{@link VolumeControl}
15695
         *
15696
         * @param {number} volume
15697
         *        The volume level to update the tooltip to
15698
         *
15699
         * @param {Function} cb
15700
         *        A function that will be called during the request animation frame
15701
         *        for tooltips that need to do additional animations from the default
15702
         */
15703
        updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, cb) {
15704
            this.requestNamedAnimationFrame('VolumeLevelTooltip#updateVolume', () => {
15705
                this.update(rangeBarRect, rangeBarPoint, vertical, volume.toFixed(0));
15706
                if (cb) {
15707
                    cb();
15708
                }
15709
            });
15710
        }
15711
    }
15712
    Component$1.registerComponent('VolumeLevelTooltip', VolumeLevelTooltip);
15713
 
15714
    /**
15715
     * @file mouse-volume-level-display.js
15716
     */
15717
 
15718
    /**
15719
     * The {@link MouseVolumeLevelDisplay} component tracks mouse movement over the
15720
     * {@link VolumeControl}. It displays an indicator and a {@link VolumeLevelTooltip}
15721
     * indicating the volume level which is represented by a given point in the
15722
     * {@link VolumeBar}.
15723
     *
15724
     * @extends Component
15725
     */
15726
    class MouseVolumeLevelDisplay extends Component$1 {
15727
        /**
15728
         * Creates an instance of this class.
15729
         *
15730
         * @param { import('../../player').default } player
15731
         *        The {@link Player} that this class should be attached to.
15732
         *
15733
         * @param {Object} [options]
15734
         *        The key/value store of player options.
15735
         */
15736
        constructor(player, options) {
15737
            super(player, options);
15738
            this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
15739
        }
15740
 
15741
        /**
15742
         * Create the DOM element for this class.
15743
         *
15744
         * @return {Element}
15745
         *         The element that was created.
15746
         */
15747
        createEl() {
15748
            return super.createEl('div', {
15749
                className: 'vjs-mouse-display'
15750
            });
15751
        }
15752
 
15753
        /**
15754
         * Enquires updates to its own DOM as well as the DOM of its
15755
         * {@link VolumeLevelTooltip} child.
15756
         *
15757
         * @param {Object} rangeBarRect
15758
         *        The `ClientRect` for the {@link VolumeBar} element.
15759
         *
15760
         * @param {number} rangeBarPoint
15761
         *        A number from 0 to 1, representing a horizontal/vertical reference point
15762
         *        from the left edge of the {@link VolumeBar}
15763
         *
15764
         * @param {boolean} vertical
15765
         *        Referees to the Volume control position
15766
         *        in the control bar{@link VolumeControl}
15767
         *
15768
         */
15769
        update(rangeBarRect, rangeBarPoint, vertical) {
15770
            const volume = 100 * rangeBarPoint;
15771
            this.getChild('volumeLevelTooltip').updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, () => {
15772
                if (vertical) {
15773
                    this.el_.style.bottom = `${rangeBarRect.height * rangeBarPoint}px`;
15774
                } else {
15775
                    this.el_.style.left = `${rangeBarRect.width * rangeBarPoint}px`;
15776
                }
15777
            });
15778
        }
15779
    }
15780
 
15781
    /**
15782
     * Default options for `MouseVolumeLevelDisplay`
15783
     *
15784
     * @type {Object}
15785
     * @private
15786
     */
15787
    MouseVolumeLevelDisplay.prototype.options_ = {
15788
        children: ['volumeLevelTooltip']
15789
    };
15790
    Component$1.registerComponent('MouseVolumeLevelDisplay', MouseVolumeLevelDisplay);
15791
 
15792
    /**
15793
     * @file volume-bar.js
15794
     */
15795
 
15796
    /**
15797
     * The bar that contains the volume level and can be clicked on to adjust the level
15798
     *
15799
     * @extends Slider
15800
     */
15801
    class VolumeBar extends Slider {
15802
        /**
15803
         * Creates an instance of this class.
15804
         *
15805
         * @param { import('../../player').default } player
15806
         *        The `Player` that this class should be attached to.
15807
         *
15808
         * @param {Object} [options]
15809
         *        The key/value store of player options.
15810
         */
15811
        constructor(player, options) {
15812
            super(player, options);
15813
            this.on('slideractive', e => this.updateLastVolume_(e));
15814
            this.on(player, 'volumechange', e => this.updateARIAAttributes(e));
15815
            player.ready(() => this.updateARIAAttributes());
15816
        }
15817
 
15818
        /**
15819
         * Create the `Component`'s DOM element
15820
         *
15821
         * @return {Element}
15822
         *         The element that was created.
15823
         */
15824
        createEl() {
15825
            return super.createEl('div', {
15826
                className: 'vjs-volume-bar vjs-slider-bar'
15827
            }, {
15828
                'aria-label': this.localize('Volume Level'),
15829
                'aria-live': 'polite'
15830
            });
15831
        }
15832
 
15833
        /**
15834
         * Handle mouse down on volume bar
15835
         *
15836
         * @param {Event} event
15837
         *        The `mousedown` event that caused this to run.
15838
         *
15839
         * @listens mousedown
15840
         */
15841
        handleMouseDown(event) {
15842
            if (!isSingleLeftClick(event)) {
15843
                return;
15844
            }
15845
            super.handleMouseDown(event);
15846
        }
15847
 
15848
        /**
15849
         * Handle movement events on the {@link VolumeMenuButton}.
15850
         *
15851
         * @param {Event} event
15852
         *        The event that caused this function to run.
15853
         *
15854
         * @listens mousemove
15855
         */
15856
        handleMouseMove(event) {
15857
            const mouseVolumeLevelDisplay = this.getChild('mouseVolumeLevelDisplay');
15858
            if (mouseVolumeLevelDisplay) {
15859
                const volumeBarEl = this.el();
15860
                const volumeBarRect = getBoundingClientRect(volumeBarEl);
15861
                const vertical = this.vertical();
15862
                let volumeBarPoint = getPointerPosition(volumeBarEl, event);
15863
                volumeBarPoint = vertical ? volumeBarPoint.y : volumeBarPoint.x;
15864
                // The default skin has a gap on either side of the `VolumeBar`. This means
15865
                // that it's possible to trigger this behavior outside the boundaries of
15866
                // the `VolumeBar`. This ensures we stay within it at all times.
15867
                volumeBarPoint = clamp(volumeBarPoint, 0, 1);
15868
                mouseVolumeLevelDisplay.update(volumeBarRect, volumeBarPoint, vertical);
15869
            }
15870
            if (!isSingleLeftClick(event)) {
15871
                return;
15872
            }
15873
            this.checkMuted();
15874
            this.player_.volume(this.calculateDistance(event));
15875
        }
15876
 
15877
        /**
15878
         * If the player is muted unmute it.
15879
         */
15880
        checkMuted() {
15881
            if (this.player_.muted()) {
15882
                this.player_.muted(false);
15883
            }
15884
        }
15885
 
15886
        /**
15887
         * Get percent of volume level
15888
         *
15889
         * @return {number}
15890
         *         Volume level percent as a decimal number.
15891
         */
15892
        getPercent() {
15893
            if (this.player_.muted()) {
15894
                return 0;
15895
            }
15896
            return this.player_.volume();
15897
        }
15898
 
15899
        /**
15900
         * Increase volume level for keyboard users
15901
         */
15902
        stepForward() {
15903
            this.checkMuted();
15904
            this.player_.volume(this.player_.volume() + 0.1);
15905
        }
15906
 
15907
        /**
15908
         * Decrease volume level for keyboard users
15909
         */
15910
        stepBack() {
15911
            this.checkMuted();
15912
            this.player_.volume(this.player_.volume() - 0.1);
15913
        }
15914
 
15915
        /**
15916
         * Update ARIA accessibility attributes
15917
         *
15918
         * @param {Event} [event]
15919
         *        The `volumechange` event that caused this function to run.
15920
         *
15921
         * @listens Player#volumechange
15922
         */
15923
        updateARIAAttributes(event) {
15924
            const ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
15925
            this.el_.setAttribute('aria-valuenow', ariaValue);
15926
            this.el_.setAttribute('aria-valuetext', ariaValue + '%');
15927
        }
15928
 
15929
        /**
15930
         * Returns the current value of the player volume as a percentage
15931
         *
15932
         * @private
15933
         */
15934
        volumeAsPercentage_() {
15935
            return Math.round(this.player_.volume() * 100);
15936
        }
15937
 
15938
        /**
15939
         * When user starts dragging the VolumeBar, store the volume and listen for
15940
         * the end of the drag. When the drag ends, if the volume was set to zero,
15941
         * set lastVolume to the stored volume.
15942
         *
15943
         * @listens slideractive
15944
         * @private
15945
         */
15946
        updateLastVolume_() {
15947
            const volumeBeforeDrag = this.player_.volume();
15948
            this.one('sliderinactive', () => {
15949
                if (this.player_.volume() === 0) {
15950
                    this.player_.lastVolume_(volumeBeforeDrag);
15951
                }
15952
            });
15953
        }
15954
    }
15955
 
15956
    /**
15957
     * Default options for the `VolumeBar`
15958
     *
15959
     * @type {Object}
15960
     * @private
15961
     */
15962
    VolumeBar.prototype.options_ = {
15963
        children: ['volumeLevel'],
15964
        barName: 'volumeLevel'
15965
    };
15966
 
15967
    // MouseVolumeLevelDisplay tooltip should not be added to a player on mobile devices
15968
    if (!IS_IOS && !IS_ANDROID) {
15969
        VolumeBar.prototype.options_.children.splice(0, 0, 'mouseVolumeLevelDisplay');
15970
    }
15971
 
15972
    /**
15973
     * Call the update event for this Slider when this event happens on the player.
15974
     *
15975
     * @type {string}
15976
     */
15977
    VolumeBar.prototype.playerEvent = 'volumechange';
15978
    Component$1.registerComponent('VolumeBar', VolumeBar);
15979
 
15980
    /**
15981
     * @file volume-control.js
15982
     */
15983
 
15984
    /**
15985
     * The component for controlling the volume level
15986
     *
15987
     * @extends Component
15988
     */
15989
    class VolumeControl extends Component$1 {
15990
        /**
15991
         * Creates an instance of this class.
15992
         *
15993
         * @param { import('../../player').default } player
15994
         *        The `Player` that this class should be attached to.
15995
         *
15996
         * @param {Object} [options={}]
15997
         *        The key/value store of player options.
15998
         */
15999
        constructor(player, options = {}) {
16000
            options.vertical = options.vertical || false;
16001
 
16002
            // Pass the vertical option down to the VolumeBar if
16003
            // the VolumeBar is turned on.
16004
            if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
16005
                options.volumeBar = options.volumeBar || {};
16006
                options.volumeBar.vertical = options.vertical;
16007
            }
16008
            super(player, options);
16009
 
16010
            // hide this control if volume support is missing
16011
            checkVolumeSupport(this, player);
16012
            this.throttledHandleMouseMove = throttle(bind_(this, this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
16013
            this.handleMouseUpHandler_ = e => this.handleMouseUp(e);
16014
            this.on('mousedown', e => this.handleMouseDown(e));
16015
            this.on('touchstart', e => this.handleMouseDown(e));
16016
            this.on('mousemove', e => this.handleMouseMove(e));
16017
 
16018
            // while the slider is active (the mouse has been pressed down and
16019
            // is dragging) or in focus we do not want to hide the VolumeBar
16020
            this.on(this.volumeBar, ['focus', 'slideractive'], () => {
16021
                this.volumeBar.addClass('vjs-slider-active');
16022
                this.addClass('vjs-slider-active');
16023
                this.trigger('slideractive');
16024
            });
16025
            this.on(this.volumeBar, ['blur', 'sliderinactive'], () => {
16026
                this.volumeBar.removeClass('vjs-slider-active');
16027
                this.removeClass('vjs-slider-active');
16028
                this.trigger('sliderinactive');
16029
            });
16030
        }
16031
 
16032
        /**
16033
         * Create the `Component`'s DOM element
16034
         *
16035
         * @return {Element}
16036
         *         The element that was created.
16037
         */
16038
        createEl() {
16039
            let orientationClass = 'vjs-volume-horizontal';
16040
            if (this.options_.vertical) {
16041
                orientationClass = 'vjs-volume-vertical';
16042
            }
16043
            return super.createEl('div', {
16044
                className: `vjs-volume-control vjs-control ${orientationClass}`
16045
            });
16046
        }
16047
 
16048
        /**
16049
         * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
16050
         *
16051
         * @param {Event} event
16052
         *        `mousedown` or `touchstart` event that triggered this function
16053
         *
16054
         * @listens mousedown
16055
         * @listens touchstart
16056
         */
16057
        handleMouseDown(event) {
16058
            const doc = this.el_.ownerDocument;
16059
            this.on(doc, 'mousemove', this.throttledHandleMouseMove);
16060
            this.on(doc, 'touchmove', this.throttledHandleMouseMove);
16061
            this.on(doc, 'mouseup', this.handleMouseUpHandler_);
16062
            this.on(doc, 'touchend', this.handleMouseUpHandler_);
16063
        }
16064
 
16065
        /**
16066
         * Handle `mouseup` or `touchend` events on the `VolumeControl`.
16067
         *
16068
         * @param {Event} event
16069
         *        `mouseup` or `touchend` event that triggered this function.
16070
         *
16071
         * @listens touchend
16072
         * @listens mouseup
16073
         */
16074
        handleMouseUp(event) {
16075
            const doc = this.el_.ownerDocument;
16076
            this.off(doc, 'mousemove', this.throttledHandleMouseMove);
16077
            this.off(doc, 'touchmove', this.throttledHandleMouseMove);
16078
            this.off(doc, 'mouseup', this.handleMouseUpHandler_);
16079
            this.off(doc, 'touchend', this.handleMouseUpHandler_);
16080
        }
16081
 
16082
        /**
16083
         * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
16084
         *
16085
         * @param {Event} event
16086
         *        `mousedown` or `touchstart` event that triggered this function
16087
         *
16088
         * @listens mousedown
16089
         * @listens touchstart
16090
         */
16091
        handleMouseMove(event) {
16092
            this.volumeBar.handleMouseMove(event);
16093
        }
16094
    }
16095
 
16096
    /**
16097
     * Default options for the `VolumeControl`
16098
     *
16099
     * @type {Object}
16100
     * @private
16101
     */
16102
    VolumeControl.prototype.options_ = {
16103
        children: ['volumeBar']
16104
    };
16105
    Component$1.registerComponent('VolumeControl', VolumeControl);
16106
 
16107
    /**
16108
     * Check if muting volume is supported and if it isn't hide the mute toggle
16109
     * button.
16110
     *
16111
     * @param { import('../../component').default } self
16112
     *        A reference to the mute toggle button
16113
     *
16114
     * @param { import('../../player').default } player
16115
     *        A reference to the player
16116
     *
16117
     * @private
16118
     */
16119
    const checkMuteSupport = function (self, player) {
16120
        // hide mute toggle button if it's not supported by the current tech
16121
        if (player.tech_ && !player.tech_.featuresMuteControl) {
16122
            self.addClass('vjs-hidden');
16123
        }
16124
        self.on(player, 'loadstart', function () {
16125
            if (!player.tech_.featuresMuteControl) {
16126
                self.addClass('vjs-hidden');
16127
            } else {
16128
                self.removeClass('vjs-hidden');
16129
            }
16130
        });
16131
    };
16132
 
16133
    /**
16134
     * @file mute-toggle.js
16135
     */
16136
 
16137
    /**
16138
     * A button component for muting the audio.
16139
     *
16140
     * @extends Button
16141
     */
16142
    class MuteToggle extends Button {
16143
        /**
16144
         * Creates an instance of this class.
16145
         *
16146
         * @param { import('./player').default } player
16147
         *        The `Player` that this class should be attached to.
16148
         *
16149
         * @param {Object} [options]
16150
         *        The key/value store of player options.
16151
         */
16152
        constructor(player, options) {
16153
            super(player, options);
16154
 
16155
            // hide this control if volume support is missing
16156
            checkMuteSupport(this, player);
16157
            this.on(player, ['loadstart', 'volumechange'], e => this.update(e));
16158
        }
16159
 
16160
        /**
16161
         * Builds the default DOM `className`.
16162
         *
16163
         * @return {string}
16164
         *         The DOM `className` for this object.
16165
         */
16166
        buildCSSClass() {
16167
            return `vjs-mute-control ${super.buildCSSClass()}`;
16168
        }
16169
 
16170
        /**
16171
         * This gets called when an `MuteToggle` is "clicked". See
16172
         * {@link ClickableComponent} for more detailed information on what a click can be.
16173
         *
16174
         * @param {Event} [event]
16175
         *        The `keydown`, `tap`, or `click` event that caused this function to be
16176
         *        called.
16177
         *
16178
         * @listens tap
16179
         * @listens click
16180
         */
16181
        handleClick(event) {
16182
            const vol = this.player_.volume();
16183
            const lastVolume = this.player_.lastVolume_();
16184
            if (vol === 0) {
16185
                const volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
16186
                this.player_.volume(volumeToSet);
16187
                this.player_.muted(false);
16188
            } else {
16189
                this.player_.muted(this.player_.muted() ? false : true);
16190
            }
16191
        }
16192
 
16193
        /**
16194
         * Update the `MuteToggle` button based on the state of `volume` and `muted`
16195
         * on the player.
16196
         *
16197
         * @param {Event} [event]
16198
         *        The {@link Player#loadstart} event if this function was called
16199
         *        through an event.
16200
         *
16201
         * @listens Player#loadstart
16202
         * @listens Player#volumechange
16203
         */
16204
        update(event) {
16205
            this.updateIcon_();
16206
            this.updateControlText_();
16207
        }
16208
 
16209
        /**
16210
         * Update the appearance of the `MuteToggle` icon.
16211
         *
16212
         * Possible states (given `level` variable below):
16213
         * - 0: crossed out
16214
         * - 1: zero bars of volume
16215
         * - 2: one bar of volume
16216
         * - 3: two bars of volume
16217
         *
16218
         * @private
16219
         */
16220
        updateIcon_() {
16221
            const vol = this.player_.volume();
16222
            let level = 3;
16223
            this.setIcon('volume-high');
16224
 
16225
            // in iOS when a player is loaded with muted attribute
16226
            // and volume is changed with a native mute button
16227
            // we want to make sure muted state is updated
16228
            if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) {
16229
                this.player_.muted(this.player_.tech_.el_.muted);
16230
            }
16231
            if (vol === 0 || this.player_.muted()) {
16232
                this.setIcon('volume-mute');
16233
                level = 0;
16234
            } else if (vol < 0.33) {
16235
                this.setIcon('volume-low');
16236
                level = 1;
16237
            } else if (vol < 0.67) {
16238
                this.setIcon('volume-medium');
16239
                level = 2;
16240
            }
16241
            removeClass(this.el_, [0, 1, 2, 3].reduce((str, i) => str + `${i ? ' ' : ''}vjs-vol-${i}`, ''));
16242
            addClass(this.el_, `vjs-vol-${level}`);
16243
        }
16244
 
16245
        /**
16246
         * If `muted` has changed on the player, update the control text
16247
         * (`title` attribute on `vjs-mute-control` element and content of
16248
         * `vjs-control-text` element).
16249
         *
16250
         * @private
16251
         */
16252
        updateControlText_() {
16253
            const soundOff = this.player_.muted() || this.player_.volume() === 0;
16254
            const text = soundOff ? 'Unmute' : 'Mute';
16255
            if (this.controlText() !== text) {
16256
                this.controlText(text);
16257
            }
16258
        }
16259
    }
16260
 
16261
    /**
16262
     * The text that should display over the `MuteToggle`s controls. Added for localization.
16263
     *
16264
     * @type {string}
16265
     * @protected
16266
     */
16267
    MuteToggle.prototype.controlText_ = 'Mute';
16268
    Component$1.registerComponent('MuteToggle', MuteToggle);
16269
 
16270
    /**
16271
     * @file volume-control.js
16272
     */
16273
 
16274
    /**
16275
     * A Component to contain the MuteToggle and VolumeControl so that
16276
     * they can work together.
16277
     *
16278
     * @extends Component
16279
     */
16280
    class VolumePanel extends Component$1 {
16281
        /**
16282
         * Creates an instance of this class.
16283
         *
16284
         * @param { import('./player').default } player
16285
         *        The `Player` that this class should be attached to.
16286
         *
16287
         * @param {Object} [options={}]
16288
         *        The key/value store of player options.
16289
         */
16290
        constructor(player, options = {}) {
16291
            if (typeof options.inline !== 'undefined') {
16292
                options.inline = options.inline;
16293
            } else {
16294
                options.inline = true;
16295
            }
16296
 
16297
            // pass the inline option down to the VolumeControl as vertical if
16298
            // the VolumeControl is on.
16299
            if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
16300
                options.volumeControl = options.volumeControl || {};
16301
                options.volumeControl.vertical = !options.inline;
16302
            }
16303
            super(player, options);
16304
 
16305
            // this handler is used by mouse handler methods below
16306
            this.handleKeyPressHandler_ = e => this.handleKeyPress(e);
16307
            this.on(player, ['loadstart'], e => this.volumePanelState_(e));
16308
            this.on(this.muteToggle, 'keyup', e => this.handleKeyPress(e));
16309
            this.on(this.volumeControl, 'keyup', e => this.handleVolumeControlKeyUp(e));
16310
            this.on('keydown', e => this.handleKeyPress(e));
16311
            this.on('mouseover', e => this.handleMouseOver(e));
16312
            this.on('mouseout', e => this.handleMouseOut(e));
16313
 
16314
            // while the slider is active (the mouse has been pressed down and
16315
            // is dragging) we do not want to hide the VolumeBar
16316
            this.on(this.volumeControl, ['slideractive'], this.sliderActive_);
16317
            this.on(this.volumeControl, ['sliderinactive'], this.sliderInactive_);
16318
        }
16319
 
16320
        /**
16321
         * Add vjs-slider-active class to the VolumePanel
16322
         *
16323
         * @listens VolumeControl#slideractive
16324
         * @private
16325
         */
16326
        sliderActive_() {
16327
            this.addClass('vjs-slider-active');
16328
        }
16329
 
16330
        /**
16331
         * Removes vjs-slider-active class to the VolumePanel
16332
         *
16333
         * @listens VolumeControl#sliderinactive
16334
         * @private
16335
         */
16336
        sliderInactive_() {
16337
            this.removeClass('vjs-slider-active');
16338
        }
16339
 
16340
        /**
16341
         * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
16342
         * depending on MuteToggle and VolumeControl state
16343
         *
16344
         * @listens Player#loadstart
16345
         * @private
16346
         */
16347
        volumePanelState_() {
16348
            // hide volume panel if neither volume control or mute toggle
16349
            // are displayed
16350
            if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
16351
                this.addClass('vjs-hidden');
16352
            }
16353
 
16354
            // if only mute toggle is visible we don't want
16355
            // volume panel expanding when hovered or active
16356
            if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
16357
                this.addClass('vjs-mute-toggle-only');
16358
            }
16359
        }
16360
 
16361
        /**
16362
         * Create the `Component`'s DOM element
16363
         *
16364
         * @return {Element}
16365
         *         The element that was created.
16366
         */
16367
        createEl() {
16368
            let orientationClass = 'vjs-volume-panel-horizontal';
16369
            if (!this.options_.inline) {
16370
                orientationClass = 'vjs-volume-panel-vertical';
16371
            }
16372
            return super.createEl('div', {
16373
                className: `vjs-volume-panel vjs-control ${orientationClass}`
16374
            });
16375
        }
16376
 
16377
        /**
16378
         * Dispose of the `volume-panel` and all child components.
16379
         */
16380
        dispose() {
16381
            this.handleMouseOut();
16382
            super.dispose();
16383
        }
16384
 
16385
        /**
16386
         * Handles `keyup` events on the `VolumeControl`, looking for ESC, which closes
16387
         * the volume panel and sets focus on `MuteToggle`.
16388
         *
16389
         * @param {Event} event
16390
         *        The `keyup` event that caused this function to be called.
16391
         *
16392
         * @listens keyup
16393
         */
16394
        handleVolumeControlKeyUp(event) {
16395
            if (keycode.isEventKey(event, 'Esc')) {
16396
                this.muteToggle.focus();
16397
            }
16398
        }
16399
 
16400
        /**
16401
         * This gets called when a `VolumePanel` gains hover via a `mouseover` event.
16402
         * Turns on listening for `mouseover` event. When they happen it
16403
         * calls `this.handleMouseOver`.
16404
         *
16405
         * @param {Event} event
16406
         *        The `mouseover` event that caused this function to be called.
16407
         *
16408
         * @listens mouseover
16409
         */
16410
        handleMouseOver(event) {
16411
            this.addClass('vjs-hover');
16412
            on(document, 'keyup', this.handleKeyPressHandler_);
16413
        }
16414
 
16415
        /**
16416
         * This gets called when a `VolumePanel` gains hover via a `mouseout` event.
16417
         * Turns on listening for `mouseout` event. When they happen it
16418
         * calls `this.handleMouseOut`.
16419
         *
16420
         * @param {Event} event
16421
         *        The `mouseout` event that caused this function to be called.
16422
         *
16423
         * @listens mouseout
16424
         */
16425
        handleMouseOut(event) {
16426
            this.removeClass('vjs-hover');
16427
            off(document, 'keyup', this.handleKeyPressHandler_);
16428
        }
16429
 
16430
        /**
16431
         * Handles `keyup` event on the document or `keydown` event on the `VolumePanel`,
16432
         * looking for ESC, which hides the `VolumeControl`.
16433
         *
16434
         * @param {Event} event
16435
         *        The keypress that triggered this event.
16436
         *
16437
         * @listens keydown | keyup
16438
         */
16439
        handleKeyPress(event) {
16440
            if (keycode.isEventKey(event, 'Esc')) {
16441
                this.handleMouseOut();
16442
            }
16443
        }
16444
    }
16445
 
16446
    /**
16447
     * Default options for the `VolumeControl`
16448
     *
16449
     * @type {Object}
16450
     * @private
16451
     */
16452
    VolumePanel.prototype.options_ = {
16453
        children: ['muteToggle', 'volumeControl']
16454
    };
16455
    Component$1.registerComponent('VolumePanel', VolumePanel);
16456
 
16457
    /**
16458
     * Button to skip forward a configurable amount of time
16459
     * through a video. Renders in the control bar.
16460
     *
16461
     * e.g. options: {controlBar: {skipButtons: forward: 5}}
16462
     *
16463
     * @extends Button
16464
     */
16465
    class SkipForward extends Button {
16466
        constructor(player, options) {
16467
            super(player, options);
16468
            this.validOptions = [5, 10, 30];
16469
            this.skipTime = this.getSkipForwardTime();
16470
            if (this.skipTime && this.validOptions.includes(this.skipTime)) {
16471
                this.setIcon(`forward-${this.skipTime}`);
16472
                this.controlText(this.localize('Skip forward {1} seconds', [this.skipTime]));
16473
                this.show();
16474
            } else {
16475
                this.hide();
16476
            }
16477
        }
16478
        getSkipForwardTime() {
16479
            const playerOptions = this.options_.playerOptions;
16480
            return playerOptions.controlBar && playerOptions.controlBar.skipButtons && playerOptions.controlBar.skipButtons.forward;
16481
        }
16482
        buildCSSClass() {
16483
            return `vjs-skip-forward-${this.getSkipForwardTime()} ${super.buildCSSClass()}`;
16484
        }
16485
 
16486
        /**
16487
         * On click, skips forward in the duration/seekable range by a configurable amount of seconds.
16488
         * If the time left in the duration/seekable range is less than the configured 'skip forward' time,
16489
         * skips to end of duration/seekable range.
16490
         *
16491
         * Handle a click on a `SkipForward` button
16492
         *
16493
         * @param {EventTarget~Event} event
16494
         *        The `click` event that caused this function
16495
         *        to be called
16496
         */
16497
        handleClick(event) {
16498
            if (isNaN(this.player_.duration())) {
16499
                return;
16500
            }
16501
            const currentVideoTime = this.player_.currentTime();
16502
            const liveTracker = this.player_.liveTracker;
16503
            const duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
16504
            let newTime;
16505
            if (currentVideoTime + this.skipTime <= duration) {
16506
                newTime = currentVideoTime + this.skipTime;
16507
            } else {
16508
                newTime = duration;
16509
            }
16510
            this.player_.currentTime(newTime);
16511
        }
16512
 
16513
        /**
16514
         * Update control text on languagechange
16515
         */
16516
        handleLanguagechange() {
16517
            this.controlText(this.localize('Skip forward {1} seconds', [this.skipTime]));
16518
        }
16519
    }
16520
    SkipForward.prototype.controlText_ = 'Skip Forward';
16521
    Component$1.registerComponent('SkipForward', SkipForward);
16522
 
16523
    /**
16524
     * Button to skip backward a configurable amount of time
16525
     * through a video. Renders in the control bar.
16526
     *
16527
     *  * e.g. options: {controlBar: {skipButtons: backward: 5}}
16528
     *
16529
     * @extends Button
16530
     */
16531
    class SkipBackward extends Button {
16532
        constructor(player, options) {
16533
            super(player, options);
16534
            this.validOptions = [5, 10, 30];
16535
            this.skipTime = this.getSkipBackwardTime();
16536
            if (this.skipTime && this.validOptions.includes(this.skipTime)) {
16537
                this.setIcon(`replay-${this.skipTime}`);
16538
                this.controlText(this.localize('Skip backward {1} seconds', [this.skipTime]));
16539
                this.show();
16540
            } else {
16541
                this.hide();
16542
            }
16543
        }
16544
        getSkipBackwardTime() {
16545
            const playerOptions = this.options_.playerOptions;
16546
            return playerOptions.controlBar && playerOptions.controlBar.skipButtons && playerOptions.controlBar.skipButtons.backward;
16547
        }
16548
        buildCSSClass() {
16549
            return `vjs-skip-backward-${this.getSkipBackwardTime()} ${super.buildCSSClass()}`;
16550
        }
16551
 
16552
        /**
16553
         * On click, skips backward in the video by a configurable amount of seconds.
16554
         * If the current time in the video is less than the configured 'skip backward' time,
16555
         * skips to beginning of video or seekable range.
16556
         *
16557
         * Handle a click on a `SkipBackward` button
16558
         *
16559
         * @param {EventTarget~Event} event
16560
         *        The `click` event that caused this function
16561
         *        to be called
16562
         */
16563
        handleClick(event) {
16564
            const currentVideoTime = this.player_.currentTime();
16565
            const liveTracker = this.player_.liveTracker;
16566
            const seekableStart = liveTracker && liveTracker.isLive() && liveTracker.seekableStart();
16567
            let newTime;
16568
            if (seekableStart && currentVideoTime - this.skipTime <= seekableStart) {
16569
                newTime = seekableStart;
16570
            } else if (currentVideoTime >= this.skipTime) {
16571
                newTime = currentVideoTime - this.skipTime;
16572
            } else {
16573
                newTime = 0;
16574
            }
16575
            this.player_.currentTime(newTime);
16576
        }
16577
 
16578
        /**
16579
         * Update control text on languagechange
16580
         */
16581
        handleLanguagechange() {
16582
            this.controlText(this.localize('Skip backward {1} seconds', [this.skipTime]));
16583
        }
16584
    }
16585
    SkipBackward.prototype.controlText_ = 'Skip Backward';
16586
    Component$1.registerComponent('SkipBackward', SkipBackward);
16587
 
16588
    /**
16589
     * @file menu.js
16590
     */
16591
 
16592
    /**
16593
     * The Menu component is used to build popup menus, including subtitle and
16594
     * captions selection menus.
16595
     *
16596
     * @extends Component
16597
     */
16598
    class Menu extends Component$1 {
16599
        /**
16600
         * Create an instance of this class.
16601
         *
16602
         * @param { import('../player').default } player
16603
         *        the player that this component should attach to
16604
         *
16605
         * @param {Object} [options]
16606
         *        Object of option names and values
16607
         *
16608
         */
16609
        constructor(player, options) {
16610
            super(player, options);
16611
            if (options) {
16612
                this.menuButton_ = options.menuButton;
16613
            }
16614
            this.focusedChild_ = -1;
16615
            this.on('keydown', e => this.handleKeyDown(e));
16616
 
16617
            // All the menu item instances share the same blur handler provided by the menu container.
16618
            this.boundHandleBlur_ = e => this.handleBlur(e);
16619
            this.boundHandleTapClick_ = e => this.handleTapClick(e);
16620
        }
16621
 
16622
        /**
16623
         * Add event listeners to the {@link MenuItem}.
16624
         *
16625
         * @param {Object} component
16626
         *        The instance of the `MenuItem` to add listeners to.
16627
         *
16628
         */
16629
        addEventListenerForItem(component) {
16630
            if (!(component instanceof Component$1)) {
16631
                return;
16632
            }
16633
            this.on(component, 'blur', this.boundHandleBlur_);
16634
            this.on(component, ['tap', 'click'], this.boundHandleTapClick_);
16635
        }
16636
 
16637
        /**
16638
         * Remove event listeners from the {@link MenuItem}.
16639
         *
16640
         * @param {Object} component
16641
         *        The instance of the `MenuItem` to remove listeners.
16642
         *
16643
         */
16644
        removeEventListenerForItem(component) {
16645
            if (!(component instanceof Component$1)) {
16646
                return;
16647
            }
16648
            this.off(component, 'blur', this.boundHandleBlur_);
16649
            this.off(component, ['tap', 'click'], this.boundHandleTapClick_);
16650
        }
16651
 
16652
        /**
16653
         * This method will be called indirectly when the component has been added
16654
         * before the component adds to the new menu instance by `addItem`.
16655
         * In this case, the original menu instance will remove the component
16656
         * by calling `removeChild`.
16657
         *
16658
         * @param {Object} component
16659
         *        The instance of the `MenuItem`
16660
         */
16661
        removeChild(component) {
16662
            if (typeof component === 'string') {
16663
                component = this.getChild(component);
16664
            }
16665
            this.removeEventListenerForItem(component);
16666
            super.removeChild(component);
16667
        }
16668
 
16669
        /**
16670
         * Add a {@link MenuItem} to the menu.
16671
         *
16672
         * @param {Object|string} component
16673
         *        The name or instance of the `MenuItem` to add.
16674
         *
16675
         */
16676
        addItem(component) {
16677
            const childComponent = this.addChild(component);
16678
            if (childComponent) {
16679
                this.addEventListenerForItem(childComponent);
16680
            }
16681
        }
16682
 
16683
        /**
16684
         * Create the `Menu`s DOM element.
16685
         *
16686
         * @return {Element}
16687
         *         the element that was created
16688
         */
16689
        createEl() {
16690
            const contentElType = this.options_.contentElType || 'ul';
16691
            this.contentEl_ = createEl(contentElType, {
16692
                className: 'vjs-menu-content'
16693
            });
16694
            this.contentEl_.setAttribute('role', 'menu');
16695
            const el = super.createEl('div', {
16696
                append: this.contentEl_,
16697
                className: 'vjs-menu'
16698
            });
16699
            el.appendChild(this.contentEl_);
16700
 
16701
            // Prevent clicks from bubbling up. Needed for Menu Buttons,
16702
            // where a click on the parent is significant
16703
            on(el, 'click', function (event) {
16704
                event.preventDefault();
16705
                event.stopImmediatePropagation();
16706
            });
16707
            return el;
16708
        }
16709
        dispose() {
16710
            this.contentEl_ = null;
16711
            this.boundHandleBlur_ = null;
16712
            this.boundHandleTapClick_ = null;
16713
            super.dispose();
16714
        }
16715
 
16716
        /**
16717
         * Called when a `MenuItem` loses focus.
16718
         *
16719
         * @param {Event} event
16720
         *        The `blur` event that caused this function to be called.
16721
         *
16722
         * @listens blur
16723
         */
16724
        handleBlur(event) {
16725
            const relatedTarget = event.relatedTarget || document.activeElement;
16726
 
16727
            // Close menu popup when a user clicks outside the menu
16728
            if (!this.children().some(element => {
16729
                return element.el() === relatedTarget;
16730
            })) {
16731
                const btn = this.menuButton_;
16732
                if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) {
16733
                    btn.unpressButton();
16734
                }
16735
            }
16736
        }
16737
 
16738
        /**
16739
         * Called when a `MenuItem` gets clicked or tapped.
16740
         *
16741
         * @param {Event} event
16742
         *        The `click` or `tap` event that caused this function to be called.
16743
         *
16744
         * @listens click,tap
16745
         */
16746
        handleTapClick(event) {
16747
            // Unpress the associated MenuButton, and move focus back to it
16748
            if (this.menuButton_) {
16749
                this.menuButton_.unpressButton();
16750
                const childComponents = this.children();
16751
                if (!Array.isArray(childComponents)) {
16752
                    return;
16753
                }
16754
                const foundComponent = childComponents.filter(component => component.el() === event.target)[0];
16755
                if (!foundComponent) {
16756
                    return;
16757
                }
16758
 
16759
                // don't focus menu button if item is a caption settings item
16760
                // because focus will move elsewhere
16761
                if (foundComponent.name() !== 'CaptionSettingsMenuItem') {
16762
                    this.menuButton_.focus();
16763
                }
16764
            }
16765
        }
16766
 
16767
        /**
16768
         * Handle a `keydown` event on this menu. This listener is added in the constructor.
16769
         *
16770
         * @param {KeyboardEvent} event
16771
         *        A `keydown` event that happened on the menu.
16772
         *
16773
         * @listens keydown
16774
         */
16775
        handleKeyDown(event) {
16776
            // Left and Down Arrows
16777
            if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
16778
                event.preventDefault();
16779
                event.stopPropagation();
16780
                this.stepForward();
16781
 
16782
                // Up and Right Arrows
16783
            } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
16784
                event.preventDefault();
16785
                event.stopPropagation();
16786
                this.stepBack();
16787
            }
16788
        }
16789
 
16790
        /**
16791
         * Move to next (lower) menu item for keyboard users.
16792
         */
16793
        stepForward() {
16794
            let stepChild = 0;
16795
            if (this.focusedChild_ !== undefined) {
16796
                stepChild = this.focusedChild_ + 1;
16797
            }
16798
            this.focus(stepChild);
16799
        }
16800
 
16801
        /**
16802
         * Move to previous (higher) menu item for keyboard users.
16803
         */
16804
        stepBack() {
16805
            let stepChild = 0;
16806
            if (this.focusedChild_ !== undefined) {
16807
                stepChild = this.focusedChild_ - 1;
16808
            }
16809
            this.focus(stepChild);
16810
        }
16811
 
16812
        /**
16813
         * Set focus on a {@link MenuItem} in the `Menu`.
16814
         *
16815
         * @param {Object|string} [item=0]
16816
         *        Index of child item set focus on.
16817
         */
16818
        focus(item = 0) {
16819
            const children = this.children().slice();
16820
            const haveTitle = children.length && children[0].hasClass('vjs-menu-title');
16821
            if (haveTitle) {
16822
                children.shift();
16823
            }
16824
            if (children.length > 0) {
16825
                if (item < 0) {
16826
                    item = 0;
16827
                } else if (item >= children.length) {
16828
                    item = children.length - 1;
16829
                }
16830
                this.focusedChild_ = item;
16831
                children[item].el_.focus();
16832
            }
16833
        }
16834
    }
16835
    Component$1.registerComponent('Menu', Menu);
16836
 
16837
    /**
16838
     * @file menu-button.js
16839
     */
16840
 
16841
    /**
16842
     * A `MenuButton` class for any popup {@link Menu}.
16843
     *
16844
     * @extends Component
16845
     */
16846
    class MenuButton extends Component$1 {
16847
        /**
16848
         * Creates an instance of this class.
16849
         *
16850
         * @param { import('../player').default } player
16851
         *        The `Player` that this class should be attached to.
16852
         *
16853
         * @param {Object} [options={}]
16854
         *        The key/value store of player options.
16855
         */
16856
        constructor(player, options = {}) {
16857
            super(player, options);
16858
            this.menuButton_ = new Button(player, options);
16859
            this.menuButton_.controlText(this.controlText_);
16860
            this.menuButton_.el_.setAttribute('aria-haspopup', 'true');
16861
 
16862
            // Add buildCSSClass values to the button, not the wrapper
16863
            const buttonClass = Button.prototype.buildCSSClass();
16864
            this.menuButton_.el_.className = this.buildCSSClass() + ' ' + buttonClass;
16865
            this.menuButton_.removeClass('vjs-control');
16866
            this.addChild(this.menuButton_);
16867
            this.update();
16868
            this.enabled_ = true;
16869
            const handleClick = e => this.handleClick(e);
16870
            this.handleMenuKeyUp_ = e => this.handleMenuKeyUp(e);
16871
            this.on(this.menuButton_, 'tap', handleClick);
16872
            this.on(this.menuButton_, 'click', handleClick);
16873
            this.on(this.menuButton_, 'keydown', e => this.handleKeyDown(e));
16874
            this.on(this.menuButton_, 'mouseenter', () => {
16875
                this.addClass('vjs-hover');
16876
                this.menu.show();
16877
                on(document, 'keyup', this.handleMenuKeyUp_);
16878
            });
16879
            this.on('mouseleave', e => this.handleMouseLeave(e));
16880
            this.on('keydown', e => this.handleSubmenuKeyDown(e));
16881
        }
16882
 
16883
        /**
16884
         * Update the menu based on the current state of its items.
16885
         */
16886
        update() {
16887
            const menu = this.createMenu();
16888
            if (this.menu) {
16889
                this.menu.dispose();
16890
                this.removeChild(this.menu);
16891
            }
16892
            this.menu = menu;
16893
            this.addChild(menu);
16894
 
16895
            /**
16896
             * Track the state of the menu button
16897
             *
16898
             * @type {Boolean}
16899
             * @private
16900
             */
16901
            this.buttonPressed_ = false;
16902
            this.menuButton_.el_.setAttribute('aria-expanded', 'false');
16903
            if (this.items && this.items.length <= this.hideThreshold_) {
16904
                this.hide();
16905
                this.menu.contentEl_.removeAttribute('role');
16906
            } else {
16907
                this.show();
16908
                this.menu.contentEl_.setAttribute('role', 'menu');
16909
            }
16910
        }
16911
 
16912
        /**
16913
         * Create the menu and add all items to it.
16914
         *
16915
         * @return {Menu}
16916
         *         The constructed menu
16917
         */
16918
        createMenu() {
16919
            const menu = new Menu(this.player_, {
16920
                menuButton: this
16921
            });
16922
 
16923
            /**
16924
             * Hide the menu if the number of items is less than or equal to this threshold. This defaults
16925
             * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
16926
             * it here because every time we run `createMenu` we need to reset the value.
16927
             *
16928
             * @protected
16929
             * @type {Number}
16930
             */
16931
            this.hideThreshold_ = 0;
16932
 
16933
            // Add a title list item to the top
16934
            if (this.options_.title) {
16935
                const titleEl = createEl('li', {
16936
                    className: 'vjs-menu-title',
16937
                    textContent: toTitleCase$1(this.options_.title),
16938
                    tabIndex: -1
16939
                });
16940
                const titleComponent = new Component$1(this.player_, {
16941
                    el: titleEl
16942
                });
16943
                menu.addItem(titleComponent);
16944
            }
16945
            this.items = this.createItems();
16946
            if (this.items) {
16947
                // Add menu items to the menu
16948
                for (let i = 0; i < this.items.length; i++) {
16949
                    menu.addItem(this.items[i]);
16950
                }
16951
            }
16952
            return menu;
16953
        }
16954
 
16955
        /**
16956
         * Create the list of menu items. Specific to each subclass.
16957
         *
16958
         * @abstract
16959
         */
16960
        createItems() {}
16961
 
16962
        /**
16963
         * Create the `MenuButtons`s DOM element.
16964
         *
16965
         * @return {Element}
16966
         *         The element that gets created.
16967
         */
16968
        createEl() {
16969
            return super.createEl('div', {
16970
                className: this.buildWrapperCSSClass()
16971
            }, {});
16972
        }
16973
 
16974
        /**
16975
         * Overwrites the `setIcon` method from `Component`.
16976
         * In this case, we want the icon to be appended to the menuButton.
16977
         *
16978
         * @param {string} name
16979
         *         The icon name to be added.
16980
         */
16981
        setIcon(name) {
16982
            super.setIcon(name, this.menuButton_.el_);
16983
        }
16984
 
16985
        /**
16986
         * Allow sub components to stack CSS class names for the wrapper element
16987
         *
16988
         * @return {string}
16989
         *         The constructed wrapper DOM `className`
16990
         */
16991
        buildWrapperCSSClass() {
16992
            let menuButtonClass = 'vjs-menu-button';
16993
 
16994
            // If the inline option is passed, we want to use different styles altogether.
16995
            if (this.options_.inline === true) {
16996
                menuButtonClass += '-inline';
16997
            } else {
16998
                menuButtonClass += '-popup';
16999
            }
17000
 
17001
            // TODO: Fix the CSS so that this isn't necessary
17002
            const buttonClass = Button.prototype.buildCSSClass();
17003
            return `vjs-menu-button ${menuButtonClass} ${buttonClass} ${super.buildCSSClass()}`;
17004
        }
17005
 
17006
        /**
17007
         * Builds the default DOM `className`.
17008
         *
17009
         * @return {string}
17010
         *         The DOM `className` for this object.
17011
         */
17012
        buildCSSClass() {
17013
            let menuButtonClass = 'vjs-menu-button';
17014
 
17015
            // If the inline option is passed, we want to use different styles altogether.
17016
            if (this.options_.inline === true) {
17017
                menuButtonClass += '-inline';
17018
            } else {
17019
                menuButtonClass += '-popup';
17020
            }
17021
            return `vjs-menu-button ${menuButtonClass} ${super.buildCSSClass()}`;
17022
        }
17023
 
17024
        /**
17025
         * Get or set the localized control text that will be used for accessibility.
17026
         *
17027
         * > NOTE: This will come from the internal `menuButton_` element.
17028
         *
17029
         * @param {string} [text]
17030
         *        Control text for element.
17031
         *
17032
         * @param {Element} [el=this.menuButton_.el()]
17033
         *        Element to set the title on.
17034
         *
17035
         * @return {string}
17036
         *         - The control text when getting
17037
         */
17038
        controlText(text, el = this.menuButton_.el()) {
17039
            return this.menuButton_.controlText(text, el);
17040
        }
17041
 
17042
        /**
17043
         * Dispose of the `menu-button` and all child components.
17044
         */
17045
        dispose() {
17046
            this.handleMouseLeave();
17047
            super.dispose();
17048
        }
17049
 
17050
        /**
17051
         * Handle a click on a `MenuButton`.
17052
         * See {@link ClickableComponent#handleClick} for instances where this is called.
17053
         *
17054
         * @param {Event} event
17055
         *        The `keydown`, `tap`, or `click` event that caused this function to be
17056
         *        called.
17057
         *
17058
         * @listens tap
17059
         * @listens click
17060
         */
17061
        handleClick(event) {
17062
            if (this.buttonPressed_) {
17063
                this.unpressButton();
17064
            } else {
17065
                this.pressButton();
17066
            }
17067
        }
17068
 
17069
        /**
17070
         * Handle `mouseleave` for `MenuButton`.
17071
         *
17072
         * @param {Event} event
17073
         *        The `mouseleave` event that caused this function to be called.
17074
         *
17075
         * @listens mouseleave
17076
         */
17077
        handleMouseLeave(event) {
17078
            this.removeClass('vjs-hover');
17079
            off(document, 'keyup', this.handleMenuKeyUp_);
17080
        }
17081
 
17082
        /**
17083
         * Set the focus to the actual button, not to this element
17084
         */
17085
        focus() {
17086
            this.menuButton_.focus();
17087
        }
17088
 
17089
        /**
17090
         * Remove the focus from the actual button, not this element
17091
         */
17092
        blur() {
17093
            this.menuButton_.blur();
17094
        }
17095
 
17096
        /**
17097
         * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
17098
         * {@link ClickableComponent#handleKeyDown} for instances where this is called.
17099
         *
17100
         * @param {Event} event
17101
         *        The `keydown` event that caused this function to be called.
17102
         *
17103
         * @listens keydown
17104
         */
17105
        handleKeyDown(event) {
17106
            // Escape or Tab unpress the 'button'
17107
            if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
17108
                if (this.buttonPressed_) {
17109
                    this.unpressButton();
17110
                }
17111
 
17112
                // Don't preventDefault for Tab key - we still want to lose focus
17113
                if (!keycode.isEventKey(event, 'Tab')) {
17114
                    event.preventDefault();
17115
                    // Set focus back to the menu button's button
17116
                    this.menuButton_.focus();
17117
                }
17118
                // Up Arrow or Down Arrow also 'press' the button to open the menu
17119
            } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) {
17120
                if (!this.buttonPressed_) {
17121
                    event.preventDefault();
17122
                    this.pressButton();
17123
                }
17124
            }
17125
        }
17126
 
17127
        /**
17128
         * Handle a `keyup` event on a `MenuButton`. The listener for this is added in
17129
         * the constructor.
17130
         *
17131
         * @param {Event} event
17132
         *        Key press event
17133
         *
17134
         * @listens keyup
17135
         */
17136
        handleMenuKeyUp(event) {
17137
            // Escape hides popup menu
17138
            if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
17139
                this.removeClass('vjs-hover');
17140
            }
17141
        }
17142
 
17143
        /**
17144
         * This method name now delegates to `handleSubmenuKeyDown`. This means
17145
         * anyone calling `handleSubmenuKeyPress` will not see their method calls
17146
         * stop working.
17147
         *
17148
         * @param {Event} event
17149
         *        The event that caused this function to be called.
17150
         */
17151
        handleSubmenuKeyPress(event) {
17152
            this.handleSubmenuKeyDown(event);
17153
        }
17154
 
17155
        /**
17156
         * Handle a `keydown` event on a sub-menu. The listener for this is added in
17157
         * the constructor.
17158
         *
17159
         * @param {Event} event
17160
         *        Key press event
17161
         *
17162
         * @listens keydown
17163
         */
17164
        handleSubmenuKeyDown(event) {
17165
            // Escape or Tab unpress the 'button'
17166
            if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
17167
                if (this.buttonPressed_) {
17168
                    this.unpressButton();
17169
                }
17170
                // Don't preventDefault for Tab key - we still want to lose focus
17171
                if (!keycode.isEventKey(event, 'Tab')) {
17172
                    event.preventDefault();
17173
                    // Set focus back to the menu button's button
17174
                    this.menuButton_.focus();
17175
                }
17176
            }
17177
        }
17178
 
17179
        /**
17180
         * Put the current `MenuButton` into a pressed state.
17181
         */
17182
        pressButton() {
17183
            if (this.enabled_) {
17184
                this.buttonPressed_ = true;
17185
                this.menu.show();
17186
                this.menu.lockShowing();
17187
                this.menuButton_.el_.setAttribute('aria-expanded', 'true');
17188
 
17189
                // set the focus into the submenu, except on iOS where it is resulting in
17190
                // undesired scrolling behavior when the player is in an iframe
17191
                if (IS_IOS && isInFrame()) {
17192
                    // Return early so that the menu isn't focused
17193
                    return;
17194
                }
17195
                this.menu.focus();
17196
            }
17197
        }
17198
 
17199
        /**
17200
         * Take the current `MenuButton` out of a pressed state.
17201
         */
17202
        unpressButton() {
17203
            if (this.enabled_) {
17204
                this.buttonPressed_ = false;
17205
                this.menu.unlockShowing();
17206
                this.menu.hide();
17207
                this.menuButton_.el_.setAttribute('aria-expanded', 'false');
17208
            }
17209
        }
17210
 
17211
        /**
17212
         * Disable the `MenuButton`. Don't allow it to be clicked.
17213
         */
17214
        disable() {
17215
            this.unpressButton();
17216
            this.enabled_ = false;
17217
            this.addClass('vjs-disabled');
17218
            this.menuButton_.disable();
17219
        }
17220
 
17221
        /**
17222
         * Enable the `MenuButton`. Allow it to be clicked.
17223
         */
17224
        enable() {
17225
            this.enabled_ = true;
17226
            this.removeClass('vjs-disabled');
17227
            this.menuButton_.enable();
17228
        }
17229
    }
17230
    Component$1.registerComponent('MenuButton', MenuButton);
17231
 
17232
    /**
17233
     * @file track-button.js
17234
     */
17235
 
17236
    /**
17237
     * The base class for buttons that toggle specific  track types (e.g. subtitles).
17238
     *
17239
     * @extends MenuButton
17240
     */
17241
    class TrackButton extends MenuButton {
17242
        /**
17243
         * Creates an instance of this class.
17244
         *
17245
         * @param { import('./player').default } player
17246
         *        The `Player` that this class should be attached to.
17247
         *
17248
         * @param {Object} [options]
17249
         *        The key/value store of player options.
17250
         */
17251
        constructor(player, options) {
17252
            const tracks = options.tracks;
17253
            super(player, options);
17254
            if (this.items.length <= 1) {
17255
                this.hide();
17256
            }
17257
            if (!tracks) {
17258
                return;
17259
            }
17260
            const updateHandler = bind_(this, this.update);
17261
            tracks.addEventListener('removetrack', updateHandler);
17262
            tracks.addEventListener('addtrack', updateHandler);
17263
            tracks.addEventListener('labelchange', updateHandler);
17264
            this.player_.on('ready', updateHandler);
17265
            this.player_.on('dispose', function () {
17266
                tracks.removeEventListener('removetrack', updateHandler);
17267
                tracks.removeEventListener('addtrack', updateHandler);
17268
                tracks.removeEventListener('labelchange', updateHandler);
17269
            });
17270
        }
17271
    }
17272
    Component$1.registerComponent('TrackButton', TrackButton);
17273
 
17274
    /**
17275
     * @file menu-keys.js
17276
     */
17277
 
17278
    /**
17279
     * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`)
17280
     * Note that 'Enter' and 'Space' are not included here (otherwise they would
17281
     * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable)
17282
     *
17283
     * @typedef MenuKeys
17284
     * @array
17285
     */
17286
    const MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left'];
17287
 
17288
    /**
17289
     * @file menu-item.js
17290
     */
17291
 
17292
    /**
17293
     * The component for a menu item. `<li>`
17294
     *
17295
     * @extends ClickableComponent
17296
     */
17297
    class MenuItem extends ClickableComponent {
17298
        /**
17299
         * Creates an instance of the this class.
17300
         *
17301
         * @param { import('../player').default } player
17302
         *        The `Player` that this class should be attached to.
17303
         *
17304
         * @param {Object} [options={}]
17305
         *        The key/value store of player options.
17306
         *
17307
         */
17308
        constructor(player, options) {
17309
            super(player, options);
17310
            this.selectable = options.selectable;
17311
            this.isSelected_ = options.selected || false;
17312
            this.multiSelectable = options.multiSelectable;
17313
            this.selected(this.isSelected_);
17314
            if (this.selectable) {
17315
                if (this.multiSelectable) {
17316
                    this.el_.setAttribute('role', 'menuitemcheckbox');
17317
                } else {
17318
                    this.el_.setAttribute('role', 'menuitemradio');
17319
                }
17320
            } else {
17321
                this.el_.setAttribute('role', 'menuitem');
17322
            }
17323
        }
17324
 
17325
        /**
17326
         * Create the `MenuItem's DOM element
17327
         *
17328
         * @param {string} [type=li]
17329
         *        Element's node type, not actually used, always set to `li`.
17330
         *
17331
         * @param {Object} [props={}]
17332
         *        An object of properties that should be set on the element
17333
         *
17334
         * @param {Object} [attrs={}]
17335
         *        An object of attributes that should be set on the element
17336
         *
17337
         * @return {Element}
17338
         *         The element that gets created.
17339
         */
17340
        createEl(type, props, attrs) {
17341
            // The control is textual, not just an icon
17342
            this.nonIconControl = true;
17343
            const el = super.createEl('li', Object.assign({
17344
                className: 'vjs-menu-item',
17345
                tabIndex: -1
17346
            }, props), attrs);
17347
 
17348
            // swap icon with menu item text.
17349
            const menuItemEl = createEl('span', {
17350
                className: 'vjs-menu-item-text',
17351
                textContent: this.localize(this.options_.label)
17352
            });
17353
 
17354
            // If using SVG icons, the element with vjs-icon-placeholder will be added separately.
17355
            if (this.player_.options_.experimentalSvgIcons) {
17356
                el.appendChild(menuItemEl);
17357
            } else {
17358
                el.replaceChild(menuItemEl, el.querySelector('.vjs-icon-placeholder'));
17359
            }
17360
            return el;
17361
        }
17362
 
17363
        /**
17364
         * Ignore keys which are used by the menu, but pass any other ones up. See
17365
         * {@link ClickableComponent#handleKeyDown} for instances where this is called.
17366
         *
17367
         * @param {KeyboardEvent} event
17368
         *        The `keydown` event that caused this function to be called.
17369
         *
17370
         * @listens keydown
17371
         */
17372
        handleKeyDown(event) {
17373
            if (!MenuKeys.some(key => keycode.isEventKey(event, key))) {
17374
                // Pass keydown handling up for unused keys
17375
                super.handleKeyDown(event);
17376
            }
17377
        }
17378
 
17379
        /**
17380
         * Any click on a `MenuItem` puts it into the selected state.
17381
         * See {@link ClickableComponent#handleClick} for instances where this is called.
17382
         *
17383
         * @param {Event} event
17384
         *        The `keydown`, `tap`, or `click` event that caused this function to be
17385
         *        called.
17386
         *
17387
         * @listens tap
17388
         * @listens click
17389
         */
17390
        handleClick(event) {
17391
            this.selected(true);
17392
        }
17393
 
17394
        /**
17395
         * Set the state for this menu item as selected or not.
17396
         *
17397
         * @param {boolean} selected
17398
         *        if the menu item is selected or not
17399
         */
17400
        selected(selected) {
17401
            if (this.selectable) {
17402
                if (selected) {
17403
                    this.addClass('vjs-selected');
17404
                    this.el_.setAttribute('aria-checked', 'true');
17405
                    // aria-checked isn't fully supported by browsers/screen readers,
17406
                    // so indicate selected state to screen reader in the control text.
17407
                    this.controlText(', selected');
17408
                    this.isSelected_ = true;
17409
                } else {
17410
                    this.removeClass('vjs-selected');
17411
                    this.el_.setAttribute('aria-checked', 'false');
17412
                    // Indicate un-selected state to screen reader
17413
                    this.controlText('');
17414
                    this.isSelected_ = false;
17415
                }
17416
            }
17417
        }
17418
    }
17419
    Component$1.registerComponent('MenuItem', MenuItem);
17420
 
17421
    /**
17422
     * @file text-track-menu-item.js
17423
     */
17424
 
17425
    /**
17426
     * The specific menu item type for selecting a language within a text track kind
17427
     *
17428
     * @extends MenuItem
17429
     */
17430
    class TextTrackMenuItem extends MenuItem {
17431
        /**
17432
         * Creates an instance of this class.
17433
         *
17434
         * @param { import('../../player').default } player
17435
         *        The `Player` that this class should be attached to.
17436
         *
17437
         * @param {Object} [options]
17438
         *        The key/value store of player options.
17439
         */
17440
        constructor(player, options) {
17441
            const track = options.track;
17442
            const tracks = player.textTracks();
17443
 
17444
            // Modify options for parent MenuItem class's init.
17445
            options.label = track.label || track.language || 'Unknown';
17446
            options.selected = track.mode === 'showing';
17447
            super(player, options);
17448
            this.track = track;
17449
            // Determine the relevant kind(s) of tracks for this component and filter
17450
            // out empty kinds.
17451
            this.kinds = (options.kinds || [options.kind || this.track.kind]).filter(Boolean);
17452
            const changeHandler = (...args) => {
17453
                this.handleTracksChange.apply(this, args);
17454
            };
17455
            const selectedLanguageChangeHandler = (...args) => {
17456
                this.handleSelectedLanguageChange.apply(this, args);
17457
            };
17458
            player.on(['loadstart', 'texttrackchange'], changeHandler);
17459
            tracks.addEventListener('change', changeHandler);
17460
            tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
17461
            this.on('dispose', function () {
17462
                player.off(['loadstart', 'texttrackchange'], changeHandler);
17463
                tracks.removeEventListener('change', changeHandler);
17464
                tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
17465
            });
17466
 
17467
            // iOS7 doesn't dispatch change events to TextTrackLists when an
17468
            // associated track's mode changes. Without something like
17469
            // Object.observe() (also not present on iOS7), it's not
17470
            // possible to detect changes to the mode attribute and polyfill
17471
            // the change event. As a poor substitute, we manually dispatch
17472
            // change events whenever the controls modify the mode.
17473
            if (tracks.onchange === undefined) {
17474
                let event;
17475
                this.on(['tap', 'click'], function () {
17476
                    if (typeof window.Event !== 'object') {
17477
                        // Android 2.3 throws an Illegal Constructor error for window.Event
17478
                        try {
17479
                            event = new window.Event('change');
17480
                        } catch (err) {
17481
                            // continue regardless of error
17482
                        }
17483
                    }
17484
                    if (!event) {
17485
                        event = document.createEvent('Event');
17486
                        event.initEvent('change', true, true);
17487
                    }
17488
                    tracks.dispatchEvent(event);
17489
                });
17490
            }
17491
 
17492
            // set the default state based on current tracks
17493
            this.handleTracksChange();
17494
        }
17495
 
17496
        /**
17497
         * This gets called when an `TextTrackMenuItem` is "clicked". See
17498
         * {@link ClickableComponent} for more detailed information on what a click can be.
17499
         *
17500
         * @param {Event} event
17501
         *        The `keydown`, `tap`, or `click` event that caused this function to be
17502
         *        called.
17503
         *
17504
         * @listens tap
17505
         * @listens click
17506
         */
17507
        handleClick(event) {
17508
            const referenceTrack = this.track;
17509
            const tracks = this.player_.textTracks();
17510
            super.handleClick(event);
17511
            if (!tracks) {
17512
                return;
17513
            }
17514
            for (let i = 0; i < tracks.length; i++) {
17515
                const track = tracks[i];
17516
 
17517
                // If the track from the text tracks list is not of the right kind,
17518
                // skip it. We do not want to affect tracks of incompatible kind(s).
17519
                if (this.kinds.indexOf(track.kind) === -1) {
17520
                    continue;
17521
                }
17522
 
17523
                // If this text track is the component's track and it is not showing,
17524
                // set it to showing.
17525
                if (track === referenceTrack) {
17526
                    if (track.mode !== 'showing') {
17527
                        track.mode = 'showing';
17528
                    }
17529
 
17530
                    // If this text track is not the component's track and it is not
17531
                    // disabled, set it to disabled.
17532
                } else if (track.mode !== 'disabled') {
17533
                    track.mode = 'disabled';
17534
                }
17535
            }
17536
        }
17537
 
17538
        /**
17539
         * Handle text track list change
17540
         *
17541
         * @param {Event} event
17542
         *        The `change` event that caused this function to be called.
17543
         *
17544
         * @listens TextTrackList#change
17545
         */
17546
        handleTracksChange(event) {
17547
            const shouldBeSelected = this.track.mode === 'showing';
17548
 
17549
            // Prevent redundant selected() calls because they may cause
17550
            // screen readers to read the appended control text unnecessarily
17551
            if (shouldBeSelected !== this.isSelected_) {
17552
                this.selected(shouldBeSelected);
17553
            }
17554
        }
17555
        handleSelectedLanguageChange(event) {
17556
            if (this.track.mode === 'showing') {
17557
                const selectedLanguage = this.player_.cache_.selectedLanguage;
17558
 
17559
                // Don't replace the kind of track across the same language
17560
                if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
17561
                    return;
17562
                }
17563
                this.player_.cache_.selectedLanguage = {
17564
                    enabled: true,
17565
                    language: this.track.language,
17566
                    kind: this.track.kind
17567
                };
17568
            }
17569
        }
17570
        dispose() {
17571
            // remove reference to track object on dispose
17572
            this.track = null;
17573
            super.dispose();
17574
        }
17575
    }
17576
    Component$1.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
17577
 
17578
    /**
17579
     * @file off-text-track-menu-item.js
17580
     */
17581
 
17582
    /**
17583
     * A special menu item for turning off a specific type of text track
17584
     *
17585
     * @extends TextTrackMenuItem
17586
     */
17587
    class OffTextTrackMenuItem extends TextTrackMenuItem {
17588
        /**
17589
         * Creates an instance of this class.
17590
         *
17591
         * @param { import('../../player').default } player
17592
         *        The `Player` that this class should be attached to.
17593
         *
17594
         * @param {Object} [options]
17595
         *        The key/value store of player options.
17596
         */
17597
        constructor(player, options) {
17598
            // Create pseudo track info
17599
            // Requires options['kind']
17600
            options.track = {
17601
                player,
17602
                // it is no longer necessary to store `kind` or `kinds` on the track itself
17603
                // since they are now stored in the `kinds` property of all instances of
17604
                // TextTrackMenuItem, but this will remain for backwards compatibility
17605
                kind: options.kind,
17606
                kinds: options.kinds,
17607
                default: false,
17608
                mode: 'disabled'
17609
            };
17610
            if (!options.kinds) {
17611
                options.kinds = [options.kind];
17612
            }
17613
            if (options.label) {
17614
                options.track.label = options.label;
17615
            } else {
17616
                options.track.label = options.kinds.join(' and ') + ' off';
17617
            }
17618
 
17619
            // MenuItem is selectable
17620
            options.selectable = true;
17621
            // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
17622
            options.multiSelectable = false;
17623
            super(player, options);
17624
        }
17625
 
17626
        /**
17627
         * Handle text track change
17628
         *
17629
         * @param {Event} event
17630
         *        The event that caused this function to run
17631
         */
17632
        handleTracksChange(event) {
17633
            const tracks = this.player().textTracks();
17634
            let shouldBeSelected = true;
17635
            for (let i = 0, l = tracks.length; i < l; i++) {
17636
                const track = tracks[i];
17637
                if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
17638
                    shouldBeSelected = false;
17639
                    break;
17640
                }
17641
            }
17642
 
17643
            // Prevent redundant selected() calls because they may cause
17644
            // screen readers to read the appended control text unnecessarily
17645
            if (shouldBeSelected !== this.isSelected_) {
17646
                this.selected(shouldBeSelected);
17647
            }
17648
        }
17649
        handleSelectedLanguageChange(event) {
17650
            const tracks = this.player().textTracks();
17651
            let allHidden = true;
17652
            for (let i = 0, l = tracks.length; i < l; i++) {
17653
                const track = tracks[i];
17654
                if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
17655
                    allHidden = false;
17656
                    break;
17657
                }
17658
            }
17659
            if (allHidden) {
17660
                this.player_.cache_.selectedLanguage = {
17661
                    enabled: false
17662
                };
17663
            }
17664
        }
17665
 
17666
        /**
17667
         * Update control text and label on languagechange
17668
         */
17669
        handleLanguagechange() {
17670
            this.$('.vjs-menu-item-text').textContent = this.player_.localize(this.options_.label);
17671
            super.handleLanguagechange();
17672
        }
17673
    }
17674
    Component$1.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
17675
 
17676
    /**
17677
     * @file text-track-button.js
17678
     */
17679
 
17680
    /**
17681
     * The base class for buttons that toggle specific text track types (e.g. subtitles)
17682
     *
17683
     * @extends MenuButton
17684
     */
17685
    class TextTrackButton extends TrackButton {
17686
        /**
17687
         * Creates an instance of this class.
17688
         *
17689
         * @param { import('../../player').default } player
17690
         *        The `Player` that this class should be attached to.
17691
         *
17692
         * @param {Object} [options={}]
17693
         *        The key/value store of player options.
17694
         */
17695
        constructor(player, options = {}) {
17696
            options.tracks = player.textTracks();
17697
            super(player, options);
17698
        }
17699
 
17700
        /**
17701
         * Create a menu item for each text track
17702
         *
17703
         * @param {TextTrackMenuItem[]} [items=[]]
17704
         *        Existing array of items to use during creation
17705
         *
17706
         * @return {TextTrackMenuItem[]}
17707
         *         Array of menu items that were created
17708
         */
17709
        createItems(items = [], TrackMenuItem = TextTrackMenuItem) {
17710
            // Label is an override for the [track] off label
17711
            // USed to localise captions/subtitles
17712
            let label;
17713
            if (this.label_) {
17714
                label = `${this.label_} off`;
17715
            }
17716
            // Add an OFF menu item to turn all tracks off
17717
            items.push(new OffTextTrackMenuItem(this.player_, {
17718
                kinds: this.kinds_,
17719
                kind: this.kind_,
17720
                label
17721
            }));
17722
            this.hideThreshold_ += 1;
17723
            const tracks = this.player_.textTracks();
17724
            if (!Array.isArray(this.kinds_)) {
17725
                this.kinds_ = [this.kind_];
17726
            }
17727
            for (let i = 0; i < tracks.length; i++) {
17728
                const track = tracks[i];
17729
 
17730
                // only add tracks that are of an appropriate kind and have a label
17731
                if (this.kinds_.indexOf(track.kind) > -1) {
17732
                    const item = new TrackMenuItem(this.player_, {
17733
                        track,
17734
                        kinds: this.kinds_,
17735
                        kind: this.kind_,
17736
                        // MenuItem is selectable
17737
                        selectable: true,
17738
                        // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
17739
                        multiSelectable: false
17740
                    });
17741
                    item.addClass(`vjs-${track.kind}-menu-item`);
17742
                    items.push(item);
17743
                }
17744
            }
17745
            return items;
17746
        }
17747
    }
17748
    Component$1.registerComponent('TextTrackButton', TextTrackButton);
17749
 
17750
    /**
17751
     * @file chapters-track-menu-item.js
17752
     */
17753
 
17754
    /**
17755
     * The chapter track menu item
17756
     *
17757
     * @extends MenuItem
17758
     */
17759
    class ChaptersTrackMenuItem extends MenuItem {
17760
        /**
17761
         * Creates an instance of this class.
17762
         *
17763
         * @param { import('../../player').default } player
17764
         *        The `Player` that this class should be attached to.
17765
         *
17766
         * @param {Object} [options]
17767
         *        The key/value store of player options.
17768
         */
17769
        constructor(player, options) {
17770
            const track = options.track;
17771
            const cue = options.cue;
17772
            const currentTime = player.currentTime();
17773
 
17774
            // Modify options for parent MenuItem class's init.
17775
            options.selectable = true;
17776
            options.multiSelectable = false;
17777
            options.label = cue.text;
17778
            options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
17779
            super(player, options);
17780
            this.track = track;
17781
            this.cue = cue;
17782
        }
17783
 
17784
        /**
17785
         * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
17786
         * {@link ClickableComponent} for more detailed information on what a click can be.
17787
         *
17788
         * @param {Event} [event]
17789
         *        The `keydown`, `tap`, or `click` event that caused this function to be
17790
         *        called.
17791
         *
17792
         * @listens tap
17793
         * @listens click
17794
         */
17795
        handleClick(event) {
17796
            super.handleClick();
17797
            this.player_.currentTime(this.cue.startTime);
17798
        }
17799
    }
17800
    Component$1.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
17801
 
17802
    /**
17803
     * @file chapters-button.js
17804
     */
17805
 
17806
    /**
17807
     * The button component for toggling and selecting chapters
17808
     * Chapters act much differently than other text tracks
17809
     * Cues are navigation vs. other tracks of alternative languages
17810
     *
17811
     * @extends TextTrackButton
17812
     */
17813
    class ChaptersButton extends TextTrackButton {
17814
        /**
17815
         * Creates an instance of this class.
17816
         *
17817
         * @param { import('../../player').default } player
17818
         *        The `Player` that this class should be attached to.
17819
         *
17820
         * @param {Object} [options]
17821
         *        The key/value store of player options.
17822
         *
17823
         * @param {Function} [ready]
17824
         *        The function to call when this function is ready.
17825
         */
17826
        constructor(player, options, ready) {
17827
            super(player, options, ready);
17828
            this.setIcon('chapters');
17829
            this.selectCurrentItem_ = () => {
17830
                this.items.forEach(item => {
17831
                    item.selected(this.track_.activeCues[0] === item.cue);
17832
                });
17833
            };
17834
        }
17835
 
17836
        /**
17837
         * Builds the default DOM `className`.
17838
         *
17839
         * @return {string}
17840
         *         The DOM `className` for this object.
17841
         */
17842
        buildCSSClass() {
17843
            return `vjs-chapters-button ${super.buildCSSClass()}`;
17844
        }
17845
        buildWrapperCSSClass() {
17846
            return `vjs-chapters-button ${super.buildWrapperCSSClass()}`;
17847
        }
17848
 
17849
        /**
17850
         * Update the menu based on the current state of its items.
17851
         *
17852
         * @param {Event} [event]
17853
         *        An event that triggered this function to run.
17854
         *
17855
         * @listens TextTrackList#addtrack
17856
         * @listens TextTrackList#removetrack
17857
         * @listens TextTrackList#change
17858
         */
17859
        update(event) {
17860
            if (event && event.track && event.track.kind !== 'chapters') {
17861
                return;
17862
            }
17863
            const track = this.findChaptersTrack();
17864
            if (track !== this.track_) {
17865
                this.setTrack(track);
17866
                super.update();
17867
            } else if (!this.items || track && track.cues && track.cues.length !== this.items.length) {
17868
                // Update the menu initially or if the number of cues has changed since set
17869
                super.update();
17870
            }
17871
        }
17872
 
17873
        /**
17874
         * Set the currently selected track for the chapters button.
17875
         *
17876
         * @param {TextTrack} track
17877
         *        The new track to select. Nothing will change if this is the currently selected
17878
         *        track.
17879
         */
17880
        setTrack(track) {
17881
            if (this.track_ === track) {
17882
                return;
17883
            }
17884
            if (!this.updateHandler_) {
17885
                this.updateHandler_ = this.update.bind(this);
17886
            }
17887
 
17888
            // here this.track_ refers to the old track instance
17889
            if (this.track_) {
17890
                const remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
17891
                if (remoteTextTrackEl) {
17892
                    remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
17893
                }
17894
                this.track_.removeEventListener('cuechange', this.selectCurrentItem_);
17895
                this.track_ = null;
17896
            }
17897
            this.track_ = track;
17898
 
17899
            // here this.track_ refers to the new track instance
17900
            if (this.track_) {
17901
                this.track_.mode = 'hidden';
17902
                const remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
17903
                if (remoteTextTrackEl) {
17904
                    remoteTextTrackEl.addEventListener('load', this.updateHandler_);
17905
                }
17906
                this.track_.addEventListener('cuechange', this.selectCurrentItem_);
17907
            }
17908
        }
17909
 
17910
        /**
17911
         * Find the track object that is currently in use by this ChaptersButton
17912
         *
17913
         * @return {TextTrack|undefined}
17914
         *         The current track or undefined if none was found.
17915
         */
17916
        findChaptersTrack() {
17917
            const tracks = this.player_.textTracks() || [];
17918
            for (let i = tracks.length - 1; i >= 0; i--) {
17919
                // We will always choose the last track as our chaptersTrack
17920
                const track = tracks[i];
17921
                if (track.kind === this.kind_) {
17922
                    return track;
17923
                }
17924
            }
17925
        }
17926
 
17927
        /**
17928
         * Get the caption for the ChaptersButton based on the track label. This will also
17929
         * use the current tracks localized kind as a fallback if a label does not exist.
17930
         *
17931
         * @return {string}
17932
         *         The tracks current label or the localized track kind.
17933
         */
17934
        getMenuCaption() {
17935
            if (this.track_ && this.track_.label) {
17936
                return this.track_.label;
17937
            }
17938
            return this.localize(toTitleCase$1(this.kind_));
17939
        }
17940
 
17941
        /**
17942
         * Create menu from chapter track
17943
         *
17944
         * @return { import('../../menu/menu').default }
17945
         *         New menu for the chapter buttons
17946
         */
17947
        createMenu() {
17948
            this.options_.title = this.getMenuCaption();
17949
            return super.createMenu();
17950
        }
17951
 
17952
        /**
17953
         * Create a menu item for each text track
17954
         *
17955
         * @return  { import('./text-track-menu-item').default[] }
17956
         *         Array of menu items
17957
         */
17958
        createItems() {
17959
            const items = [];
17960
            if (!this.track_) {
17961
                return items;
17962
            }
17963
            const cues = this.track_.cues;
17964
            if (!cues) {
17965
                return items;
17966
            }
17967
            for (let i = 0, l = cues.length; i < l; i++) {
17968
                const cue = cues[i];
17969
                const mi = new ChaptersTrackMenuItem(this.player_, {
17970
                    track: this.track_,
17971
                    cue
17972
                });
17973
                items.push(mi);
17974
            }
17975
            return items;
17976
        }
17977
    }
17978
 
17979
    /**
17980
     * `kind` of TextTrack to look for to associate it with this menu.
17981
     *
17982
     * @type {string}
17983
     * @private
17984
     */
17985
    ChaptersButton.prototype.kind_ = 'chapters';
17986
 
17987
    /**
17988
     * The text that should display over the `ChaptersButton`s controls. Added for localization.
17989
     *
17990
     * @type {string}
17991
     * @protected
17992
     */
17993
    ChaptersButton.prototype.controlText_ = 'Chapters';
17994
    Component$1.registerComponent('ChaptersButton', ChaptersButton);
17995
 
17996
    /**
17997
     * @file descriptions-button.js
17998
     */
17999
 
18000
    /**
18001
     * The button component for toggling and selecting descriptions
18002
     *
18003
     * @extends TextTrackButton
18004
     */
18005
    class DescriptionsButton extends TextTrackButton {
18006
        /**
18007
         * Creates an instance of this class.
18008
         *
18009
         * @param { import('../../player').default } player
18010
         *        The `Player` that this class should be attached to.
18011
         *
18012
         * @param {Object} [options]
18013
         *        The key/value store of player options.
18014
         *
18015
         * @param {Function} [ready]
18016
         *        The function to call when this component is ready.
18017
         */
18018
        constructor(player, options, ready) {
18019
            super(player, options, ready);
18020
            this.setIcon('audio-description');
18021
            const tracks = player.textTracks();
18022
            const changeHandler = bind_(this, this.handleTracksChange);
18023
            tracks.addEventListener('change', changeHandler);
18024
            this.on('dispose', function () {
18025
                tracks.removeEventListener('change', changeHandler);
18026
            });
18027
        }
18028
 
18029
        /**
18030
         * Handle text track change
18031
         *
18032
         * @param {Event} event
18033
         *        The event that caused this function to run
18034
         *
18035
         * @listens TextTrackList#change
18036
         */
18037
        handleTracksChange(event) {
18038
            const tracks = this.player().textTracks();
18039
            let disabled = false;
18040
 
18041
            // Check whether a track of a different kind is showing
18042
            for (let i = 0, l = tracks.length; i < l; i++) {
18043
                const track = tracks[i];
18044
                if (track.kind !== this.kind_ && track.mode === 'showing') {
18045
                    disabled = true;
18046
                    break;
18047
                }
18048
            }
18049
 
18050
            // If another track is showing, disable this menu button
18051
            if (disabled) {
18052
                this.disable();
18053
            } else {
18054
                this.enable();
18055
            }
18056
        }
18057
 
18058
        /**
18059
         * Builds the default DOM `className`.
18060
         *
18061
         * @return {string}
18062
         *         The DOM `className` for this object.
18063
         */
18064
        buildCSSClass() {
18065
            return `vjs-descriptions-button ${super.buildCSSClass()}`;
18066
        }
18067
        buildWrapperCSSClass() {
18068
            return `vjs-descriptions-button ${super.buildWrapperCSSClass()}`;
18069
        }
18070
    }
18071
 
18072
    /**
18073
     * `kind` of TextTrack to look for to associate it with this menu.
18074
     *
18075
     * @type {string}
18076
     * @private
18077
     */
18078
    DescriptionsButton.prototype.kind_ = 'descriptions';
18079
 
18080
    /**
18081
     * The text that should display over the `DescriptionsButton`s controls. Added for localization.
18082
     *
18083
     * @type {string}
18084
     * @protected
18085
     */
18086
    DescriptionsButton.prototype.controlText_ = 'Descriptions';
18087
    Component$1.registerComponent('DescriptionsButton', DescriptionsButton);
18088
 
18089
    /**
18090
     * @file subtitles-button.js
18091
     */
18092
 
18093
    /**
18094
     * The button component for toggling and selecting subtitles
18095
     *
18096
     * @extends TextTrackButton
18097
     */
18098
    class SubtitlesButton extends TextTrackButton {
18099
        /**
18100
         * Creates an instance of this class.
18101
         *
18102
         * @param { import('../../player').default } player
18103
         *        The `Player` that this class should be attached to.
18104
         *
18105
         * @param {Object} [options]
18106
         *        The key/value store of player options.
18107
         *
18108
         * @param {Function} [ready]
18109
         *        The function to call when this component is ready.
18110
         */
18111
        constructor(player, options, ready) {
18112
            super(player, options, ready);
18113
            this.setIcon('subtitles');
18114
        }
18115
 
18116
        /**
18117
         * Builds the default DOM `className`.
18118
         *
18119
         * @return {string}
18120
         *         The DOM `className` for this object.
18121
         */
18122
        buildCSSClass() {
18123
            return `vjs-subtitles-button ${super.buildCSSClass()}`;
18124
        }
18125
        buildWrapperCSSClass() {
18126
            return `vjs-subtitles-button ${super.buildWrapperCSSClass()}`;
18127
        }
18128
    }
18129
 
18130
    /**
18131
     * `kind` of TextTrack to look for to associate it with this menu.
18132
     *
18133
     * @type {string}
18134
     * @private
18135
     */
18136
    SubtitlesButton.prototype.kind_ = 'subtitles';
18137
 
18138
    /**
18139
     * The text that should display over the `SubtitlesButton`s controls. Added for localization.
18140
     *
18141
     * @type {string}
18142
     * @protected
18143
     */
18144
    SubtitlesButton.prototype.controlText_ = 'Subtitles';
18145
    Component$1.registerComponent('SubtitlesButton', SubtitlesButton);
18146
 
18147
    /**
18148
     * @file caption-settings-menu-item.js
18149
     */
18150
 
18151
    /**
18152
     * The menu item for caption track settings menu
18153
     *
18154
     * @extends TextTrackMenuItem
18155
     */
18156
    class CaptionSettingsMenuItem extends TextTrackMenuItem {
18157
        /**
18158
         * Creates an instance of this class.
18159
         *
18160
         * @param { import('../../player').default } player
18161
         *        The `Player` that this class should be attached to.
18162
         *
18163
         * @param {Object} [options]
18164
         *        The key/value store of player options.
18165
         */
18166
        constructor(player, options) {
18167
            options.track = {
18168
                player,
18169
                kind: options.kind,
18170
                label: options.kind + ' settings',
18171
                selectable: false,
18172
                default: false,
18173
                mode: 'disabled'
18174
            };
18175
 
18176
            // CaptionSettingsMenuItem has no concept of 'selected'
18177
            options.selectable = false;
18178
            options.name = 'CaptionSettingsMenuItem';
18179
            super(player, options);
18180
            this.addClass('vjs-texttrack-settings');
18181
            this.controlText(', opens ' + options.kind + ' settings dialog');
18182
        }
18183
 
18184
        /**
18185
         * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
18186
         * {@link ClickableComponent} for more detailed information on what a click can be.
18187
         *
18188
         * @param {Event} [event]
18189
         *        The `keydown`, `tap`, or `click` event that caused this function to be
18190
         *        called.
18191
         *
18192
         * @listens tap
18193
         * @listens click
18194
         */
18195
        handleClick(event) {
18196
            this.player().getChild('textTrackSettings').open();
18197
        }
18198
 
18199
        /**
18200
         * Update control text and label on languagechange
18201
         */
18202
        handleLanguagechange() {
18203
            this.$('.vjs-menu-item-text').textContent = this.player_.localize(this.options_.kind + ' settings');
18204
            super.handleLanguagechange();
18205
        }
18206
    }
18207
    Component$1.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
18208
 
18209
    /**
18210
     * @file captions-button.js
18211
     */
18212
 
18213
    /**
18214
     * The button component for toggling and selecting captions
18215
     *
18216
     * @extends TextTrackButton
18217
     */
18218
    class CaptionsButton extends TextTrackButton {
18219
        /**
18220
         * Creates an instance of this class.
18221
         *
18222
         * @param { import('../../player').default } player
18223
         *        The `Player` that this class should be attached to.
18224
         *
18225
         * @param {Object} [options]
18226
         *        The key/value store of player options.
18227
         *
18228
         * @param {Function} [ready]
18229
         *        The function to call when this component is ready.
18230
         */
18231
        constructor(player, options, ready) {
18232
            super(player, options, ready);
18233
            this.setIcon('captions');
18234
        }
18235
 
18236
        /**
18237
         * Builds the default DOM `className`.
18238
         *
18239
         * @return {string}
18240
         *         The DOM `className` for this object.
18241
         */
18242
        buildCSSClass() {
18243
            return `vjs-captions-button ${super.buildCSSClass()}`;
18244
        }
18245
        buildWrapperCSSClass() {
18246
            return `vjs-captions-button ${super.buildWrapperCSSClass()}`;
18247
        }
18248
 
18249
        /**
18250
         * Create caption menu items
18251
         *
18252
         * @return {CaptionSettingsMenuItem[]}
18253
         *         The array of current menu items.
18254
         */
18255
        createItems() {
18256
            const items = [];
18257
            if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
18258
                items.push(new CaptionSettingsMenuItem(this.player_, {
18259
                    kind: this.kind_
18260
                }));
18261
                this.hideThreshold_ += 1;
18262
            }
18263
            return super.createItems(items);
18264
        }
18265
    }
18266
 
18267
    /**
18268
     * `kind` of TextTrack to look for to associate it with this menu.
18269
     *
18270
     * @type {string}
18271
     * @private
18272
     */
18273
    CaptionsButton.prototype.kind_ = 'captions';
18274
 
18275
    /**
18276
     * The text that should display over the `CaptionsButton`s controls. Added for localization.
18277
     *
18278
     * @type {string}
18279
     * @protected
18280
     */
18281
    CaptionsButton.prototype.controlText_ = 'Captions';
18282
    Component$1.registerComponent('CaptionsButton', CaptionsButton);
18283
 
18284
    /**
18285
     * @file subs-caps-menu-item.js
18286
     */
18287
 
18288
    /**
18289
     * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
18290
     * in the SubsCapsMenu.
18291
     *
18292
     * @extends TextTrackMenuItem
18293
     */
18294
    class SubsCapsMenuItem extends TextTrackMenuItem {
18295
        createEl(type, props, attrs) {
18296
            const el = super.createEl(type, props, attrs);
18297
            const parentSpan = el.querySelector('.vjs-menu-item-text');
18298
            if (this.options_.track.kind === 'captions') {
18299
                if (this.player_.options_.experimentalSvgIcons) {
18300
                    this.setIcon('captions', el);
18301
                } else {
18302
                    parentSpan.appendChild(createEl('span', {
18303
                        className: 'vjs-icon-placeholder'
18304
                    }, {
18305
                        'aria-hidden': true
18306
                    }));
18307
                }
18308
                parentSpan.appendChild(createEl('span', {
18309
                    className: 'vjs-control-text',
18310
                    // space added as the text will visually flow with the
18311
                    // label
18312
                    textContent: ` ${this.localize('Captions')}`
18313
                }));
18314
            }
18315
            return el;
18316
        }
18317
    }
18318
    Component$1.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
18319
 
18320
    /**
18321
     * @file sub-caps-button.js
18322
     */
18323
 
18324
    /**
18325
     * The button component for toggling and selecting captions and/or subtitles
18326
     *
18327
     * @extends TextTrackButton
18328
     */
18329
    class SubsCapsButton extends TextTrackButton {
18330
        /**
18331
         * Creates an instance of this class.
18332
         *
18333
         * @param { import('../../player').default } player
18334
         *        The `Player` that this class should be attached to.
18335
         *
18336
         * @param {Object} [options]
18337
         *        The key/value store of player options.
18338
         *
18339
         * @param {Function} [ready]
18340
         *        The function to call when this component is ready.
18341
         */
18342
        constructor(player, options = {}) {
18343
            super(player, options);
18344
 
18345
            // Although North America uses "captions" in most cases for
18346
            // "captions and subtitles" other locales use "subtitles"
18347
            this.label_ = 'subtitles';
18348
            this.setIcon('subtitles');
18349
            if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(this.player_.language_) > -1) {
18350
                this.label_ = 'captions';
18351
                this.setIcon('captions');
18352
            }
18353
            this.menuButton_.controlText(toTitleCase$1(this.label_));
18354
        }
18355
 
18356
        /**
18357
         * Builds the default DOM `className`.
18358
         *
18359
         * @return {string}
18360
         *         The DOM `className` for this object.
18361
         */
18362
        buildCSSClass() {
18363
            return `vjs-subs-caps-button ${super.buildCSSClass()}`;
18364
        }
18365
        buildWrapperCSSClass() {
18366
            return `vjs-subs-caps-button ${super.buildWrapperCSSClass()}`;
18367
        }
18368
 
18369
        /**
18370
         * Create caption/subtitles menu items
18371
         *
18372
         * @return {CaptionSettingsMenuItem[]}
18373
         *         The array of current menu items.
18374
         */
18375
        createItems() {
18376
            let items = [];
18377
            if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
18378
                items.push(new CaptionSettingsMenuItem(this.player_, {
18379
                    kind: this.label_
18380
                }));
18381
                this.hideThreshold_ += 1;
18382
            }
18383
            items = super.createItems(items, SubsCapsMenuItem);
18384
            return items;
18385
        }
18386
    }
18387
 
18388
    /**
18389
     * `kind`s of TextTrack to look for to associate it with this menu.
18390
     *
18391
     * @type {array}
18392
     * @private
18393
     */
18394
    SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
18395
 
18396
    /**
18397
     * The text that should display over the `SubsCapsButton`s controls.
18398
     *
18399
     *
18400
     * @type {string}
18401
     * @protected
18402
     */
18403
    SubsCapsButton.prototype.controlText_ = 'Subtitles';
18404
    Component$1.registerComponent('SubsCapsButton', SubsCapsButton);
18405
 
18406
    /**
18407
     * @file audio-track-menu-item.js
18408
     */
18409
 
18410
    /**
18411
     * An {@link AudioTrack} {@link MenuItem}
18412
     *
18413
     * @extends MenuItem
18414
     */
18415
    class AudioTrackMenuItem extends MenuItem {
18416
        /**
18417
         * Creates an instance of this class.
18418
         *
18419
         * @param { import('../../player').default } player
18420
         *        The `Player` that this class should be attached to.
18421
         *
18422
         * @param {Object} [options]
18423
         *        The key/value store of player options.
18424
         */
18425
        constructor(player, options) {
18426
            const track = options.track;
18427
            const tracks = player.audioTracks();
18428
 
18429
            // Modify options for parent MenuItem class's init.
18430
            options.label = track.label || track.language || 'Unknown';
18431
            options.selected = track.enabled;
18432
            super(player, options);
18433
            this.track = track;
18434
            this.addClass(`vjs-${track.kind}-menu-item`);
18435
            const changeHandler = (...args) => {
18436
                this.handleTracksChange.apply(this, args);
18437
            };
18438
            tracks.addEventListener('change', changeHandler);
18439
            this.on('dispose', () => {
18440
                tracks.removeEventListener('change', changeHandler);
18441
            });
18442
        }
18443
        createEl(type, props, attrs) {
18444
            const el = super.createEl(type, props, attrs);
18445
            const parentSpan = el.querySelector('.vjs-menu-item-text');
18446
            if (['main-desc', 'description'].indexOf(this.options_.track.kind) >= 0) {
18447
                parentSpan.appendChild(createEl('span', {
18448
                    className: 'vjs-icon-placeholder'
18449
                }, {
18450
                    'aria-hidden': true
18451
                }));
18452
                parentSpan.appendChild(createEl('span', {
18453
                    className: 'vjs-control-text',
18454
                    textContent: ' ' + this.localize('Descriptions')
18455
                }));
18456
            }
18457
            return el;
18458
        }
18459
 
18460
        /**
18461
         * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
18462
         * for more detailed information on what a click can be.
18463
         *
18464
         * @param {Event} [event]
18465
         *        The `keydown`, `tap`, or `click` event that caused this function to be
18466
         *        called.
18467
         *
18468
         * @listens tap
18469
         * @listens click
18470
         */
18471
        handleClick(event) {
18472
            super.handleClick(event);
18473
 
18474
            // the audio track list will automatically toggle other tracks
18475
            // off for us.
18476
            this.track.enabled = true;
18477
 
18478
            // when native audio tracks are used, we want to make sure that other tracks are turned off
18479
            if (this.player_.tech_.featuresNativeAudioTracks) {
18480
                const tracks = this.player_.audioTracks();
18481
                for (let i = 0; i < tracks.length; i++) {
18482
                    const track = tracks[i];
18483
 
18484
                    // skip the current track since we enabled it above
18485
                    if (track === this.track) {
18486
                        continue;
18487
                    }
18488
                    track.enabled = track === this.track;
18489
                }
18490
            }
18491
        }
18492
 
18493
        /**
18494
         * Handle any {@link AudioTrack} change.
18495
         *
18496
         * @param {Event} [event]
18497
         *        The {@link AudioTrackList#change} event that caused this to run.
18498
         *
18499
         * @listens AudioTrackList#change
18500
         */
18501
        handleTracksChange(event) {
18502
            this.selected(this.track.enabled);
18503
        }
18504
    }
18505
    Component$1.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
18506
 
18507
    /**
18508
     * @file audio-track-button.js
18509
     */
18510
 
18511
    /**
18512
     * The base class for buttons that toggle specific {@link AudioTrack} types.
18513
     *
18514
     * @extends TrackButton
18515
     */
18516
    class AudioTrackButton extends TrackButton {
18517
        /**
18518
         * Creates an instance of this class.
18519
         *
18520
         * @param {Player} player
18521
         *        The `Player` that this class should be attached to.
18522
         *
18523
         * @param {Object} [options={}]
18524
         *        The key/value store of player options.
18525
         */
18526
        constructor(player, options = {}) {
18527
            options.tracks = player.audioTracks();
18528
            super(player, options);
18529
            this.setIcon('audio');
18530
        }
18531
 
18532
        /**
18533
         * Builds the default DOM `className`.
18534
         *
18535
         * @return {string}
18536
         *         The DOM `className` for this object.
18537
         */
18538
        buildCSSClass() {
18539
            return `vjs-audio-button ${super.buildCSSClass()}`;
18540
        }
18541
        buildWrapperCSSClass() {
18542
            return `vjs-audio-button ${super.buildWrapperCSSClass()}`;
18543
        }
18544
 
18545
        /**
18546
         * Create a menu item for each audio track
18547
         *
18548
         * @param {AudioTrackMenuItem[]} [items=[]]
18549
         *        An array of existing menu items to use.
18550
         *
18551
         * @return {AudioTrackMenuItem[]}
18552
         *         An array of menu items
18553
         */
18554
        createItems(items = []) {
18555
            // if there's only one audio track, there no point in showing it
18556
            this.hideThreshold_ = 1;
18557
            const tracks = this.player_.audioTracks();
18558
            for (let i = 0; i < tracks.length; i++) {
18559
                const track = tracks[i];
18560
                items.push(new AudioTrackMenuItem(this.player_, {
18561
                    track,
18562
                    // MenuItem is selectable
18563
                    selectable: true,
18564
                    // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
18565
                    multiSelectable: false
18566
                }));
18567
            }
18568
            return items;
18569
        }
18570
    }
18571
 
18572
    /**
18573
     * The text that should display over the `AudioTrackButton`s controls. Added for localization.
18574
     *
18575
     * @type {string}
18576
     * @protected
18577
     */
18578
    AudioTrackButton.prototype.controlText_ = 'Audio Track';
18579
    Component$1.registerComponent('AudioTrackButton', AudioTrackButton);
18580
 
18581
    /**
18582
     * @file playback-rate-menu-item.js
18583
     */
18584
 
18585
    /**
18586
     * The specific menu item type for selecting a playback rate.
18587
     *
18588
     * @extends MenuItem
18589
     */
18590
    class PlaybackRateMenuItem extends MenuItem {
18591
        /**
18592
         * Creates an instance of this class.
18593
         *
18594
         * @param { import('../../player').default } player
18595
         *        The `Player` that this class should be attached to.
18596
         *
18597
         * @param {Object} [options]
18598
         *        The key/value store of player options.
18599
         */
18600
        constructor(player, options) {
18601
            const label = options.rate;
18602
            const rate = parseFloat(label, 10);
18603
 
18604
            // Modify options for parent MenuItem class's init.
18605
            options.label = label;
18606
            options.selected = rate === player.playbackRate();
18607
            options.selectable = true;
18608
            options.multiSelectable = false;
18609
            super(player, options);
18610
            this.label = label;
18611
            this.rate = rate;
18612
            this.on(player, 'ratechange', e => this.update(e));
18613
        }
18614
 
18615
        /**
18616
         * This gets called when an `PlaybackRateMenuItem` is "clicked". See
18617
         * {@link ClickableComponent} for more detailed information on what a click can be.
18618
         *
18619
         * @param {Event} [event]
18620
         *        The `keydown`, `tap`, or `click` event that caused this function to be
18621
         *        called.
18622
         *
18623
         * @listens tap
18624
         * @listens click
18625
         */
18626
        handleClick(event) {
18627
            super.handleClick();
18628
            this.player().playbackRate(this.rate);
18629
        }
18630
 
18631
        /**
18632
         * Update the PlaybackRateMenuItem when the playbackrate changes.
18633
         *
18634
         * @param {Event} [event]
18635
         *        The `ratechange` event that caused this function to run.
18636
         *
18637
         * @listens Player#ratechange
18638
         */
18639
        update(event) {
18640
            this.selected(this.player().playbackRate() === this.rate);
18641
        }
18642
    }
18643
 
18644
    /**
18645
     * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
18646
     *
18647
     * @type {string}
18648
     * @private
18649
     */
18650
    PlaybackRateMenuItem.prototype.contentElType = 'button';
18651
    Component$1.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
18652
 
18653
    /**
18654
     * @file playback-rate-menu-button.js
18655
     */
18656
 
18657
    /**
18658
     * The component for controlling the playback rate.
18659
     *
18660
     * @extends MenuButton
18661
     */
18662
    class PlaybackRateMenuButton extends MenuButton {
18663
        /**
18664
         * Creates an instance of this class.
18665
         *
18666
         * @param { import('../../player').default } player
18667
         *        The `Player` that this class should be attached to.
18668
         *
18669
         * @param {Object} [options]
18670
         *        The key/value store of player options.
18671
         */
18672
        constructor(player, options) {
18673
            super(player, options);
18674
            this.menuButton_.el_.setAttribute('aria-describedby', this.labelElId_);
18675
            this.updateVisibility();
18676
            this.updateLabel();
18677
            this.on(player, 'loadstart', e => this.updateVisibility(e));
18678
            this.on(player, 'ratechange', e => this.updateLabel(e));
18679
            this.on(player, 'playbackrateschange', e => this.handlePlaybackRateschange(e));
18680
        }
18681
 
18682
        /**
18683
         * Create the `Component`'s DOM element
18684
         *
18685
         * @return {Element}
18686
         *         The element that was created.
18687
         */
18688
        createEl() {
18689
            const el = super.createEl();
18690
            this.labelElId_ = 'vjs-playback-rate-value-label-' + this.id_;
18691
            this.labelEl_ = createEl('div', {
18692
                className: 'vjs-playback-rate-value',
18693
                id: this.labelElId_,
18694
                textContent: '1x'
18695
            });
18696
            el.appendChild(this.labelEl_);
18697
            return el;
18698
        }
18699
        dispose() {
18700
            this.labelEl_ = null;
18701
            super.dispose();
18702
        }
18703
 
18704
        /**
18705
         * Builds the default DOM `className`.
18706
         *
18707
         * @return {string}
18708
         *         The DOM `className` for this object.
18709
         */
18710
        buildCSSClass() {
18711
            return `vjs-playback-rate ${super.buildCSSClass()}`;
18712
        }
18713
        buildWrapperCSSClass() {
18714
            return `vjs-playback-rate ${super.buildWrapperCSSClass()}`;
18715
        }
18716
 
18717
        /**
18718
         * Create the list of menu items. Specific to each subclass.
18719
         *
18720
         */
18721
        createItems() {
18722
            const rates = this.playbackRates();
18723
            const items = [];
18724
            for (let i = rates.length - 1; i >= 0; i--) {
18725
                items.push(new PlaybackRateMenuItem(this.player(), {
18726
                    rate: rates[i] + 'x'
18727
                }));
18728
            }
18729
            return items;
18730
        }
18731
 
18732
        /**
18733
         * On playbackrateschange, update the menu to account for the new items.
18734
         *
18735
         * @listens Player#playbackrateschange
18736
         */
18737
        handlePlaybackRateschange(event) {
18738
            this.update();
18739
        }
18740
 
18741
        /**
18742
         * Get possible playback rates
18743
         *
18744
         * @return {Array}
18745
         *         All possible playback rates
18746
         */
18747
        playbackRates() {
18748
            const player = this.player();
18749
            return player.playbackRates && player.playbackRates() || [];
18750
        }
18751
 
18752
        /**
18753
         * Get whether playback rates is supported by the tech
18754
         * and an array of playback rates exists
18755
         *
18756
         * @return {boolean}
18757
         *         Whether changing playback rate is supported
18758
         */
18759
        playbackRateSupported() {
18760
            return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
18761
        }
18762
 
18763
        /**
18764
         * Hide playback rate controls when they're no playback rate options to select
18765
         *
18766
         * @param {Event} [event]
18767
         *        The event that caused this function to run.
18768
         *
18769
         * @listens Player#loadstart
18770
         */
18771
        updateVisibility(event) {
18772
            if (this.playbackRateSupported()) {
18773
                this.removeClass('vjs-hidden');
18774
            } else {
18775
                this.addClass('vjs-hidden');
18776
            }
18777
        }
18778
 
18779
        /**
18780
         * Update button label when rate changed
18781
         *
18782
         * @param {Event} [event]
18783
         *        The event that caused this function to run.
18784
         *
18785
         * @listens Player#ratechange
18786
         */
18787
        updateLabel(event) {
18788
            if (this.playbackRateSupported()) {
18789
                this.labelEl_.textContent = this.player().playbackRate() + 'x';
18790
            }
18791
        }
18792
    }
18793
 
18794
    /**
18795
     * The text that should display over the `PlaybackRateMenuButton`s controls.
18796
     *
18797
     * Added for localization.
18798
     *
18799
     * @type {string}
18800
     * @protected
18801
     */
18802
    PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
18803
    Component$1.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
18804
 
18805
    /**
18806
     * @file spacer.js
18807
     */
18808
 
18809
    /**
18810
     * Just an empty spacer element that can be used as an append point for plugins, etc.
18811
     * Also can be used to create space between elements when necessary.
18812
     *
18813
     * @extends Component
18814
     */
18815
    class Spacer extends Component$1 {
18816
        /**
18817
         * Builds the default DOM `className`.
18818
         *
18819
         * @return {string}
18820
         *         The DOM `className` for this object.
18821
         */
18822
        buildCSSClass() {
18823
            return `vjs-spacer ${super.buildCSSClass()}`;
18824
        }
18825
 
18826
        /**
18827
         * Create the `Component`'s DOM element
18828
         *
18829
         * @return {Element}
18830
         *         The element that was created.
18831
         */
18832
        createEl(tag = 'div', props = {}, attributes = {}) {
18833
            if (!props.className) {
18834
                props.className = this.buildCSSClass();
18835
            }
18836
            return super.createEl(tag, props, attributes);
18837
        }
18838
    }
18839
    Component$1.registerComponent('Spacer', Spacer);
18840
 
18841
    /**
18842
     * @file custom-control-spacer.js
18843
     */
18844
 
18845
    /**
18846
     * Spacer specifically meant to be used as an insertion point for new plugins, etc.
18847
     *
18848
     * @extends Spacer
18849
     */
18850
    class CustomControlSpacer extends Spacer {
18851
        /**
18852
         * Builds the default DOM `className`.
18853
         *
18854
         * @return {string}
18855
         *         The DOM `className` for this object.
18856
         */
18857
        buildCSSClass() {
18858
            return `vjs-custom-control-spacer ${super.buildCSSClass()}`;
18859
        }
18860
 
18861
        /**
18862
         * Create the `Component`'s DOM element
18863
         *
18864
         * @return {Element}
18865
         *         The element that was created.
18866
         */
18867
        createEl() {
18868
            return super.createEl('div', {
18869
                className: this.buildCSSClass(),
18870
                // No-flex/table-cell mode requires there be some content
18871
                // in the cell to fill the remaining space of the table.
18872
                textContent: '\u00a0'
18873
            });
18874
        }
18875
    }
18876
    Component$1.registerComponent('CustomControlSpacer', CustomControlSpacer);
18877
 
18878
    /**
18879
     * @file control-bar.js
18880
     */
18881
 
18882
    /**
18883
     * Container of main controls.
18884
     *
18885
     * @extends Component
18886
     */
18887
    class ControlBar extends Component$1 {
18888
        /**
18889
         * Create the `Component`'s DOM element
18890
         *
18891
         * @return {Element}
18892
         *         The element that was created.
18893
         */
18894
        createEl() {
18895
            return super.createEl('div', {
18896
                className: 'vjs-control-bar',
18897
                dir: 'ltr'
18898
            });
18899
        }
18900
    }
18901
 
18902
    /**
18903
     * Default options for `ControlBar`
18904
     *
18905
     * @type {Object}
18906
     * @private
18907
     */
18908
    ControlBar.prototype.options_ = {
18909
        children: ['playToggle', 'skipBackward', 'skipForward', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'pictureInPictureToggle', 'fullscreenToggle']
18910
    };
18911
    Component$1.registerComponent('ControlBar', ControlBar);
18912
 
18913
    /**
18914
     * @file error-display.js
18915
     */
18916
 
18917
    /**
18918
     * A display that indicates an error has occurred. This means that the video
18919
     * is unplayable.
18920
     *
18921
     * @extends ModalDialog
18922
     */
18923
    class ErrorDisplay extends ModalDialog {
18924
        /**
18925
         * Creates an instance of this class.
18926
         *
18927
         * @param  { import('./player').default } player
18928
         *         The `Player` that this class should be attached to.
18929
         *
18930
         * @param  {Object} [options]
18931
         *         The key/value store of player options.
18932
         */
18933
        constructor(player, options) {
18934
            super(player, options);
18935
            this.on(player, 'error', e => {
18936
                this.close();
18937
                this.open(e);
18938
            });
18939
        }
18940
 
18941
        /**
18942
         * Builds the default DOM `className`.
18943
         *
18944
         * @return {string}
18945
         *         The DOM `className` for this object.
18946
         *
18947
         * @deprecated Since version 5.
18948
         */
18949
        buildCSSClass() {
18950
            return `vjs-error-display ${super.buildCSSClass()}`;
18951
        }
18952
 
18953
        /**
18954
         * Gets the localized error message based on the `Player`s error.
18955
         *
18956
         * @return {string}
18957
         *         The `Player`s error message localized or an empty string.
18958
         */
18959
        content() {
18960
            const error = this.player().error();
18961
            return error ? this.localize(error.message) : '';
18962
        }
18963
    }
18964
 
18965
    /**
18966
     * The default options for an `ErrorDisplay`.
18967
     *
18968
     * @private
18969
     */
18970
    ErrorDisplay.prototype.options_ = Object.assign({}, ModalDialog.prototype.options_, {
18971
        pauseOnOpen: false,
18972
        fillAlways: true,
18973
        temporary: false,
18974
        uncloseable: true
18975
    });
18976
    Component$1.registerComponent('ErrorDisplay', ErrorDisplay);
18977
 
18978
    /**
18979
     * @file text-track-settings.js
18980
     */
18981
    const LOCAL_STORAGE_KEY$1 = 'vjs-text-track-settings';
18982
    const COLOR_BLACK = ['#000', 'Black'];
18983
    const COLOR_BLUE = ['#00F', 'Blue'];
18984
    const COLOR_CYAN = ['#0FF', 'Cyan'];
18985
    const COLOR_GREEN = ['#0F0', 'Green'];
18986
    const COLOR_MAGENTA = ['#F0F', 'Magenta'];
18987
    const COLOR_RED = ['#F00', 'Red'];
18988
    const COLOR_WHITE = ['#FFF', 'White'];
18989
    const COLOR_YELLOW = ['#FF0', 'Yellow'];
18990
    const OPACITY_OPAQUE = ['1', 'Opaque'];
18991
    const OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
18992
    const OPACITY_TRANS = ['0', 'Transparent'];
18993
 
18994
    // Configuration for the various <select> elements in the DOM of this component.
18995
    //
18996
    // Possible keys include:
18997
    //
18998
    // `default`:
18999
    //   The default option index. Only needs to be provided if not zero.
19000
    // `parser`:
19001
    //   A function which is used to parse the value from the selected option in
19002
    //   a customized way.
19003
    // `selector`:
19004
    //   The selector used to find the associated <select> element.
19005
    const selectConfigs = {
19006
        backgroundColor: {
19007
            selector: '.vjs-bg-color > select',
19008
            id: 'captions-background-color-%s',
19009
            label: 'Color',
19010
            options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
19011
        },
19012
        backgroundOpacity: {
19013
            selector: '.vjs-bg-opacity > select',
19014
            id: 'captions-background-opacity-%s',
19015
            label: 'Opacity',
19016
            options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
19017
        },
19018
        color: {
19019
            selector: '.vjs-text-color > select',
19020
            id: 'captions-foreground-color-%s',
19021
            label: 'Color',
19022
            options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
19023
        },
19024
        edgeStyle: {
19025
            selector: '.vjs-edge-style > select',
19026
            id: '%s',
19027
            label: 'Text Edge Style',
19028
            options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Drop shadow']]
19029
        },
19030
        fontFamily: {
19031
            selector: '.vjs-font-family > select',
19032
            id: 'captions-font-family-%s',
19033
            label: 'Font Family',
19034
            options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
19035
        },
19036
        fontPercent: {
19037
            selector: '.vjs-font-percent > select',
19038
            id: 'captions-font-size-%s',
19039
            label: 'Font Size',
19040
            options: [['0.50', '50%'], ['0.75', '75%'], ['1.00', '100%'], ['1.25', '125%'], ['1.50', '150%'], ['1.75', '175%'], ['2.00', '200%'], ['3.00', '300%'], ['4.00', '400%']],
19041
            default: 2,
19042
            parser: v => v === '1.00' ? null : Number(v)
19043
        },
19044
        textOpacity: {
19045
            selector: '.vjs-text-opacity > select',
19046
            id: 'captions-foreground-opacity-%s',
19047
            label: 'Opacity',
19048
            options: [OPACITY_OPAQUE, OPACITY_SEMI]
19049
        },
19050
        // Options for this object are defined below.
19051
        windowColor: {
19052
            selector: '.vjs-window-color > select',
19053
            id: 'captions-window-color-%s',
19054
            label: 'Color'
19055
        },
19056
        // Options for this object are defined below.
19057
        windowOpacity: {
19058
            selector: '.vjs-window-opacity > select',
19059
            id: 'captions-window-opacity-%s',
19060
            label: 'Opacity',
19061
            options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
19062
        }
19063
    };
19064
    selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
19065
 
19066
    /**
19067
     * Get the actual value of an option.
19068
     *
19069
     * @param  {string} value
19070
     *         The value to get
19071
     *
19072
     * @param  {Function} [parser]
19073
     *         Optional function to adjust the value.
19074
     *
19075
     * @return {*}
19076
     *         - Will be `undefined` if no value exists
19077
     *         - Will be `undefined` if the given value is "none".
19078
     *         - Will be the actual value otherwise.
19079
     *
19080
     * @private
19081
     */
19082
    function parseOptionValue(value, parser) {
19083
        if (parser) {
19084
            value = parser(value);
19085
        }
19086
        if (value && value !== 'none') {
19087
            return value;
19088
        }
19089
    }
19090
 
19091
    /**
19092
     * Gets the value of the selected <option> element within a <select> element.
19093
     *
19094
     * @param  {Element} el
19095
     *         the element to look in
19096
     *
19097
     * @param  {Function} [parser]
19098
     *         Optional function to adjust the value.
19099
     *
19100
     * @return {*}
19101
     *         - Will be `undefined` if no value exists
19102
     *         - Will be `undefined` if the given value is "none".
19103
     *         - Will be the actual value otherwise.
19104
     *
19105
     * @private
19106
     */
19107
    function getSelectedOptionValue(el, parser) {
19108
        const value = el.options[el.options.selectedIndex].value;
19109
        return parseOptionValue(value, parser);
19110
    }
19111
 
19112
    /**
19113
     * Sets the selected <option> element within a <select> element based on a
19114
     * given value.
19115
     *
19116
     * @param {Element} el
19117
     *        The element to look in.
19118
     *
19119
     * @param {string} value
19120
     *        the property to look on.
19121
     *
19122
     * @param {Function} [parser]
19123
     *        Optional function to adjust the value before comparing.
19124
     *
19125
     * @private
19126
     */
19127
    function setSelectedOption(el, value, parser) {
19128
        if (!value) {
19129
            return;
19130
        }
19131
        for (let i = 0; i < el.options.length; i++) {
19132
            if (parseOptionValue(el.options[i].value, parser) === value) {
19133
                el.selectedIndex = i;
19134
                break;
19135
            }
19136
        }
19137
    }
19138
 
19139
    /**
19140
     * Manipulate Text Tracks settings.
19141
     *
19142
     * @extends ModalDialog
19143
     */
19144
    class TextTrackSettings extends ModalDialog {
19145
        /**
19146
         * Creates an instance of this class.
19147
         *
19148
         * @param { import('../player').default } player
19149
         *         The `Player` that this class should be attached to.
19150
         *
19151
         * @param {Object} [options]
19152
         *         The key/value store of player options.
19153
         */
19154
        constructor(player, options) {
19155
            options.temporary = false;
19156
            super(player, options);
19157
            this.updateDisplay = this.updateDisplay.bind(this);
19158
 
19159
            // fill the modal and pretend we have opened it
19160
            this.fill();
19161
            this.hasBeenOpened_ = this.hasBeenFilled_ = true;
19162
            this.endDialog = createEl('p', {
19163
                className: 'vjs-control-text',
19164
                textContent: this.localize('End of dialog window.')
19165
            });
19166
            this.el().appendChild(this.endDialog);
19167
            this.setDefaults();
19168
 
19169
            // Grab `persistTextTrackSettings` from the player options if not passed in child options
19170
            if (options.persistTextTrackSettings === undefined) {
19171
                this.options_.persistTextTrackSettings = this.options_.playerOptions.persistTextTrackSettings;
19172
            }
19173
            this.on(this.$('.vjs-done-button'), 'click', () => {
19174
                this.saveSettings();
19175
                this.close();
19176
            });
19177
            this.on(this.$('.vjs-default-button'), 'click', () => {
19178
                this.setDefaults();
19179
                this.updateDisplay();
19180
            });
19181
            each(selectConfigs, config => {
19182
                this.on(this.$(config.selector), 'change', this.updateDisplay);
19183
            });
19184
            if (this.options_.persistTextTrackSettings) {
19185
                this.restoreSettings();
19186
            }
19187
        }
19188
        dispose() {
19189
            this.endDialog = null;
19190
            super.dispose();
19191
        }
19192
 
19193
        /**
19194
         * Create a <select> element with configured options.
19195
         *
19196
         * @param {string} key
19197
         *        Configuration key to use during creation.
19198
         *
19199
         * @param {string} [legendId]
19200
         *        Id of associated <legend>.
19201
         *
19202
         * @param {string} [type=label]
19203
         *        Type of labelling element, `label` or `legend`
19204
         *
19205
         * @return {string}
19206
         *         An HTML string.
19207
         *
19208
         * @private
19209
         */
19210
        createElSelect_(key, legendId = '', type = 'label') {
19211
            const config = selectConfigs[key];
19212
            const id = config.id.replace('%s', this.id_);
19213
            const selectLabelledbyIds = [legendId, id].join(' ').trim();
19214
            const guid = `vjs_select_${newGUID()}`;
19215
            return [`<${type} id="${id}"${type === 'label' ? ` for="${guid}" class="vjs-label"` : ''}>`, this.localize(config.label), `</${type}>`, `<select aria-labelledby="${selectLabelledbyIds}" id="${guid}">`].concat(config.options.map(o => {
19216
                const optionId = id + '-' + o[1].replace(/\W+/g, '');
19217
                return [`<option id="${optionId}" value="${o[0]}" `, `aria-labelledby="${selectLabelledbyIds} ${optionId}">`, this.localize(o[1]), '</option>'].join('');
19218
            })).concat('</select>').join('');
19219
        }
19220
 
19221
        /**
19222
         * Create foreground color element for the component
19223
         *
19224
         * @return {string}
19225
         *         An HTML string.
19226
         *
19227
         * @private
19228
         */
19229
        createElFgColor_() {
19230
            const legendId = `captions-text-legend-${this.id_}`;
19231
            return ['<fieldset class="vjs-fg vjs-track-setting">', `<legend id="${legendId}">`, this.localize('Text'), '</legend>', '<span class="vjs-text-color">', this.createElSelect_('color', legendId), '</span>', '<span class="vjs-text-opacity vjs-opacity">', this.createElSelect_('textOpacity', legendId), '</span>', '</fieldset>'].join('');
19232
        }
19233
 
19234
        /**
19235
         * Create background color element for the component
19236
         *
19237
         * @return {string}
19238
         *         An HTML string.
19239
         *
19240
         * @private
19241
         */
19242
        createElBgColor_() {
19243
            const legendId = `captions-background-${this.id_}`;
19244
            return ['<fieldset class="vjs-bg vjs-track-setting">', `<legend id="${legendId}">`, this.localize('Text Background'), '</legend>', '<span class="vjs-bg-color">', this.createElSelect_('backgroundColor', legendId), '</span>', '<span class="vjs-bg-opacity vjs-opacity">', this.createElSelect_('backgroundOpacity', legendId), '</span>', '</fieldset>'].join('');
19245
        }
19246
 
19247
        /**
19248
         * Create window color element for the component
19249
         *
19250
         * @return {string}
19251
         *         An HTML string.
19252
         *
19253
         * @private
19254
         */
19255
        createElWinColor_() {
19256
            const legendId = `captions-window-${this.id_}`;
19257
            return ['<fieldset class="vjs-window vjs-track-setting">', `<legend id="${legendId}">`, this.localize('Caption Area Background'), '</legend>', '<span class="vjs-window-color">', this.createElSelect_('windowColor', legendId), '</span>', '<span class="vjs-window-opacity vjs-opacity">', this.createElSelect_('windowOpacity', legendId), '</span>', '</fieldset>'].join('');
19258
        }
19259
 
19260
        /**
19261
         * Create color elements for the component
19262
         *
19263
         * @return {Element}
19264
         *         The element that was created
19265
         *
19266
         * @private
19267
         */
19268
        createElColors_() {
19269
            return createEl('div', {
19270
                className: 'vjs-track-settings-colors',
19271
                innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
19272
            });
19273
        }
19274
 
19275
        /**
19276
         * Create font elements for the component
19277
         *
19278
         * @return {Element}
19279
         *         The element that was created.
19280
         *
19281
         * @private
19282
         */
19283
        createElFont_() {
19284
            return createEl('div', {
19285
                className: 'vjs-track-settings-font',
19286
                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('')
19287
            });
19288
        }
19289
 
19290
        /**
19291
         * Create controls for the component
19292
         *
19293
         * @return {Element}
19294
         *         The element that was created.
19295
         *
19296
         * @private
19297
         */
19298
        createElControls_() {
19299
            const defaultsDescription = this.localize('restore all settings to the default values');
19300
            return createEl('div', {
19301
                className: 'vjs-track-settings-controls',
19302
                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('')
19303
            });
19304
        }
19305
        content() {
19306
            return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
19307
        }
19308
        label() {
19309
            return this.localize('Caption Settings Dialog');
19310
        }
19311
        description() {
19312
            return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
19313
        }
19314
        buildCSSClass() {
19315
            return super.buildCSSClass() + ' vjs-text-track-settings';
19316
        }
19317
 
19318
        /**
19319
         * Gets an object of text track settings (or null).
19320
         *
19321
         * @return {Object}
19322
         *         An object with config values parsed from the DOM or localStorage.
19323
         */
19324
        getValues() {
19325
            return reduce(selectConfigs, (accum, config, key) => {
19326
                const value = getSelectedOptionValue(this.$(config.selector), config.parser);
19327
                if (value !== undefined) {
19328
                    accum[key] = value;
19329
                }
19330
                return accum;
19331
            }, {});
19332
        }
19333
 
19334
        /**
19335
         * Sets text track settings from an object of values.
19336
         *
19337
         * @param {Object} values
19338
         *        An object with config values parsed from the DOM or localStorage.
19339
         */
19340
        setValues(values) {
19341
            each(selectConfigs, (config, key) => {
19342
                setSelectedOption(this.$(config.selector), values[key], config.parser);
19343
            });
19344
        }
19345
 
19346
        /**
19347
         * Sets all `<select>` elements to their default values.
19348
         */
19349
        setDefaults() {
19350
            each(selectConfigs, config => {
19351
                const index = config.hasOwnProperty('default') ? config.default : 0;
19352
                this.$(config.selector).selectedIndex = index;
19353
            });
19354
        }
19355
 
19356
        /**
19357
         * Restore texttrack settings from localStorage
19358
         */
19359
        restoreSettings() {
19360
            let values;
19361
            try {
19362
                values = JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_KEY$1));
19363
            } catch (err) {
19364
                log$1.warn(err);
19365
            }
19366
            if (values) {
19367
                this.setValues(values);
19368
            }
19369
        }
19370
 
19371
        /**
19372
         * Save text track settings to localStorage
19373
         */
19374
        saveSettings() {
19375
            if (!this.options_.persistTextTrackSettings) {
19376
                return;
19377
            }
19378
            const values = this.getValues();
19379
            try {
19380
                if (Object.keys(values).length) {
19381
                    window.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(values));
19382
                } else {
19383
                    window.localStorage.removeItem(LOCAL_STORAGE_KEY$1);
19384
                }
19385
            } catch (err) {
19386
                log$1.warn(err);
19387
            }
19388
        }
19389
 
19390
        /**
19391
         * Update display of text track settings
19392
         */
19393
        updateDisplay() {
19394
            const ttDisplay = this.player_.getChild('textTrackDisplay');
19395
            if (ttDisplay) {
19396
                ttDisplay.updateDisplay();
19397
            }
19398
        }
19399
 
19400
        /**
19401
         * conditionally blur the element and refocus the captions button
19402
         *
19403
         * @private
19404
         */
19405
        conditionalBlur_() {
19406
            this.previouslyActiveEl_ = null;
19407
            const cb = this.player_.controlBar;
19408
            const subsCapsBtn = cb && cb.subsCapsButton;
19409
            const ccBtn = cb && cb.captionsButton;
19410
            if (subsCapsBtn) {
19411
                subsCapsBtn.focus();
19412
            } else if (ccBtn) {
19413
                ccBtn.focus();
19414
            }
19415
        }
19416
 
19417
        /**
19418
         * Repopulate dialog with new localizations on languagechange
19419
         */
19420
        handleLanguagechange() {
19421
            this.fill();
19422
        }
19423
    }
19424
    Component$1.registerComponent('TextTrackSettings', TextTrackSettings);
19425
 
19426
    /**
19427
     * @file resize-manager.js
19428
     */
19429
 
19430
    /**
19431
     * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
19432
     *
19433
     * 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}.
19434
     *
19435
     * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
19436
     * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
19437
     *
19438
     * @example <caption>How to disable the resize manager</caption>
19439
     * const player = videojs('#vid', {
19440
     *   resizeManager: false
19441
     * });
19442
     *
19443
     * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
19444
     *
19445
     * @extends Component
19446
     */
19447
    class ResizeManager extends Component$1 {
19448
        /**
19449
         * Create the ResizeManager.
19450
         *
19451
         * @param {Object} player
19452
         *        The `Player` that this class should be attached to.
19453
         *
19454
         * @param {Object} [options]
19455
         *        The key/value store of ResizeManager options.
19456
         *
19457
         * @param {Object} [options.ResizeObserver]
19458
         *        A polyfill for ResizeObserver can be passed in here.
19459
         *        If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
19460
         */
19461
        constructor(player, options) {
19462
            let RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window.ResizeObserver;
19463
 
19464
            // if `null` was passed, we want to disable the ResizeObserver
19465
            if (options.ResizeObserver === null) {
19466
                RESIZE_OBSERVER_AVAILABLE = false;
19467
            }
19468
 
19469
            // Only create an element when ResizeObserver isn't available
19470
            const options_ = merge$2({
19471
                createEl: !RESIZE_OBSERVER_AVAILABLE,
19472
                reportTouchActivity: false
19473
            }, options);
19474
            super(player, options_);
19475
            this.ResizeObserver = options.ResizeObserver || window.ResizeObserver;
19476
            this.loadListener_ = null;
19477
            this.resizeObserver_ = null;
19478
            this.debouncedHandler_ = debounce(() => {
19479
                this.resizeHandler();
19480
            }, 100, false, this);
19481
            if (RESIZE_OBSERVER_AVAILABLE) {
19482
                this.resizeObserver_ = new this.ResizeObserver(this.debouncedHandler_);
19483
                this.resizeObserver_.observe(player.el());
19484
            } else {
19485
                this.loadListener_ = () => {
19486
                    if (!this.el_ || !this.el_.contentWindow) {
19487
                        return;
19488
                    }
19489
                    const debouncedHandler_ = this.debouncedHandler_;
19490
                    let unloadListener_ = this.unloadListener_ = function () {
19491
                        off(this, 'resize', debouncedHandler_);
19492
                        off(this, 'unload', unloadListener_);
19493
                        unloadListener_ = null;
19494
                    };
19495
 
19496
                    // safari and edge can unload the iframe before resizemanager dispose
19497
                    // we have to dispose of event handlers correctly before that happens
19498
                    on(this.el_.contentWindow, 'unload', unloadListener_);
19499
                    on(this.el_.contentWindow, 'resize', debouncedHandler_);
19500
                };
19501
                this.one('load', this.loadListener_);
19502
            }
19503
        }
19504
        createEl() {
19505
            return super.createEl('iframe', {
19506
                className: 'vjs-resize-manager',
19507
                tabIndex: -1,
19508
                title: this.localize('No content')
19509
            }, {
19510
                'aria-hidden': 'true'
19511
            });
19512
        }
19513
 
19514
        /**
19515
         * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
19516
         *
19517
         * @fires Player#playerresize
19518
         */
19519
        resizeHandler() {
19520
            /**
19521
             * Called when the player size has changed
19522
             *
19523
             * @event Player#playerresize
19524
             * @type {Event}
19525
             */
19526
            // make sure player is still around to trigger
19527
            // prevents this from causing an error after dispose
19528
            if (!this.player_ || !this.player_.trigger) {
19529
                return;
19530
            }
19531
            this.player_.trigger('playerresize');
19532
        }
19533
        dispose() {
19534
            if (this.debouncedHandler_) {
19535
                this.debouncedHandler_.cancel();
19536
            }
19537
            if (this.resizeObserver_) {
19538
                if (this.player_.el()) {
19539
                    this.resizeObserver_.unobserve(this.player_.el());
19540
                }
19541
                this.resizeObserver_.disconnect();
19542
            }
19543
            if (this.loadListener_) {
19544
                this.off('load', this.loadListener_);
19545
            }
19546
            if (this.el_ && this.el_.contentWindow && this.unloadListener_) {
19547
                this.unloadListener_.call(this.el_.contentWindow);
19548
            }
19549
            this.ResizeObserver = null;
19550
            this.resizeObserver = null;
19551
            this.debouncedHandler_ = null;
19552
            this.loadListener_ = null;
19553
            super.dispose();
19554
        }
19555
    }
19556
    Component$1.registerComponent('ResizeManager', ResizeManager);
19557
 
19558
    const defaults = {
19559
        trackingThreshold: 20,
19560
        liveTolerance: 15
19561
    };
19562
 
19563
    /*
19564
    track when we are at the live edge, and other helpers for live playback */
19565
 
19566
    /**
19567
     * A class for checking live current time and determining when the player
19568
     * is at or behind the live edge.
19569
     */
19570
    class LiveTracker extends Component$1 {
19571
        /**
19572
         * Creates an instance of this class.
19573
         *
19574
         * @param { import('./player').default } player
19575
         *        The `Player` that this class should be attached to.
19576
         *
19577
         * @param {Object} [options]
19578
         *        The key/value store of player options.
19579
         *
19580
         * @param {number} [options.trackingThreshold=20]
19581
         *        Number of seconds of live window (seekableEnd - seekableStart) that
19582
         *        media needs to have before the liveui will be shown.
19583
         *
19584
         * @param {number} [options.liveTolerance=15]
19585
         *        Number of seconds behind live that we have to be
19586
         *        before we will be considered non-live. Note that this will only
19587
         *        be used when playing at the live edge. This allows large seekable end
19588
         *        changes to not effect whether we are live or not.
19589
         */
19590
        constructor(player, options) {
19591
            // LiveTracker does not need an element
19592
            const options_ = merge$2(defaults, options, {
19593
                createEl: false
19594
            });
19595
            super(player, options_);
19596
            this.trackLiveHandler_ = () => this.trackLive_();
19597
            this.handlePlay_ = e => this.handlePlay(e);
19598
            this.handleFirstTimeupdate_ = e => this.handleFirstTimeupdate(e);
19599
            this.handleSeeked_ = e => this.handleSeeked(e);
19600
            this.seekToLiveEdge_ = e => this.seekToLiveEdge(e);
19601
            this.reset_();
19602
            this.on(this.player_, 'durationchange', e => this.handleDurationchange(e));
19603
            // we should try to toggle tracking on canplay as native playback engines, like Safari
19604
            // may not have the proper values for things like seekableEnd until then
19605
            this.on(this.player_, 'canplay', () => this.toggleTracking());
19606
        }
19607
 
19608
        /**
19609
         * all the functionality for tracking when seek end changes
19610
         * and for tracking how far past seek end we should be
19611
         */
19612
        trackLive_() {
19613
            const seekable = this.player_.seekable();
19614
 
19615
            // skip undefined seekable
19616
            if (!seekable || !seekable.length) {
19617
                return;
19618
            }
19619
            const newTime = Number(window.performance.now().toFixed(4));
19620
            const deltaTime = this.lastTime_ === -1 ? 0 : (newTime - this.lastTime_) / 1000;
19621
            this.lastTime_ = newTime;
19622
            this.pastSeekEnd_ = this.pastSeekEnd() + deltaTime;
19623
            const liveCurrentTime = this.liveCurrentTime();
19624
            const currentTime = this.player_.currentTime();
19625
 
19626
            // we are behind live if any are true
19627
            // 1. the player is paused
19628
            // 2. the user seeked to a location 2 seconds away from live
19629
            // 3. the difference between live and current time is greater
19630
            //    liveTolerance which defaults to 15s
19631
            let isBehind = this.player_.paused() || this.seekedBehindLive_ || Math.abs(liveCurrentTime - currentTime) > this.options_.liveTolerance;
19632
 
19633
            // we cannot be behind if
19634
            // 1. until we have not seen a timeupdate yet
19635
            // 2. liveCurrentTime is Infinity, which happens on Android and Native Safari
19636
            if (!this.timeupdateSeen_ || liveCurrentTime === Infinity) {
19637
                isBehind = false;
19638
            }
19639
            if (isBehind !== this.behindLiveEdge_) {
19640
                this.behindLiveEdge_ = isBehind;
19641
                this.trigger('liveedgechange');
19642
            }
19643
        }
19644
 
19645
        /**
19646
         * handle a durationchange event on the player
19647
         * and start/stop tracking accordingly.
19648
         */
19649
        handleDurationchange() {
19650
            this.toggleTracking();
19651
        }
19652
 
19653
        /**
19654
         * start/stop tracking
19655
         */
19656
        toggleTracking() {
19657
            if (this.player_.duration() === Infinity && this.liveWindow() >= this.options_.trackingThreshold) {
19658
                if (this.player_.options_.liveui) {
19659
                    this.player_.addClass('vjs-liveui');
19660
                }
19661
                this.startTracking();
19662
            } else {
19663
                this.player_.removeClass('vjs-liveui');
19664
                this.stopTracking();
19665
            }
19666
        }
19667
 
19668
        /**
19669
         * start tracking live playback
19670
         */
19671
        startTracking() {
19672
            if (this.isTracking()) {
19673
                return;
19674
            }
19675
 
19676
            // If we haven't seen a timeupdate, we need to check whether playback
19677
            // began before this component started tracking. This can happen commonly
19678
            // when using autoplay.
19679
            if (!this.timeupdateSeen_) {
19680
                this.timeupdateSeen_ = this.player_.hasStarted();
19681
            }
19682
            this.trackingInterval_ = this.setInterval(this.trackLiveHandler_, UPDATE_REFRESH_INTERVAL);
19683
            this.trackLive_();
19684
            this.on(this.player_, ['play', 'pause'], this.trackLiveHandler_);
19685
            if (!this.timeupdateSeen_) {
19686
                this.one(this.player_, 'play', this.handlePlay_);
19687
                this.one(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
19688
            } else {
19689
                this.on(this.player_, 'seeked', this.handleSeeked_);
19690
            }
19691
        }
19692
 
19693
        /**
19694
         * handle the first timeupdate on the player if it wasn't already playing
19695
         * when live tracker started tracking.
19696
         */
19697
        handleFirstTimeupdate() {
19698
            this.timeupdateSeen_ = true;
19699
            this.on(this.player_, 'seeked', this.handleSeeked_);
19700
        }
19701
 
19702
        /**
19703
         * Keep track of what time a seek starts, and listen for seeked
19704
         * to find where a seek ends.
19705
         */
19706
        handleSeeked() {
19707
            const timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime());
19708
            this.seekedBehindLive_ = this.nextSeekedFromUser_ && timeDiff > 2;
19709
            this.nextSeekedFromUser_ = false;
19710
            this.trackLive_();
19711
        }
19712
 
19713
        /**
19714
         * handle the first play on the player, and make sure that we seek
19715
         * right to the live edge.
19716
         */
19717
        handlePlay() {
19718
            this.one(this.player_, 'timeupdate', this.seekToLiveEdge_);
19719
        }
19720
 
19721
        /**
19722
         * Stop tracking, and set all internal variables to
19723
         * their initial value.
19724
         */
19725
        reset_() {
19726
            this.lastTime_ = -1;
19727
            this.pastSeekEnd_ = 0;
19728
            this.lastSeekEnd_ = -1;
19729
            this.behindLiveEdge_ = true;
19730
            this.timeupdateSeen_ = false;
19731
            this.seekedBehindLive_ = false;
19732
            this.nextSeekedFromUser_ = false;
19733
            this.clearInterval(this.trackingInterval_);
19734
            this.trackingInterval_ = null;
19735
            this.off(this.player_, ['play', 'pause'], this.trackLiveHandler_);
19736
            this.off(this.player_, 'seeked', this.handleSeeked_);
19737
            this.off(this.player_, 'play', this.handlePlay_);
19738
            this.off(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
19739
            this.off(this.player_, 'timeupdate', this.seekToLiveEdge_);
19740
        }
19741
 
19742
        /**
19743
         * The next seeked event is from the user. Meaning that any seek
19744
         * > 2s behind live will be considered behind live for real and
19745
         * liveTolerance will be ignored.
19746
         */
19747
        nextSeekedFromUser() {
19748
            this.nextSeekedFromUser_ = true;
19749
        }
19750
 
19751
        /**
19752
         * stop tracking live playback
19753
         */
19754
        stopTracking() {
19755
            if (!this.isTracking()) {
19756
                return;
19757
            }
19758
            this.reset_();
19759
            this.trigger('liveedgechange');
19760
        }
19761
 
19762
        /**
19763
         * A helper to get the player seekable end
19764
         * so that we don't have to null check everywhere
19765
         *
19766
         * @return {number}
19767
         *         The furthest seekable end or Infinity.
19768
         */
19769
        seekableEnd() {
19770
            const seekable = this.player_.seekable();
19771
            const seekableEnds = [];
19772
            let i = seekable ? seekable.length : 0;
19773
            while (i--) {
19774
                seekableEnds.push(seekable.end(i));
19775
            }
19776
 
19777
            // grab the furthest seekable end after sorting, or if there are none
19778
            // default to Infinity
19779
            return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity;
19780
        }
19781
 
19782
        /**
19783
         * A helper to get the player seekable start
19784
         * so that we don't have to null check everywhere
19785
         *
19786
         * @return {number}
19787
         *         The earliest seekable start or 0.
19788
         */
19789
        seekableStart() {
19790
            const seekable = this.player_.seekable();
19791
            const seekableStarts = [];
19792
            let i = seekable ? seekable.length : 0;
19793
            while (i--) {
19794
                seekableStarts.push(seekable.start(i));
19795
            }
19796
 
19797
            // grab the first seekable start after sorting, or if there are none
19798
            // default to 0
19799
            return seekableStarts.length ? seekableStarts.sort()[0] : 0;
19800
        }
19801
 
19802
        /**
19803
         * Get the live time window aka
19804
         * the amount of time between seekable start and
19805
         * live current time.
19806
         *
19807
         * @return {number}
19808
         *         The amount of seconds that are seekable in
19809
         *         the live video.
19810
         */
19811
        liveWindow() {
19812
            const liveCurrentTime = this.liveCurrentTime();
19813
 
19814
            // if liveCurrenTime is Infinity then we don't have a liveWindow at all
19815
            if (liveCurrentTime === Infinity) {
19816
                return 0;
19817
            }
19818
            return liveCurrentTime - this.seekableStart();
19819
        }
19820
 
19821
        /**
19822
         * Determines if the player is live, only checks if this component
19823
         * is tracking live playback or not
19824
         *
19825
         * @return {boolean}
19826
         *         Whether liveTracker is tracking
19827
         */
19828
        isLive() {
19829
            return this.isTracking();
19830
        }
19831
 
19832
        /**
19833
         * Determines if currentTime is at the live edge and won't fall behind
19834
         * on each seekableendchange
19835
         *
19836
         * @return {boolean}
19837
         *         Whether playback is at the live edge
19838
         */
19839
        atLiveEdge() {
19840
            return !this.behindLiveEdge();
19841
        }
19842
 
19843
        /**
19844
         * get what we expect the live current time to be
19845
         *
19846
         * @return {number}
19847
         *         The expected live current time
19848
         */
19849
        liveCurrentTime() {
19850
            return this.pastSeekEnd() + this.seekableEnd();
19851
        }
19852
 
19853
        /**
19854
         * The number of seconds that have occurred after seekable end
19855
         * changed. This will be reset to 0 once seekable end changes.
19856
         *
19857
         * @return {number}
19858
         *         Seconds past the current seekable end
19859
         */
19860
        pastSeekEnd() {
19861
            const seekableEnd = this.seekableEnd();
19862
            if (this.lastSeekEnd_ !== -1 && seekableEnd !== this.lastSeekEnd_) {
19863
                this.pastSeekEnd_ = 0;
19864
            }
19865
            this.lastSeekEnd_ = seekableEnd;
19866
            return this.pastSeekEnd_;
19867
        }
19868
 
19869
        /**
19870
         * If we are currently behind the live edge, aka currentTime will be
19871
         * behind on a seekableendchange
19872
         *
19873
         * @return {boolean}
19874
         *         If we are behind the live edge
19875
         */
19876
        behindLiveEdge() {
19877
            return this.behindLiveEdge_;
19878
        }
19879
 
19880
        /**
19881
         * Whether live tracker is currently tracking or not.
19882
         */
19883
        isTracking() {
19884
            return typeof this.trackingInterval_ === 'number';
19885
        }
19886
 
19887
        /**
19888
         * Seek to the live edge if we are behind the live edge
19889
         */
19890
        seekToLiveEdge() {
19891
            this.seekedBehindLive_ = false;
19892
            if (this.atLiveEdge()) {
19893
                return;
19894
            }
19895
            this.nextSeekedFromUser_ = false;
19896
            this.player_.currentTime(this.liveCurrentTime());
19897
        }
19898
 
19899
        /**
19900
         * Dispose of liveTracker
19901
         */
19902
        dispose() {
19903
            this.stopTracking();
19904
            super.dispose();
19905
        }
19906
    }
19907
    Component$1.registerComponent('LiveTracker', LiveTracker);
19908
 
19909
    /**
19910
     * Displays an element over the player which contains an optional title and
19911
     * description for the current content.
19912
     *
19913
     * Much of the code for this component originated in the now obsolete
19914
     * videojs-dock plugin: https://github.com/brightcove/videojs-dock/
19915
     *
19916
     * @extends Component
19917
     */
19918
    class TitleBar extends Component$1 {
19919
        constructor(player, options) {
19920
            super(player, options);
19921
            this.on('statechanged', e => this.updateDom_());
19922
            this.updateDom_();
19923
        }
19924
 
19925
        /**
19926
         * Create the `TitleBar`'s DOM element
19927
         *
19928
         * @return {Element}
19929
         *         The element that was created.
19930
         */
19931
        createEl() {
19932
            this.els = {
19933
                title: createEl('div', {
19934
                    className: 'vjs-title-bar-title',
19935
                    id: `vjs-title-bar-title-${newGUID()}`
19936
                }),
19937
                description: createEl('div', {
19938
                    className: 'vjs-title-bar-description',
19939
                    id: `vjs-title-bar-description-${newGUID()}`
19940
                })
19941
            };
19942
            return createEl('div', {
19943
                className: 'vjs-title-bar'
19944
            }, {}, values$1(this.els));
19945
        }
19946
 
19947
        /**
19948
         * Updates the DOM based on the component's state object.
19949
         */
19950
        updateDom_() {
19951
            const tech = this.player_.tech_;
19952
            const techEl = tech && tech.el_;
19953
            const techAriaAttrs = {
19954
                title: 'aria-labelledby',
19955
                description: 'aria-describedby'
19956
            };
19957
            ['title', 'description'].forEach(k => {
19958
                const value = this.state[k];
19959
                const el = this.els[k];
19960
                const techAriaAttr = techAriaAttrs[k];
19961
                emptyEl(el);
19962
                if (value) {
19963
                    textContent(el, value);
19964
                }
19965
 
19966
                // If there is a tech element available, update its ARIA attributes
19967
                // according to whether a title and/or description have been provided.
19968
                if (techEl) {
19969
                    techEl.removeAttribute(techAriaAttr);
19970
                    if (value) {
19971
                        techEl.setAttribute(techAriaAttr, el.id);
19972
                    }
19973
                }
19974
            });
19975
            if (this.state.title || this.state.description) {
19976
                this.show();
19977
            } else {
19978
                this.hide();
19979
            }
19980
        }
19981
 
19982
        /**
19983
         * Update the contents of the title bar component with new title and
19984
         * description text.
19985
         *
19986
         * If both title and description are missing, the title bar will be hidden.
19987
         *
19988
         * If either title or description are present, the title bar will be visible.
19989
         *
19990
         * NOTE: Any previously set value will be preserved. To unset a previously
19991
         * set value, you must pass an empty string or null.
19992
         *
19993
         * For example:
19994
         *
19995
         * ```
19996
         * update({title: 'foo', description: 'bar'}) // title: 'foo', description: 'bar'
19997
         * update({description: 'bar2'}) // title: 'foo', description: 'bar2'
19998
         * update({title: ''}) // title: '', description: 'bar2'
19999
         * update({title: 'foo', description: null}) // title: 'foo', description: null
20000
         * ```
20001
         *
20002
         * @param  {Object} [options={}]
20003
         *         An options object. When empty, the title bar will be hidden.
20004
         *
20005
         * @param  {string} [options.title]
20006
         *         A title to display in the title bar.
20007
         *
20008
         * @param  {string} [options.description]
20009
         *         A description to display in the title bar.
20010
         */
20011
        update(options) {
20012
            this.setState(options);
20013
        }
20014
 
20015
        /**
20016
         * Dispose the component.
20017
         */
20018
        dispose() {
20019
            const tech = this.player_.tech_;
20020
            const techEl = tech && tech.el_;
20021
            if (techEl) {
20022
                techEl.removeAttribute('aria-labelledby');
20023
                techEl.removeAttribute('aria-describedby');
20024
            }
20025
            super.dispose();
20026
            this.els = null;
20027
        }
20028
    }
20029
    Component$1.registerComponent('TitleBar', TitleBar);
20030
 
20031
    /**
20032
     * This function is used to fire a sourceset when there is something
20033
     * similar to `mediaEl.load()` being called. It will try to find the source via
20034
     * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
20035
     * with the source that was found or empty string if we cannot know. If it cannot
20036
     * find a source then `sourceset` will not be fired.
20037
     *
20038
     * @param { import('./html5').default } tech
20039
     *        The tech object that sourceset was setup on
20040
     *
20041
     * @return {boolean}
20042
     *         returns false if the sourceset was not fired and true otherwise.
20043
     */
20044
    const sourcesetLoad = tech => {
20045
        const el = tech.el();
20046
 
20047
        // if `el.src` is set, that source will be loaded.
20048
        if (el.hasAttribute('src')) {
20049
            tech.triggerSourceset(el.src);
20050
            return true;
20051
        }
20052
 
20053
        /**
20054
         * Since there isn't a src property on the media element, source elements will be used for
20055
         * implementing the source selection algorithm. This happens asynchronously and
20056
         * for most cases were there is more than one source we cannot tell what source will
20057
         * be loaded, without re-implementing the source selection algorithm. At this time we are not
20058
         * going to do that. There are three special cases that we do handle here though:
20059
         *
20060
         * 1. If there are no sources, do not fire `sourceset`.
20061
         * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
20062
         * 3. If there is more than one `<source>` but all of them have the same `src` url.
20063
         *    That will be our src.
20064
         */
20065
        const sources = tech.$$('source');
20066
        const srcUrls = [];
20067
        let src = '';
20068
 
20069
        // if there are no sources, do not fire sourceset
20070
        if (!sources.length) {
20071
            return false;
20072
        }
20073
 
20074
        // only count valid/non-duplicate source elements
20075
        for (let i = 0; i < sources.length; i++) {
20076
            const url = sources[i].src;
20077
            if (url && srcUrls.indexOf(url) === -1) {
20078
                srcUrls.push(url);
20079
            }
20080
        }
20081
 
20082
        // there were no valid sources
20083
        if (!srcUrls.length) {
20084
            return false;
20085
        }
20086
 
20087
        // there is only one valid source element url
20088
        // use that
20089
        if (srcUrls.length === 1) {
20090
            src = srcUrls[0];
20091
        }
20092
        tech.triggerSourceset(src);
20093
        return true;
20094
    };
20095
 
20096
    /**
20097
     * our implementation of an `innerHTML` descriptor for browsers
20098
     * that do not have one.
20099
     */
20100
    const innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
20101
        get() {
20102
            return this.cloneNode(true).innerHTML;
20103
        },
20104
        set(v) {
20105
            // make a dummy node to use innerHTML on
20106
            const dummy = document.createElement(this.nodeName.toLowerCase());
20107
 
20108
            // set innerHTML to the value provided
20109
            dummy.innerHTML = v;
20110
 
20111
            // make a document fragment to hold the nodes from dummy
20112
            const docFrag = document.createDocumentFragment();
20113
 
20114
            // copy all of the nodes created by the innerHTML on dummy
20115
            // to the document fragment
20116
            while (dummy.childNodes.length) {
20117
                docFrag.appendChild(dummy.childNodes[0]);
20118
            }
20119
 
20120
            // remove content
20121
            this.innerText = '';
20122
 
20123
            // now we add all of that html in one by appending the
20124
            // document fragment. This is how innerHTML does it.
20125
            window.Element.prototype.appendChild.call(this, docFrag);
20126
 
20127
            // then return the result that innerHTML's setter would
20128
            return this.innerHTML;
20129
        }
20130
    });
20131
 
20132
    /**
20133
     * Get a property descriptor given a list of priorities and the
20134
     * property to get.
20135
     */
20136
    const getDescriptor = (priority, prop) => {
20137
        let descriptor = {};
20138
        for (let i = 0; i < priority.length; i++) {
20139
            descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
20140
            if (descriptor && descriptor.set && descriptor.get) {
20141
                break;
20142
            }
20143
        }
20144
        descriptor.enumerable = true;
20145
        descriptor.configurable = true;
20146
        return descriptor;
20147
    };
20148
    const getInnerHTMLDescriptor = tech => getDescriptor([tech.el(), window.HTMLMediaElement.prototype, window.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
20149
 
20150
    /**
20151
     * Patches browser internal functions so that we can tell synchronously
20152
     * if a `<source>` was appended to the media element. For some reason this
20153
     * causes a `sourceset` if the the media element is ready and has no source.
20154
     * This happens when:
20155
     * - The page has just loaded and the media element does not have a source.
20156
     * - The media element was emptied of all sources, then `load()` was called.
20157
     *
20158
     * It does this by patching the following functions/properties when they are supported:
20159
     *
20160
     * - `append()` - can be used to add a `<source>` element to the media element
20161
     * - `appendChild()` - can be used to add a `<source>` element to the media element
20162
     * - `insertAdjacentHTML()` -  can be used to add a `<source>` element to the media element
20163
     * - `innerHTML` -  can be used to add a `<source>` element to the media element
20164
     *
20165
     * @param {Html5} tech
20166
     *        The tech object that sourceset is being setup on.
20167
     */
20168
    const firstSourceWatch = function (tech) {
20169
        const el = tech.el();
20170
 
20171
        // make sure firstSourceWatch isn't setup twice.
20172
        if (el.resetSourceWatch_) {
20173
            return;
20174
        }
20175
        const old = {};
20176
        const innerDescriptor = getInnerHTMLDescriptor(tech);
20177
        const appendWrapper = appendFn => (...args) => {
20178
            const retval = appendFn.apply(el, args);
20179
            sourcesetLoad(tech);
20180
            return retval;
20181
        };
20182
        ['append', 'appendChild', 'insertAdjacentHTML'].forEach(k => {
20183
            if (!el[k]) {
20184
                return;
20185
            }
20186
 
20187
            // store the old function
20188
            old[k] = el[k];
20189
 
20190
            // call the old function with a sourceset if a source
20191
            // was loaded
20192
            el[k] = appendWrapper(old[k]);
20193
        });
20194
        Object.defineProperty(el, 'innerHTML', merge$2(innerDescriptor, {
20195
            set: appendWrapper(innerDescriptor.set)
20196
        }));
20197
        el.resetSourceWatch_ = () => {
20198
            el.resetSourceWatch_ = null;
20199
            Object.keys(old).forEach(k => {
20200
                el[k] = old[k];
20201
            });
20202
            Object.defineProperty(el, 'innerHTML', innerDescriptor);
20203
        };
20204
 
20205
        // on the first sourceset, we need to revert our changes
20206
        tech.one('sourceset', el.resetSourceWatch_);
20207
    };
20208
 
20209
    /**
20210
     * our implementation of a `src` descriptor for browsers
20211
     * that do not have one
20212
     */
20213
    const srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
20214
        get() {
20215
            if (this.hasAttribute('src')) {
20216
                return getAbsoluteURL(window.Element.prototype.getAttribute.call(this, 'src'));
20217
            }
20218
            return '';
20219
        },
20220
        set(v) {
20221
            window.Element.prototype.setAttribute.call(this, 'src', v);
20222
            return v;
20223
        }
20224
    });
20225
    const getSrcDescriptor = tech => getDescriptor([tech.el(), window.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
20226
 
20227
    /**
20228
     * setup `sourceset` handling on the `Html5` tech. This function
20229
     * patches the following element properties/functions:
20230
     *
20231
     * - `src` - to determine when `src` is set
20232
     * - `setAttribute()` - to determine when `src` is set
20233
     * - `load()` - this re-triggers the source selection algorithm, and can
20234
     *              cause a sourceset.
20235
     *
20236
     * If there is no source when we are adding `sourceset` support or during a `load()`
20237
     * we also patch the functions listed in `firstSourceWatch`.
20238
     *
20239
     * @param {Html5} tech
20240
     *        The tech to patch
20241
     */
20242
    const setupSourceset = function (tech) {
20243
        if (!tech.featuresSourceset) {
20244
            return;
20245
        }
20246
        const el = tech.el();
20247
 
20248
        // make sure sourceset isn't setup twice.
20249
        if (el.resetSourceset_) {
20250
            return;
20251
        }
20252
        const srcDescriptor = getSrcDescriptor(tech);
20253
        const oldSetAttribute = el.setAttribute;
20254
        const oldLoad = el.load;
20255
        Object.defineProperty(el, 'src', merge$2(srcDescriptor, {
20256
            set: v => {
20257
                const retval = srcDescriptor.set.call(el, v);
20258
 
20259
                // we use the getter here to get the actual value set on src
20260
                tech.triggerSourceset(el.src);
20261
                return retval;
20262
            }
20263
        }));
20264
        el.setAttribute = (n, v) => {
20265
            const retval = oldSetAttribute.call(el, n, v);
20266
            if (/src/i.test(n)) {
20267
                tech.triggerSourceset(el.src);
20268
            }
20269
            return retval;
20270
        };
20271
        el.load = () => {
20272
            const retval = oldLoad.call(el);
20273
 
20274
            // if load was called, but there was no source to fire
20275
            // sourceset on. We have to watch for a source append
20276
            // as that can trigger a `sourceset` when the media element
20277
            // has no source
20278
            if (!sourcesetLoad(tech)) {
20279
                tech.triggerSourceset('');
20280
                firstSourceWatch(tech);
20281
            }
20282
            return retval;
20283
        };
20284
        if (el.currentSrc) {
20285
            tech.triggerSourceset(el.currentSrc);
20286
        } else if (!sourcesetLoad(tech)) {
20287
            firstSourceWatch(tech);
20288
        }
20289
        el.resetSourceset_ = () => {
20290
            el.resetSourceset_ = null;
20291
            el.load = oldLoad;
20292
            el.setAttribute = oldSetAttribute;
20293
            Object.defineProperty(el, 'src', srcDescriptor);
20294
            if (el.resetSourceWatch_) {
20295
                el.resetSourceWatch_();
20296
            }
20297
        };
20298
    };
20299
 
20300
    /**
20301
     * @file html5.js
20302
     */
20303
 
20304
    /**
20305
     * HTML5 Media Controller - Wrapper for HTML5 Media API
20306
     *
20307
     * @mixes Tech~SourceHandlerAdditions
20308
     * @extends Tech
20309
     */
20310
    class Html5 extends Tech {
20311
        /**
20312
         * Create an instance of this Tech.
20313
         *
20314
         * @param {Object} [options]
20315
         *        The key/value store of player options.
20316
         *
20317
         * @param {Function} [ready]
20318
         *        Callback function to call when the `HTML5` Tech is ready.
20319
         */
20320
        constructor(options, ready) {
20321
            super(options, ready);
20322
            const source = options.source;
20323
            let crossoriginTracks = false;
20324
            this.featuresVideoFrameCallback = this.featuresVideoFrameCallback && this.el_.tagName === 'VIDEO';
20325
 
20326
            // Set the source if one is provided
20327
            // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
20328
            // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
20329
            // anyway so the error gets fired.
20330
            if (source && (this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
20331
                this.setSource(source);
20332
            } else {
20333
                this.handleLateInit_(this.el_);
20334
            }
20335
 
20336
            // setup sourceset after late sourceset/init
20337
            if (options.enableSourceset) {
20338
                this.setupSourcesetHandling_();
20339
            }
20340
            this.isScrubbing_ = false;
20341
            if (this.el_.hasChildNodes()) {
20342
                const nodes = this.el_.childNodes;
20343
                let nodesLength = nodes.length;
20344
                const removeNodes = [];
20345
                while (nodesLength--) {
20346
                    const node = nodes[nodesLength];
20347
                    const nodeName = node.nodeName.toLowerCase();
20348
                    if (nodeName === 'track') {
20349
                        if (!this.featuresNativeTextTracks) {
20350
                            // Empty video tag tracks so the built-in player doesn't use them also.
20351
                            // This may not be fast enough to stop HTML5 browsers from reading the tags
20352
                            // so we'll need to turn off any default tracks if we're manually doing
20353
                            // captions and subtitles. videoElement.textTracks
20354
                            removeNodes.push(node);
20355
                        } else {
20356
                            // store HTMLTrackElement and TextTrack to remote list
20357
                            this.remoteTextTrackEls().addTrackElement_(node);
20358
                            this.remoteTextTracks().addTrack(node.track);
20359
                            this.textTracks().addTrack(node.track);
20360
                            if (!crossoriginTracks && !this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
20361
                                crossoriginTracks = true;
20362
                            }
20363
                        }
20364
                    }
20365
                }
20366
                for (let i = 0; i < removeNodes.length; i++) {
20367
                    this.el_.removeChild(removeNodes[i]);
20368
                }
20369
            }
20370
            this.proxyNativeTracks_();
20371
            if (this.featuresNativeTextTracks && crossoriginTracks) {
20372
                log$1.warn('Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used.\n' + 'This may prevent text tracks from loading.');
20373
            }
20374
 
20375
            // prevent iOS Safari from disabling metadata text tracks during native playback
20376
            this.restoreMetadataTracksInIOSNativePlayer_();
20377
 
20378
            // Determine if native controls should be used
20379
            // Our goal should be to get the custom controls on mobile solid everywhere
20380
            // so we can remove this all together. Right now this will block custom
20381
            // controls on touch enabled laptops like the Chrome Pixel
20382
            if ((TOUCH_ENABLED || IS_IPHONE) && options.nativeControlsForTouch === true) {
20383
                this.setControls(true);
20384
            }
20385
 
20386
            // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
20387
            // into a `fullscreenchange` event
20388
            this.proxyWebkitFullscreen_();
20389
            this.triggerReady();
20390
        }
20391
 
20392
        /**
20393
         * Dispose of `HTML5` media element and remove all tracks.
20394
         */
20395
        dispose() {
20396
            if (this.el_ && this.el_.resetSourceset_) {
20397
                this.el_.resetSourceset_();
20398
            }
20399
            Html5.disposeMediaElement(this.el_);
20400
            this.options_ = null;
20401
 
20402
            // tech will handle clearing of the emulated track list
20403
            super.dispose();
20404
        }
20405
 
20406
        /**
20407
         * Modify the media element so that we can detect when
20408
         * the source is changed. Fires `sourceset` just after the source has changed
20409
         */
20410
        setupSourcesetHandling_() {
20411
            setupSourceset(this);
20412
        }
20413
 
20414
        /**
20415
         * When a captions track is enabled in the iOS Safari native player, all other
20416
         * tracks are disabled (including metadata tracks), which nulls all of their
20417
         * associated cue points. This will restore metadata tracks to their pre-fullscreen
20418
         * state in those cases so that cue points are not needlessly lost.
20419
         *
20420
         * @private
20421
         */
20422
        restoreMetadataTracksInIOSNativePlayer_() {
20423
            const textTracks = this.textTracks();
20424
            let metadataTracksPreFullscreenState;
20425
 
20426
            // captures a snapshot of every metadata track's current state
20427
            const takeMetadataTrackSnapshot = () => {
20428
                metadataTracksPreFullscreenState = [];
20429
                for (let i = 0; i < textTracks.length; i++) {
20430
                    const track = textTracks[i];
20431
                    if (track.kind === 'metadata') {
20432
                        metadataTracksPreFullscreenState.push({
20433
                            track,
20434
                            storedMode: track.mode
20435
                        });
20436
                    }
20437
                }
20438
            };
20439
 
20440
            // snapshot each metadata track's initial state, and update the snapshot
20441
            // each time there is a track 'change' event
20442
            takeMetadataTrackSnapshot();
20443
            textTracks.addEventListener('change', takeMetadataTrackSnapshot);
20444
            this.on('dispose', () => textTracks.removeEventListener('change', takeMetadataTrackSnapshot));
20445
            const restoreTrackMode = () => {
20446
                for (let i = 0; i < metadataTracksPreFullscreenState.length; i++) {
20447
                    const storedTrack = metadataTracksPreFullscreenState[i];
20448
                    if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
20449
                        storedTrack.track.mode = storedTrack.storedMode;
20450
                    }
20451
                }
20452
                // we only want this handler to be executed on the first 'change' event
20453
                textTracks.removeEventListener('change', restoreTrackMode);
20454
            };
20455
 
20456
            // when we enter fullscreen playback, stop updating the snapshot and
20457
            // restore all track modes to their pre-fullscreen state
20458
            this.on('webkitbeginfullscreen', () => {
20459
                textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
20460
 
20461
                // remove the listener before adding it just in case it wasn't previously removed
20462
                textTracks.removeEventListener('change', restoreTrackMode);
20463
                textTracks.addEventListener('change', restoreTrackMode);
20464
            });
20465
 
20466
            // start updating the snapshot again after leaving fullscreen
20467
            this.on('webkitendfullscreen', () => {
20468
                // remove the listener before adding it just in case it wasn't previously removed
20469
                textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
20470
                textTracks.addEventListener('change', takeMetadataTrackSnapshot);
20471
 
20472
                // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
20473
                textTracks.removeEventListener('change', restoreTrackMode);
20474
            });
20475
        }
20476
 
20477
        /**
20478
         * Attempt to force override of tracks for the given type
20479
         *
20480
         * @param {string} type - Track type to override, possible values include 'Audio',
20481
         * 'Video', and 'Text'.
20482
         * @param {boolean} override - If set to true native audio/video will be overridden,
20483
         * otherwise native audio/video will potentially be used.
20484
         * @private
20485
         */
20486
        overrideNative_(type, override) {
20487
            // If there is no behavioral change don't add/remove listeners
20488
            if (override !== this[`featuresNative${type}Tracks`]) {
20489
                return;
20490
            }
20491
            const lowerCaseType = type.toLowerCase();
20492
            if (this[`${lowerCaseType}TracksListeners_`]) {
20493
                Object.keys(this[`${lowerCaseType}TracksListeners_`]).forEach(eventName => {
20494
                    const elTracks = this.el()[`${lowerCaseType}Tracks`];
20495
                    elTracks.removeEventListener(eventName, this[`${lowerCaseType}TracksListeners_`][eventName]);
20496
                });
20497
            }
20498
            this[`featuresNative${type}Tracks`] = !override;
20499
            this[`${lowerCaseType}TracksListeners_`] = null;
20500
            this.proxyNativeTracksForType_(lowerCaseType);
20501
        }
20502
 
20503
        /**
20504
         * Attempt to force override of native audio tracks.
20505
         *
20506
         * @param {boolean} override - If set to true native audio will be overridden,
20507
         * otherwise native audio will potentially be used.
20508
         */
20509
        overrideNativeAudioTracks(override) {
20510
            this.overrideNative_('Audio', override);
20511
        }
20512
 
20513
        /**
20514
         * Attempt to force override of native video tracks.
20515
         *
20516
         * @param {boolean} override - If set to true native video will be overridden,
20517
         * otherwise native video will potentially be used.
20518
         */
20519
        overrideNativeVideoTracks(override) {
20520
            this.overrideNative_('Video', override);
20521
        }
20522
 
20523
        /**
20524
         * Proxy native track list events for the given type to our track
20525
         * lists if the browser we are playing in supports that type of track list.
20526
         *
20527
         * @param {string} name - Track type; values include 'audio', 'video', and 'text'
20528
         * @private
20529
         */
20530
        proxyNativeTracksForType_(name) {
20531
            const props = NORMAL[name];
20532
            const elTracks = this.el()[props.getterName];
20533
            const techTracks = this[props.getterName]();
20534
            if (!this[`featuresNative${props.capitalName}Tracks`] || !elTracks || !elTracks.addEventListener) {
20535
                return;
20536
            }
20537
            const listeners = {
20538
                change: e => {
20539
                    const event = {
20540
                        type: 'change',
20541
                        target: techTracks,
20542
                        currentTarget: techTracks,
20543
                        srcElement: techTracks
20544
                    };
20545
                    techTracks.trigger(event);
20546
 
20547
                    // if we are a text track change event, we should also notify the
20548
                    // remote text track list. This can potentially cause a false positive
20549
                    // if we were to get a change event on a non-remote track and
20550
                    // we triggered the event on the remote text track list which doesn't
20551
                    // contain that track. However, best practices mean looping through the
20552
                    // list of tracks and searching for the appropriate mode value, so,
20553
                    // this shouldn't pose an issue
20554
                    if (name === 'text') {
20555
                        this[REMOTE.remoteText.getterName]().trigger(event);
20556
                    }
20557
                },
20558
                addtrack(e) {
20559
                    techTracks.addTrack(e.track);
20560
                },
20561
                removetrack(e) {
20562
                    techTracks.removeTrack(e.track);
20563
                }
20564
            };
20565
            const removeOldTracks = function () {
20566
                const removeTracks = [];
20567
                for (let i = 0; i < techTracks.length; i++) {
20568
                    let found = false;
20569
                    for (let j = 0; j < elTracks.length; j++) {
20570
                        if (elTracks[j] === techTracks[i]) {
20571
                            found = true;
20572
                            break;
20573
                        }
20574
                    }
20575
                    if (!found) {
20576
                        removeTracks.push(techTracks[i]);
20577
                    }
20578
                }
20579
                while (removeTracks.length) {
20580
                    techTracks.removeTrack(removeTracks.shift());
20581
                }
20582
            };
20583
            this[props.getterName + 'Listeners_'] = listeners;
20584
            Object.keys(listeners).forEach(eventName => {
20585
                const listener = listeners[eventName];
20586
                elTracks.addEventListener(eventName, listener);
20587
                this.on('dispose', e => elTracks.removeEventListener(eventName, listener));
20588
            });
20589
 
20590
            // Remove (native) tracks that are not used anymore
20591
            this.on('loadstart', removeOldTracks);
20592
            this.on('dispose', e => this.off('loadstart', removeOldTracks));
20593
        }
20594
 
20595
        /**
20596
         * Proxy all native track list events to our track lists if the browser we are playing
20597
         * in supports that type of track list.
20598
         *
20599
         * @private
20600
         */
20601
        proxyNativeTracks_() {
20602
            NORMAL.names.forEach(name => {
20603
                this.proxyNativeTracksForType_(name);
20604
            });
20605
        }
20606
 
20607
        /**
20608
         * Create the `Html5` Tech's DOM element.
20609
         *
20610
         * @return {Element}
20611
         *         The element that gets created.
20612
         */
20613
        createEl() {
20614
            let el = this.options_.tag;
20615
 
20616
            // Check if this browser supports moving the element into the box.
20617
            // On the iPhone video will break if you move the element,
20618
            // So we have to create a brand new element.
20619
            // If we ingested the player div, we do not need to move the media element.
20620
            if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
20621
                // If the original tag is still there, clone and remove it.
20622
                if (el) {
20623
                    const clone = el.cloneNode(true);
20624
                    if (el.parentNode) {
20625
                        el.parentNode.insertBefore(clone, el);
20626
                    }
20627
                    Html5.disposeMediaElement(el);
20628
                    el = clone;
20629
                } else {
20630
                    el = document.createElement('video');
20631
 
20632
                    // determine if native controls should be used
20633
                    const tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
20634
                    const attributes = merge$2({}, tagAttributes);
20635
                    if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
20636
                        delete attributes.controls;
20637
                    }
20638
                    setAttributes(el, Object.assign(attributes, {
20639
                        id: this.options_.techId,
20640
                        class: 'vjs-tech'
20641
                    }));
20642
                }
20643
                el.playerId = this.options_.playerId;
20644
            }
20645
            if (typeof this.options_.preload !== 'undefined') {
20646
                setAttribute(el, 'preload', this.options_.preload);
20647
            }
20648
            if (this.options_.disablePictureInPicture !== undefined) {
20649
                el.disablePictureInPicture = this.options_.disablePictureInPicture;
20650
            }
20651
 
20652
            // Update specific tag settings, in case they were overridden
20653
            // `autoplay` has to be *last* so that `muted` and `playsinline` are present
20654
            // when iOS/Safari or other browsers attempt to autoplay.
20655
            const settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
20656
            for (let i = 0; i < settingsAttrs.length; i++) {
20657
                const attr = settingsAttrs[i];
20658
                const value = this.options_[attr];
20659
                if (typeof value !== 'undefined') {
20660
                    if (value) {
20661
                        setAttribute(el, attr, attr);
20662
                    } else {
20663
                        removeAttribute(el, attr);
20664
                    }
20665
                    el[attr] = value;
20666
                }
20667
            }
20668
            return el;
20669
        }
20670
 
20671
        /**
20672
         * This will be triggered if the loadstart event has already fired, before videojs was
20673
         * ready. Two known examples of when this can happen are:
20674
         * 1. If we're loading the playback object after it has started loading
20675
         * 2. The media is already playing the (often with autoplay on) then
20676
         *
20677
         * This function will fire another loadstart so that videojs can catchup.
20678
         *
20679
         * @fires Tech#loadstart
20680
         *
20681
         * @return {undefined}
20682
         *         returns nothing.
20683
         */
20684
        handleLateInit_(el) {
20685
            if (el.networkState === 0 || el.networkState === 3) {
20686
                // The video element hasn't started loading the source yet
20687
                // or didn't find a source
20688
                return;
20689
            }
20690
            if (el.readyState === 0) {
20691
                // NetworkState is set synchronously BUT loadstart is fired at the
20692
                // end of the current stack, usually before setInterval(fn, 0).
20693
                // So at this point we know loadstart may have already fired or is
20694
                // about to fire, and either way the player hasn't seen it yet.
20695
                // We don't want to fire loadstart prematurely here and cause a
20696
                // double loadstart so we'll wait and see if it happens between now
20697
                // and the next loop, and fire it if not.
20698
                // HOWEVER, we also want to make sure it fires before loadedmetadata
20699
                // which could also happen between now and the next loop, so we'll
20700
                // watch for that also.
20701
                let loadstartFired = false;
20702
                const setLoadstartFired = function () {
20703
                    loadstartFired = true;
20704
                };
20705
                this.on('loadstart', setLoadstartFired);
20706
                const triggerLoadstart = function () {
20707
                    // We did miss the original loadstart. Make sure the player
20708
                    // sees loadstart before loadedmetadata
20709
                    if (!loadstartFired) {
20710
                        this.trigger('loadstart');
20711
                    }
20712
                };
20713
                this.on('loadedmetadata', triggerLoadstart);
20714
                this.ready(function () {
20715
                    this.off('loadstart', setLoadstartFired);
20716
                    this.off('loadedmetadata', triggerLoadstart);
20717
                    if (!loadstartFired) {
20718
                        // We did miss the original native loadstart. Fire it now.
20719
                        this.trigger('loadstart');
20720
                    }
20721
                });
20722
                return;
20723
            }
20724
 
20725
            // From here on we know that loadstart already fired and we missed it.
20726
            // The other readyState events aren't as much of a problem if we double
20727
            // them, so not going to go to as much trouble as loadstart to prevent
20728
            // that unless we find reason to.
20729
            const eventsToTrigger = ['loadstart'];
20730
 
20731
            // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
20732
            eventsToTrigger.push('loadedmetadata');
20733
 
20734
            // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
20735
            if (el.readyState >= 2) {
20736
                eventsToTrigger.push('loadeddata');
20737
            }
20738
 
20739
            // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
20740
            if (el.readyState >= 3) {
20741
                eventsToTrigger.push('canplay');
20742
            }
20743
 
20744
            // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
20745
            if (el.readyState >= 4) {
20746
                eventsToTrigger.push('canplaythrough');
20747
            }
20748
 
20749
            // We still need to give the player time to add event listeners
20750
            this.ready(function () {
20751
                eventsToTrigger.forEach(function (type) {
20752
                    this.trigger(type);
20753
                }, this);
20754
            });
20755
        }
20756
 
20757
        /**
20758
         * Set whether we are scrubbing or not.
20759
         * This is used to decide whether we should use `fastSeek` or not.
20760
         * `fastSeek` is used to provide trick play on Safari browsers.
20761
         *
20762
         * @param {boolean} isScrubbing
20763
         *                  - true for we are currently scrubbing
20764
         *                  - false for we are no longer scrubbing
20765
         */
20766
        setScrubbing(isScrubbing) {
20767
            this.isScrubbing_ = isScrubbing;
20768
        }
20769
 
20770
        /**
20771
         * Get whether we are scrubbing or not.
20772
         *
20773
         * @return {boolean} isScrubbing
20774
         *                  - true for we are currently scrubbing
20775
         *                  - false for we are no longer scrubbing
20776
         */
20777
        scrubbing() {
20778
            return this.isScrubbing_;
20779
        }
20780
 
20781
        /**
20782
         * Set current time for the `HTML5` tech.
20783
         *
20784
         * @param {number} seconds
20785
         *        Set the current time of the media to this.
20786
         */
20787
        setCurrentTime(seconds) {
20788
            try {
20789
                if (this.isScrubbing_ && this.el_.fastSeek && IS_ANY_SAFARI) {
20790
                    this.el_.fastSeek(seconds);
20791
                } else {
20792
                    this.el_.currentTime = seconds;
20793
                }
20794
            } catch (e) {
20795
                log$1(e, 'Video is not ready. (Video.js)');
20796
                // this.warning(VideoJS.warnings.videoNotReady);
20797
            }
20798
        }
20799
 
20800
        /**
20801
         * Get the current duration of the HTML5 media element.
20802
         *
20803
         * @return {number}
20804
         *         The duration of the media or 0 if there is no duration.
20805
         */
20806
        duration() {
20807
            // Android Chrome will report duration as Infinity for VOD HLS until after
20808
            // playback has started, which triggers the live display erroneously.
20809
            // Return NaN if playback has not started and trigger a durationupdate once
20810
            // the duration can be reliably known.
20811
            if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
20812
                // Wait for the first `timeupdate` with currentTime > 0 - there may be
20813
                // several with 0
20814
                const checkProgress = () => {
20815
                    if (this.el_.currentTime > 0) {
20816
                        // Trigger durationchange for genuinely live video
20817
                        if (this.el_.duration === Infinity) {
20818
                            this.trigger('durationchange');
20819
                        }
20820
                        this.off('timeupdate', checkProgress);
20821
                    }
20822
                };
20823
                this.on('timeupdate', checkProgress);
20824
                return NaN;
20825
            }
20826
            return this.el_.duration || NaN;
20827
        }
20828
 
20829
        /**
20830
         * Get the current width of the HTML5 media element.
20831
         *
20832
         * @return {number}
20833
         *         The width of the HTML5 media element.
20834
         */
20835
        width() {
20836
            return this.el_.offsetWidth;
20837
        }
20838
 
20839
        /**
20840
         * Get the current height of the HTML5 media element.
20841
         *
20842
         * @return {number}
20843
         *         The height of the HTML5 media element.
20844
         */
20845
        height() {
20846
            return this.el_.offsetHeight;
20847
        }
20848
 
20849
        /**
20850
         * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
20851
         * `fullscreenchange` event.
20852
         *
20853
         * @private
20854
         * @fires fullscreenchange
20855
         * @listens webkitendfullscreen
20856
         * @listens webkitbeginfullscreen
20857
         * @listens webkitbeginfullscreen
20858
         */
20859
        proxyWebkitFullscreen_() {
20860
            if (!('webkitDisplayingFullscreen' in this.el_)) {
20861
                return;
20862
            }
20863
            const endFn = function () {
20864
                this.trigger('fullscreenchange', {
20865
                    isFullscreen: false
20866
                });
20867
                // Safari will sometimes set controls on the videoelement when existing fullscreen.
20868
                if (this.el_.controls && !this.options_.nativeControlsForTouch && this.controls()) {
20869
                    this.el_.controls = false;
20870
                }
20871
            };
20872
            const beginFn = function () {
20873
                if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
20874
                    this.one('webkitendfullscreen', endFn);
20875
                    this.trigger('fullscreenchange', {
20876
                        isFullscreen: true,
20877
                        // set a flag in case another tech triggers fullscreenchange
20878
                        nativeIOSFullscreen: true
20879
                    });
20880
                }
20881
            };
20882
            this.on('webkitbeginfullscreen', beginFn);
20883
            this.on('dispose', () => {
20884
                this.off('webkitbeginfullscreen', beginFn);
20885
                this.off('webkitendfullscreen', endFn);
20886
            });
20887
        }
20888
 
20889
        /**
20890
         * Check if fullscreen is supported on the video el.
20891
         *
20892
         * @return {boolean}
20893
         *         - True if fullscreen is supported.
20894
         *         - False if fullscreen is not supported.
20895
         */
20896
        supportsFullScreen() {
20897
            return typeof this.el_.webkitEnterFullScreen === 'function';
20898
        }
20899
 
20900
        /**
20901
         * Request that the `HTML5` Tech enter fullscreen.
20902
         */
20903
        enterFullScreen() {
20904
            const video = this.el_;
20905
            if (video.paused && video.networkState <= video.HAVE_METADATA) {
20906
                // attempt to prime the video element for programmatic access
20907
                // this isn't necessary on the desktop but shouldn't hurt
20908
                silencePromise(this.el_.play());
20909
 
20910
                // playing and pausing synchronously during the transition to fullscreen
20911
                // can get iOS ~6.1 devices into a play/pause loop
20912
                this.setTimeout(function () {
20913
                    video.pause();
20914
                    try {
20915
                        video.webkitEnterFullScreen();
20916
                    } catch (e) {
20917
                        this.trigger('fullscreenerror', e);
20918
                    }
20919
                }, 0);
20920
            } else {
20921
                try {
20922
                    video.webkitEnterFullScreen();
20923
                } catch (e) {
20924
                    this.trigger('fullscreenerror', e);
20925
                }
20926
            }
20927
        }
20928
 
20929
        /**
20930
         * Request that the `HTML5` Tech exit fullscreen.
20931
         */
20932
        exitFullScreen() {
20933
            if (!this.el_.webkitDisplayingFullscreen) {
20934
                this.trigger('fullscreenerror', new Error('The video is not fullscreen'));
20935
                return;
20936
            }
20937
            this.el_.webkitExitFullScreen();
20938
        }
20939
 
20940
        /**
20941
         * Create a floating video window always on top of other windows so that users may
20942
         * continue consuming media while they interact with other content sites, or
20943
         * applications on their device.
20944
         *
20945
         * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
20946
         *
20947
         * @return {Promise}
20948
         *         A promise with a Picture-in-Picture window.
20949
         */
20950
        requestPictureInPicture() {
20951
            return this.el_.requestPictureInPicture();
20952
        }
20953
 
20954
        /**
20955
         * Native requestVideoFrameCallback if supported by browser/tech, or fallback
20956
         * Don't use rVCF on Safari when DRM is playing, as it doesn't fire
20957
         * Needs to be checked later than the constructor
20958
         * This will be a false positive for clear sources loaded after a Fairplay source
20959
         *
20960
         * @param {function} cb function to call
20961
         * @return {number} id of request
20962
         */
20963
        requestVideoFrameCallback(cb) {
20964
            if (this.featuresVideoFrameCallback && !this.el_.webkitKeys) {
20965
                return this.el_.requestVideoFrameCallback(cb);
20966
            }
20967
            return super.requestVideoFrameCallback(cb);
20968
        }
20969
 
20970
        /**
20971
         * Native or fallback requestVideoFrameCallback
20972
         *
20973
         * @param {number} id request id to cancel
20974
         */
20975
        cancelVideoFrameCallback(id) {
20976
            if (this.featuresVideoFrameCallback && !this.el_.webkitKeys) {
20977
                this.el_.cancelVideoFrameCallback(id);
20978
            } else {
20979
                super.cancelVideoFrameCallback(id);
20980
            }
20981
        }
20982
 
20983
        /**
20984
         * A getter/setter for the `Html5` Tech's source object.
20985
         * > Note: Please use {@link Html5#setSource}
20986
         *
20987
         * @param {Tech~SourceObject} [src]
20988
         *        The source object you want to set on the `HTML5` techs element.
20989
         *
20990
         * @return {Tech~SourceObject|undefined}
20991
         *         - The current source object when a source is not passed in.
20992
         *         - undefined when setting
20993
         *
20994
         * @deprecated Since version 5.
20995
         */
20996
        src(src) {
20997
            if (src === undefined) {
20998
                return this.el_.src;
20999
            }
21000
 
21001
            // Setting src through `src` instead of `setSrc` will be deprecated
21002
            this.setSrc(src);
21003
        }
21004
 
21005
        /**
21006
         * Reset the tech by removing all sources and then calling
21007
         * {@link Html5.resetMediaElement}.
21008
         */
21009
        reset() {
21010
            Html5.resetMediaElement(this.el_);
21011
        }
21012
 
21013
        /**
21014
         * Get the current source on the `HTML5` Tech. Falls back to returning the source from
21015
         * the HTML5 media element.
21016
         *
21017
         * @return {Tech~SourceObject}
21018
         *         The current source object from the HTML5 tech. With a fallback to the
21019
         *         elements source.
21020
         */
21021
        currentSrc() {
21022
            if (this.currentSource_) {
21023
                return this.currentSource_.src;
21024
            }
21025
            return this.el_.currentSrc;
21026
        }
21027
 
21028
        /**
21029
         * Set controls attribute for the HTML5 media Element.
21030
         *
21031
         * @param {string} val
21032
         *        Value to set the controls attribute to
21033
         */
21034
        setControls(val) {
21035
            this.el_.controls = !!val;
21036
        }
21037
 
21038
        /**
21039
         * Create and returns a remote {@link TextTrack} object.
21040
         *
21041
         * @param {string} kind
21042
         *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
21043
         *
21044
         * @param {string} [label]
21045
         *        Label to identify the text track
21046
         *
21047
         * @param {string} [language]
21048
         *        Two letter language abbreviation
21049
         *
21050
         * @return {TextTrack}
21051
         *         The TextTrack that gets created.
21052
         */
21053
        addTextTrack(kind, label, language) {
21054
            if (!this.featuresNativeTextTracks) {
21055
                return super.addTextTrack(kind, label, language);
21056
            }
21057
            return this.el_.addTextTrack(kind, label, language);
21058
        }
21059
 
21060
        /**
21061
         * Creates either native TextTrack or an emulated TextTrack depending
21062
         * on the value of `featuresNativeTextTracks`
21063
         *
21064
         * @param {Object} options
21065
         *        The object should contain the options to initialize the TextTrack with.
21066
         *
21067
         * @param {string} [options.kind]
21068
         *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
21069
         *
21070
         * @param {string} [options.label]
21071
         *        Label to identify the text track
21072
         *
21073
         * @param {string} [options.language]
21074
         *        Two letter language abbreviation.
21075
         *
21076
         * @param {boolean} [options.default]
21077
         *        Default this track to on.
21078
         *
21079
         * @param {string} [options.id]
21080
         *        The internal id to assign this track.
21081
         *
21082
         * @param {string} [options.src]
21083
         *        A source url for the track.
21084
         *
21085
         * @return {HTMLTrackElement}
21086
         *         The track element that gets created.
21087
         */
21088
        createRemoteTextTrack(options) {
21089
            if (!this.featuresNativeTextTracks) {
21090
                return super.createRemoteTextTrack(options);
21091
            }
21092
            const htmlTrackElement = document.createElement('track');
21093
            if (options.kind) {
21094
                htmlTrackElement.kind = options.kind;
21095
            }
21096
            if (options.label) {
21097
                htmlTrackElement.label = options.label;
21098
            }
21099
            if (options.language || options.srclang) {
21100
                htmlTrackElement.srclang = options.language || options.srclang;
21101
            }
21102
            if (options.default) {
21103
                htmlTrackElement.default = options.default;
21104
            }
21105
            if (options.id) {
21106
                htmlTrackElement.id = options.id;
21107
            }
21108
            if (options.src) {
21109
                htmlTrackElement.src = options.src;
21110
            }
21111
            return htmlTrackElement;
21112
        }
21113
 
21114
        /**
21115
         * Creates a remote text track object and returns an html track element.
21116
         *
21117
         * @param {Object} options The object should contain values for
21118
         * kind, language, label, and src (location of the WebVTT file)
21119
         * @param {boolean} [manualCleanup=false] if set to true, the TextTrack
21120
         * will not be removed from the TextTrackList and HtmlTrackElementList
21121
         * after a source change
21122
         * @return {HTMLTrackElement} An Html Track Element.
21123
         * This can be an emulated {@link HTMLTrackElement} or a native one.
21124
         *
21125
         */
21126
        addRemoteTextTrack(options, manualCleanup) {
21127
            const htmlTrackElement = super.addRemoteTextTrack(options, manualCleanup);
21128
            if (this.featuresNativeTextTracks) {
21129
                this.el().appendChild(htmlTrackElement);
21130
            }
21131
            return htmlTrackElement;
21132
        }
21133
 
21134
        /**
21135
         * Remove remote `TextTrack` from `TextTrackList` object
21136
         *
21137
         * @param {TextTrack} track
21138
         *        `TextTrack` object to remove
21139
         */
21140
        removeRemoteTextTrack(track) {
21141
            super.removeRemoteTextTrack(track);
21142
            if (this.featuresNativeTextTracks) {
21143
                const tracks = this.$$('track');
21144
                let i = tracks.length;
21145
                while (i--) {
21146
                    if (track === tracks[i] || track === tracks[i].track) {
21147
                        this.el().removeChild(tracks[i]);
21148
                    }
21149
                }
21150
            }
21151
        }
21152
 
21153
        /**
21154
         * Gets available media playback quality metrics as specified by the W3C's Media
21155
         * Playback Quality API.
21156
         *
21157
         * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
21158
         *
21159
         * @return {Object}
21160
         *         An object with supported media playback quality metrics
21161
         */
21162
        getVideoPlaybackQuality() {
21163
            if (typeof this.el().getVideoPlaybackQuality === 'function') {
21164
                return this.el().getVideoPlaybackQuality();
21165
            }
21166
            const videoPlaybackQuality = {};
21167
            if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
21168
                videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
21169
                videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
21170
            }
21171
            if (window.performance) {
21172
                videoPlaybackQuality.creationTime = window.performance.now();
21173
            }
21174
            return videoPlaybackQuality;
21175
        }
21176
    }
21177
 
21178
    /* HTML5 Support Testing ---------------------------------------------------- */
21179
 
21180
    /**
21181
     * Element for testing browser HTML5 media capabilities
21182
     *
21183
     * @type {Element}
21184
     * @constant
21185
     * @private
21186
     */
21187
    defineLazyProperty(Html5, 'TEST_VID', function () {
21188
        if (!isReal()) {
21189
            return;
21190
        }
21191
        const video = document.createElement('video');
21192
        const track = document.createElement('track');
21193
        track.kind = 'captions';
21194
        track.srclang = 'en';
21195
        track.label = 'English';
21196
        video.appendChild(track);
21197
        return video;
21198
    });
21199
 
21200
    /**
21201
     * Check if HTML5 media is supported by this browser/device.
21202
     *
21203
     * @return {boolean}
21204
     *         - True if HTML5 media is supported.
21205
     *         - False if HTML5 media is not supported.
21206
     */
21207
    Html5.isSupported = function () {
21208
        // IE with no Media Player is a LIAR! (#984)
21209
        try {
21210
            Html5.TEST_VID.volume = 0.5;
21211
        } catch (e) {
21212
            return false;
21213
        }
21214
        return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
21215
    };
21216
 
21217
    /**
21218
     * Check if the tech can support the given type
21219
     *
21220
     * @param {string} type
21221
     *        The mimetype to check
21222
     * @return {string} 'probably', 'maybe', or '' (empty string)
21223
     */
21224
    Html5.canPlayType = function (type) {
21225
        return Html5.TEST_VID.canPlayType(type);
21226
    };
21227
 
21228
    /**
21229
     * Check if the tech can support the given source
21230
     *
21231
     * @param {Object} srcObj
21232
     *        The source object
21233
     * @param {Object} options
21234
     *        The options passed to the tech
21235
     * @return {string} 'probably', 'maybe', or '' (empty string)
21236
     */
21237
    Html5.canPlaySource = function (srcObj, options) {
21238
        return Html5.canPlayType(srcObj.type);
21239
    };
21240
 
21241
    /**
21242
     * Check if the volume can be changed in this browser/device.
21243
     * Volume cannot be changed in a lot of mobile devices.
21244
     * Specifically, it can't be changed from 1 on iOS.
21245
     *
21246
     * @return {boolean}
21247
     *         - True if volume can be controlled
21248
     *         - False otherwise
21249
     */
21250
    Html5.canControlVolume = function () {
21251
        // IE will error if Windows Media Player not installed #3315
21252
        try {
21253
            const volume = Html5.TEST_VID.volume;
21254
            Html5.TEST_VID.volume = volume / 2 + 0.1;
21255
            const canControl = volume !== Html5.TEST_VID.volume;
21256
 
21257
            // With the introduction of iOS 15, there are cases where the volume is read as
21258
            // changed but reverts back to its original state at the start of the next tick.
21259
            // To determine whether volume can be controlled on iOS,
21260
            // a timeout is set and the volume is checked asynchronously.
21261
            // Since `features` doesn't currently work asynchronously, the value is manually set.
21262
            if (canControl && IS_IOS) {
21263
                window.setTimeout(() => {
21264
                    if (Html5 && Html5.prototype) {
21265
                        Html5.prototype.featuresVolumeControl = volume !== Html5.TEST_VID.volume;
21266
                    }
21267
                });
21268
 
21269
                // default iOS to false, which will be updated in the timeout above.
21270
                return false;
21271
            }
21272
            return canControl;
21273
        } catch (e) {
21274
            return false;
21275
        }
21276
    };
21277
 
21278
    /**
21279
     * Check if the volume can be muted in this browser/device.
21280
     * Some devices, e.g. iOS, don't allow changing volume
21281
     * but permits muting/unmuting.
21282
     *
21283
     * @return {boolean}
21284
     *      - True if volume can be muted
21285
     *      - False otherwise
21286
     */
21287
    Html5.canMuteVolume = function () {
21288
        try {
21289
            const muted = Html5.TEST_VID.muted;
21290
 
21291
            // in some versions of iOS muted property doesn't always
21292
            // work, so we want to set both property and attribute
21293
            Html5.TEST_VID.muted = !muted;
21294
            if (Html5.TEST_VID.muted) {
21295
                setAttribute(Html5.TEST_VID, 'muted', 'muted');
21296
            } else {
21297
                removeAttribute(Html5.TEST_VID, 'muted', 'muted');
21298
            }
21299
            return muted !== Html5.TEST_VID.muted;
21300
        } catch (e) {
21301
            return false;
21302
        }
21303
    };
21304
 
21305
    /**
21306
     * Check if the playback rate can be changed in this browser/device.
21307
     *
21308
     * @return {boolean}
21309
     *         - True if playback rate can be controlled
21310
     *         - False otherwise
21311
     */
21312
    Html5.canControlPlaybackRate = function () {
21313
        // Playback rate API is implemented in Android Chrome, but doesn't do anything
21314
        // https://github.com/videojs/video.js/issues/3180
21315
        if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
21316
            return false;
21317
        }
21318
        // IE will error if Windows Media Player not installed #3315
21319
        try {
21320
            const playbackRate = Html5.TEST_VID.playbackRate;
21321
            Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
21322
            return playbackRate !== Html5.TEST_VID.playbackRate;
21323
        } catch (e) {
21324
            return false;
21325
        }
21326
    };
21327
 
21328
    /**
21329
     * Check if we can override a video/audio elements attributes, with
21330
     * Object.defineProperty.
21331
     *
21332
     * @return {boolean}
21333
     *         - True if builtin attributes can be overridden
21334
     *         - False otherwise
21335
     */
21336
    Html5.canOverrideAttributes = function () {
21337
        // if we cannot overwrite the src/innerHTML property, there is no support
21338
        // iOS 7 safari for instance cannot do this.
21339
        try {
21340
            const noop = () => {};
21341
            Object.defineProperty(document.createElement('video'), 'src', {
21342
                get: noop,
21343
                set: noop
21344
            });
21345
            Object.defineProperty(document.createElement('audio'), 'src', {
21346
                get: noop,
21347
                set: noop
21348
            });
21349
            Object.defineProperty(document.createElement('video'), 'innerHTML', {
21350
                get: noop,
21351
                set: noop
21352
            });
21353
            Object.defineProperty(document.createElement('audio'), 'innerHTML', {
21354
                get: noop,
21355
                set: noop
21356
            });
21357
        } catch (e) {
21358
            return false;
21359
        }
21360
        return true;
21361
    };
21362
 
21363
    /**
21364
     * Check to see if native `TextTrack`s are supported by this browser/device.
21365
     *
21366
     * @return {boolean}
21367
     *         - True if native `TextTrack`s are supported.
21368
     *         - False otherwise
21369
     */
21370
    Html5.supportsNativeTextTracks = function () {
21371
        return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
21372
    };
21373
 
21374
    /**
21375
     * Check to see if native `VideoTrack`s are supported by this browser/device
21376
     *
21377
     * @return {boolean}
21378
     *        - True if native `VideoTrack`s are supported.
21379
     *        - False otherwise
21380
     */
21381
    Html5.supportsNativeVideoTracks = function () {
21382
        return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
21383
    };
21384
 
21385
    /**
21386
     * Check to see if native `AudioTrack`s are supported by this browser/device
21387
     *
21388
     * @return {boolean}
21389
     *        - True if native `AudioTrack`s are supported.
21390
     *        - False otherwise
21391
     */
21392
    Html5.supportsNativeAudioTracks = function () {
21393
        return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
21394
    };
21395
 
21396
    /**
21397
     * An array of events available on the Html5 tech.
21398
     *
21399
     * @private
21400
     * @type {Array}
21401
     */
21402
    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'];
21403
 
21404
    /**
21405
     * Boolean indicating whether the `Tech` supports volume control.
21406
     *
21407
     * @type {boolean}
21408
     * @default {@link Html5.canControlVolume}
21409
     */
21410
    /**
21411
     * Boolean indicating whether the `Tech` supports muting volume.
21412
     *
21413
     * @type {boolean}
21414
     * @default {@link Html5.canMuteVolume}
21415
     */
21416
 
21417
    /**
21418
     * Boolean indicating whether the `Tech` supports changing the speed at which the media
21419
     * plays. Examples:
21420
     *   - Set player to play 2x (twice) as fast
21421
     *   - Set player to play 0.5x (half) as fast
21422
     *
21423
     * @type {boolean}
21424
     * @default {@link Html5.canControlPlaybackRate}
21425
     */
21426
 
21427
    /**
21428
     * Boolean indicating whether the `Tech` supports the `sourceset` event.
21429
     *
21430
     * @type {boolean}
21431
     * @default
21432
     */
21433
    /**
21434
     * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
21435
     *
21436
     * @type {boolean}
21437
     * @default {@link Html5.supportsNativeTextTracks}
21438
     */
21439
    /**
21440
     * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
21441
     *
21442
     * @type {boolean}
21443
     * @default {@link Html5.supportsNativeVideoTracks}
21444
     */
21445
    /**
21446
     * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
21447
     *
21448
     * @type {boolean}
21449
     * @default {@link Html5.supportsNativeAudioTracks}
21450
     */
21451
    [['featuresMuteControl', 'canMuteVolume'], ['featuresPlaybackRate', 'canControlPlaybackRate'], ['featuresSourceset', 'canOverrideAttributes'], ['featuresNativeTextTracks', 'supportsNativeTextTracks'], ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'], ['featuresNativeAudioTracks', 'supportsNativeAudioTracks']].forEach(function ([key, fn]) {
21452
        defineLazyProperty(Html5.prototype, key, () => Html5[fn](), true);
21453
    });
21454
    Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
21455
 
21456
    /**
21457
     * Boolean indicating whether the `HTML5` tech currently supports the media element
21458
     * moving in the DOM. iOS breaks if you move the media element, so this is set this to
21459
     * false there. Everywhere else this should be true.
21460
     *
21461
     * @type {boolean}
21462
     * @default
21463
     */
21464
    Html5.prototype.movingMediaElementInDOM = !IS_IOS;
21465
 
21466
    // TODO: Previous comment: No longer appears to be used. Can probably be removed.
21467
    //       Is this true?
21468
    /**
21469
     * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
21470
     * when going into fullscreen.
21471
     *
21472
     * @type {boolean}
21473
     * @default
21474
     */
21475
    Html5.prototype.featuresFullscreenResize = true;
21476
 
21477
    /**
21478
     * Boolean indicating whether the `HTML5` tech currently supports the progress event.
21479
     * If this is false, manual `progress` events will be triggered instead.
21480
     *
21481
     * @type {boolean}
21482
     * @default
21483
     */
21484
    Html5.prototype.featuresProgressEvents = true;
21485
 
21486
    /**
21487
     * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
21488
     * If this is false, manual `timeupdate` events will be triggered instead.
21489
     *
21490
     * @default
21491
     */
21492
    Html5.prototype.featuresTimeupdateEvents = true;
21493
 
21494
    /**
21495
     * Whether the HTML5 el supports `requestVideoFrameCallback`
21496
     *
21497
     * @type {boolean}
21498
     */
21499
    Html5.prototype.featuresVideoFrameCallback = !!(Html5.TEST_VID && Html5.TEST_VID.requestVideoFrameCallback);
21500
    Html5.disposeMediaElement = function (el) {
21501
        if (!el) {
21502
            return;
21503
        }
21504
        if (el.parentNode) {
21505
            el.parentNode.removeChild(el);
21506
        }
21507
 
21508
        // remove any child track or source nodes to prevent their loading
21509
        while (el.hasChildNodes()) {
21510
            el.removeChild(el.firstChild);
21511
        }
21512
 
21513
        // remove any src reference. not setting `src=''` because that causes a warning
21514
        // in firefox
21515
        el.removeAttribute('src');
21516
 
21517
        // force the media element to update its loading state by calling load()
21518
        // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
21519
        if (typeof el.load === 'function') {
21520
            // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
21521
            (function () {
21522
                try {
21523
                    el.load();
21524
                } catch (e) {
21525
                    // not supported
21526
                }
21527
            })();
21528
        }
21529
    };
21530
    Html5.resetMediaElement = function (el) {
21531
        if (!el) {
21532
            return;
21533
        }
21534
        const sources = el.querySelectorAll('source');
21535
        let i = sources.length;
21536
        while (i--) {
21537
            el.removeChild(sources[i]);
21538
        }
21539
 
21540
        // remove any src reference.
21541
        // not setting `src=''` because that throws an error
21542
        el.removeAttribute('src');
21543
        if (typeof el.load === 'function') {
21544
            // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
21545
            (function () {
21546
                try {
21547
                    el.load();
21548
                } catch (e) {
21549
                    // satisfy linter
21550
                }
21551
            })();
21552
        }
21553
    };
21554
 
21555
    /* Native HTML5 element property wrapping ----------------------------------- */
21556
    // Wrap native boolean attributes with getters that check both property and attribute
21557
    // The list is as followed:
21558
    // muted, defaultMuted, autoplay, controls, loop, playsinline
21559
    [
21560
        /**
21561
         * Get the value of `muted` from the media element. `muted` indicates
21562
         * that the volume for the media should be set to silent. This does not actually change
21563
         * the `volume` attribute.
21564
         *
21565
         * @method Html5#muted
21566
         * @return {boolean}
21567
         *         - True if the value of `volume` should be ignored and the audio set to silent.
21568
         *         - False if the value of `volume` should be used.
21569
         *
21570
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
21571
         */
21572
        'muted',
21573
        /**
21574
         * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
21575
         * whether the media should start muted or not. Only changes the default state of the
21576
         * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
21577
         * current state.
21578
         *
21579
         * @method Html5#defaultMuted
21580
         * @return {boolean}
21581
         *         - The value of `defaultMuted` from the media element.
21582
         *         - True indicates that the media should start muted.
21583
         *         - False indicates that the media should not start muted
21584
         *
21585
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
21586
         */
21587
        'defaultMuted',
21588
        /**
21589
         * Get the value of `autoplay` from the media element. `autoplay` indicates
21590
         * that the media should start to play as soon as the page is ready.
21591
         *
21592
         * @method Html5#autoplay
21593
         * @return {boolean}
21594
         *         - The value of `autoplay` from the media element.
21595
         *         - True indicates that the media should start as soon as the page loads.
21596
         *         - False indicates that the media should not start as soon as the page loads.
21597
         *
21598
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
21599
         */
21600
        'autoplay',
21601
        /**
21602
         * Get the value of `controls` from the media element. `controls` indicates
21603
         * whether the native media controls should be shown or hidden.
21604
         *
21605
         * @method Html5#controls
21606
         * @return {boolean}
21607
         *         - The value of `controls` from the media element.
21608
         *         - True indicates that native controls should be showing.
21609
         *         - False indicates that native controls should be hidden.
21610
         *
21611
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
21612
         */
21613
        'controls',
21614
        /**
21615
         * Get the value of `loop` from the media element. `loop` indicates
21616
         * that the media should return to the start of the media and continue playing once
21617
         * it reaches the end.
21618
         *
21619
         * @method Html5#loop
21620
         * @return {boolean}
21621
         *         - The value of `loop` from the media element.
21622
         *         - True indicates that playback should seek back to start once
21623
         *           the end of a media is reached.
21624
         *         - False indicates that playback should not loop back to the start when the
21625
         *           end of the media is reached.
21626
         *
21627
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
21628
         */
21629
        'loop',
21630
        /**
21631
         * Get the value of `playsinline` from the media element. `playsinline` indicates
21632
         * to the browser that non-fullscreen playback is preferred when fullscreen
21633
         * playback is the native default, such as in iOS Safari.
21634
         *
21635
         * @method Html5#playsinline
21636
         * @return {boolean}
21637
         *         - The value of `playsinline` from the media element.
21638
         *         - True indicates that the media should play inline.
21639
         *         - False indicates that the media should not play inline.
21640
         *
21641
         * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
21642
         */
21643
        'playsinline'].forEach(function (prop) {
21644
        Html5.prototype[prop] = function () {
21645
            return this.el_[prop] || this.el_.hasAttribute(prop);
21646
        };
21647
    });
21648
 
21649
    // Wrap native boolean attributes with setters that set both property and attribute
21650
    // The list is as followed:
21651
    // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
21652
    // setControls is special-cased above
21653
    [
21654
        /**
21655
         * Set the value of `muted` on the media element. `muted` indicates that the current
21656
         * audio level should be silent.
21657
         *
21658
         * @method Html5#setMuted
21659
         * @param {boolean} muted
21660
         *        - True if the audio should be set to silent
21661
         *        - False otherwise
21662
         *
21663
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
21664
         */
21665
        'muted',
21666
        /**
21667
         * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
21668
         * audio level should be silent, but will only effect the muted level on initial playback..
21669
         *
21670
         * @method Html5.prototype.setDefaultMuted
21671
         * @param {boolean} defaultMuted
21672
         *        - True if the audio should be set to silent
21673
         *        - False otherwise
21674
         *
21675
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
21676
         */
21677
        'defaultMuted',
21678
        /**
21679
         * Set the value of `autoplay` on the media element. `autoplay` indicates
21680
         * that the media should start to play as soon as the page is ready.
21681
         *
21682
         * @method Html5#setAutoplay
21683
         * @param {boolean} autoplay
21684
         *         - True indicates that the media should start as soon as the page loads.
21685
         *         - False indicates that the media should not start as soon as the page loads.
21686
         *
21687
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
21688
         */
21689
        'autoplay',
21690
        /**
21691
         * Set the value of `loop` on the media element. `loop` indicates
21692
         * that the media should return to the start of the media and continue playing once
21693
         * it reaches the end.
21694
         *
21695
         * @method Html5#setLoop
21696
         * @param {boolean} loop
21697
         *         - True indicates that playback should seek back to start once
21698
         *           the end of a media is reached.
21699
         *         - False indicates that playback should not loop back to the start when the
21700
         *           end of the media is reached.
21701
         *
21702
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
21703
         */
21704
        'loop',
21705
        /**
21706
         * Set the value of `playsinline` from the media element. `playsinline` indicates
21707
         * to the browser that non-fullscreen playback is preferred when fullscreen
21708
         * playback is the native default, such as in iOS Safari.
21709
         *
21710
         * @method Html5#setPlaysinline
21711
         * @param {boolean} playsinline
21712
         *         - True indicates that the media should play inline.
21713
         *         - False indicates that the media should not play inline.
21714
         *
21715
         * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
21716
         */
21717
        'playsinline'].forEach(function (prop) {
21718
        Html5.prototype['set' + toTitleCase$1(prop)] = function (v) {
21719
            this.el_[prop] = v;
21720
            if (v) {
21721
                this.el_.setAttribute(prop, prop);
21722
            } else {
21723
                this.el_.removeAttribute(prop);
21724
            }
21725
        };
21726
    });
21727
 
21728
    // Wrap native properties with a getter
21729
    // The list is as followed
21730
    // paused, currentTime, buffered, volume, poster, preload, error, seeking
21731
    // seekable, ended, playbackRate, defaultPlaybackRate, disablePictureInPicture
21732
    // played, networkState, readyState, videoWidth, videoHeight, crossOrigin
21733
    [
21734
        /**
21735
         * Get the value of `paused` from the media element. `paused` indicates whether the media element
21736
         * is currently paused or not.
21737
         *
21738
         * @method Html5#paused
21739
         * @return {boolean}
21740
         *         The value of `paused` from the media element.
21741
         *
21742
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
21743
         */
21744
        'paused',
21745
        /**
21746
         * Get the value of `currentTime` from the media element. `currentTime` indicates
21747
         * the current second that the media is at in playback.
21748
         *
21749
         * @method Html5#currentTime
21750
         * @return {number}
21751
         *         The value of `currentTime` from the media element.
21752
         *
21753
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
21754
         */
21755
        'currentTime',
21756
        /**
21757
         * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
21758
         * object that represents the parts of the media that are already downloaded and
21759
         * available for playback.
21760
         *
21761
         * @method Html5#buffered
21762
         * @return {TimeRange}
21763
         *         The value of `buffered` from the media element.
21764
         *
21765
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
21766
         */
21767
        'buffered',
21768
        /**
21769
         * Get the value of `volume` from the media element. `volume` indicates
21770
         * the current playback volume of audio for a media. `volume` will be a value from 0
21771
         * (silent) to 1 (loudest and default).
21772
         *
21773
         * @method Html5#volume
21774
         * @return {number}
21775
         *         The value of `volume` from the media element. Value will be between 0-1.
21776
         *
21777
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
21778
         */
21779
        'volume',
21780
        /**
21781
         * Get the value of `poster` from the media element. `poster` indicates
21782
         * that the url of an image file that can/will be shown when no media data is available.
21783
         *
21784
         * @method Html5#poster
21785
         * @return {string}
21786
         *         The value of `poster` from the media element. Value will be a url to an
21787
         *         image.
21788
         *
21789
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
21790
         */
21791
        'poster',
21792
        /**
21793
         * Get the value of `preload` from the media element. `preload` indicates
21794
         * what should download before the media is interacted with. It can have the following
21795
         * values:
21796
         * - none: nothing should be downloaded
21797
         * - metadata: poster and the first few frames of the media may be downloaded to get
21798
         *   media dimensions and other metadata
21799
         * - auto: allow the media and metadata for the media to be downloaded before
21800
         *    interaction
21801
         *
21802
         * @method Html5#preload
21803
         * @return {string}
21804
         *         The value of `preload` from the media element. Will be 'none', 'metadata',
21805
         *         or 'auto'.
21806
         *
21807
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
21808
         */
21809
        'preload',
21810
        /**
21811
         * Get the value of the `error` from the media element. `error` indicates any
21812
         * MediaError that may have occurred during playback. If error returns null there is no
21813
         * current error.
21814
         *
21815
         * @method Html5#error
21816
         * @return {MediaError|null}
21817
         *         The value of `error` from the media element. Will be `MediaError` if there
21818
         *         is a current error and null otherwise.
21819
         *
21820
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
21821
         */
21822
        'error',
21823
        /**
21824
         * Get the value of `seeking` from the media element. `seeking` indicates whether the
21825
         * media is currently seeking to a new position or not.
21826
         *
21827
         * @method Html5#seeking
21828
         * @return {boolean}
21829
         *         - The value of `seeking` from the media element.
21830
         *         - True indicates that the media is currently seeking to a new position.
21831
         *         - False indicates that the media is not seeking to a new position at this time.
21832
         *
21833
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
21834
         */
21835
        'seeking',
21836
        /**
21837
         * Get the value of `seekable` from the media element. `seekable` returns a
21838
         * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
21839
         *
21840
         * @method Html5#seekable
21841
         * @return {TimeRange}
21842
         *         The value of `seekable` from the media element. A `TimeRange` object
21843
         *         indicating the current ranges of time that can be seeked to.
21844
         *
21845
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
21846
         */
21847
        'seekable',
21848
        /**
21849
         * Get the value of `ended` from the media element. `ended` indicates whether
21850
         * the media has reached the end or not.
21851
         *
21852
         * @method Html5#ended
21853
         * @return {boolean}
21854
         *         - The value of `ended` from the media element.
21855
         *         - True indicates that the media has ended.
21856
         *         - False indicates that the media has not ended.
21857
         *
21858
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
21859
         */
21860
        'ended',
21861
        /**
21862
         * Get the value of `playbackRate` from the media element. `playbackRate` indicates
21863
         * the rate at which the media is currently playing back. Examples:
21864
         *   - if playbackRate is set to 2, media will play twice as fast.
21865
         *   - if playbackRate is set to 0.5, media will play half as fast.
21866
         *
21867
         * @method Html5#playbackRate
21868
         * @return {number}
21869
         *         The value of `playbackRate` from the media element. A number indicating
21870
         *         the current playback speed of the media, where 1 is normal speed.
21871
         *
21872
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
21873
         */
21874
        'playbackRate',
21875
        /**
21876
         * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
21877
         * the rate at which the media is currently playing back. This value will not indicate the current
21878
         * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
21879
         *
21880
         * Examples:
21881
         *   - if defaultPlaybackRate is set to 2, media will play twice as fast.
21882
         *   - if defaultPlaybackRate is set to 0.5, media will play half as fast.
21883
         *
21884
         * @method Html5.prototype.defaultPlaybackRate
21885
         * @return {number}
21886
         *         The value of `defaultPlaybackRate` from the media element. A number indicating
21887
         *         the current playback speed of the media, where 1 is normal speed.
21888
         *
21889
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
21890
         */
21891
        'defaultPlaybackRate',
21892
        /**
21893
         * Get the value of 'disablePictureInPicture' from the video element.
21894
         *
21895
         * @method Html5#disablePictureInPicture
21896
         * @return {boolean} value
21897
         *         - The value of `disablePictureInPicture` from the video element.
21898
         *         - True indicates that the video can't be played in Picture-In-Picture mode
21899
         *         - False indicates that the video can be played in Picture-In-Picture mode
21900
         *
21901
         * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
21902
         */
21903
        'disablePictureInPicture',
21904
        /**
21905
         * Get the value of `played` from the media element. `played` returns a `TimeRange`
21906
         * object representing points in the media timeline that have been played.
21907
         *
21908
         * @method Html5#played
21909
         * @return {TimeRange}
21910
         *         The value of `played` from the media element. A `TimeRange` object indicating
21911
         *         the ranges of time that have been played.
21912
         *
21913
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
21914
         */
21915
        'played',
21916
        /**
21917
         * Get the value of `networkState` from the media element. `networkState` indicates
21918
         * the current network state. It returns an enumeration from the following list:
21919
         * - 0: NETWORK_EMPTY
21920
         * - 1: NETWORK_IDLE
21921
         * - 2: NETWORK_LOADING
21922
         * - 3: NETWORK_NO_SOURCE
21923
         *
21924
         * @method Html5#networkState
21925
         * @return {number}
21926
         *         The value of `networkState` from the media element. This will be a number
21927
         *         from the list in the description.
21928
         *
21929
         * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
21930
         */
21931
        'networkState',
21932
        /**
21933
         * Get the value of `readyState` from the media element. `readyState` indicates
21934
         * the current state of the media element. It returns an enumeration from the
21935
         * following list:
21936
         * - 0: HAVE_NOTHING
21937
         * - 1: HAVE_METADATA
21938
         * - 2: HAVE_CURRENT_DATA
21939
         * - 3: HAVE_FUTURE_DATA
21940
         * - 4: HAVE_ENOUGH_DATA
21941
         *
21942
         * @method Html5#readyState
21943
         * @return {number}
21944
         *         The value of `readyState` from the media element. This will be a number
21945
         *         from the list in the description.
21946
         *
21947
         * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
21948
         */
21949
        'readyState',
21950
        /**
21951
         * Get the value of `videoWidth` from the video element. `videoWidth` indicates
21952
         * the current width of the video in css pixels.
21953
         *
21954
         * @method Html5#videoWidth
21955
         * @return {number}
21956
         *         The value of `videoWidth` from the video element. This will be a number
21957
         *         in css pixels.
21958
         *
21959
         * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
21960
         */
21961
        'videoWidth',
21962
        /**
21963
         * Get the value of `videoHeight` from the video element. `videoHeight` indicates
21964
         * the current height of the video in css pixels.
21965
         *
21966
         * @method Html5#videoHeight
21967
         * @return {number}
21968
         *         The value of `videoHeight` from the video element. This will be a number
21969
         *         in css pixels.
21970
         *
21971
         * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
21972
         */
21973
        'videoHeight',
21974
        /**
21975
         * Get the value of `crossOrigin` from the media element. `crossOrigin` indicates
21976
         * to the browser that should sent the cookies along with the requests for the
21977
         * different assets/playlists
21978
         *
21979
         * @method Html5#crossOrigin
21980
         * @return {string}
21981
         *         - anonymous indicates that the media should not sent cookies.
21982
         *         - use-credentials indicates that the media should sent cookies along the requests.
21983
         *
21984
         * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
21985
         */
21986
        'crossOrigin'].forEach(function (prop) {
21987
        Html5.prototype[prop] = function () {
21988
            return this.el_[prop];
21989
        };
21990
    });
21991
 
21992
    // Wrap native properties with a setter in this format:
21993
    // set + toTitleCase(name)
21994
    // The list is as follows:
21995
    // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate,
21996
    // setDisablePictureInPicture, setCrossOrigin
21997
    [
21998
        /**
21999
         * Set the value of `volume` on the media element. `volume` indicates the current
22000
         * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
22001
         * so on.
22002
         *
22003
         * @method Html5#setVolume
22004
         * @param {number} percentAsDecimal
22005
         *        The volume percent as a decimal. Valid range is from 0-1.
22006
         *
22007
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
22008
         */
22009
        'volume',
22010
        /**
22011
         * Set the value of `src` on the media element. `src` indicates the current
22012
         * {@link Tech~SourceObject} for the media.
22013
         *
22014
         * @method Html5#setSrc
22015
         * @param {Tech~SourceObject} src
22016
         *        The source object to set as the current source.
22017
         *
22018
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
22019
         */
22020
        'src',
22021
        /**
22022
         * Set the value of `poster` on the media element. `poster` is the url to
22023
         * an image file that can/will be shown when no media data is available.
22024
         *
22025
         * @method Html5#setPoster
22026
         * @param {string} poster
22027
         *        The url to an image that should be used as the `poster` for the media
22028
         *        element.
22029
         *
22030
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
22031
         */
22032
        'poster',
22033
        /**
22034
         * Set the value of `preload` on the media element. `preload` indicates
22035
         * what should download before the media is interacted with. It can have the following
22036
         * values:
22037
         * - none: nothing should be downloaded
22038
         * - metadata: poster and the first few frames of the media may be downloaded to get
22039
         *   media dimensions and other metadata
22040
         * - auto: allow the media and metadata for the media to be downloaded before
22041
         *    interaction
22042
         *
22043
         * @method Html5#setPreload
22044
         * @param {string} preload
22045
         *         The value of `preload` to set on the media element. Must be 'none', 'metadata',
22046
         *         or 'auto'.
22047
         *
22048
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
22049
         */
22050
        'preload',
22051
        /**
22052
         * Set the value of `playbackRate` on the media element. `playbackRate` indicates
22053
         * the rate at which the media should play back. Examples:
22054
         *   - if playbackRate is set to 2, media will play twice as fast.
22055
         *   - if playbackRate is set to 0.5, media will play half as fast.
22056
         *
22057
         * @method Html5#setPlaybackRate
22058
         * @return {number}
22059
         *         The value of `playbackRate` from the media element. A number indicating
22060
         *         the current playback speed of the media, where 1 is normal speed.
22061
         *
22062
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
22063
         */
22064
        'playbackRate',
22065
        /**
22066
         * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
22067
         * the rate at which the media should play back upon initial startup. Changing this value
22068
         * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
22069
         *
22070
         * Example Values:
22071
         *   - if playbackRate is set to 2, media will play twice as fast.
22072
         *   - if playbackRate is set to 0.5, media will play half as fast.
22073
         *
22074
         * @method Html5.prototype.setDefaultPlaybackRate
22075
         * @return {number}
22076
         *         The value of `defaultPlaybackRate` from the media element. A number indicating
22077
         *         the current playback speed of the media, where 1 is normal speed.
22078
         *
22079
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
22080
         */
22081
        'defaultPlaybackRate',
22082
        /**
22083
         * Prevents the browser from suggesting a Picture-in-Picture context menu
22084
         * or to request Picture-in-Picture automatically in some cases.
22085
         *
22086
         * @method Html5#setDisablePictureInPicture
22087
         * @param {boolean} value
22088
         *         The true value will disable Picture-in-Picture mode.
22089
         *
22090
         * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
22091
         */
22092
        'disablePictureInPicture',
22093
        /**
22094
         * Set the value of `crossOrigin` from the media element. `crossOrigin` indicates
22095
         * to the browser that should sent the cookies along with the requests for the
22096
         * different assets/playlists
22097
         *
22098
         * @method Html5#setCrossOrigin
22099
         * @param {string} crossOrigin
22100
         *         - anonymous indicates that the media should not sent cookies.
22101
         *         - use-credentials indicates that the media should sent cookies along the requests.
22102
         *
22103
         * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
22104
         */
22105
        'crossOrigin'].forEach(function (prop) {
22106
        Html5.prototype['set' + toTitleCase$1(prop)] = function (v) {
22107
            this.el_[prop] = v;
22108
        };
22109
    });
22110
 
22111
    // wrap native functions with a function
22112
    // The list is as follows:
22113
    // pause, load, play
22114
    [
22115
        /**
22116
         * A wrapper around the media elements `pause` function. This will call the `HTML5`
22117
         * media elements `pause` function.
22118
         *
22119
         * @method Html5#pause
22120
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
22121
         */
22122
        'pause',
22123
        /**
22124
         * A wrapper around the media elements `load` function. This will call the `HTML5`s
22125
         * media element `load` function.
22126
         *
22127
         * @method Html5#load
22128
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
22129
         */
22130
        'load',
22131
        /**
22132
         * A wrapper around the media elements `play` function. This will call the `HTML5`s
22133
         * media element `play` function.
22134
         *
22135
         * @method Html5#play
22136
         * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
22137
         */
22138
        'play'].forEach(function (prop) {
22139
        Html5.prototype[prop] = function () {
22140
            return this.el_[prop]();
22141
        };
22142
    });
22143
    Tech.withSourceHandlers(Html5);
22144
 
22145
    /**
22146
     * Native source handler for Html5, simply passes the source to the media element.
22147
     *
22148
     * @property {Tech~SourceObject} source
22149
     *        The source object
22150
     *
22151
     * @property {Html5} tech
22152
     *        The instance of the HTML5 tech.
22153
     */
22154
    Html5.nativeSourceHandler = {};
22155
 
22156
    /**
22157
     * Check if the media element can play the given mime type.
22158
     *
22159
     * @param {string} type
22160
     *        The mimetype to check
22161
     *
22162
     * @return {string}
22163
     *         'probably', 'maybe', or '' (empty string)
22164
     */
22165
    Html5.nativeSourceHandler.canPlayType = function (type) {
22166
        // IE without MediaPlayer throws an error (#519)
22167
        try {
22168
            return Html5.TEST_VID.canPlayType(type);
22169
        } catch (e) {
22170
            return '';
22171
        }
22172
    };
22173
 
22174
    /**
22175
     * Check if the media element can handle a source natively.
22176
     *
22177
     * @param {Tech~SourceObject} source
22178
     *         The source object
22179
     *
22180
     * @param {Object} [options]
22181
     *         Options to be passed to the tech.
22182
     *
22183
     * @return {string}
22184
     *         'probably', 'maybe', or '' (empty string).
22185
     */
22186
    Html5.nativeSourceHandler.canHandleSource = function (source, options) {
22187
        // If a type was provided we should rely on that
22188
        if (source.type) {
22189
            return Html5.nativeSourceHandler.canPlayType(source.type);
22190
 
22191
            // If no type, fall back to checking 'video/[EXTENSION]'
22192
        } else if (source.src) {
22193
            const ext = getFileExtension(source.src);
22194
            return Html5.nativeSourceHandler.canPlayType(`video/${ext}`);
22195
        }
22196
        return '';
22197
    };
22198
 
22199
    /**
22200
     * Pass the source to the native media element.
22201
     *
22202
     * @param {Tech~SourceObject} source
22203
     *        The source object
22204
     *
22205
     * @param {Html5} tech
22206
     *        The instance of the Html5 tech
22207
     *
22208
     * @param {Object} [options]
22209
     *        The options to pass to the source
22210
     */
22211
    Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
22212
        tech.setSrc(source.src);
22213
    };
22214
 
22215
    /**
22216
     * A noop for the native dispose function, as cleanup is not needed.
22217
     */
22218
    Html5.nativeSourceHandler.dispose = function () {};
22219
 
22220
    // Register the native source handler
22221
    Html5.registerSourceHandler(Html5.nativeSourceHandler);
22222
    Tech.registerTech('Html5', Html5);
22223
 
22224
    /**
22225
     * @file player.js
22226
     */
22227
 
22228
        // The following tech events are simply re-triggered
22229
        // on the player when they happen
22230
    const TECH_EVENTS_RETRIGGER = [
22231
            /**
22232
             * Fired while the user agent is downloading media data.
22233
             *
22234
             * @event Player#progress
22235
             * @type {Event}
22236
             */
22237
            /**
22238
             * Retrigger the `progress` event that was triggered by the {@link Tech}.
22239
             *
22240
             * @private
22241
             * @method Player#handleTechProgress_
22242
             * @fires Player#progress
22243
             * @listens Tech#progress
22244
             */
22245
            'progress',
22246
            /**
22247
             * Fires when the loading of an audio/video is aborted.
22248
             *
22249
             * @event Player#abort
22250
             * @type {Event}
22251
             */
22252
            /**
22253
             * Retrigger the `abort` event that was triggered by the {@link Tech}.
22254
             *
22255
             * @private
22256
             * @method Player#handleTechAbort_
22257
             * @fires Player#abort
22258
             * @listens Tech#abort
22259
             */
22260
            'abort',
22261
            /**
22262
             * Fires when the browser is intentionally not getting media data.
22263
             *
22264
             * @event Player#suspend
22265
             * @type {Event}
22266
             */
22267
            /**
22268
             * Retrigger the `suspend` event that was triggered by the {@link Tech}.
22269
             *
22270
             * @private
22271
             * @method Player#handleTechSuspend_
22272
             * @fires Player#suspend
22273
             * @listens Tech#suspend
22274
             */
22275
            'suspend',
22276
            /**
22277
             * Fires when the current playlist is empty.
22278
             *
22279
             * @event Player#emptied
22280
             * @type {Event}
22281
             */
22282
            /**
22283
             * Retrigger the `emptied` event that was triggered by the {@link Tech}.
22284
             *
22285
             * @private
22286
             * @method Player#handleTechEmptied_
22287
             * @fires Player#emptied
22288
             * @listens Tech#emptied
22289
             */
22290
            'emptied',
22291
            /**
22292
             * Fires when the browser is trying to get media data, but data is not available.
22293
             *
22294
             * @event Player#stalled
22295
             * @type {Event}
22296
             */
22297
            /**
22298
             * Retrigger the `stalled` event that was triggered by the {@link Tech}.
22299
             *
22300
             * @private
22301
             * @method Player#handleTechStalled_
22302
             * @fires Player#stalled
22303
             * @listens Tech#stalled
22304
             */
22305
            'stalled',
22306
            /**
22307
             * Fires when the browser has loaded meta data for the audio/video.
22308
             *
22309
             * @event Player#loadedmetadata
22310
             * @type {Event}
22311
             */
22312
            /**
22313
             * Retrigger the `loadedmetadata` event that was triggered by the {@link Tech}.
22314
             *
22315
             * @private
22316
             * @method Player#handleTechLoadedmetadata_
22317
             * @fires Player#loadedmetadata
22318
             * @listens Tech#loadedmetadata
22319
             */
22320
            'loadedmetadata',
22321
            /**
22322
             * Fires when the browser has loaded the current frame of the audio/video.
22323
             *
22324
             * @event Player#loadeddata
22325
             * @type {event}
22326
             */
22327
            /**
22328
             * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
22329
             *
22330
             * @private
22331
             * @method Player#handleTechLoaddeddata_
22332
             * @fires Player#loadeddata
22333
             * @listens Tech#loadeddata
22334
             */
22335
            'loadeddata',
22336
            /**
22337
             * Fires when the current playback position has changed.
22338
             *
22339
             * @event Player#timeupdate
22340
             * @type {event}
22341
             */
22342
            /**
22343
             * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
22344
             *
22345
             * @private
22346
             * @method Player#handleTechTimeUpdate_
22347
             * @fires Player#timeupdate
22348
             * @listens Tech#timeupdate
22349
             */
22350
            'timeupdate',
22351
            /**
22352
             * Fires when the video's intrinsic dimensions change
22353
             *
22354
             * @event Player#resize
22355
             * @type {event}
22356
             */
22357
            /**
22358
             * Retrigger the `resize` event that was triggered by the {@link Tech}.
22359
             *
22360
             * @private
22361
             * @method Player#handleTechResize_
22362
             * @fires Player#resize
22363
             * @listens Tech#resize
22364
             */
22365
            'resize',
22366
            /**
22367
             * Fires when the volume has been changed
22368
             *
22369
             * @event Player#volumechange
22370
             * @type {event}
22371
             */
22372
            /**
22373
             * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
22374
             *
22375
             * @private
22376
             * @method Player#handleTechVolumechange_
22377
             * @fires Player#volumechange
22378
             * @listens Tech#volumechange
22379
             */
22380
            'volumechange',
22381
            /**
22382
             * Fires when the text track has been changed
22383
             *
22384
             * @event Player#texttrackchange
22385
             * @type {event}
22386
             */
22387
            /**
22388
             * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
22389
             *
22390
             * @private
22391
             * @method Player#handleTechTexttrackchange_
22392
             * @fires Player#texttrackchange
22393
             * @listens Tech#texttrackchange
22394
             */
22395
            'texttrackchange'];
22396
 
22397
    // events to queue when playback rate is zero
22398
    // this is a hash for the sole purpose of mapping non-camel-cased event names
22399
    // to camel-cased function names
22400
    const TECH_EVENTS_QUEUE = {
22401
        canplay: 'CanPlay',
22402
        canplaythrough: 'CanPlayThrough',
22403
        playing: 'Playing',
22404
        seeked: 'Seeked'
22405
    };
22406
    const BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
22407
    const BREAKPOINT_CLASSES = {};
22408
 
22409
    // grep: vjs-layout-tiny
22410
    // grep: vjs-layout-x-small
22411
    // grep: vjs-layout-small
22412
    // grep: vjs-layout-medium
22413
    // grep: vjs-layout-large
22414
    // grep: vjs-layout-x-large
22415
    // grep: vjs-layout-huge
22416
    BREAKPOINT_ORDER.forEach(k => {
22417
        const v = k.charAt(0) === 'x' ? `x-${k.substring(1)}` : k;
22418
        BREAKPOINT_CLASSES[k] = `vjs-layout-${v}`;
22419
    });
22420
    const DEFAULT_BREAKPOINTS = {
22421
        tiny: 210,
22422
        xsmall: 320,
22423
        small: 425,
22424
        medium: 768,
22425
        large: 1440,
22426
        xlarge: 2560,
22427
        huge: Infinity
22428
    };
22429
 
22430
    /**
22431
     * An instance of the `Player` class is created when any of the Video.js setup methods
22432
     * are used to initialize a video.
22433
     *
22434
     * After an instance has been created it can be accessed globally in three ways:
22435
     * 1. By calling `videojs.getPlayer('example_video_1');`
22436
     * 2. By calling `videojs('example_video_1');` (not recommended)
22437
     * 2. By using it directly via `videojs.players.example_video_1;`
22438
     *
22439
     * @extends Component
22440
     * @global
22441
     */
22442
    class Player extends Component$1 {
22443
        /**
22444
         * Create an instance of this class.
22445
         *
22446
         * @param {Element} tag
22447
         *        The original video DOM element used for configuring options.
22448
         *
22449
         * @param {Object} [options]
22450
         *        Object of option names and values.
22451
         *
22452
         * @param {Function} [ready]
22453
         *        Ready callback function.
22454
         */
22455
        constructor(tag, options, ready) {
22456
            // Make sure tag ID exists
22457
            // also here.. probably better
22458
            tag.id = tag.id || options.id || `vjs_video_${newGUID()}`;
22459
 
22460
            // Set Options
22461
            // The options argument overrides options set in the video tag
22462
            // which overrides globally set options.
22463
            // This latter part coincides with the load order
22464
            // (tag must exist before Player)
22465
            options = Object.assign(Player.getTagSettings(tag), options);
22466
 
22467
            // Delay the initialization of children because we need to set up
22468
            // player properties first, and can't use `this` before `super()`
22469
            options.initChildren = false;
22470
 
22471
            // Same with creating the element
22472
            options.createEl = false;
22473
 
22474
            // don't auto mixin the evented mixin
22475
            options.evented = false;
22476
 
22477
            // we don't want the player to report touch activity on itself
22478
            // see enableTouchActivity in Component
22479
            options.reportTouchActivity = false;
22480
 
22481
            // If language is not set, get the closest lang attribute
22482
            if (!options.language) {
22483
                const closest = tag.closest('[lang]');
22484
                if (closest) {
22485
                    options.language = closest.getAttribute('lang');
22486
                }
22487
            }
22488
 
22489
            // Run base component initializing with new options
22490
            super(null, options, ready);
22491
 
22492
            // Create bound methods for document listeners.
22493
            this.boundDocumentFullscreenChange_ = e => this.documentFullscreenChange_(e);
22494
            this.boundFullWindowOnEscKey_ = e => this.fullWindowOnEscKey(e);
22495
            this.boundUpdateStyleEl_ = e => this.updateStyleEl_(e);
22496
            this.boundApplyInitTime_ = e => this.applyInitTime_(e);
22497
            this.boundUpdateCurrentBreakpoint_ = e => this.updateCurrentBreakpoint_(e);
22498
            this.boundHandleTechClick_ = e => this.handleTechClick_(e);
22499
            this.boundHandleTechDoubleClick_ = e => this.handleTechDoubleClick_(e);
22500
            this.boundHandleTechTouchStart_ = e => this.handleTechTouchStart_(e);
22501
            this.boundHandleTechTouchMove_ = e => this.handleTechTouchMove_(e);
22502
            this.boundHandleTechTouchEnd_ = e => this.handleTechTouchEnd_(e);
22503
            this.boundHandleTechTap_ = e => this.handleTechTap_(e);
22504
 
22505
            // default isFullscreen_ to false
22506
            this.isFullscreen_ = false;
22507
 
22508
            // create logger
22509
            this.log = createLogger(this.id_);
22510
 
22511
            // Hold our own reference to fullscreen api so it can be mocked in tests
22512
            this.fsApi_ = FullscreenApi;
22513
 
22514
            // Tracks when a tech changes the poster
22515
            this.isPosterFromTech_ = false;
22516
 
22517
            // Holds callback info that gets queued when playback rate is zero
22518
            // and a seek is happening
22519
            this.queuedCallbacks_ = [];
22520
 
22521
            // Turn off API access because we're loading a new tech that might load asynchronously
22522
            this.isReady_ = false;
22523
 
22524
            // Init state hasStarted_
22525
            this.hasStarted_ = false;
22526
 
22527
            // Init state userActive_
22528
            this.userActive_ = false;
22529
 
22530
            // Init debugEnabled_
22531
            this.debugEnabled_ = false;
22532
 
22533
            // Init state audioOnlyMode_
22534
            this.audioOnlyMode_ = false;
22535
 
22536
            // Init state audioPosterMode_
22537
            this.audioPosterMode_ = false;
22538
 
22539
            // Init state audioOnlyCache_
22540
            this.audioOnlyCache_ = {
22541
                playerHeight: null,
22542
                hiddenChildren: []
22543
            };
22544
 
22545
            // if the global option object was accidentally blown away by
22546
            // someone, bail early with an informative error
22547
            if (!this.options_ || !this.options_.techOrder || !this.options_.techOrder.length) {
22548
                throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
22549
            }
22550
 
22551
            // Store the original tag used to set options
22552
            this.tag = tag;
22553
 
22554
            // Store the tag attributes used to restore html5 element
22555
            this.tagAttributes = tag && getAttributes(tag);
22556
 
22557
            // Update current language
22558
            this.language(this.options_.language);
22559
 
22560
            // Update Supported Languages
22561
            if (options.languages) {
22562
                // Normalise player option languages to lowercase
22563
                const languagesToLower = {};
22564
                Object.getOwnPropertyNames(options.languages).forEach(function (name) {
22565
                    languagesToLower[name.toLowerCase()] = options.languages[name];
22566
                });
22567
                this.languages_ = languagesToLower;
22568
            } else {
22569
                this.languages_ = Player.prototype.options_.languages;
22570
            }
22571
            this.resetCache_();
22572
 
22573
            // Set poster
22574
            /** @type string */
22575
            this.poster_ = options.poster || '';
22576
 
22577
            // Set controls
22578
            /** @type {boolean} */
22579
            this.controls_ = !!options.controls;
22580
 
22581
            // Original tag settings stored in options
22582
            // now remove immediately so native controls don't flash.
22583
            // May be turned back on by HTML5 tech if nativeControlsForTouch is true
22584
            tag.controls = false;
22585
            tag.removeAttribute('controls');
22586
            this.changingSrc_ = false;
22587
            this.playCallbacks_ = [];
22588
            this.playTerminatedQueue_ = [];
22589
 
22590
            // the attribute overrides the option
22591
            if (tag.hasAttribute('autoplay')) {
22592
                this.autoplay(true);
22593
            } else {
22594
                // otherwise use the setter to validate and
22595
                // set the correct value.
22596
                this.autoplay(this.options_.autoplay);
22597
            }
22598
 
22599
            // check plugins
22600
            if (options.plugins) {
22601
                Object.keys(options.plugins).forEach(name => {
22602
                    if (typeof this[name] !== 'function') {
22603
                        throw new Error(`plugin "${name}" does not exist`);
22604
                    }
22605
                });
22606
            }
22607
 
22608
            /*
22609
       * Store the internal state of scrubbing
22610
       *
22611
       * @private
22612
       * @return {Boolean} True if the user is scrubbing
22613
       */
22614
            this.scrubbing_ = false;
22615
            this.el_ = this.createEl();
22616
 
22617
            // Make this an evented object and use `el_` as its event bus.
22618
            evented(this, {
22619
                eventBusKey: 'el_'
22620
            });
22621
 
22622
            // listen to document and player fullscreenchange handlers so we receive those events
22623
            // before a user can receive them so we can update isFullscreen appropriately.
22624
            // make sure that we listen to fullscreenchange events before everything else to make sure that
22625
            // our isFullscreen method is updated properly for internal components as well as external.
22626
            if (this.fsApi_.requestFullscreen) {
22627
                on(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
22628
                this.on(this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
22629
            }
22630
            if (this.fluid_) {
22631
                this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
22632
            }
22633
            // We also want to pass the original player options to each component and plugin
22634
            // as well so they don't need to reach back into the player for options later.
22635
            // We also need to do another copy of this.options_ so we don't end up with
22636
            // an infinite loop.
22637
            const playerOptionsCopy = merge$2(this.options_);
22638
 
22639
            // Load plugins
22640
            if (options.plugins) {
22641
                Object.keys(options.plugins).forEach(name => {
22642
                    this[name](options.plugins[name]);
22643
                });
22644
            }
22645
 
22646
            // Enable debug mode to fire debugon event for all plugins.
22647
            if (options.debug) {
22648
                this.debug(true);
22649
            }
22650
            this.options_.playerOptions = playerOptionsCopy;
22651
            this.middleware_ = [];
22652
            this.playbackRates(options.playbackRates);
22653
            if (options.experimentalSvgIcons) {
22654
                // Add SVG Sprite to the DOM
22655
                const parser = new window.DOMParser();
22656
                const parsedSVG = parser.parseFromString(icons, 'image/svg+xml');
22657
                const errorNode = parsedSVG.querySelector('parsererror');
22658
                if (errorNode) {
22659
                    log$1.warn('Failed to load SVG Icons. Falling back to Font Icons.');
22660
                    this.options_.experimentalSvgIcons = null;
22661
                } else {
22662
                    const sprite = parsedSVG.documentElement;
22663
                    sprite.style.display = 'none';
22664
                    this.el_.appendChild(sprite);
22665
                    this.addClass('vjs-svg-icons-enabled');
22666
                }
22667
            }
22668
            this.initChildren();
22669
 
22670
            // Set isAudio based on whether or not an audio tag was used
22671
            this.isAudio(tag.nodeName.toLowerCase() === 'audio');
22672
 
22673
            // Update controls className. Can't do this when the controls are initially
22674
            // set because the element doesn't exist yet.
22675
            if (this.controls()) {
22676
                this.addClass('vjs-controls-enabled');
22677
            } else {
22678
                this.addClass('vjs-controls-disabled');
22679
            }
22680
 
22681
            // Set ARIA label and region role depending on player type
22682
            this.el_.setAttribute('role', 'region');
22683
            if (this.isAudio()) {
22684
                this.el_.setAttribute('aria-label', this.localize('Audio Player'));
22685
            } else {
22686
                this.el_.setAttribute('aria-label', this.localize('Video Player'));
22687
            }
22688
            if (this.isAudio()) {
22689
                this.addClass('vjs-audio');
22690
            }
22691
 
22692
            // TODO: Make this smarter. Toggle user state between touching/mousing
22693
            // using events, since devices can have both touch and mouse events.
22694
            // TODO: Make this check be performed again when the window switches between monitors
22695
            // (See https://github.com/videojs/video.js/issues/5683)
22696
            if (TOUCH_ENABLED) {
22697
                this.addClass('vjs-touch-enabled');
22698
            }
22699
 
22700
            // iOS Safari has broken hover handling
22701
            if (!IS_IOS) {
22702
                this.addClass('vjs-workinghover');
22703
            }
22704
 
22705
            // Make player easily findable by ID
22706
            Player.players[this.id_] = this;
22707
 
22708
            // Add a major version class to aid css in plugins
22709
            const majorVersion = version$5.split('.')[0];
22710
            this.addClass(`vjs-v${majorVersion}`);
22711
 
22712
            // When the player is first initialized, trigger activity so components
22713
            // like the control bar show themselves if needed
22714
            this.userActive(true);
22715
            this.reportUserActivity();
22716
            this.one('play', e => this.listenForUserActivity_(e));
22717
            this.on('keydown', e => this.handleKeyDown(e));
22718
            this.on('languagechange', e => this.handleLanguagechange(e));
22719
            this.breakpoints(this.options_.breakpoints);
22720
            this.responsive(this.options_.responsive);
22721
 
22722
            // Calling both the audio mode methods after the player is fully
22723
            // setup to be able to listen to the events triggered by them
22724
            this.on('ready', () => {
22725
                // Calling the audioPosterMode method first so that
22726
                // the audioOnlyMode can take precedence when both options are set to true
22727
                this.audioPosterMode(this.options_.audioPosterMode);
22728
                this.audioOnlyMode(this.options_.audioOnlyMode);
22729
            });
22730
        }
22731
 
22732
        /**
22733
         * Destroys the video player and does any necessary cleanup.
22734
         *
22735
         * This is especially helpful if you are dynamically adding and removing videos
22736
         * to/from the DOM.
22737
         *
22738
         * @fires Player#dispose
22739
         */
22740
        dispose() {
22741
            /**
22742
             * Called when the player is being disposed of.
22743
             *
22744
             * @event Player#dispose
22745
             * @type {Event}
22746
             */
22747
            this.trigger('dispose');
22748
            // prevent dispose from being called twice
22749
            this.off('dispose');
22750
 
22751
            // Make sure all player-specific document listeners are unbound. This is
22752
            off(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
22753
            off(document, 'keydown', this.boundFullWindowOnEscKey_);
22754
            if (this.styleEl_ && this.styleEl_.parentNode) {
22755
                this.styleEl_.parentNode.removeChild(this.styleEl_);
22756
                this.styleEl_ = null;
22757
            }
22758
 
22759
            // Kill reference to this player
22760
            Player.players[this.id_] = null;
22761
            if (this.tag && this.tag.player) {
22762
                this.tag.player = null;
22763
            }
22764
            if (this.el_ && this.el_.player) {
22765
                this.el_.player = null;
22766
            }
22767
            if (this.tech_) {
22768
                this.tech_.dispose();
22769
                this.isPosterFromTech_ = false;
22770
                this.poster_ = '';
22771
            }
22772
            if (this.playerElIngest_) {
22773
                this.playerElIngest_ = null;
22774
            }
22775
            if (this.tag) {
22776
                this.tag = null;
22777
            }
22778
            clearCacheForPlayer(this);
22779
 
22780
            // remove all event handlers for track lists
22781
            // all tracks and track listeners are removed on
22782
            // tech dispose
22783
            ALL.names.forEach(name => {
22784
                const props = ALL[name];
22785
                const list = this[props.getterName]();
22786
 
22787
                // if it is not a native list
22788
                // we have to manually remove event listeners
22789
                if (list && list.off) {
22790
                    list.off();
22791
                }
22792
            });
22793
 
22794
            // the actual .el_ is removed here, or replaced if
22795
            super.dispose({
22796
                restoreEl: this.options_.restoreEl
22797
            });
22798
        }
22799
 
22800
        /**
22801
         * Create the `Player`'s DOM element.
22802
         *
22803
         * @return {Element}
22804
         *         The DOM element that gets created.
22805
         */
22806
        createEl() {
22807
            let tag = this.tag;
22808
            let el;
22809
            let playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
22810
            const divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
22811
            if (playerElIngest) {
22812
                el = this.el_ = tag.parentNode;
22813
            } else if (!divEmbed) {
22814
                el = this.el_ = super.createEl('div');
22815
            }
22816
 
22817
            // Copy over all the attributes from the tag, including ID and class
22818
            // ID will now reference player box, not the video tag
22819
            const attrs = getAttributes(tag);
22820
            if (divEmbed) {
22821
                el = this.el_ = tag;
22822
                tag = this.tag = document.createElement('video');
22823
                while (el.children.length) {
22824
                    tag.appendChild(el.firstChild);
22825
                }
22826
                if (!hasClass(el, 'video-js')) {
22827
                    addClass(el, 'video-js');
22828
                }
22829
                el.appendChild(tag);
22830
                playerElIngest = this.playerElIngest_ = el;
22831
                // move properties over from our custom `video-js` element
22832
                // to our new `video` element. This will move things like
22833
                // `src` or `controls` that were set via js before the player
22834
                // was initialized.
22835
                Object.keys(el).forEach(k => {
22836
                    try {
22837
                        tag[k] = el[k];
22838
                    } catch (e) {
22839
                        // we got a a property like outerHTML which we can't actually copy, ignore it
22840
                    }
22841
                });
22842
            }
22843
 
22844
            // set tabindex to -1 to remove the video element from the focus order
22845
            tag.setAttribute('tabindex', '-1');
22846
            attrs.tabindex = '-1';
22847
 
22848
            // Workaround for #4583 on Chrome (on Windows) with JAWS.
22849
            // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
22850
            // Note that we can't detect if JAWS is being used, but this ARIA attribute
22851
            // doesn't change behavior of Chrome if JAWS is not being used
22852
            if (IS_CHROME && IS_WINDOWS) {
22853
                tag.setAttribute('role', 'application');
22854
                attrs.role = 'application';
22855
            }
22856
 
22857
            // Remove width/height attrs from tag so CSS can make it 100% width/height
22858
            tag.removeAttribute('width');
22859
            tag.removeAttribute('height');
22860
            if ('width' in attrs) {
22861
                delete attrs.width;
22862
            }
22863
            if ('height' in attrs) {
22864
                delete attrs.height;
22865
            }
22866
            Object.getOwnPropertyNames(attrs).forEach(function (attr) {
22867
                // don't copy over the class attribute to the player element when we're in a div embed
22868
                // the class is already set up properly in the divEmbed case
22869
                // and we want to make sure that the `video-js` class doesn't get lost
22870
                if (!(divEmbed && attr === 'class')) {
22871
                    el.setAttribute(attr, attrs[attr]);
22872
                }
22873
                if (divEmbed) {
22874
                    tag.setAttribute(attr, attrs[attr]);
22875
                }
22876
            });
22877
 
22878
            // Update tag id/class for use as HTML5 playback tech
22879
            // Might think we should do this after embedding in container so .vjs-tech class
22880
            // doesn't flash 100% width/height, but class only applies with .video-js parent
22881
            tag.playerId = tag.id;
22882
            tag.id += '_html5_api';
22883
            tag.className = 'vjs-tech';
22884
 
22885
            // Make player findable on elements
22886
            tag.player = el.player = this;
22887
            // Default state of video is paused
22888
            this.addClass('vjs-paused');
22889
 
22890
            // Add a style element in the player that we'll use to set the width/height
22891
            // of the player in a way that's still overridable by CSS, just like the
22892
            // video element
22893
            if (window.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
22894
                this.styleEl_ = createStyleElement('vjs-styles-dimensions');
22895
                const defaultsStyleEl = $('.vjs-styles-defaults');
22896
                const head = $('head');
22897
                head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
22898
            }
22899
            this.fill_ = false;
22900
            this.fluid_ = false;
22901
 
22902
            // Pass in the width/height/aspectRatio options which will update the style el
22903
            this.width(this.options_.width);
22904
            this.height(this.options_.height);
22905
            this.fill(this.options_.fill);
22906
            this.fluid(this.options_.fluid);
22907
            this.aspectRatio(this.options_.aspectRatio);
22908
            // support both crossOrigin and crossorigin to reduce confusion and issues around the name
22909
            this.crossOrigin(this.options_.crossOrigin || this.options_.crossorigin);
22910
 
22911
            // Hide any links within the video/audio tag,
22912
            // because IE doesn't hide them completely from screen readers.
22913
            const links = tag.getElementsByTagName('a');
22914
            for (let i = 0; i < links.length; i++) {
22915
                const linkEl = links.item(i);
22916
                addClass(linkEl, 'vjs-hidden');
22917
                linkEl.setAttribute('hidden', 'hidden');
22918
            }
22919
 
22920
            // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
22921
            // keep track of the original for later so we can know if the source originally failed
22922
            tag.initNetworkState_ = tag.networkState;
22923
 
22924
            // Wrap video tag in div (el/box) container
22925
            if (tag.parentNode && !playerElIngest) {
22926
                tag.parentNode.insertBefore(el, tag);
22927
            }
22928
 
22929
            // insert the tag as the first child of the player element
22930
            // then manually add it to the children array so that this.addChild
22931
            // will work properly for other components
22932
            //
22933
            // Breaks iPhone, fixed in HTML5 setup.
22934
            prependTo(tag, el);
22935
            this.children_.unshift(tag);
22936
 
22937
            // Set lang attr on player to ensure CSS :lang() in consistent with player
22938
            // if it's been set to something different to the doc
22939
            this.el_.setAttribute('lang', this.language_);
22940
            this.el_.setAttribute('translate', 'no');
22941
            this.el_ = el;
22942
            return el;
22943
        }
22944
 
22945
        /**
22946
         * Get or set the `Player`'s crossOrigin option. For the HTML5 player, this
22947
         * sets the `crossOrigin` property on the `<video>` tag to control the CORS
22948
         * behavior.
22949
         *
22950
         * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
22951
         *
22952
         * @param {string|null} [value]
22953
         *        The value to set the `Player`'s crossOrigin to. If an argument is
22954
         *        given, must be one of `'anonymous'` or `'use-credentials'`, or 'null'.
22955
         *
22956
         * @return {string|null|undefined}
22957
         *         - The current crossOrigin value of the `Player` when getting.
22958
         *         - undefined when setting
22959
         */
22960
        crossOrigin(value) {
22961
            // `null` can be set to unset a value
22962
            if (typeof value === 'undefined') {
22963
                return this.techGet_('crossOrigin');
22964
            }
22965
            if (value !== null && value !== 'anonymous' && value !== 'use-credentials') {
22966
                log$1.warn(`crossOrigin must be null,  "anonymous" or "use-credentials", given "${value}"`);
22967
                return;
22968
            }
22969
            this.techCall_('setCrossOrigin', value);
22970
            if (this.posterImage) {
22971
                this.posterImage.crossOrigin(value);
22972
            }
22973
            return;
22974
        }
22975
 
22976
        /**
22977
         * A getter/setter for the `Player`'s width. Returns the player's configured value.
22978
         * To get the current width use `currentWidth()`.
22979
         *
22980
         * @param {number|string} [value]
22981
         *        CSS value to set the `Player`'s width to.
22982
         *
22983
         * @return {number|undefined}
22984
         *         - The current width of the `Player` when getting.
22985
         *         - Nothing when setting
22986
         */
22987
        width(value) {
22988
            return this.dimension('width', value);
22989
        }
22990
 
22991
        /**
22992
         * A getter/setter for the `Player`'s height. Returns the player's configured value.
22993
         * To get the current height use `currentheight()`.
22994
         *
22995
         * @param {number|string} [value]
22996
         *        CSS value to set the `Player`'s height to.
22997
         *
22998
         * @return {number|undefined}
22999
         *         - The current height of the `Player` when getting.
23000
         *         - Nothing when setting
23001
         */
23002
        height(value) {
23003
            return this.dimension('height', value);
23004
        }
23005
 
23006
        /**
23007
         * A getter/setter for the `Player`'s width & height.
23008
         *
23009
         * @param {string} dimension
23010
         *        This string can be:
23011
         *        - 'width'
23012
         *        - 'height'
23013
         *
23014
         * @param {number|string} [value]
23015
         *        Value for dimension specified in the first argument.
23016
         *
23017
         * @return {number}
23018
         *         The dimension arguments value when getting (width/height).
23019
         */
23020
        dimension(dimension, value) {
23021
            const privDimension = dimension + '_';
23022
            if (value === undefined) {
23023
                return this[privDimension] || 0;
23024
            }
23025
            if (value === '' || value === 'auto') {
23026
                // If an empty string is given, reset the dimension to be automatic
23027
                this[privDimension] = undefined;
23028
                this.updateStyleEl_();
23029
                return;
23030
            }
23031
            const parsedVal = parseFloat(value);
23032
            if (isNaN(parsedVal)) {
23033
                log$1.error(`Improper value "${value}" supplied for for ${dimension}`);
23034
                return;
23035
            }
23036
            this[privDimension] = parsedVal;
23037
            this.updateStyleEl_();
23038
        }
23039
 
23040
        /**
23041
         * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
23042
         *
23043
         * Turning this on will turn off fill mode.
23044
         *
23045
         * @param {boolean} [bool]
23046
         *        - A value of true adds the class.
23047
         *        - A value of false removes the class.
23048
         *        - No value will be a getter.
23049
         *
23050
         * @return {boolean|undefined}
23051
         *         - The value of fluid when getting.
23052
         *         - `undefined` when setting.
23053
         */
23054
        fluid(bool) {
23055
            if (bool === undefined) {
23056
                return !!this.fluid_;
23057
            }
23058
            this.fluid_ = !!bool;
23059
            if (isEvented(this)) {
23060
                this.off(['playerreset', 'resize'], this.boundUpdateStyleEl_);
23061
            }
23062
            if (bool) {
23063
                this.addClass('vjs-fluid');
23064
                this.fill(false);
23065
                addEventedCallback(this, () => {
23066
                    this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
23067
                });
23068
            } else {
23069
                this.removeClass('vjs-fluid');
23070
            }
23071
            this.updateStyleEl_();
23072
        }
23073
 
23074
        /**
23075
         * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
23076
         *
23077
         * Turning this on will turn off fluid mode.
23078
         *
23079
         * @param {boolean} [bool]
23080
         *        - A value of true adds the class.
23081
         *        - A value of false removes the class.
23082
         *        - No value will be a getter.
23083
         *
23084
         * @return {boolean|undefined}
23085
         *         - The value of fluid when getting.
23086
         *         - `undefined` when setting.
23087
         */
23088
        fill(bool) {
23089
            if (bool === undefined) {
23090
                return !!this.fill_;
23091
            }
23092
            this.fill_ = !!bool;
23093
            if (bool) {
23094
                this.addClass('vjs-fill');
23095
                this.fluid(false);
23096
            } else {
23097
                this.removeClass('vjs-fill');
23098
            }
23099
        }
23100
 
23101
        /**
23102
         * Get/Set the aspect ratio
23103
         *
23104
         * @param {string} [ratio]
23105
         *        Aspect ratio for player
23106
         *
23107
         * @return {string|undefined}
23108
         *         returns the current aspect ratio when getting
23109
         */
23110
 
23111
        /**
23112
         * A getter/setter for the `Player`'s aspect ratio.
23113
         *
23114
         * @param {string} [ratio]
23115
         *        The value to set the `Player`'s aspect ratio to.
23116
         *
23117
         * @return {string|undefined}
23118
         *         - The current aspect ratio of the `Player` when getting.
23119
         *         - undefined when setting
23120
         */
23121
        aspectRatio(ratio) {
23122
            if (ratio === undefined) {
23123
                return this.aspectRatio_;
23124
            }
23125
 
23126
            // Check for width:height format
23127
            if (!/^\d+\:\d+$/.test(ratio)) {
23128
                throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
23129
            }
23130
            this.aspectRatio_ = ratio;
23131
 
23132
            // We're assuming if you set an aspect ratio you want fluid mode,
23133
            // because in fixed mode you could calculate width and height yourself.
23134
            this.fluid(true);
23135
            this.updateStyleEl_();
23136
        }
23137
 
23138
        /**
23139
         * Update styles of the `Player` element (height, width and aspect ratio).
23140
         *
23141
         * @private
23142
         * @listens Tech#loadedmetadata
23143
         */
23144
        updateStyleEl_() {
23145
            if (window.VIDEOJS_NO_DYNAMIC_STYLE === true) {
23146
                const width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
23147
                const height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
23148
                const techEl = this.tech_ && this.tech_.el();
23149
                if (techEl) {
23150
                    if (width >= 0) {
23151
                        techEl.width = width;
23152
                    }
23153
                    if (height >= 0) {
23154
                        techEl.height = height;
23155
                    }
23156
                }
23157
                return;
23158
            }
23159
            let width;
23160
            let height;
23161
            let aspectRatio;
23162
            let idClass;
23163
 
23164
            // The aspect ratio is either used directly or to calculate width and height.
23165
            if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
23166
                // Use any aspectRatio that's been specifically set
23167
                aspectRatio = this.aspectRatio_;
23168
            } else if (this.videoWidth() > 0) {
23169
                // Otherwise try to get the aspect ratio from the video metadata
23170
                aspectRatio = this.videoWidth() + ':' + this.videoHeight();
23171
            } else {
23172
                // Or use a default. The video element's is 2:1, but 16:9 is more common.
23173
                aspectRatio = '16:9';
23174
            }
23175
 
23176
            // Get the ratio as a decimal we can use to calculate dimensions
23177
            const ratioParts = aspectRatio.split(':');
23178
            const ratioMultiplier = ratioParts[1] / ratioParts[0];
23179
            if (this.width_ !== undefined) {
23180
                // Use any width that's been specifically set
23181
                width = this.width_;
23182
            } else if (this.height_ !== undefined) {
23183
                // Or calculate the width from the aspect ratio if a height has been set
23184
                width = this.height_ / ratioMultiplier;
23185
            } else {
23186
                // Or use the video's metadata, or use the video el's default of 300
23187
                width = this.videoWidth() || 300;
23188
            }
23189
            if (this.height_ !== undefined) {
23190
                // Use any height that's been specifically set
23191
                height = this.height_;
23192
            } else {
23193
                // Otherwise calculate the height from the ratio and the width
23194
                height = width * ratioMultiplier;
23195
            }
23196
 
23197
            // Ensure the CSS class is valid by starting with an alpha character
23198
            if (/^[^a-zA-Z]/.test(this.id())) {
23199
                idClass = 'dimensions-' + this.id();
23200
            } else {
23201
                idClass = this.id() + '-dimensions';
23202
            }
23203
 
23204
            // Ensure the right class is still on the player for the style element
23205
            this.addClass(idClass);
23206
            setTextContent(this.styleEl_, `
23207
      .${idClass} {
23208
        width: ${width}px;
23209
        height: ${height}px;
23210
      }
23211
 
23212
      .${idClass}.vjs-fluid:not(.vjs-audio-only-mode) {
23213
        padding-top: ${ratioMultiplier * 100}%;
23214
      }
23215
    `);
23216
        }
23217
 
23218
        /**
23219
         * Load/Create an instance of playback {@link Tech} including element
23220
         * and API methods. Then append the `Tech` element in `Player` as a child.
23221
         *
23222
         * @param {string} techName
23223
         *        name of the playback technology
23224
         *
23225
         * @param {string} source
23226
         *        video source
23227
         *
23228
         * @private
23229
         */
23230
        loadTech_(techName, source) {
23231
            // Pause and remove current playback technology
23232
            if (this.tech_) {
23233
                this.unloadTech_();
23234
            }
23235
            const titleTechName = toTitleCase$1(techName);
23236
            const camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1);
23237
 
23238
            // get rid of the HTML5 video tag as soon as we are using another tech
23239
            if (titleTechName !== 'Html5' && this.tag) {
23240
                Tech.getTech('Html5').disposeMediaElement(this.tag);
23241
                this.tag.player = null;
23242
                this.tag = null;
23243
            }
23244
            this.techName_ = titleTechName;
23245
 
23246
            // Turn off API access because we're loading a new tech that might load asynchronously
23247
            this.isReady_ = false;
23248
            let autoplay = this.autoplay();
23249
 
23250
            // if autoplay is a string (or `true` with normalizeAutoplay: true) we pass false to the tech
23251
            // because the player is going to handle autoplay on `loadstart`
23252
            if (typeof this.autoplay() === 'string' || this.autoplay() === true && this.options_.normalizeAutoplay) {
23253
                autoplay = false;
23254
            }
23255
 
23256
            // Grab tech-specific options from player options and add source and parent element to use.
23257
            const techOptions = {
23258
                source,
23259
                autoplay,
23260
                'nativeControlsForTouch': this.options_.nativeControlsForTouch,
23261
                'playerId': this.id(),
23262
                'techId': `${this.id()}_${camelTechName}_api`,
23263
                'playsinline': this.options_.playsinline,
23264
                'preload': this.options_.preload,
23265
                'loop': this.options_.loop,
23266
                'disablePictureInPicture': this.options_.disablePictureInPicture,
23267
                'muted': this.options_.muted,
23268
                'poster': this.poster(),
23269
                'language': this.language(),
23270
                'playerElIngest': this.playerElIngest_ || false,
23271
                'vtt.js': this.options_['vtt.js'],
23272
                'canOverridePoster': !!this.options_.techCanOverridePoster,
23273
                'enableSourceset': this.options_.enableSourceset
23274
            };
23275
            ALL.names.forEach(name => {
23276
                const props = ALL[name];
23277
                techOptions[props.getterName] = this[props.privateName];
23278
            });
23279
            Object.assign(techOptions, this.options_[titleTechName]);
23280
            Object.assign(techOptions, this.options_[camelTechName]);
23281
            Object.assign(techOptions, this.options_[techName.toLowerCase()]);
23282
            if (this.tag) {
23283
                techOptions.tag = this.tag;
23284
            }
23285
            if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
23286
                techOptions.startTime = this.cache_.currentTime;
23287
            }
23288
 
23289
            // Initialize tech instance
23290
            const TechClass = Tech.getTech(techName);
23291
            if (!TechClass) {
23292
                throw new Error(`No Tech named '${titleTechName}' exists! '${titleTechName}' should be registered using videojs.registerTech()'`);
23293
            }
23294
            this.tech_ = new TechClass(techOptions);
23295
 
23296
            // player.triggerReady is always async, so don't need this to be async
23297
            this.tech_.ready(bind_(this, this.handleTechReady_), true);
23298
            textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_);
23299
 
23300
            // Listen to all HTML5-defined events and trigger them on the player
23301
            TECH_EVENTS_RETRIGGER.forEach(event => {
23302
                this.on(this.tech_, event, e => this[`handleTech${toTitleCase$1(event)}_`](e));
23303
            });
23304
            Object.keys(TECH_EVENTS_QUEUE).forEach(event => {
23305
                this.on(this.tech_, event, eventObj => {
23306
                    if (this.tech_.playbackRate() === 0 && this.tech_.seeking()) {
23307
                        this.queuedCallbacks_.push({
23308
                            callback: this[`handleTech${TECH_EVENTS_QUEUE[event]}_`].bind(this),
23309
                            event: eventObj
23310
                        });
23311
                        return;
23312
                    }
23313
                    this[`handleTech${TECH_EVENTS_QUEUE[event]}_`](eventObj);
23314
                });
23315
            });
23316
            this.on(this.tech_, 'loadstart', e => this.handleTechLoadStart_(e));
23317
            this.on(this.tech_, 'sourceset', e => this.handleTechSourceset_(e));
23318
            this.on(this.tech_, 'waiting', e => this.handleTechWaiting_(e));
23319
            this.on(this.tech_, 'ended', e => this.handleTechEnded_(e));
23320
            this.on(this.tech_, 'seeking', e => this.handleTechSeeking_(e));
23321
            this.on(this.tech_, 'play', e => this.handleTechPlay_(e));
23322
            this.on(this.tech_, 'pause', e => this.handleTechPause_(e));
23323
            this.on(this.tech_, 'durationchange', e => this.handleTechDurationChange_(e));
23324
            this.on(this.tech_, 'fullscreenchange', (e, data) => this.handleTechFullscreenChange_(e, data));
23325
            this.on(this.tech_, 'fullscreenerror', (e, err) => this.handleTechFullscreenError_(e, err));
23326
            this.on(this.tech_, 'enterpictureinpicture', e => this.handleTechEnterPictureInPicture_(e));
23327
            this.on(this.tech_, 'leavepictureinpicture', e => this.handleTechLeavePictureInPicture_(e));
23328
            this.on(this.tech_, 'error', e => this.handleTechError_(e));
23329
            this.on(this.tech_, 'posterchange', e => this.handleTechPosterChange_(e));
23330
            this.on(this.tech_, 'textdata', e => this.handleTechTextData_(e));
23331
            this.on(this.tech_, 'ratechange', e => this.handleTechRateChange_(e));
23332
            this.on(this.tech_, 'loadedmetadata', this.boundUpdateStyleEl_);
23333
            this.usingNativeControls(this.techGet_('controls'));
23334
            if (this.controls() && !this.usingNativeControls()) {
23335
                this.addTechControlsListeners_();
23336
            }
23337
 
23338
            // Add the tech element in the DOM if it was not already there
23339
            // Make sure to not insert the original video element if using Html5
23340
            if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
23341
                prependTo(this.tech_.el(), this.el());
23342
            }
23343
 
23344
            // Get rid of the original video tag reference after the first tech is loaded
23345
            if (this.tag) {
23346
                this.tag.player = null;
23347
                this.tag = null;
23348
            }
23349
        }
23350
 
23351
        /**
23352
         * Unload and dispose of the current playback {@link Tech}.
23353
         *
23354
         * @private
23355
         */
23356
        unloadTech_() {
23357
            // Save the current text tracks so that we can reuse the same text tracks with the next tech
23358
            ALL.names.forEach(name => {
23359
                const props = ALL[name];
23360
                this[props.privateName] = this[props.getterName]();
23361
            });
23362
            this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
23363
            this.isReady_ = false;
23364
            this.tech_.dispose();
23365
            this.tech_ = false;
23366
            if (this.isPosterFromTech_) {
23367
                this.poster_ = '';
23368
                this.trigger('posterchange');
23369
            }
23370
            this.isPosterFromTech_ = false;
23371
        }
23372
 
23373
        /**
23374
         * Return a reference to the current {@link Tech}.
23375
         * It will print a warning by default about the danger of using the tech directly
23376
         * but any argument that is passed in will silence the warning.
23377
         *
23378
         * @param {*} [safety]
23379
         *        Anything passed in to silence the warning
23380
         *
23381
         * @return {Tech}
23382
         *         The Tech
23383
         */
23384
        tech(safety) {
23385
            if (safety === undefined) {
23386
                log$1.warn('Using the tech directly can be dangerous. I hope you know what you\'re doing.\n' + 'See https://github.com/videojs/video.js/issues/2617 for more info.\n');
23387
            }
23388
            return this.tech_;
23389
        }
23390
 
23391
        /**
23392
         * An object that contains Video.js version.
23393
         *
23394
         * @typedef {Object} PlayerVersion
23395
         *
23396
         * @property {string} 'video.js' - Video.js version
23397
         */
23398
 
23399
        /**
23400
         * Returns an object with Video.js version.
23401
         *
23402
         * @return {PlayerVersion}
23403
         *          An object with Video.js version.
23404
         */
23405
        version() {
23406
            return {
23407
                'video.js': version$5
23408
            };
23409
        }
23410
 
23411
        /**
23412
         * Set up click and touch listeners for the playback element
23413
         *
23414
         * - On desktops: a click on the video itself will toggle playback
23415
         * - On mobile devices: a click on the video toggles controls
23416
         *   which is done by toggling the user state between active and
23417
         *   inactive
23418
         * - A tap can signal that a user has become active or has become inactive
23419
         *   e.g. a quick tap on an iPhone movie should reveal the controls. Another
23420
         *   quick tap should hide them again (signaling the user is in an inactive
23421
         *   viewing state)
23422
         * - In addition to this, we still want the user to be considered inactive after
23423
         *   a few seconds of inactivity.
23424
         *
23425
         * > Note: the only part of iOS interaction we can't mimic with this setup
23426
         * is a touch and hold on the video element counting as activity in order to
23427
         * keep the controls showing, but that shouldn't be an issue. A touch and hold
23428
         * on any controls will still keep the user active
23429
         *
23430
         * @private
23431
         */
23432
        addTechControlsListeners_() {
23433
            // Make sure to remove all the previous listeners in case we are called multiple times.
23434
            this.removeTechControlsListeners_();
23435
            this.on(this.tech_, 'click', this.boundHandleTechClick_);
23436
            this.on(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);
23437
 
23438
            // If the controls were hidden we don't want that to change without a tap event
23439
            // so we'll check if the controls were already showing before reporting user
23440
            // activity
23441
            this.on(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
23442
            this.on(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
23443
            this.on(this.tech_, 'touchend', this.boundHandleTechTouchEnd_);
23444
 
23445
            // The tap listener needs to come after the touchend listener because the tap
23446
            // listener cancels out any reportedUserActivity when setting userActive(false)
23447
            this.on(this.tech_, 'tap', this.boundHandleTechTap_);
23448
        }
23449
 
23450
        /**
23451
         * Remove the listeners used for click and tap controls. This is needed for
23452
         * toggling to controls disabled, where a tap/touch should do nothing.
23453
         *
23454
         * @private
23455
         */
23456
        removeTechControlsListeners_() {
23457
            // We don't want to just use `this.off()` because there might be other needed
23458
            // listeners added by techs that extend this.
23459
            this.off(this.tech_, 'tap', this.boundHandleTechTap_);
23460
            this.off(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
23461
            this.off(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
23462
            this.off(this.tech_, 'touchend', this.boundHandleTechTouchEnd_);
23463
            this.off(this.tech_, 'click', this.boundHandleTechClick_);
23464
            this.off(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);
23465
        }
23466
 
23467
        /**
23468
         * Player waits for the tech to be ready
23469
         *
23470
         * @private
23471
         */
23472
        handleTechReady_() {
23473
            this.triggerReady();
23474
 
23475
            // Keep the same volume as before
23476
            if (this.cache_.volume) {
23477
                this.techCall_('setVolume', this.cache_.volume);
23478
            }
23479
 
23480
            // Look if the tech found a higher resolution poster while loading
23481
            this.handleTechPosterChange_();
23482
 
23483
            // Update the duration if available
23484
            this.handleTechDurationChange_();
23485
        }
23486
 
23487
        /**
23488
         * Retrigger the `loadstart` event that was triggered by the {@link Tech}.
23489
         *
23490
         * @fires Player#loadstart
23491
         * @listens Tech#loadstart
23492
         * @private
23493
         */
23494
        handleTechLoadStart_() {
23495
            // TODO: Update to use `emptied` event instead. See #1277.
23496
 
23497
            this.removeClass('vjs-ended', 'vjs-seeking');
23498
 
23499
            // reset the error state
23500
            this.error(null);
23501
 
23502
            // Update the duration
23503
            this.handleTechDurationChange_();
23504
            if (!this.paused()) {
23505
                /**
23506
                 * Fired when the user agent begins looking for media data
23507
                 *
23508
                 * @event Player#loadstart
23509
                 * @type {Event}
23510
                 */
23511
                this.trigger('loadstart');
23512
            } else {
23513
                // reset the hasStarted state
23514
                this.hasStarted(false);
23515
                this.trigger('loadstart');
23516
            }
23517
 
23518
            // autoplay happens after loadstart for the browser,
23519
            // so we mimic that behavior
23520
            this.manualAutoplay_(this.autoplay() === true && this.options_.normalizeAutoplay ? 'play' : this.autoplay());
23521
        }
23522
 
23523
        /**
23524
         * Handle autoplay string values, rather than the typical boolean
23525
         * values that should be handled by the tech. Note that this is not
23526
         * part of any specification. Valid values and what they do can be
23527
         * found on the autoplay getter at Player#autoplay()
23528
         */
23529
        manualAutoplay_(type) {
23530
            if (!this.tech_ || typeof type !== 'string') {
23531
                return;
23532
            }
23533
 
23534
            // Save original muted() value, set muted to true, and attempt to play().
23535
            // On promise rejection, restore muted from saved value
23536
            const resolveMuted = () => {
23537
                const previouslyMuted = this.muted();
23538
                this.muted(true);
23539
                const restoreMuted = () => {
23540
                    this.muted(previouslyMuted);
23541
                };
23542
 
23543
                // restore muted on play terminatation
23544
                this.playTerminatedQueue_.push(restoreMuted);
23545
                const mutedPromise = this.play();
23546
                if (!isPromise(mutedPromise)) {
23547
                    return;
23548
                }
23549
                return mutedPromise.catch(err => {
23550
                    restoreMuted();
23551
                    throw new Error(`Rejection at manualAutoplay. Restoring muted value. ${err ? err : ''}`);
23552
                });
23553
            };
23554
            let promise;
23555
 
23556
            // if muted defaults to true
23557
            // the only thing we can do is call play
23558
            if (type === 'any' && !this.muted()) {
23559
                promise = this.play();
23560
                if (isPromise(promise)) {
23561
                    promise = promise.catch(resolveMuted);
23562
                }
23563
            } else if (type === 'muted' && !this.muted()) {
23564
                promise = resolveMuted();
23565
            } else {
23566
                promise = this.play();
23567
            }
23568
            if (!isPromise(promise)) {
23569
                return;
23570
            }
23571
            return promise.then(() => {
23572
                this.trigger({
23573
                    type: 'autoplay-success',
23574
                    autoplay: type
23575
                });
23576
            }).catch(() => {
23577
                this.trigger({
23578
                    type: 'autoplay-failure',
23579
                    autoplay: type
23580
                });
23581
            });
23582
        }
23583
 
23584
        /**
23585
         * Update the internal source caches so that we return the correct source from
23586
         * `src()`, `currentSource()`, and `currentSources()`.
23587
         *
23588
         * > Note: `currentSources` will not be updated if the source that is passed in exists
23589
         *         in the current `currentSources` cache.
23590
         *
23591
         *
23592
         * @param {Tech~SourceObject} srcObj
23593
         *        A string or object source to update our caches to.
23594
         */
23595
        updateSourceCaches_(srcObj = '') {
23596
            let src = srcObj;
23597
            let type = '';
23598
            if (typeof src !== 'string') {
23599
                src = srcObj.src;
23600
                type = srcObj.type;
23601
            }
23602
 
23603
            // make sure all the caches are set to default values
23604
            // to prevent null checking
23605
            this.cache_.source = this.cache_.source || {};
23606
            this.cache_.sources = this.cache_.sources || [];
23607
 
23608
            // try to get the type of the src that was passed in
23609
            if (src && !type) {
23610
                type = findMimetype(this, src);
23611
            }
23612
 
23613
            // update `currentSource` cache always
23614
            this.cache_.source = merge$2({}, srcObj, {
23615
                src,
23616
                type
23617
            });
23618
            const matchingSources = this.cache_.sources.filter(s => s.src && s.src === src);
23619
            const sourceElSources = [];
23620
            const sourceEls = this.$$('source');
23621
            const matchingSourceEls = [];
23622
            for (let i = 0; i < sourceEls.length; i++) {
23623
                const sourceObj = getAttributes(sourceEls[i]);
23624
                sourceElSources.push(sourceObj);
23625
                if (sourceObj.src && sourceObj.src === src) {
23626
                    matchingSourceEls.push(sourceObj.src);
23627
                }
23628
            }
23629
 
23630
            // if we have matching source els but not matching sources
23631
            // the current source cache is not up to date
23632
            if (matchingSourceEls.length && !matchingSources.length) {
23633
                this.cache_.sources = sourceElSources;
23634
                // if we don't have matching source or source els set the
23635
                // sources cache to the `currentSource` cache
23636
            } else if (!matchingSources.length) {
23637
                this.cache_.sources = [this.cache_.source];
23638
            }
23639
 
23640
            // update the tech `src` cache
23641
            this.cache_.src = src;
23642
        }
23643
 
23644
        /**
23645
         * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
23646
         * causing the media element to reload.
23647
         *
23648
         * It will fire for the initial source and each subsequent source.
23649
         * This event is a custom event from Video.js and is triggered by the {@link Tech}.
23650
         *
23651
         * The event object for this event contains a `src` property that will contain the source
23652
         * that was available when the event was triggered. This is generally only necessary if Video.js
23653
         * is switching techs while the source was being changed.
23654
         *
23655
         * It is also fired when `load` is called on the player (or media element)
23656
         * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
23657
         * says that the resource selection algorithm needs to be aborted and restarted.
23658
         * In this case, it is very likely that the `src` property will be set to the
23659
         * empty string `""` to indicate we do not know what the source will be but
23660
         * that it is changing.
23661
         *
23662
         * *This event is currently still experimental and may change in minor releases.*
23663
         * __To use this, pass `enableSourceset` option to the player.__
23664
         *
23665
         * @event Player#sourceset
23666
         * @type {Event}
23667
         * @prop {string} src
23668
         *                The source url available when the `sourceset` was triggered.
23669
         *                It will be an empty string if we cannot know what the source is
23670
         *                but know that the source will change.
23671
         */
23672
        /**
23673
         * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
23674
         *
23675
         * @fires Player#sourceset
23676
         * @listens Tech#sourceset
23677
         * @private
23678
         */
23679
        handleTechSourceset_(event) {
23680
            // only update the source cache when the source
23681
            // was not updated using the player api
23682
            if (!this.changingSrc_) {
23683
                let updateSourceCaches = src => this.updateSourceCaches_(src);
23684
                const playerSrc = this.currentSource().src;
23685
                const eventSrc = event.src;
23686
 
23687
                // if we have a playerSrc that is not a blob, and a tech src that is a blob
23688
                if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) {
23689
                    // if both the tech source and the player source were updated we assume
23690
                    // something like @videojs/http-streaming did the sourceset and skip updating the source cache.
23691
                    if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) {
23692
                        updateSourceCaches = () => {};
23693
                    }
23694
                }
23695
 
23696
                // update the source to the initial source right away
23697
                // in some cases this will be empty string
23698
                updateSourceCaches(eventSrc);
23699
 
23700
                // if the `sourceset` `src` was an empty string
23701
                // wait for a `loadstart` to update the cache to `currentSrc`.
23702
                // If a sourceset happens before a `loadstart`, we reset the state
23703
                if (!event.src) {
23704
                    this.tech_.any(['sourceset', 'loadstart'], e => {
23705
                        // if a sourceset happens before a `loadstart` there
23706
                        // is nothing to do as this `handleTechSourceset_`
23707
                        // will be called again and this will be handled there.
23708
                        if (e.type === 'sourceset') {
23709
                            return;
23710
                        }
23711
                        const techSrc = this.techGet_('currentSrc');
23712
                        this.lastSource_.tech = techSrc;
23713
                        this.updateSourceCaches_(techSrc);
23714
                    });
23715
                }
23716
            }
23717
            this.lastSource_ = {
23718
                player: this.currentSource().src,
23719
                tech: event.src
23720
            };
23721
            this.trigger({
23722
                src: event.src,
23723
                type: 'sourceset'
23724
            });
23725
        }
23726
 
23727
        /**
23728
         * Add/remove the vjs-has-started class
23729
         *
23730
         *
23731
         * @param {boolean} request
23732
         *        - true: adds the class
23733
         *        - false: remove the class
23734
         *
23735
         * @return {boolean}
23736
         *         the boolean value of hasStarted_
23737
         */
23738
        hasStarted(request) {
23739
            if (request === undefined) {
23740
                // act as getter, if we have no request to change
23741
                return this.hasStarted_;
23742
            }
23743
            if (request === this.hasStarted_) {
23744
                return;
23745
            }
23746
            this.hasStarted_ = request;
23747
            if (this.hasStarted_) {
23748
                this.addClass('vjs-has-started');
23749
            } else {
23750
                this.removeClass('vjs-has-started');
23751
            }
23752
        }
23753
 
23754
        /**
23755
         * Fired whenever the media begins or resumes playback
23756
         *
23757
         * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
23758
         * @fires Player#play
23759
         * @listens Tech#play
23760
         * @private
23761
         */
23762
        handleTechPlay_() {
23763
            this.removeClass('vjs-ended', 'vjs-paused');
23764
            this.addClass('vjs-playing');
23765
 
23766
            // hide the poster when the user hits play
23767
            this.hasStarted(true);
23768
            /**
23769
             * Triggered whenever an {@link Tech#play} event happens. Indicates that
23770
             * playback has started or resumed.
23771
             *
23772
             * @event Player#play
23773
             * @type {Event}
23774
             */
23775
            this.trigger('play');
23776
        }
23777
 
23778
        /**
23779
         * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
23780
         *
23781
         * If there were any events queued while the playback rate was zero, fire
23782
         * those events now.
23783
         *
23784
         * @private
23785
         * @method Player#handleTechRateChange_
23786
         * @fires Player#ratechange
23787
         * @listens Tech#ratechange
23788
         */
23789
        handleTechRateChange_() {
23790
            if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
23791
                this.queuedCallbacks_.forEach(queued => queued.callback(queued.event));
23792
                this.queuedCallbacks_ = [];
23793
            }
23794
            this.cache_.lastPlaybackRate = this.tech_.playbackRate();
23795
            /**
23796
             * Fires when the playing speed of the audio/video is changed
23797
             *
23798
             * @event Player#ratechange
23799
             * @type {event}
23800
             */
23801
            this.trigger('ratechange');
23802
        }
23803
 
23804
        /**
23805
         * Retrigger the `waiting` event that was triggered by the {@link Tech}.
23806
         *
23807
         * @fires Player#waiting
23808
         * @listens Tech#waiting
23809
         * @private
23810
         */
23811
        handleTechWaiting_() {
23812
            this.addClass('vjs-waiting');
23813
            /**
23814
             * A readyState change on the DOM element has caused playback to stop.
23815
             *
23816
             * @event Player#waiting
23817
             * @type {Event}
23818
             */
23819
            this.trigger('waiting');
23820
 
23821
            // Browsers may emit a timeupdate event after a waiting event. In order to prevent
23822
            // premature removal of the waiting class, wait for the time to change.
23823
            const timeWhenWaiting = this.currentTime();
23824
            const timeUpdateListener = () => {
23825
                if (timeWhenWaiting !== this.currentTime()) {
23826
                    this.removeClass('vjs-waiting');
23827
                    this.off('timeupdate', timeUpdateListener);
23828
                }
23829
            };
23830
            this.on('timeupdate', timeUpdateListener);
23831
        }
23832
 
23833
        /**
23834
         * Retrigger the `canplay` event that was triggered by the {@link Tech}.
23835
         * > Note: This is not consistent between browsers. See #1351
23836
         *
23837
         * @fires Player#canplay
23838
         * @listens Tech#canplay
23839
         * @private
23840
         */
23841
        handleTechCanPlay_() {
23842
            this.removeClass('vjs-waiting');
23843
            /**
23844
             * The media has a readyState of HAVE_FUTURE_DATA or greater.
23845
             *
23846
             * @event Player#canplay
23847
             * @type {Event}
23848
             */
23849
            this.trigger('canplay');
23850
        }
23851
 
23852
        /**
23853
         * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
23854
         *
23855
         * @fires Player#canplaythrough
23856
         * @listens Tech#canplaythrough
23857
         * @private
23858
         */
23859
        handleTechCanPlayThrough_() {
23860
            this.removeClass('vjs-waiting');
23861
            /**
23862
             * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
23863
             * entire media file can be played without buffering.
23864
             *
23865
             * @event Player#canplaythrough
23866
             * @type {Event}
23867
             */
23868
            this.trigger('canplaythrough');
23869
        }
23870
 
23871
        /**
23872
         * Retrigger the `playing` event that was triggered by the {@link Tech}.
23873
         *
23874
         * @fires Player#playing
23875
         * @listens Tech#playing
23876
         * @private
23877
         */
23878
        handleTechPlaying_() {
23879
            this.removeClass('vjs-waiting');
23880
            /**
23881
             * The media is no longer blocked from playback, and has started playing.
23882
             *
23883
             * @event Player#playing
23884
             * @type {Event}
23885
             */
23886
            this.trigger('playing');
23887
        }
23888
 
23889
        /**
23890
         * Retrigger the `seeking` event that was triggered by the {@link Tech}.
23891
         *
23892
         * @fires Player#seeking
23893
         * @listens Tech#seeking
23894
         * @private
23895
         */
23896
        handleTechSeeking_() {
23897
            this.addClass('vjs-seeking');
23898
            /**
23899
             * Fired whenever the player is jumping to a new time
23900
             *
23901
             * @event Player#seeking
23902
             * @type {Event}
23903
             */
23904
            this.trigger('seeking');
23905
        }
23906
 
23907
        /**
23908
         * Retrigger the `seeked` event that was triggered by the {@link Tech}.
23909
         *
23910
         * @fires Player#seeked
23911
         * @listens Tech#seeked
23912
         * @private
23913
         */
23914
        handleTechSeeked_() {
23915
            this.removeClass('vjs-seeking', 'vjs-ended');
23916
            /**
23917
             * Fired when the player has finished jumping to a new time
23918
             *
23919
             * @event Player#seeked
23920
             * @type {Event}
23921
             */
23922
            this.trigger('seeked');
23923
        }
23924
 
23925
        /**
23926
         * Retrigger the `pause` event that was triggered by the {@link Tech}.
23927
         *
23928
         * @fires Player#pause
23929
         * @listens Tech#pause
23930
         * @private
23931
         */
23932
        handleTechPause_() {
23933
            this.removeClass('vjs-playing');
23934
            this.addClass('vjs-paused');
23935
            /**
23936
             * Fired whenever the media has been paused
23937
             *
23938
             * @event Player#pause
23939
             * @type {Event}
23940
             */
23941
            this.trigger('pause');
23942
        }
23943
 
23944
        /**
23945
         * Retrigger the `ended` event that was triggered by the {@link Tech}.
23946
         *
23947
         * @fires Player#ended
23948
         * @listens Tech#ended
23949
         * @private
23950
         */
23951
        handleTechEnded_() {
23952
            this.addClass('vjs-ended');
23953
            this.removeClass('vjs-waiting');
23954
            if (this.options_.loop) {
23955
                this.currentTime(0);
23956
                this.play();
23957
            } else if (!this.paused()) {
23958
                this.pause();
23959
            }
23960
 
23961
            /**
23962
             * Fired when the end of the media resource is reached (currentTime == duration)
23963
             *
23964
             * @event Player#ended
23965
             * @type {Event}
23966
             */
23967
            this.trigger('ended');
23968
        }
23969
 
23970
        /**
23971
         * Fired when the duration of the media resource is first known or changed
23972
         *
23973
         * @listens Tech#durationchange
23974
         * @private
23975
         */
23976
        handleTechDurationChange_() {
23977
            this.duration(this.techGet_('duration'));
23978
        }
23979
 
23980
        /**
23981
         * Handle a click on the media element to play/pause
23982
         *
23983
         * @param {Event} event
23984
         *        the event that caused this function to trigger
23985
         *
23986
         * @listens Tech#click
23987
         * @private
23988
         */
23989
        handleTechClick_(event) {
23990
            // When controls are disabled a click should not toggle playback because
23991
            // the click is considered a control
23992
            if (!this.controls_) {
23993
                return;
23994
            }
23995
            if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.click === undefined || this.options_.userActions.click !== false) {
23996
                if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.click === 'function') {
23997
                    this.options_.userActions.click.call(this, event);
23998
                } else if (this.paused()) {
23999
                    silencePromise(this.play());
24000
                } else {
24001
                    this.pause();
24002
                }
24003
            }
24004
        }
24005
 
24006
        /**
24007
         * Handle a double-click on the media element to enter/exit fullscreen
24008
         *
24009
         * @param {Event} event
24010
         *        the event that caused this function to trigger
24011
         *
24012
         * @listens Tech#dblclick
24013
         * @private
24014
         */
24015
        handleTechDoubleClick_(event) {
24016
            if (!this.controls_) {
24017
                return;
24018
            }
24019
 
24020
            // we do not want to toggle fullscreen state
24021
            // when double-clicking inside a control bar or a modal
24022
            const inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), el => el.contains(event.target));
24023
            if (!inAllowedEls) {
24024
                /*
24025
         * options.userActions.doubleClick
24026
         *
24027
         * If `undefined` or `true`, double-click toggles fullscreen if controls are present
24028
         * Set to `false` to disable double-click handling
24029
         * Set to a function to substitute an external double-click handler
24030
         */
24031
                if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) {
24032
                    if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') {
24033
                        this.options_.userActions.doubleClick.call(this, event);
24034
                    } else if (this.isFullscreen()) {
24035
                        this.exitFullscreen();
24036
                    } else {
24037
                        this.requestFullscreen();
24038
                    }
24039
                }
24040
            }
24041
        }
24042
 
24043
        /**
24044
         * Handle a tap on the media element. It will toggle the user
24045
         * activity state, which hides and shows the controls.
24046
         *
24047
         * @listens Tech#tap
24048
         * @private
24049
         */
24050
        handleTechTap_() {
24051
            this.userActive(!this.userActive());
24052
        }
24053
 
24054
        /**
24055
         * Handle touch to start
24056
         *
24057
         * @listens Tech#touchstart
24058
         * @private
24059
         */
24060
        handleTechTouchStart_() {
24061
            this.userWasActive = this.userActive();
24062
        }
24063
 
24064
        /**
24065
         * Handle touch to move
24066
         *
24067
         * @listens Tech#touchmove
24068
         * @private
24069
         */
24070
        handleTechTouchMove_() {
24071
            if (this.userWasActive) {
24072
                this.reportUserActivity();
24073
            }
24074
        }
24075
 
24076
        /**
24077
         * Handle touch to end
24078
         *
24079
         * @param {Event} event
24080
         *        the touchend event that triggered
24081
         *        this function
24082
         *
24083
         * @listens Tech#touchend
24084
         * @private
24085
         */
24086
        handleTechTouchEnd_(event) {
24087
            // Stop the mouse events from also happening
24088
            if (event.cancelable) {
24089
                event.preventDefault();
24090
            }
24091
        }
24092
 
24093
        /**
24094
         * @private
24095
         */
24096
        toggleFullscreenClass_() {
24097
            if (this.isFullscreen()) {
24098
                this.addClass('vjs-fullscreen');
24099
            } else {
24100
                this.removeClass('vjs-fullscreen');
24101
            }
24102
        }
24103
 
24104
        /**
24105
         * when the document fschange event triggers it calls this
24106
         */
24107
        documentFullscreenChange_(e) {
24108
            const targetPlayer = e.target.player;
24109
 
24110
            // if another player was fullscreen
24111
            // do a null check for targetPlayer because older firefox's would put document as e.target
24112
            if (targetPlayer && targetPlayer !== this) {
24113
                return;
24114
            }
24115
            const el = this.el();
24116
            let isFs = document[this.fsApi_.fullscreenElement] === el;
24117
            if (!isFs && el.matches) {
24118
                isFs = el.matches(':' + this.fsApi_.fullscreen);
24119
            }
24120
            this.isFullscreen(isFs);
24121
        }
24122
 
24123
        /**
24124
         * Handle Tech Fullscreen Change
24125
         *
24126
         * @param {Event} event
24127
         *        the fullscreenchange event that triggered this function
24128
         *
24129
         * @param {Object} data
24130
         *        the data that was sent with the event
24131
         *
24132
         * @private
24133
         * @listens Tech#fullscreenchange
24134
         * @fires Player#fullscreenchange
24135
         */
24136
        handleTechFullscreenChange_(event, data) {
24137
            if (data) {
24138
                if (data.nativeIOSFullscreen) {
24139
                    this.addClass('vjs-ios-native-fs');
24140
                    this.tech_.one('webkitendfullscreen', () => {
24141
                        this.removeClass('vjs-ios-native-fs');
24142
                    });
24143
                }
24144
                this.isFullscreen(data.isFullscreen);
24145
            }
24146
        }
24147
        handleTechFullscreenError_(event, err) {
24148
            this.trigger('fullscreenerror', err);
24149
        }
24150
 
24151
        /**
24152
         * @private
24153
         */
24154
        togglePictureInPictureClass_() {
24155
            if (this.isInPictureInPicture()) {
24156
                this.addClass('vjs-picture-in-picture');
24157
            } else {
24158
                this.removeClass('vjs-picture-in-picture');
24159
            }
24160
        }
24161
 
24162
        /**
24163
         * Handle Tech Enter Picture-in-Picture.
24164
         *
24165
         * @param {Event} event
24166
         *        the enterpictureinpicture event that triggered this function
24167
         *
24168
         * @private
24169
         * @listens Tech#enterpictureinpicture
24170
         */
24171
        handleTechEnterPictureInPicture_(event) {
24172
            this.isInPictureInPicture(true);
24173
        }
24174
 
24175
        /**
24176
         * Handle Tech Leave Picture-in-Picture.
24177
         *
24178
         * @param {Event} event
24179
         *        the leavepictureinpicture event that triggered this function
24180
         *
24181
         * @private
24182
         * @listens Tech#leavepictureinpicture
24183
         */
24184
        handleTechLeavePictureInPicture_(event) {
24185
            this.isInPictureInPicture(false);
24186
        }
24187
 
24188
        /**
24189
         * Fires when an error occurred during the loading of an audio/video.
24190
         *
24191
         * @private
24192
         * @listens Tech#error
24193
         */
24194
        handleTechError_() {
24195
            const error = this.tech_.error();
24196
            if (error) {
24197
                this.error(error);
24198
            }
24199
        }
24200
 
24201
        /**
24202
         * Retrigger the `textdata` event that was triggered by the {@link Tech}.
24203
         *
24204
         * @fires Player#textdata
24205
         * @listens Tech#textdata
24206
         * @private
24207
         */
24208
        handleTechTextData_() {
24209
            let data = null;
24210
            if (arguments.length > 1) {
24211
                data = arguments[1];
24212
            }
24213
 
24214
            /**
24215
             * Fires when we get a textdata event from tech
24216
             *
24217
             * @event Player#textdata
24218
             * @type {Event}
24219
             */
24220
            this.trigger('textdata', data);
24221
        }
24222
 
24223
        /**
24224
         * Get object for cached values.
24225
         *
24226
         * @return {Object}
24227
         *         get the current object cache
24228
         */
24229
        getCache() {
24230
            return this.cache_;
24231
        }
24232
 
24233
        /**
24234
         * Resets the internal cache object.
24235
         *
24236
         * Using this function outside the player constructor or reset method may
24237
         * have unintended side-effects.
24238
         *
24239
         * @private
24240
         */
24241
        resetCache_() {
24242
            this.cache_ = {
24243
                // Right now, the currentTime is not _really_ cached because it is always
24244
                // retrieved from the tech (see: currentTime). However, for completeness,
24245
                // we set it to zero here to ensure that if we do start actually caching
24246
                // it, we reset it along with everything else.
24247
                currentTime: 0,
24248
                initTime: 0,
24249
                inactivityTimeout: this.options_.inactivityTimeout,
24250
                duration: NaN,
24251
                lastVolume: 1,
24252
                lastPlaybackRate: this.defaultPlaybackRate(),
24253
                media: null,
24254
                src: '',
24255
                source: {},
24256
                sources: [],
24257
                playbackRates: [],
24258
                volume: 1
24259
            };
24260
        }
24261
 
24262
        /**
24263
         * Pass values to the playback tech
24264
         *
24265
         * @param {string} [method]
24266
         *        the method to call
24267
         *
24268
         * @param {Object} [arg]
24269
         *        the argument to pass
24270
         *
24271
         * @private
24272
         */
24273
        techCall_(method, arg) {
24274
            // If it's not ready yet, call method when it is
24275
 
24276
            this.ready(function () {
24277
                if (method in allowedSetters) {
24278
                    return set(this.middleware_, this.tech_, method, arg);
24279
                } else if (method in allowedMediators) {
24280
                    return mediate(this.middleware_, this.tech_, method, arg);
24281
                }
24282
                try {
24283
                    if (this.tech_) {
24284
                        this.tech_[method](arg);
24285
                    }
24286
                } catch (e) {
24287
                    log$1(e);
24288
                    throw e;
24289
                }
24290
            }, true);
24291
        }
24292
 
24293
        /**
24294
         * Mediate attempt to call playback tech method
24295
         * and return the value of the method called.
24296
         *
24297
         * @param {string} method
24298
         *        Tech method
24299
         *
24300
         * @return {*}
24301
         *         Value returned by the tech method called, undefined if tech
24302
         *         is not ready or tech method is not present
24303
         *
24304
         * @private
24305
         */
24306
        techGet_(method) {
24307
            if (!this.tech_ || !this.tech_.isReady_) {
24308
                return;
24309
            }
24310
            if (method in allowedGetters) {
24311
                return get(this.middleware_, this.tech_, method);
24312
            } else if (method in allowedMediators) {
24313
                return mediate(this.middleware_, this.tech_, method);
24314
            }
24315
 
24316
            // Log error when playback tech object is present but method
24317
            // is undefined or unavailable
24318
            try {
24319
                return this.tech_[method]();
24320
            } catch (e) {
24321
                // When building additional tech libs, an expected method may not be defined yet
24322
                if (this.tech_[method] === undefined) {
24323
                    log$1(`Video.js: ${method} method not defined for ${this.techName_} playback technology.`, e);
24324
                    throw e;
24325
                }
24326
 
24327
                // When a method isn't available on the object it throws a TypeError
24328
                if (e.name === 'TypeError') {
24329
                    log$1(`Video.js: ${method} unavailable on ${this.techName_} playback technology element.`, e);
24330
                    this.tech_.isReady_ = false;
24331
                    throw e;
24332
                }
24333
 
24334
                // If error unknown, just log and throw
24335
                log$1(e);
24336
                throw e;
24337
            }
24338
        }
24339
 
24340
        /**
24341
         * Attempt to begin playback at the first opportunity.
24342
         *
24343
         * @return {Promise|undefined}
24344
         *         Returns a promise if the browser supports Promises (or one
24345
         *         was passed in as an option). This promise will be resolved on
24346
         *         the return value of play. If this is undefined it will fulfill the
24347
         *         promise chain otherwise the promise chain will be fulfilled when
24348
         *         the promise from play is fulfilled.
24349
         */
24350
        play() {
24351
            return new Promise(resolve => {
24352
                this.play_(resolve);
24353
            });
24354
        }
24355
 
24356
        /**
24357
         * The actual logic for play, takes a callback that will be resolved on the
24358
         * return value of play. This allows us to resolve to the play promise if there
24359
         * is one on modern browsers.
24360
         *
24361
         * @private
24362
         * @param {Function} [callback]
24363
         *        The callback that should be called when the techs play is actually called
24364
         */
24365
        play_(callback = silencePromise) {
24366
            this.playCallbacks_.push(callback);
24367
            const isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc()));
24368
            const isSafariOrIOS = Boolean(IS_ANY_SAFARI || IS_IOS);
24369
 
24370
            // treat calls to play_ somewhat like the `one` event function
24371
            if (this.waitToPlay_) {
24372
                this.off(['ready', 'loadstart'], this.waitToPlay_);
24373
                this.waitToPlay_ = null;
24374
            }
24375
 
24376
            // if the player/tech is not ready or the src itself is not ready
24377
            // queue up a call to play on `ready` or `loadstart`
24378
            if (!this.isReady_ || !isSrcReady) {
24379
                this.waitToPlay_ = e => {
24380
                    this.play_();
24381
                };
24382
                this.one(['ready', 'loadstart'], this.waitToPlay_);
24383
 
24384
                // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
24385
                // in that case, we need to prime the video element by calling load so it'll be ready in time
24386
                if (!isSrcReady && isSafariOrIOS) {
24387
                    this.load();
24388
                }
24389
                return;
24390
            }
24391
 
24392
            // If the player/tech is ready and we have a source, we can attempt playback.
24393
            const val = this.techGet_('play');
24394
 
24395
            // For native playback, reset the progress bar if we get a play call from a replay.
24396
            const isNativeReplay = isSafariOrIOS && this.hasClass('vjs-ended');
24397
            if (isNativeReplay) {
24398
                this.resetProgressBar_();
24399
            }
24400
            // play was terminated if the returned value is null
24401
            if (val === null) {
24402
                this.runPlayTerminatedQueue_();
24403
            } else {
24404
                this.runPlayCallbacks_(val);
24405
            }
24406
        }
24407
 
24408
        /**
24409
         * These functions will be run when if play is terminated. If play
24410
         * runPlayCallbacks_ is run these function will not be run. This allows us
24411
         * to differentiate between a terminated play and an actual call to play.
24412
         */
24413
        runPlayTerminatedQueue_() {
24414
            const queue = this.playTerminatedQueue_.slice(0);
24415
            this.playTerminatedQueue_ = [];
24416
            queue.forEach(function (q) {
24417
                q();
24418
            });
24419
        }
24420
 
24421
        /**
24422
         * When a callback to play is delayed we have to run these
24423
         * callbacks when play is actually called on the tech. This function
24424
         * runs the callbacks that were delayed and accepts the return value
24425
         * from the tech.
24426
         *
24427
         * @param {undefined|Promise} val
24428
         *        The return value from the tech.
24429
         */
24430
        runPlayCallbacks_(val) {
24431
            const callbacks = this.playCallbacks_.slice(0);
24432
            this.playCallbacks_ = [];
24433
            // clear play terminatedQueue since we finished a real play
24434
            this.playTerminatedQueue_ = [];
24435
            callbacks.forEach(function (cb) {
24436
                cb(val);
24437
            });
24438
        }
24439
 
24440
        /**
24441
         * Pause the video playback
24442
         */
24443
        pause() {
24444
            this.techCall_('pause');
24445
        }
24446
 
24447
        /**
24448
         * Check if the player is paused or has yet to play
24449
         *
24450
         * @return {boolean}
24451
         *         - false: if the media is currently playing
24452
         *         - true: if media is not currently playing
24453
         */
24454
        paused() {
24455
            // The initial state of paused should be true (in Safari it's actually false)
24456
            return this.techGet_('paused') === false ? false : true;
24457
        }
24458
 
24459
        /**
24460
         * Get a TimeRange object representing the current ranges of time that the user
24461
         * has played.
24462
         *
24463
         * @return { import('./utils/time').TimeRange }
24464
         *         A time range object that represents all the increments of time that have
24465
         *         been played.
24466
         */
24467
        played() {
24468
            return this.techGet_('played') || createTimeRanges$1(0, 0);
24469
        }
24470
 
24471
        /**
24472
         * Sets or returns whether or not the user is "scrubbing". Scrubbing is
24473
         * when the user has clicked the progress bar handle and is
24474
         * dragging it along the progress bar.
24475
         *
24476
         * @param {boolean} [isScrubbing]
24477
         *        whether the user is or is not scrubbing
24478
         *
24479
         * @return {boolean|undefined}
24480
         *         - The value of scrubbing when getting
24481
         *         - Nothing when setting
24482
         */
24483
        scrubbing(isScrubbing) {
24484
            if (typeof isScrubbing === 'undefined') {
24485
                return this.scrubbing_;
24486
            }
24487
            this.scrubbing_ = !!isScrubbing;
24488
            this.techCall_('setScrubbing', this.scrubbing_);
24489
            if (isScrubbing) {
24490
                this.addClass('vjs-scrubbing');
24491
            } else {
24492
                this.removeClass('vjs-scrubbing');
24493
            }
24494
        }
24495
 
24496
        /**
24497
         * Get or set the current time (in seconds)
24498
         *
24499
         * @param {number|string} [seconds]
24500
         *        The time to seek to in seconds
24501
         *
24502
         * @return {number|undefined}
24503
         *         - the current time in seconds when getting
24504
         *         - Nothing when setting
24505
         */
24506
        currentTime(seconds) {
24507
            if (seconds === undefined) {
24508
                // cache last currentTime and return. default to 0 seconds
24509
                //
24510
                // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
24511
                // currentTime when scrubbing, but may not provide much performance benefit after all.
24512
                // Should be tested. Also something has to read the actual current time or the cache will
24513
                // never get updated.
24514
                this.cache_.currentTime = this.techGet_('currentTime') || 0;
24515
                return this.cache_.currentTime;
24516
            }
24517
            if (seconds < 0) {
24518
                seconds = 0;
24519
            }
24520
            if (!this.isReady_ || this.changingSrc_ || !this.tech_ || !this.tech_.isReady_) {
24521
                this.cache_.initTime = seconds;
24522
                this.off('canplay', this.boundApplyInitTime_);
24523
                this.one('canplay', this.boundApplyInitTime_);
24524
                return;
24525
            }
24526
            this.techCall_('setCurrentTime', seconds);
24527
            this.cache_.initTime = 0;
24528
            if (isFinite(seconds)) {
24529
                this.cache_.currentTime = Number(seconds);
24530
            }
24531
        }
24532
 
24533
        /**
24534
         * Apply the value of initTime stored in cache as currentTime.
24535
         *
24536
         * @private
24537
         */
24538
        applyInitTime_() {
24539
            this.currentTime(this.cache_.initTime);
24540
        }
24541
 
24542
        /**
24543
         * Normally gets the length in time of the video in seconds;
24544
         * in all but the rarest use cases an argument will NOT be passed to the method
24545
         *
24546
         * > **NOTE**: The video must have started loading before the duration can be
24547
         * known, and depending on preload behaviour may not be known until the video starts
24548
         * playing.
24549
         *
24550
         * @fires Player#durationchange
24551
         *
24552
         * @param {number} [seconds]
24553
         *        The duration of the video to set in seconds
24554
         *
24555
         * @return {number|undefined}
24556
         *         - The duration of the video in seconds when getting
24557
         *         - Nothing when setting
24558
         */
24559
        duration(seconds) {
24560
            if (seconds === undefined) {
24561
                // return NaN if the duration is not known
24562
                return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
24563
            }
24564
            seconds = parseFloat(seconds);
24565
 
24566
            // Standardize on Infinity for signaling video is live
24567
            if (seconds < 0) {
24568
                seconds = Infinity;
24569
            }
24570
            if (seconds !== this.cache_.duration) {
24571
                // Cache the last set value for optimized scrubbing
24572
                this.cache_.duration = seconds;
24573
                if (seconds === Infinity) {
24574
                    this.addClass('vjs-live');
24575
                } else {
24576
                    this.removeClass('vjs-live');
24577
                }
24578
                if (!isNaN(seconds)) {
24579
                    // Do not fire durationchange unless the duration value is known.
24580
                    // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
24581
 
24582
                    /**
24583
                     * @event Player#durationchange
24584
                     * @type {Event}
24585
                     */
24586
                    this.trigger('durationchange');
24587
                }
24588
            }
24589
        }
24590
 
24591
        /**
24592
         * Calculates how much time is left in the video. Not part
24593
         * of the native video API.
24594
         *
24595
         * @return {number}
24596
         *         The time remaining in seconds
24597
         */
24598
        remainingTime() {
24599
            return this.duration() - this.currentTime();
24600
        }
24601
 
24602
        /**
24603
         * A remaining time function that is intended to be used when
24604
         * the time is to be displayed directly to the user.
24605
         *
24606
         * @return {number}
24607
         *         The rounded time remaining in seconds
24608
         */
24609
        remainingTimeDisplay() {
24610
            return Math.floor(this.duration()) - Math.floor(this.currentTime());
24611
        }
24612
 
24613
        //
24614
        // Kind of like an array of portions of the video that have been downloaded.
24615
 
24616
        /**
24617
         * Get a TimeRange object with an array of the times of the video
24618
         * that have been downloaded. If you just want the percent of the
24619
         * video that's been downloaded, use bufferedPercent.
24620
         *
24621
         * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
24622
         *
24623
         * @return { import('./utils/time').TimeRange }
24624
         *         A mock {@link TimeRanges} object (following HTML spec)
24625
         */
24626
        buffered() {
24627
            let buffered = this.techGet_('buffered');
24628
            if (!buffered || !buffered.length) {
24629
                buffered = createTimeRanges$1(0, 0);
24630
            }
24631
            return buffered;
24632
        }
24633
 
24634
        /**
24635
         * Get the TimeRanges of the media that are currently available
24636
         * for seeking to.
24637
         *
24638
         * @see [Seekable Spec]{@link https://html.spec.whatwg.org/multipage/media.html#dom-media-seekable}
24639
         *
24640
         * @return { import('./utils/time').TimeRange }
24641
         *         A mock {@link TimeRanges} object (following HTML spec)
24642
         */
24643
        seekable() {
24644
            let seekable = this.techGet_('seekable');
24645
            if (!seekable || !seekable.length) {
24646
                seekable = createTimeRanges$1(0, 0);
24647
            }
24648
            return seekable;
24649
        }
24650
 
24651
        /**
24652
         * Returns whether the player is in the "seeking" state.
24653
         *
24654
         * @return {boolean} True if the player is in the seeking state, false if not.
24655
         */
24656
        seeking() {
24657
            return this.techGet_('seeking');
24658
        }
24659
 
24660
        /**
24661
         * Returns whether the player is in the "ended" state.
24662
         *
24663
         * @return {boolean} True if the player is in the ended state, false if not.
24664
         */
24665
        ended() {
24666
            return this.techGet_('ended');
24667
        }
24668
 
24669
        /**
24670
         * Returns the current state of network activity for the element, from
24671
         * the codes in the list below.
24672
         * - NETWORK_EMPTY (numeric value 0)
24673
         *   The element has not yet been initialised. All attributes are in
24674
         *   their initial states.
24675
         * - NETWORK_IDLE (numeric value 1)
24676
         *   The element's resource selection algorithm is active and has
24677
         *   selected a resource, but it is not actually using the network at
24678
         *   this time.
24679
         * - NETWORK_LOADING (numeric value 2)
24680
         *   The user agent is actively trying to download data.
24681
         * - NETWORK_NO_SOURCE (numeric value 3)
24682
         *   The element's resource selection algorithm is active, but it has
24683
         *   not yet found a resource to use.
24684
         *
24685
         * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
24686
         * @return {number} the current network activity state
24687
         */
24688
        networkState() {
24689
            return this.techGet_('networkState');
24690
        }
24691
 
24692
        /**
24693
         * Returns a value that expresses the current state of the element
24694
         * with respect to rendering the current playback position, from the
24695
         * codes in the list below.
24696
         * - HAVE_NOTHING (numeric value 0)
24697
         *   No information regarding the media resource is available.
24698
         * - HAVE_METADATA (numeric value 1)
24699
         *   Enough of the resource has been obtained that the duration of the
24700
         *   resource is available.
24701
         * - HAVE_CURRENT_DATA (numeric value 2)
24702
         *   Data for the immediate current playback position is available.
24703
         * - HAVE_FUTURE_DATA (numeric value 3)
24704
         *   Data for the immediate current playback position is available, as
24705
         *   well as enough data for the user agent to advance the current
24706
         *   playback position in the direction of playback.
24707
         * - HAVE_ENOUGH_DATA (numeric value 4)
24708
         *   The user agent estimates that enough data is available for
24709
         *   playback to proceed uninterrupted.
24710
         *
24711
         * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
24712
         * @return {number} the current playback rendering state
24713
         */
24714
        readyState() {
24715
            return this.techGet_('readyState');
24716
        }
24717
 
24718
        /**
24719
         * Get the percent (as a decimal) of the video that's been downloaded.
24720
         * This method is not a part of the native HTML video API.
24721
         *
24722
         * @return {number}
24723
         *         A decimal between 0 and 1 representing the percent
24724
         *         that is buffered 0 being 0% and 1 being 100%
24725
         */
24726
        bufferedPercent() {
24727
            return bufferedPercent(this.buffered(), this.duration());
24728
        }
24729
 
24730
        /**
24731
         * Get the ending time of the last buffered time range
24732
         * This is used in the progress bar to encapsulate all time ranges.
24733
         *
24734
         * @return {number}
24735
         *         The end of the last buffered time range
24736
         */
24737
        bufferedEnd() {
24738
            const buffered = this.buffered();
24739
            const duration = this.duration();
24740
            let end = buffered.end(buffered.length - 1);
24741
            if (end > duration) {
24742
                end = duration;
24743
            }
24744
            return end;
24745
        }
24746
 
24747
        /**
24748
         * Get or set the current volume of the media
24749
         *
24750
         * @param  {number} [percentAsDecimal]
24751
         *         The new volume as a decimal percent:
24752
         *         - 0 is muted/0%/off
24753
         *         - 1.0 is 100%/full
24754
         *         - 0.5 is half volume or 50%
24755
         *
24756
         * @return {number|undefined}
24757
         *         The current volume as a percent when getting
24758
         */
24759
        volume(percentAsDecimal) {
24760
            let vol;
24761
            if (percentAsDecimal !== undefined) {
24762
                // Force value to between 0 and 1
24763
                vol = Math.max(0, Math.min(1, percentAsDecimal));
24764
                this.cache_.volume = vol;
24765
                this.techCall_('setVolume', vol);
24766
                if (vol > 0) {
24767
                    this.lastVolume_(vol);
24768
                }
24769
                return;
24770
            }
24771
 
24772
            // Default to 1 when returning current volume.
24773
            vol = parseFloat(this.techGet_('volume'));
24774
            return isNaN(vol) ? 1 : vol;
24775
        }
24776
 
24777
        /**
24778
         * Get the current muted state, or turn mute on or off
24779
         *
24780
         * @param {boolean} [muted]
24781
         *        - true to mute
24782
         *        - false to unmute
24783
         *
24784
         * @return {boolean|undefined}
24785
         *         - true if mute is on and getting
24786
         *         - false if mute is off and getting
24787
         *         - nothing if setting
24788
         */
24789
        muted(muted) {
24790
            if (muted !== undefined) {
24791
                this.techCall_('setMuted', muted);
24792
                return;
24793
            }
24794
            return this.techGet_('muted') || false;
24795
        }
24796
 
24797
        /**
24798
         * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
24799
         * indicates the state of muted on initial playback.
24800
         *
24801
         * ```js
24802
         *   var myPlayer = videojs('some-player-id');
24803
         *
24804
         *   myPlayer.src("http://www.example.com/path/to/video.mp4");
24805
         *
24806
         *   // get, should be false
24807
         *   console.log(myPlayer.defaultMuted());
24808
         *   // set to true
24809
         *   myPlayer.defaultMuted(true);
24810
         *   // get should be true
24811
         *   console.log(myPlayer.defaultMuted());
24812
         * ```
24813
         *
24814
         * @param {boolean} [defaultMuted]
24815
         *        - true to mute
24816
         *        - false to unmute
24817
         *
24818
         * @return {boolean|undefined}
24819
         *         - true if defaultMuted is on and getting
24820
         *         - false if defaultMuted is off and getting
24821
         *         - Nothing when setting
24822
         */
24823
        defaultMuted(defaultMuted) {
24824
            if (defaultMuted !== undefined) {
24825
                this.techCall_('setDefaultMuted', defaultMuted);
24826
            }
24827
            return this.techGet_('defaultMuted') || false;
24828
        }
24829
 
24830
        /**
24831
         * Get the last volume, or set it
24832
         *
24833
         * @param  {number} [percentAsDecimal]
24834
         *         The new last volume as a decimal percent:
24835
         *         - 0 is muted/0%/off
24836
         *         - 1.0 is 100%/full
24837
         *         - 0.5 is half volume or 50%
24838
         *
24839
         * @return {number|undefined}
24840
         *         - The current value of lastVolume as a percent when getting
24841
         *         - Nothing when setting
24842
         *
24843
         * @private
24844
         */
24845
        lastVolume_(percentAsDecimal) {
24846
            if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
24847
                this.cache_.lastVolume = percentAsDecimal;
24848
                return;
24849
            }
24850
            return this.cache_.lastVolume;
24851
        }
24852
 
24853
        /**
24854
         * Check if current tech can support native fullscreen
24855
         * (e.g. with built in controls like iOS)
24856
         *
24857
         * @return {boolean}
24858
         *         if native fullscreen is supported
24859
         */
24860
        supportsFullScreen() {
24861
            return this.techGet_('supportsFullScreen') || false;
24862
        }
24863
 
24864
        /**
24865
         * Check if the player is in fullscreen mode or tell the player that it
24866
         * is or is not in fullscreen mode.
24867
         *
24868
         * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
24869
         * property and instead document.fullscreenElement is used. But isFullscreen is
24870
         * still a valuable property for internal player workings.
24871
         *
24872
         * @param  {boolean} [isFS]
24873
         *         Set the players current fullscreen state
24874
         *
24875
         * @return {boolean|undefined}
24876
         *         - true if fullscreen is on and getting
24877
         *         - false if fullscreen is off and getting
24878
         *         - Nothing when setting
24879
         */
24880
        isFullscreen(isFS) {
24881
            if (isFS !== undefined) {
24882
                const oldValue = this.isFullscreen_;
24883
                this.isFullscreen_ = Boolean(isFS);
24884
 
24885
                // if we changed fullscreen state and we're in prefixed mode, trigger fullscreenchange
24886
                // this is the only place where we trigger fullscreenchange events for older browsers
24887
                // fullWindow mode is treated as a prefixed event and will get a fullscreenchange event as well
24888
                if (this.isFullscreen_ !== oldValue && this.fsApi_.prefixed) {
24889
                    /**
24890
                     * @event Player#fullscreenchange
24891
                     * @type {Event}
24892
                     */
24893
                    this.trigger('fullscreenchange');
24894
                }
24895
                this.toggleFullscreenClass_();
24896
                return;
24897
            }
24898
            return this.isFullscreen_;
24899
        }
24900
 
24901
        /**
24902
         * Increase the size of the video to full screen
24903
         * In some browsers, full screen is not supported natively, so it enters
24904
         * "full window mode", where the video fills the browser window.
24905
         * In browsers and devices that support native full screen, sometimes the
24906
         * browser's default controls will be shown, and not the Video.js custom skin.
24907
         * This includes most mobile devices (iOS, Android) and older versions of
24908
         * Safari.
24909
         *
24910
         * @param  {Object} [fullscreenOptions]
24911
         *         Override the player fullscreen options
24912
         *
24913
         * @fires Player#fullscreenchange
24914
         */
24915
        requestFullscreen(fullscreenOptions) {
24916
            if (this.isInPictureInPicture()) {
24917
                this.exitPictureInPicture();
24918
            }
24919
            const self = this;
24920
            return new Promise((resolve, reject) => {
24921
                function offHandler() {
24922
                    self.off('fullscreenerror', errorHandler);
24923
                    self.off('fullscreenchange', changeHandler);
24924
                }
24925
                function changeHandler() {
24926
                    offHandler();
24927
                    resolve();
24928
                }
24929
                function errorHandler(e, err) {
24930
                    offHandler();
24931
                    reject(err);
24932
                }
24933
                self.one('fullscreenchange', changeHandler);
24934
                self.one('fullscreenerror', errorHandler);
24935
                const promise = self.requestFullscreenHelper_(fullscreenOptions);
24936
                if (promise) {
24937
                    promise.then(offHandler, offHandler);
24938
                    promise.then(resolve, reject);
24939
                }
24940
            });
24941
        }
24942
        requestFullscreenHelper_(fullscreenOptions) {
24943
            let fsOptions;
24944
 
24945
            // Only pass fullscreen options to requestFullscreen in spec-compliant browsers.
24946
            // Use defaults or player configured option unless passed directly to this method.
24947
            if (!this.fsApi_.prefixed) {
24948
                fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {};
24949
                if (fullscreenOptions !== undefined) {
24950
                    fsOptions = fullscreenOptions;
24951
                }
24952
            }
24953
 
24954
            // This method works as follows:
24955
            // 1. if a fullscreen api is available, use it
24956
            //   1. call requestFullscreen with potential options
24957
            //   2. if we got a promise from above, use it to update isFullscreen()
24958
            // 2. otherwise, if the tech supports fullscreen, call `enterFullScreen` on it.
24959
            //   This is particularly used for iPhone, older iPads, and non-safari browser on iOS.
24960
            // 3. otherwise, use "fullWindow" mode
24961
            if (this.fsApi_.requestFullscreen) {
24962
                const promise = this.el_[this.fsApi_.requestFullscreen](fsOptions);
24963
 
24964
                // Even on browsers with promise support this may not return a promise
24965
                if (promise) {
24966
                    promise.then(() => this.isFullscreen(true), () => this.isFullscreen(false));
24967
                }
24968
                return promise;
24969
            } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
24970
                // we can't take the video.js controls fullscreen but we can go fullscreen
24971
                // with native controls
24972
                this.techCall_('enterFullScreen');
24973
            } else {
24974
                // fullscreen isn't supported so we'll just stretch the video element to
24975
                // fill the viewport
24976
                this.enterFullWindow();
24977
            }
24978
        }
24979
 
24980
        /**
24981
         * Return the video to its normal size after having been in full screen mode
24982
         *
24983
         * @fires Player#fullscreenchange
24984
         */
24985
        exitFullscreen() {
24986
            const self = this;
24987
            return new Promise((resolve, reject) => {
24988
                function offHandler() {
24989
                    self.off('fullscreenerror', errorHandler);
24990
                    self.off('fullscreenchange', changeHandler);
24991
                }
24992
                function changeHandler() {
24993
                    offHandler();
24994
                    resolve();
24995
                }
24996
                function errorHandler(e, err) {
24997
                    offHandler();
24998
                    reject(err);
24999
                }
25000
                self.one('fullscreenchange', changeHandler);
25001
                self.one('fullscreenerror', errorHandler);
25002
                const promise = self.exitFullscreenHelper_();
25003
                if (promise) {
25004
                    promise.then(offHandler, offHandler);
25005
                    // map the promise to our resolve/reject methods
25006
                    promise.then(resolve, reject);
25007
                }
25008
            });
25009
        }
25010
        exitFullscreenHelper_() {
25011
            if (this.fsApi_.requestFullscreen) {
25012
                const promise = document[this.fsApi_.exitFullscreen]();
25013
 
25014
                // Even on browsers with promise support this may not return a promise
25015
                if (promise) {
25016
                    // we're splitting the promise here, so, we want to catch the
25017
                    // potential error so that this chain doesn't have unhandled errors
25018
                    silencePromise(promise.then(() => this.isFullscreen(false)));
25019
                }
25020
                return promise;
25021
            } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
25022
                this.techCall_('exitFullScreen');
25023
            } else {
25024
                this.exitFullWindow();
25025
            }
25026
        }
25027
 
25028
        /**
25029
         * When fullscreen isn't supported we can stretch the
25030
         * video container to as wide as the browser will let us.
25031
         *
25032
         * @fires Player#enterFullWindow
25033
         */
25034
        enterFullWindow() {
25035
            this.isFullscreen(true);
25036
            this.isFullWindow = true;
25037
 
25038
            // Storing original doc overflow value to return to when fullscreen is off
25039
            this.docOrigOverflow = document.documentElement.style.overflow;
25040
 
25041
            // Add listener for esc key to exit fullscreen
25042
            on(document, 'keydown', this.boundFullWindowOnEscKey_);
25043
 
25044
            // Hide any scroll bars
25045
            document.documentElement.style.overflow = 'hidden';
25046
 
25047
            // Apply fullscreen styles
25048
            addClass(document.body, 'vjs-full-window');
25049
 
25050
            /**
25051
             * @event Player#enterFullWindow
25052
             * @type {Event}
25053
             */
25054
            this.trigger('enterFullWindow');
25055
        }
25056
 
25057
        /**
25058
         * Check for call to either exit full window or
25059
         * full screen on ESC key
25060
         *
25061
         * @param {string} event
25062
         *        Event to check for key press
25063
         */
25064
        fullWindowOnEscKey(event) {
25065
            if (keycode.isEventKey(event, 'Esc')) {
25066
                if (this.isFullscreen() === true) {
25067
                    if (!this.isFullWindow) {
25068
                        this.exitFullscreen();
25069
                    } else {
25070
                        this.exitFullWindow();
25071
                    }
25072
                }
25073
            }
25074
        }
25075
 
25076
        /**
25077
         * Exit full window
25078
         *
25079
         * @fires Player#exitFullWindow
25080
         */
25081
        exitFullWindow() {
25082
            this.isFullscreen(false);
25083
            this.isFullWindow = false;
25084
            off(document, 'keydown', this.boundFullWindowOnEscKey_);
25085
 
25086
            // Unhide scroll bars.
25087
            document.documentElement.style.overflow = this.docOrigOverflow;
25088
 
25089
            // Remove fullscreen styles
25090
            removeClass(document.body, 'vjs-full-window');
25091
 
25092
            // Resize the box, controller, and poster to original sizes
25093
            // this.positionAll();
25094
            /**
25095
             * @event Player#exitFullWindow
25096
             * @type {Event}
25097
             */
25098
            this.trigger('exitFullWindow');
25099
        }
25100
 
25101
        /**
25102
         * Get or set disable Picture-in-Picture mode.
25103
         *
25104
         * @param {boolean} [value]
25105
         *                  - true will disable Picture-in-Picture mode
25106
         *                  - false will enable Picture-in-Picture mode
25107
         */
25108
        disablePictureInPicture(value) {
25109
            if (value === undefined) {
25110
                return this.techGet_('disablePictureInPicture');
25111
            }
25112
            this.techCall_('setDisablePictureInPicture', value);
25113
            this.options_.disablePictureInPicture = value;
25114
            this.trigger('disablepictureinpicturechanged');
25115
        }
25116
 
25117
        /**
25118
         * Check if the player is in Picture-in-Picture mode or tell the player that it
25119
         * is or is not in Picture-in-Picture mode.
25120
         *
25121
         * @param  {boolean} [isPiP]
25122
         *         Set the players current Picture-in-Picture state
25123
         *
25124
         * @return {boolean|undefined}
25125
         *         - true if Picture-in-Picture is on and getting
25126
         *         - false if Picture-in-Picture is off and getting
25127
         *         - nothing if setting
25128
         */
25129
        isInPictureInPicture(isPiP) {
25130
            if (isPiP !== undefined) {
25131
                this.isInPictureInPicture_ = !!isPiP;
25132
                this.togglePictureInPictureClass_();
25133
                return;
25134
            }
25135
            return !!this.isInPictureInPicture_;
25136
        }
25137
 
25138
        /**
25139
         * Create a floating video window always on top of other windows so that users may
25140
         * continue consuming media while they interact with other content sites, or
25141
         * applications on their device.
25142
         *
25143
         * This can use document picture-in-picture or element picture in picture
25144
         *
25145
         * Set `enableDocumentPictureInPicture` to `true` to use docPiP on a supported browser
25146
         * Else set `disablePictureInPicture` to `false` to disable elPiP on a supported browser
25147
         *
25148
         *
25149
         * @see [Spec]{@link https://w3c.github.io/picture-in-picture/}
25150
         * @see [Spec]{@link https://wicg.github.io/document-picture-in-picture/}
25151
         *
25152
         * @fires Player#enterpictureinpicture
25153
         *
25154
         * @return {Promise}
25155
         *         A promise with a Picture-in-Picture window.
25156
         */
25157
        requestPictureInPicture() {
25158
            if (this.options_.enableDocumentPictureInPicture && window.documentPictureInPicture) {
25159
                const pipContainer = document.createElement(this.el().tagName);
25160
                pipContainer.classList = this.el().classList;
25161
                pipContainer.classList.add('vjs-pip-container');
25162
                if (this.posterImage) {
25163
                    pipContainer.appendChild(this.posterImage.el().cloneNode(true));
25164
                }
25165
                if (this.titleBar) {
25166
                    pipContainer.appendChild(this.titleBar.el().cloneNode(true));
25167
                }
25168
                pipContainer.appendChild(createEl('p', {
25169
                    className: 'vjs-pip-text'
25170
                }, {}, this.localize('Playing in picture-in-picture')));
25171
                return window.documentPictureInPicture.requestWindow({
25172
                    // The aspect ratio won't be correct, Chrome bug https://crbug.com/1407629
25173
                    width: this.videoWidth(),
25174
                    height: this.videoHeight()
25175
                }).then(pipWindow => {
25176
                    copyStyleSheetsToWindow(pipWindow);
25177
                    this.el_.parentNode.insertBefore(pipContainer, this.el_);
25178
                    pipWindow.document.body.appendChild(this.el_);
25179
                    pipWindow.document.body.classList.add('vjs-pip-window');
25180
                    this.player_.isInPictureInPicture(true);
25181
                    this.player_.trigger('enterpictureinpicture');
25182
 
25183
                    // Listen for the PiP closing event to move the video back.
25184
                    pipWindow.addEventListener('pagehide', event => {
25185
                        const pipVideo = event.target.querySelector('.video-js');
25186
                        pipContainer.parentNode.replaceChild(pipVideo, pipContainer);
25187
                        this.player_.isInPictureInPicture(false);
25188
                        this.player_.trigger('leavepictureinpicture');
25189
                    });
25190
                    return pipWindow;
25191
                });
25192
            }
25193
            if ('pictureInPictureEnabled' in document && this.disablePictureInPicture() === false) {
25194
                /**
25195
                 * This event fires when the player enters picture in picture mode
25196
                 *
25197
                 * @event Player#enterpictureinpicture
25198
                 * @type {Event}
25199
                 */
25200
                return this.techGet_('requestPictureInPicture');
25201
            }
25202
            return Promise.reject('No PiP mode is available');
25203
        }
25204
 
25205
        /**
25206
         * Exit Picture-in-Picture mode.
25207
         *
25208
         * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
25209
         *
25210
         * @fires Player#leavepictureinpicture
25211
         *
25212
         * @return {Promise}
25213
         *         A promise.
25214
         */
25215
        exitPictureInPicture() {
25216
            if (window.documentPictureInPicture && window.documentPictureInPicture.window) {
25217
                // With documentPictureInPicture, Player#leavepictureinpicture is fired in the pagehide handler
25218
                window.documentPictureInPicture.window.close();
25219
                return Promise.resolve();
25220
            }
25221
            if ('pictureInPictureEnabled' in document) {
25222
                /**
25223
                 * This event fires when the player leaves picture in picture mode
25224
                 *
25225
                 * @event Player#leavepictureinpicture
25226
                 * @type {Event}
25227
                 */
25228
                return document.exitPictureInPicture();
25229
            }
25230
        }
25231
 
25232
        /**
25233
         * Called when this Player has focus and a key gets pressed down, or when
25234
         * any Component of this player receives a key press that it doesn't handle.
25235
         * This allows player-wide hotkeys (either as defined below, or optionally
25236
         * by an external function).
25237
         *
25238
         * @param {KeyboardEvent} event
25239
         *        The `keydown` event that caused this function to be called.
25240
         *
25241
         * @listens keydown
25242
         */
25243
        handleKeyDown(event) {
25244
            const {
25245
                userActions
25246
            } = this.options_;
25247
 
25248
            // Bail out if hotkeys are not configured.
25249
            if (!userActions || !userActions.hotkeys) {
25250
                return;
25251
            }
25252
 
25253
            // Function that determines whether or not to exclude an element from
25254
            // hotkeys handling.
25255
            const excludeElement = el => {
25256
                const tagName = el.tagName.toLowerCase();
25257
 
25258
                // The first and easiest test is for `contenteditable` elements.
25259
                if (el.isContentEditable) {
25260
                    return true;
25261
                }
25262
 
25263
                // Inputs matching these types will still trigger hotkey handling as
25264
                // they are not text inputs.
25265
                const allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit'];
25266
                if (tagName === 'input') {
25267
                    return allowedInputTypes.indexOf(el.type) === -1;
25268
                }
25269
 
25270
                // The final test is by tag name. These tags will be excluded entirely.
25271
                const excludedTags = ['textarea'];
25272
                return excludedTags.indexOf(tagName) !== -1;
25273
            };
25274
 
25275
            // Bail out if the user is focused on an interactive form element.
25276
            if (excludeElement(this.el_.ownerDocument.activeElement)) {
25277
                return;
25278
            }
25279
            if (typeof userActions.hotkeys === 'function') {
25280
                userActions.hotkeys.call(this, event);
25281
            } else {
25282
                this.handleHotkeys(event);
25283
            }
25284
        }
25285
 
25286
        /**
25287
         * Called when this Player receives a hotkey keydown event.
25288
         * Supported player-wide hotkeys are:
25289
         *
25290
         *   f          - toggle fullscreen
25291
         *   m          - toggle mute
25292
         *   k or Space - toggle play/pause
25293
         *
25294
         * @param {Event} event
25295
         *        The `keydown` event that caused this function to be called.
25296
         */
25297
        handleHotkeys(event) {
25298
            const hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {};
25299
 
25300
            // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set
25301
            const {
25302
                fullscreenKey = keydownEvent => keycode.isEventKey(keydownEvent, 'f'),
25303
                muteKey = keydownEvent => keycode.isEventKey(keydownEvent, 'm'),
25304
                playPauseKey = keydownEvent => keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space')
25305
            } = hotkeys;
25306
            if (fullscreenKey.call(this, event)) {
25307
                event.preventDefault();
25308
                event.stopPropagation();
25309
                const FSToggle = Component$1.getComponent('FullscreenToggle');
25310
                if (document[this.fsApi_.fullscreenEnabled] !== false) {
25311
                    FSToggle.prototype.handleClick.call(this, event);
25312
                }
25313
            } else if (muteKey.call(this, event)) {
25314
                event.preventDefault();
25315
                event.stopPropagation();
25316
                const MuteToggle = Component$1.getComponent('MuteToggle');
25317
                MuteToggle.prototype.handleClick.call(this, event);
25318
            } else if (playPauseKey.call(this, event)) {
25319
                event.preventDefault();
25320
                event.stopPropagation();
25321
                const PlayToggle = Component$1.getComponent('PlayToggle');
25322
                PlayToggle.prototype.handleClick.call(this, event);
25323
            }
25324
        }
25325
 
25326
        /**
25327
         * Check whether the player can play a given mimetype
25328
         *
25329
         * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
25330
         *
25331
         * @param {string} type
25332
         *        The mimetype to check
25333
         *
25334
         * @return {string}
25335
         *         'probably', 'maybe', or '' (empty string)
25336
         */
25337
        canPlayType(type) {
25338
            let can;
25339
 
25340
            // Loop through each playback technology in the options order
25341
            for (let i = 0, j = this.options_.techOrder; i < j.length; i++) {
25342
                const techName = j[i];
25343
                let tech = Tech.getTech(techName);
25344
 
25345
                // Support old behavior of techs being registered as components.
25346
                // Remove once that deprecated behavior is removed.
25347
                if (!tech) {
25348
                    tech = Component$1.getComponent(techName);
25349
                }
25350
 
25351
                // Check if the current tech is defined before continuing
25352
                if (!tech) {
25353
                    log$1.error(`The "${techName}" tech is undefined. Skipped browser support check for that tech.`);
25354
                    continue;
25355
                }
25356
 
25357
                // Check if the browser supports this technology
25358
                if (tech.isSupported()) {
25359
                    can = tech.canPlayType(type);
25360
                    if (can) {
25361
                        return can;
25362
                    }
25363
                }
25364
            }
25365
            return '';
25366
        }
25367
 
25368
        /**
25369
         * Select source based on tech-order or source-order
25370
         * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
25371
         * defaults to tech-order selection
25372
         *
25373
         * @param {Array} sources
25374
         *        The sources for a media asset
25375
         *
25376
         * @return {Object|boolean}
25377
         *         Object of source and tech order or false
25378
         */
25379
        selectSource(sources) {
25380
            // Get only the techs specified in `techOrder` that exist and are supported by the
25381
            // current platform
25382
            const techs = this.options_.techOrder.map(techName => {
25383
                return [techName, Tech.getTech(techName)];
25384
            }).filter(([techName, tech]) => {
25385
                // Check if the current tech is defined before continuing
25386
                if (tech) {
25387
                    // Check if the browser supports this technology
25388
                    return tech.isSupported();
25389
                }
25390
                log$1.error(`The "${techName}" tech is undefined. Skipped browser support check for that tech.`);
25391
                return false;
25392
            });
25393
 
25394
            // Iterate over each `innerArray` element once per `outerArray` element and execute
25395
            // `tester` with both. If `tester` returns a non-falsy value, exit early and return
25396
            // that value.
25397
            const findFirstPassingTechSourcePair = function (outerArray, innerArray, tester) {
25398
                let found;
25399
                outerArray.some(outerChoice => {
25400
                    return innerArray.some(innerChoice => {
25401
                        found = tester(outerChoice, innerChoice);
25402
                        if (found) {
25403
                            return true;
25404
                        }
25405
                    });
25406
                });
25407
                return found;
25408
            };
25409
            let foundSourceAndTech;
25410
            const flip = fn => (a, b) => fn(b, a);
25411
            const finder = ([techName, tech], source) => {
25412
                if (tech.canPlaySource(source, this.options_[techName.toLowerCase()])) {
25413
                    return {
25414
                        source,
25415
                        tech: techName
25416
                    };
25417
                }
25418
            };
25419
 
25420
            // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
25421
            // to select from them based on their priority.
25422
            if (this.options_.sourceOrder) {
25423
                // Source-first ordering
25424
                foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
25425
            } else {
25426
                // Tech-first ordering
25427
                foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
25428
            }
25429
            return foundSourceAndTech || false;
25430
        }
25431
 
25432
        /**
25433
         * Executes source setting and getting logic
25434
         *
25435
         * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
25436
         *        A SourceObject, an array of SourceObjects, or a string referencing
25437
         *        a URL to a media source. It is _highly recommended_ that an object
25438
         *        or array of objects is used here, so that source selection
25439
         *        algorithms can take the `type` into account.
25440
         *
25441
         *        If not provided, this method acts as a getter.
25442
         * @param {boolean} [isRetry]
25443
         *        Indicates whether this is being called internally as a result of a retry
25444
         *
25445
         * @return {string|undefined}
25446
         *         If the `source` argument is missing, returns the current source
25447
         *         URL. Otherwise, returns nothing/undefined.
25448
         */
25449
        handleSrc_(source, isRetry) {
25450
            // getter usage
25451
            if (typeof source === 'undefined') {
25452
                return this.cache_.src || '';
25453
            }
25454
 
25455
            // Reset retry behavior for new source
25456
            if (this.resetRetryOnError_) {
25457
                this.resetRetryOnError_();
25458
            }
25459
 
25460
            // filter out invalid sources and turn our source into
25461
            // an array of source objects
25462
            const sources = filterSource(source);
25463
 
25464
            // if a source was passed in then it is invalid because
25465
            // it was filtered to a zero length Array. So we have to
25466
            // show an error
25467
            if (!sources.length) {
25468
                this.setTimeout(function () {
25469
                    this.error({
25470
                        code: 4,
25471
                        message: this.options_.notSupportedMessage
25472
                    });
25473
                }, 0);
25474
                return;
25475
            }
25476
 
25477
            // initial sources
25478
            this.changingSrc_ = true;
25479
 
25480
            // Only update the cached source list if we are not retrying a new source after error,
25481
            // since in that case we want to include the failed source(s) in the cache
25482
            if (!isRetry) {
25483
                this.cache_.sources = sources;
25484
            }
25485
            this.updateSourceCaches_(sources[0]);
25486
 
25487
            // middlewareSource is the source after it has been changed by middleware
25488
            setSource(this, sources[0], (middlewareSource, mws) => {
25489
                this.middleware_ = mws;
25490
 
25491
                // since sourceSet is async we have to update the cache again after we select a source since
25492
                // the source that is selected could be out of order from the cache update above this callback.
25493
                if (!isRetry) {
25494
                    this.cache_.sources = sources;
25495
                }
25496
                this.updateSourceCaches_(middlewareSource);
25497
                const err = this.src_(middlewareSource);
25498
                if (err) {
25499
                    if (sources.length > 1) {
25500
                        return this.handleSrc_(sources.slice(1));
25501
                    }
25502
                    this.changingSrc_ = false;
25503
 
25504
                    // We need to wrap this in a timeout to give folks a chance to add error event handlers
25505
                    this.setTimeout(function () {
25506
                        this.error({
25507
                            code: 4,
25508
                            message: this.options_.notSupportedMessage
25509
                        });
25510
                    }, 0);
25511
 
25512
                    // we could not find an appropriate tech, but let's still notify the delegate that this is it
25513
                    // this needs a better comment about why this is needed
25514
                    this.triggerReady();
25515
                    return;
25516
                }
25517
                setTech(mws, this.tech_);
25518
            });
25519
 
25520
            // Try another available source if this one fails before playback.
25521
            if (sources.length > 1) {
25522
                const retry = () => {
25523
                    // Remove the error modal
25524
                    this.error(null);
25525
                    this.handleSrc_(sources.slice(1), true);
25526
                };
25527
                const stopListeningForErrors = () => {
25528
                    this.off('error', retry);
25529
                };
25530
                this.one('error', retry);
25531
                this.one('playing', stopListeningForErrors);
25532
                this.resetRetryOnError_ = () => {
25533
                    this.off('error', retry);
25534
                    this.off('playing', stopListeningForErrors);
25535
                };
25536
            }
25537
        }
25538
 
25539
        /**
25540
         * Get or set the video source.
25541
         *
25542
         * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
25543
         *        A SourceObject, an array of SourceObjects, or a string referencing
25544
         *        a URL to a media source. It is _highly recommended_ that an object
25545
         *        or array of objects is used here, so that source selection
25546
         *        algorithms can take the `type` into account.
25547
         *
25548
         *        If not provided, this method acts as a getter.
25549
         *
25550
         * @return {string|undefined}
25551
         *         If the `source` argument is missing, returns the current source
25552
         *         URL. Otherwise, returns nothing/undefined.
25553
         */
25554
        src(source) {
25555
            return this.handleSrc_(source, false);
25556
        }
25557
 
25558
        /**
25559
         * Set the source object on the tech, returns a boolean that indicates whether
25560
         * there is a tech that can play the source or not
25561
         *
25562
         * @param {Tech~SourceObject} source
25563
         *        The source object to set on the Tech
25564
         *
25565
         * @return {boolean}
25566
         *         - True if there is no Tech to playback this source
25567
         *         - False otherwise
25568
         *
25569
         * @private
25570
         */
25571
        src_(source) {
25572
            const sourceTech = this.selectSource([source]);
25573
            if (!sourceTech) {
25574
                return true;
25575
            }
25576
            if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
25577
                this.changingSrc_ = true;
25578
                // load this technology with the chosen source
25579
                this.loadTech_(sourceTech.tech, sourceTech.source);
25580
                this.tech_.ready(() => {
25581
                    this.changingSrc_ = false;
25582
                });
25583
                return false;
25584
            }
25585
 
25586
            // wait until the tech is ready to set the source
25587
            // and set it synchronously if possible (#2326)
25588
            this.ready(function () {
25589
                // The setSource tech method was added with source handlers
25590
                // so older techs won't support it
25591
                // We need to check the direct prototype for the case where subclasses
25592
                // of the tech do not support source handlers
25593
                if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
25594
                    this.techCall_('setSource', source);
25595
                } else {
25596
                    this.techCall_('src', source.src);
25597
                }
25598
                this.changingSrc_ = false;
25599
            }, true);
25600
            return false;
25601
        }
25602
 
25603
        /**
25604
         * Begin loading the src data.
25605
         */
25606
        load() {
25607
            // Workaround to use the load method with the VHS.
25608
            // Does not cover the case when the load method is called directly from the mediaElement.
25609
            if (this.tech_ && this.tech_.vhs) {
25610
                this.src(this.currentSource());
25611
                return;
25612
            }
25613
            this.techCall_('load');
25614
        }
25615
 
25616
        /**
25617
         * Reset the player. Loads the first tech in the techOrder,
25618
         * removes all the text tracks in the existing `tech`,
25619
         * and calls `reset` on the `tech`.
25620
         */
25621
        reset() {
25622
            if (this.paused()) {
25623
                this.doReset_();
25624
            } else {
25625
                const playPromise = this.play();
25626
                silencePromise(playPromise.then(() => this.doReset_()));
25627
            }
25628
        }
25629
        doReset_() {
25630
            if (this.tech_) {
25631
                this.tech_.clearTracks('text');
25632
            }
25633
            this.removeClass('vjs-playing');
25634
            this.addClass('vjs-paused');
25635
            this.resetCache_();
25636
            this.poster('');
25637
            this.loadTech_(this.options_.techOrder[0], null);
25638
            this.techCall_('reset');
25639
            this.resetControlBarUI_();
25640
            this.error(null);
25641
            if (this.titleBar) {
25642
                this.titleBar.update({
25643
                    title: undefined,
25644
                    description: undefined
25645
                });
25646
            }
25647
            if (isEvented(this)) {
25648
                this.trigger('playerreset');
25649
            }
25650
        }
25651
 
25652
        /**
25653
         * Reset Control Bar's UI by calling sub-methods that reset
25654
         * all of Control Bar's components
25655
         */
25656
        resetControlBarUI_() {
25657
            this.resetProgressBar_();
25658
            this.resetPlaybackRate_();
25659
            this.resetVolumeBar_();
25660
        }
25661
 
25662
        /**
25663
         * Reset tech's progress so progress bar is reset in the UI
25664
         */
25665
        resetProgressBar_() {
25666
            this.currentTime(0);
25667
            const {
25668
                currentTimeDisplay,
25669
                durationDisplay,
25670
                progressControl,
25671
                remainingTimeDisplay
25672
            } = this.controlBar || {};
25673
            const {
25674
                seekBar
25675
            } = progressControl || {};
25676
            if (currentTimeDisplay) {
25677
                currentTimeDisplay.updateContent();
25678
            }
25679
            if (durationDisplay) {
25680
                durationDisplay.updateContent();
25681
            }
25682
            if (remainingTimeDisplay) {
25683
                remainingTimeDisplay.updateContent();
25684
            }
25685
            if (seekBar) {
25686
                seekBar.update();
25687
                if (seekBar.loadProgressBar) {
25688
                    seekBar.loadProgressBar.update();
25689
                }
25690
            }
25691
        }
25692
 
25693
        /**
25694
         * Reset Playback ratio
25695
         */
25696
        resetPlaybackRate_() {
25697
            this.playbackRate(this.defaultPlaybackRate());
25698
            this.handleTechRateChange_();
25699
        }
25700
 
25701
        /**
25702
         * Reset Volume bar
25703
         */
25704
        resetVolumeBar_() {
25705
            this.volume(1.0);
25706
            this.trigger('volumechange');
25707
        }
25708
 
25709
        /**
25710
         * Returns all of the current source objects.
25711
         *
25712
         * @return {Tech~SourceObject[]}
25713
         *         The current source objects
25714
         */
25715
        currentSources() {
25716
            const source = this.currentSource();
25717
            const sources = [];
25718
 
25719
            // assume `{}` or `{ src }`
25720
            if (Object.keys(source).length !== 0) {
25721
                sources.push(source);
25722
            }
25723
            return this.cache_.sources || sources;
25724
        }
25725
 
25726
        /**
25727
         * Returns the current source object.
25728
         *
25729
         * @return {Tech~SourceObject}
25730
         *         The current source object
25731
         */
25732
        currentSource() {
25733
            return this.cache_.source || {};
25734
        }
25735
 
25736
        /**
25737
         * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
25738
         * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
25739
         *
25740
         * @return {string}
25741
         *         The current source
25742
         */
25743
        currentSrc() {
25744
            return this.currentSource() && this.currentSource().src || '';
25745
        }
25746
 
25747
        /**
25748
         * Get the current source type e.g. video/mp4
25749
         * This can allow you rebuild the current source object so that you could load the same
25750
         * source and tech later
25751
         *
25752
         * @return {string}
25753
         *         The source MIME type
25754
         */
25755
        currentType() {
25756
            return this.currentSource() && this.currentSource().type || '';
25757
        }
25758
 
25759
        /**
25760
         * Get or set the preload attribute
25761
         *
25762
         * @param {'none'|'auto'|'metadata'} [value]
25763
         *        Preload mode to pass to tech
25764
         *
25765
         * @return {string|undefined}
25766
         *         - The preload attribute value when getting
25767
         *         - Nothing when setting
25768
         */
25769
        preload(value) {
25770
            if (value !== undefined) {
25771
                this.techCall_('setPreload', value);
25772
                this.options_.preload = value;
25773
                return;
25774
            }
25775
            return this.techGet_('preload');
25776
        }
25777
 
25778
        /**
25779
         * Get or set the autoplay option. When this is a boolean it will
25780
         * modify the attribute on the tech. When this is a string the attribute on
25781
         * the tech will be removed and `Player` will handle autoplay on loadstarts.
25782
         *
25783
         * @param {boolean|'play'|'muted'|'any'} [value]
25784
         *        - true: autoplay using the browser behavior
25785
         *        - false: do not autoplay
25786
         *        - 'play': call play() on every loadstart
25787
         *        - 'muted': call muted() then play() on every loadstart
25788
         *        - 'any': call play() on every loadstart. if that fails call muted() then play().
25789
         *        - *: values other than those listed here will be set `autoplay` to true
25790
         *
25791
         * @return {boolean|string|undefined}
25792
         *         - The current value of autoplay when getting
25793
         *         - Nothing when setting
25794
         */
25795
        autoplay(value) {
25796
            // getter usage
25797
            if (value === undefined) {
25798
                return this.options_.autoplay || false;
25799
            }
25800
            let techAutoplay;
25801
 
25802
            // if the value is a valid string set it to that, or normalize `true` to 'play', if need be
25803
            if (typeof value === 'string' && /(any|play|muted)/.test(value) || value === true && this.options_.normalizeAutoplay) {
25804
                this.options_.autoplay = value;
25805
                this.manualAutoplay_(typeof value === 'string' ? value : 'play');
25806
                techAutoplay = false;
25807
 
25808
                // any falsy value sets autoplay to false in the browser,
25809
                // lets do the same
25810
            } else if (!value) {
25811
                this.options_.autoplay = false;
25812
 
25813
                // any other value (ie truthy) sets autoplay to true
25814
            } else {
25815
                this.options_.autoplay = true;
25816
            }
25817
            techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay;
25818
 
25819
            // if we don't have a tech then we do not queue up
25820
            // a setAutoplay call on tech ready. We do this because the
25821
            // autoplay option will be passed in the constructor and we
25822
            // do not need to set it twice
25823
            if (this.tech_) {
25824
                this.techCall_('setAutoplay', techAutoplay);
25825
            }
25826
        }
25827
 
25828
        /**
25829
         * Set or unset the playsinline attribute.
25830
         * Playsinline tells the browser that non-fullscreen playback is preferred.
25831
         *
25832
         * @param {boolean} [value]
25833
         *        - true means that we should try to play inline by default
25834
         *        - false means that we should use the browser's default playback mode,
25835
         *          which in most cases is inline. iOS Safari is a notable exception
25836
         *          and plays fullscreen by default.
25837
         *
25838
         * @return {string|undefined}
25839
         *         - the current value of playsinline
25840
         *         - Nothing when setting
25841
         *
25842
         * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
25843
         */
25844
        playsinline(value) {
25845
            if (value !== undefined) {
25846
                this.techCall_('setPlaysinline', value);
25847
                this.options_.playsinline = value;
25848
            }
25849
            return this.techGet_('playsinline');
25850
        }
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|undefined}
25860
         *         - The current value of loop when getting
25861
         *         - Nothing when setting
25862
         */
25863
        loop(value) {
25864
            if (value !== undefined) {
25865
                this.techCall_('setLoop', value);
25866
                this.options_.loop = value;
25867
                return;
25868
            }
25869
            return this.techGet_('loop');
25870
        }
25871
 
25872
        /**
25873
         * Get or set the poster image source url
25874
         *
25875
         * @fires Player#posterchange
25876
         *
25877
         * @param {string} [src]
25878
         *        Poster image source URL
25879
         *
25880
         * @return {string|undefined}
25881
         *         - The current value of poster when getting
25882
         *         - Nothing when setting
25883
         */
25884
        poster(src) {
25885
            if (src === undefined) {
25886
                return this.poster_;
25887
            }
25888
 
25889
            // The correct way to remove a poster is to set as an empty string
25890
            // other falsey values will throw errors
25891
            if (!src) {
25892
                src = '';
25893
            }
25894
            if (src === this.poster_) {
25895
                return;
25896
            }
25897
 
25898
            // update the internal poster variable
25899
            this.poster_ = src;
25900
 
25901
            // update the tech's poster
25902
            this.techCall_('setPoster', src);
25903
            this.isPosterFromTech_ = false;
25904
 
25905
            // alert components that the poster has been set
25906
            /**
25907
             * This event fires when the poster image is changed on the player.
25908
             *
25909
             * @event Player#posterchange
25910
             * @type {Event}
25911
             */
25912
            this.trigger('posterchange');
25913
        }
25914
 
25915
        /**
25916
         * Some techs (e.g. YouTube) can provide a poster source in an
25917
         * asynchronous way. We want the poster component to use this
25918
         * poster source so that it covers up the tech's controls.
25919
         * (YouTube's play button). However we only want to use this
25920
         * source if the player user hasn't set a poster through
25921
         * the normal APIs.
25922
         *
25923
         * @fires Player#posterchange
25924
         * @listens Tech#posterchange
25925
         * @private
25926
         */
25927
        handleTechPosterChange_() {
25928
            if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
25929
                const newPoster = this.tech_.poster() || '';
25930
                if (newPoster !== this.poster_) {
25931
                    this.poster_ = newPoster;
25932
                    this.isPosterFromTech_ = true;
25933
 
25934
                    // Let components know the poster has changed
25935
                    this.trigger('posterchange');
25936
                }
25937
            }
25938
        }
25939
 
25940
        /**
25941
         * Get or set whether or not the controls are showing.
25942
         *
25943
         * @fires Player#controlsenabled
25944
         *
25945
         * @param {boolean} [bool]
25946
         *        - true to turn controls on
25947
         *        - false to turn controls off
25948
         *
25949
         * @return {boolean|undefined}
25950
         *         - The current value of controls when getting
25951
         *         - Nothing when setting
25952
         */
25953
        controls(bool) {
25954
            if (bool === undefined) {
25955
                return !!this.controls_;
25956
            }
25957
            bool = !!bool;
25958
 
25959
            // Don't trigger a change event unless it actually changed
25960
            if (this.controls_ === bool) {
25961
                return;
25962
            }
25963
            this.controls_ = bool;
25964
            if (this.usingNativeControls()) {
25965
                this.techCall_('setControls', bool);
25966
            }
25967
            if (this.controls_) {
25968
                this.removeClass('vjs-controls-disabled');
25969
                this.addClass('vjs-controls-enabled');
25970
                /**
25971
                 * @event Player#controlsenabled
25972
                 * @type {Event}
25973
                 */
25974
                this.trigger('controlsenabled');
25975
                if (!this.usingNativeControls()) {
25976
                    this.addTechControlsListeners_();
25977
                }
25978
            } else {
25979
                this.removeClass('vjs-controls-enabled');
25980
                this.addClass('vjs-controls-disabled');
25981
                /**
25982
                 * @event Player#controlsdisabled
25983
                 * @type {Event}
25984
                 */
25985
                this.trigger('controlsdisabled');
25986
                if (!this.usingNativeControls()) {
25987
                    this.removeTechControlsListeners_();
25988
                }
25989
            }
25990
        }
25991
 
25992
        /**
25993
         * Toggle native controls on/off. Native controls are the controls built into
25994
         * devices (e.g. default iPhone controls) or other techs
25995
         * (e.g. Vimeo Controls)
25996
         * **This should only be set by the current tech, because only the tech knows
25997
         * if it can support native controls**
25998
         *
25999
         * @fires Player#usingnativecontrols
26000
         * @fires Player#usingcustomcontrols
26001
         *
26002
         * @param {boolean} [bool]
26003
         *        - true to turn native controls on
26004
         *        - false to turn native controls off
26005
         *
26006
         * @return {boolean|undefined}
26007
         *         - The current value of native controls when getting
26008
         *         - Nothing when setting
26009
         */
26010
        usingNativeControls(bool) {
26011
            if (bool === undefined) {
26012
                return !!this.usingNativeControls_;
26013
            }
26014
            bool = !!bool;
26015
 
26016
            // Don't trigger a change event unless it actually changed
26017
            if (this.usingNativeControls_ === bool) {
26018
                return;
26019
            }
26020
            this.usingNativeControls_ = bool;
26021
            if (this.usingNativeControls_) {
26022
                this.addClass('vjs-using-native-controls');
26023
 
26024
                /**
26025
                 * player is using the native device controls
26026
                 *
26027
                 * @event Player#usingnativecontrols
26028
                 * @type {Event}
26029
                 */
26030
                this.trigger('usingnativecontrols');
26031
            } else {
26032
                this.removeClass('vjs-using-native-controls');
26033
 
26034
                /**
26035
                 * player is using the custom HTML controls
26036
                 *
26037
                 * @event Player#usingcustomcontrols
26038
                 * @type {Event}
26039
                 */
26040
                this.trigger('usingcustomcontrols');
26041
            }
26042
        }
26043
 
26044
        /**
26045
         * Set or get the current MediaError
26046
         *
26047
         * @fires Player#error
26048
         *
26049
         * @param  {MediaError|string|number} [err]
26050
         *         A MediaError or a string/number to be turned
26051
         *         into a MediaError
26052
         *
26053
         * @return {MediaError|null|undefined}
26054
         *         - The current MediaError when getting (or null)
26055
         *         - Nothing when setting
26056
         */
26057
        error(err) {
26058
            if (err === undefined) {
26059
                return this.error_ || null;
26060
            }
26061
 
26062
            // allow hooks to modify error object
26063
            hooks('beforeerror').forEach(hookFunction => {
26064
                const newErr = hookFunction(this, err);
26065
                if (!(isObject$1(newErr) && !Array.isArray(newErr) || typeof newErr === 'string' || typeof newErr === 'number' || newErr === null)) {
26066
                    this.log.error('please return a value that MediaError expects in beforeerror hooks');
26067
                    return;
26068
                }
26069
                err = newErr;
26070
            });
26071
 
26072
            // Suppress the first error message for no compatible source until
26073
            // user interaction
26074
            if (this.options_.suppressNotSupportedError && err && err.code === 4) {
26075
                const triggerSuppressedError = function () {
26076
                    this.error(err);
26077
                };
26078
                this.options_.suppressNotSupportedError = false;
26079
                this.any(['click', 'touchstart'], triggerSuppressedError);
26080
                this.one('loadstart', function () {
26081
                    this.off(['click', 'touchstart'], triggerSuppressedError);
26082
                });
26083
                return;
26084
            }
26085
 
26086
            // restoring to default
26087
            if (err === null) {
26088
                this.error_ = null;
26089
                this.removeClass('vjs-error');
26090
                if (this.errorDisplay) {
26091
                    this.errorDisplay.close();
26092
                }
26093
                return;
26094
            }
26095
            this.error_ = new MediaError(err);
26096
 
26097
            // add the vjs-error classname to the player
26098
            this.addClass('vjs-error');
26099
 
26100
            // log the name of the error type and any message
26101
            // IE11 logs "[object object]" and required you to expand message to see error object
26102
            log$1.error(`(CODE:${this.error_.code} ${MediaError.errorTypes[this.error_.code]})`, this.error_.message, this.error_);
26103
 
26104
            /**
26105
             * @event Player#error
26106
             * @type {Event}
26107
             */
26108
            this.trigger('error');
26109
 
26110
            // notify hooks of the per player error
26111
            hooks('error').forEach(hookFunction => hookFunction(this, this.error_));
26112
            return;
26113
        }
26114
 
26115
        /**
26116
         * Report user activity
26117
         *
26118
         * @param {Object} event
26119
         *        Event object
26120
         */
26121
        reportUserActivity(event) {
26122
            this.userActivity_ = true;
26123
        }
26124
 
26125
        /**
26126
         * Get/set if user is active
26127
         *
26128
         * @fires Player#useractive
26129
         * @fires Player#userinactive
26130
         *
26131
         * @param {boolean} [bool]
26132
         *        - true if the user is active
26133
         *        - false if the user is inactive
26134
         *
26135
         * @return {boolean|undefined}
26136
         *         - The current value of userActive when getting
26137
         *         - Nothing when setting
26138
         */
26139
        userActive(bool) {
26140
            if (bool === undefined) {
26141
                return this.userActive_;
26142
            }
26143
            bool = !!bool;
26144
            if (bool === this.userActive_) {
26145
                return;
26146
            }
26147
            this.userActive_ = bool;
26148
            if (this.userActive_) {
26149
                this.userActivity_ = true;
26150
                this.removeClass('vjs-user-inactive');
26151
                this.addClass('vjs-user-active');
26152
                /**
26153
                 * @event Player#useractive
26154
                 * @type {Event}
26155
                 */
26156
                this.trigger('useractive');
26157
                return;
26158
            }
26159
 
26160
            // Chrome/Safari/IE have bugs where when you change the cursor it can
26161
            // trigger a mousemove event. This causes an issue when you're hiding
26162
            // the cursor when the user is inactive, and a mousemove signals user
26163
            // activity. Making it impossible to go into inactive mode. Specifically
26164
            // this happens in fullscreen when we really need to hide the cursor.
26165
            //
26166
            // When this gets resolved in ALL browsers it can be removed
26167
            // https://code.google.com/p/chromium/issues/detail?id=103041
26168
            if (this.tech_) {
26169
                this.tech_.one('mousemove', function (e) {
26170
                    e.stopPropagation();
26171
                    e.preventDefault();
26172
                });
26173
            }
26174
            this.userActivity_ = false;
26175
            this.removeClass('vjs-user-active');
26176
            this.addClass('vjs-user-inactive');
26177
            /**
26178
             * @event Player#userinactive
26179
             * @type {Event}
26180
             */
26181
            this.trigger('userinactive');
26182
        }
26183
 
26184
        /**
26185
         * Listen for user activity based on timeout value
26186
         *
26187
         * @private
26188
         */
26189
        listenForUserActivity_() {
26190
            let mouseInProgress;
26191
            let lastMoveX;
26192
            let lastMoveY;
26193
            const handleActivity = bind_(this, this.reportUserActivity);
26194
            const handleMouseMove = function (e) {
26195
                // #1068 - Prevent mousemove spamming
26196
                // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
26197
                if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
26198
                    lastMoveX = e.screenX;
26199
                    lastMoveY = e.screenY;
26200
                    handleActivity();
26201
                }
26202
            };
26203
            const handleMouseDown = function () {
26204
                handleActivity();
26205
                // For as long as the they are touching the device or have their mouse down,
26206
                // we consider them active even if they're not moving their finger or mouse.
26207
                // So we want to continue to update that they are active
26208
                this.clearInterval(mouseInProgress);
26209
                // Setting userActivity=true now and setting the interval to the same time
26210
                // as the activityCheck interval (250) should ensure we never miss the
26211
                // next activityCheck
26212
                mouseInProgress = this.setInterval(handleActivity, 250);
26213
            };
26214
            const handleMouseUpAndMouseLeave = function (event) {
26215
                handleActivity();
26216
                // Stop the interval that maintains activity if the mouse/touch is down
26217
                this.clearInterval(mouseInProgress);
26218
            };
26219
 
26220
            // Any mouse movement will be considered user activity
26221
            this.on('mousedown', handleMouseDown);
26222
            this.on('mousemove', handleMouseMove);
26223
            this.on('mouseup', handleMouseUpAndMouseLeave);
26224
            this.on('mouseleave', handleMouseUpAndMouseLeave);
26225
            const controlBar = this.getChild('controlBar');
26226
 
26227
            // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed)
26228
            // controlBar would no longer be hidden by default timeout.
26229
            if (controlBar && !IS_IOS && !IS_ANDROID) {
26230
                controlBar.on('mouseenter', function (event) {
26231
                    if (this.player().options_.inactivityTimeout !== 0) {
26232
                        this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
26233
                    }
26234
                    this.player().options_.inactivityTimeout = 0;
26235
                });
26236
                controlBar.on('mouseleave', function (event) {
26237
                    this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
26238
                });
26239
            }
26240
 
26241
            // Listen for keyboard navigation
26242
            // Shouldn't need to use inProgress interval because of key repeat
26243
            this.on('keydown', handleActivity);
26244
            this.on('keyup', handleActivity);
26245
 
26246
            // Run an interval every 250 milliseconds instead of stuffing everything into
26247
            // the mousemove/touchmove function itself, to prevent performance degradation.
26248
            // `this.reportUserActivity` simply sets this.userActivity_ to true, which
26249
            // then gets picked up by this loop
26250
            // http://ejohn.org/blog/learning-from-twitter/
26251
            let inactivityTimeout;
26252
 
26253
            /** @this Player */
26254
            const activityCheck = function () {
26255
                // Check to see if mouse/touch activity has happened
26256
                if (!this.userActivity_) {
26257
                    return;
26258
                }
26259
 
26260
                // Reset the activity tracker
26261
                this.userActivity_ = false;
26262
 
26263
                // If the user state was inactive, set the state to active
26264
                this.userActive(true);
26265
 
26266
                // Clear any existing inactivity timeout to start the timer over
26267
                this.clearTimeout(inactivityTimeout);
26268
                const timeout = this.options_.inactivityTimeout;
26269
                if (timeout <= 0) {
26270
                    return;
26271
                }
26272
 
26273
                // In <timeout> milliseconds, if no more activity has occurred the
26274
                // user will be considered inactive
26275
                inactivityTimeout = this.setTimeout(function () {
26276
                    // Protect against the case where the inactivityTimeout can trigger just
26277
                    // before the next user activity is picked up by the activity check loop
26278
                    // causing a flicker
26279
                    if (!this.userActivity_) {
26280
                        this.userActive(false);
26281
                    }
26282
                }, timeout);
26283
            };
26284
            this.setInterval(activityCheck, 250);
26285
        }
26286
 
26287
        /**
26288
         * Gets or sets the current playback rate. A playback rate of
26289
         * 1.0 represents normal speed and 0.5 would indicate half-speed
26290
         * playback, for instance.
26291
         *
26292
         * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
26293
         *
26294
         * @param {number} [rate]
26295
         *       New playback rate to set.
26296
         *
26297
         * @return {number|undefined}
26298
         *         - The current playback rate when getting or 1.0
26299
         *         - Nothing when setting
26300
         */
26301
        playbackRate(rate) {
26302
            if (rate !== undefined) {
26303
                // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
26304
                // that is registered above
26305
                this.techCall_('setPlaybackRate', rate);
26306
                return;
26307
            }
26308
            if (this.tech_ && this.tech_.featuresPlaybackRate) {
26309
                return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
26310
            }
26311
            return 1.0;
26312
        }
26313
 
26314
        /**
26315
         * Gets or sets the current default playback rate. A default playback rate of
26316
         * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
26317
         * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
26318
         * not the current playbackRate.
26319
         *
26320
         * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
26321
         *
26322
         * @param {number} [rate]
26323
         *       New default playback rate to set.
26324
         *
26325
         * @return {number|undefined}
26326
         *         - The default playback rate when getting or 1.0
26327
         *         - Nothing when setting
26328
         */
26329
        defaultPlaybackRate(rate) {
26330
            if (rate !== undefined) {
26331
                return this.techCall_('setDefaultPlaybackRate', rate);
26332
            }
26333
            if (this.tech_ && this.tech_.featuresPlaybackRate) {
26334
                return this.techGet_('defaultPlaybackRate');
26335
            }
26336
            return 1.0;
26337
        }
26338
 
26339
        /**
26340
         * Gets or sets the audio flag
26341
         *
26342
         * @param {boolean} [bool]
26343
         *        - true signals that this is an audio player
26344
         *        - false signals that this is not an audio player
26345
         *
26346
         * @return {boolean|undefined}
26347
         *         - The current value of isAudio when getting
26348
         *         - Nothing when setting
26349
         */
26350
        isAudio(bool) {
26351
            if (bool !== undefined) {
26352
                this.isAudio_ = !!bool;
26353
                return;
26354
            }
26355
            return !!this.isAudio_;
26356
        }
26357
        enableAudioOnlyUI_() {
26358
            // Update styling immediately to show the control bar so we can get its height
26359
            this.addClass('vjs-audio-only-mode');
26360
            const playerChildren = this.children();
26361
            const controlBar = this.getChild('ControlBar');
26362
            const controlBarHeight = controlBar && controlBar.currentHeight();
26363
 
26364
            // Hide all player components except the control bar. Control bar components
26365
            // needed only for video are hidden with CSS
26366
            playerChildren.forEach(child => {
26367
                if (child === controlBar) {
26368
                    return;
26369
                }
26370
                if (child.el_ && !child.hasClass('vjs-hidden')) {
26371
                    child.hide();
26372
                    this.audioOnlyCache_.hiddenChildren.push(child);
26373
                }
26374
            });
26375
            this.audioOnlyCache_.playerHeight = this.currentHeight();
26376
 
26377
            // Set the player height the same as the control bar
26378
            this.height(controlBarHeight);
26379
            this.trigger('audioonlymodechange');
26380
        }
26381
        disableAudioOnlyUI_() {
26382
            this.removeClass('vjs-audio-only-mode');
26383
 
26384
            // Show player components that were previously hidden
26385
            this.audioOnlyCache_.hiddenChildren.forEach(child => child.show());
26386
 
26387
            // Reset player height
26388
            this.height(this.audioOnlyCache_.playerHeight);
26389
            this.trigger('audioonlymodechange');
26390
        }
26391
 
26392
        /**
26393
         * Get the current audioOnlyMode state or set audioOnlyMode to true or false.
26394
         *
26395
         * Setting this to `true` will hide all player components except the control bar,
26396
         * as well as control bar components needed only for video.
26397
         *
26398
         * @param {boolean} [value]
26399
         *         The value to set audioOnlyMode to.
26400
         *
26401
         * @return {Promise|boolean}
26402
         *        A Promise is returned when setting the state, and a boolean when getting
26403
         *        the present state
26404
         */
26405
        audioOnlyMode(value) {
26406
            if (typeof value !== 'boolean' || value === this.audioOnlyMode_) {
26407
                return this.audioOnlyMode_;
26408
            }
26409
            this.audioOnlyMode_ = value;
26410
 
26411
            // Enable Audio Only Mode
26412
            if (value) {
26413
                const exitPromises = [];
26414
 
26415
                // Fullscreen and PiP are not supported in audioOnlyMode, so exit if we need to.
26416
                if (this.isInPictureInPicture()) {
26417
                    exitPromises.push(this.exitPictureInPicture());
26418
                }
26419
                if (this.isFullscreen()) {
26420
                    exitPromises.push(this.exitFullscreen());
26421
                }
26422
                if (this.audioPosterMode()) {
26423
                    exitPromises.push(this.audioPosterMode(false));
26424
                }
26425
                return Promise.all(exitPromises).then(() => this.enableAudioOnlyUI_());
26426
            }
26427
 
26428
            // Disable Audio Only Mode
26429
            return Promise.resolve().then(() => this.disableAudioOnlyUI_());
26430
        }
26431
        enablePosterModeUI_() {
26432
            // Hide the video element and show the poster image to enable posterModeUI
26433
            const tech = this.tech_ && this.tech_;
26434
            tech.hide();
26435
            this.addClass('vjs-audio-poster-mode');
26436
            this.trigger('audiopostermodechange');
26437
        }
26438
        disablePosterModeUI_() {
26439
            // Show the video element and hide the poster image to disable posterModeUI
26440
            const tech = this.tech_ && this.tech_;
26441
            tech.show();
26442
            this.removeClass('vjs-audio-poster-mode');
26443
            this.trigger('audiopostermodechange');
26444
        }
26445
 
26446
        /**
26447
         * Get the current audioPosterMode state or set audioPosterMode to true or false
26448
         *
26449
         * @param {boolean} [value]
26450
         *         The value to set audioPosterMode to.
26451
         *
26452
         * @return {Promise|boolean}
26453
         *         A Promise is returned when setting the state, and a boolean when getting
26454
         *        the present state
26455
         */
26456
        audioPosterMode(value) {
26457
            if (typeof value !== 'boolean' || value === this.audioPosterMode_) {
26458
                return this.audioPosterMode_;
26459
            }
26460
            this.audioPosterMode_ = value;
26461
            if (value) {
26462
                if (this.audioOnlyMode()) {
26463
                    const audioOnlyModePromise = this.audioOnlyMode(false);
26464
                    return audioOnlyModePromise.then(() => {
26465
                        // enable audio poster mode after audio only mode is disabled
26466
                        this.enablePosterModeUI_();
26467
                    });
26468
                }
26469
                return Promise.resolve().then(() => {
26470
                    // enable audio poster mode
26471
                    this.enablePosterModeUI_();
26472
                });
26473
            }
26474
            return Promise.resolve().then(() => {
26475
                // disable audio poster mode
26476
                this.disablePosterModeUI_();
26477
            });
26478
        }
26479
 
26480
        /**
26481
         * A helper method for adding a {@link TextTrack} to our
26482
         * {@link TextTrackList}.
26483
         *
26484
         * In addition to the W3C settings we allow adding additional info through options.
26485
         *
26486
         * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
26487
         *
26488
         * @param {string} [kind]
26489
         *        the kind of TextTrack you are adding
26490
         *
26491
         * @param {string} [label]
26492
         *        the label to give the TextTrack label
26493
         *
26494
         * @param {string} [language]
26495
         *        the language to set on the TextTrack
26496
         *
26497
         * @return {TextTrack|undefined}
26498
         *         the TextTrack that was added or undefined
26499
         *         if there is no tech
26500
         */
26501
        addTextTrack(kind, label, language) {
26502
            if (this.tech_) {
26503
                return this.tech_.addTextTrack(kind, label, language);
26504
            }
26505
        }
26506
 
26507
        /**
26508
         * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}.
26509
         *
26510
         * @param {Object} options
26511
         *        Options to pass to {@link HTMLTrackElement} during creation. See
26512
         *        {@link HTMLTrackElement} for object properties that you should use.
26513
         *
26514
         * @param {boolean} [manualCleanup=false] if set to true, the TextTrack will not be removed
26515
         *                                        from the TextTrackList and HtmlTrackElementList
26516
         *                                        after a source change
26517
         *
26518
         * @return { import('./tracks/html-track-element').default }
26519
         *         the HTMLTrackElement that was created and added
26520
         *         to the HtmlTrackElementList and the remote
26521
         *         TextTrackList
26522
         *
26523
         */
26524
        addRemoteTextTrack(options, manualCleanup) {
26525
            if (this.tech_) {
26526
                return this.tech_.addRemoteTextTrack(options, manualCleanup);
26527
            }
26528
        }
26529
 
26530
        /**
26531
         * Remove a remote {@link TextTrack} from the respective
26532
         * {@link TextTrackList} and {@link HtmlTrackElementList}.
26533
         *
26534
         * @param {Object} track
26535
         *        Remote {@link TextTrack} to remove
26536
         *
26537
         * @return {undefined}
26538
         *         does not return anything
26539
         */
26540
        removeRemoteTextTrack(obj = {}) {
26541
            let {
26542
                track
26543
            } = obj;
26544
            if (!track) {
26545
                track = obj;
26546
            }
26547
 
26548
            // destructure the input into an object with a track argument, defaulting to arguments[0]
26549
            // default the whole argument to an empty object if nothing was passed in
26550
 
26551
            if (this.tech_) {
26552
                return this.tech_.removeRemoteTextTrack(track);
26553
            }
26554
        }
26555
 
26556
        /**
26557
         * Gets available media playback quality metrics as specified by the W3C's Media
26558
         * Playback Quality API.
26559
         *
26560
         * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
26561
         *
26562
         * @return {Object|undefined}
26563
         *         An object with supported media playback quality metrics or undefined if there
26564
         *         is no tech or the tech does not support it.
26565
         */
26566
        getVideoPlaybackQuality() {
26567
            return this.techGet_('getVideoPlaybackQuality');
26568
        }
26569
 
26570
        /**
26571
         * Get video width
26572
         *
26573
         * @return {number}
26574
         *         current video width
26575
         */
26576
        videoWidth() {
26577
            return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
26578
        }
26579
 
26580
        /**
26581
         * Get video height
26582
         *
26583
         * @return {number}
26584
         *         current video height
26585
         */
26586
        videoHeight() {
26587
            return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
26588
        }
26589
 
26590
        /**
26591
         * Set or get the player's language code.
26592
         *
26593
         * Changing the language will trigger
26594
         * [languagechange]{@link Player#event:languagechange}
26595
         * which Components can use to update control text.
26596
         * ClickableComponent will update its control text by default on
26597
         * [languagechange]{@link Player#event:languagechange}.
26598
         *
26599
         * @fires Player#languagechange
26600
         *
26601
         * @param {string} [code]
26602
         *        the language code to set the player to
26603
         *
26604
         * @return {string|undefined}
26605
         *         - The current language code when getting
26606
         *         - Nothing when setting
26607
         */
26608
        language(code) {
26609
            if (code === undefined) {
26610
                return this.language_;
26611
            }
26612
            if (this.language_ !== String(code).toLowerCase()) {
26613
                this.language_ = String(code).toLowerCase();
26614
 
26615
                // during first init, it's possible some things won't be evented
26616
                if (isEvented(this)) {
26617
                    /**
26618
                     * fires when the player language change
26619
                     *
26620
                     * @event Player#languagechange
26621
                     * @type {Event}
26622
                     */
26623
                    this.trigger('languagechange');
26624
                }
26625
            }
26626
        }
26627
 
26628
        /**
26629
         * Get the player's language dictionary
26630
         * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
26631
         * Languages specified directly in the player options have precedence
26632
         *
26633
         * @return {Array}
26634
         *         An array of of supported languages
26635
         */
26636
        languages() {
26637
            return merge$2(Player.prototype.options_.languages, this.languages_);
26638
        }
26639
 
26640
        /**
26641
         * returns a JavaScript object representing the current track
26642
         * information. **DOES not return it as JSON**
26643
         *
26644
         * @return {Object}
26645
         *         Object representing the current of track info
26646
         */
26647
        toJSON() {
26648
            const options = merge$2(this.options_);
26649
            const tracks = options.tracks;
26650
            options.tracks = [];
26651
            for (let i = 0; i < tracks.length; i++) {
26652
                let track = tracks[i];
26653
 
26654
                // deep merge tracks and null out player so no circular references
26655
                track = merge$2(track);
26656
                track.player = undefined;
26657
                options.tracks[i] = track;
26658
            }
26659
            return options;
26660
        }
26661
 
26662
        /**
26663
         * Creates a simple modal dialog (an instance of the {@link ModalDialog}
26664
         * component) that immediately overlays the player with arbitrary
26665
         * content and removes itself when closed.
26666
         *
26667
         * @param {string|Function|Element|Array|null} content
26668
         *        Same as {@link ModalDialog#content}'s param of the same name.
26669
         *        The most straight-forward usage is to provide a string or DOM
26670
         *        element.
26671
         *
26672
         * @param {Object} [options]
26673
         *        Extra options which will be passed on to the {@link ModalDialog}.
26674
         *
26675
         * @return {ModalDialog}
26676
         *         the {@link ModalDialog} that was created
26677
         */
26678
        createModal(content, options) {
26679
            options = options || {};
26680
            options.content = content || '';
26681
            const modal = new ModalDialog(this, options);
26682
            this.addChild(modal);
26683
            modal.on('dispose', () => {
26684
                this.removeChild(modal);
26685
            });
26686
            modal.open();
26687
            return modal;
26688
        }
26689
 
26690
        /**
26691
         * Change breakpoint classes when the player resizes.
26692
         *
26693
         * @private
26694
         */
26695
        updateCurrentBreakpoint_() {
26696
            if (!this.responsive()) {
26697
                return;
26698
            }
26699
            const currentBreakpoint = this.currentBreakpoint();
26700
            const currentWidth = this.currentWidth();
26701
            for (let i = 0; i < BREAKPOINT_ORDER.length; i++) {
26702
                const candidateBreakpoint = BREAKPOINT_ORDER[i];
26703
                const maxWidth = this.breakpoints_[candidateBreakpoint];
26704
                if (currentWidth <= maxWidth) {
26705
                    // The current breakpoint did not change, nothing to do.
26706
                    if (currentBreakpoint === candidateBreakpoint) {
26707
                        return;
26708
                    }
26709
 
26710
                    // Only remove a class if there is a current breakpoint.
26711
                    if (currentBreakpoint) {
26712
                        this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
26713
                    }
26714
                    this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
26715
                    this.breakpoint_ = candidateBreakpoint;
26716
                    break;
26717
                }
26718
            }
26719
        }
26720
 
26721
        /**
26722
         * Removes the current breakpoint.
26723
         *
26724
         * @private
26725
         */
26726
        removeCurrentBreakpoint_() {
26727
            const className = this.currentBreakpointClass();
26728
            this.breakpoint_ = '';
26729
            if (className) {
26730
                this.removeClass(className);
26731
            }
26732
        }
26733
 
26734
        /**
26735
         * Get or set breakpoints on the player.
26736
         *
26737
         * Calling this method with an object or `true` will remove any previous
26738
         * custom breakpoints and start from the defaults again.
26739
         *
26740
         * @param  {Object|boolean} [breakpoints]
26741
         *         If an object is given, it can be used to provide custom
26742
         *         breakpoints. If `true` is given, will set default breakpoints.
26743
         *         If this argument is not given, will simply return the current
26744
         *         breakpoints.
26745
         *
26746
         * @param  {number} [breakpoints.tiny]
26747
         *         The maximum width for the "vjs-layout-tiny" class.
26748
         *
26749
         * @param  {number} [breakpoints.xsmall]
26750
         *         The maximum width for the "vjs-layout-x-small" class.
26751
         *
26752
         * @param  {number} [breakpoints.small]
26753
         *         The maximum width for the "vjs-layout-small" class.
26754
         *
26755
         * @param  {number} [breakpoints.medium]
26756
         *         The maximum width for the "vjs-layout-medium" class.
26757
         *
26758
         * @param  {number} [breakpoints.large]
26759
         *         The maximum width for the "vjs-layout-large" class.
26760
         *
26761
         * @param  {number} [breakpoints.xlarge]
26762
         *         The maximum width for the "vjs-layout-x-large" class.
26763
         *
26764
         * @param  {number} [breakpoints.huge]
26765
         *         The maximum width for the "vjs-layout-huge" class.
26766
         *
26767
         * @return {Object}
26768
         *         An object mapping breakpoint names to maximum width values.
26769
         */
26770
        breakpoints(breakpoints) {
26771
            // Used as a getter.
26772
            if (breakpoints === undefined) {
26773
                return Object.assign(this.breakpoints_);
26774
            }
26775
            this.breakpoint_ = '';
26776
            this.breakpoints_ = Object.assign({}, DEFAULT_BREAKPOINTS, breakpoints);
26777
 
26778
            // When breakpoint definitions change, we need to update the currently
26779
            // selected breakpoint.
26780
            this.updateCurrentBreakpoint_();
26781
 
26782
            // Clone the breakpoints before returning.
26783
            return Object.assign(this.breakpoints_);
26784
        }
26785
 
26786
        /**
26787
         * Get or set a flag indicating whether or not this player should adjust
26788
         * its UI based on its dimensions.
26789
         *
26790
         * @param  {boolean} [value]
26791
         *         Should be `true` if the player should adjust its UI based on its
26792
         *         dimensions; otherwise, should be `false`.
26793
         *
26794
         * @return {boolean|undefined}
26795
         *         Will be `true` if this player should adjust its UI based on its
26796
         *         dimensions; otherwise, will be `false`.
26797
         *         Nothing if setting
26798
         */
26799
        responsive(value) {
26800
            // Used as a getter.
26801
            if (value === undefined) {
26802
                return this.responsive_;
26803
            }
26804
            value = Boolean(value);
26805
            const current = this.responsive_;
26806
 
26807
            // Nothing changed.
26808
            if (value === current) {
26809
                return;
26810
            }
26811
 
26812
            // The value actually changed, set it.
26813
            this.responsive_ = value;
26814
 
26815
            // Start listening for breakpoints and set the initial breakpoint if the
26816
            // player is now responsive.
26817
            if (value) {
26818
                this.on('playerresize', this.boundUpdateCurrentBreakpoint_);
26819
                this.updateCurrentBreakpoint_();
26820
 
26821
                // Stop listening for breakpoints if the player is no longer responsive.
26822
            } else {
26823
                this.off('playerresize', this.boundUpdateCurrentBreakpoint_);
26824
                this.removeCurrentBreakpoint_();
26825
            }
26826
            return value;
26827
        }
26828
 
26829
        /**
26830
         * Get current breakpoint name, if any.
26831
         *
26832
         * @return {string}
26833
         *         If there is currently a breakpoint set, returns a the key from the
26834
         *         breakpoints object matching it. Otherwise, returns an empty string.
26835
         */
26836
        currentBreakpoint() {
26837
            return this.breakpoint_;
26838
        }
26839
 
26840
        /**
26841
         * Get the current breakpoint class name.
26842
         *
26843
         * @return {string}
26844
         *         The matching class name (e.g. `"vjs-layout-tiny"` or
26845
         *         `"vjs-layout-large"`) for the current breakpoint. Empty string if
26846
         *         there is no current breakpoint.
26847
         */
26848
        currentBreakpointClass() {
26849
            return BREAKPOINT_CLASSES[this.breakpoint_] || '';
26850
        }
26851
 
26852
        /**
26853
         * An object that describes a single piece of media.
26854
         *
26855
         * Properties that are not part of this type description will be retained; so,
26856
         * this can be viewed as a generic metadata storage mechanism as well.
26857
         *
26858
         * @see      {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
26859
         * @typedef  {Object} Player~MediaObject
26860
         *
26861
         * @property {string} [album]
26862
         *           Unused, except if this object is passed to the `MediaSession`
26863
         *           API.
26864
         *
26865
         * @property {string} [artist]
26866
         *           Unused, except if this object is passed to the `MediaSession`
26867
         *           API.
26868
         *
26869
         * @property {Object[]} [artwork]
26870
         *           Unused, except if this object is passed to the `MediaSession`
26871
         *           API. If not specified, will be populated via the `poster`, if
26872
         *           available.
26873
         *
26874
         * @property {string} [poster]
26875
         *           URL to an image that will display before playback.
26876
         *
26877
         * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
26878
         *           A single source object, an array of source objects, or a string
26879
         *           referencing a URL to a media source. It is _highly recommended_
26880
         *           that an object or array of objects is used here, so that source
26881
         *           selection algorithms can take the `type` into account.
26882
         *
26883
         * @property {string} [title]
26884
         *           Unused, except if this object is passed to the `MediaSession`
26885
         *           API.
26886
         *
26887
         * @property {Object[]} [textTracks]
26888
         *           An array of objects to be used to create text tracks, following
26889
         *           the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}.
26890
         *           For ease of removal, these will be created as "remote" text
26891
         *           tracks and set to automatically clean up on source changes.
26892
         *
26893
         *           These objects may have properties like `src`, `kind`, `label`,
26894
         *           and `language`, see {@link Tech#createRemoteTextTrack}.
26895
         */
26896
 
26897
        /**
26898
         * Populate the player using a {@link Player~MediaObject|MediaObject}.
26899
         *
26900
         * @param  {Player~MediaObject} media
26901
         *         A media object.
26902
         *
26903
         * @param  {Function} ready
26904
         *         A callback to be called when the player is ready.
26905
         */
26906
        loadMedia(media, ready) {
26907
            if (!media || typeof media !== 'object') {
26908
                return;
26909
            }
26910
            const crossOrigin = this.crossOrigin();
26911
            this.reset();
26912
 
26913
            // Clone the media object so it cannot be mutated from outside.
26914
            this.cache_.media = merge$2(media);
26915
            const {
26916
                artist,
26917
                artwork,
26918
                description,
26919
                poster,
26920
                src,
26921
                textTracks,
26922
                title
26923
            } = this.cache_.media;
26924
 
26925
            // If `artwork` is not given, create it using `poster`.
26926
            if (!artwork && poster) {
26927
                this.cache_.media.artwork = [{
26928
                    src: poster,
26929
                    type: getMimetype(poster)
26930
                }];
26931
            }
26932
            if (crossOrigin) {
26933
                this.crossOrigin(crossOrigin);
26934
            }
26935
            if (src) {
26936
                this.src(src);
26937
            }
26938
            if (poster) {
26939
                this.poster(poster);
26940
            }
26941
            if (Array.isArray(textTracks)) {
26942
                textTracks.forEach(tt => this.addRemoteTextTrack(tt, false));
26943
            }
26944
            if (this.titleBar) {
26945
                this.titleBar.update({
26946
                    title,
26947
                    description: description || artist || ''
26948
                });
26949
            }
26950
            this.ready(ready);
26951
        }
26952
 
26953
        /**
26954
         * Get a clone of the current {@link Player~MediaObject} for this player.
26955
         *
26956
         * If the `loadMedia` method has not been used, will attempt to return a
26957
         * {@link Player~MediaObject} based on the current state of the player.
26958
         *
26959
         * @return {Player~MediaObject}
26960
         */
26961
        getMedia() {
26962
            if (!this.cache_.media) {
26963
                const poster = this.poster();
26964
                const src = this.currentSources();
26965
                const textTracks = Array.prototype.map.call(this.remoteTextTracks(), tt => ({
26966
                    kind: tt.kind,
26967
                    label: tt.label,
26968
                    language: tt.language,
26969
                    src: tt.src
26970
                }));
26971
                const media = {
26972
                    src,
26973
                    textTracks
26974
                };
26975
                if (poster) {
26976
                    media.poster = poster;
26977
                    media.artwork = [{
26978
                        src: media.poster,
26979
                        type: getMimetype(media.poster)
26980
                    }];
26981
                }
26982
                return media;
26983
            }
26984
            return merge$2(this.cache_.media);
26985
        }
26986
 
26987
        /**
26988
         * Gets tag settings
26989
         *
26990
         * @param {Element} tag
26991
         *        The player tag
26992
         *
26993
         * @return {Object}
26994
         *         An object containing all of the settings
26995
         *         for a player tag
26996
         */
26997
        static getTagSettings(tag) {
26998
            const baseOptions = {
26999
                sources: [],
27000
                tracks: []
27001
            };
27002
            const tagOptions = getAttributes(tag);
27003
            const dataSetup = tagOptions['data-setup'];
27004
            if (hasClass(tag, 'vjs-fill')) {
27005
                tagOptions.fill = true;
27006
            }
27007
            if (hasClass(tag, 'vjs-fluid')) {
27008
                tagOptions.fluid = true;
27009
            }
27010
 
27011
            // Check if data-setup attr exists.
27012
            if (dataSetup !== null) {
27013
                // Parse options JSON
27014
                // If empty string, make it a parsable json object.
27015
                const [err, data] = tuple(dataSetup || '{}');
27016
                if (err) {
27017
                    log$1.error(err);
27018
                }
27019
                Object.assign(tagOptions, data);
27020
            }
27021
            Object.assign(baseOptions, tagOptions);
27022
 
27023
            // Get tag children settings
27024
            if (tag.hasChildNodes()) {
27025
                const children = tag.childNodes;
27026
                for (let i = 0, j = children.length; i < j; i++) {
27027
                    const child = children[i];
27028
                    // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
27029
                    const childName = child.nodeName.toLowerCase();
27030
                    if (childName === 'source') {
27031
                        baseOptions.sources.push(getAttributes(child));
27032
                    } else if (childName === 'track') {
27033
                        baseOptions.tracks.push(getAttributes(child));
27034
                    }
27035
                }
27036
            }
27037
            return baseOptions;
27038
        }
27039
 
27040
        /**
27041
         * Set debug mode to enable/disable logs at info level.
27042
         *
27043
         * @param {boolean} enabled
27044
         * @fires Player#debugon
27045
         * @fires Player#debugoff
27046
         * @return {boolean|undefined}
27047
         */
27048
        debug(enabled) {
27049
            if (enabled === undefined) {
27050
                return this.debugEnabled_;
27051
            }
27052
            if (enabled) {
27053
                this.trigger('debugon');
27054
                this.previousLogLevel_ = this.log.level;
27055
                this.log.level('debug');
27056
                this.debugEnabled_ = true;
27057
            } else {
27058
                this.trigger('debugoff');
27059
                this.log.level(this.previousLogLevel_);
27060
                this.previousLogLevel_ = undefined;
27061
                this.debugEnabled_ = false;
27062
            }
27063
        }
27064
 
27065
        /**
27066
         * Set or get current playback rates.
27067
         * Takes an array and updates the playback rates menu with the new items.
27068
         * Pass in an empty array to hide the menu.
27069
         * Values other than arrays are ignored.
27070
         *
27071
         * @fires Player#playbackrateschange
27072
         * @param {number[]} newRates
27073
         *                   The new rates that the playback rates menu should update to.
27074
         *                   An empty array will hide the menu
27075
         * @return {number[]} When used as a getter will return the current playback rates
27076
         */
27077
        playbackRates(newRates) {
27078
            if (newRates === undefined) {
27079
                return this.cache_.playbackRates;
27080
            }
27081
 
27082
            // ignore any value that isn't an array
27083
            if (!Array.isArray(newRates)) {
27084
                return;
27085
            }
27086
 
27087
            // ignore any arrays that don't only contain numbers
27088
            if (!newRates.every(rate => typeof rate === 'number')) {
27089
                return;
27090
            }
27091
            this.cache_.playbackRates = newRates;
27092
 
27093
            /**
27094
             * fires when the playback rates in a player are changed
27095
             *
27096
             * @event Player#playbackrateschange
27097
             * @type {Event}
27098
             */
27099
            this.trigger('playbackrateschange');
27100
        }
27101
    }
27102
 
27103
    /**
27104
     * Get the {@link VideoTrackList}
27105
     *
27106
     * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
27107
     *
27108
     * @return {VideoTrackList}
27109
     *         the current video track list
27110
     *
27111
     * @method Player.prototype.videoTracks
27112
     */
27113
 
27114
    /**
27115
     * Get the {@link AudioTrackList}
27116
     *
27117
     * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
27118
     *
27119
     * @return {AudioTrackList}
27120
     *         the current audio track list
27121
     *
27122
     * @method Player.prototype.audioTracks
27123
     */
27124
 
27125
    /**
27126
     * Get the {@link TextTrackList}
27127
     *
27128
     * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
27129
     *
27130
     * @return {TextTrackList}
27131
     *         the current text track list
27132
     *
27133
     * @method Player.prototype.textTracks
27134
     */
27135
 
27136
    /**
27137
     * Get the remote {@link TextTrackList}
27138
     *
27139
     * @return {TextTrackList}
27140
     *         The current remote text track list
27141
     *
27142
     * @method Player.prototype.remoteTextTracks
27143
     */
27144
 
27145
    /**
27146
     * Get the remote {@link HtmlTrackElementList} tracks.
27147
     *
27148
     * @return {HtmlTrackElementList}
27149
     *         The current remote text track element list
27150
     *
27151
     * @method Player.prototype.remoteTextTrackEls
27152
     */
27153
 
27154
    ALL.names.forEach(function (name) {
27155
        const props = ALL[name];
27156
        Player.prototype[props.getterName] = function () {
27157
            if (this.tech_) {
27158
                return this.tech_[props.getterName]();
27159
            }
27160
 
27161
            // if we have not yet loadTech_, we create {video,audio,text}Tracks_
27162
            // these will be passed to the tech during loading
27163
            this[props.privateName] = this[props.privateName] || new props.ListClass();
27164
            return this[props.privateName];
27165
        };
27166
    });
27167
 
27168
    /**
27169
     * Get or set the `Player`'s crossorigin option. For the HTML5 player, this
27170
     * sets the `crossOrigin` property on the `<video>` tag to control the CORS
27171
     * behavior.
27172
     *
27173
     * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
27174
     *
27175
     * @param {string} [value]
27176
     *        The value to set the `Player`'s crossorigin to. If an argument is
27177
     *        given, must be one of `anonymous` or `use-credentials`.
27178
     *
27179
     * @return {string|undefined}
27180
     *         - The current crossorigin value of the `Player` when getting.
27181
     *         - undefined when setting
27182
     */
27183
    Player.prototype.crossorigin = Player.prototype.crossOrigin;
27184
 
27185
    /**
27186
     * Global enumeration of players.
27187
     *
27188
     * The keys are the player IDs and the values are either the {@link Player}
27189
     * instance or `null` for disposed players.
27190
     *
27191
     * @type {Object}
27192
     */
27193
    Player.players = {};
27194
    const navigator = window.navigator;
27195
 
27196
    /*
27197
   * Player instance options, surfaced using options
27198
   * options = Player.prototype.options_
27199
   * Make changes in options, not here.
27200
   *
27201
   * @type {Object}
27202
   * @private
27203
   */
27204
    Player.prototype.options_ = {
27205
        // Default order of fallback technology
27206
        techOrder: Tech.defaultTechOrder_,
27207
        html5: {},
27208
        // enable sourceset by default
27209
        enableSourceset: true,
27210
        // default inactivity timeout
27211
        inactivityTimeout: 2000,
27212
        // default playback rates
27213
        playbackRates: [],
27214
        // Add playback rate selection by adding rates
27215
        // 'playbackRates': [0.5, 1, 1.5, 2],
27216
        liveui: false,
27217
        // Included control sets
27218
        children: ['mediaLoader', 'posterImage', 'titleBar', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
27219
        language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
27220
        // locales and their language translations
27221
        languages: {},
27222
        // Default message to show when a video cannot be played.
27223
        notSupportedMessage: 'No compatible source was found for this media.',
27224
        normalizeAutoplay: false,
27225
        fullscreen: {
27226
            options: {
27227
                navigationUI: 'hide'
27228
            }
27229
        },
27230
        breakpoints: {},
27231
        responsive: false,
27232
        audioOnlyMode: false,
27233
        audioPosterMode: false,
27234
        // Default smooth seeking to false
27235
        enableSmoothSeeking: false
27236
    };
27237
    TECH_EVENTS_RETRIGGER.forEach(function (event) {
27238
        Player.prototype[`handleTech${toTitleCase$1(event)}_`] = function () {
27239
            return this.trigger(event);
27240
        };
27241
    });
27242
 
27243
    /**
27244
     * Fired when the player has initial duration and dimension information
27245
     *
27246
     * @event Player#loadedmetadata
27247
     * @type {Event}
27248
     */
27249
 
27250
    /**
27251
     * Fired when the player has downloaded data at the current playback position
27252
     *
27253
     * @event Player#loadeddata
27254
     * @type {Event}
27255
     */
27256
 
27257
    /**
27258
     * Fired when the current playback position has changed *
27259
     * During playback this is fired every 15-250 milliseconds, depending on the
27260
     * playback technology in use.
27261
     *
27262
     * @event Player#timeupdate
27263
     * @type {Event}
27264
     */
27265
 
27266
    /**
27267
     * Fired when the volume changes
27268
     *
27269
     * @event Player#volumechange
27270
     * @type {Event}
27271
     */
27272
 
27273
    /**
27274
     * Reports whether or not a player has a plugin available.
27275
     *
27276
     * This does not report whether or not the plugin has ever been initialized
27277
     * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
27278
     *
27279
     * @method Player#hasPlugin
27280
     * @param  {string}  name
27281
     *         The name of a plugin.
27282
     *
27283
     * @return {boolean}
27284
     *         Whether or not this player has the requested plugin available.
27285
     */
27286
 
27287
    /**
27288
     * Reports whether or not a player is using a plugin by name.
27289
     *
27290
     * For basic plugins, this only reports whether the plugin has _ever_ been
27291
     * initialized on this player.
27292
     *
27293
     * @method Player#usingPlugin
27294
     * @param  {string} name
27295
     *         The name of a plugin.
27296
     *
27297
     * @return {boolean}
27298
     *         Whether or not this player is using the requested plugin.
27299
     */
27300
 
27301
    Component$1.registerComponent('Player', Player);
27302
 
27303
    /**
27304
     * @file plugin.js
27305
     */
27306
 
27307
    /**
27308
     * The base plugin name.
27309
     *
27310
     * @private
27311
     * @constant
27312
     * @type {string}
27313
     */
27314
    const BASE_PLUGIN_NAME = 'plugin';
27315
 
27316
    /**
27317
     * The key on which a player's active plugins cache is stored.
27318
     *
27319
     * @private
27320
     * @constant
27321
     * @type     {string}
27322
     */
27323
    const PLUGIN_CACHE_KEY = 'activePlugins_';
27324
 
27325
    /**
27326
     * Stores registered plugins in a private space.
27327
     *
27328
     * @private
27329
     * @type    {Object}
27330
     */
27331
    const pluginStorage = {};
27332
 
27333
    /**
27334
     * Reports whether or not a plugin has been registered.
27335
     *
27336
     * @private
27337
     * @param   {string} name
27338
     *          The name of a plugin.
27339
     *
27340
     * @return {boolean}
27341
     *          Whether or not the plugin has been registered.
27342
     */
27343
    const pluginExists = name => pluginStorage.hasOwnProperty(name);
27344
 
27345
    /**
27346
     * Get a single registered plugin by name.
27347
     *
27348
     * @private
27349
     * @param   {string} name
27350
     *          The name of a plugin.
27351
     *
27352
     * @return {typeof Plugin|Function|undefined}
27353
     *          The plugin (or undefined).
27354
     */
27355
    const getPlugin = name => pluginExists(name) ? pluginStorage[name] : undefined;
27356
 
27357
    /**
27358
     * Marks a plugin as "active" on a player.
27359
     *
27360
     * Also, ensures that the player has an object for tracking active plugins.
27361
     *
27362
     * @private
27363
     * @param   {Player} player
27364
     *          A Video.js player instance.
27365
     *
27366
     * @param   {string} name
27367
     *          The name of a plugin.
27368
     */
27369
    const markPluginAsActive = (player, name) => {
27370
        player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
27371
        player[PLUGIN_CACHE_KEY][name] = true;
27372
    };
27373
 
27374
    /**
27375
     * Triggers a pair of plugin setup events.
27376
     *
27377
     * @private
27378
     * @param  {Player} player
27379
     *         A Video.js player instance.
27380
     *
27381
     * @param  {PluginEventHash} hash
27382
     *         A plugin event hash.
27383
     *
27384
     * @param  {boolean} [before]
27385
     *         If true, prefixes the event name with "before". In other words,
27386
     *         use this to trigger "beforepluginsetup" instead of "pluginsetup".
27387
     */
27388
    const triggerSetupEvent = (player, hash, before) => {
27389
        const eventName = (before ? 'before' : '') + 'pluginsetup';
27390
        player.trigger(eventName, hash);
27391
        player.trigger(eventName + ':' + hash.name, hash);
27392
    };
27393
 
27394
    /**
27395
     * Takes a basic plugin function and returns a wrapper function which marks
27396
     * on the player that the plugin has been activated.
27397
     *
27398
     * @private
27399
     * @param   {string} name
27400
     *          The name of the plugin.
27401
     *
27402
     * @param   {Function} plugin
27403
     *          The basic plugin.
27404
     *
27405
     * @return {Function}
27406
     *          A wrapper function for the given plugin.
27407
     */
27408
    const createBasicPlugin = function (name, plugin) {
27409
        const basicPluginWrapper = function () {
27410
            // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
27411
            // regardless, but we want the hash to be consistent with the hash provided
27412
            // for advanced plugins.
27413
            //
27414
            // The only potentially counter-intuitive thing here is the `instance` in
27415
            // the "pluginsetup" event is the value returned by the `plugin` function.
27416
            triggerSetupEvent(this, {
27417
                name,
27418
                plugin,
27419
                instance: null
27420
            }, true);
27421
            const instance = plugin.apply(this, arguments);
27422
            markPluginAsActive(this, name);
27423
            triggerSetupEvent(this, {
27424
                name,
27425
                plugin,
27426
                instance
27427
            });
27428
            return instance;
27429
        };
27430
        Object.keys(plugin).forEach(function (prop) {
27431
            basicPluginWrapper[prop] = plugin[prop];
27432
        });
27433
        return basicPluginWrapper;
27434
    };
27435
 
27436
    /**
27437
     * Takes a plugin sub-class and returns a factory function for generating
27438
     * instances of it.
27439
     *
27440
     * This factory function will replace itself with an instance of the requested
27441
     * sub-class of Plugin.
27442
     *
27443
     * @private
27444
     * @param   {string} name
27445
     *          The name of the plugin.
27446
     *
27447
     * @param   {Plugin} PluginSubClass
27448
     *          The advanced plugin.
27449
     *
27450
     * @return {Function}
27451
     */
27452
    const createPluginFactory = (name, PluginSubClass) => {
27453
        // Add a `name` property to the plugin prototype so that each plugin can
27454
        // refer to itself by name.
27455
        PluginSubClass.prototype.name = name;
27456
        return function (...args) {
27457
            triggerSetupEvent(this, {
27458
                name,
27459
                plugin: PluginSubClass,
27460
                instance: null
27461
            }, true);
27462
            const instance = new PluginSubClass(...[this, ...args]);
27463
 
27464
            // The plugin is replaced by a function that returns the current instance.
27465
            this[name] = () => instance;
27466
            triggerSetupEvent(this, instance.getEventHash());
27467
            return instance;
27468
        };
27469
    };
27470
 
27471
    /**
27472
     * Parent class for all advanced plugins.
27473
     *
27474
     * @mixes   module:evented~EventedMixin
27475
     * @mixes   module:stateful~StatefulMixin
27476
     * @fires   Player#beforepluginsetup
27477
     * @fires   Player#beforepluginsetup:$name
27478
     * @fires   Player#pluginsetup
27479
     * @fires   Player#pluginsetup:$name
27480
     * @listens Player#dispose
27481
     * @throws  {Error}
27482
     *          If attempting to instantiate the base {@link Plugin} class
27483
     *          directly instead of via a sub-class.
27484
     */
27485
    class Plugin {
27486
        /**
27487
         * Creates an instance of this class.
27488
         *
27489
         * Sub-classes should call `super` to ensure plugins are properly initialized.
27490
         *
27491
         * @param {Player} player
27492
         *        A Video.js player instance.
27493
         */
27494
        constructor(player) {
27495
            if (this.constructor === Plugin) {
27496
                throw new Error('Plugin must be sub-classed; not directly instantiated.');
27497
            }
27498
            this.player = player;
27499
            if (!this.log) {
27500
                this.log = this.player.log.createLogger(this.name);
27501
            }
27502
 
27503
            // Make this object evented, but remove the added `trigger` method so we
27504
            // use the prototype version instead.
27505
            evented(this);
27506
            delete this.trigger;
27507
            stateful(this, this.constructor.defaultState);
27508
            markPluginAsActive(player, this.name);
27509
 
27510
            // Auto-bind the dispose method so we can use it as a listener and unbind
27511
            // it later easily.
27512
            this.dispose = this.dispose.bind(this);
27513
 
27514
            // If the player is disposed, dispose the plugin.
27515
            player.on('dispose', this.dispose);
27516
        }
27517
 
27518
        /**
27519
         * Get the version of the plugin that was set on <pluginName>.VERSION
27520
         */
27521
        version() {
27522
            return this.constructor.VERSION;
27523
        }
27524
 
27525
        /**
27526
         * Each event triggered by plugins includes a hash of additional data with
27527
         * conventional properties.
27528
         *
27529
         * This returns that object or mutates an existing hash.
27530
         *
27531
         * @param   {Object} [hash={}]
27532
         *          An object to be used as event an event hash.
27533
         *
27534
         * @return {PluginEventHash}
27535
         *          An event hash object with provided properties mixed-in.
27536
         */
27537
        getEventHash(hash = {}) {
27538
            hash.name = this.name;
27539
            hash.plugin = this.constructor;
27540
            hash.instance = this;
27541
            return hash;
27542
        }
27543
 
27544
        /**
27545
         * Triggers an event on the plugin object and overrides
27546
         * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
27547
         *
27548
         * @param   {string|Object} event
27549
         *          An event type or an object with a type property.
27550
         *
27551
         * @param   {Object} [hash={}]
27552
         *          Additional data hash to merge with a
27553
         *          {@link PluginEventHash|PluginEventHash}.
27554
         *
27555
         * @return {boolean}
27556
         *          Whether or not default was prevented.
27557
         */
27558
        trigger(event, hash = {}) {
27559
            return trigger(this.eventBusEl_, event, this.getEventHash(hash));
27560
        }
27561
 
27562
        /**
27563
         * Handles "statechanged" events on the plugin. No-op by default, override by
27564
         * subclassing.
27565
         *
27566
         * @abstract
27567
         * @param    {Event} e
27568
         *           An event object provided by a "statechanged" event.
27569
         *
27570
         * @param    {Object} e.changes
27571
         *           An object describing changes that occurred with the "statechanged"
27572
         *           event.
27573
         */
27574
        handleStateChanged(e) {}
27575
 
27576
        /**
27577
         * Disposes a plugin.
27578
         *
27579
         * Subclasses can override this if they want, but for the sake of safety,
27580
         * it's probably best to subscribe the "dispose" event.
27581
         *
27582
         * @fires Plugin#dispose
27583
         */
27584
        dispose() {
27585
            const {
27586
                name,
27587
                player
27588
            } = this;
27589
 
27590
            /**
27591
             * Signals that a advanced plugin is about to be disposed.
27592
             *
27593
             * @event Plugin#dispose
27594
             * @type  {Event}
27595
             */
27596
            this.trigger('dispose');
27597
            this.off();
27598
            player.off('dispose', this.dispose);
27599
 
27600
            // Eliminate any possible sources of leaking memory by clearing up
27601
            // references between the player and the plugin instance and nulling out
27602
            // the plugin's state and replacing methods with a function that throws.
27603
            player[PLUGIN_CACHE_KEY][name] = false;
27604
            this.player = this.state = null;
27605
 
27606
            // Finally, replace the plugin name on the player with a new factory
27607
            // function, so that the plugin is ready to be set up again.
27608
            player[name] = createPluginFactory(name, pluginStorage[name]);
27609
        }
27610
 
27611
        /**
27612
         * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
27613
         *
27614
         * @param   {string|Function} plugin
27615
         *          If a string, matches the name of a plugin. If a function, will be
27616
         *          tested directly.
27617
         *
27618
         * @return {boolean}
27619
         *          Whether or not a plugin is a basic plugin.
27620
         */
27621
        static isBasic(plugin) {
27622
            const p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
27623
            return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
27624
        }
27625
 
27626
        /**
27627
         * Register a Video.js plugin.
27628
         *
27629
         * @param   {string} name
27630
         *          The name of the plugin to be registered. Must be a string and
27631
         *          must not match an existing plugin or a method on the `Player`
27632
         *          prototype.
27633
         *
27634
         * @param   {typeof Plugin|Function} plugin
27635
         *          A sub-class of `Plugin` or a function for basic plugins.
27636
         *
27637
         * @return {typeof Plugin|Function}
27638
         *          For advanced plugins, a factory function for that plugin. For
27639
         *          basic plugins, a wrapper function that initializes the plugin.
27640
         */
27641
        static registerPlugin(name, plugin) {
27642
            if (typeof name !== 'string') {
27643
                throw new Error(`Illegal plugin name, "${name}", must be a string, was ${typeof name}.`);
27644
            }
27645
            if (pluginExists(name)) {
27646
                log$1.warn(`A plugin named "${name}" already exists. You may want to avoid re-registering plugins!`);
27647
            } else if (Player.prototype.hasOwnProperty(name)) {
27648
                throw new Error(`Illegal plugin name, "${name}", cannot share a name with an existing player method!`);
27649
            }
27650
            if (typeof plugin !== 'function') {
27651
                throw new Error(`Illegal plugin for "${name}", must be a function, was ${typeof plugin}.`);
27652
            }
27653
            pluginStorage[name] = plugin;
27654
 
27655
            // Add a player prototype method for all sub-classed plugins (but not for
27656
            // the base Plugin class).
27657
            if (name !== BASE_PLUGIN_NAME) {
27658
                if (Plugin.isBasic(plugin)) {
27659
                    Player.prototype[name] = createBasicPlugin(name, plugin);
27660
                } else {
27661
                    Player.prototype[name] = createPluginFactory(name, plugin);
27662
                }
27663
            }
27664
            return plugin;
27665
        }
27666
 
27667
        /**
27668
         * De-register a Video.js plugin.
27669
         *
27670
         * @param  {string} name
27671
         *         The name of the plugin to be de-registered. Must be a string that
27672
         *         matches an existing plugin.
27673
         *
27674
         * @throws {Error}
27675
         *         If an attempt is made to de-register the base plugin.
27676
         */
27677
        static deregisterPlugin(name) {
27678
            if (name === BASE_PLUGIN_NAME) {
27679
                throw new Error('Cannot de-register base plugin.');
27680
            }
27681
            if (pluginExists(name)) {
27682
                delete pluginStorage[name];
27683
                delete Player.prototype[name];
27684
            }
27685
        }
27686
 
27687
        /**
27688
         * Gets an object containing multiple Video.js plugins.
27689
         *
27690
         * @param   {Array} [names]
27691
         *          If provided, should be an array of plugin names. Defaults to _all_
27692
         *          plugin names.
27693
         *
27694
         * @return {Object|undefined}
27695
         *          An object containing plugin(s) associated with their name(s) or
27696
         *          `undefined` if no matching plugins exist).
27697
         */
27698
        static getPlugins(names = Object.keys(pluginStorage)) {
27699
            let result;
27700
            names.forEach(name => {
27701
                const plugin = getPlugin(name);
27702
                if (plugin) {
27703
                    result = result || {};
27704
                    result[name] = plugin;
27705
                }
27706
            });
27707
            return result;
27708
        }
27709
 
27710
        /**
27711
         * Gets a plugin's version, if available
27712
         *
27713
         * @param   {string} name
27714
         *          The name of a plugin.
27715
         *
27716
         * @return {string}
27717
         *          The plugin's version or an empty string.
27718
         */
27719
        static getPluginVersion(name) {
27720
            const plugin = getPlugin(name);
27721
            return plugin && plugin.VERSION || '';
27722
        }
27723
    }
27724
 
27725
    /**
27726
     * Gets a plugin by name if it exists.
27727
     *
27728
     * @static
27729
     * @method   getPlugin
27730
     * @memberOf Plugin
27731
     * @param    {string} name
27732
     *           The name of a plugin.
27733
     *
27734
     * @returns  {typeof Plugin|Function|undefined}
27735
     *           The plugin (or `undefined`).
27736
     */
27737
    Plugin.getPlugin = getPlugin;
27738
 
27739
    /**
27740
     * The name of the base plugin class as it is registered.
27741
     *
27742
     * @type {string}
27743
     */
27744
    Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
27745
    Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
27746
 
27747
    /**
27748
     * Documented in player.js
27749
     *
27750
     * @ignore
27751
     */
27752
    Player.prototype.usingPlugin = function (name) {
27753
        return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
27754
    };
27755
 
27756
    /**
27757
     * Documented in player.js
27758
     *
27759
     * @ignore
27760
     */
27761
    Player.prototype.hasPlugin = function (name) {
27762
        return !!pluginExists(name);
27763
    };
27764
 
27765
    /**
27766
     * Signals that a plugin is about to be set up on a player.
27767
     *
27768
     * @event    Player#beforepluginsetup
27769
     * @type     {PluginEventHash}
27770
     */
27771
 
27772
    /**
27773
     * Signals that a plugin is about to be set up on a player - by name. The name
27774
     * is the name of the plugin.
27775
     *
27776
     * @event    Player#beforepluginsetup:$name
27777
     * @type     {PluginEventHash}
27778
     */
27779
 
27780
    /**
27781
     * Signals that a plugin has just been set up on a player.
27782
     *
27783
     * @event    Player#pluginsetup
27784
     * @type     {PluginEventHash}
27785
     */
27786
 
27787
    /**
27788
     * Signals that a plugin has just been set up on a player - by name. The name
27789
     * is the name of the plugin.
27790
     *
27791
     * @event    Player#pluginsetup:$name
27792
     * @type     {PluginEventHash}
27793
     */
27794
 
27795
    /**
27796
     * @typedef  {Object} PluginEventHash
27797
     *
27798
     * @property {string} instance
27799
     *           For basic plugins, the return value of the plugin function. For
27800
     *           advanced plugins, the plugin instance on which the event is fired.
27801
     *
27802
     * @property {string} name
27803
     *           The name of the plugin.
27804
     *
27805
     * @property {string} plugin
27806
     *           For basic plugins, the plugin function. For advanced plugins, the
27807
     *           plugin class/constructor.
27808
     */
27809
 
27810
    /**
27811
     * @file deprecate.js
27812
     * @module deprecate
27813
     */
27814
 
27815
    /**
27816
     * Decorate a function with a deprecation message the first time it is called.
27817
     *
27818
     * @param  {string}   message
27819
     *         A deprecation message to log the first time the returned function
27820
     *         is called.
27821
     *
27822
     * @param  {Function} fn
27823
     *         The function to be deprecated.
27824
     *
27825
     * @return {Function}
27826
     *         A wrapper function that will log a deprecation warning the first
27827
     *         time it is called. The return value will be the return value of
27828
     *         the wrapped function.
27829
     */
27830
    function deprecate(message, fn) {
27831
        let warned = false;
27832
        return function (...args) {
27833
            if (!warned) {
27834
                log$1.warn(message);
27835
            }
27836
            warned = true;
27837
            return fn.apply(this, args);
27838
        };
27839
    }
27840
 
27841
    /**
27842
     * Internal function used to mark a function as deprecated in the next major
27843
     * version with consistent messaging.
27844
     *
27845
     * @param  {number}   major   The major version where it will be removed
27846
     * @param  {string}   oldName The old function name
27847
     * @param  {string}   newName The new function name
27848
     * @param  {Function} fn      The function to deprecate
27849
     * @return {Function}         The decorated function
27850
     */
27851
    function deprecateForMajor(major, oldName, newName, fn) {
27852
        return deprecate(`${oldName} is deprecated and will be removed in ${major}.0; please use ${newName} instead.`, fn);
27853
    }
27854
 
27855
    /**
27856
     * @file video.js
27857
     * @module videojs
27858
     */
27859
 
27860
    /**
27861
     * Normalize an `id` value by trimming off a leading `#`
27862
     *
27863
     * @private
27864
     * @param   {string} id
27865
     *          A string, maybe with a leading `#`.
27866
     *
27867
     * @return {string}
27868
     *          The string, without any leading `#`.
27869
     */
27870
    const normalizeId = id => id.indexOf('#') === 0 ? id.slice(1) : id;
27871
 
27872
    /**
27873
     * A callback that is called when a component is ready. Does not have any
27874
     * parameters and any callback value will be ignored. See: {@link Component~ReadyCallback}
27875
     *
27876
     * @callback ReadyCallback
27877
     */
27878
 
27879
    /**
27880
     * The `videojs()` function doubles as the main function for users to create a
27881
     * {@link Player} instance as well as the main library namespace.
27882
     *
27883
     * It can also be used as a getter for a pre-existing {@link Player} instance.
27884
     * However, we _strongly_ recommend using `videojs.getPlayer()` for this
27885
     * purpose because it avoids any potential for unintended initialization.
27886
     *
27887
     * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
27888
     * of our JSDoc template, we cannot properly document this as both a function
27889
     * and a namespace, so its function signature is documented here.
27890
     *
27891
     * #### Arguments
27892
     * ##### id
27893
     * string|Element, **required**
27894
     *
27895
     * Video element or video element ID.
27896
     *
27897
     * ##### options
27898
     * Object, optional
27899
     *
27900
     * Options object for providing settings.
27901
     * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
27902
     *
27903
     * ##### ready
27904
     * {@link Component~ReadyCallback}, optional
27905
     *
27906
     * A function to be called when the {@link Player} and {@link Tech} are ready.
27907
     *
27908
     * #### Return Value
27909
     *
27910
     * The `videojs()` function returns a {@link Player} instance.
27911
     *
27912
     * @namespace
27913
     *
27914
     * @borrows AudioTrack as AudioTrack
27915
     * @borrows Component.getComponent as getComponent
27916
     * @borrows module:events.on as on
27917
     * @borrows module:events.one as one
27918
     * @borrows module:events.off as off
27919
     * @borrows module:events.trigger as trigger
27920
     * @borrows EventTarget as EventTarget
27921
     * @borrows module:middleware.use as use
27922
     * @borrows Player.players as players
27923
     * @borrows Plugin.registerPlugin as registerPlugin
27924
     * @borrows Plugin.deregisterPlugin as deregisterPlugin
27925
     * @borrows Plugin.getPlugins as getPlugins
27926
     * @borrows Plugin.getPlugin as getPlugin
27927
     * @borrows Plugin.getPluginVersion as getPluginVersion
27928
     * @borrows Tech.getTech as getTech
27929
     * @borrows Tech.registerTech as registerTech
27930
     * @borrows TextTrack as TextTrack
27931
     * @borrows VideoTrack as VideoTrack
27932
     *
27933
     * @param  {string|Element} id
27934
     *         Video element or video element ID.
27935
     *
27936
     * @param  {Object} [options]
27937
     *         Options object for providing settings.
27938
     *         See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
27939
     *
27940
     * @param  {ReadyCallback} [ready]
27941
     *         A function to be called when the {@link Player} and {@link Tech} are
27942
     *         ready.
27943
     *
27944
     * @return {Player}
27945
     *         The `videojs()` function returns a {@link Player|Player} instance.
27946
     */
27947
    function videojs(id, options, ready) {
27948
        let player = videojs.getPlayer(id);
27949
        if (player) {
27950
            if (options) {
27951
                log$1.warn(`Player "${id}" is already initialised. Options will not be applied.`);
27952
            }
27953
            if (ready) {
27954
                player.ready(ready);
27955
            }
27956
            return player;
27957
        }
27958
        const el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
27959
        if (!isEl(el)) {
27960
            throw new TypeError('The element or ID supplied is not valid. (videojs)');
27961
        }
27962
 
27963
        // document.body.contains(el) will only check if el is contained within that one document.
27964
        // This causes problems for elements in iframes.
27965
        // Instead, use the element's ownerDocument instead of the global document.
27966
        // This will make sure that the element is indeed in the dom of that document.
27967
        // Additionally, check that the document in question has a default view.
27968
        // If the document is no longer attached to the dom, the defaultView of the document will be null.
27969
        // If element is inside Shadow DOM (e.g. is part of a Custom element), ownerDocument.body
27970
        // always returns false. Instead, use the Shadow DOM root.
27971
        const inShadowDom = 'getRootNode' in el ? el.getRootNode() instanceof window.ShadowRoot : false;
27972
        const rootNode = inShadowDom ? el.getRootNode() : el.ownerDocument.body;
27973
        if (!el.ownerDocument.defaultView || !rootNode.contains(el)) {
27974
            log$1.warn('The element supplied is not included in the DOM');
27975
        }
27976
        options = options || {};
27977
 
27978
        // Store a copy of the el before modification, if it is to be restored in destroy()
27979
        // If div ingest, store the parent div
27980
        if (options.restoreEl === true) {
27981
            options.restoreEl = (el.parentNode && el.parentNode.hasAttribute('data-vjs-player') ? el.parentNode : el).cloneNode(true);
27982
        }
27983
        hooks('beforesetup').forEach(hookFunction => {
27984
            const opts = hookFunction(el, merge$2(options));
27985
            if (!isObject$1(opts) || Array.isArray(opts)) {
27986
                log$1.error('please return an object in beforesetup hooks');
27987
                return;
27988
            }
27989
            options = merge$2(options, opts);
27990
        });
27991
 
27992
        // We get the current "Player" component here in case an integration has
27993
        // replaced it with a custom player.
27994
        const PlayerComponent = Component$1.getComponent('Player');
27995
        player = new PlayerComponent(el, options, ready);
27996
        hooks('setup').forEach(hookFunction => hookFunction(player));
27997
        return player;
27998
    }
27999
    videojs.hooks_ = hooks_;
28000
    videojs.hooks = hooks;
28001
    videojs.hook = hook;
28002
    videojs.hookOnce = hookOnce;
28003
    videojs.removeHook = removeHook;
28004
 
28005
    // Add default styles
28006
    if (window.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
28007
        let style = $('.vjs-styles-defaults');
28008
        if (!style) {
28009
            style = createStyleElement('vjs-styles-defaults');
28010
            const head = $('head');
28011
            if (head) {
28012
                head.insertBefore(style, head.firstChild);
28013
            }
28014
            setTextContent(style, `
28015
      .video-js {
28016
        width: 300px;
28017
        height: 150px;
28018
      }
28019
 
28020
      .vjs-fluid:not(.vjs-audio-only-mode) {
28021
        padding-top: 56.25%
28022
      }
28023
    `);
28024
        }
28025
    }
28026
 
28027
    // Run Auto-load players
28028
    // You have to wait at least once in case this script is loaded after your
28029
    // video in the DOM (weird behavior only with minified version)
28030
    autoSetupTimeout(1, videojs);
28031
 
28032
    /**
28033
     * Current Video.js version. Follows [semantic versioning](https://semver.org/).
28034
     *
28035
     * @type {string}
28036
     */
28037
    videojs.VERSION = version$5;
28038
 
28039
    /**
28040
     * The global options object. These are the settings that take effect
28041
     * if no overrides are specified when the player is created.
28042
     *
28043
     * @type {Object}
28044
     */
28045
    videojs.options = Player.prototype.options_;
28046
 
28047
    /**
28048
     * Get an object with the currently created players, keyed by player ID
28049
     *
28050
     * @return {Object}
28051
     *         The created players
28052
     */
28053
    videojs.getPlayers = () => Player.players;
28054
 
28055
    /**
28056
     * Get a single player based on an ID or DOM element.
28057
     *
28058
     * This is useful if you want to check if an element or ID has an associated
28059
     * Video.js player, but not create one if it doesn't.
28060
     *
28061
     * @param   {string|Element} id
28062
     *          An HTML element - `<video>`, `<audio>`, or `<video-js>` -
28063
     *          or a string matching the `id` of such an element.
28064
     *
28065
     * @return {Player|undefined}
28066
     *          A player instance or `undefined` if there is no player instance
28067
     *          matching the argument.
28068
     */
28069
    videojs.getPlayer = id => {
28070
        const players = Player.players;
28071
        let tag;
28072
        if (typeof id === 'string') {
28073
            const nId = normalizeId(id);
28074
            const player = players[nId];
28075
            if (player) {
28076
                return player;
28077
            }
28078
            tag = $('#' + nId);
28079
        } else {
28080
            tag = id;
28081
        }
28082
        if (isEl(tag)) {
28083
            const {
28084
                player,
28085
                playerId
28086
            } = tag;
28087
 
28088
            // Element may have a `player` property referring to an already created
28089
            // player instance. If so, return that.
28090
            if (player || players[playerId]) {
28091
                return player || players[playerId];
28092
            }
28093
        }
28094
    };
28095
 
28096
    /**
28097
     * Returns an array of all current players.
28098
     *
28099
     * @return {Array}
28100
     *         An array of all players. The array will be in the order that
28101
     *         `Object.keys` provides, which could potentially vary between
28102
     *         JavaScript engines.
28103
     *
28104
     */
28105
    videojs.getAllPlayers = () =>
28106
        // Disposed players leave a key with a `null` value, so we need to make sure
28107
        // we filter those out.
28108
        Object.keys(Player.players).map(k => Player.players[k]).filter(Boolean);
28109
    videojs.players = Player.players;
28110
    videojs.getComponent = Component$1.getComponent;
28111
 
28112
    /**
28113
     * Register a component so it can referred to by name. Used when adding to other
28114
     * components, either through addChild `component.addChild('myComponent')` or through
28115
     * default children options  `{ children: ['myComponent'] }`.
28116
     *
28117
     * > NOTE: You could also just initialize the component before adding.
28118
     * `component.addChild(new MyComponent());`
28119
     *
28120
     * @param {string} name
28121
     *        The class name of the component
28122
     *
28123
     * @param {typeof Component} comp
28124
     *        The component class
28125
     *
28126
     * @return {typeof Component}
28127
     *         The newly registered component
28128
     */
28129
    videojs.registerComponent = (name, comp) => {
28130
        if (Tech.isTech(comp)) {
28131
            log$1.warn(`The ${name} tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)`);
28132
        }
28133
        return Component$1.registerComponent.call(Component$1, name, comp);
28134
    };
28135
    videojs.getTech = Tech.getTech;
28136
    videojs.registerTech = Tech.registerTech;
28137
    videojs.use = use;
28138
 
28139
    /**
28140
     * An object that can be returned by a middleware to signify
28141
     * that the middleware is being terminated.
28142
     *
28143
     * @type {object}
28144
     * @property {object} middleware.TERMINATOR
28145
     */
28146
    Object.defineProperty(videojs, 'middleware', {
28147
        value: {},
28148
        writeable: false,
28149
        enumerable: true
28150
    });
28151
    Object.defineProperty(videojs.middleware, 'TERMINATOR', {
28152
        value: TERMINATOR,
28153
        writeable: false,
28154
        enumerable: true
28155
    });
28156
 
28157
    /**
28158
     * A reference to the {@link module:browser|browser utility module} as an object.
28159
     *
28160
     * @type {Object}
28161
     * @see  {@link module:browser|browser}
28162
     */
28163
    videojs.browser = browser;
28164
 
28165
    /**
28166
     * A reference to the {@link module:obj|obj utility module} as an object.
28167
     *
28168
     * @type {Object}
28169
     * @see  {@link module:obj|obj}
28170
     */
28171
    videojs.obj = Obj;
28172
 
28173
    /**
28174
     * Deprecated reference to the {@link module:obj.merge|merge function}
28175
     *
28176
     * @type {Function}
28177
     * @see {@link module:obj.merge|merge}
28178
     * @deprecated Deprecated and will be removed in 9.0. Please use videojs.obj.merge instead.
28179
     */
28180
    videojs.mergeOptions = deprecateForMajor(9, 'videojs.mergeOptions', 'videojs.obj.merge', merge$2);
28181
 
28182
    /**
28183
     * Deprecated reference to the {@link module:obj.defineLazyProperty|defineLazyProperty function}
28184
     *
28185
     * @type {Function}
28186
     * @see {@link module:obj.defineLazyProperty|defineLazyProperty}
28187
     * @deprecated Deprecated and will be removed in 9.0. Please use videojs.obj.defineLazyProperty instead.
28188
     */
28189
    videojs.defineLazyProperty = deprecateForMajor(9, 'videojs.defineLazyProperty', 'videojs.obj.defineLazyProperty', defineLazyProperty);
28190
 
28191
    /**
28192
     * Deprecated reference to the {@link module:fn.bind_|fn.bind_ function}
28193
     *
28194
     * @type {Function}
28195
     * @see {@link module:fn.bind_|fn.bind_}
28196
     * @deprecated Deprecated and will be removed in 9.0. Please use native Function.prototype.bind instead.
28197
     */
28198
    videojs.bind = deprecateForMajor(9, 'videojs.bind', 'native Function.prototype.bind', bind_);
28199
    videojs.registerPlugin = Plugin.registerPlugin;
28200
    videojs.deregisterPlugin = Plugin.deregisterPlugin;
28201
 
28202
    /**
28203
     * Deprecated method to register a plugin with Video.js
28204
     *
28205
     * @deprecated Deprecated and will be removed in 9.0. Use videojs.registerPlugin() instead.
28206
     *
28207
     * @param {string} name
28208
     *        The plugin name
28209
     *
28210
     * @param {typeof Plugin|Function} plugin
28211
     *         The plugin sub-class or function
28212
     *
28213
     * @return {typeof Plugin|Function}
28214
     */
28215
    videojs.plugin = (name, plugin) => {
28216
        log$1.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
28217
        return Plugin.registerPlugin(name, plugin);
28218
    };
28219
    videojs.getPlugins = Plugin.getPlugins;
28220
    videojs.getPlugin = Plugin.getPlugin;
28221
    videojs.getPluginVersion = Plugin.getPluginVersion;
28222
 
28223
    /**
28224
     * Adding languages so that they're available to all players.
28225
     * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
28226
     *
28227
     * @param {string} code
28228
     *        The language code or dictionary property
28229
     *
28230
     * @param {Object} data
28231
     *        The data values to be translated
28232
     *
28233
     * @return {Object}
28234
     *         The resulting language dictionary object
28235
     */
28236
    videojs.addLanguage = function (code, data) {
28237
        code = ('' + code).toLowerCase();
28238
        videojs.options.languages = merge$2(videojs.options.languages, {
28239
            [code]: data
28240
        });
28241
        return videojs.options.languages[code];
28242
    };
28243
 
28244
    /**
28245
     * A reference to the {@link module:log|log utility module} as an object.
28246
     *
28247
     * @type {Function}
28248
     * @see  {@link module:log|log}
28249
     */
28250
    videojs.log = log$1;
28251
    videojs.createLogger = createLogger;
28252
 
28253
    /**
28254
     * A reference to the {@link module:time|time utility module} as an object.
28255
     *
28256
     * @type {Object}
28257
     * @see {@link module:time|time}
28258
     */
28259
    videojs.time = Time;
28260
 
28261
    /**
28262
     * Deprecated reference to the {@link module:time.createTimeRanges|createTimeRanges function}
28263
     *
28264
     * @type {Function}
28265
     * @see {@link module:time.createTimeRanges|createTimeRanges}
28266
     * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.createTimeRanges instead.
28267
     */
28268
    videojs.createTimeRange = deprecateForMajor(9, 'videojs.createTimeRange', 'videojs.time.createTimeRanges', createTimeRanges$1);
28269
 
28270
    /**
28271
     * Deprecated reference to the {@link module:time.createTimeRanges|createTimeRanges function}
28272
     *
28273
     * @type {Function}
28274
     * @see {@link module:time.createTimeRanges|createTimeRanges}
28275
     * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.createTimeRanges instead.
28276
     */
28277
    videojs.createTimeRanges = deprecateForMajor(9, 'videojs.createTimeRanges', 'videojs.time.createTimeRanges', createTimeRanges$1);
28278
 
28279
    /**
28280
     * Deprecated reference to the {@link module:time.formatTime|formatTime function}
28281
     *
28282
     * @type {Function}
28283
     * @see {@link module:time.formatTime|formatTime}
28284
     * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.format instead.
28285
     */
28286
    videojs.formatTime = deprecateForMajor(9, 'videojs.formatTime', 'videojs.time.formatTime', formatTime);
28287
 
28288
    /**
28289
     * Deprecated reference to the {@link module:time.setFormatTime|setFormatTime function}
28290
     *
28291
     * @type {Function}
28292
     * @see {@link module:time.setFormatTime|setFormatTime}
28293
     * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.setFormat instead.
28294
     */
28295
    videojs.setFormatTime = deprecateForMajor(9, 'videojs.setFormatTime', 'videojs.time.setFormatTime', setFormatTime);
28296
 
28297
    /**
28298
     * Deprecated reference to the {@link module:time.resetFormatTime|resetFormatTime function}
28299
     *
28300
     * @type {Function}
28301
     * @see {@link module:time.resetFormatTime|resetFormatTime}
28302
     * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.resetFormat instead.
28303
     */
28304
    videojs.resetFormatTime = deprecateForMajor(9, 'videojs.resetFormatTime', 'videojs.time.resetFormatTime', resetFormatTime);
28305
 
28306
    /**
28307
     * Deprecated reference to the {@link module:url.parseUrl|Url.parseUrl function}
28308
     *
28309
     * @type {Function}
28310
     * @see {@link module:url.parseUrl|parseUrl}
28311
     * @deprecated Deprecated and will be removed in 9.0. Please use videojs.url.parseUrl instead.
28312
     */
28313
    videojs.parseUrl = deprecateForMajor(9, 'videojs.parseUrl', 'videojs.url.parseUrl', parseUrl);
28314
 
28315
    /**
28316
     * Deprecated reference to the {@link module:url.isCrossOrigin|Url.isCrossOrigin function}
28317
     *
28318
     * @type {Function}
28319
     * @see {@link module:url.isCrossOrigin|isCrossOrigin}
28320
     * @deprecated Deprecated and will be removed in 9.0. Please use videojs.url.isCrossOrigin instead.
28321
     */
28322
    videojs.isCrossOrigin = deprecateForMajor(9, 'videojs.isCrossOrigin', 'videojs.url.isCrossOrigin', isCrossOrigin);
28323
    videojs.EventTarget = EventTarget$2;
28324
    videojs.any = any;
28325
    videojs.on = on;
28326
    videojs.one = one;
28327
    videojs.off = off;
28328
    videojs.trigger = trigger;
28329
 
28330
    /**
28331
     * A cross-browser XMLHttpRequest wrapper.
28332
     *
28333
     * @function
28334
     * @param    {Object} options
28335
     *           Settings for the request.
28336
     *
28337
     * @return   {XMLHttpRequest|XDomainRequest}
28338
     *           The request object.
28339
     *
28340
     * @see      https://github.com/Raynos/xhr
28341
     */
28342
    videojs.xhr = lib;
28343
    videojs.TextTrack = TextTrack;
28344
    videojs.AudioTrack = AudioTrack;
28345
    videojs.VideoTrack = VideoTrack;
28346
    ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(k => {
28347
        videojs[k] = function () {
28348
            log$1.warn(`videojs.${k}() is deprecated; use videojs.dom.${k}() instead`);
28349
            return Dom[k].apply(null, arguments);
28350
        };
28351
    });
28352
    videojs.computedStyle = deprecateForMajor(9, 'videojs.computedStyle', 'videojs.dom.computedStyle', computedStyle);
28353
 
28354
    /**
28355
     * A reference to the {@link module:dom|DOM utility module} as an object.
28356
     *
28357
     * @type {Object}
28358
     * @see {@link module:dom|dom}
28359
     */
28360
    videojs.dom = Dom;
28361
 
28362
    /**
28363
     * A reference to the {@link module:fn|fn utility module} as an object.
28364
     *
28365
     * @type {Object}
28366
     * @see {@link module:fn|fn}
28367
     */
28368
    videojs.fn = Fn;
28369
 
28370
    /**
28371
     * A reference to the {@link module:num|num utility module} as an object.
28372
     *
28373
     * @type {Object}
28374
     * @see {@link module:num|num}
28375
     */
28376
    videojs.num = Num;
28377
 
28378
    /**
28379
     * A reference to the {@link module:str|str utility module} as an object.
28380
     *
28381
     * @type {Object}
28382
     * @see {@link module:str|str}
28383
     */
28384
    videojs.str = Str;
28385
 
28386
    /**
28387
     * A reference to the {@link module:url|URL utility module} as an object.
28388
     *
28389
     * @type {Object}
28390
     * @see {@link module:url|url}
28391
     */
28392
    videojs.url = Url;
28393
 
28394
    createCommonjsModule(function (module, exports) {
28395
        /*! @name videojs-contrib-quality-levels @version 4.0.0 @license Apache-2.0 */
28396
        (function (global, factory) {
28397
            module.exports = factory(videojs) ;
28398
        })(commonjsGlobal, function (videojs) {
28399
 
28400
            function _interopDefaultLegacy(e) {
28401
                return e && typeof e === 'object' && 'default' in e ? e : {
28402
                    'default': e
28403
                };
28404
            }
28405
            var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
28406
 
28407
            /**
28408
             * A single QualityLevel.
28409
             *
28410
             * interface QualityLevel {
28411
             *   readonly attribute DOMString id;
28412
             *            attribute DOMString label;
28413
             *   readonly attribute long width;
28414
             *   readonly attribute long height;
28415
             *   readonly attribute long bitrate;
28416
             *            attribute boolean enabled;
28417
             * };
28418
             *
28419
             * @class QualityLevel
28420
             */
28421
            class QualityLevel {
28422
                /**
28423
                 * Creates a QualityLevel
28424
                 *
28425
                 * @param {Representation|Object} representation The representation of the quality level
28426
                 * @param {string}   representation.id        Unique id of the QualityLevel
28427
                 * @param {number=}  representation.width     Resolution width of the QualityLevel
28428
                 * @param {number=}  representation.height    Resolution height of the QualityLevel
28429
                 * @param {number}   representation.bandwidth Bitrate of the QualityLevel
28430
                 * @param {number=}  representation.frameRate Frame-rate of the QualityLevel
28431
                 * @param {Function} representation.enabled   Callback to enable/disable QualityLevel
28432
                 */
28433
                constructor(representation) {
28434
                    let level = this; // eslint-disable-line
28435
 
28436
                    level.id = representation.id;
28437
                    level.label = level.id;
28438
                    level.width = representation.width;
28439
                    level.height = representation.height;
28440
                    level.bitrate = representation.bandwidth;
28441
                    level.frameRate = representation.frameRate;
28442
                    level.enabled_ = representation.enabled;
28443
                    Object.defineProperty(level, 'enabled', {
28444
                        /**
28445
                         * Get whether the QualityLevel is enabled.
28446
                         *
28447
                         * @return {boolean} True if the QualityLevel is enabled.
28448
                         */
28449
                        get() {
28450
                            return level.enabled_();
28451
                        },
28452
                        /**
28453
                         * Enable or disable the QualityLevel.
28454
                         *
28455
                         * @param {boolean} enable true to enable QualityLevel, false to disable.
28456
                         */
28457
                        set(enable) {
28458
                            level.enabled_(enable);
28459
                        }
28460
                    });
28461
                    return level;
28462
                }
28463
            }
28464
 
28465
            /**
28466
             * A list of QualityLevels.
28467
             *
28468
             * interface QualityLevelList : EventTarget {
28469
             *   getter QualityLevel (unsigned long index);
28470
             *   readonly attribute unsigned long length;
28471
             *   readonly attribute long selectedIndex;
28472
             *
28473
             *   void addQualityLevel(QualityLevel qualityLevel)
28474
             *   void removeQualityLevel(QualityLevel remove)
28475
             *   QualityLevel? getQualityLevelById(DOMString id);
28476
             *
28477
             *   attribute EventHandler onchange;
28478
             *   attribute EventHandler onaddqualitylevel;
28479
             *   attribute EventHandler onremovequalitylevel;
28480
             * };
28481
             *
28482
             * @extends videojs.EventTarget
28483
             * @class QualityLevelList
28484
             */
28485
 
28486
            class QualityLevelList extends videojs__default['default'].EventTarget {
28487
                /**
28488
                 * Creates a QualityLevelList.
28489
                 */
28490
                constructor() {
28491
                    super();
28492
                    let list = this; // eslint-disable-line
28493
 
28494
                    list.levels_ = [];
28495
                    list.selectedIndex_ = -1;
28496
                    /**
28497
                     * Get the index of the currently selected QualityLevel.
28498
                     *
28499
                     * @returns {number} The index of the selected QualityLevel. -1 if none selected.
28500
                     * @readonly
28501
                     */
28502
 
28503
                    Object.defineProperty(list, 'selectedIndex', {
28504
                        get() {
28505
                            return list.selectedIndex_;
28506
                        }
28507
                    });
28508
                    /**
28509
                     * Get the length of the list of QualityLevels.
28510
                     *
28511
                     * @returns {number} The length of the list.
28512
                     * @readonly
28513
                     */
28514
 
28515
                    Object.defineProperty(list, 'length', {
28516
                        get() {
28517
                            return list.levels_.length;
28518
                        }
28519
                    });
28520
                    list[Symbol.iterator] = () => list.levels_.values();
28521
                    return list;
28522
                }
28523
                /**
28524
                 * Adds a quality level to the list.
28525
                 *
28526
                 * @param {Representation|Object} representation The representation of the quality level
28527
                 * @param {string}   representation.id        Unique id of the QualityLevel
28528
                 * @param {number=}  representation.width     Resolution width of the QualityLevel
28529
                 * @param {number=}  representation.height    Resolution height of the QualityLevel
28530
                 * @param {number}   representation.bandwidth Bitrate of the QualityLevel
28531
                 * @param {number=}  representation.frameRate Frame-rate of the QualityLevel
28532
                 * @param {Function} representation.enabled   Callback to enable/disable QualityLevel
28533
                 * @return {QualityLevel} the QualityLevel added to the list
28534
                 * @method addQualityLevel
28535
                 */
28536
 
28537
                addQualityLevel(representation) {
28538
                    let qualityLevel = this.getQualityLevelById(representation.id); // Do not add duplicate quality levels
28539
 
28540
                    if (qualityLevel) {
28541
                        return qualityLevel;
28542
                    }
28543
                    const index = this.levels_.length;
28544
                    qualityLevel = new QualityLevel(representation);
28545
                    if (!('' + index in this)) {
28546
                        Object.defineProperty(this, index, {
28547
                            get() {
28548
                                return this.levels_[index];
28549
                            }
28550
                        });
28551
                    }
28552
                    this.levels_.push(qualityLevel);
28553
                    this.trigger({
28554
                        qualityLevel,
28555
                        type: 'addqualitylevel'
28556
                    });
28557
                    return qualityLevel;
28558
                }
28559
                /**
28560
                 * Removes a quality level from the list.
28561
                 *
28562
                 * @param {QualityLevel} qualityLevel The QualityLevel to remove from the list.
28563
                 * @return {QualityLevel|null} the QualityLevel removed or null if nothing removed
28564
                 * @method removeQualityLevel
28565
                 */
28566
 
28567
                removeQualityLevel(qualityLevel) {
28568
                    let removed = null;
28569
                    for (let i = 0, l = this.length; i < l; i++) {
28570
                        if (this[i] === qualityLevel) {
28571
                            removed = this.levels_.splice(i, 1)[0];
28572
                            if (this.selectedIndex_ === i) {
28573
                                this.selectedIndex_ = -1;
28574
                            } else if (this.selectedIndex_ > i) {
28575
                                this.selectedIndex_--;
28576
                            }
28577
                            break;
28578
                        }
28579
                    }
28580
                    if (removed) {
28581
                        this.trigger({
28582
                            qualityLevel,
28583
                            type: 'removequalitylevel'
28584
                        });
28585
                    }
28586
                    return removed;
28587
                }
28588
                /**
28589
                 * Searches for a QualityLevel with the given id.
28590
                 *
28591
                 * @param {string} id The id of the QualityLevel to find.
28592
                 * @return {QualityLevel|null} The QualityLevel with id, or null if not found.
28593
                 * @method getQualityLevelById
28594
                 */
28595
 
28596
                getQualityLevelById(id) {
28597
                    for (let i = 0, l = this.length; i < l; i++) {
28598
                        const level = this[i];
28599
                        if (level.id === id) {
28600
                            return level;
28601
                        }
28602
                    }
28603
                    return null;
28604
                }
28605
                /**
28606
                 * Resets the list of QualityLevels to empty
28607
                 *
28608
                 * @method dispose
28609
                 */
28610
 
28611
                dispose() {
28612
                    this.selectedIndex_ = -1;
28613
                    this.levels_.length = 0;
28614
                }
28615
            }
28616
            /**
28617
             * change - The selected QualityLevel has changed.
28618
             * addqualitylevel - A QualityLevel has been added to the QualityLevelList.
28619
             * removequalitylevel - A QualityLevel has been removed from the QualityLevelList.
28620
             */
28621
 
28622
            QualityLevelList.prototype.allowedEvents_ = {
28623
                change: 'change',
28624
                addqualitylevel: 'addqualitylevel',
28625
                removequalitylevel: 'removequalitylevel'
28626
            }; // emulate attribute EventHandler support to allow for feature detection
28627
 
28628
            for (const event in QualityLevelList.prototype.allowedEvents_) {
28629
                QualityLevelList.prototype['on' + event] = null;
28630
            }
28631
            var version = "4.0.0";
28632
 
28633
            /**
28634
             * Initialization function for the qualityLevels plugin. Sets up the QualityLevelList and
28635
             * event handlers.
28636
             *
28637
             * @param {Player} player Player object.
28638
             * @param {Object} options Plugin options object.
28639
             * @return {QualityLevelList} a list of QualityLevels
28640
             */
28641
 
28642
            const initPlugin = function (player, options) {
28643
                const originalPluginFn = player.qualityLevels;
28644
                const qualityLevelList = new QualityLevelList();
28645
                const disposeHandler = function () {
28646
                    qualityLevelList.dispose();
28647
                    player.qualityLevels = originalPluginFn;
28648
                    player.off('dispose', disposeHandler);
28649
                };
28650
                player.on('dispose', disposeHandler);
28651
                player.qualityLevels = () => qualityLevelList;
28652
                player.qualityLevels.VERSION = version;
28653
                return qualityLevelList;
28654
            };
28655
            /**
28656
             * A video.js plugin.
28657
             *
28658
             * In the plugin function, the value of `this` is a video.js `Player`
28659
             * instance. You cannot rely on the player being in a "ready" state here,
28660
             * depending on how the plugin is invoked. This may or may not be important
28661
             * to you; if not, remove the wait for "ready"!
28662
             *
28663
             * @param {Object} options Plugin options object
28664
             * @return {QualityLevelList} a list of QualityLevels
28665
             */
28666
 
28667
            const qualityLevels = function (options) {
28668
                return initPlugin(this, videojs__default['default'].obj.merge({}, options));
28669
            }; // Register the plugin with video.js.
28670
 
28671
            videojs__default['default'].registerPlugin('qualityLevels', qualityLevels); // Include the version number.
28672
 
28673
            qualityLevels.VERSION = version;
28674
            return qualityLevels;
28675
        });
28676
    });
28677
 
28678
    var urlToolkit = createCommonjsModule(function (module, exports) {
28679
        // see https://tools.ietf.org/html/rfc1808
28680
 
28681
        (function (root) {
28682
            var URL_REGEX = /^(?=((?:[a-zA-Z0-9+\-.]+:)?))\1(?=((?:\/\/[^\/?#]*)?))\2(?=((?:(?:[^?#\/]*\/)*[^;?#\/]*)?))\3((?:;[^?#]*)?)(\?[^#]*)?(#[^]*)?$/;
28683
            var FIRST_SEGMENT_REGEX = /^(?=([^\/?#]*))\1([^]*)$/;
28684
            var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
28685
            var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/)[^\/]*(?=\/)/g;
28686
            var URLToolkit = {
28687
                // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
28688
                // E.g
28689
                // With opts.alwaysNormalize = false (default, spec compliant)
28690
                // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
28691
                // With opts.alwaysNormalize = true (not spec compliant)
28692
                // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
28693
                buildAbsoluteURL: function (baseURL, relativeURL, opts) {
28694
                    opts = opts || {};
28695
                    // remove any remaining space and CRLF
28696
                    baseURL = baseURL.trim();
28697
                    relativeURL = relativeURL.trim();
28698
                    if (!relativeURL) {
28699
                        // 2a) If the embedded URL is entirely empty, it inherits the
28700
                        // entire base URL (i.e., is set equal to the base URL)
28701
                        // and we are done.
28702
                        if (!opts.alwaysNormalize) {
28703
                            return baseURL;
28704
                        }
28705
                        var basePartsForNormalise = URLToolkit.parseURL(baseURL);
28706
                        if (!basePartsForNormalise) {
28707
                            throw new Error('Error trying to parse base URL.');
28708
                        }
28709
                        basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
28710
                        return URLToolkit.buildURLFromParts(basePartsForNormalise);
28711
                    }
28712
                    var relativeParts = URLToolkit.parseURL(relativeURL);
28713
                    if (!relativeParts) {
28714
                        throw new Error('Error trying to parse relative URL.');
28715
                    }
28716
                    if (relativeParts.scheme) {
28717
                        // 2b) If the embedded URL starts with a scheme name, it is
28718
                        // interpreted as an absolute URL and we are done.
28719
                        if (!opts.alwaysNormalize) {
28720
                            return relativeURL;
28721
                        }
28722
                        relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
28723
                        return URLToolkit.buildURLFromParts(relativeParts);
28724
                    }
28725
                    var baseParts = URLToolkit.parseURL(baseURL);
28726
                    if (!baseParts) {
28727
                        throw new Error('Error trying to parse base URL.');
28728
                    }
28729
                    if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
28730
                        // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
28731
                        // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
28732
                        var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
28733
                        baseParts.netLoc = pathParts[1];
28734
                        baseParts.path = pathParts[2];
28735
                    }
28736
                    if (baseParts.netLoc && !baseParts.path) {
28737
                        baseParts.path = '/';
28738
                    }
28739
                    var builtParts = {
28740
                        // 2c) Otherwise, the embedded URL inherits the scheme of
28741
                        // the base URL.
28742
                        scheme: baseParts.scheme,
28743
                        netLoc: relativeParts.netLoc,
28744
                        path: null,
28745
                        params: relativeParts.params,
28746
                        query: relativeParts.query,
28747
                        fragment: relativeParts.fragment
28748
                    };
28749
                    if (!relativeParts.netLoc) {
28750
                        // 3) If the embedded URL's <net_loc> is non-empty, we skip to
28751
                        // Step 7.  Otherwise, the embedded URL inherits the <net_loc>
28752
                        // (if any) of the base URL.
28753
                        builtParts.netLoc = baseParts.netLoc;
28754
                        // 4) If the embedded URL path is preceded by a slash "/", the
28755
                        // path is not relative and we skip to Step 7.
28756
                        if (relativeParts.path[0] !== '/') {
28757
                            if (!relativeParts.path) {
28758
                                // 5) If the embedded URL path is empty (and not preceded by a
28759
                                // slash), then the embedded URL inherits the base URL path
28760
                                builtParts.path = baseParts.path;
28761
                                // 5a) if the embedded URL's <params> is non-empty, we skip to
28762
                                // step 7; otherwise, it inherits the <params> of the base
28763
                                // URL (if any) and
28764
                                if (!relativeParts.params) {
28765
                                    builtParts.params = baseParts.params;
28766
                                    // 5b) if the embedded URL's <query> is non-empty, we skip to
28767
                                    // step 7; otherwise, it inherits the <query> of the base
28768
                                    // URL (if any) and we skip to step 7.
28769
                                    if (!relativeParts.query) {
28770
                                        builtParts.query = baseParts.query;
28771
                                    }
28772
                                }
28773
                            } else {
28774
                                // 6) The last segment of the base URL's path (anything
28775
                                // following the rightmost slash "/", or the entire path if no
28776
                                // slash is present) is removed and the embedded URL's path is
28777
                                // appended in its place.
28778
                                var baseURLPath = baseParts.path;
28779
                                var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
28780
                                builtParts.path = URLToolkit.normalizePath(newPath);
28781
                            }
28782
                        }
28783
                    }
28784
                    if (builtParts.path === null) {
28785
                        builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
28786
                    }
28787
                    return URLToolkit.buildURLFromParts(builtParts);
28788
                },
28789
                parseURL: function (url) {
28790
                    var parts = URL_REGEX.exec(url);
28791
                    if (!parts) {
28792
                        return null;
28793
                    }
28794
                    return {
28795
                        scheme: parts[1] || '',
28796
                        netLoc: parts[2] || '',
28797
                        path: parts[3] || '',
28798
                        params: parts[4] || '',
28799
                        query: parts[5] || '',
28800
                        fragment: parts[6] || ''
28801
                    };
28802
                },
28803
                normalizePath: function (path) {
28804
                    // The following operations are
28805
                    // then applied, in order, to the new path:
28806
                    // 6a) All occurrences of "./", where "." is a complete path
28807
                    // segment, are removed.
28808
                    // 6b) If the path ends with "." as a complete path segment,
28809
                    // that "." is removed.
28810
                    path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, '');
28811
                    // 6c) All occurrences of "<segment>/../", where <segment> is a
28812
                    // complete path segment not equal to "..", are removed.
28813
                    // Removal of these path segments is performed iteratively,
28814
                    // removing the leftmost matching pattern on each iteration,
28815
                    // until no matching pattern remains.
28816
                    // 6d) If the path ends with "<segment>/..", where <segment> is a
28817
                    // complete path segment not equal to "..", that
28818
                    // "<segment>/.." is removed.
28819
                    while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {}
28820
                    return path.split('').reverse().join('');
28821
                },
28822
                buildURLFromParts: function (parts) {
28823
                    return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
28824
                }
28825
            };
28826
            module.exports = URLToolkit;
28827
        })();
28828
    });
28829
 
28830
    var DEFAULT_LOCATION = 'http://example.com';
28831
    var resolveUrl$1 = function resolveUrl(baseUrl, relativeUrl) {
28832
        // return early if we don't need to resolve
28833
        if (/^[a-z]+:/i.test(relativeUrl)) {
28834
            return relativeUrl;
28835
        } // if baseUrl is a data URI, ignore it and resolve everything relative to window.location
28836
 
28837
        if (/^data:/.test(baseUrl)) {
28838
            baseUrl = window.location && window.location.href || '';
28839
        } // IE11 supports URL but not the URL constructor
28840
        // feature detect the behavior we want
28841
 
28842
        var nativeURL = typeof window.URL === 'function';
28843
        var protocolLess = /^\/\//.test(baseUrl); // remove location if window.location isn't available (i.e. we're in node)
28844
        // and if baseUrl isn't an absolute url
28845
 
28846
        var removeLocation = !window.location && !/\/\//i.test(baseUrl); // if the base URL is relative then combine with the current location
28847
 
28848
        if (nativeURL) {
28849
            baseUrl = new window.URL(baseUrl, window.location || DEFAULT_LOCATION);
28850
        } else if (!/\/\//i.test(baseUrl)) {
28851
            baseUrl = urlToolkit.buildAbsoluteURL(window.location && window.location.href || '', baseUrl);
28852
        }
28853
        if (nativeURL) {
28854
            var newUrl = new URL(relativeUrl, baseUrl); // if we're a protocol-less url, remove the protocol
28855
            // and if we're location-less, remove the location
28856
            // otherwise, return the url unmodified
28857
 
28858
            if (removeLocation) {
28859
                return newUrl.href.slice(DEFAULT_LOCATION.length);
28860
            } else if (protocolLess) {
28861
                return newUrl.href.slice(newUrl.protocol.length);
28862
            }
28863
            return newUrl.href;
28864
        }
28865
        return urlToolkit.buildAbsoluteURL(baseUrl, relativeUrl);
28866
    };
28867
 
28868
    /**
28869
     * @file stream.js
28870
     */
28871
 
28872
    /**
28873
     * A lightweight readable stream implemention that handles event dispatching.
28874
     *
28875
     * @class Stream
28876
     */
28877
    var Stream = /*#__PURE__*/function () {
28878
        function Stream() {
28879
            this.listeners = {};
28880
        }
28881
        /**
28882
         * Add a listener for a specified event type.
28883
         *
28884
         * @param {string} type the event name
28885
         * @param {Function} listener the callback to be invoked when an event of
28886
         * the specified type occurs
28887
         */
28888
 
28889
        var _proto = Stream.prototype;
28890
        _proto.on = function on(type, listener) {
28891
            if (!this.listeners[type]) {
28892
                this.listeners[type] = [];
28893
            }
28894
            this.listeners[type].push(listener);
28895
        }
28896
        /**
28897
         * Remove a listener for a specified event type.
28898
         *
28899
         * @param {string} type the event name
28900
         * @param {Function} listener  a function previously registered for this
28901
         * type of event through `on`
28902
         * @return {boolean} if we could turn it off or not
28903
         */;
28904
 
28905
        _proto.off = function off(type, listener) {
28906
            if (!this.listeners[type]) {
28907
                return false;
28908
            }
28909
            var index = this.listeners[type].indexOf(listener); // TODO: which is better?
28910
            // In Video.js we slice listener functions
28911
            // on trigger so that it does not mess up the order
28912
            // while we loop through.
28913
            //
28914
            // Here we slice on off so that the loop in trigger
28915
            // can continue using it's old reference to loop without
28916
            // messing up the order.
28917
 
28918
            this.listeners[type] = this.listeners[type].slice(0);
28919
            this.listeners[type].splice(index, 1);
28920
            return index > -1;
28921
        }
28922
        /**
28923
         * Trigger an event of the specified type on this stream. Any additional
28924
         * arguments to this function are passed as parameters to event listeners.
28925
         *
28926
         * @param {string} type the event name
28927
         */;
28928
 
28929
        _proto.trigger = function trigger(type) {
28930
            var callbacks = this.listeners[type];
28931
            if (!callbacks) {
28932
                return;
28933
            } // Slicing the arguments on every invocation of this method
28934
            // can add a significant amount of overhead. Avoid the
28935
            // intermediate object creation for the common case of a
28936
            // single callback argument
28937
 
28938
            if (arguments.length === 2) {
28939
                var length = callbacks.length;
28940
                for (var i = 0; i < length; ++i) {
28941
                    callbacks[i].call(this, arguments[1]);
28942
                }
28943
            } else {
28944
                var args = Array.prototype.slice.call(arguments, 1);
28945
                var _length = callbacks.length;
28946
                for (var _i = 0; _i < _length; ++_i) {
28947
                    callbacks[_i].apply(this, args);
28948
                }
28949
            }
28950
        }
28951
        /**
28952
         * Destroys the stream and cleans up.
28953
         */;
28954
 
28955
        _proto.dispose = function dispose() {
28956
            this.listeners = {};
28957
        }
28958
        /**
28959
         * Forwards all `data` events on this stream to the destination stream. The
28960
         * destination stream should provide a method `push` to receive the data
28961
         * events as they arrive.
28962
         *
28963
         * @param {Stream} destination the stream that will receive all `data` events
28964
         * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
28965
         */;
28966
 
28967
        _proto.pipe = function pipe(destination) {
28968
            this.on('data', function (data) {
28969
                destination.push(data);
28970
            });
28971
        };
28972
        return Stream;
28973
    }();
28974
 
28975
    var atob$1 = function atob(s) {
28976
        return window.atob ? window.atob(s) : Buffer.from(s, 'base64').toString('binary');
28977
    };
28978
    function decodeB64ToUint8Array$1(b64Text) {
28979
        var decodedString = atob$1(b64Text);
28980
        var array = new Uint8Array(decodedString.length);
28981
        for (var i = 0; i < decodedString.length; i++) {
28982
            array[i] = decodedString.charCodeAt(i);
28983
        }
28984
        return array;
28985
    }
28986
 
28987
    /*! @name m3u8-parser @version 7.1.0 @license Apache-2.0 */
28988
 
28989
    /**
28990
     * @file m3u8/line-stream.js
28991
     */
28992
    /**
28993
     * A stream that buffers string input and generates a `data` event for each
28994
     * line.
28995
     *
28996
     * @class LineStream
28997
     * @extends Stream
28998
     */
28999
 
29000
    class LineStream extends Stream {
29001
        constructor() {
29002
            super();
29003
            this.buffer = '';
29004
        }
29005
        /**
29006
         * Add new data to be parsed.
29007
         *
29008
         * @param {string} data the text to process
29009
         */
29010
 
29011
        push(data) {
29012
            let nextNewline;
29013
            this.buffer += data;
29014
            nextNewline = this.buffer.indexOf('\n');
29015
            for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
29016
                this.trigger('data', this.buffer.substring(0, nextNewline));
29017
                this.buffer = this.buffer.substring(nextNewline + 1);
29018
            }
29019
        }
29020
    }
29021
    const TAB = String.fromCharCode(0x09);
29022
    const parseByterange = function (byterangeString) {
29023
        // optionally match and capture 0+ digits before `@`
29024
        // optionally match and capture 0+ digits after `@`
29025
        const match = /([0-9.]*)?@?([0-9.]*)?/.exec(byterangeString || '');
29026
        const result = {};
29027
        if (match[1]) {
29028
            result.length = parseInt(match[1], 10);
29029
        }
29030
        if (match[2]) {
29031
            result.offset = parseInt(match[2], 10);
29032
        }
29033
        return result;
29034
    };
29035
    /**
29036
     * "forgiving" attribute list psuedo-grammar:
29037
     * attributes -> keyvalue (',' keyvalue)*
29038
     * keyvalue   -> key '=' value
29039
     * key        -> [^=]*
29040
     * value      -> '"' [^"]* '"' | [^,]*
29041
     */
29042
 
29043
    const attributeSeparator = function () {
29044
        const key = '[^=]*';
29045
        const value = '"[^"]*"|[^,]*';
29046
        const keyvalue = '(?:' + key + ')=(?:' + value + ')';
29047
        return new RegExp('(?:^|,)(' + keyvalue + ')');
29048
    };
29049
    /**
29050
     * Parse attributes from a line given the separator
29051
     *
29052
     * @param {string} attributes the attribute line to parse
29053
     */
29054
 
29055
    const parseAttributes$1 = function (attributes) {
29056
        const result = {};
29057
        if (!attributes) {
29058
            return result;
29059
        } // split the string using attributes as the separator
29060
 
29061
        const attrs = attributes.split(attributeSeparator());
29062
        let i = attrs.length;
29063
        let attr;
29064
        while (i--) {
29065
            // filter out unmatched portions of the string
29066
            if (attrs[i] === '') {
29067
                continue;
29068
            } // split the key and value
29069
 
29070
            attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value
29071
 
29072
            attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
29073
            attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
29074
            attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
29075
            result[attr[0]] = attr[1];
29076
        }
29077
        return result;
29078
    };
29079
    /**
29080
     * A line-level M3U8 parser event stream. It expects to receive input one
29081
     * line at a time and performs a context-free parse of its contents. A stream
29082
     * interpretation of a manifest can be useful if the manifest is expected to
29083
     * be too large to fit comfortably into memory or the entirety of the input
29084
     * is not immediately available. Otherwise, it's probably much easier to work
29085
     * with a regular `Parser` object.
29086
     *
29087
     * Produces `data` events with an object that captures the parser's
29088
     * interpretation of the input. That object has a property `tag` that is one
29089
     * of `uri`, `comment`, or `tag`. URIs only have a single additional
29090
     * property, `line`, which captures the entirety of the input without
29091
     * interpretation. Comments similarly have a single additional property
29092
     * `text` which is the input without the leading `#`.
29093
     *
29094
     * Tags always have a property `tagType` which is the lower-cased version of
29095
     * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
29096
     * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
29097
     * tags are given the tag type `unknown` and a single additional property
29098
     * `data` with the remainder of the input.
29099
     *
29100
     * @class ParseStream
29101
     * @extends Stream
29102
     */
29103
 
29104
    class ParseStream extends Stream {
29105
        constructor() {
29106
            super();
29107
            this.customParsers = [];
29108
            this.tagMappers = [];
29109
        }
29110
        /**
29111
         * Parses an additional line of input.
29112
         *
29113
         * @param {string} line a single line of an M3U8 file to parse
29114
         */
29115
 
29116
        push(line) {
29117
            let match;
29118
            let event; // strip whitespace
29119
 
29120
            line = line.trim();
29121
            if (line.length === 0) {
29122
                // ignore empty lines
29123
                return;
29124
            } // URIs
29125
 
29126
            if (line[0] !== '#') {
29127
                this.trigger('data', {
29128
                    type: 'uri',
29129
                    uri: line
29130
                });
29131
                return;
29132
            } // map tags
29133
 
29134
            const newLines = this.tagMappers.reduce((acc, mapper) => {
29135
                const mappedLine = mapper(line); // skip if unchanged
29136
 
29137
                if (mappedLine === line) {
29138
                    return acc;
29139
                }
29140
                return acc.concat([mappedLine]);
29141
            }, [line]);
29142
            newLines.forEach(newLine => {
29143
                for (let i = 0; i < this.customParsers.length; i++) {
29144
                    if (this.customParsers[i].call(this, newLine)) {
29145
                        return;
29146
                    }
29147
                } // Comments
29148
 
29149
                if (newLine.indexOf('#EXT') !== 0) {
29150
                    this.trigger('data', {
29151
                        type: 'comment',
29152
                        text: newLine.slice(1)
29153
                    });
29154
                    return;
29155
                } // strip off any carriage returns here so the regex matching
29156
                // doesn't have to account for them.
29157
 
29158
                newLine = newLine.replace('\r', ''); // Tags
29159
 
29160
                match = /^#EXTM3U/.exec(newLine);
29161
                if (match) {
29162
                    this.trigger('data', {
29163
                        type: 'tag',
29164
                        tagType: 'm3u'
29165
                    });
29166
                    return;
29167
                }
29168
                match = /^#EXTINF:([0-9\.]*)?,?(.*)?$/.exec(newLine);
29169
                if (match) {
29170
                    event = {
29171
                        type: 'tag',
29172
                        tagType: 'inf'
29173
                    };
29174
                    if (match[1]) {
29175
                        event.duration = parseFloat(match[1]);
29176
                    }
29177
                    if (match[2]) {
29178
                        event.title = match[2];
29179
                    }
29180
                    this.trigger('data', event);
29181
                    return;
29182
                }
29183
                match = /^#EXT-X-TARGETDURATION:([0-9.]*)?/.exec(newLine);
29184
                if (match) {
29185
                    event = {
29186
                        type: 'tag',
29187
                        tagType: 'targetduration'
29188
                    };
29189
                    if (match[1]) {
29190
                        event.duration = parseInt(match[1], 10);
29191
                    }
29192
                    this.trigger('data', event);
29193
                    return;
29194
                }
29195
                match = /^#EXT-X-VERSION:([0-9.]*)?/.exec(newLine);
29196
                if (match) {
29197
                    event = {
29198
                        type: 'tag',
29199
                        tagType: 'version'
29200
                    };
29201
                    if (match[1]) {
29202
                        event.version = parseInt(match[1], 10);
29203
                    }
29204
                    this.trigger('data', event);
29205
                    return;
29206
                }
29207
                match = /^#EXT-X-MEDIA-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
29208
                if (match) {
29209
                    event = {
29210
                        type: 'tag',
29211
                        tagType: 'media-sequence'
29212
                    };
29213
                    if (match[1]) {
29214
                        event.number = parseInt(match[1], 10);
29215
                    }
29216
                    this.trigger('data', event);
29217
                    return;
29218
                }
29219
                match = /^#EXT-X-DISCONTINUITY-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
29220
                if (match) {
29221
                    event = {
29222
                        type: 'tag',
29223
                        tagType: 'discontinuity-sequence'
29224
                    };
29225
                    if (match[1]) {
29226
                        event.number = parseInt(match[1], 10);
29227
                    }
29228
                    this.trigger('data', event);
29229
                    return;
29230
                }
29231
                match = /^#EXT-X-PLAYLIST-TYPE:(.*)?$/.exec(newLine);
29232
                if (match) {
29233
                    event = {
29234
                        type: 'tag',
29235
                        tagType: 'playlist-type'
29236
                    };
29237
                    if (match[1]) {
29238
                        event.playlistType = match[1];
29239
                    }
29240
                    this.trigger('data', event);
29241
                    return;
29242
                }
29243
                match = /^#EXT-X-BYTERANGE:(.*)?$/.exec(newLine);
29244
                if (match) {
29245
                    event = _extends$1(parseByterange(match[1]), {
29246
                        type: 'tag',
29247
                        tagType: 'byterange'
29248
                    });
29249
                    this.trigger('data', event);
29250
                    return;
29251
                }
29252
                match = /^#EXT-X-ALLOW-CACHE:(YES|NO)?/.exec(newLine);
29253
                if (match) {
29254
                    event = {
29255
                        type: 'tag',
29256
                        tagType: 'allow-cache'
29257
                    };
29258
                    if (match[1]) {
29259
                        event.allowed = !/NO/.test(match[1]);
29260
                    }
29261
                    this.trigger('data', event);
29262
                    return;
29263
                }
29264
                match = /^#EXT-X-MAP:(.*)$/.exec(newLine);
29265
                if (match) {
29266
                    event = {
29267
                        type: 'tag',
29268
                        tagType: 'map'
29269
                    };
29270
                    if (match[1]) {
29271
                        const attributes = parseAttributes$1(match[1]);
29272
                        if (attributes.URI) {
29273
                            event.uri = attributes.URI;
29274
                        }
29275
                        if (attributes.BYTERANGE) {
29276
                            event.byterange = parseByterange(attributes.BYTERANGE);
29277
                        }
29278
                    }
29279
                    this.trigger('data', event);
29280
                    return;
29281
                }
29282
                match = /^#EXT-X-STREAM-INF:(.*)$/.exec(newLine);
29283
                if (match) {
29284
                    event = {
29285
                        type: 'tag',
29286
                        tagType: 'stream-inf'
29287
                    };
29288
                    if (match[1]) {
29289
                        event.attributes = parseAttributes$1(match[1]);
29290
                        if (event.attributes.RESOLUTION) {
29291
                            const split = event.attributes.RESOLUTION.split('x');
29292
                            const resolution = {};
29293
                            if (split[0]) {
29294
                                resolution.width = parseInt(split[0], 10);
29295
                            }
29296
                            if (split[1]) {
29297
                                resolution.height = parseInt(split[1], 10);
29298
                            }
29299
                            event.attributes.RESOLUTION = resolution;
29300
                        }
29301
                        if (event.attributes.BANDWIDTH) {
29302
                            event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
29303
                        }
29304
                        if (event.attributes['FRAME-RATE']) {
29305
                            event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']);
29306
                        }
29307
                        if (event.attributes['PROGRAM-ID']) {
29308
                            event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
29309
                        }
29310
                    }
29311
                    this.trigger('data', event);
29312
                    return;
29313
                }
29314
                match = /^#EXT-X-MEDIA:(.*)$/.exec(newLine);
29315
                if (match) {
29316
                    event = {
29317
                        type: 'tag',
29318
                        tagType: 'media'
29319
                    };
29320
                    if (match[1]) {
29321
                        event.attributes = parseAttributes$1(match[1]);
29322
                    }
29323
                    this.trigger('data', event);
29324
                    return;
29325
                }
29326
                match = /^#EXT-X-ENDLIST/.exec(newLine);
29327
                if (match) {
29328
                    this.trigger('data', {
29329
                        type: 'tag',
29330
                        tagType: 'endlist'
29331
                    });
29332
                    return;
29333
                }
29334
                match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
29335
                if (match) {
29336
                    this.trigger('data', {
29337
                        type: 'tag',
29338
                        tagType: 'discontinuity'
29339
                    });
29340
                    return;
29341
                }
29342
                match = /^#EXT-X-PROGRAM-DATE-TIME:(.*)$/.exec(newLine);
29343
                if (match) {
29344
                    event = {
29345
                        type: 'tag',
29346
                        tagType: 'program-date-time'
29347
                    };
29348
                    if (match[1]) {
29349
                        event.dateTimeString = match[1];
29350
                        event.dateTimeObject = new Date(match[1]);
29351
                    }
29352
                    this.trigger('data', event);
29353
                    return;
29354
                }
29355
                match = /^#EXT-X-KEY:(.*)$/.exec(newLine);
29356
                if (match) {
29357
                    event = {
29358
                        type: 'tag',
29359
                        tagType: 'key'
29360
                    };
29361
                    if (match[1]) {
29362
                        event.attributes = parseAttributes$1(match[1]); // parse the IV string into a Uint32Array
29363
 
29364
                        if (event.attributes.IV) {
29365
                            if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
29366
                                event.attributes.IV = event.attributes.IV.substring(2);
29367
                            }
29368
                            event.attributes.IV = event.attributes.IV.match(/.{8}/g);
29369
                            event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
29370
                            event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
29371
                            event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
29372
                            event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
29373
                            event.attributes.IV = new Uint32Array(event.attributes.IV);
29374
                        }
29375
                    }
29376
                    this.trigger('data', event);
29377
                    return;
29378
                }
29379
                match = /^#EXT-X-START:(.*)$/.exec(newLine);
29380
                if (match) {
29381
                    event = {
29382
                        type: 'tag',
29383
                        tagType: 'start'
29384
                    };
29385
                    if (match[1]) {
29386
                        event.attributes = parseAttributes$1(match[1]);
29387
                        event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']);
29388
                        event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
29389
                    }
29390
                    this.trigger('data', event);
29391
                    return;
29392
                }
29393
                match = /^#EXT-X-CUE-OUT-CONT:(.*)?$/.exec(newLine);
29394
                if (match) {
29395
                    event = {
29396
                        type: 'tag',
29397
                        tagType: 'cue-out-cont'
29398
                    };
29399
                    if (match[1]) {
29400
                        event.data = match[1];
29401
                    } else {
29402
                        event.data = '';
29403
                    }
29404
                    this.trigger('data', event);
29405
                    return;
29406
                }
29407
                match = /^#EXT-X-CUE-OUT:(.*)?$/.exec(newLine);
29408
                if (match) {
29409
                    event = {
29410
                        type: 'tag',
29411
                        tagType: 'cue-out'
29412
                    };
29413
                    if (match[1]) {
29414
                        event.data = match[1];
29415
                    } else {
29416
                        event.data = '';
29417
                    }
29418
                    this.trigger('data', event);
29419
                    return;
29420
                }
29421
                match = /^#EXT-X-CUE-IN:(.*)?$/.exec(newLine);
29422
                if (match) {
29423
                    event = {
29424
                        type: 'tag',
29425
                        tagType: 'cue-in'
29426
                    };
29427
                    if (match[1]) {
29428
                        event.data = match[1];
29429
                    } else {
29430
                        event.data = '';
29431
                    }
29432
                    this.trigger('data', event);
29433
                    return;
29434
                }
29435
                match = /^#EXT-X-SKIP:(.*)$/.exec(newLine);
29436
                if (match && match[1]) {
29437
                    event = {
29438
                        type: 'tag',
29439
                        tagType: 'skip'
29440
                    };
29441
                    event.attributes = parseAttributes$1(match[1]);
29442
                    if (event.attributes.hasOwnProperty('SKIPPED-SEGMENTS')) {
29443
                        event.attributes['SKIPPED-SEGMENTS'] = parseInt(event.attributes['SKIPPED-SEGMENTS'], 10);
29444
                    }
29445
                    if (event.attributes.hasOwnProperty('RECENTLY-REMOVED-DATERANGES')) {
29446
                        event.attributes['RECENTLY-REMOVED-DATERANGES'] = event.attributes['RECENTLY-REMOVED-DATERANGES'].split(TAB);
29447
                    }
29448
                    this.trigger('data', event);
29449
                    return;
29450
                }
29451
                match = /^#EXT-X-PART:(.*)$/.exec(newLine);
29452
                if (match && match[1]) {
29453
                    event = {
29454
                        type: 'tag',
29455
                        tagType: 'part'
29456
                    };
29457
                    event.attributes = parseAttributes$1(match[1]);
29458
                    ['DURATION'].forEach(function (key) {
29459
                        if (event.attributes.hasOwnProperty(key)) {
29460
                            event.attributes[key] = parseFloat(event.attributes[key]);
29461
                        }
29462
                    });
29463
                    ['INDEPENDENT', 'GAP'].forEach(function (key) {
29464
                        if (event.attributes.hasOwnProperty(key)) {
29465
                            event.attributes[key] = /YES/.test(event.attributes[key]);
29466
                        }
29467
                    });
29468
                    if (event.attributes.hasOwnProperty('BYTERANGE')) {
29469
                        event.attributes.byterange = parseByterange(event.attributes.BYTERANGE);
29470
                    }
29471
                    this.trigger('data', event);
29472
                    return;
29473
                }
29474
                match = /^#EXT-X-SERVER-CONTROL:(.*)$/.exec(newLine);
29475
                if (match && match[1]) {
29476
                    event = {
29477
                        type: 'tag',
29478
                        tagType: 'server-control'
29479
                    };
29480
                    event.attributes = parseAttributes$1(match[1]);
29481
                    ['CAN-SKIP-UNTIL', 'PART-HOLD-BACK', 'HOLD-BACK'].forEach(function (key) {
29482
                        if (event.attributes.hasOwnProperty(key)) {
29483
                            event.attributes[key] = parseFloat(event.attributes[key]);
29484
                        }
29485
                    });
29486
                    ['CAN-SKIP-DATERANGES', 'CAN-BLOCK-RELOAD'].forEach(function (key) {
29487
                        if (event.attributes.hasOwnProperty(key)) {
29488
                            event.attributes[key] = /YES/.test(event.attributes[key]);
29489
                        }
29490
                    });
29491
                    this.trigger('data', event);
29492
                    return;
29493
                }
29494
                match = /^#EXT-X-PART-INF:(.*)$/.exec(newLine);
29495
                if (match && match[1]) {
29496
                    event = {
29497
                        type: 'tag',
29498
                        tagType: 'part-inf'
29499
                    };
29500
                    event.attributes = parseAttributes$1(match[1]);
29501
                    ['PART-TARGET'].forEach(function (key) {
29502
                        if (event.attributes.hasOwnProperty(key)) {
29503
                            event.attributes[key] = parseFloat(event.attributes[key]);
29504
                        }
29505
                    });
29506
                    this.trigger('data', event);
29507
                    return;
29508
                }
29509
                match = /^#EXT-X-PRELOAD-HINT:(.*)$/.exec(newLine);
29510
                if (match && match[1]) {
29511
                    event = {
29512
                        type: 'tag',
29513
                        tagType: 'preload-hint'
29514
                    };
29515
                    event.attributes = parseAttributes$1(match[1]);
29516
                    ['BYTERANGE-START', 'BYTERANGE-LENGTH'].forEach(function (key) {
29517
                        if (event.attributes.hasOwnProperty(key)) {
29518
                            event.attributes[key] = parseInt(event.attributes[key], 10);
29519
                            const subkey = key === 'BYTERANGE-LENGTH' ? 'length' : 'offset';
29520
                            event.attributes.byterange = event.attributes.byterange || {};
29521
                            event.attributes.byterange[subkey] = event.attributes[key]; // only keep the parsed byterange object.
29522
 
29523
                            delete event.attributes[key];
29524
                        }
29525
                    });
29526
                    this.trigger('data', event);
29527
                    return;
29528
                }
29529
                match = /^#EXT-X-RENDITION-REPORT:(.*)$/.exec(newLine);
29530
                if (match && match[1]) {
29531
                    event = {
29532
                        type: 'tag',
29533
                        tagType: 'rendition-report'
29534
                    };
29535
                    event.attributes = parseAttributes$1(match[1]);
29536
                    ['LAST-MSN', 'LAST-PART'].forEach(function (key) {
29537
                        if (event.attributes.hasOwnProperty(key)) {
29538
                            event.attributes[key] = parseInt(event.attributes[key], 10);
29539
                        }
29540
                    });
29541
                    this.trigger('data', event);
29542
                    return;
29543
                }
29544
                match = /^#EXT-X-DATERANGE:(.*)$/.exec(newLine);
29545
                if (match && match[1]) {
29546
                    event = {
29547
                        type: 'tag',
29548
                        tagType: 'daterange'
29549
                    };
29550
                    event.attributes = parseAttributes$1(match[1]);
29551
                    ['ID', 'CLASS'].forEach(function (key) {
29552
                        if (event.attributes.hasOwnProperty(key)) {
29553
                            event.attributes[key] = String(event.attributes[key]);
29554
                        }
29555
                    });
29556
                    ['START-DATE', 'END-DATE'].forEach(function (key) {
29557
                        if (event.attributes.hasOwnProperty(key)) {
29558
                            event.attributes[key] = new Date(event.attributes[key]);
29559
                        }
29560
                    });
29561
                    ['DURATION', 'PLANNED-DURATION'].forEach(function (key) {
29562
                        if (event.attributes.hasOwnProperty(key)) {
29563
                            event.attributes[key] = parseFloat(event.attributes[key]);
29564
                        }
29565
                    });
29566
                    ['END-ON-NEXT'].forEach(function (key) {
29567
                        if (event.attributes.hasOwnProperty(key)) {
29568
                            event.attributes[key] = /YES/i.test(event.attributes[key]);
29569
                        }
29570
                    });
29571
                    ['SCTE35-CMD', ' SCTE35-OUT', 'SCTE35-IN'].forEach(function (key) {
29572
                        if (event.attributes.hasOwnProperty(key)) {
29573
                            event.attributes[key] = event.attributes[key].toString(16);
29574
                        }
29575
                    });
29576
                    const clientAttributePattern = /^X-([A-Z]+-)+[A-Z]+$/;
29577
                    for (const key in event.attributes) {
29578
                        if (!clientAttributePattern.test(key)) {
29579
                            continue;
29580
                        }
29581
                        const isHexaDecimal = /[0-9A-Fa-f]{6}/g.test(event.attributes[key]);
29582
                        const isDecimalFloating = /^\d+(\.\d+)?$/.test(event.attributes[key]);
29583
                        event.attributes[key] = isHexaDecimal ? event.attributes[key].toString(16) : isDecimalFloating ? parseFloat(event.attributes[key]) : String(event.attributes[key]);
29584
                    }
29585
                    this.trigger('data', event);
29586
                    return;
29587
                }
29588
                match = /^#EXT-X-INDEPENDENT-SEGMENTS/.exec(newLine);
29589
                if (match) {
29590
                    this.trigger('data', {
29591
                        type: 'tag',
29592
                        tagType: 'independent-segments'
29593
                    });
29594
                    return;
29595
                }
29596
                match = /^#EXT-X-CONTENT-STEERING:(.*)$/.exec(newLine);
29597
                if (match) {
29598
                    event = {
29599
                        type: 'tag',
29600
                        tagType: 'content-steering'
29601
                    };
29602
                    event.attributes = parseAttributes$1(match[1]);
29603
                    this.trigger('data', event);
29604
                    return;
29605
                } // unknown tag type
29606
 
29607
                this.trigger('data', {
29608
                    type: 'tag',
29609
                    data: newLine.slice(4)
29610
                });
29611
            });
29612
        }
29613
        /**
29614
         * Add a parser for custom headers
29615
         *
29616
         * @param {Object}   options              a map of options for the added parser
29617
         * @param {RegExp}   options.expression   a regular expression to match the custom header
29618
         * @param {string}   options.customType   the custom type to register to the output
29619
         * @param {Function} [options.dataParser] function to parse the line into an object
29620
         * @param {boolean}  [options.segment]    should tag data be attached to the segment object
29621
         */
29622
 
29623
        addParser({
29624
                      expression,
29625
                      customType,
29626
                      dataParser,
29627
                      segment
29628
                  }) {
29629
            if (typeof dataParser !== 'function') {
29630
                dataParser = line => line;
29631
            }
29632
            this.customParsers.push(line => {
29633
                const match = expression.exec(line);
29634
                if (match) {
29635
                    this.trigger('data', {
29636
                        type: 'custom',
29637
                        data: dataParser(line),
29638
                        customType,
29639
                        segment
29640
                    });
29641
                    return true;
29642
                }
29643
            });
29644
        }
29645
        /**
29646
         * Add a custom header mapper
29647
         *
29648
         * @param {Object}   options
29649
         * @param {RegExp}   options.expression   a regular expression to match the custom header
29650
         * @param {Function} options.map          function to translate tag into a different tag
29651
         */
29652
 
29653
        addTagMapper({
29654
                         expression,
29655
                         map
29656
                     }) {
29657
            const mapFn = line => {
29658
                if (expression.test(line)) {
29659
                    return map(line);
29660
                }
29661
                return line;
29662
            };
29663
            this.tagMappers.push(mapFn);
29664
        }
29665
    }
29666
    const camelCase = str => str.toLowerCase().replace(/-(\w)/g, a => a[1].toUpperCase());
29667
    const camelCaseKeys = function (attributes) {
29668
        const result = {};
29669
        Object.keys(attributes).forEach(function (key) {
29670
            result[camelCase(key)] = attributes[key];
29671
        });
29672
        return result;
29673
    }; // set SERVER-CONTROL hold back based upon targetDuration and partTargetDuration
29674
    // we need this helper because defaults are based upon targetDuration and
29675
    // partTargetDuration being set, but they may not be if SERVER-CONTROL appears before
29676
    // target durations are set.
29677
 
29678
    const setHoldBack = function (manifest) {
29679
        const {
29680
            serverControl,
29681
            targetDuration,
29682
            partTargetDuration
29683
        } = manifest;
29684
        if (!serverControl) {
29685
            return;
29686
        }
29687
        const tag = '#EXT-X-SERVER-CONTROL';
29688
        const hb = 'holdBack';
29689
        const phb = 'partHoldBack';
29690
        const minTargetDuration = targetDuration && targetDuration * 3;
29691
        const minPartDuration = partTargetDuration && partTargetDuration * 2;
29692
        if (targetDuration && !serverControl.hasOwnProperty(hb)) {
29693
            serverControl[hb] = minTargetDuration;
29694
            this.trigger('info', {
29695
                message: `${tag} defaulting HOLD-BACK to targetDuration * 3 (${minTargetDuration}).`
29696
            });
29697
        }
29698
        if (minTargetDuration && serverControl[hb] < minTargetDuration) {
29699
            this.trigger('warn', {
29700
                message: `${tag} clamping HOLD-BACK (${serverControl[hb]}) to targetDuration * 3 (${minTargetDuration})`
29701
            });
29702
            serverControl[hb] = minTargetDuration;
29703
        } // default no part hold back to part target duration * 3
29704
 
29705
        if (partTargetDuration && !serverControl.hasOwnProperty(phb)) {
29706
            serverControl[phb] = partTargetDuration * 3;
29707
            this.trigger('info', {
29708
                message: `${tag} defaulting PART-HOLD-BACK to partTargetDuration * 3 (${serverControl[phb]}).`
29709
            });
29710
        } // if part hold back is too small default it to part target duration * 2
29711
 
29712
        if (partTargetDuration && serverControl[phb] < minPartDuration) {
29713
            this.trigger('warn', {
29714
                message: `${tag} clamping PART-HOLD-BACK (${serverControl[phb]}) to partTargetDuration * 2 (${minPartDuration}).`
29715
            });
29716
            serverControl[phb] = minPartDuration;
29717
        }
29718
    };
29719
    /**
29720
     * A parser for M3U8 files. The current interpretation of the input is
29721
     * exposed as a property `manifest` on parser objects. It's just two lines to
29722
     * create and parse a manifest once you have the contents available as a string:
29723
     *
29724
     * ```js
29725
     * var parser = new m3u8.Parser();
29726
     * parser.push(xhr.responseText);
29727
     * ```
29728
     *
29729
     * New input can later be applied to update the manifest object by calling
29730
     * `push` again.
29731
     *
29732
     * The parser attempts to create a usable manifest object even if the
29733
     * underlying input is somewhat nonsensical. It emits `info` and `warning`
29734
     * events during the parse if it encounters input that seems invalid or
29735
     * requires some property of the manifest object to be defaulted.
29736
     *
29737
     * @class Parser
29738
     * @extends Stream
29739
     */
29740
 
29741
    class Parser extends Stream {
29742
        constructor() {
29743
            super();
29744
            this.lineStream = new LineStream();
29745
            this.parseStream = new ParseStream();
29746
            this.lineStream.pipe(this.parseStream);
29747
            this.lastProgramDateTime = null;
29748
            /* eslint-disable consistent-this */
29749
 
29750
            const self = this;
29751
            /* eslint-enable consistent-this */
29752
 
29753
            const uris = [];
29754
            let currentUri = {}; // if specified, the active EXT-X-MAP definition
29755
 
29756
            let currentMap; // if specified, the active decryption key
29757
 
29758
            let key;
29759
            let hasParts = false;
29760
            const noop = function () {};
29761
            const defaultMediaGroups = {
29762
                'AUDIO': {},
29763
                'VIDEO': {},
29764
                'CLOSED-CAPTIONS': {},
29765
                'SUBTITLES': {}
29766
            }; // This is the Widevine UUID from DASH IF IOP. The same exact string is
29767
            // used in MPDs with Widevine encrypted streams.
29768
 
29769
            const widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities
29770
 
29771
            let currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
29772
 
29773
            this.manifest = {
29774
                allowCache: true,
29775
                discontinuityStarts: [],
29776
                dateRanges: [],
29777
                segments: []
29778
            }; // keep track of the last seen segment's byte range end, as segments are not required
29779
            // to provide the offset, in which case it defaults to the next byte after the
29780
            // previous segment
29781
 
29782
            let lastByterangeEnd = 0; // keep track of the last seen part's byte range end.
29783
 
29784
            let lastPartByterangeEnd = 0;
29785
            const dateRangeTags = {};
29786
            this.on('end', () => {
29787
                // only add preloadSegment if we don't yet have a uri for it.
29788
                // and we actually have parts/preloadHints
29789
                if (currentUri.uri || !currentUri.parts && !currentUri.preloadHints) {
29790
                    return;
29791
                }
29792
                if (!currentUri.map && currentMap) {
29793
                    currentUri.map = currentMap;
29794
                }
29795
                if (!currentUri.key && key) {
29796
                    currentUri.key = key;
29797
                }
29798
                if (!currentUri.timeline && typeof currentTimeline === 'number') {
29799
                    currentUri.timeline = currentTimeline;
29800
                }
29801
                this.manifest.preloadSegment = currentUri;
29802
            }); // update the manifest with the m3u8 entry from the parse stream
29803
 
29804
            this.parseStream.on('data', function (entry) {
29805
                let mediaGroup;
29806
                let rendition;
29807
                ({
29808
                    tag() {
29809
                        // switch based on the tag type
29810
                        (({
29811
                            version() {
29812
                                if (entry.version) {
29813
                                    this.manifest.version = entry.version;
29814
                                }
29815
                            },
29816
                            'allow-cache'() {
29817
                                this.manifest.allowCache = entry.allowed;
29818
                                if (!('allowed' in entry)) {
29819
                                    this.trigger('info', {
29820
                                        message: 'defaulting allowCache to YES'
29821
                                    });
29822
                                    this.manifest.allowCache = true;
29823
                                }
29824
                            },
29825
                            byterange() {
29826
                                const byterange = {};
29827
                                if ('length' in entry) {
29828
                                    currentUri.byterange = byterange;
29829
                                    byterange.length = entry.length;
29830
                                    if (!('offset' in entry)) {
29831
                                        /*
29832
                     * From the latest spec (as of this writing):
29833
                     * https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.2
29834
                     *
29835
                     * Same text since EXT-X-BYTERANGE's introduction in draft 7:
29836
                     * https://tools.ietf.org/html/draft-pantos-http-live-streaming-07#section-3.3.1)
29837
                     *
29838
                     * "If o [offset] is not present, the sub-range begins at the next byte
29839
                     * following the sub-range of the previous media segment."
29840
                     */
29841
                                        entry.offset = lastByterangeEnd;
29842
                                    }
29843
                                }
29844
                                if ('offset' in entry) {
29845
                                    currentUri.byterange = byterange;
29846
                                    byterange.offset = entry.offset;
29847
                                }
29848
                                lastByterangeEnd = byterange.offset + byterange.length;
29849
                            },
29850
                            endlist() {
29851
                                this.manifest.endList = true;
29852
                            },
29853
                            inf() {
29854
                                if (!('mediaSequence' in this.manifest)) {
29855
                                    this.manifest.mediaSequence = 0;
29856
                                    this.trigger('info', {
29857
                                        message: 'defaulting media sequence to zero'
29858
                                    });
29859
                                }
29860
                                if (!('discontinuitySequence' in this.manifest)) {
29861
                                    this.manifest.discontinuitySequence = 0;
29862
                                    this.trigger('info', {
29863
                                        message: 'defaulting discontinuity sequence to zero'
29864
                                    });
29865
                                }
29866
                                if (entry.title) {
29867
                                    currentUri.title = entry.title;
29868
                                }
29869
                                if (entry.duration > 0) {
29870
                                    currentUri.duration = entry.duration;
29871
                                }
29872
                                if (entry.duration === 0) {
29873
                                    currentUri.duration = 0.01;
29874
                                    this.trigger('info', {
29875
                                        message: 'updating zero segment duration to a small value'
29876
                                    });
29877
                                }
29878
                                this.manifest.segments = uris;
29879
                            },
29880
                            key() {
29881
                                if (!entry.attributes) {
29882
                                    this.trigger('warn', {
29883
                                        message: 'ignoring key declaration without attribute list'
29884
                                    });
29885
                                    return;
29886
                                } // clear the active encryption key
29887
 
29888
                                if (entry.attributes.METHOD === 'NONE') {
29889
                                    key = null;
29890
                                    return;
29891
                                }
29892
                                if (!entry.attributes.URI) {
29893
                                    this.trigger('warn', {
29894
                                        message: 'ignoring key declaration without URI'
29895
                                    });
29896
                                    return;
29897
                                }
29898
                                if (entry.attributes.KEYFORMAT === 'com.apple.streamingkeydelivery') {
29899
                                    this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
29900
 
29901
                                    this.manifest.contentProtection['com.apple.fps.1_0'] = {
29902
                                        attributes: entry.attributes
29903
                                    };
29904
                                    return;
29905
                                }
29906
                                if (entry.attributes.KEYFORMAT === 'com.microsoft.playready') {
29907
                                    this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
29908
 
29909
                                    this.manifest.contentProtection['com.microsoft.playready'] = {
29910
                                        uri: entry.attributes.URI
29911
                                    };
29912
                                    return;
29913
                                } // check if the content is encrypted for Widevine
29914
                                // Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf
29915
 
29916
                                if (entry.attributes.KEYFORMAT === widevineUuid) {
29917
                                    const VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC'];
29918
                                    if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) {
29919
                                        this.trigger('warn', {
29920
                                            message: 'invalid key method provided for Widevine'
29921
                                        });
29922
                                        return;
29923
                                    }
29924
                                    if (entry.attributes.METHOD === 'SAMPLE-AES-CENC') {
29925
                                        this.trigger('warn', {
29926
                                            message: 'SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead'
29927
                                        });
29928
                                    }
29929
                                    if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') {
29930
                                        this.trigger('warn', {
29931
                                            message: 'invalid key URI provided for Widevine'
29932
                                        });
29933
                                        return;
29934
                                    }
29935
                                    if (!(entry.attributes.KEYID && entry.attributes.KEYID.substring(0, 2) === '0x')) {
29936
                                        this.trigger('warn', {
29937
                                            message: 'invalid key ID provided for Widevine'
29938
                                        });
29939
                                        return;
29940
                                    } // if Widevine key attributes are valid, store them as `contentProtection`
29941
                                    // on the manifest to emulate Widevine tag structure in a DASH mpd
29942
 
29943
                                    this.manifest.contentProtection = this.manifest.contentProtection || {};
29944
                                    this.manifest.contentProtection['com.widevine.alpha'] = {
29945
                                        attributes: {
29946
                                            schemeIdUri: entry.attributes.KEYFORMAT,
29947
                                            // remove '0x' from the key id string
29948
                                            keyId: entry.attributes.KEYID.substring(2)
29949
                                        },
29950
                                        // decode the base64-encoded PSSH box
29951
                                        pssh: decodeB64ToUint8Array$1(entry.attributes.URI.split(',')[1])
29952
                                    };
29953
                                    return;
29954
                                }
29955
                                if (!entry.attributes.METHOD) {
29956
                                    this.trigger('warn', {
29957
                                        message: 'defaulting key method to AES-128'
29958
                                    });
29959
                                } // setup an encryption key for upcoming segments
29960
 
29961
                                key = {
29962
                                    method: entry.attributes.METHOD || 'AES-128',
29963
                                    uri: entry.attributes.URI
29964
                                };
29965
                                if (typeof entry.attributes.IV !== 'undefined') {
29966
                                    key.iv = entry.attributes.IV;
29967
                                }
29968
                            },
29969
                            'media-sequence'() {
29970
                                if (!isFinite(entry.number)) {
29971
                                    this.trigger('warn', {
29972
                                        message: 'ignoring invalid media sequence: ' + entry.number
29973
                                    });
29974
                                    return;
29975
                                }
29976
                                this.manifest.mediaSequence = entry.number;
29977
                            },
29978
                            'discontinuity-sequence'() {
29979
                                if (!isFinite(entry.number)) {
29980
                                    this.trigger('warn', {
29981
                                        message: 'ignoring invalid discontinuity sequence: ' + entry.number
29982
                                    });
29983
                                    return;
29984
                                }
29985
                                this.manifest.discontinuitySequence = entry.number;
29986
                                currentTimeline = entry.number;
29987
                            },
29988
                            'playlist-type'() {
29989
                                if (!/VOD|EVENT/.test(entry.playlistType)) {
29990
                                    this.trigger('warn', {
29991
                                        message: 'ignoring unknown playlist type: ' + entry.playlist
29992
                                    });
29993
                                    return;
29994
                                }
29995
                                this.manifest.playlistType = entry.playlistType;
29996
                            },
29997
                            map() {
29998
                                currentMap = {};
29999
                                if (entry.uri) {
30000
                                    currentMap.uri = entry.uri;
30001
                                }
30002
                                if (entry.byterange) {
30003
                                    currentMap.byterange = entry.byterange;
30004
                                }
30005
                                if (key) {
30006
                                    currentMap.key = key;
30007
                                }
30008
                            },
30009
                            'stream-inf'() {
30010
                                this.manifest.playlists = uris;
30011
                                this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
30012
                                if (!entry.attributes) {
30013
                                    this.trigger('warn', {
30014
                                        message: 'ignoring empty stream-inf attributes'
30015
                                    });
30016
                                    return;
30017
                                }
30018
                                if (!currentUri.attributes) {
30019
                                    currentUri.attributes = {};
30020
                                }
30021
                                _extends$1(currentUri.attributes, entry.attributes);
30022
                            },
30023
                            media() {
30024
                                this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
30025
                                if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
30026
                                    this.trigger('warn', {
30027
                                        message: 'ignoring incomplete or missing media group'
30028
                                    });
30029
                                    return;
30030
                                } // find the media group, creating defaults as necessary
30031
 
30032
                                const mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
30033
                                mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
30034
                                mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata
30035
 
30036
                                rendition = {
30037
                                    default: /yes/i.test(entry.attributes.DEFAULT)
30038
                                };
30039
                                if (rendition.default) {
30040
                                    rendition.autoselect = true;
30041
                                } else {
30042
                                    rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
30043
                                }
30044
                                if (entry.attributes.LANGUAGE) {
30045
                                    rendition.language = entry.attributes.LANGUAGE;
30046
                                }
30047
                                if (entry.attributes.URI) {
30048
                                    rendition.uri = entry.attributes.URI;
30049
                                }
30050
                                if (entry.attributes['INSTREAM-ID']) {
30051
                                    rendition.instreamId = entry.attributes['INSTREAM-ID'];
30052
                                }
30053
                                if (entry.attributes.CHARACTERISTICS) {
30054
                                    rendition.characteristics = entry.attributes.CHARACTERISTICS;
30055
                                }
30056
                                if (entry.attributes.FORCED) {
30057
                                    rendition.forced = /yes/i.test(entry.attributes.FORCED);
30058
                                } // insert the new rendition
30059
 
30060
                                mediaGroup[entry.attributes.NAME] = rendition;
30061
                            },
30062
                            discontinuity() {
30063
                                currentTimeline += 1;
30064
                                currentUri.discontinuity = true;
30065
                                this.manifest.discontinuityStarts.push(uris.length);
30066
                            },
30067
                            'program-date-time'() {
30068
                                if (typeof this.manifest.dateTimeString === 'undefined') {
30069
                                    // PROGRAM-DATE-TIME is a media-segment tag, but for backwards
30070
                                    // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
30071
                                    // to the manifest object
30072
                                    // TODO: Consider removing this in future major version
30073
                                    this.manifest.dateTimeString = entry.dateTimeString;
30074
                                    this.manifest.dateTimeObject = entry.dateTimeObject;
30075
                                }
30076
                                currentUri.dateTimeString = entry.dateTimeString;
30077
                                currentUri.dateTimeObject = entry.dateTimeObject;
30078
                                const {
30079
                                    lastProgramDateTime
30080
                                } = this;
30081
                                this.lastProgramDateTime = new Date(entry.dateTimeString).getTime(); // We should extrapolate Program Date Time backward only during first program date time occurrence.
30082
                                // Once we have at least one program date time point, we can always extrapolate it forward using lastProgramDateTime reference.
30083
 
30084
                                if (lastProgramDateTime === null) {
30085
                                    // Extrapolate Program Date Time backward
30086
                                    // Since it is first program date time occurrence we're assuming that
30087
                                    // all this.manifest.segments have no program date time info
30088
                                    this.manifest.segments.reduceRight((programDateTime, segment) => {
30089
                                        segment.programDateTime = programDateTime - segment.duration * 1000;
30090
                                        return segment.programDateTime;
30091
                                    }, this.lastProgramDateTime);
30092
                                }
30093
                            },
30094
                            targetduration() {
30095
                                if (!isFinite(entry.duration) || entry.duration < 0) {
30096
                                    this.trigger('warn', {
30097
                                        message: 'ignoring invalid target duration: ' + entry.duration
30098
                                    });
30099
                                    return;
30100
                                }
30101
                                this.manifest.targetDuration = entry.duration;
30102
                                setHoldBack.call(this, this.manifest);
30103
                            },
30104
                            start() {
30105
                                if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
30106
                                    this.trigger('warn', {
30107
                                        message: 'ignoring start declaration without appropriate attribute list'
30108
                                    });
30109
                                    return;
30110
                                }
30111
                                this.manifest.start = {
30112
                                    timeOffset: entry.attributes['TIME-OFFSET'],
30113
                                    precise: entry.attributes.PRECISE
30114
                                };
30115
                            },
30116
                            'cue-out'() {
30117
                                currentUri.cueOut = entry.data;
30118
                            },
30119
                            'cue-out-cont'() {
30120
                                currentUri.cueOutCont = entry.data;
30121
                            },
30122
                            'cue-in'() {
30123
                                currentUri.cueIn = entry.data;
30124
                            },
30125
                            'skip'() {
30126
                                this.manifest.skip = camelCaseKeys(entry.attributes);
30127
                                this.warnOnMissingAttributes_('#EXT-X-SKIP', entry.attributes, ['SKIPPED-SEGMENTS']);
30128
                            },
30129
                            'part'() {
30130
                                hasParts = true; // parts are always specifed before a segment
30131
 
30132
                                const segmentIndex = this.manifest.segments.length;
30133
                                const part = camelCaseKeys(entry.attributes);
30134
                                currentUri.parts = currentUri.parts || [];
30135
                                currentUri.parts.push(part);
30136
                                if (part.byterange) {
30137
                                    if (!part.byterange.hasOwnProperty('offset')) {
30138
                                        part.byterange.offset = lastPartByterangeEnd;
30139
                                    }
30140
                                    lastPartByterangeEnd = part.byterange.offset + part.byterange.length;
30141
                                }
30142
                                const partIndex = currentUri.parts.length - 1;
30143
                                this.warnOnMissingAttributes_(`#EXT-X-PART #${partIndex} for segment #${segmentIndex}`, entry.attributes, ['URI', 'DURATION']);
30144
                                if (this.manifest.renditionReports) {
30145
                                    this.manifest.renditionReports.forEach((r, i) => {
30146
                                        if (!r.hasOwnProperty('lastPart')) {
30147
                                            this.trigger('warn', {
30148
                                                message: `#EXT-X-RENDITION-REPORT #${i} lacks required attribute(s): LAST-PART`
30149
                                            });
30150
                                        }
30151
                                    });
30152
                                }
30153
                            },
30154
                            'server-control'() {
30155
                                const attrs = this.manifest.serverControl = camelCaseKeys(entry.attributes);
30156
                                if (!attrs.hasOwnProperty('canBlockReload')) {
30157
                                    attrs.canBlockReload = false;
30158
                                    this.trigger('info', {
30159
                                        message: '#EXT-X-SERVER-CONTROL defaulting CAN-BLOCK-RELOAD to false'
30160
                                    });
30161
                                }
30162
                                setHoldBack.call(this, this.manifest);
30163
                                if (attrs.canSkipDateranges && !attrs.hasOwnProperty('canSkipUntil')) {
30164
                                    this.trigger('warn', {
30165
                                        message: '#EXT-X-SERVER-CONTROL lacks required attribute CAN-SKIP-UNTIL which is required when CAN-SKIP-DATERANGES is set'
30166
                                    });
30167
                                }
30168
                            },
30169
                            'preload-hint'() {
30170
                                // parts are always specifed before a segment
30171
                                const segmentIndex = this.manifest.segments.length;
30172
                                const hint = camelCaseKeys(entry.attributes);
30173
                                const isPart = hint.type && hint.type === 'PART';
30174
                                currentUri.preloadHints = currentUri.preloadHints || [];
30175
                                currentUri.preloadHints.push(hint);
30176
                                if (hint.byterange) {
30177
                                    if (!hint.byterange.hasOwnProperty('offset')) {
30178
                                        // use last part byterange end or zero if not a part.
30179
                                        hint.byterange.offset = isPart ? lastPartByterangeEnd : 0;
30180
                                        if (isPart) {
30181
                                            lastPartByterangeEnd = hint.byterange.offset + hint.byterange.length;
30182
                                        }
30183
                                    }
30184
                                }
30185
                                const index = currentUri.preloadHints.length - 1;
30186
                                this.warnOnMissingAttributes_(`#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex}`, entry.attributes, ['TYPE', 'URI']);
30187
                                if (!hint.type) {
30188
                                    return;
30189
                                } // search through all preload hints except for the current one for
30190
                                // a duplicate type.
30191
 
30192
                                for (let i = 0; i < currentUri.preloadHints.length - 1; i++) {
30193
                                    const otherHint = currentUri.preloadHints[i];
30194
                                    if (!otherHint.type) {
30195
                                        continue;
30196
                                    }
30197
                                    if (otherHint.type === hint.type) {
30198
                                        this.trigger('warn', {
30199
                                            message: `#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex} has the same TYPE ${hint.type} as preload hint #${i}`
30200
                                        });
30201
                                    }
30202
                                }
30203
                            },
30204
                            'rendition-report'() {
30205
                                const report = camelCaseKeys(entry.attributes);
30206
                                this.manifest.renditionReports = this.manifest.renditionReports || [];
30207
                                this.manifest.renditionReports.push(report);
30208
                                const index = this.manifest.renditionReports.length - 1;
30209
                                const required = ['LAST-MSN', 'URI'];
30210
                                if (hasParts) {
30211
                                    required.push('LAST-PART');
30212
                                }
30213
                                this.warnOnMissingAttributes_(`#EXT-X-RENDITION-REPORT #${index}`, entry.attributes, required);
30214
                            },
30215
                            'part-inf'() {
30216
                                this.manifest.partInf = camelCaseKeys(entry.attributes);
30217
                                this.warnOnMissingAttributes_('#EXT-X-PART-INF', entry.attributes, ['PART-TARGET']);
30218
                                if (this.manifest.partInf.partTarget) {
30219
                                    this.manifest.partTargetDuration = this.manifest.partInf.partTarget;
30220
                                }
30221
                                setHoldBack.call(this, this.manifest);
30222
                            },
30223
                            'daterange'() {
30224
                                this.manifest.dateRanges.push(camelCaseKeys(entry.attributes));
30225
                                const index = this.manifest.dateRanges.length - 1;
30226
                                this.warnOnMissingAttributes_(`#EXT-X-DATERANGE #${index}`, entry.attributes, ['ID', 'START-DATE']);
30227
                                const dateRange = this.manifest.dateRanges[index];
30228
                                if (dateRange.endDate && dateRange.startDate && new Date(dateRange.endDate) < new Date(dateRange.startDate)) {
30229
                                    this.trigger('warn', {
30230
                                        message: 'EXT-X-DATERANGE END-DATE must be equal to or later than the value of the START-DATE'
30231
                                    });
30232
                                }
30233
                                if (dateRange.duration && dateRange.duration < 0) {
30234
                                    this.trigger('warn', {
30235
                                        message: 'EXT-X-DATERANGE DURATION must not be negative'
30236
                                    });
30237
                                }
30238
                                if (dateRange.plannedDuration && dateRange.plannedDuration < 0) {
30239
                                    this.trigger('warn', {
30240
                                        message: 'EXT-X-DATERANGE PLANNED-DURATION must not be negative'
30241
                                    });
30242
                                }
30243
                                const endOnNextYes = !!dateRange.endOnNext;
30244
                                if (endOnNextYes && !dateRange.class) {
30245
                                    this.trigger('warn', {
30246
                                        message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must have a CLASS attribute'
30247
                                    });
30248
                                }
30249
                                if (endOnNextYes && (dateRange.duration || dateRange.endDate)) {
30250
                                    this.trigger('warn', {
30251
                                        message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must not contain DURATION or END-DATE attributes'
30252
                                    });
30253
                                }
30254
                                if (dateRange.duration && dateRange.endDate) {
30255
                                    const startDate = dateRange.startDate;
30256
                                    const newDateInSeconds = startDate.getTime() + dateRange.duration * 1000;
30257
                                    this.manifest.dateRanges[index].endDate = new Date(newDateInSeconds);
30258
                                }
30259
                                if (!dateRangeTags[dateRange.id]) {
30260
                                    dateRangeTags[dateRange.id] = dateRange;
30261
                                } else {
30262
                                    for (const attribute in dateRangeTags[dateRange.id]) {
30263
                                        if (!!dateRange[attribute] && JSON.stringify(dateRangeTags[dateRange.id][attribute]) !== JSON.stringify(dateRange[attribute])) {
30264
                                            this.trigger('warn', {
30265
                                                message: 'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes values'
30266
                                            });
30267
                                            break;
30268
                                        }
30269
                                    } // if tags with the same ID do not have conflicting attributes, merge them
30270
 
30271
                                    const dateRangeWithSameId = this.manifest.dateRanges.findIndex(dateRangeToFind => dateRangeToFind.id === dateRange.id);
30272
                                    this.manifest.dateRanges[dateRangeWithSameId] = _extends$1(this.manifest.dateRanges[dateRangeWithSameId], dateRange);
30273
                                    dateRangeTags[dateRange.id] = _extends$1(dateRangeTags[dateRange.id], dateRange); // after merging, delete the duplicate dateRange that was added last
30274
 
30275
                                    this.manifest.dateRanges.pop();
30276
                                }
30277
                            },
30278
                            'independent-segments'() {
30279
                                this.manifest.independentSegments = true;
30280
                            },
30281
                            'content-steering'() {
30282
                                this.manifest.contentSteering = camelCaseKeys(entry.attributes);
30283
                                this.warnOnMissingAttributes_('#EXT-X-CONTENT-STEERING', entry.attributes, ['SERVER-URI']);
30284
                            }
30285
                        })[entry.tagType] || noop).call(self);
30286
                    },
30287
                    uri() {
30288
                        currentUri.uri = entry.uri;
30289
                        uris.push(currentUri); // if no explicit duration was declared, use the target duration
30290
 
30291
                        if (this.manifest.targetDuration && !('duration' in currentUri)) {
30292
                            this.trigger('warn', {
30293
                                message: 'defaulting segment duration to the target duration'
30294
                            });
30295
                            currentUri.duration = this.manifest.targetDuration;
30296
                        } // annotate with encryption information, if necessary
30297
 
30298
                        if (key) {
30299
                            currentUri.key = key;
30300
                        }
30301
                        currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary
30302
 
30303
                        if (currentMap) {
30304
                            currentUri.map = currentMap;
30305
                        } // reset the last byterange end as it needs to be 0 between parts
30306
 
30307
                        lastPartByterangeEnd = 0; // Once we have at least one program date time we can always extrapolate it forward
30308
 
30309
                        if (this.lastProgramDateTime !== null) {
30310
                            currentUri.programDateTime = this.lastProgramDateTime;
30311
                            this.lastProgramDateTime += currentUri.duration * 1000;
30312
                        } // prepare for the next URI
30313
 
30314
                        currentUri = {};
30315
                    },
30316
                    comment() {// comments are not important for playback
30317
                    },
30318
                    custom() {
30319
                        // if this is segment-level data attach the output to the segment
30320
                        if (entry.segment) {
30321
                            currentUri.custom = currentUri.custom || {};
30322
                            currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object
30323
                        } else {
30324
                            this.manifest.custom = this.manifest.custom || {};
30325
                            this.manifest.custom[entry.customType] = entry.data;
30326
                        }
30327
                    }
30328
                })[entry.type].call(self);
30329
            });
30330
        }
30331
        warnOnMissingAttributes_(identifier, attributes, required) {
30332
            const missing = [];
30333
            required.forEach(function (key) {
30334
                if (!attributes.hasOwnProperty(key)) {
30335
                    missing.push(key);
30336
                }
30337
            });
30338
            if (missing.length) {
30339
                this.trigger('warn', {
30340
                    message: `${identifier} lacks required attribute(s): ${missing.join(', ')}`
30341
                });
30342
            }
30343
        }
30344
        /**
30345
         * Parse the input string and update the manifest object.
30346
         *
30347
         * @param {string} chunk a potentially incomplete portion of the manifest
30348
         */
30349
 
30350
        push(chunk) {
30351
            this.lineStream.push(chunk);
30352
        }
30353
        /**
30354
         * Flush any remaining input. This can be handy if the last line of an M3U8
30355
         * manifest did not contain a trailing newline but the file has been
30356
         * completely received.
30357
         */
30358
 
30359
        end() {
30360
            // flush any buffered input
30361
            this.lineStream.push('\n');
30362
            if (this.manifest.dateRanges.length && this.lastProgramDateTime === null) {
30363
                this.trigger('warn', {
30364
                    message: 'A playlist with EXT-X-DATERANGE tag must contain atleast one EXT-X-PROGRAM-DATE-TIME tag'
30365
                });
30366
            }
30367
            this.lastProgramDateTime = null;
30368
            this.trigger('end');
30369
        }
30370
        /**
30371
         * Add an additional parser for non-standard tags
30372
         *
30373
         * @param {Object}   options              a map of options for the added parser
30374
         * @param {RegExp}   options.expression   a regular expression to match the custom header
30375
         * @param {string}   options.customType   the custom type to register to the output
30376
         * @param {Function} [options.dataParser] function to parse the line into an object
30377
         * @param {boolean}  [options.segment]    should tag data be attached to the segment object
30378
         */
30379
 
30380
        addParser(options) {
30381
            this.parseStream.addParser(options);
30382
        }
30383
        /**
30384
         * Add a custom header mapper
30385
         *
30386
         * @param {Object}   options
30387
         * @param {RegExp}   options.expression   a regular expression to match the custom header
30388
         * @param {Function} options.map          function to translate tag into a different tag
30389
         */
30390
 
30391
        addTagMapper(options) {
30392
            this.parseStream.addTagMapper(options);
30393
        }
30394
    }
30395
 
30396
    var regexs = {
30397
        // to determine mime types
30398
        mp4: /^(av0?1|avc0?[1234]|vp0?9|flac|opus|mp3|mp4a|mp4v|stpp.ttml.im1t)/,
30399
        webm: /^(vp0?[89]|av0?1|opus|vorbis)/,
30400
        ogg: /^(vp0?[89]|theora|flac|opus|vorbis)/,
30401
        // to determine if a codec is audio or video
30402
        video: /^(av0?1|avc0?[1234]|vp0?[89]|hvc1|hev1|theora|mp4v)/,
30403
        audio: /^(mp4a|flac|vorbis|opus|ac-[34]|ec-3|alac|mp3|speex|aac)/,
30404
        text: /^(stpp.ttml.im1t)/,
30405
        // mux.js support regex
30406
        muxerVideo: /^(avc0?1)/,
30407
        muxerAudio: /^(mp4a)/,
30408
        // match nothing as muxer does not support text right now.
30409
        // there cannot never be a character before the start of a string
30410
        // so this matches nothing.
30411
        muxerText: /a^/
30412
    };
30413
    var mediaTypes = ['video', 'audio', 'text'];
30414
    var upperMediaTypes = ['Video', 'Audio', 'Text'];
30415
    /**
30416
     * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
30417
     * `avc1.<hhhhhh>`
30418
     *
30419
     * @param {string} codec
30420
     *        Codec string to translate
30421
     * @return {string}
30422
     *         The translated codec string
30423
     */
30424
 
30425
    var translateLegacyCodec = function translateLegacyCodec(codec) {
30426
        if (!codec) {
30427
            return codec;
30428
        }
30429
        return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
30430
            var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
30431
            var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
30432
            return 'avc1.' + profileHex + '00' + avcLevelHex;
30433
        });
30434
    };
30435
    /**
30436
     * @typedef {Object} ParsedCodecInfo
30437
     * @property {number} codecCount
30438
     *           Number of codecs parsed
30439
     * @property {string} [videoCodec]
30440
     *           Parsed video codec (if found)
30441
     * @property {string} [videoObjectTypeIndicator]
30442
     *           Video object type indicator (if found)
30443
     * @property {string|null} audioProfile
30444
     *           Audio profile
30445
     */
30446
 
30447
    /**
30448
     * Parses a codec string to retrieve the number of codecs specified, the video codec and
30449
     * object type indicator, and the audio profile.
30450
     *
30451
     * @param {string} [codecString]
30452
     *        The codec string to parse
30453
     * @return {ParsedCodecInfo}
30454
     *         Parsed codec info
30455
     */
30456
 
30457
    var parseCodecs = function parseCodecs(codecString) {
30458
        if (codecString === void 0) {
30459
            codecString = '';
30460
        }
30461
        var codecs = codecString.split(',');
30462
        var result = [];
30463
        codecs.forEach(function (codec) {
30464
            codec = codec.trim();
30465
            var codecType;
30466
            mediaTypes.forEach(function (name) {
30467
                var match = regexs[name].exec(codec.toLowerCase());
30468
                if (!match || match.length <= 1) {
30469
                    return;
30470
                }
30471
                codecType = name; // maintain codec case
30472
 
30473
                var type = codec.substring(0, match[1].length);
30474
                var details = codec.replace(type, '');
30475
                result.push({
30476
                    type: type,
30477
                    details: details,
30478
                    mediaType: name
30479
                });
30480
            });
30481
            if (!codecType) {
30482
                result.push({
30483
                    type: codec,
30484
                    details: '',
30485
                    mediaType: 'unknown'
30486
                });
30487
            }
30488
        });
30489
        return result;
30490
    };
30491
    /**
30492
     * Returns a ParsedCodecInfo object for the default alternate audio playlist if there is
30493
     * a default alternate audio playlist for the provided audio group.
30494
     *
30495
     * @param {Object} master
30496
     *        The master playlist
30497
     * @param {string} audioGroupId
30498
     *        ID of the audio group for which to find the default codec info
30499
     * @return {ParsedCodecInfo}
30500
     *         Parsed codec info
30501
     */
30502
 
30503
    var codecsFromDefault = function codecsFromDefault(master, audioGroupId) {
30504
        if (!master.mediaGroups.AUDIO || !audioGroupId) {
30505
            return null;
30506
        }
30507
        var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
30508
        if (!audioGroup) {
30509
            return null;
30510
        }
30511
        for (var name in audioGroup) {
30512
            var audioType = audioGroup[name];
30513
            if (audioType.default && audioType.playlists) {
30514
                // codec should be the same for all playlists within the audio type
30515
                return parseCodecs(audioType.playlists[0].attributes.CODECS);
30516
            }
30517
        }
30518
        return null;
30519
    };
30520
    var isAudioCodec = function isAudioCodec(codec) {
30521
        if (codec === void 0) {
30522
            codec = '';
30523
        }
30524
        return regexs.audio.test(codec.trim().toLowerCase());
30525
    };
30526
    var isTextCodec = function isTextCodec(codec) {
30527
        if (codec === void 0) {
30528
            codec = '';
30529
        }
30530
        return regexs.text.test(codec.trim().toLowerCase());
30531
    };
30532
    var getMimeForCodec = function getMimeForCodec(codecString) {
30533
        if (!codecString || typeof codecString !== 'string') {
30534
            return;
30535
        }
30536
        var codecs = codecString.toLowerCase().split(',').map(function (c) {
30537
            return translateLegacyCodec(c.trim());
30538
        }); // default to video type
30539
 
30540
        var type = 'video'; // only change to audio type if the only codec we have is
30541
        // audio
30542
 
30543
        if (codecs.length === 1 && isAudioCodec(codecs[0])) {
30544
            type = 'audio';
30545
        } else if (codecs.length === 1 && isTextCodec(codecs[0])) {
30546
            // text uses application/<container> for now
30547
            type = 'application';
30548
        } // default the container to mp4
30549
 
30550
        var container = 'mp4'; // every codec must be able to go into the container
30551
        // for that container to be the correct one
30552
 
30553
        if (codecs.every(function (c) {
30554
            return regexs.mp4.test(c);
30555
        })) {
30556
            container = 'mp4';
30557
        } else if (codecs.every(function (c) {
30558
            return regexs.webm.test(c);
30559
        })) {
30560
            container = 'webm';
30561
        } else if (codecs.every(function (c) {
30562
            return regexs.ogg.test(c);
30563
        })) {
30564
            container = 'ogg';
30565
        }
30566
        return type + "/" + container + ";codecs=\"" + codecString + "\"";
30567
    };
30568
    var browserSupportsCodec = function browserSupportsCodec(codecString) {
30569
        if (codecString === void 0) {
30570
            codecString = '';
30571
        }
30572
        return window.MediaSource && window.MediaSource.isTypeSupported && window.MediaSource.isTypeSupported(getMimeForCodec(codecString)) || false;
30573
    };
30574
    var muxerSupportsCodec = function muxerSupportsCodec(codecString) {
30575
        if (codecString === void 0) {
30576
            codecString = '';
30577
        }
30578
        return codecString.toLowerCase().split(',').every(function (codec) {
30579
            codec = codec.trim(); // any match is supported.
30580
 
30581
            for (var i = 0; i < upperMediaTypes.length; i++) {
30582
                var type = upperMediaTypes[i];
30583
                if (regexs["muxer" + type].test(codec)) {
30584
                    return true;
30585
                }
30586
            }
30587
            return false;
30588
        });
30589
    };
30590
    var DEFAULT_AUDIO_CODEC = 'mp4a.40.2';
30591
    var DEFAULT_VIDEO_CODEC = 'avc1.4d400d';
30592
 
30593
    var MPEGURL_REGEX = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
30594
    var DASH_REGEX = /^application\/dash\+xml/i;
30595
    /**
30596
     * Returns a string that describes the type of source based on a video source object's
30597
     * media type.
30598
     *
30599
     * @see {@link https://dev.w3.org/html5/pf-summary/video.html#dom-source-type|Source Type}
30600
     *
30601
     * @param {string} type
30602
     *        Video source object media type
30603
     * @return {('hls'|'dash'|'vhs-json'|null)}
30604
     *         VHS source type string
30605
     */
30606
 
30607
    var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
30608
        if (MPEGURL_REGEX.test(type)) {
30609
            return 'hls';
30610
        }
30611
        if (DASH_REGEX.test(type)) {
30612
            return 'dash';
30613
        } // Denotes the special case of a manifest object passed to http-streaming instead of a
30614
        // source URL.
30615
        //
30616
        // See https://en.wikipedia.org/wiki/Media_type for details on specifying media types.
30617
        //
30618
        // In this case, vnd stands for vendor, video.js for the organization, VHS for this
30619
        // project, and the +json suffix identifies the structure of the media type.
30620
 
30621
        if (type === 'application/vnd.videojs.vhs+json') {
30622
            return 'vhs-json';
30623
        }
30624
        return null;
30625
    };
30626
 
30627
    // const log2 = Math.log2 ? Math.log2 : (x) => (Math.log(x) / Math.log(2));
30628
    // we used to do this with log2 but BigInt does not support builtin math
30629
    // Math.ceil(log2(x));
30630
 
30631
    var countBits = function countBits(x) {
30632
        return x.toString(2).length;
30633
    }; // count the number of whole bytes it would take to represent a number
30634
 
30635
    var countBytes = function countBytes(x) {
30636
        return Math.ceil(countBits(x) / 8);
30637
    };
30638
    var isArrayBufferView = function isArrayBufferView(obj) {
30639
        if (ArrayBuffer.isView === 'function') {
30640
            return ArrayBuffer.isView(obj);
30641
        }
30642
        return obj && obj.buffer instanceof ArrayBuffer;
30643
    };
30644
    var isTypedArray = function isTypedArray(obj) {
30645
        return isArrayBufferView(obj);
30646
    };
30647
    var toUint8 = function toUint8(bytes) {
30648
        if (bytes instanceof Uint8Array) {
30649
            return bytes;
30650
        }
30651
        if (!Array.isArray(bytes) && !isTypedArray(bytes) && !(bytes instanceof ArrayBuffer)) {
30652
            // any non-number or NaN leads to empty uint8array
30653
            // eslint-disable-next-line
30654
            if (typeof bytes !== 'number' || typeof bytes === 'number' && bytes !== bytes) {
30655
                bytes = 0;
30656
            } else {
30657
                bytes = [bytes];
30658
            }
30659
        }
30660
        return new Uint8Array(bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0);
30661
    };
30662
    var BigInt = window.BigInt || Number;
30663
    var BYTE_TABLE = [BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')];
30664
    (function () {
30665
        var a = new Uint16Array([0xFFCC]);
30666
        var b = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
30667
        if (b[0] === 0xFF) {
30668
            return 'big';
30669
        }
30670
        if (b[0] === 0xCC) {
30671
            return 'little';
30672
        }
30673
        return 'unknown';
30674
    })();
30675
    var bytesToNumber = function bytesToNumber(bytes, _temp) {
30676
        var _ref = _temp === void 0 ? {} : _temp,
30677
            _ref$signed = _ref.signed,
30678
            signed = _ref$signed === void 0 ? false : _ref$signed,
30679
            _ref$le = _ref.le,
30680
            le = _ref$le === void 0 ? false : _ref$le;
30681
        bytes = toUint8(bytes);
30682
        var fn = le ? 'reduce' : 'reduceRight';
30683
        var obj = bytes[fn] ? bytes[fn] : Array.prototype[fn];
30684
        var number = obj.call(bytes, function (total, byte, i) {
30685
            var exponent = le ? i : Math.abs(i + 1 - bytes.length);
30686
            return total + BigInt(byte) * BYTE_TABLE[exponent];
30687
        }, BigInt(0));
30688
        if (signed) {
30689
            var max = BYTE_TABLE[bytes.length] / BigInt(2) - BigInt(1);
30690
            number = BigInt(number);
30691
            if (number > max) {
30692
                number -= max;
30693
                number -= max;
30694
                number -= BigInt(2);
30695
            }
30696
        }
30697
        return Number(number);
30698
    };
30699
    var numberToBytes = function numberToBytes(number, _temp2) {
30700
        var _ref2 = _temp2 === void 0 ? {} : _temp2,
30701
            _ref2$le = _ref2.le,
30702
            le = _ref2$le === void 0 ? false : _ref2$le;
30703
 
30704
        // eslint-disable-next-line
30705
        if (typeof number !== 'bigint' && typeof number !== 'number' || typeof number === 'number' && number !== number) {
30706
            number = 0;
30707
        }
30708
        number = BigInt(number);
30709
        var byteCount = countBytes(number);
30710
        var bytes = new Uint8Array(new ArrayBuffer(byteCount));
30711
        for (var i = 0; i < byteCount; i++) {
30712
            var byteIndex = le ? i : Math.abs(i + 1 - bytes.length);
30713
            bytes[byteIndex] = Number(number / BYTE_TABLE[i] & BigInt(0xFF));
30714
            if (number < 0) {
30715
                bytes[byteIndex] = Math.abs(~bytes[byteIndex]);
30716
                bytes[byteIndex] -= i === 0 ? 1 : 2;
30717
            }
30718
        }
30719
        return bytes;
30720
    };
30721
    var stringToBytes = function stringToBytes(string, stringIsBytes) {
30722
        if (typeof string !== 'string' && string && typeof string.toString === 'function') {
30723
            string = string.toString();
30724
        }
30725
        if (typeof string !== 'string') {
30726
            return new Uint8Array();
30727
        } // If the string already is bytes, we don't have to do this
30728
        // otherwise we do this so that we split multi length characters
30729
        // into individual bytes
30730
 
30731
        if (!stringIsBytes) {
30732
            string = unescape(encodeURIComponent(string));
30733
        }
30734
        var view = new Uint8Array(string.length);
30735
        for (var i = 0; i < string.length; i++) {
30736
            view[i] = string.charCodeAt(i);
30737
        }
30738
        return view;
30739
    };
30740
    var concatTypedArrays = function concatTypedArrays() {
30741
        for (var _len = arguments.length, buffers = new Array(_len), _key = 0; _key < _len; _key++) {
30742
            buffers[_key] = arguments[_key];
30743
        }
30744
        buffers = buffers.filter(function (b) {
30745
            return b && (b.byteLength || b.length) && typeof b !== 'string';
30746
        });
30747
        if (buffers.length <= 1) {
30748
            // for 0 length we will return empty uint8
30749
            // for 1 length we return the first uint8
30750
            return toUint8(buffers[0]);
30751
        }
30752
        var totalLen = buffers.reduce(function (total, buf, i) {
30753
            return total + (buf.byteLength || buf.length);
30754
        }, 0);
30755
        var tempBuffer = new Uint8Array(totalLen);
30756
        var offset = 0;
30757
        buffers.forEach(function (buf) {
30758
            buf = toUint8(buf);
30759
            tempBuffer.set(buf, offset);
30760
            offset += buf.byteLength;
30761
        });
30762
        return tempBuffer;
30763
    };
30764
    /**
30765
     * Check if the bytes "b" are contained within bytes "a".
30766
     *
30767
     * @param {Uint8Array|Array} a
30768
     *        Bytes to check in
30769
     *
30770
     * @param {Uint8Array|Array} b
30771
     *        Bytes to check for
30772
     *
30773
     * @param {Object} options
30774
     *        options
30775
     *
30776
     * @param {Array|Uint8Array} [offset=0]
30777
     *        offset to use when looking at bytes in a
30778
     *
30779
     * @param {Array|Uint8Array} [mask=[]]
30780
     *        mask to use on bytes before comparison.
30781
     *
30782
     * @return {boolean}
30783
     *         If all bytes in b are inside of a, taking into account
30784
     *         bit masks.
30785
     */
30786
 
30787
    var bytesMatch = function bytesMatch(a, b, _temp3) {
30788
        var _ref3 = _temp3 === void 0 ? {} : _temp3,
30789
            _ref3$offset = _ref3.offset,
30790
            offset = _ref3$offset === void 0 ? 0 : _ref3$offset,
30791
            _ref3$mask = _ref3.mask,
30792
            mask = _ref3$mask === void 0 ? [] : _ref3$mask;
30793
        a = toUint8(a);
30794
        b = toUint8(b); // ie 11 does not support uint8 every
30795
 
30796
        var fn = b.every ? b.every : Array.prototype.every;
30797
        return b.length && a.length - offset >= b.length &&
30798
            // ie 11 doesn't support every on uin8
30799
            fn.call(b, function (bByte, i) {
30800
                var aByte = mask[i] ? mask[i] & a[offset + i] : a[offset + i];
30801
                return bByte === aByte;
30802
            });
30803
    };
30804
 
30805
    /**
30806
     * Loops through all supported media groups in master and calls the provided
30807
     * callback for each group
30808
     *
30809
     * @param {Object} master
30810
     *        The parsed master manifest object
30811
     * @param {string[]} groups
30812
     *        The media groups to call the callback for
30813
     * @param {Function} callback
30814
     *        Callback to call for each media group
30815
     */
30816
    var forEachMediaGroup$1 = function forEachMediaGroup(master, groups, callback) {
30817
        groups.forEach(function (mediaType) {
30818
            for (var groupKey in master.mediaGroups[mediaType]) {
30819
                for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
30820
                    var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
30821
                    callback(mediaProperties, mediaType, groupKey, labelKey);
30822
                }
30823
            }
30824
        });
30825
    };
30826
 
30827
    var atob = function atob(s) {
30828
        return window.atob ? window.atob(s) : Buffer.from(s, 'base64').toString('binary');
30829
    };
30830
    function decodeB64ToUint8Array(b64Text) {
30831
        var decodedString = atob(b64Text);
30832
        var array = new Uint8Array(decodedString.length);
30833
        for (var i = 0; i < decodedString.length; i++) {
30834
            array[i] = decodedString.charCodeAt(i);
30835
        }
30836
        return array;
30837
    }
30838
 
30839
    /**
30840
     * Ponyfill for `Array.prototype.find` which is only available in ES6 runtimes.
30841
     *
30842
     * Works with anything that has a `length` property and index access properties, including NodeList.
30843
     *
30844
     * @template {unknown} T
30845
     * @param {Array<T> | ({length:number, [number]: T})} list
30846
     * @param {function (item: T, index: number, list:Array<T> | ({length:number, [number]: T})):boolean} predicate
30847
     * @param {Partial<Pick<ArrayConstructor['prototype'], 'find'>>?} ac `Array.prototype` by default,
30848
     * 				allows injecting a custom implementation in tests
30849
     * @returns {T | undefined}
30850
     *
30851
     * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
30852
     * @see https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.find
30853
     */
30854
    function find$1(list, predicate, ac) {
30855
        if (ac === undefined) {
30856
            ac = Array.prototype;
30857
        }
30858
        if (list && typeof ac.find === 'function') {
30859
            return ac.find.call(list, predicate);
30860
        }
30861
        for (var i = 0; i < list.length; i++) {
30862
            if (Object.prototype.hasOwnProperty.call(list, i)) {
30863
                var item = list[i];
30864
                if (predicate.call(undefined, item, i, list)) {
30865
                    return item;
30866
                }
30867
            }
30868
        }
30869
    }
30870
 
30871
    /**
30872
     * "Shallow freezes" an object to render it immutable.
30873
     * Uses `Object.freeze` if available,
30874
     * otherwise the immutability is only in the type.
30875
     *
30876
     * Is used to create "enum like" objects.
30877
     *
30878
     * @template T
30879
     * @param {T} object the object to freeze
30880
     * @param {Pick<ObjectConstructor, 'freeze'> = Object} oc `Object` by default,
30881
     * 				allows to inject custom object constructor for tests
30882
     * @returns {Readonly<T>}
30883
     *
30884
     * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
30885
     */
30886
    function freeze(object, oc) {
30887
        if (oc === undefined) {
30888
            oc = Object;
30889
        }
30890
        return oc && typeof oc.freeze === 'function' ? oc.freeze(object) : object;
30891
    }
30892
 
30893
    /**
30894
     * Since we can not rely on `Object.assign` we provide a simplified version
30895
     * that is sufficient for our needs.
30896
     *
30897
     * @param {Object} target
30898
     * @param {Object | null | undefined} source
30899
     *
30900
     * @returns {Object} target
30901
     * @throws TypeError if target is not an object
30902
     *
30903
     * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
30904
     * @see https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.assign
30905
     */
30906
    function assign(target, source) {
30907
        if (target === null || typeof target !== 'object') {
30908
            throw new TypeError('target is not an object');
30909
        }
30910
        for (var key in source) {
30911
            if (Object.prototype.hasOwnProperty.call(source, key)) {
30912
                target[key] = source[key];
30913
            }
30914
        }
30915
        return target;
30916
    }
30917
 
30918
    /**
30919
     * All mime types that are allowed as input to `DOMParser.parseFromString`
30920
     *
30921
     * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02 MDN
30922
     * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#domparsersupportedtype WHATWG HTML Spec
30923
     * @see DOMParser.prototype.parseFromString
30924
     */
30925
    var MIME_TYPE = freeze({
30926
        /**
30927
         * `text/html`, the only mime type that triggers treating an XML document as HTML.
30928
         *
30929
         * @see DOMParser.SupportedType.isHTML
30930
         * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration
30931
         * @see https://en.wikipedia.org/wiki/HTML Wikipedia
30932
         * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN
30933
         * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring WHATWG HTML Spec
30934
         */
30935
        HTML: 'text/html',
30936
        /**
30937
         * Helper method to check a mime type if it indicates an HTML document
30938
         *
30939
         * @param {string} [value]
30940
         * @returns {boolean}
30941
         *
30942
         * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration
30943
         * @see https://en.wikipedia.org/wiki/HTML Wikipedia
30944
         * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN
30945
         * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring 	 */
30946
        isHTML: function (value) {
30947
            return value === MIME_TYPE.HTML;
30948
        },
30949
        /**
30950
         * `application/xml`, the standard mime type for XML documents.
30951
         *
30952
         * @see https://www.iana.org/assignments/media-types/application/xml IANA MimeType registration
30953
         * @see https://tools.ietf.org/html/rfc7303#section-9.1 RFC 7303
30954
         * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia
30955
         */
30956
        XML_APPLICATION: 'application/xml',
30957
        /**
30958
         * `text/html`, an alias for `application/xml`.
30959
         *
30960
         * @see https://tools.ietf.org/html/rfc7303#section-9.2 RFC 7303
30961
         * @see https://www.iana.org/assignments/media-types/text/xml IANA MimeType registration
30962
         * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia
30963
         */
30964
        XML_TEXT: 'text/xml',
30965
        /**
30966
         * `application/xhtml+xml`, indicates an XML document that has the default HTML namespace,
30967
         * but is parsed as an XML document.
30968
         *
30969
         * @see https://www.iana.org/assignments/media-types/application/xhtml+xml IANA MimeType registration
30970
         * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument WHATWG DOM Spec
30971
         * @see https://en.wikipedia.org/wiki/XHTML Wikipedia
30972
         */
30973
        XML_XHTML_APPLICATION: 'application/xhtml+xml',
30974
        /**
30975
         * `image/svg+xml`,
30976
         *
30977
         * @see https://www.iana.org/assignments/media-types/image/svg+xml IANA MimeType registration
30978
         * @see https://www.w3.org/TR/SVG11/ W3C SVG 1.1
30979
         * @see https://en.wikipedia.org/wiki/Scalable_Vector_Graphics Wikipedia
30980
         */
30981
        XML_SVG_IMAGE: 'image/svg+xml'
30982
    });
30983
 
30984
    /**
30985
     * Namespaces that are used in this code base.
30986
     *
30987
     * @see http://www.w3.org/TR/REC-xml-names
30988
     */
30989
    var NAMESPACE$3 = freeze({
30990
        /**
30991
         * The XHTML namespace.
30992
         *
30993
         * @see http://www.w3.org/1999/xhtml
30994
         */
30995
        HTML: 'http://www.w3.org/1999/xhtml',
30996
        /**
30997
         * Checks if `uri` equals `NAMESPACE.HTML`.
30998
         *
30999
         * @param {string} [uri]
31000
         *
31001
         * @see NAMESPACE.HTML
31002
         */
31003
        isHTML: function (uri) {
31004
            return uri === NAMESPACE$3.HTML;
31005
        },
31006
        /**
31007
         * The SVG namespace.
31008
         *
31009
         * @see http://www.w3.org/2000/svg
31010
         */
31011
        SVG: 'http://www.w3.org/2000/svg',
31012
        /**
31013
         * The `xml:` namespace.
31014
         *
31015
         * @see http://www.w3.org/XML/1998/namespace
31016
         */
31017
        XML: 'http://www.w3.org/XML/1998/namespace',
31018
        /**
31019
         * The `xmlns:` namespace
31020
         *
31021
         * @see https://www.w3.org/2000/xmlns/
31022
         */
31023
        XMLNS: 'http://www.w3.org/2000/xmlns/'
31024
    });
31025
    var assign_1 = assign;
31026
    var find_1 = find$1;
31027
    var freeze_1 = freeze;
31028
    var MIME_TYPE_1 = MIME_TYPE;
31029
    var NAMESPACE_1 = NAMESPACE$3;
31030
    var conventions = {
31031
        assign: assign_1,
31032
        find: find_1,
31033
        freeze: freeze_1,
31034
        MIME_TYPE: MIME_TYPE_1,
31035
        NAMESPACE: NAMESPACE_1
31036
    };
31037
 
31038
    var find = conventions.find;
31039
    var NAMESPACE$2 = conventions.NAMESPACE;
31040
 
31041
    /**
31042
     * A prerequisite for `[].filter`, to drop elements that are empty
31043
     * @param {string} input
31044
     * @returns {boolean}
31045
     */
31046
    function notEmptyString(input) {
31047
        return input !== '';
31048
    }
31049
    /**
31050
     * @see https://infra.spec.whatwg.org/#split-on-ascii-whitespace
31051
     * @see https://infra.spec.whatwg.org/#ascii-whitespace
31052
     *
31053
     * @param {string} input
31054
     * @returns {string[]} (can be empty)
31055
     */
31056
    function splitOnASCIIWhitespace(input) {
31057
        // U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, U+0020 SPACE
31058
        return input ? input.split(/[\t\n\f\r ]+/).filter(notEmptyString) : [];
31059
    }
31060
 
31061
    /**
31062
     * Adds element as a key to current if it is not already present.
31063
     *
31064
     * @param {Record<string, boolean | undefined>} current
31065
     * @param {string} element
31066
     * @returns {Record<string, boolean | undefined>}
31067
     */
31068
    function orderedSetReducer(current, element) {
31069
        if (!current.hasOwnProperty(element)) {
31070
            current[element] = true;
31071
        }
31072
        return current;
31073
    }
31074
 
31075
    /**
31076
     * @see https://infra.spec.whatwg.org/#ordered-set
31077
     * @param {string} input
31078
     * @returns {string[]}
31079
     */
31080
    function toOrderedSet(input) {
31081
        if (!input) return [];
31082
        var list = splitOnASCIIWhitespace(input);
31083
        return Object.keys(list.reduce(orderedSetReducer, {}));
31084
    }
31085
 
31086
    /**
31087
     * Uses `list.indexOf` to implement something like `Array.prototype.includes`,
31088
     * which we can not rely on being available.
31089
     *
31090
     * @param {any[]} list
31091
     * @returns {function(any): boolean}
31092
     */
31093
    function arrayIncludes(list) {
31094
        return function (element) {
31095
            return list && list.indexOf(element) !== -1;
31096
        };
31097
    }
31098
    function copy(src, dest) {
31099
        for (var p in src) {
31100
            if (Object.prototype.hasOwnProperty.call(src, p)) {
31101
                dest[p] = src[p];
31102
            }
31103
        }
31104
    }
31105
 
31106
    /**
31107
     ^\w+\.prototype\.([_\w]+)\s*=\s*((?:.*\{\s*?[\r\n][\s\S]*?^})|\S.*?(?=[;\r\n]));?
31108
     ^\w+\.prototype\.([_\w]+)\s*=\s*(\S.*?(?=[;\r\n]));?
31109
     */
31110
    function _extends(Class, Super) {
31111
        var pt = Class.prototype;
31112
        if (!(pt instanceof Super)) {
31113
            function t() {}
31114
            t.prototype = Super.prototype;
31115
            t = new t();
31116
            copy(pt, t);
31117
            Class.prototype = pt = t;
31118
        }
31119
        if (pt.constructor != Class) {
31120
            if (typeof Class != 'function') {
31121
                console.error("unknown Class:" + Class);
31122
            }
31123
            pt.constructor = Class;
31124
        }
31125
    }
31126
 
31127
    // Node Types
31128
    var NodeType = {};
31129
    var ELEMENT_NODE = NodeType.ELEMENT_NODE = 1;
31130
    var ATTRIBUTE_NODE = NodeType.ATTRIBUTE_NODE = 2;
31131
    var TEXT_NODE = NodeType.TEXT_NODE = 3;
31132
    var CDATA_SECTION_NODE = NodeType.CDATA_SECTION_NODE = 4;
31133
    var ENTITY_REFERENCE_NODE = NodeType.ENTITY_REFERENCE_NODE = 5;
31134
    var ENTITY_NODE = NodeType.ENTITY_NODE = 6;
31135
    var PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE = 7;
31136
    var COMMENT_NODE = NodeType.COMMENT_NODE = 8;
31137
    var DOCUMENT_NODE = NodeType.DOCUMENT_NODE = 9;
31138
    var DOCUMENT_TYPE_NODE = NodeType.DOCUMENT_TYPE_NODE = 10;
31139
    var DOCUMENT_FRAGMENT_NODE = NodeType.DOCUMENT_FRAGMENT_NODE = 11;
31140
    var NOTATION_NODE = NodeType.NOTATION_NODE = 12;
31141
 
31142
    // ExceptionCode
31143
    var ExceptionCode = {};
31144
    var ExceptionMessage = {};
31145
    ExceptionCode.INDEX_SIZE_ERR = (ExceptionMessage[1] = "Index size error", 1);
31146
    ExceptionCode.DOMSTRING_SIZE_ERR = (ExceptionMessage[2] = "DOMString size error", 2);
31147
    var HIERARCHY_REQUEST_ERR = ExceptionCode.HIERARCHY_REQUEST_ERR = (ExceptionMessage[3] = "Hierarchy request error", 3);
31148
    ExceptionCode.WRONG_DOCUMENT_ERR = (ExceptionMessage[4] = "Wrong document", 4);
31149
    ExceptionCode.INVALID_CHARACTER_ERR = (ExceptionMessage[5] = "Invalid character", 5);
31150
    ExceptionCode.NO_DATA_ALLOWED_ERR = (ExceptionMessage[6] = "No data allowed", 6);
31151
    ExceptionCode.NO_MODIFICATION_ALLOWED_ERR = (ExceptionMessage[7] = "No modification allowed", 7);
31152
    var NOT_FOUND_ERR = ExceptionCode.NOT_FOUND_ERR = (ExceptionMessage[8] = "Not found", 8);
31153
    ExceptionCode.NOT_SUPPORTED_ERR = (ExceptionMessage[9] = "Not supported", 9);
31154
    var INUSE_ATTRIBUTE_ERR = ExceptionCode.INUSE_ATTRIBUTE_ERR = (ExceptionMessage[10] = "Attribute in use", 10);
31155
    //level2
31156
    ExceptionCode.INVALID_STATE_ERR = (ExceptionMessage[11] = "Invalid state", 11);
31157
    ExceptionCode.SYNTAX_ERR = (ExceptionMessage[12] = "Syntax error", 12);
31158
    ExceptionCode.INVALID_MODIFICATION_ERR = (ExceptionMessage[13] = "Invalid modification", 13);
31159
    ExceptionCode.NAMESPACE_ERR = (ExceptionMessage[14] = "Invalid namespace", 14);
31160
    ExceptionCode.INVALID_ACCESS_ERR = (ExceptionMessage[15] = "Invalid access", 15);
31161
 
31162
    /**
31163
     * DOM Level 2
31164
     * Object DOMException
31165
     * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html
31166
     * @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html
31167
     */
31168
    function DOMException(code, message) {
31169
        if (message instanceof Error) {
31170
            var error = message;
31171
        } else {
31172
            error = this;
31173
            Error.call(this, ExceptionMessage[code]);
31174
            this.message = ExceptionMessage[code];
31175
            if (Error.captureStackTrace) Error.captureStackTrace(this, DOMException);
31176
        }
31177
        error.code = code;
31178
        if (message) this.message = this.message + ": " + message;
31179
        return error;
31180
    }
31181
    DOMException.prototype = Error.prototype;
31182
    copy(ExceptionCode, DOMException);
31183
 
31184
    /**
31185
     * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177
31186
     * The NodeList interface provides the abstraction of an ordered collection of nodes, without defining or constraining how this collection is implemented. NodeList objects in the DOM are live.
31187
     * The items in the NodeList are accessible via an integral index, starting from 0.
31188
     */
31189
    function NodeList() {}
31190
    NodeList.prototype = {
31191
        /**
31192
         * The number of nodes in the list. The range of valid child node indices is 0 to length-1 inclusive.
31193
         * @standard level1
31194
         */
31195
        length: 0,
31196
        /**
31197
         * Returns the indexth item in the collection. If index is greater than or equal to the number of nodes in the list, this returns null.
31198
         * @standard level1
31199
         * @param index  unsigned long
31200
         *   Index into the collection.
31201
         * @return Node
31202
         * 	The node at the indexth position in the NodeList, or null if that is not a valid index.
31203
         */
31204
        item: function (index) {
31205
            return index >= 0 && index < this.length ? this[index] : null;
31206
        },
31207
        toString: function (isHTML, nodeFilter) {
31208
            for (var buf = [], i = 0; i < this.length; i++) {
31209
                serializeToString(this[i], buf, isHTML, nodeFilter);
31210
            }
31211
            return buf.join('');
31212
        },
31213
        /**
31214
         * @private
31215
         * @param {function (Node):boolean} predicate
31216
         * @returns {Node[]}
31217
         */
31218
        filter: function (predicate) {
31219
            return Array.prototype.filter.call(this, predicate);
31220
        },
31221
        /**
31222
         * @private
31223
         * @param {Node} item
31224
         * @returns {number}
31225
         */
31226
        indexOf: function (item) {
31227
            return Array.prototype.indexOf.call(this, item);
31228
        }
31229
    };
31230
    function LiveNodeList(node, refresh) {
31231
        this._node = node;
31232
        this._refresh = refresh;
31233
        _updateLiveList(this);
31234
    }
31235
    function _updateLiveList(list) {
31236
        var inc = list._node._inc || list._node.ownerDocument._inc;
31237
        if (list._inc !== inc) {
31238
            var ls = list._refresh(list._node);
31239
            __set__(list, 'length', ls.length);
31240
            if (!list.$$length || ls.length < list.$$length) {
31241
                for (var i = ls.length; (i in list); i++) {
31242
                    if (Object.prototype.hasOwnProperty.call(list, i)) {
31243
                        delete list[i];
31244
                    }
31245
                }
31246
            }
31247
            copy(ls, list);
31248
            list._inc = inc;
31249
        }
31250
    }
31251
    LiveNodeList.prototype.item = function (i) {
31252
        _updateLiveList(this);
31253
        return this[i] || null;
31254
    };
31255
    _extends(LiveNodeList, NodeList);
31256
 
31257
    /**
31258
     * Objects implementing the NamedNodeMap interface are used
31259
     * to represent collections of nodes that can be accessed by name.
31260
     * Note that NamedNodeMap does not inherit from NodeList;
31261
     * NamedNodeMaps are not maintained in any particular order.
31262
     * Objects contained in an object implementing NamedNodeMap may also be accessed by an ordinal index,
31263
     * but this is simply to allow convenient enumeration of the contents of a NamedNodeMap,
31264
     * and does not imply that the DOM specifies an order to these Nodes.
31265
     * NamedNodeMap objects in the DOM are live.
31266
     * used for attributes or DocumentType entities
31267
     */
31268
    function NamedNodeMap() {}
31269
    function _findNodeIndex(list, node) {
31270
        var i = list.length;
31271
        while (i--) {
31272
            if (list[i] === node) {
31273
                return i;
31274
            }
31275
        }
31276
    }
31277
    function _addNamedNode(el, list, newAttr, oldAttr) {
31278
        if (oldAttr) {
31279
            list[_findNodeIndex(list, oldAttr)] = newAttr;
31280
        } else {
31281
            list[list.length++] = newAttr;
31282
        }
31283
        if (el) {
31284
            newAttr.ownerElement = el;
31285
            var doc = el.ownerDocument;
31286
            if (doc) {
31287
                oldAttr && _onRemoveAttribute(doc, el, oldAttr);
31288
                _onAddAttribute(doc, el, newAttr);
31289
            }
31290
        }
31291
    }
31292
    function _removeNamedNode(el, list, attr) {
31293
        //console.log('remove attr:'+attr)
31294
        var i = _findNodeIndex(list, attr);
31295
        if (i >= 0) {
31296
            var lastIndex = list.length - 1;
31297
            while (i < lastIndex) {
31298
                list[i] = list[++i];
31299
            }
31300
            list.length = lastIndex;
31301
            if (el) {
31302
                var doc = el.ownerDocument;
31303
                if (doc) {
31304
                    _onRemoveAttribute(doc, el, attr);
31305
                    attr.ownerElement = null;
31306
                }
31307
            }
31308
        } else {
31309
            throw new DOMException(NOT_FOUND_ERR, new Error(el.tagName + '@' + attr));
31310
        }
31311
    }
31312
    NamedNodeMap.prototype = {
31313
        length: 0,
31314
        item: NodeList.prototype.item,
31315
        getNamedItem: function (key) {
31316
            //		if(key.indexOf(':')>0 || key == 'xmlns'){
31317
            //			return null;
31318
            //		}
31319
            //console.log()
31320
            var i = this.length;
31321
            while (i--) {
31322
                var attr = this[i];
31323
                //console.log(attr.nodeName,key)
31324
                if (attr.nodeName == key) {
31325
                    return attr;
31326
                }
31327
            }
31328
        },
31329
        setNamedItem: function (attr) {
31330
            var el = attr.ownerElement;
31331
            if (el && el != this._ownerElement) {
31332
                throw new DOMException(INUSE_ATTRIBUTE_ERR);
31333
            }
31334
            var oldAttr = this.getNamedItem(attr.nodeName);
31335
            _addNamedNode(this._ownerElement, this, attr, oldAttr);
31336
            return oldAttr;
31337
        },
31338
        /* returns Node */
31339
        setNamedItemNS: function (attr) {
31340
            // raises: WRONG_DOCUMENT_ERR,NO_MODIFICATION_ALLOWED_ERR,INUSE_ATTRIBUTE_ERR
31341
            var el = attr.ownerElement,
31342
                oldAttr;
31343
            if (el && el != this._ownerElement) {
31344
                throw new DOMException(INUSE_ATTRIBUTE_ERR);
31345
            }
31346
            oldAttr = this.getNamedItemNS(attr.namespaceURI, attr.localName);
31347
            _addNamedNode(this._ownerElement, this, attr, oldAttr);
31348
            return oldAttr;
31349
        },
31350
        /* returns Node */
31351
        removeNamedItem: function (key) {
31352
            var attr = this.getNamedItem(key);
31353
            _removeNamedNode(this._ownerElement, this, attr);
31354
            return attr;
31355
        },
31356
        // raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR
31357
 
31358
        //for level2
31359
        removeNamedItemNS: function (namespaceURI, localName) {
31360
            var attr = this.getNamedItemNS(namespaceURI, localName);
31361
            _removeNamedNode(this._ownerElement, this, attr);
31362
            return attr;
31363
        },
31364
        getNamedItemNS: function (namespaceURI, localName) {
31365
            var i = this.length;
31366
            while (i--) {
31367
                var node = this[i];
31368
                if (node.localName == localName && node.namespaceURI == namespaceURI) {
31369
                    return node;
31370
                }
31371
            }
31372
            return null;
31373
        }
31374
    };
31375
 
31376
    /**
31377
     * The DOMImplementation interface represents an object providing methods
31378
     * which are not dependent on any particular document.
31379
     * Such an object is returned by the `Document.implementation` property.
31380
     *
31381
     * __The individual methods describe the differences compared to the specs.__
31382
     *
31383
     * @constructor
31384
     *
31385
     * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation MDN
31386
     * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490 DOM Level 1 Core (Initial)
31387
     * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-102161490 DOM Level 2 Core
31388
     * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-102161490 DOM Level 3 Core
31389
     * @see https://dom.spec.whatwg.org/#domimplementation DOM Living Standard
31390
     */
31391
    function DOMImplementation$1() {}
31392
    DOMImplementation$1.prototype = {
31393
        /**
31394
         * The DOMImplementation.hasFeature() method returns a Boolean flag indicating if a given feature is supported.
31395
         * The different implementations fairly diverged in what kind of features were reported.
31396
         * The latest version of the spec settled to force this method to always return true, where the functionality was accurate and in use.
31397
         *
31398
         * @deprecated It is deprecated and modern browsers return true in all cases.
31399
         *
31400
         * @param {string} feature
31401
         * @param {string} [version]
31402
         * @returns {boolean} always true
31403
         *
31404
         * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/hasFeature MDN
31405
         * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-5CED94D7 DOM Level 1 Core
31406
         * @see https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature DOM Living Standard
31407
         */
31408
        hasFeature: function (feature, version) {
31409
            return true;
31410
        },
31411
        /**
31412
         * Creates an XML Document object of the specified type with its document element.
31413
         *
31414
         * __It behaves slightly different from the description in the living standard__:
31415
         * - There is no interface/class `XMLDocument`, it returns a `Document` instance.
31416
         * - `contentType`, `encoding`, `mode`, `origin`, `url` fields are currently not declared.
31417
         * - this implementation is not validating names or qualified names
31418
         *   (when parsing XML strings, the SAX parser takes care of that)
31419
         *
31420
         * @param {string|null} namespaceURI
31421
         * @param {string} qualifiedName
31422
         * @param {DocumentType=null} doctype
31423
         * @returns {Document}
31424
         *
31425
         * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocument MDN
31426
         * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocument DOM Level 2 Core (initial)
31427
         * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument  DOM Level 2 Core
31428
         *
31429
         * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract
31430
         * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names
31431
         * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names
31432
         */
31433
        createDocument: function (namespaceURI, qualifiedName, doctype) {
31434
            var doc = new Document();
31435
            doc.implementation = this;
31436
            doc.childNodes = new NodeList();
31437
            doc.doctype = doctype || null;
31438
            if (doctype) {
31439
                doc.appendChild(doctype);
31440
            }
31441
            if (qualifiedName) {
31442
                var root = doc.createElementNS(namespaceURI, qualifiedName);
31443
                doc.appendChild(root);
31444
            }
31445
            return doc;
31446
        },
31447
        /**
31448
         * Returns a doctype, with the given `qualifiedName`, `publicId`, and `systemId`.
31449
         *
31450
         * __This behavior is slightly different from the in the specs__:
31451
         * - this implementation is not validating names or qualified names
31452
         *   (when parsing XML strings, the SAX parser takes care of that)
31453
         *
31454
         * @param {string} qualifiedName
31455
         * @param {string} [publicId]
31456
         * @param {string} [systemId]
31457
         * @returns {DocumentType} which can either be used with `DOMImplementation.createDocument` upon document creation
31458
         * 				  or can be put into the document via methods like `Node.insertBefore()` or `Node.replaceChild()`
31459
         *
31460
         * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocumentType MDN
31461
         * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocType DOM Level 2 Core
31462
         * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype DOM Living Standard
31463
         *
31464
         * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract
31465
         * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names
31466
         * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names
31467
         */
31468
        createDocumentType: function (qualifiedName, publicId, systemId) {
31469
            var node = new DocumentType();
31470
            node.name = qualifiedName;
31471
            node.nodeName = qualifiedName;
31472
            node.publicId = publicId || '';
31473
            node.systemId = systemId || '';
31474
            return node;
31475
        }
31476
    };
31477
 
31478
    /**
31479
     * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247
31480
     */
31481
 
31482
    function Node() {}
31483
    Node.prototype = {
31484
        firstChild: null,
31485
        lastChild: null,
31486
        previousSibling: null,
31487
        nextSibling: null,
31488
        attributes: null,
31489
        parentNode: null,
31490
        childNodes: null,
31491
        ownerDocument: null,
31492
        nodeValue: null,
31493
        namespaceURI: null,
31494
        prefix: null,
31495
        localName: null,
31496
        // Modified in DOM Level 2:
31497
        insertBefore: function (newChild, refChild) {
31498
            //raises
31499
            return _insertBefore(this, newChild, refChild);
31500
        },
31501
        replaceChild: function (newChild, oldChild) {
31502
            //raises
31503
            _insertBefore(this, newChild, oldChild, assertPreReplacementValidityInDocument);
31504
            if (oldChild) {
31505
                this.removeChild(oldChild);
31506
            }
31507
        },
31508
        removeChild: function (oldChild) {
31509
            return _removeChild(this, oldChild);
31510
        },
31511
        appendChild: function (newChild) {
31512
            return this.insertBefore(newChild, null);
31513
        },
31514
        hasChildNodes: function () {
31515
            return this.firstChild != null;
31516
        },
31517
        cloneNode: function (deep) {
31518
            return cloneNode(this.ownerDocument || this, this, deep);
31519
        },
31520
        // Modified in DOM Level 2:
31521
        normalize: function () {
31522
            var child = this.firstChild;
31523
            while (child) {
31524
                var next = child.nextSibling;
31525
                if (next && next.nodeType == TEXT_NODE && child.nodeType == TEXT_NODE) {
31526
                    this.removeChild(next);
31527
                    child.appendData(next.data);
31528
                } else {
31529
                    child.normalize();
31530
                    child = next;
31531
                }
31532
            }
31533
        },
31534
        // Introduced in DOM Level 2:
31535
        isSupported: function (feature, version) {
31536
            return this.ownerDocument.implementation.hasFeature(feature, version);
31537
        },
31538
        // Introduced in DOM Level 2:
31539
        hasAttributes: function () {
31540
            return this.attributes.length > 0;
31541
        },
31542
        /**
31543
         * Look up the prefix associated to the given namespace URI, starting from this node.
31544
         * **The default namespace declarations are ignored by this method.**
31545
         * See Namespace Prefix Lookup for details on the algorithm used by this method.
31546
         *
31547
         * _Note: The implementation seems to be incomplete when compared to the algorithm described in the specs._
31548
         *
31549
         * @param {string | null} namespaceURI
31550
         * @returns {string | null}
31551
         * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespacePrefix
31552
         * @see https://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#lookupNamespacePrefixAlgo
31553
         * @see https://dom.spec.whatwg.org/#dom-node-lookupprefix
31554
         * @see https://github.com/xmldom/xmldom/issues/322
31555
         */
31556
        lookupPrefix: function (namespaceURI) {
31557
            var el = this;
31558
            while (el) {
31559
                var map = el._nsMap;
31560
                //console.dir(map)
31561
                if (map) {
31562
                    for (var n in map) {
31563
                        if (Object.prototype.hasOwnProperty.call(map, n) && map[n] === namespaceURI) {
31564
                            return n;
31565
                        }
31566
                    }
31567
                }
31568
                el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode;
31569
            }
31570
            return null;
31571
        },
31572
        // Introduced in DOM Level 3:
31573
        lookupNamespaceURI: function (prefix) {
31574
            var el = this;
31575
            while (el) {
31576
                var map = el._nsMap;
31577
                //console.dir(map)
31578
                if (map) {
31579
                    if (Object.prototype.hasOwnProperty.call(map, prefix)) {
31580
                        return map[prefix];
31581
                    }
31582
                }
31583
                el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode;
31584
            }
31585
            return null;
31586
        },
31587
        // Introduced in DOM Level 3:
31588
        isDefaultNamespace: function (namespaceURI) {
31589
            var prefix = this.lookupPrefix(namespaceURI);
31590
            return prefix == null;
31591
        }
31592
    };
31593
    function _xmlEncoder(c) {
31594
        return c == '<' && '&lt;' || c == '>' && '&gt;' || c == '&' && '&amp;' || c == '"' && '&quot;' || '&#' + c.charCodeAt() + ';';
31595
    }
31596
    copy(NodeType, Node);
31597
    copy(NodeType, Node.prototype);
31598
 
31599
    /**
31600
     * @param callback return true for continue,false for break
31601
     * @return boolean true: break visit;
31602
     */
31603
    function _visitNode(node, callback) {
31604
        if (callback(node)) {
31605
            return true;
31606
        }
31607
        if (node = node.firstChild) {
31608
            do {
31609
                if (_visitNode(node, callback)) {
31610
                    return true;
31611
                }
31612
            } while (node = node.nextSibling);
31613
        }
31614
    }
31615
    function Document() {
31616
        this.ownerDocument = this;
31617
    }
31618
    function _onAddAttribute(doc, el, newAttr) {
31619
        doc && doc._inc++;
31620
        var ns = newAttr.namespaceURI;
31621
        if (ns === NAMESPACE$2.XMLNS) {
31622
            //update namespace
31623
            el._nsMap[newAttr.prefix ? newAttr.localName : ''] = newAttr.value;
31624
        }
31625
    }
31626
    function _onRemoveAttribute(doc, el, newAttr, remove) {
31627
        doc && doc._inc++;
31628
        var ns = newAttr.namespaceURI;
31629
        if (ns === NAMESPACE$2.XMLNS) {
31630
            //update namespace
31631
            delete el._nsMap[newAttr.prefix ? newAttr.localName : ''];
31632
        }
31633
    }
31634
 
31635
    /**
31636
     * Updates `el.childNodes`, updating the indexed items and it's `length`.
31637
     * Passing `newChild` means it will be appended.
31638
     * Otherwise it's assumed that an item has been removed,
31639
     * and `el.firstNode` and it's `.nextSibling` are used
31640
     * to walk the current list of child nodes.
31641
     *
31642
     * @param {Document} doc
31643
     * @param {Node} el
31644
     * @param {Node} [newChild]
31645
     * @private
31646
     */
31647
    function _onUpdateChild(doc, el, newChild) {
31648
        if (doc && doc._inc) {
31649
            doc._inc++;
31650
            //update childNodes
31651
            var cs = el.childNodes;
31652
            if (newChild) {
31653
                cs[cs.length++] = newChild;
31654
            } else {
31655
                var child = el.firstChild;
31656
                var i = 0;
31657
                while (child) {
31658
                    cs[i++] = child;
31659
                    child = child.nextSibling;
31660
                }
31661
                cs.length = i;
31662
                delete cs[cs.length];
31663
            }
31664
        }
31665
    }
31666
 
31667
    /**
31668
     * Removes the connections between `parentNode` and `child`
31669
     * and any existing `child.previousSibling` or `child.nextSibling`.
31670
     *
31671
     * @see https://github.com/xmldom/xmldom/issues/135
31672
     * @see https://github.com/xmldom/xmldom/issues/145
31673
     *
31674
     * @param {Node} parentNode
31675
     * @param {Node} child
31676
     * @returns {Node} the child that was removed.
31677
     * @private
31678
     */
31679
    function _removeChild(parentNode, child) {
31680
        var previous = child.previousSibling;
31681
        var next = child.nextSibling;
31682
        if (previous) {
31683
            previous.nextSibling = next;
31684
        } else {
31685
            parentNode.firstChild = next;
31686
        }
31687
        if (next) {
31688
            next.previousSibling = previous;
31689
        } else {
31690
            parentNode.lastChild = previous;
31691
        }
31692
        child.parentNode = null;
31693
        child.previousSibling = null;
31694
        child.nextSibling = null;
31695
        _onUpdateChild(parentNode.ownerDocument, parentNode);
31696
        return child;
31697
    }
31698
 
31699
    /**
31700
     * Returns `true` if `node` can be a parent for insertion.
31701
     * @param {Node} node
31702
     * @returns {boolean}
31703
     */
31704
    function hasValidParentNodeType(node) {
31705
        return node && (node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.ELEMENT_NODE);
31706
    }
31707
 
31708
    /**
31709
     * Returns `true` if `node` can be inserted according to it's `nodeType`.
31710
     * @param {Node} node
31711
     * @returns {boolean}
31712
     */
31713
    function hasInsertableNodeType(node) {
31714
        return node && (isElementNode(node) || isTextNode(node) || isDocTypeNode(node) || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.COMMENT_NODE || node.nodeType === Node.PROCESSING_INSTRUCTION_NODE);
31715
    }
31716
 
31717
    /**
31718
     * Returns true if `node` is a DOCTYPE node
31719
     * @param {Node} node
31720
     * @returns {boolean}
31721
     */
31722
    function isDocTypeNode(node) {
31723
        return node && node.nodeType === Node.DOCUMENT_TYPE_NODE;
31724
    }
31725
 
31726
    /**
31727
     * Returns true if the node is an element
31728
     * @param {Node} node
31729
     * @returns {boolean}
31730
     */
31731
    function isElementNode(node) {
31732
        return node && node.nodeType === Node.ELEMENT_NODE;
31733
    }
31734
    /**
31735
     * Returns true if `node` is a text node
31736
     * @param {Node} node
31737
     * @returns {boolean}
31738
     */
31739
    function isTextNode(node) {
31740
        return node && node.nodeType === Node.TEXT_NODE;
31741
    }
31742
 
31743
    /**
31744
     * Check if en element node can be inserted before `child`, or at the end if child is falsy,
31745
     * according to the presence and position of a doctype node on the same level.
31746
     *
31747
     * @param {Document} doc The document node
31748
     * @param {Node} child the node that would become the nextSibling if the element would be inserted
31749
     * @returns {boolean} `true` if an element can be inserted before child
31750
     * @private
31751
     * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
31752
     */
31753
    function isElementInsertionPossible(doc, child) {
31754
        var parentChildNodes = doc.childNodes || [];
31755
        if (find(parentChildNodes, isElementNode) || isDocTypeNode(child)) {
31756
            return false;
31757
        }
31758
        var docTypeNode = find(parentChildNodes, isDocTypeNode);
31759
        return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
31760
    }
31761
 
31762
    /**
31763
     * Check if en element node can be inserted before `child`, or at the end if child is falsy,
31764
     * according to the presence and position of a doctype node on the same level.
31765
     *
31766
     * @param {Node} doc The document node
31767
     * @param {Node} child the node that would become the nextSibling if the element would be inserted
31768
     * @returns {boolean} `true` if an element can be inserted before child
31769
     * @private
31770
     * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
31771
     */
31772
    function isElementReplacementPossible(doc, child) {
31773
        var parentChildNodes = doc.childNodes || [];
31774
        function hasElementChildThatIsNotChild(node) {
31775
            return isElementNode(node) && node !== child;
31776
        }
31777
        if (find(parentChildNodes, hasElementChildThatIsNotChild)) {
31778
            return false;
31779
        }
31780
        var docTypeNode = find(parentChildNodes, isDocTypeNode);
31781
        return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
31782
    }
31783
 
31784
    /**
31785
     * @private
31786
     * Steps 1-5 of the checks before inserting and before replacing a child are the same.
31787
     *
31788
     * @param {Node} parent the parent node to insert `node` into
31789
     * @param {Node} node the node to insert
31790
     * @param {Node=} child the node that should become the `nextSibling` of `node`
31791
     * @returns {Node}
31792
     * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
31793
     * @throws DOMException if `child` is provided but is not a child of `parent`.
31794
     * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
31795
     * @see https://dom.spec.whatwg.org/#concept-node-replace
31796
     */
31797
    function assertPreInsertionValidity1to5(parent, node, child) {
31798
        // 1. If `parent` is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException.
31799
        if (!hasValidParentNodeType(parent)) {
31800
            throw new DOMException(HIERARCHY_REQUEST_ERR, 'Unexpected parent node type ' + parent.nodeType);
31801
        }
31802
        // 2. If `node` is a host-including inclusive ancestor of `parent`, then throw a "HierarchyRequestError" DOMException.
31803
        // not implemented!
31804
        // 3. If `child` is non-null and its parent is not `parent`, then throw a "NotFoundError" DOMException.
31805
        if (child && child.parentNode !== parent) {
31806
            throw new DOMException(NOT_FOUND_ERR, 'child not in parent');
31807
        }
31808
        if (
31809
            // 4. If `node` is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException.
31810
            !hasInsertableNodeType(node) ||
31811
            // 5. If either `node` is a Text node and `parent` is a document,
31812
            // the sax parser currently adds top level text nodes, this will be fixed in 0.9.0
31813
            // || (node.nodeType === Node.TEXT_NODE && parent.nodeType === Node.DOCUMENT_NODE)
31814
            // or `node` is a doctype and `parent` is not a document, then throw a "HierarchyRequestError" DOMException.
31815
            isDocTypeNode(node) && parent.nodeType !== Node.DOCUMENT_NODE) {
31816
            throw new DOMException(HIERARCHY_REQUEST_ERR, 'Unexpected node type ' + node.nodeType + ' for parent node type ' + parent.nodeType);
31817
        }
31818
    }
31819
 
31820
    /**
31821
     * @private
31822
     * Step 6 of the checks before inserting and before replacing a child are different.
31823
     *
31824
     * @param {Document} parent the parent node to insert `node` into
31825
     * @param {Node} node the node to insert
31826
     * @param {Node | undefined} child the node that should become the `nextSibling` of `node`
31827
     * @returns {Node}
31828
     * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
31829
     * @throws DOMException if `child` is provided but is not a child of `parent`.
31830
     * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
31831
     * @see https://dom.spec.whatwg.org/#concept-node-replace
31832
     */
31833
    function assertPreInsertionValidityInDocument(parent, node, child) {
31834
        var parentChildNodes = parent.childNodes || [];
31835
        var nodeChildNodes = node.childNodes || [];
31836
 
31837
        // DocumentFragment
31838
        if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
31839
            var nodeChildElements = nodeChildNodes.filter(isElementNode);
31840
            // If node has more than one element child or has a Text node child.
31841
            if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) {
31842
                throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
31843
            }
31844
            // Otherwise, if `node` has one element child and either `parent` has an element child,
31845
            // `child` is a doctype, or `child` is non-null and a doctype is following `child`.
31846
            if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) {
31847
                throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
31848
            }
31849
        }
31850
        // Element
31851
        if (isElementNode(node)) {
31852
            // `parent` has an element child, `child` is a doctype,
31853
            // or `child` is non-null and a doctype is following `child`.
31854
            if (!isElementInsertionPossible(parent, child)) {
31855
                throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
31856
            }
31857
        }
31858
        // DocumentType
31859
        if (isDocTypeNode(node)) {
31860
            // `parent` has a doctype child,
31861
            if (find(parentChildNodes, isDocTypeNode)) {
31862
                throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
31863
            }
31864
            var parentElementChild = find(parentChildNodes, isElementNode);
31865
            // `child` is non-null and an element is preceding `child`,
31866
            if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
31867
                throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
31868
            }
31869
            // or `child` is null and `parent` has an element child.
31870
            if (!child && parentElementChild) {
31871
                throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present');
31872
            }
31873
        }
31874
    }
31875
 
31876
    /**
31877
     * @private
31878
     * Step 6 of the checks before inserting and before replacing a child are different.
31879
     *
31880
     * @param {Document} parent the parent node to insert `node` into
31881
     * @param {Node} node the node to insert
31882
     * @param {Node | undefined} child the node that should become the `nextSibling` of `node`
31883
     * @returns {Node}
31884
     * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
31885
     * @throws DOMException if `child` is provided but is not a child of `parent`.
31886
     * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
31887
     * @see https://dom.spec.whatwg.org/#concept-node-replace
31888
     */
31889
    function assertPreReplacementValidityInDocument(parent, node, child) {
31890
        var parentChildNodes = parent.childNodes || [];
31891
        var nodeChildNodes = node.childNodes || [];
31892
 
31893
        // DocumentFragment
31894
        if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
31895
            var nodeChildElements = nodeChildNodes.filter(isElementNode);
31896
            // If `node` has more than one element child or has a Text node child.
31897
            if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) {
31898
                throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
31899
            }
31900
            // Otherwise, if `node` has one element child and either `parent` has an element child that is not `child` or a doctype is following `child`.
31901
            if (nodeChildElements.length === 1 && !isElementReplacementPossible(parent, child)) {
31902
                throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
31903
            }
31904
        }
31905
        // Element
31906
        if (isElementNode(node)) {
31907
            // `parent` has an element child that is not `child` or a doctype is following `child`.
31908
            if (!isElementReplacementPossible(parent, child)) {
31909
                throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
31910
            }
31911
        }
31912
        // DocumentType
31913
        if (isDocTypeNode(node)) {
31914
            function hasDoctypeChildThatIsNotChild(node) {
31915
                return isDocTypeNode(node) && node !== child;
31916
            }
31917
 
31918
            // `parent` has a doctype child that is not `child`,
31919
            if (find(parentChildNodes, hasDoctypeChildThatIsNotChild)) {
31920
                throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
31921
            }
31922
            var parentElementChild = find(parentChildNodes, isElementNode);
31923
            // or an element is preceding `child`.
31924
            if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
31925
                throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
31926
            }
31927
        }
31928
    }
31929
 
31930
    /**
31931
     * @private
31932
     * @param {Node} parent the parent node to insert `node` into
31933
     * @param {Node} node the node to insert
31934
     * @param {Node=} child the node that should become the `nextSibling` of `node`
31935
     * @returns {Node}
31936
     * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
31937
     * @throws DOMException if `child` is provided but is not a child of `parent`.
31938
     * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
31939
     */
31940
    function _insertBefore(parent, node, child, _inDocumentAssertion) {
31941
        // To ensure pre-insertion validity of a node into a parent before a child, run these steps:
31942
        assertPreInsertionValidity1to5(parent, node, child);
31943
 
31944
        // If parent is a document, and any of the statements below, switched on the interface node implements,
31945
        // are true, then throw a "HierarchyRequestError" DOMException.
31946
        if (parent.nodeType === Node.DOCUMENT_NODE) {
31947
            (_inDocumentAssertion || assertPreInsertionValidityInDocument)(parent, node, child);
31948
        }
31949
        var cp = node.parentNode;
31950
        if (cp) {
31951
            cp.removeChild(node); //remove and update
31952
        }
31953
 
31954
        if (node.nodeType === DOCUMENT_FRAGMENT_NODE) {
31955
            var newFirst = node.firstChild;
31956
            if (newFirst == null) {
31957
                return node;
31958
            }
31959
            var newLast = node.lastChild;
31960
        } else {
31961
            newFirst = newLast = node;
31962
        }
31963
        var pre = child ? child.previousSibling : parent.lastChild;
31964
        newFirst.previousSibling = pre;
31965
        newLast.nextSibling = child;
31966
        if (pre) {
31967
            pre.nextSibling = newFirst;
31968
        } else {
31969
            parent.firstChild = newFirst;
31970
        }
31971
        if (child == null) {
31972
            parent.lastChild = newLast;
31973
        } else {
31974
            child.previousSibling = newLast;
31975
        }
31976
        do {
31977
            newFirst.parentNode = parent;
31978
        } while (newFirst !== newLast && (newFirst = newFirst.nextSibling));
31979
        _onUpdateChild(parent.ownerDocument || parent, parent);
31980
        //console.log(parent.lastChild.nextSibling == null)
31981
        if (node.nodeType == DOCUMENT_FRAGMENT_NODE) {
31982
            node.firstChild = node.lastChild = null;
31983
        }
31984
        return node;
31985
    }
31986
 
31987
    /**
31988
     * Appends `newChild` to `parentNode`.
31989
     * If `newChild` is already connected to a `parentNode` it is first removed from it.
31990
     *
31991
     * @see https://github.com/xmldom/xmldom/issues/135
31992
     * @see https://github.com/xmldom/xmldom/issues/145
31993
     * @param {Node} parentNode
31994
     * @param {Node} newChild
31995
     * @returns {Node}
31996
     * @private
31997
     */
31998
    function _appendSingleChild(parentNode, newChild) {
31999
        if (newChild.parentNode) {
32000
            newChild.parentNode.removeChild(newChild);
32001
        }
32002
        newChild.parentNode = parentNode;
32003
        newChild.previousSibling = parentNode.lastChild;
32004
        newChild.nextSibling = null;
32005
        if (newChild.previousSibling) {
32006
            newChild.previousSibling.nextSibling = newChild;
32007
        } else {
32008
            parentNode.firstChild = newChild;
32009
        }
32010
        parentNode.lastChild = newChild;
32011
        _onUpdateChild(parentNode.ownerDocument, parentNode, newChild);
32012
        return newChild;
32013
    }
32014
    Document.prototype = {
32015
        //implementation : null,
32016
        nodeName: '#document',
32017
        nodeType: DOCUMENT_NODE,
32018
        /**
32019
         * The DocumentType node of the document.
32020
         *
32021
         * @readonly
32022
         * @type DocumentType
32023
         */
32024
        doctype: null,
32025
        documentElement: null,
32026
        _inc: 1,
32027
        insertBefore: function (newChild, refChild) {
32028
            //raises
32029
            if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) {
32030
                var child = newChild.firstChild;
32031
                while (child) {
32032
                    var next = child.nextSibling;
32033
                    this.insertBefore(child, refChild);
32034
                    child = next;
32035
                }
32036
                return newChild;
32037
            }
32038
            _insertBefore(this, newChild, refChild);
32039
            newChild.ownerDocument = this;
32040
            if (this.documentElement === null && newChild.nodeType === ELEMENT_NODE) {
32041
                this.documentElement = newChild;
32042
            }
32043
            return newChild;
32044
        },
32045
        removeChild: function (oldChild) {
32046
            if (this.documentElement == oldChild) {
32047
                this.documentElement = null;
32048
            }
32049
            return _removeChild(this, oldChild);
32050
        },
32051
        replaceChild: function (newChild, oldChild) {
32052
            //raises
32053
            _insertBefore(this, newChild, oldChild, assertPreReplacementValidityInDocument);
32054
            newChild.ownerDocument = this;
32055
            if (oldChild) {
32056
                this.removeChild(oldChild);
32057
            }
32058
            if (isElementNode(newChild)) {
32059
                this.documentElement = newChild;
32060
            }
32061
        },
32062
        // Introduced in DOM Level 2:
32063
        importNode: function (importedNode, deep) {
32064
            return importNode(this, importedNode, deep);
32065
        },
32066
        // Introduced in DOM Level 2:
32067
        getElementById: function (id) {
32068
            var rtv = null;
32069
            _visitNode(this.documentElement, function (node) {
32070
                if (node.nodeType == ELEMENT_NODE) {
32071
                    if (node.getAttribute('id') == id) {
32072
                        rtv = node;
32073
                        return true;
32074
                    }
32075
                }
32076
            });
32077
            return rtv;
32078
        },
32079
        /**
32080
         * The `getElementsByClassName` method of `Document` interface returns an array-like object
32081
         * of all child elements which have **all** of the given class name(s).
32082
         *
32083
         * Returns an empty list if `classeNames` is an empty string or only contains HTML white space characters.
32084
         *
32085
         *
32086
         * Warning: This is a live LiveNodeList.
32087
         * Changes in the DOM will reflect in the array as the changes occur.
32088
         * If an element selected by this array no longer qualifies for the selector,
32089
         * it will automatically be removed. Be aware of this for iteration purposes.
32090
         *
32091
         * @param {string} classNames is a string representing the class name(s) to match; multiple class names are separated by (ASCII-)whitespace
32092
         *
32093
         * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
32094
         * @see https://dom.spec.whatwg.org/#concept-getelementsbyclassname
32095
         */
32096
        getElementsByClassName: function (classNames) {
32097
            var classNamesSet = toOrderedSet(classNames);
32098
            return new LiveNodeList(this, function (base) {
32099
                var ls = [];
32100
                if (classNamesSet.length > 0) {
32101
                    _visitNode(base.documentElement, function (node) {
32102
                        if (node !== base && node.nodeType === ELEMENT_NODE) {
32103
                            var nodeClassNames = node.getAttribute('class');
32104
                            // can be null if the attribute does not exist
32105
                            if (nodeClassNames) {
32106
                                // before splitting and iterating just compare them for the most common case
32107
                                var matches = classNames === nodeClassNames;
32108
                                if (!matches) {
32109
                                    var nodeClassNamesSet = toOrderedSet(nodeClassNames);
32110
                                    matches = classNamesSet.every(arrayIncludes(nodeClassNamesSet));
32111
                                }
32112
                                if (matches) {
32113
                                    ls.push(node);
32114
                                }
32115
                            }
32116
                        }
32117
                    });
32118
                }
32119
                return ls;
32120
            });
32121
        },
32122
        //document factory method:
32123
        createElement: function (tagName) {
32124
            var node = new Element();
32125
            node.ownerDocument = this;
32126
            node.nodeName = tagName;
32127
            node.tagName = tagName;
32128
            node.localName = tagName;
32129
            node.childNodes = new NodeList();
32130
            var attrs = node.attributes = new NamedNodeMap();
32131
            attrs._ownerElement = node;
32132
            return node;
32133
        },
32134
        createDocumentFragment: function () {
32135
            var node = new DocumentFragment();
32136
            node.ownerDocument = this;
32137
            node.childNodes = new NodeList();
32138
            return node;
32139
        },
32140
        createTextNode: function (data) {
32141
            var node = new Text();
32142
            node.ownerDocument = this;
32143
            node.appendData(data);
32144
            return node;
32145
        },
32146
        createComment: function (data) {
32147
            var node = new Comment();
32148
            node.ownerDocument = this;
32149
            node.appendData(data);
32150
            return node;
32151
        },
32152
        createCDATASection: function (data) {
32153
            var node = new CDATASection();
32154
            node.ownerDocument = this;
32155
            node.appendData(data);
32156
            return node;
32157
        },
32158
        createProcessingInstruction: function (target, data) {
32159
            var node = new ProcessingInstruction();
32160
            node.ownerDocument = this;
32161
            node.tagName = node.nodeName = node.target = target;
32162
            node.nodeValue = node.data = data;
32163
            return node;
32164
        },
32165
        createAttribute: function (name) {
32166
            var node = new Attr();
32167
            node.ownerDocument = this;
32168
            node.name = name;
32169
            node.nodeName = name;
32170
            node.localName = name;
32171
            node.specified = true;
32172
            return node;
32173
        },
32174
        createEntityReference: function (name) {
32175
            var node = new EntityReference();
32176
            node.ownerDocument = this;
32177
            node.nodeName = name;
32178
            return node;
32179
        },
32180
        // Introduced in DOM Level 2:
32181
        createElementNS: function (namespaceURI, qualifiedName) {
32182
            var node = new Element();
32183
            var pl = qualifiedName.split(':');
32184
            var attrs = node.attributes = new NamedNodeMap();
32185
            node.childNodes = new NodeList();
32186
            node.ownerDocument = this;
32187
            node.nodeName = qualifiedName;
32188
            node.tagName = qualifiedName;
32189
            node.namespaceURI = namespaceURI;
32190
            if (pl.length == 2) {
32191
                node.prefix = pl[0];
32192
                node.localName = pl[1];
32193
            } else {
32194
                //el.prefix = null;
32195
                node.localName = qualifiedName;
32196
            }
32197
            attrs._ownerElement = node;
32198
            return node;
32199
        },
32200
        // Introduced in DOM Level 2:
32201
        createAttributeNS: function (namespaceURI, qualifiedName) {
32202
            var node = new Attr();
32203
            var pl = qualifiedName.split(':');
32204
            node.ownerDocument = this;
32205
            node.nodeName = qualifiedName;
32206
            node.name = qualifiedName;
32207
            node.namespaceURI = namespaceURI;
32208
            node.specified = true;
32209
            if (pl.length == 2) {
32210
                node.prefix = pl[0];
32211
                node.localName = pl[1];
32212
            } else {
32213
                //el.prefix = null;
32214
                node.localName = qualifiedName;
32215
            }
32216
            return node;
32217
        }
32218
    };
32219
    _extends(Document, Node);
32220
    function Element() {
32221
        this._nsMap = {};
32222
    }
32223
    Element.prototype = {
32224
        nodeType: ELEMENT_NODE,
32225
        hasAttribute: function (name) {
32226
            return this.getAttributeNode(name) != null;
32227
        },
32228
        getAttribute: function (name) {
32229
            var attr = this.getAttributeNode(name);
32230
            return attr && attr.value || '';
32231
        },
32232
        getAttributeNode: function (name) {
32233
            return this.attributes.getNamedItem(name);
32234
        },
32235
        setAttribute: function (name, value) {
32236
            var attr = this.ownerDocument.createAttribute(name);
32237
            attr.value = attr.nodeValue = "" + value;
32238
            this.setAttributeNode(attr);
32239
        },
32240
        removeAttribute: function (name) {
32241
            var attr = this.getAttributeNode(name);
32242
            attr && this.removeAttributeNode(attr);
32243
        },
32244
        //four real opeartion method
32245
        appendChild: function (newChild) {
32246
            if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE) {
32247
                return this.insertBefore(newChild, null);
32248
            } else {
32249
                return _appendSingleChild(this, newChild);
32250
            }
32251
        },
32252
        setAttributeNode: function (newAttr) {
32253
            return this.attributes.setNamedItem(newAttr);
32254
        },
32255
        setAttributeNodeNS: function (newAttr) {
32256
            return this.attributes.setNamedItemNS(newAttr);
32257
        },
32258
        removeAttributeNode: function (oldAttr) {
32259
            //console.log(this == oldAttr.ownerElement)
32260
            return this.attributes.removeNamedItem(oldAttr.nodeName);
32261
        },
32262
        //get real attribute name,and remove it by removeAttributeNode
32263
        removeAttributeNS: function (namespaceURI, localName) {
32264
            var old = this.getAttributeNodeNS(namespaceURI, localName);
32265
            old && this.removeAttributeNode(old);
32266
        },
32267
        hasAttributeNS: function (namespaceURI, localName) {
32268
            return this.getAttributeNodeNS(namespaceURI, localName) != null;
32269
        },
32270
        getAttributeNS: function (namespaceURI, localName) {
32271
            var attr = this.getAttributeNodeNS(namespaceURI, localName);
32272
            return attr && attr.value || '';
32273
        },
32274
        setAttributeNS: function (namespaceURI, qualifiedName, value) {
32275
            var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName);
32276
            attr.value = attr.nodeValue = "" + value;
32277
            this.setAttributeNode(attr);
32278
        },
32279
        getAttributeNodeNS: function (namespaceURI, localName) {
32280
            return this.attributes.getNamedItemNS(namespaceURI, localName);
32281
        },
32282
        getElementsByTagName: function (tagName) {
32283
            return new LiveNodeList(this, function (base) {
32284
                var ls = [];
32285
                _visitNode(base, function (node) {
32286
                    if (node !== base && node.nodeType == ELEMENT_NODE && (tagName === '*' || node.tagName == tagName)) {
32287
                        ls.push(node);
32288
                    }
32289
                });
32290
                return ls;
32291
            });
32292
        },
32293
        getElementsByTagNameNS: function (namespaceURI, localName) {
32294
            return new LiveNodeList(this, function (base) {
32295
                var ls = [];
32296
                _visitNode(base, function (node) {
32297
                    if (node !== base && node.nodeType === ELEMENT_NODE && (namespaceURI === '*' || node.namespaceURI === namespaceURI) && (localName === '*' || node.localName == localName)) {
32298
                        ls.push(node);
32299
                    }
32300
                });
32301
                return ls;
32302
            });
32303
        }
32304
    };
32305
    Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName;
32306
    Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS;
32307
    _extends(Element, Node);
32308
    function Attr() {}
32309
    Attr.prototype.nodeType = ATTRIBUTE_NODE;
32310
    _extends(Attr, Node);
32311
    function CharacterData() {}
32312
    CharacterData.prototype = {
32313
        data: '',
32314
        substringData: function (offset, count) {
32315
            return this.data.substring(offset, offset + count);
32316
        },
32317
        appendData: function (text) {
32318
            text = this.data + text;
32319
            this.nodeValue = this.data = text;
32320
            this.length = text.length;
32321
        },
32322
        insertData: function (offset, text) {
32323
            this.replaceData(offset, 0, text);
32324
        },
32325
        appendChild: function (newChild) {
32326
            throw new Error(ExceptionMessage[HIERARCHY_REQUEST_ERR]);
32327
        },
32328
        deleteData: function (offset, count) {
32329
            this.replaceData(offset, count, "");
32330
        },
32331
        replaceData: function (offset, count, text) {
32332
            var start = this.data.substring(0, offset);
32333
            var end = this.data.substring(offset + count);
32334
            text = start + text + end;
32335
            this.nodeValue = this.data = text;
32336
            this.length = text.length;
32337
        }
32338
    };
32339
    _extends(CharacterData, Node);
32340
    function Text() {}
32341
    Text.prototype = {
32342
        nodeName: "#text",
32343
        nodeType: TEXT_NODE,
32344
        splitText: function (offset) {
32345
            var text = this.data;
32346
            var newText = text.substring(offset);
32347
            text = text.substring(0, offset);
32348
            this.data = this.nodeValue = text;
32349
            this.length = text.length;
32350
            var newNode = this.ownerDocument.createTextNode(newText);
32351
            if (this.parentNode) {
32352
                this.parentNode.insertBefore(newNode, this.nextSibling);
32353
            }
32354
            return newNode;
32355
        }
32356
    };
32357
    _extends(Text, CharacterData);
32358
    function Comment() {}
32359
    Comment.prototype = {
32360
        nodeName: "#comment",
32361
        nodeType: COMMENT_NODE
32362
    };
32363
    _extends(Comment, CharacterData);
32364
    function CDATASection() {}
32365
    CDATASection.prototype = {
32366
        nodeName: "#cdata-section",
32367
        nodeType: CDATA_SECTION_NODE
32368
    };
32369
    _extends(CDATASection, CharacterData);
32370
    function DocumentType() {}
32371
    DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE;
32372
    _extends(DocumentType, Node);
32373
    function Notation() {}
32374
    Notation.prototype.nodeType = NOTATION_NODE;
32375
    _extends(Notation, Node);
32376
    function Entity() {}
32377
    Entity.prototype.nodeType = ENTITY_NODE;
32378
    _extends(Entity, Node);
32379
    function EntityReference() {}
32380
    EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE;
32381
    _extends(EntityReference, Node);
32382
    function DocumentFragment() {}
32383
    DocumentFragment.prototype.nodeName = "#document-fragment";
32384
    DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE;
32385
    _extends(DocumentFragment, Node);
32386
    function ProcessingInstruction() {}
32387
    ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE;
32388
    _extends(ProcessingInstruction, Node);
32389
    function XMLSerializer() {}
32390
    XMLSerializer.prototype.serializeToString = function (node, isHtml, nodeFilter) {
32391
        return nodeSerializeToString.call(node, isHtml, nodeFilter);
32392
    };
32393
    Node.prototype.toString = nodeSerializeToString;
32394
    function nodeSerializeToString(isHtml, nodeFilter) {
32395
        var buf = [];
32396
        var refNode = this.nodeType == 9 && this.documentElement || this;
32397
        var prefix = refNode.prefix;
32398
        var uri = refNode.namespaceURI;
32399
        if (uri && prefix == null) {
32400
            //console.log(prefix)
32401
            var prefix = refNode.lookupPrefix(uri);
32402
            if (prefix == null) {
32403
                //isHTML = true;
32404
                var visibleNamespaces = [{
32405
                    namespace: uri,
32406
                    prefix: null
32407
                }
32408
                    //{namespace:uri,prefix:''}
32409
                ];
32410
            }
32411
        }
32412
 
32413
        serializeToString(this, buf, isHtml, nodeFilter, visibleNamespaces);
32414
        //console.log('###',this.nodeType,uri,prefix,buf.join(''))
32415
        return buf.join('');
32416
    }
32417
    function needNamespaceDefine(node, isHTML, visibleNamespaces) {
32418
        var prefix = node.prefix || '';
32419
        var uri = node.namespaceURI;
32420
        // According to [Namespaces in XML 1.0](https://www.w3.org/TR/REC-xml-names/#ns-using) ,
32421
        // and more specifically https://www.w3.org/TR/REC-xml-names/#nsc-NoPrefixUndecl :
32422
        // > In a namespace declaration for a prefix [...], the attribute value MUST NOT be empty.
32423
        // in a similar manner [Namespaces in XML 1.1](https://www.w3.org/TR/xml-names11/#ns-using)
32424
        // and more specifically https://www.w3.org/TR/xml-names11/#nsc-NSDeclared :
32425
        // > [...] Furthermore, the attribute value [...] must not be an empty string.
32426
        // so serializing empty namespace value like xmlns:ds="" would produce an invalid XML document.
32427
        if (!uri) {
32428
            return false;
32429
        }
32430
        if (prefix === "xml" && uri === NAMESPACE$2.XML || uri === NAMESPACE$2.XMLNS) {
32431
            return false;
32432
        }
32433
        var i = visibleNamespaces.length;
32434
        while (i--) {
32435
            var ns = visibleNamespaces[i];
32436
            // get namespace prefix
32437
            if (ns.prefix === prefix) {
32438
                return ns.namespace !== uri;
32439
            }
32440
        }
32441
        return true;
32442
    }
32443
    /**
32444
     * Well-formed constraint: No < in Attribute Values
32445
     * > The replacement text of any entity referred to directly or indirectly
32446
     * > in an attribute value must not contain a <.
32447
     * @see https://www.w3.org/TR/xml11/#CleanAttrVals
32448
     * @see https://www.w3.org/TR/xml11/#NT-AttValue
32449
     *
32450
     * Literal whitespace other than space that appear in attribute values
32451
     * are serialized as their entity references, so they will be preserved.
32452
     * (In contrast to whitespace literals in the input which are normalized to spaces)
32453
     * @see https://www.w3.org/TR/xml11/#AVNormalize
32454
     * @see https://w3c.github.io/DOM-Parsing/#serializing-an-element-s-attributes
32455
     */
32456
    function addSerializedAttribute(buf, qualifiedName, value) {
32457
        buf.push(' ', qualifiedName, '="', value.replace(/[<>&"\t\n\r]/g, _xmlEncoder), '"');
32458
    }
32459
    function serializeToString(node, buf, isHTML, nodeFilter, visibleNamespaces) {
32460
        if (!visibleNamespaces) {
32461
            visibleNamespaces = [];
32462
        }
32463
        if (nodeFilter) {
32464
            node = nodeFilter(node);
32465
            if (node) {
32466
                if (typeof node == 'string') {
32467
                    buf.push(node);
32468
                    return;
32469
                }
32470
            } else {
32471
                return;
32472
            }
32473
            //buf.sort.apply(attrs, attributeSorter);
32474
        }
32475
 
32476
        switch (node.nodeType) {
32477
            case ELEMENT_NODE:
32478
                var attrs = node.attributes;
32479
                var len = attrs.length;
32480
                var child = node.firstChild;
32481
                var nodeName = node.tagName;
32482
                isHTML = NAMESPACE$2.isHTML(node.namespaceURI) || isHTML;
32483
                var prefixedNodeName = nodeName;
32484
                if (!isHTML && !node.prefix && node.namespaceURI) {
32485
                    var defaultNS;
32486
                    // lookup current default ns from `xmlns` attribute
32487
                    for (var ai = 0; ai < attrs.length; ai++) {
32488
                        if (attrs.item(ai).name === 'xmlns') {
32489
                            defaultNS = attrs.item(ai).value;
32490
                            break;
32491
                        }
32492
                    }
32493
                    if (!defaultNS) {
32494
                        // lookup current default ns in visibleNamespaces
32495
                        for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) {
32496
                            var namespace = visibleNamespaces[nsi];
32497
                            if (namespace.prefix === '' && namespace.namespace === node.namespaceURI) {
32498
                                defaultNS = namespace.namespace;
32499
                                break;
32500
                            }
32501
                        }
32502
                    }
32503
                    if (defaultNS !== node.namespaceURI) {
32504
                        for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) {
32505
                            var namespace = visibleNamespaces[nsi];
32506
                            if (namespace.namespace === node.namespaceURI) {
32507
                                if (namespace.prefix) {
32508
                                    prefixedNodeName = namespace.prefix + ':' + nodeName;
32509
                                }
32510
                                break;
32511
                            }
32512
                        }
32513
                    }
32514
                }
32515
                buf.push('<', prefixedNodeName);
32516
                for (var i = 0; i < len; i++) {
32517
                    // add namespaces for attributes
32518
                    var attr = attrs.item(i);
32519
                    if (attr.prefix == 'xmlns') {
32520
                        visibleNamespaces.push({
32521
                            prefix: attr.localName,
32522
                            namespace: attr.value
32523
                        });
32524
                    } else if (attr.nodeName == 'xmlns') {
32525
                        visibleNamespaces.push({
32526
                            prefix: '',
32527
                            namespace: attr.value
32528
                        });
32529
                    }
32530
                }
32531
                for (var i = 0; i < len; i++) {
32532
                    var attr = attrs.item(i);
32533
                    if (needNamespaceDefine(attr, isHTML, visibleNamespaces)) {
32534
                        var prefix = attr.prefix || '';
32535
                        var uri = attr.namespaceURI;
32536
                        addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri);
32537
                        visibleNamespaces.push({
32538
                            prefix: prefix,
32539
                            namespace: uri
32540
                        });
32541
                    }
32542
                    serializeToString(attr, buf, isHTML, nodeFilter, visibleNamespaces);
32543
                }
32544
 
32545
                // add namespace for current node
32546
                if (nodeName === prefixedNodeName && needNamespaceDefine(node, isHTML, visibleNamespaces)) {
32547
                    var prefix = node.prefix || '';
32548
                    var uri = node.namespaceURI;
32549
                    addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri);
32550
                    visibleNamespaces.push({
32551
                        prefix: prefix,
32552
                        namespace: uri
32553
                    });
32554
                }
32555
                if (child || isHTML && !/^(?:meta|link|img|br|hr|input)$/i.test(nodeName)) {
32556
                    buf.push('>');
32557
                    //if is cdata child node
32558
                    if (isHTML && /^script$/i.test(nodeName)) {
32559
                        while (child) {
32560
                            if (child.data) {
32561
                                buf.push(child.data);
32562
                            } else {
32563
                                serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice());
32564
                            }
32565
                            child = child.nextSibling;
32566
                        }
32567
                    } else {
32568
                        while (child) {
32569
                            serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice());
32570
                            child = child.nextSibling;
32571
                        }
32572
                    }
32573
                    buf.push('</', prefixedNodeName, '>');
32574
                } else {
32575
                    buf.push('/>');
32576
                }
32577
                // remove added visible namespaces
32578
                //visibleNamespaces.length = startVisibleNamespaces;
32579
                return;
32580
            case DOCUMENT_NODE:
32581
            case DOCUMENT_FRAGMENT_NODE:
32582
                var child = node.firstChild;
32583
                while (child) {
32584
                    serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice());
32585
                    child = child.nextSibling;
32586
                }
32587
                return;
32588
            case ATTRIBUTE_NODE:
32589
                return addSerializedAttribute(buf, node.name, node.value);
32590
            case TEXT_NODE:
32591
                /**
32592
                 * The ampersand character (&) and the left angle bracket (<) must not appear in their literal form,
32593
                 * except when used as markup delimiters, or within a comment, a processing instruction, or a CDATA section.
32594
                 * If they are needed elsewhere, they must be escaped using either numeric character references or the strings
32595
                 * `&amp;` and `&lt;` respectively.
32596
                 * The right angle bracket (>) may be represented using the string " &gt; ", and must, for compatibility,
32597
                 * be escaped using either `&gt;` or a character reference when it appears in the string `]]>` in content,
32598
                 * when that string is not marking the end of a CDATA section.
32599
                 *
32600
                 * In the content of elements, character data is any string of characters
32601
                 * which does not contain the start-delimiter of any markup
32602
                 * and does not include the CDATA-section-close delimiter, `]]>`.
32603
                 *
32604
                 * @see https://www.w3.org/TR/xml/#NT-CharData
32605
                 * @see https://w3c.github.io/DOM-Parsing/#xml-serializing-a-text-node
32606
                 */
32607
                return buf.push(node.data.replace(/[<&>]/g, _xmlEncoder));
32608
            case CDATA_SECTION_NODE:
32609
                return buf.push('<![CDATA[', node.data, ']]>');
32610
            case COMMENT_NODE:
32611
                return buf.push("<!--", node.data, "-->");
32612
            case DOCUMENT_TYPE_NODE:
32613
                var pubid = node.publicId;
32614
                var sysid = node.systemId;
32615
                buf.push('<!DOCTYPE ', node.name);
32616
                if (pubid) {
32617
                    buf.push(' PUBLIC ', pubid);
32618
                    if (sysid && sysid != '.') {
32619
                        buf.push(' ', sysid);
32620
                    }
32621
                    buf.push('>');
32622
                } else if (sysid && sysid != '.') {
32623
                    buf.push(' SYSTEM ', sysid, '>');
32624
                } else {
32625
                    var sub = node.internalSubset;
32626
                    if (sub) {
32627
                        buf.push(" [", sub, "]");
32628
                    }
32629
                    buf.push(">");
32630
                }
32631
                return;
32632
            case PROCESSING_INSTRUCTION_NODE:
32633
                return buf.push("<?", node.target, " ", node.data, "?>");
32634
            case ENTITY_REFERENCE_NODE:
32635
                return buf.push('&', node.nodeName, ';');
32636
            //case ENTITY_NODE:
32637
            //case NOTATION_NODE:
32638
            default:
32639
                buf.push('??', node.nodeName);
32640
        }
32641
    }
32642
    function importNode(doc, node, deep) {
32643
        var node2;
32644
        switch (node.nodeType) {
32645
            case ELEMENT_NODE:
32646
                node2 = node.cloneNode(false);
32647
                node2.ownerDocument = doc;
32648
            //var attrs = node2.attributes;
32649
            //var len = attrs.length;
32650
            //for(var i=0;i<len;i++){
32651
            //node2.setAttributeNodeNS(importNode(doc,attrs.item(i),deep));
32652
            //}
32653
            case DOCUMENT_FRAGMENT_NODE:
32654
                break;
32655
            case ATTRIBUTE_NODE:
32656
                deep = true;
32657
                break;
32658
            //case ENTITY_REFERENCE_NODE:
32659
            //case PROCESSING_INSTRUCTION_NODE:
32660
            ////case TEXT_NODE:
32661
            //case CDATA_SECTION_NODE:
32662
            //case COMMENT_NODE:
32663
            //	deep = false;
32664
            //	break;
32665
            //case DOCUMENT_NODE:
32666
            //case DOCUMENT_TYPE_NODE:
32667
            //cannot be imported.
32668
            //case ENTITY_NODE:
32669
            //case NOTATION_NODE:
32670
            //can not hit in level3
32671
            //default:throw e;
32672
        }
32673
 
32674
        if (!node2) {
32675
            node2 = node.cloneNode(false); //false
32676
        }
32677
 
32678
        node2.ownerDocument = doc;
32679
        node2.parentNode = null;
32680
        if (deep) {
32681
            var child = node.firstChild;
32682
            while (child) {
32683
                node2.appendChild(importNode(doc, child, deep));
32684
                child = child.nextSibling;
32685
            }
32686
        }
32687
        return node2;
32688
    }
32689
    //
32690
    //var _relationMap = {firstChild:1,lastChild:1,previousSibling:1,nextSibling:1,
32691
    //					attributes:1,childNodes:1,parentNode:1,documentElement:1,doctype,};
32692
    function cloneNode(doc, node, deep) {
32693
        var node2 = new node.constructor();
32694
        for (var n in node) {
32695
            if (Object.prototype.hasOwnProperty.call(node, n)) {
32696
                var v = node[n];
32697
                if (typeof v != "object") {
32698
                    if (v != node2[n]) {
32699
                        node2[n] = v;
32700
                    }
32701
                }
32702
            }
32703
        }
32704
        if (node.childNodes) {
32705
            node2.childNodes = new NodeList();
32706
        }
32707
        node2.ownerDocument = doc;
32708
        switch (node2.nodeType) {
32709
            case ELEMENT_NODE:
32710
                var attrs = node.attributes;
32711
                var attrs2 = node2.attributes = new NamedNodeMap();
32712
                var len = attrs.length;
32713
                attrs2._ownerElement = node2;
32714
                for (var i = 0; i < len; i++) {
32715
                    node2.setAttributeNode(cloneNode(doc, attrs.item(i), true));
32716
                }
32717
                break;
32718
            case ATTRIBUTE_NODE:
32719
                deep = true;
32720
        }
32721
        if (deep) {
32722
            var child = node.firstChild;
32723
            while (child) {
32724
                node2.appendChild(cloneNode(doc, child, deep));
32725
                child = child.nextSibling;
32726
            }
32727
        }
32728
        return node2;
32729
    }
32730
    function __set__(object, key, value) {
32731
        object[key] = value;
32732
    }
32733
    //do dynamic
32734
    try {
32735
        if (Object.defineProperty) {
32736
            Object.defineProperty(LiveNodeList.prototype, 'length', {
32737
                get: function () {
32738
                    _updateLiveList(this);
32739
                    return this.$$length;
32740
                }
32741
            });
32742
            Object.defineProperty(Node.prototype, 'textContent', {
32743
                get: function () {
32744
                    return getTextContent(this);
32745
                },
32746
                set: function (data) {
32747
                    switch (this.nodeType) {
32748
                        case ELEMENT_NODE:
32749
                        case DOCUMENT_FRAGMENT_NODE:
32750
                            while (this.firstChild) {
32751
                                this.removeChild(this.firstChild);
32752
                            }
32753
                            if (data || String(data)) {
32754
                                this.appendChild(this.ownerDocument.createTextNode(data));
32755
                            }
32756
                            break;
32757
                        default:
32758
                            this.data = data;
32759
                            this.value = data;
32760
                            this.nodeValue = data;
32761
                    }
32762
                }
32763
            });
32764
            function getTextContent(node) {
32765
                switch (node.nodeType) {
32766
                    case ELEMENT_NODE:
32767
                    case DOCUMENT_FRAGMENT_NODE:
32768
                        var buf = [];
32769
                        node = node.firstChild;
32770
                        while (node) {
32771
                            if (node.nodeType !== 7 && node.nodeType !== 8) {
32772
                                buf.push(getTextContent(node));
32773
                            }
32774
                            node = node.nextSibling;
32775
                        }
32776
                        return buf.join('');
32777
                    default:
32778
                        return node.nodeValue;
32779
                }
32780
            }
32781
            __set__ = function (object, key, value) {
32782
                //console.log(value)
32783
                object['$$' + key] = value;
32784
            };
32785
        }
32786
    } catch (e) {//ie8
32787
    }
32788
 
32789
    //if(typeof require == 'function'){
32790
    var DocumentType_1 = DocumentType;
32791
    var DOMException_1 = DOMException;
32792
    var DOMImplementation_1 = DOMImplementation$1;
32793
    var Element_1 = Element;
32794
    var Node_1 = Node;
32795
    var NodeList_1 = NodeList;
32796
    var XMLSerializer_1 = XMLSerializer;
32797
    //}
32798
 
32799
    var dom = {
32800
        DocumentType: DocumentType_1,
32801
        DOMException: DOMException_1,
32802
        DOMImplementation: DOMImplementation_1,
32803
        Element: Element_1,
32804
        Node: Node_1,
32805
        NodeList: NodeList_1,
32806
        XMLSerializer: XMLSerializer_1
32807
    };
32808
 
32809
    var entities = createCommonjsModule(function (module, exports) {
32810
 
32811
        var freeze = conventions.freeze;
32812
 
32813
        /**
32814
         * The entities that are predefined in every XML document.
32815
         *
32816
         * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-predefined-ent W3C XML 1.1
32817
         * @see https://www.w3.org/TR/2008/REC-xml-20081126/#sec-predefined-ent W3C XML 1.0
32818
         * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Predefined_entities_in_XML Wikipedia
32819
         */
32820
        exports.XML_ENTITIES = freeze({
32821
            amp: '&',
32822
            apos: "'",
32823
            gt: '>',
32824
            lt: '<',
32825
            quot: '"'
32826
        });
32827
 
32828
        /**
32829
         * A map of all entities that are detected in an HTML document.
32830
         * They contain all entries from `XML_ENTITIES`.
32831
         *
32832
         * @see XML_ENTITIES
32833
         * @see DOMParser.parseFromString
32834
         * @see DOMImplementation.prototype.createHTMLDocument
32835
         * @see https://html.spec.whatwg.org/#named-character-references WHATWG HTML(5) Spec
32836
         * @see https://html.spec.whatwg.org/entities.json JSON
32837
         * @see https://www.w3.org/TR/xml-entity-names/ W3C XML Entity Names
32838
         * @see https://www.w3.org/TR/html4/sgml/entities.html W3C HTML4/SGML
32839
         * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Character_entity_references_in_HTML Wikipedia (HTML)
32840
         * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Entities_representing_special_characters_in_XHTML Wikpedia (XHTML)
32841
         */
32842
        exports.HTML_ENTITIES = freeze({
32843
            Aacute: '\u00C1',
32844
            aacute: '\u00E1',
32845
            Abreve: '\u0102',
32846
            abreve: '\u0103',
32847
            ac: '\u223E',
32848
            acd: '\u223F',
32849
            acE: '\u223E\u0333',
32850
            Acirc: '\u00C2',
32851
            acirc: '\u00E2',
32852
            acute: '\u00B4',
32853
            Acy: '\u0410',
32854
            acy: '\u0430',
32855
            AElig: '\u00C6',
32856
            aelig: '\u00E6',
32857
            af: '\u2061',
32858
            Afr: '\uD835\uDD04',
32859
            afr: '\uD835\uDD1E',
32860
            Agrave: '\u00C0',
32861
            agrave: '\u00E0',
32862
            alefsym: '\u2135',
32863
            aleph: '\u2135',
32864
            Alpha: '\u0391',
32865
            alpha: '\u03B1',
32866
            Amacr: '\u0100',
32867
            amacr: '\u0101',
32868
            amalg: '\u2A3F',
32869
            AMP: '\u0026',
32870
            amp: '\u0026',
32871
            And: '\u2A53',
32872
            and: '\u2227',
32873
            andand: '\u2A55',
32874
            andd: '\u2A5C',
32875
            andslope: '\u2A58',
32876
            andv: '\u2A5A',
32877
            ang: '\u2220',
32878
            ange: '\u29A4',
32879
            angle: '\u2220',
32880
            angmsd: '\u2221',
32881
            angmsdaa: '\u29A8',
32882
            angmsdab: '\u29A9',
32883
            angmsdac: '\u29AA',
32884
            angmsdad: '\u29AB',
32885
            angmsdae: '\u29AC',
32886
            angmsdaf: '\u29AD',
32887
            angmsdag: '\u29AE',
32888
            angmsdah: '\u29AF',
32889
            angrt: '\u221F',
32890
            angrtvb: '\u22BE',
32891
            angrtvbd: '\u299D',
32892
            angsph: '\u2222',
32893
            angst: '\u00C5',
32894
            angzarr: '\u237C',
32895
            Aogon: '\u0104',
32896
            aogon: '\u0105',
32897
            Aopf: '\uD835\uDD38',
32898
            aopf: '\uD835\uDD52',
32899
            ap: '\u2248',
32900
            apacir: '\u2A6F',
32901
            apE: '\u2A70',
32902
            ape: '\u224A',
32903
            apid: '\u224B',
32904
            apos: '\u0027',
32905
            ApplyFunction: '\u2061',
32906
            approx: '\u2248',
32907
            approxeq: '\u224A',
32908
            Aring: '\u00C5',
32909
            aring: '\u00E5',
32910
            Ascr: '\uD835\uDC9C',
32911
            ascr: '\uD835\uDCB6',
32912
            Assign: '\u2254',
32913
            ast: '\u002A',
32914
            asymp: '\u2248',
32915
            asympeq: '\u224D',
32916
            Atilde: '\u00C3',
32917
            atilde: '\u00E3',
32918
            Auml: '\u00C4',
32919
            auml: '\u00E4',
32920
            awconint: '\u2233',
32921
            awint: '\u2A11',
32922
            backcong: '\u224C',
32923
            backepsilon: '\u03F6',
32924
            backprime: '\u2035',
32925
            backsim: '\u223D',
32926
            backsimeq: '\u22CD',
32927
            Backslash: '\u2216',
32928
            Barv: '\u2AE7',
32929
            barvee: '\u22BD',
32930
            Barwed: '\u2306',
32931
            barwed: '\u2305',
32932
            barwedge: '\u2305',
32933
            bbrk: '\u23B5',
32934
            bbrktbrk: '\u23B6',
32935
            bcong: '\u224C',
32936
            Bcy: '\u0411',
32937
            bcy: '\u0431',
32938
            bdquo: '\u201E',
32939
            becaus: '\u2235',
32940
            Because: '\u2235',
32941
            because: '\u2235',
32942
            bemptyv: '\u29B0',
32943
            bepsi: '\u03F6',
32944
            bernou: '\u212C',
32945
            Bernoullis: '\u212C',
32946
            Beta: '\u0392',
32947
            beta: '\u03B2',
32948
            beth: '\u2136',
32949
            between: '\u226C',
32950
            Bfr: '\uD835\uDD05',
32951
            bfr: '\uD835\uDD1F',
32952
            bigcap: '\u22C2',
32953
            bigcirc: '\u25EF',
32954
            bigcup: '\u22C3',
32955
            bigodot: '\u2A00',
32956
            bigoplus: '\u2A01',
32957
            bigotimes: '\u2A02',
32958
            bigsqcup: '\u2A06',
32959
            bigstar: '\u2605',
32960
            bigtriangledown: '\u25BD',
32961
            bigtriangleup: '\u25B3',
32962
            biguplus: '\u2A04',
32963
            bigvee: '\u22C1',
32964
            bigwedge: '\u22C0',
32965
            bkarow: '\u290D',
32966
            blacklozenge: '\u29EB',
32967
            blacksquare: '\u25AA',
32968
            blacktriangle: '\u25B4',
32969
            blacktriangledown: '\u25BE',
32970
            blacktriangleleft: '\u25C2',
32971
            blacktriangleright: '\u25B8',
32972
            blank: '\u2423',
32973
            blk12: '\u2592',
32974
            blk14: '\u2591',
32975
            blk34: '\u2593',
32976
            block: '\u2588',
32977
            bne: '\u003D\u20E5',
32978
            bnequiv: '\u2261\u20E5',
32979
            bNot: '\u2AED',
32980
            bnot: '\u2310',
32981
            Bopf: '\uD835\uDD39',
32982
            bopf: '\uD835\uDD53',
32983
            bot: '\u22A5',
32984
            bottom: '\u22A5',
32985
            bowtie: '\u22C8',
32986
            boxbox: '\u29C9',
32987
            boxDL: '\u2557',
32988
            boxDl: '\u2556',
32989
            boxdL: '\u2555',
32990
            boxdl: '\u2510',
32991
            boxDR: '\u2554',
32992
            boxDr: '\u2553',
32993
            boxdR: '\u2552',
32994
            boxdr: '\u250C',
32995
            boxH: '\u2550',
32996
            boxh: '\u2500',
32997
            boxHD: '\u2566',
32998
            boxHd: '\u2564',
32999
            boxhD: '\u2565',
33000
            boxhd: '\u252C',
33001
            boxHU: '\u2569',
33002
            boxHu: '\u2567',
33003
            boxhU: '\u2568',
33004
            boxhu: '\u2534',
33005
            boxminus: '\u229F',
33006
            boxplus: '\u229E',
33007
            boxtimes: '\u22A0',
33008
            boxUL: '\u255D',
33009
            boxUl: '\u255C',
33010
            boxuL: '\u255B',
33011
            boxul: '\u2518',
33012
            boxUR: '\u255A',
33013
            boxUr: '\u2559',
33014
            boxuR: '\u2558',
33015
            boxur: '\u2514',
33016
            boxV: '\u2551',
33017
            boxv: '\u2502',
33018
            boxVH: '\u256C',
33019
            boxVh: '\u256B',
33020
            boxvH: '\u256A',
33021
            boxvh: '\u253C',
33022
            boxVL: '\u2563',
33023
            boxVl: '\u2562',
33024
            boxvL: '\u2561',
33025
            boxvl: '\u2524',
33026
            boxVR: '\u2560',
33027
            boxVr: '\u255F',
33028
            boxvR: '\u255E',
33029
            boxvr: '\u251C',
33030
            bprime: '\u2035',
33031
            Breve: '\u02D8',
33032
            breve: '\u02D8',
33033
            brvbar: '\u00A6',
33034
            Bscr: '\u212C',
33035
            bscr: '\uD835\uDCB7',
33036
            bsemi: '\u204F',
33037
            bsim: '\u223D',
33038
            bsime: '\u22CD',
33039
            bsol: '\u005C',
33040
            bsolb: '\u29C5',
33041
            bsolhsub: '\u27C8',
33042
            bull: '\u2022',
33043
            bullet: '\u2022',
33044
            bump: '\u224E',
33045
            bumpE: '\u2AAE',
33046
            bumpe: '\u224F',
33047
            Bumpeq: '\u224E',
33048
            bumpeq: '\u224F',
33049
            Cacute: '\u0106',
33050
            cacute: '\u0107',
33051
            Cap: '\u22D2',
33052
            cap: '\u2229',
33053
            capand: '\u2A44',
33054
            capbrcup: '\u2A49',
33055
            capcap: '\u2A4B',
33056
            capcup: '\u2A47',
33057
            capdot: '\u2A40',
33058
            CapitalDifferentialD: '\u2145',
33059
            caps: '\u2229\uFE00',
33060
            caret: '\u2041',
33061
            caron: '\u02C7',
33062
            Cayleys: '\u212D',
33063
            ccaps: '\u2A4D',
33064
            Ccaron: '\u010C',
33065
            ccaron: '\u010D',
33066
            Ccedil: '\u00C7',
33067
            ccedil: '\u00E7',
33068
            Ccirc: '\u0108',
33069
            ccirc: '\u0109',
33070
            Cconint: '\u2230',
33071
            ccups: '\u2A4C',
33072
            ccupssm: '\u2A50',
33073
            Cdot: '\u010A',
33074
            cdot: '\u010B',
33075
            cedil: '\u00B8',
33076
            Cedilla: '\u00B8',
33077
            cemptyv: '\u29B2',
33078
            cent: '\u00A2',
33079
            CenterDot: '\u00B7',
33080
            centerdot: '\u00B7',
33081
            Cfr: '\u212D',
33082
            cfr: '\uD835\uDD20',
33083
            CHcy: '\u0427',
33084
            chcy: '\u0447',
33085
            check: '\u2713',
33086
            checkmark: '\u2713',
33087
            Chi: '\u03A7',
33088
            chi: '\u03C7',
33089
            cir: '\u25CB',
33090
            circ: '\u02C6',
33091
            circeq: '\u2257',
33092
            circlearrowleft: '\u21BA',
33093
            circlearrowright: '\u21BB',
33094
            circledast: '\u229B',
33095
            circledcirc: '\u229A',
33096
            circleddash: '\u229D',
33097
            CircleDot: '\u2299',
33098
            circledR: '\u00AE',
33099
            circledS: '\u24C8',
33100
            CircleMinus: '\u2296',
33101
            CirclePlus: '\u2295',
33102
            CircleTimes: '\u2297',
33103
            cirE: '\u29C3',
33104
            cire: '\u2257',
33105
            cirfnint: '\u2A10',
33106
            cirmid: '\u2AEF',
33107
            cirscir: '\u29C2',
33108
            ClockwiseContourIntegral: '\u2232',
33109
            CloseCurlyDoubleQuote: '\u201D',
33110
            CloseCurlyQuote: '\u2019',
33111
            clubs: '\u2663',
33112
            clubsuit: '\u2663',
33113
            Colon: '\u2237',
33114
            colon: '\u003A',
33115
            Colone: '\u2A74',
33116
            colone: '\u2254',
33117
            coloneq: '\u2254',
33118
            comma: '\u002C',
33119
            commat: '\u0040',
33120
            comp: '\u2201',
33121
            compfn: '\u2218',
33122
            complement: '\u2201',
33123
            complexes: '\u2102',
33124
            cong: '\u2245',
33125
            congdot: '\u2A6D',
33126
            Congruent: '\u2261',
33127
            Conint: '\u222F',
33128
            conint: '\u222E',
33129
            ContourIntegral: '\u222E',
33130
            Copf: '\u2102',
33131
            copf: '\uD835\uDD54',
33132
            coprod: '\u2210',
33133
            Coproduct: '\u2210',
33134
            COPY: '\u00A9',
33135
            copy: '\u00A9',
33136
            copysr: '\u2117',
33137
            CounterClockwiseContourIntegral: '\u2233',
33138
            crarr: '\u21B5',
33139
            Cross: '\u2A2F',
33140
            cross: '\u2717',
33141
            Cscr: '\uD835\uDC9E',
33142
            cscr: '\uD835\uDCB8',
33143
            csub: '\u2ACF',
33144
            csube: '\u2AD1',
33145
            csup: '\u2AD0',
33146
            csupe: '\u2AD2',
33147
            ctdot: '\u22EF',
33148
            cudarrl: '\u2938',
33149
            cudarrr: '\u2935',
33150
            cuepr: '\u22DE',
33151
            cuesc: '\u22DF',
33152
            cularr: '\u21B6',
33153
            cularrp: '\u293D',
33154
            Cup: '\u22D3',
33155
            cup: '\u222A',
33156
            cupbrcap: '\u2A48',
33157
            CupCap: '\u224D',
33158
            cupcap: '\u2A46',
33159
            cupcup: '\u2A4A',
33160
            cupdot: '\u228D',
33161
            cupor: '\u2A45',
33162
            cups: '\u222A\uFE00',
33163
            curarr: '\u21B7',
33164
            curarrm: '\u293C',
33165
            curlyeqprec: '\u22DE',
33166
            curlyeqsucc: '\u22DF',
33167
            curlyvee: '\u22CE',
33168
            curlywedge: '\u22CF',
33169
            curren: '\u00A4',
33170
            curvearrowleft: '\u21B6',
33171
            curvearrowright: '\u21B7',
33172
            cuvee: '\u22CE',
33173
            cuwed: '\u22CF',
33174
            cwconint: '\u2232',
33175
            cwint: '\u2231',
33176
            cylcty: '\u232D',
33177
            Dagger: '\u2021',
33178
            dagger: '\u2020',
33179
            daleth: '\u2138',
33180
            Darr: '\u21A1',
33181
            dArr: '\u21D3',
33182
            darr: '\u2193',
33183
            dash: '\u2010',
33184
            Dashv: '\u2AE4',
33185
            dashv: '\u22A3',
33186
            dbkarow: '\u290F',
33187
            dblac: '\u02DD',
33188
            Dcaron: '\u010E',
33189
            dcaron: '\u010F',
33190
            Dcy: '\u0414',
33191
            dcy: '\u0434',
33192
            DD: '\u2145',
33193
            dd: '\u2146',
33194
            ddagger: '\u2021',
33195
            ddarr: '\u21CA',
33196
            DDotrahd: '\u2911',
33197
            ddotseq: '\u2A77',
33198
            deg: '\u00B0',
33199
            Del: '\u2207',
33200
            Delta: '\u0394',
33201
            delta: '\u03B4',
33202
            demptyv: '\u29B1',
33203
            dfisht: '\u297F',
33204
            Dfr: '\uD835\uDD07',
33205
            dfr: '\uD835\uDD21',
33206
            dHar: '\u2965',
33207
            dharl: '\u21C3',
33208
            dharr: '\u21C2',
33209
            DiacriticalAcute: '\u00B4',
33210
            DiacriticalDot: '\u02D9',
33211
            DiacriticalDoubleAcute: '\u02DD',
33212
            DiacriticalGrave: '\u0060',
33213
            DiacriticalTilde: '\u02DC',
33214
            diam: '\u22C4',
33215
            Diamond: '\u22C4',
33216
            diamond: '\u22C4',
33217
            diamondsuit: '\u2666',
33218
            diams: '\u2666',
33219
            die: '\u00A8',
33220
            DifferentialD: '\u2146',
33221
            digamma: '\u03DD',
33222
            disin: '\u22F2',
33223
            div: '\u00F7',
33224
            divide: '\u00F7',
33225
            divideontimes: '\u22C7',
33226
            divonx: '\u22C7',
33227
            DJcy: '\u0402',
33228
            djcy: '\u0452',
33229
            dlcorn: '\u231E',
33230
            dlcrop: '\u230D',
33231
            dollar: '\u0024',
33232
            Dopf: '\uD835\uDD3B',
33233
            dopf: '\uD835\uDD55',
33234
            Dot: '\u00A8',
33235
            dot: '\u02D9',
33236
            DotDot: '\u20DC',
33237
            doteq: '\u2250',
33238
            doteqdot: '\u2251',
33239
            DotEqual: '\u2250',
33240
            dotminus: '\u2238',
33241
            dotplus: '\u2214',
33242
            dotsquare: '\u22A1',
33243
            doublebarwedge: '\u2306',
33244
            DoubleContourIntegral: '\u222F',
33245
            DoubleDot: '\u00A8',
33246
            DoubleDownArrow: '\u21D3',
33247
            DoubleLeftArrow: '\u21D0',
33248
            DoubleLeftRightArrow: '\u21D4',
33249
            DoubleLeftTee: '\u2AE4',
33250
            DoubleLongLeftArrow: '\u27F8',
33251
            DoubleLongLeftRightArrow: '\u27FA',
33252
            DoubleLongRightArrow: '\u27F9',
33253
            DoubleRightArrow: '\u21D2',
33254
            DoubleRightTee: '\u22A8',
33255
            DoubleUpArrow: '\u21D1',
33256
            DoubleUpDownArrow: '\u21D5',
33257
            DoubleVerticalBar: '\u2225',
33258
            DownArrow: '\u2193',
33259
            Downarrow: '\u21D3',
33260
            downarrow: '\u2193',
33261
            DownArrowBar: '\u2913',
33262
            DownArrowUpArrow: '\u21F5',
33263
            DownBreve: '\u0311',
33264
            downdownarrows: '\u21CA',
33265
            downharpoonleft: '\u21C3',
33266
            downharpoonright: '\u21C2',
33267
            DownLeftRightVector: '\u2950',
33268
            DownLeftTeeVector: '\u295E',
33269
            DownLeftVector: '\u21BD',
33270
            DownLeftVectorBar: '\u2956',
33271
            DownRightTeeVector: '\u295F',
33272
            DownRightVector: '\u21C1',
33273
            DownRightVectorBar: '\u2957',
33274
            DownTee: '\u22A4',
33275
            DownTeeArrow: '\u21A7',
33276
            drbkarow: '\u2910',
33277
            drcorn: '\u231F',
33278
            drcrop: '\u230C',
33279
            Dscr: '\uD835\uDC9F',
33280
            dscr: '\uD835\uDCB9',
33281
            DScy: '\u0405',
33282
            dscy: '\u0455',
33283
            dsol: '\u29F6',
33284
            Dstrok: '\u0110',
33285
            dstrok: '\u0111',
33286
            dtdot: '\u22F1',
33287
            dtri: '\u25BF',
33288
            dtrif: '\u25BE',
33289
            duarr: '\u21F5',
33290
            duhar: '\u296F',
33291
            dwangle: '\u29A6',
33292
            DZcy: '\u040F',
33293
            dzcy: '\u045F',
33294
            dzigrarr: '\u27FF',
33295
            Eacute: '\u00C9',
33296
            eacute: '\u00E9',
33297
            easter: '\u2A6E',
33298
            Ecaron: '\u011A',
33299
            ecaron: '\u011B',
33300
            ecir: '\u2256',
33301
            Ecirc: '\u00CA',
33302
            ecirc: '\u00EA',
33303
            ecolon: '\u2255',
33304
            Ecy: '\u042D',
33305
            ecy: '\u044D',
33306
            eDDot: '\u2A77',
33307
            Edot: '\u0116',
33308
            eDot: '\u2251',
33309
            edot: '\u0117',
33310
            ee: '\u2147',
33311
            efDot: '\u2252',
33312
            Efr: '\uD835\uDD08',
33313
            efr: '\uD835\uDD22',
33314
            eg: '\u2A9A',
33315
            Egrave: '\u00C8',
33316
            egrave: '\u00E8',
33317
            egs: '\u2A96',
33318
            egsdot: '\u2A98',
33319
            el: '\u2A99',
33320
            Element: '\u2208',
33321
            elinters: '\u23E7',
33322
            ell: '\u2113',
33323
            els: '\u2A95',
33324
            elsdot: '\u2A97',
33325
            Emacr: '\u0112',
33326
            emacr: '\u0113',
33327
            empty: '\u2205',
33328
            emptyset: '\u2205',
33329
            EmptySmallSquare: '\u25FB',
33330
            emptyv: '\u2205',
33331
            EmptyVerySmallSquare: '\u25AB',
33332
            emsp: '\u2003',
33333
            emsp13: '\u2004',
33334
            emsp14: '\u2005',
33335
            ENG: '\u014A',
33336
            eng: '\u014B',
33337
            ensp: '\u2002',
33338
            Eogon: '\u0118',
33339
            eogon: '\u0119',
33340
            Eopf: '\uD835\uDD3C',
33341
            eopf: '\uD835\uDD56',
33342
            epar: '\u22D5',
33343
            eparsl: '\u29E3',
33344
            eplus: '\u2A71',
33345
            epsi: '\u03B5',
33346
            Epsilon: '\u0395',
33347
            epsilon: '\u03B5',
33348
            epsiv: '\u03F5',
33349
            eqcirc: '\u2256',
33350
            eqcolon: '\u2255',
33351
            eqsim: '\u2242',
33352
            eqslantgtr: '\u2A96',
33353
            eqslantless: '\u2A95',
33354
            Equal: '\u2A75',
33355
            equals: '\u003D',
33356
            EqualTilde: '\u2242',
33357
            equest: '\u225F',
33358
            Equilibrium: '\u21CC',
33359
            equiv: '\u2261',
33360
            equivDD: '\u2A78',
33361
            eqvparsl: '\u29E5',
33362
            erarr: '\u2971',
33363
            erDot: '\u2253',
33364
            Escr: '\u2130',
33365
            escr: '\u212F',
33366
            esdot: '\u2250',
33367
            Esim: '\u2A73',
33368
            esim: '\u2242',
33369
            Eta: '\u0397',
33370
            eta: '\u03B7',
33371
            ETH: '\u00D0',
33372
            eth: '\u00F0',
33373
            Euml: '\u00CB',
33374
            euml: '\u00EB',
33375
            euro: '\u20AC',
33376
            excl: '\u0021',
33377
            exist: '\u2203',
33378
            Exists: '\u2203',
33379
            expectation: '\u2130',
33380
            ExponentialE: '\u2147',
33381
            exponentiale: '\u2147',
33382
            fallingdotseq: '\u2252',
33383
            Fcy: '\u0424',
33384
            fcy: '\u0444',
33385
            female: '\u2640',
33386
            ffilig: '\uFB03',
33387
            fflig: '\uFB00',
33388
            ffllig: '\uFB04',
33389
            Ffr: '\uD835\uDD09',
33390
            ffr: '\uD835\uDD23',
33391
            filig: '\uFB01',
33392
            FilledSmallSquare: '\u25FC',
33393
            FilledVerySmallSquare: '\u25AA',
33394
            fjlig: '\u0066\u006A',
33395
            flat: '\u266D',
33396
            fllig: '\uFB02',
33397
            fltns: '\u25B1',
33398
            fnof: '\u0192',
33399
            Fopf: '\uD835\uDD3D',
33400
            fopf: '\uD835\uDD57',
33401
            ForAll: '\u2200',
33402
            forall: '\u2200',
33403
            fork: '\u22D4',
33404
            forkv: '\u2AD9',
33405
            Fouriertrf: '\u2131',
33406
            fpartint: '\u2A0D',
33407
            frac12: '\u00BD',
33408
            frac13: '\u2153',
33409
            frac14: '\u00BC',
33410
            frac15: '\u2155',
33411
            frac16: '\u2159',
33412
            frac18: '\u215B',
33413
            frac23: '\u2154',
33414
            frac25: '\u2156',
33415
            frac34: '\u00BE',
33416
            frac35: '\u2157',
33417
            frac38: '\u215C',
33418
            frac45: '\u2158',
33419
            frac56: '\u215A',
33420
            frac58: '\u215D',
33421
            frac78: '\u215E',
33422
            frasl: '\u2044',
33423
            frown: '\u2322',
33424
            Fscr: '\u2131',
33425
            fscr: '\uD835\uDCBB',
33426
            gacute: '\u01F5',
33427
            Gamma: '\u0393',
33428
            gamma: '\u03B3',
33429
            Gammad: '\u03DC',
33430
            gammad: '\u03DD',
33431
            gap: '\u2A86',
33432
            Gbreve: '\u011E',
33433
            gbreve: '\u011F',
33434
            Gcedil: '\u0122',
33435
            Gcirc: '\u011C',
33436
            gcirc: '\u011D',
33437
            Gcy: '\u0413',
33438
            gcy: '\u0433',
33439
            Gdot: '\u0120',
33440
            gdot: '\u0121',
33441
            gE: '\u2267',
33442
            ge: '\u2265',
33443
            gEl: '\u2A8C',
33444
            gel: '\u22DB',
33445
            geq: '\u2265',
33446
            geqq: '\u2267',
33447
            geqslant: '\u2A7E',
33448
            ges: '\u2A7E',
33449
            gescc: '\u2AA9',
33450
            gesdot: '\u2A80',
33451
            gesdoto: '\u2A82',
33452
            gesdotol: '\u2A84',
33453
            gesl: '\u22DB\uFE00',
33454
            gesles: '\u2A94',
33455
            Gfr: '\uD835\uDD0A',
33456
            gfr: '\uD835\uDD24',
33457
            Gg: '\u22D9',
33458
            gg: '\u226B',
33459
            ggg: '\u22D9',
33460
            gimel: '\u2137',
33461
            GJcy: '\u0403',
33462
            gjcy: '\u0453',
33463
            gl: '\u2277',
33464
            gla: '\u2AA5',
33465
            glE: '\u2A92',
33466
            glj: '\u2AA4',
33467
            gnap: '\u2A8A',
33468
            gnapprox: '\u2A8A',
33469
            gnE: '\u2269',
33470
            gne: '\u2A88',
33471
            gneq: '\u2A88',
33472
            gneqq: '\u2269',
33473
            gnsim: '\u22E7',
33474
            Gopf: '\uD835\uDD3E',
33475
            gopf: '\uD835\uDD58',
33476
            grave: '\u0060',
33477
            GreaterEqual: '\u2265',
33478
            GreaterEqualLess: '\u22DB',
33479
            GreaterFullEqual: '\u2267',
33480
            GreaterGreater: '\u2AA2',
33481
            GreaterLess: '\u2277',
33482
            GreaterSlantEqual: '\u2A7E',
33483
            GreaterTilde: '\u2273',
33484
            Gscr: '\uD835\uDCA2',
33485
            gscr: '\u210A',
33486
            gsim: '\u2273',
33487
            gsime: '\u2A8E',
33488
            gsiml: '\u2A90',
33489
            Gt: '\u226B',
33490
            GT: '\u003E',
33491
            gt: '\u003E',
33492
            gtcc: '\u2AA7',
33493
            gtcir: '\u2A7A',
33494
            gtdot: '\u22D7',
33495
            gtlPar: '\u2995',
33496
            gtquest: '\u2A7C',
33497
            gtrapprox: '\u2A86',
33498
            gtrarr: '\u2978',
33499
            gtrdot: '\u22D7',
33500
            gtreqless: '\u22DB',
33501
            gtreqqless: '\u2A8C',
33502
            gtrless: '\u2277',
33503
            gtrsim: '\u2273',
33504
            gvertneqq: '\u2269\uFE00',
33505
            gvnE: '\u2269\uFE00',
33506
            Hacek: '\u02C7',
33507
            hairsp: '\u200A',
33508
            half: '\u00BD',
33509
            hamilt: '\u210B',
33510
            HARDcy: '\u042A',
33511
            hardcy: '\u044A',
33512
            hArr: '\u21D4',
33513
            harr: '\u2194',
33514
            harrcir: '\u2948',
33515
            harrw: '\u21AD',
33516
            Hat: '\u005E',
33517
            hbar: '\u210F',
33518
            Hcirc: '\u0124',
33519
            hcirc: '\u0125',
33520
            hearts: '\u2665',
33521
            heartsuit: '\u2665',
33522
            hellip: '\u2026',
33523
            hercon: '\u22B9',
33524
            Hfr: '\u210C',
33525
            hfr: '\uD835\uDD25',
33526
            HilbertSpace: '\u210B',
33527
            hksearow: '\u2925',
33528
            hkswarow: '\u2926',
33529
            hoarr: '\u21FF',
33530
            homtht: '\u223B',
33531
            hookleftarrow: '\u21A9',
33532
            hookrightarrow: '\u21AA',
33533
            Hopf: '\u210D',
33534
            hopf: '\uD835\uDD59',
33535
            horbar: '\u2015',
33536
            HorizontalLine: '\u2500',
33537
            Hscr: '\u210B',
33538
            hscr: '\uD835\uDCBD',
33539
            hslash: '\u210F',
33540
            Hstrok: '\u0126',
33541
            hstrok: '\u0127',
33542
            HumpDownHump: '\u224E',
33543
            HumpEqual: '\u224F',
33544
            hybull: '\u2043',
33545
            hyphen: '\u2010',
33546
            Iacute: '\u00CD',
33547
            iacute: '\u00ED',
33548
            ic: '\u2063',
33549
            Icirc: '\u00CE',
33550
            icirc: '\u00EE',
33551
            Icy: '\u0418',
33552
            icy: '\u0438',
33553
            Idot: '\u0130',
33554
            IEcy: '\u0415',
33555
            iecy: '\u0435',
33556
            iexcl: '\u00A1',
33557
            iff: '\u21D4',
33558
            Ifr: '\u2111',
33559
            ifr: '\uD835\uDD26',
33560
            Igrave: '\u00CC',
33561
            igrave: '\u00EC',
33562
            ii: '\u2148',
33563
            iiiint: '\u2A0C',
33564
            iiint: '\u222D',
33565
            iinfin: '\u29DC',
33566
            iiota: '\u2129',
33567
            IJlig: '\u0132',
33568
            ijlig: '\u0133',
33569
            Im: '\u2111',
33570
            Imacr: '\u012A',
33571
            imacr: '\u012B',
33572
            image: '\u2111',
33573
            ImaginaryI: '\u2148',
33574
            imagline: '\u2110',
33575
            imagpart: '\u2111',
33576
            imath: '\u0131',
33577
            imof: '\u22B7',
33578
            imped: '\u01B5',
33579
            Implies: '\u21D2',
33580
            in: '\u2208',
33581
            incare: '\u2105',
33582
            infin: '\u221E',
33583
            infintie: '\u29DD',
33584
            inodot: '\u0131',
33585
            Int: '\u222C',
33586
            int: '\u222B',
33587
            intcal: '\u22BA',
33588
            integers: '\u2124',
33589
            Integral: '\u222B',
33590
            intercal: '\u22BA',
33591
            Intersection: '\u22C2',
33592
            intlarhk: '\u2A17',
33593
            intprod: '\u2A3C',
33594
            InvisibleComma: '\u2063',
33595
            InvisibleTimes: '\u2062',
33596
            IOcy: '\u0401',
33597
            iocy: '\u0451',
33598
            Iogon: '\u012E',
33599
            iogon: '\u012F',
33600
            Iopf: '\uD835\uDD40',
33601
            iopf: '\uD835\uDD5A',
33602
            Iota: '\u0399',
33603
            iota: '\u03B9',
33604
            iprod: '\u2A3C',
33605
            iquest: '\u00BF',
33606
            Iscr: '\u2110',
33607
            iscr: '\uD835\uDCBE',
33608
            isin: '\u2208',
33609
            isindot: '\u22F5',
33610
            isinE: '\u22F9',
33611
            isins: '\u22F4',
33612
            isinsv: '\u22F3',
33613
            isinv: '\u2208',
33614
            it: '\u2062',
33615
            Itilde: '\u0128',
33616
            itilde: '\u0129',
33617
            Iukcy: '\u0406',
33618
            iukcy: '\u0456',
33619
            Iuml: '\u00CF',
33620
            iuml: '\u00EF',
33621
            Jcirc: '\u0134',
33622
            jcirc: '\u0135',
33623
            Jcy: '\u0419',
33624
            jcy: '\u0439',
33625
            Jfr: '\uD835\uDD0D',
33626
            jfr: '\uD835\uDD27',
33627
            jmath: '\u0237',
33628
            Jopf: '\uD835\uDD41',
33629
            jopf: '\uD835\uDD5B',
33630
            Jscr: '\uD835\uDCA5',
33631
            jscr: '\uD835\uDCBF',
33632
            Jsercy: '\u0408',
33633
            jsercy: '\u0458',
33634
            Jukcy: '\u0404',
33635
            jukcy: '\u0454',
33636
            Kappa: '\u039A',
33637
            kappa: '\u03BA',
33638
            kappav: '\u03F0',
33639
            Kcedil: '\u0136',
33640
            kcedil: '\u0137',
33641
            Kcy: '\u041A',
33642
            kcy: '\u043A',
33643
            Kfr: '\uD835\uDD0E',
33644
            kfr: '\uD835\uDD28',
33645
            kgreen: '\u0138',
33646
            KHcy: '\u0425',
33647
            khcy: '\u0445',
33648
            KJcy: '\u040C',
33649
            kjcy: '\u045C',
33650
            Kopf: '\uD835\uDD42',
33651
            kopf: '\uD835\uDD5C',
33652
            Kscr: '\uD835\uDCA6',
33653
            kscr: '\uD835\uDCC0',
33654
            lAarr: '\u21DA',
33655
            Lacute: '\u0139',
33656
            lacute: '\u013A',
33657
            laemptyv: '\u29B4',
33658
            lagran: '\u2112',
33659
            Lambda: '\u039B',
33660
            lambda: '\u03BB',
33661
            Lang: '\u27EA',
33662
            lang: '\u27E8',
33663
            langd: '\u2991',
33664
            langle: '\u27E8',
33665
            lap: '\u2A85',
33666
            Laplacetrf: '\u2112',
33667
            laquo: '\u00AB',
33668
            Larr: '\u219E',
33669
            lArr: '\u21D0',
33670
            larr: '\u2190',
33671
            larrb: '\u21E4',
33672
            larrbfs: '\u291F',
33673
            larrfs: '\u291D',
33674
            larrhk: '\u21A9',
33675
            larrlp: '\u21AB',
33676
            larrpl: '\u2939',
33677
            larrsim: '\u2973',
33678
            larrtl: '\u21A2',
33679
            lat: '\u2AAB',
33680
            lAtail: '\u291B',
33681
            latail: '\u2919',
33682
            late: '\u2AAD',
33683
            lates: '\u2AAD\uFE00',
33684
            lBarr: '\u290E',
33685
            lbarr: '\u290C',
33686
            lbbrk: '\u2772',
33687
            lbrace: '\u007B',
33688
            lbrack: '\u005B',
33689
            lbrke: '\u298B',
33690
            lbrksld: '\u298F',
33691
            lbrkslu: '\u298D',
33692
            Lcaron: '\u013D',
33693
            lcaron: '\u013E',
33694
            Lcedil: '\u013B',
33695
            lcedil: '\u013C',
33696
            lceil: '\u2308',
33697
            lcub: '\u007B',
33698
            Lcy: '\u041B',
33699
            lcy: '\u043B',
33700
            ldca: '\u2936',
33701
            ldquo: '\u201C',
33702
            ldquor: '\u201E',
33703
            ldrdhar: '\u2967',
33704
            ldrushar: '\u294B',
33705
            ldsh: '\u21B2',
33706
            lE: '\u2266',
33707
            le: '\u2264',
33708
            LeftAngleBracket: '\u27E8',
33709
            LeftArrow: '\u2190',
33710
            Leftarrow: '\u21D0',
33711
            leftarrow: '\u2190',
33712
            LeftArrowBar: '\u21E4',
33713
            LeftArrowRightArrow: '\u21C6',
33714
            leftarrowtail: '\u21A2',
33715
            LeftCeiling: '\u2308',
33716
            LeftDoubleBracket: '\u27E6',
33717
            LeftDownTeeVector: '\u2961',
33718
            LeftDownVector: '\u21C3',
33719
            LeftDownVectorBar: '\u2959',
33720
            LeftFloor: '\u230A',
33721
            leftharpoondown: '\u21BD',
33722
            leftharpoonup: '\u21BC',
33723
            leftleftarrows: '\u21C7',
33724
            LeftRightArrow: '\u2194',
33725
            Leftrightarrow: '\u21D4',
33726
            leftrightarrow: '\u2194',
33727
            leftrightarrows: '\u21C6',
33728
            leftrightharpoons: '\u21CB',
33729
            leftrightsquigarrow: '\u21AD',
33730
            LeftRightVector: '\u294E',
33731
            LeftTee: '\u22A3',
33732
            LeftTeeArrow: '\u21A4',
33733
            LeftTeeVector: '\u295A',
33734
            leftthreetimes: '\u22CB',
33735
            LeftTriangle: '\u22B2',
33736
            LeftTriangleBar: '\u29CF',
33737
            LeftTriangleEqual: '\u22B4',
33738
            LeftUpDownVector: '\u2951',
33739
            LeftUpTeeVector: '\u2960',
33740
            LeftUpVector: '\u21BF',
33741
            LeftUpVectorBar: '\u2958',
33742
            LeftVector: '\u21BC',
33743
            LeftVectorBar: '\u2952',
33744
            lEg: '\u2A8B',
33745
            leg: '\u22DA',
33746
            leq: '\u2264',
33747
            leqq: '\u2266',
33748
            leqslant: '\u2A7D',
33749
            les: '\u2A7D',
33750
            lescc: '\u2AA8',
33751
            lesdot: '\u2A7F',
33752
            lesdoto: '\u2A81',
33753
            lesdotor: '\u2A83',
33754
            lesg: '\u22DA\uFE00',
33755
            lesges: '\u2A93',
33756
            lessapprox: '\u2A85',
33757
            lessdot: '\u22D6',
33758
            lesseqgtr: '\u22DA',
33759
            lesseqqgtr: '\u2A8B',
33760
            LessEqualGreater: '\u22DA',
33761
            LessFullEqual: '\u2266',
33762
            LessGreater: '\u2276',
33763
            lessgtr: '\u2276',
33764
            LessLess: '\u2AA1',
33765
            lesssim: '\u2272',
33766
            LessSlantEqual: '\u2A7D',
33767
            LessTilde: '\u2272',
33768
            lfisht: '\u297C',
33769
            lfloor: '\u230A',
33770
            Lfr: '\uD835\uDD0F',
33771
            lfr: '\uD835\uDD29',
33772
            lg: '\u2276',
33773
            lgE: '\u2A91',
33774
            lHar: '\u2962',
33775
            lhard: '\u21BD',
33776
            lharu: '\u21BC',
33777
            lharul: '\u296A',
33778
            lhblk: '\u2584',
33779
            LJcy: '\u0409',
33780
            ljcy: '\u0459',
33781
            Ll: '\u22D8',
33782
            ll: '\u226A',
33783
            llarr: '\u21C7',
33784
            llcorner: '\u231E',
33785
            Lleftarrow: '\u21DA',
33786
            llhard: '\u296B',
33787
            lltri: '\u25FA',
33788
            Lmidot: '\u013F',
33789
            lmidot: '\u0140',
33790
            lmoust: '\u23B0',
33791
            lmoustache: '\u23B0',
33792
            lnap: '\u2A89',
33793
            lnapprox: '\u2A89',
33794
            lnE: '\u2268',
33795
            lne: '\u2A87',
33796
            lneq: '\u2A87',
33797
            lneqq: '\u2268',
33798
            lnsim: '\u22E6',
33799
            loang: '\u27EC',
33800
            loarr: '\u21FD',
33801
            lobrk: '\u27E6',
33802
            LongLeftArrow: '\u27F5',
33803
            Longleftarrow: '\u27F8',
33804
            longleftarrow: '\u27F5',
33805
            LongLeftRightArrow: '\u27F7',
33806
            Longleftrightarrow: '\u27FA',
33807
            longleftrightarrow: '\u27F7',
33808
            longmapsto: '\u27FC',
33809
            LongRightArrow: '\u27F6',
33810
            Longrightarrow: '\u27F9',
33811
            longrightarrow: '\u27F6',
33812
            looparrowleft: '\u21AB',
33813
            looparrowright: '\u21AC',
33814
            lopar: '\u2985',
33815
            Lopf: '\uD835\uDD43',
33816
            lopf: '\uD835\uDD5D',
33817
            loplus: '\u2A2D',
33818
            lotimes: '\u2A34',
33819
            lowast: '\u2217',
33820
            lowbar: '\u005F',
33821
            LowerLeftArrow: '\u2199',
33822
            LowerRightArrow: '\u2198',
33823
            loz: '\u25CA',
33824
            lozenge: '\u25CA',
33825
            lozf: '\u29EB',
33826
            lpar: '\u0028',
33827
            lparlt: '\u2993',
33828
            lrarr: '\u21C6',
33829
            lrcorner: '\u231F',
33830
            lrhar: '\u21CB',
33831
            lrhard: '\u296D',
33832
            lrm: '\u200E',
33833
            lrtri: '\u22BF',
33834
            lsaquo: '\u2039',
33835
            Lscr: '\u2112',
33836
            lscr: '\uD835\uDCC1',
33837
            Lsh: '\u21B0',
33838
            lsh: '\u21B0',
33839
            lsim: '\u2272',
33840
            lsime: '\u2A8D',
33841
            lsimg: '\u2A8F',
33842
            lsqb: '\u005B',
33843
            lsquo: '\u2018',
33844
            lsquor: '\u201A',
33845
            Lstrok: '\u0141',
33846
            lstrok: '\u0142',
33847
            Lt: '\u226A',
33848
            LT: '\u003C',
33849
            lt: '\u003C',
33850
            ltcc: '\u2AA6',
33851
            ltcir: '\u2A79',
33852
            ltdot: '\u22D6',
33853
            lthree: '\u22CB',
33854
            ltimes: '\u22C9',
33855
            ltlarr: '\u2976',
33856
            ltquest: '\u2A7B',
33857
            ltri: '\u25C3',
33858
            ltrie: '\u22B4',
33859
            ltrif: '\u25C2',
33860
            ltrPar: '\u2996',
33861
            lurdshar: '\u294A',
33862
            luruhar: '\u2966',
33863
            lvertneqq: '\u2268\uFE00',
33864
            lvnE: '\u2268\uFE00',
33865
            macr: '\u00AF',
33866
            male: '\u2642',
33867
            malt: '\u2720',
33868
            maltese: '\u2720',
33869
            Map: '\u2905',
33870
            map: '\u21A6',
33871
            mapsto: '\u21A6',
33872
            mapstodown: '\u21A7',
33873
            mapstoleft: '\u21A4',
33874
            mapstoup: '\u21A5',
33875
            marker: '\u25AE',
33876
            mcomma: '\u2A29',
33877
            Mcy: '\u041C',
33878
            mcy: '\u043C',
33879
            mdash: '\u2014',
33880
            mDDot: '\u223A',
33881
            measuredangle: '\u2221',
33882
            MediumSpace: '\u205F',
33883
            Mellintrf: '\u2133',
33884
            Mfr: '\uD835\uDD10',
33885
            mfr: '\uD835\uDD2A',
33886
            mho: '\u2127',
33887
            micro: '\u00B5',
33888
            mid: '\u2223',
33889
            midast: '\u002A',
33890
            midcir: '\u2AF0',
33891
            middot: '\u00B7',
33892
            minus: '\u2212',
33893
            minusb: '\u229F',
33894
            minusd: '\u2238',
33895
            minusdu: '\u2A2A',
33896
            MinusPlus: '\u2213',
33897
            mlcp: '\u2ADB',
33898
            mldr: '\u2026',
33899
            mnplus: '\u2213',
33900
            models: '\u22A7',
33901
            Mopf: '\uD835\uDD44',
33902
            mopf: '\uD835\uDD5E',
33903
            mp: '\u2213',
33904
            Mscr: '\u2133',
33905
            mscr: '\uD835\uDCC2',
33906
            mstpos: '\u223E',
33907
            Mu: '\u039C',
33908
            mu: '\u03BC',
33909
            multimap: '\u22B8',
33910
            mumap: '\u22B8',
33911
            nabla: '\u2207',
33912
            Nacute: '\u0143',
33913
            nacute: '\u0144',
33914
            nang: '\u2220\u20D2',
33915
            nap: '\u2249',
33916
            napE: '\u2A70\u0338',
33917
            napid: '\u224B\u0338',
33918
            napos: '\u0149',
33919
            napprox: '\u2249',
33920
            natur: '\u266E',
33921
            natural: '\u266E',
33922
            naturals: '\u2115',
33923
            nbsp: '\u00A0',
33924
            nbump: '\u224E\u0338',
33925
            nbumpe: '\u224F\u0338',
33926
            ncap: '\u2A43',
33927
            Ncaron: '\u0147',
33928
            ncaron: '\u0148',
33929
            Ncedil: '\u0145',
33930
            ncedil: '\u0146',
33931
            ncong: '\u2247',
33932
            ncongdot: '\u2A6D\u0338',
33933
            ncup: '\u2A42',
33934
            Ncy: '\u041D',
33935
            ncy: '\u043D',
33936
            ndash: '\u2013',
33937
            ne: '\u2260',
33938
            nearhk: '\u2924',
33939
            neArr: '\u21D7',
33940
            nearr: '\u2197',
33941
            nearrow: '\u2197',
33942
            nedot: '\u2250\u0338',
33943
            NegativeMediumSpace: '\u200B',
33944
            NegativeThickSpace: '\u200B',
33945
            NegativeThinSpace: '\u200B',
33946
            NegativeVeryThinSpace: '\u200B',
33947
            nequiv: '\u2262',
33948
            nesear: '\u2928',
33949
            nesim: '\u2242\u0338',
33950
            NestedGreaterGreater: '\u226B',
33951
            NestedLessLess: '\u226A',
33952
            NewLine: '\u000A',
33953
            nexist: '\u2204',
33954
            nexists: '\u2204',
33955
            Nfr: '\uD835\uDD11',
33956
            nfr: '\uD835\uDD2B',
33957
            ngE: '\u2267\u0338',
33958
            nge: '\u2271',
33959
            ngeq: '\u2271',
33960
            ngeqq: '\u2267\u0338',
33961
            ngeqslant: '\u2A7E\u0338',
33962
            nges: '\u2A7E\u0338',
33963
            nGg: '\u22D9\u0338',
33964
            ngsim: '\u2275',
33965
            nGt: '\u226B\u20D2',
33966
            ngt: '\u226F',
33967
            ngtr: '\u226F',
33968
            nGtv: '\u226B\u0338',
33969
            nhArr: '\u21CE',
33970
            nharr: '\u21AE',
33971
            nhpar: '\u2AF2',
33972
            ni: '\u220B',
33973
            nis: '\u22FC',
33974
            nisd: '\u22FA',
33975
            niv: '\u220B',
33976
            NJcy: '\u040A',
33977
            njcy: '\u045A',
33978
            nlArr: '\u21CD',
33979
            nlarr: '\u219A',
33980
            nldr: '\u2025',
33981
            nlE: '\u2266\u0338',
33982
            nle: '\u2270',
33983
            nLeftarrow: '\u21CD',
33984
            nleftarrow: '\u219A',
33985
            nLeftrightarrow: '\u21CE',
33986
            nleftrightarrow: '\u21AE',
33987
            nleq: '\u2270',
33988
            nleqq: '\u2266\u0338',
33989
            nleqslant: '\u2A7D\u0338',
33990
            nles: '\u2A7D\u0338',
33991
            nless: '\u226E',
33992
            nLl: '\u22D8\u0338',
33993
            nlsim: '\u2274',
33994
            nLt: '\u226A\u20D2',
33995
            nlt: '\u226E',
33996
            nltri: '\u22EA',
33997
            nltrie: '\u22EC',
33998
            nLtv: '\u226A\u0338',
33999
            nmid: '\u2224',
34000
            NoBreak: '\u2060',
34001
            NonBreakingSpace: '\u00A0',
34002
            Nopf: '\u2115',
34003
            nopf: '\uD835\uDD5F',
34004
            Not: '\u2AEC',
34005
            not: '\u00AC',
34006
            NotCongruent: '\u2262',
34007
            NotCupCap: '\u226D',
34008
            NotDoubleVerticalBar: '\u2226',
34009
            NotElement: '\u2209',
34010
            NotEqual: '\u2260',
34011
            NotEqualTilde: '\u2242\u0338',
34012
            NotExists: '\u2204',
34013
            NotGreater: '\u226F',
34014
            NotGreaterEqual: '\u2271',
34015
            NotGreaterFullEqual: '\u2267\u0338',
34016
            NotGreaterGreater: '\u226B\u0338',
34017
            NotGreaterLess: '\u2279',
34018
            NotGreaterSlantEqual: '\u2A7E\u0338',
34019
            NotGreaterTilde: '\u2275',
34020
            NotHumpDownHump: '\u224E\u0338',
34021
            NotHumpEqual: '\u224F\u0338',
34022
            notin: '\u2209',
34023
            notindot: '\u22F5\u0338',
34024
            notinE: '\u22F9\u0338',
34025
            notinva: '\u2209',
34026
            notinvb: '\u22F7',
34027
            notinvc: '\u22F6',
34028
            NotLeftTriangle: '\u22EA',
34029
            NotLeftTriangleBar: '\u29CF\u0338',
34030
            NotLeftTriangleEqual: '\u22EC',
34031
            NotLess: '\u226E',
34032
            NotLessEqual: '\u2270',
34033
            NotLessGreater: '\u2278',
34034
            NotLessLess: '\u226A\u0338',
34035
            NotLessSlantEqual: '\u2A7D\u0338',
34036
            NotLessTilde: '\u2274',
34037
            NotNestedGreaterGreater: '\u2AA2\u0338',
34038
            NotNestedLessLess: '\u2AA1\u0338',
34039
            notni: '\u220C',
34040
            notniva: '\u220C',
34041
            notnivb: '\u22FE',
34042
            notnivc: '\u22FD',
34043
            NotPrecedes: '\u2280',
34044
            NotPrecedesEqual: '\u2AAF\u0338',
34045
            NotPrecedesSlantEqual: '\u22E0',
34046
            NotReverseElement: '\u220C',
34047
            NotRightTriangle: '\u22EB',
34048
            NotRightTriangleBar: '\u29D0\u0338',
34049
            NotRightTriangleEqual: '\u22ED',
34050
            NotSquareSubset: '\u228F\u0338',
34051
            NotSquareSubsetEqual: '\u22E2',
34052
            NotSquareSuperset: '\u2290\u0338',
34053
            NotSquareSupersetEqual: '\u22E3',
34054
            NotSubset: '\u2282\u20D2',
34055
            NotSubsetEqual: '\u2288',
34056
            NotSucceeds: '\u2281',
34057
            NotSucceedsEqual: '\u2AB0\u0338',
34058
            NotSucceedsSlantEqual: '\u22E1',
34059
            NotSucceedsTilde: '\u227F\u0338',
34060
            NotSuperset: '\u2283\u20D2',
34061
            NotSupersetEqual: '\u2289',
34062
            NotTilde: '\u2241',
34063
            NotTildeEqual: '\u2244',
34064
            NotTildeFullEqual: '\u2247',
34065
            NotTildeTilde: '\u2249',
34066
            NotVerticalBar: '\u2224',
34067
            npar: '\u2226',
34068
            nparallel: '\u2226',
34069
            nparsl: '\u2AFD\u20E5',
34070
            npart: '\u2202\u0338',
34071
            npolint: '\u2A14',
34072
            npr: '\u2280',
34073
            nprcue: '\u22E0',
34074
            npre: '\u2AAF\u0338',
34075
            nprec: '\u2280',
34076
            npreceq: '\u2AAF\u0338',
34077
            nrArr: '\u21CF',
34078
            nrarr: '\u219B',
34079
            nrarrc: '\u2933\u0338',
34080
            nrarrw: '\u219D\u0338',
34081
            nRightarrow: '\u21CF',
34082
            nrightarrow: '\u219B',
34083
            nrtri: '\u22EB',
34084
            nrtrie: '\u22ED',
34085
            nsc: '\u2281',
34086
            nsccue: '\u22E1',
34087
            nsce: '\u2AB0\u0338',
34088
            Nscr: '\uD835\uDCA9',
34089
            nscr: '\uD835\uDCC3',
34090
            nshortmid: '\u2224',
34091
            nshortparallel: '\u2226',
34092
            nsim: '\u2241',
34093
            nsime: '\u2244',
34094
            nsimeq: '\u2244',
34095
            nsmid: '\u2224',
34096
            nspar: '\u2226',
34097
            nsqsube: '\u22E2',
34098
            nsqsupe: '\u22E3',
34099
            nsub: '\u2284',
34100
            nsubE: '\u2AC5\u0338',
34101
            nsube: '\u2288',
34102
            nsubset: '\u2282\u20D2',
34103
            nsubseteq: '\u2288',
34104
            nsubseteqq: '\u2AC5\u0338',
34105
            nsucc: '\u2281',
34106
            nsucceq: '\u2AB0\u0338',
34107
            nsup: '\u2285',
34108
            nsupE: '\u2AC6\u0338',
34109
            nsupe: '\u2289',
34110
            nsupset: '\u2283\u20D2',
34111
            nsupseteq: '\u2289',
34112
            nsupseteqq: '\u2AC6\u0338',
34113
            ntgl: '\u2279',
34114
            Ntilde: '\u00D1',
34115
            ntilde: '\u00F1',
34116
            ntlg: '\u2278',
34117
            ntriangleleft: '\u22EA',
34118
            ntrianglelefteq: '\u22EC',
34119
            ntriangleright: '\u22EB',
34120
            ntrianglerighteq: '\u22ED',
34121
            Nu: '\u039D',
34122
            nu: '\u03BD',
34123
            num: '\u0023',
34124
            numero: '\u2116',
34125
            numsp: '\u2007',
34126
            nvap: '\u224D\u20D2',
34127
            nVDash: '\u22AF',
34128
            nVdash: '\u22AE',
34129
            nvDash: '\u22AD',
34130
            nvdash: '\u22AC',
34131
            nvge: '\u2265\u20D2',
34132
            nvgt: '\u003E\u20D2',
34133
            nvHarr: '\u2904',
34134
            nvinfin: '\u29DE',
34135
            nvlArr: '\u2902',
34136
            nvle: '\u2264\u20D2',
34137
            nvlt: '\u003C\u20D2',
34138
            nvltrie: '\u22B4\u20D2',
34139
            nvrArr: '\u2903',
34140
            nvrtrie: '\u22B5\u20D2',
34141
            nvsim: '\u223C\u20D2',
34142
            nwarhk: '\u2923',
34143
            nwArr: '\u21D6',
34144
            nwarr: '\u2196',
34145
            nwarrow: '\u2196',
34146
            nwnear: '\u2927',
34147
            Oacute: '\u00D3',
34148
            oacute: '\u00F3',
34149
            oast: '\u229B',
34150
            ocir: '\u229A',
34151
            Ocirc: '\u00D4',
34152
            ocirc: '\u00F4',
34153
            Ocy: '\u041E',
34154
            ocy: '\u043E',
34155
            odash: '\u229D',
34156
            Odblac: '\u0150',
34157
            odblac: '\u0151',
34158
            odiv: '\u2A38',
34159
            odot: '\u2299',
34160
            odsold: '\u29BC',
34161
            OElig: '\u0152',
34162
            oelig: '\u0153',
34163
            ofcir: '\u29BF',
34164
            Ofr: '\uD835\uDD12',
34165
            ofr: '\uD835\uDD2C',
34166
            ogon: '\u02DB',
34167
            Ograve: '\u00D2',
34168
            ograve: '\u00F2',
34169
            ogt: '\u29C1',
34170
            ohbar: '\u29B5',
34171
            ohm: '\u03A9',
34172
            oint: '\u222E',
34173
            olarr: '\u21BA',
34174
            olcir: '\u29BE',
34175
            olcross: '\u29BB',
34176
            oline: '\u203E',
34177
            olt: '\u29C0',
34178
            Omacr: '\u014C',
34179
            omacr: '\u014D',
34180
            Omega: '\u03A9',
34181
            omega: '\u03C9',
34182
            Omicron: '\u039F',
34183
            omicron: '\u03BF',
34184
            omid: '\u29B6',
34185
            ominus: '\u2296',
34186
            Oopf: '\uD835\uDD46',
34187
            oopf: '\uD835\uDD60',
34188
            opar: '\u29B7',
34189
            OpenCurlyDoubleQuote: '\u201C',
34190
            OpenCurlyQuote: '\u2018',
34191
            operp: '\u29B9',
34192
            oplus: '\u2295',
34193
            Or: '\u2A54',
34194
            or: '\u2228',
34195
            orarr: '\u21BB',
34196
            ord: '\u2A5D',
34197
            order: '\u2134',
34198
            orderof: '\u2134',
34199
            ordf: '\u00AA',
34200
            ordm: '\u00BA',
34201
            origof: '\u22B6',
34202
            oror: '\u2A56',
34203
            orslope: '\u2A57',
34204
            orv: '\u2A5B',
34205
            oS: '\u24C8',
34206
            Oscr: '\uD835\uDCAA',
34207
            oscr: '\u2134',
34208
            Oslash: '\u00D8',
34209
            oslash: '\u00F8',
34210
            osol: '\u2298',
34211
            Otilde: '\u00D5',
34212
            otilde: '\u00F5',
34213
            Otimes: '\u2A37',
34214
            otimes: '\u2297',
34215
            otimesas: '\u2A36',
34216
            Ouml: '\u00D6',
34217
            ouml: '\u00F6',
34218
            ovbar: '\u233D',
34219
            OverBar: '\u203E',
34220
            OverBrace: '\u23DE',
34221
            OverBracket: '\u23B4',
34222
            OverParenthesis: '\u23DC',
34223
            par: '\u2225',
34224
            para: '\u00B6',
34225
            parallel: '\u2225',
34226
            parsim: '\u2AF3',
34227
            parsl: '\u2AFD',
34228
            part: '\u2202',
34229
            PartialD: '\u2202',
34230
            Pcy: '\u041F',
34231
            pcy: '\u043F',
34232
            percnt: '\u0025',
34233
            period: '\u002E',
34234
            permil: '\u2030',
34235
            perp: '\u22A5',
34236
            pertenk: '\u2031',
34237
            Pfr: '\uD835\uDD13',
34238
            pfr: '\uD835\uDD2D',
34239
            Phi: '\u03A6',
34240
            phi: '\u03C6',
34241
            phiv: '\u03D5',
34242
            phmmat: '\u2133',
34243
            phone: '\u260E',
34244
            Pi: '\u03A0',
34245
            pi: '\u03C0',
34246
            pitchfork: '\u22D4',
34247
            piv: '\u03D6',
34248
            planck: '\u210F',
34249
            planckh: '\u210E',
34250
            plankv: '\u210F',
34251
            plus: '\u002B',
34252
            plusacir: '\u2A23',
34253
            plusb: '\u229E',
34254
            pluscir: '\u2A22',
34255
            plusdo: '\u2214',
34256
            plusdu: '\u2A25',
34257
            pluse: '\u2A72',
34258
            PlusMinus: '\u00B1',
34259
            plusmn: '\u00B1',
34260
            plussim: '\u2A26',
34261
            plustwo: '\u2A27',
34262
            pm: '\u00B1',
34263
            Poincareplane: '\u210C',
34264
            pointint: '\u2A15',
34265
            Popf: '\u2119',
34266
            popf: '\uD835\uDD61',
34267
            pound: '\u00A3',
34268
            Pr: '\u2ABB',
34269
            pr: '\u227A',
34270
            prap: '\u2AB7',
34271
            prcue: '\u227C',
34272
            prE: '\u2AB3',
34273
            pre: '\u2AAF',
34274
            prec: '\u227A',
34275
            precapprox: '\u2AB7',
34276
            preccurlyeq: '\u227C',
34277
            Precedes: '\u227A',
34278
            PrecedesEqual: '\u2AAF',
34279
            PrecedesSlantEqual: '\u227C',
34280
            PrecedesTilde: '\u227E',
34281
            preceq: '\u2AAF',
34282
            precnapprox: '\u2AB9',
34283
            precneqq: '\u2AB5',
34284
            precnsim: '\u22E8',
34285
            precsim: '\u227E',
34286
            Prime: '\u2033',
34287
            prime: '\u2032',
34288
            primes: '\u2119',
34289
            prnap: '\u2AB9',
34290
            prnE: '\u2AB5',
34291
            prnsim: '\u22E8',
34292
            prod: '\u220F',
34293
            Product: '\u220F',
34294
            profalar: '\u232E',
34295
            profline: '\u2312',
34296
            profsurf: '\u2313',
34297
            prop: '\u221D',
34298
            Proportion: '\u2237',
34299
            Proportional: '\u221D',
34300
            propto: '\u221D',
34301
            prsim: '\u227E',
34302
            prurel: '\u22B0',
34303
            Pscr: '\uD835\uDCAB',
34304
            pscr: '\uD835\uDCC5',
34305
            Psi: '\u03A8',
34306
            psi: '\u03C8',
34307
            puncsp: '\u2008',
34308
            Qfr: '\uD835\uDD14',
34309
            qfr: '\uD835\uDD2E',
34310
            qint: '\u2A0C',
34311
            Qopf: '\u211A',
34312
            qopf: '\uD835\uDD62',
34313
            qprime: '\u2057',
34314
            Qscr: '\uD835\uDCAC',
34315
            qscr: '\uD835\uDCC6',
34316
            quaternions: '\u210D',
34317
            quatint: '\u2A16',
34318
            quest: '\u003F',
34319
            questeq: '\u225F',
34320
            QUOT: '\u0022',
34321
            quot: '\u0022',
34322
            rAarr: '\u21DB',
34323
            race: '\u223D\u0331',
34324
            Racute: '\u0154',
34325
            racute: '\u0155',
34326
            radic: '\u221A',
34327
            raemptyv: '\u29B3',
34328
            Rang: '\u27EB',
34329
            rang: '\u27E9',
34330
            rangd: '\u2992',
34331
            range: '\u29A5',
34332
            rangle: '\u27E9',
34333
            raquo: '\u00BB',
34334
            Rarr: '\u21A0',
34335
            rArr: '\u21D2',
34336
            rarr: '\u2192',
34337
            rarrap: '\u2975',
34338
            rarrb: '\u21E5',
34339
            rarrbfs: '\u2920',
34340
            rarrc: '\u2933',
34341
            rarrfs: '\u291E',
34342
            rarrhk: '\u21AA',
34343
            rarrlp: '\u21AC',
34344
            rarrpl: '\u2945',
34345
            rarrsim: '\u2974',
34346
            Rarrtl: '\u2916',
34347
            rarrtl: '\u21A3',
34348
            rarrw: '\u219D',
34349
            rAtail: '\u291C',
34350
            ratail: '\u291A',
34351
            ratio: '\u2236',
34352
            rationals: '\u211A',
34353
            RBarr: '\u2910',
34354
            rBarr: '\u290F',
34355
            rbarr: '\u290D',
34356
            rbbrk: '\u2773',
34357
            rbrace: '\u007D',
34358
            rbrack: '\u005D',
34359
            rbrke: '\u298C',
34360
            rbrksld: '\u298E',
34361
            rbrkslu: '\u2990',
34362
            Rcaron: '\u0158',
34363
            rcaron: '\u0159',
34364
            Rcedil: '\u0156',
34365
            rcedil: '\u0157',
34366
            rceil: '\u2309',
34367
            rcub: '\u007D',
34368
            Rcy: '\u0420',
34369
            rcy: '\u0440',
34370
            rdca: '\u2937',
34371
            rdldhar: '\u2969',
34372
            rdquo: '\u201D',
34373
            rdquor: '\u201D',
34374
            rdsh: '\u21B3',
34375
            Re: '\u211C',
34376
            real: '\u211C',
34377
            realine: '\u211B',
34378
            realpart: '\u211C',
34379
            reals: '\u211D',
34380
            rect: '\u25AD',
34381
            REG: '\u00AE',
34382
            reg: '\u00AE',
34383
            ReverseElement: '\u220B',
34384
            ReverseEquilibrium: '\u21CB',
34385
            ReverseUpEquilibrium: '\u296F',
34386
            rfisht: '\u297D',
34387
            rfloor: '\u230B',
34388
            Rfr: '\u211C',
34389
            rfr: '\uD835\uDD2F',
34390
            rHar: '\u2964',
34391
            rhard: '\u21C1',
34392
            rharu: '\u21C0',
34393
            rharul: '\u296C',
34394
            Rho: '\u03A1',
34395
            rho: '\u03C1',
34396
            rhov: '\u03F1',
34397
            RightAngleBracket: '\u27E9',
34398
            RightArrow: '\u2192',
34399
            Rightarrow: '\u21D2',
34400
            rightarrow: '\u2192',
34401
            RightArrowBar: '\u21E5',
34402
            RightArrowLeftArrow: '\u21C4',
34403
            rightarrowtail: '\u21A3',
34404
            RightCeiling: '\u2309',
34405
            RightDoubleBracket: '\u27E7',
34406
            RightDownTeeVector: '\u295D',
34407
            RightDownVector: '\u21C2',
34408
            RightDownVectorBar: '\u2955',
34409
            RightFloor: '\u230B',
34410
            rightharpoondown: '\u21C1',
34411
            rightharpoonup: '\u21C0',
34412
            rightleftarrows: '\u21C4',
34413
            rightleftharpoons: '\u21CC',
34414
            rightrightarrows: '\u21C9',
34415
            rightsquigarrow: '\u219D',
34416
            RightTee: '\u22A2',
34417
            RightTeeArrow: '\u21A6',
34418
            RightTeeVector: '\u295B',
34419
            rightthreetimes: '\u22CC',
34420
            RightTriangle: '\u22B3',
34421
            RightTriangleBar: '\u29D0',
34422
            RightTriangleEqual: '\u22B5',
34423
            RightUpDownVector: '\u294F',
34424
            RightUpTeeVector: '\u295C',
34425
            RightUpVector: '\u21BE',
34426
            RightUpVectorBar: '\u2954',
34427
            RightVector: '\u21C0',
34428
            RightVectorBar: '\u2953',
34429
            ring: '\u02DA',
34430
            risingdotseq: '\u2253',
34431
            rlarr: '\u21C4',
34432
            rlhar: '\u21CC',
34433
            rlm: '\u200F',
34434
            rmoust: '\u23B1',
34435
            rmoustache: '\u23B1',
34436
            rnmid: '\u2AEE',
34437
            roang: '\u27ED',
34438
            roarr: '\u21FE',
34439
            robrk: '\u27E7',
34440
            ropar: '\u2986',
34441
            Ropf: '\u211D',
34442
            ropf: '\uD835\uDD63',
34443
            roplus: '\u2A2E',
34444
            rotimes: '\u2A35',
34445
            RoundImplies: '\u2970',
34446
            rpar: '\u0029',
34447
            rpargt: '\u2994',
34448
            rppolint: '\u2A12',
34449
            rrarr: '\u21C9',
34450
            Rrightarrow: '\u21DB',
34451
            rsaquo: '\u203A',
34452
            Rscr: '\u211B',
34453
            rscr: '\uD835\uDCC7',
34454
            Rsh: '\u21B1',
34455
            rsh: '\u21B1',
34456
            rsqb: '\u005D',
34457
            rsquo: '\u2019',
34458
            rsquor: '\u2019',
34459
            rthree: '\u22CC',
34460
            rtimes: '\u22CA',
34461
            rtri: '\u25B9',
34462
            rtrie: '\u22B5',
34463
            rtrif: '\u25B8',
34464
            rtriltri: '\u29CE',
34465
            RuleDelayed: '\u29F4',
34466
            ruluhar: '\u2968',
34467
            rx: '\u211E',
34468
            Sacute: '\u015A',
34469
            sacute: '\u015B',
34470
            sbquo: '\u201A',
34471
            Sc: '\u2ABC',
34472
            sc: '\u227B',
34473
            scap: '\u2AB8',
34474
            Scaron: '\u0160',
34475
            scaron: '\u0161',
34476
            sccue: '\u227D',
34477
            scE: '\u2AB4',
34478
            sce: '\u2AB0',
34479
            Scedil: '\u015E',
34480
            scedil: '\u015F',
34481
            Scirc: '\u015C',
34482
            scirc: '\u015D',
34483
            scnap: '\u2ABA',
34484
            scnE: '\u2AB6',
34485
            scnsim: '\u22E9',
34486
            scpolint: '\u2A13',
34487
            scsim: '\u227F',
34488
            Scy: '\u0421',
34489
            scy: '\u0441',
34490
            sdot: '\u22C5',
34491
            sdotb: '\u22A1',
34492
            sdote: '\u2A66',
34493
            searhk: '\u2925',
34494
            seArr: '\u21D8',
34495
            searr: '\u2198',
34496
            searrow: '\u2198',
34497
            sect: '\u00A7',
34498
            semi: '\u003B',
34499
            seswar: '\u2929',
34500
            setminus: '\u2216',
34501
            setmn: '\u2216',
34502
            sext: '\u2736',
34503
            Sfr: '\uD835\uDD16',
34504
            sfr: '\uD835\uDD30',
34505
            sfrown: '\u2322',
34506
            sharp: '\u266F',
34507
            SHCHcy: '\u0429',
34508
            shchcy: '\u0449',
34509
            SHcy: '\u0428',
34510
            shcy: '\u0448',
34511
            ShortDownArrow: '\u2193',
34512
            ShortLeftArrow: '\u2190',
34513
            shortmid: '\u2223',
34514
            shortparallel: '\u2225',
34515
            ShortRightArrow: '\u2192',
34516
            ShortUpArrow: '\u2191',
34517
            shy: '\u00AD',
34518
            Sigma: '\u03A3',
34519
            sigma: '\u03C3',
34520
            sigmaf: '\u03C2',
34521
            sigmav: '\u03C2',
34522
            sim: '\u223C',
34523
            simdot: '\u2A6A',
34524
            sime: '\u2243',
34525
            simeq: '\u2243',
34526
            simg: '\u2A9E',
34527
            simgE: '\u2AA0',
34528
            siml: '\u2A9D',
34529
            simlE: '\u2A9F',
34530
            simne: '\u2246',
34531
            simplus: '\u2A24',
34532
            simrarr: '\u2972',
34533
            slarr: '\u2190',
34534
            SmallCircle: '\u2218',
34535
            smallsetminus: '\u2216',
34536
            smashp: '\u2A33',
34537
            smeparsl: '\u29E4',
34538
            smid: '\u2223',
34539
            smile: '\u2323',
34540
            smt: '\u2AAA',
34541
            smte: '\u2AAC',
34542
            smtes: '\u2AAC\uFE00',
34543
            SOFTcy: '\u042C',
34544
            softcy: '\u044C',
34545
            sol: '\u002F',
34546
            solb: '\u29C4',
34547
            solbar: '\u233F',
34548
            Sopf: '\uD835\uDD4A',
34549
            sopf: '\uD835\uDD64',
34550
            spades: '\u2660',
34551
            spadesuit: '\u2660',
34552
            spar: '\u2225',
34553
            sqcap: '\u2293',
34554
            sqcaps: '\u2293\uFE00',
34555
            sqcup: '\u2294',
34556
            sqcups: '\u2294\uFE00',
34557
            Sqrt: '\u221A',
34558
            sqsub: '\u228F',
34559
            sqsube: '\u2291',
34560
            sqsubset: '\u228F',
34561
            sqsubseteq: '\u2291',
34562
            sqsup: '\u2290',
34563
            sqsupe: '\u2292',
34564
            sqsupset: '\u2290',
34565
            sqsupseteq: '\u2292',
34566
            squ: '\u25A1',
34567
            Square: '\u25A1',
34568
            square: '\u25A1',
34569
            SquareIntersection: '\u2293',
34570
            SquareSubset: '\u228F',
34571
            SquareSubsetEqual: '\u2291',
34572
            SquareSuperset: '\u2290',
34573
            SquareSupersetEqual: '\u2292',
34574
            SquareUnion: '\u2294',
34575
            squarf: '\u25AA',
34576
            squf: '\u25AA',
34577
            srarr: '\u2192',
34578
            Sscr: '\uD835\uDCAE',
34579
            sscr: '\uD835\uDCC8',
34580
            ssetmn: '\u2216',
34581
            ssmile: '\u2323',
34582
            sstarf: '\u22C6',
34583
            Star: '\u22C6',
34584
            star: '\u2606',
34585
            starf: '\u2605',
34586
            straightepsilon: '\u03F5',
34587
            straightphi: '\u03D5',
34588
            strns: '\u00AF',
34589
            Sub: '\u22D0',
34590
            sub: '\u2282',
34591
            subdot: '\u2ABD',
34592
            subE: '\u2AC5',
34593
            sube: '\u2286',
34594
            subedot: '\u2AC3',
34595
            submult: '\u2AC1',
34596
            subnE: '\u2ACB',
34597
            subne: '\u228A',
34598
            subplus: '\u2ABF',
34599
            subrarr: '\u2979',
34600
            Subset: '\u22D0',
34601
            subset: '\u2282',
34602
            subseteq: '\u2286',
34603
            subseteqq: '\u2AC5',
34604
            SubsetEqual: '\u2286',
34605
            subsetneq: '\u228A',
34606
            subsetneqq: '\u2ACB',
34607
            subsim: '\u2AC7',
34608
            subsub: '\u2AD5',
34609
            subsup: '\u2AD3',
34610
            succ: '\u227B',
34611
            succapprox: '\u2AB8',
34612
            succcurlyeq: '\u227D',
34613
            Succeeds: '\u227B',
34614
            SucceedsEqual: '\u2AB0',
34615
            SucceedsSlantEqual: '\u227D',
34616
            SucceedsTilde: '\u227F',
34617
            succeq: '\u2AB0',
34618
            succnapprox: '\u2ABA',
34619
            succneqq: '\u2AB6',
34620
            succnsim: '\u22E9',
34621
            succsim: '\u227F',
34622
            SuchThat: '\u220B',
34623
            Sum: '\u2211',
34624
            sum: '\u2211',
34625
            sung: '\u266A',
34626
            Sup: '\u22D1',
34627
            sup: '\u2283',
34628
            sup1: '\u00B9',
34629
            sup2: '\u00B2',
34630
            sup3: '\u00B3',
34631
            supdot: '\u2ABE',
34632
            supdsub: '\u2AD8',
34633
            supE: '\u2AC6',
34634
            supe: '\u2287',
34635
            supedot: '\u2AC4',
34636
            Superset: '\u2283',
34637
            SupersetEqual: '\u2287',
34638
            suphsol: '\u27C9',
34639
            suphsub: '\u2AD7',
34640
            suplarr: '\u297B',
34641
            supmult: '\u2AC2',
34642
            supnE: '\u2ACC',
34643
            supne: '\u228B',
34644
            supplus: '\u2AC0',
34645
            Supset: '\u22D1',
34646
            supset: '\u2283',
34647
            supseteq: '\u2287',
34648
            supseteqq: '\u2AC6',
34649
            supsetneq: '\u228B',
34650
            supsetneqq: '\u2ACC',
34651
            supsim: '\u2AC8',
34652
            supsub: '\u2AD4',
34653
            supsup: '\u2AD6',
34654
            swarhk: '\u2926',
34655
            swArr: '\u21D9',
34656
            swarr: '\u2199',
34657
            swarrow: '\u2199',
34658
            swnwar: '\u292A',
34659
            szlig: '\u00DF',
34660
            Tab: '\u0009',
34661
            target: '\u2316',
34662
            Tau: '\u03A4',
34663
            tau: '\u03C4',
34664
            tbrk: '\u23B4',
34665
            Tcaron: '\u0164',
34666
            tcaron: '\u0165',
34667
            Tcedil: '\u0162',
34668
            tcedil: '\u0163',
34669
            Tcy: '\u0422',
34670
            tcy: '\u0442',
34671
            tdot: '\u20DB',
34672
            telrec: '\u2315',
34673
            Tfr: '\uD835\uDD17',
34674
            tfr: '\uD835\uDD31',
34675
            there4: '\u2234',
34676
            Therefore: '\u2234',
34677
            therefore: '\u2234',
34678
            Theta: '\u0398',
34679
            theta: '\u03B8',
34680
            thetasym: '\u03D1',
34681
            thetav: '\u03D1',
34682
            thickapprox: '\u2248',
34683
            thicksim: '\u223C',
34684
            ThickSpace: '\u205F\u200A',
34685
            thinsp: '\u2009',
34686
            ThinSpace: '\u2009',
34687
            thkap: '\u2248',
34688
            thksim: '\u223C',
34689
            THORN: '\u00DE',
34690
            thorn: '\u00FE',
34691
            Tilde: '\u223C',
34692
            tilde: '\u02DC',
34693
            TildeEqual: '\u2243',
34694
            TildeFullEqual: '\u2245',
34695
            TildeTilde: '\u2248',
34696
            times: '\u00D7',
34697
            timesb: '\u22A0',
34698
            timesbar: '\u2A31',
34699
            timesd: '\u2A30',
34700
            tint: '\u222D',
34701
            toea: '\u2928',
34702
            top: '\u22A4',
34703
            topbot: '\u2336',
34704
            topcir: '\u2AF1',
34705
            Topf: '\uD835\uDD4B',
34706
            topf: '\uD835\uDD65',
34707
            topfork: '\u2ADA',
34708
            tosa: '\u2929',
34709
            tprime: '\u2034',
34710
            TRADE: '\u2122',
34711
            trade: '\u2122',
34712
            triangle: '\u25B5',
34713
            triangledown: '\u25BF',
34714
            triangleleft: '\u25C3',
34715
            trianglelefteq: '\u22B4',
34716
            triangleq: '\u225C',
34717
            triangleright: '\u25B9',
34718
            trianglerighteq: '\u22B5',
34719
            tridot: '\u25EC',
34720
            trie: '\u225C',
34721
            triminus: '\u2A3A',
34722
            TripleDot: '\u20DB',
34723
            triplus: '\u2A39',
34724
            trisb: '\u29CD',
34725
            tritime: '\u2A3B',
34726
            trpezium: '\u23E2',
34727
            Tscr: '\uD835\uDCAF',
34728
            tscr: '\uD835\uDCC9',
34729
            TScy: '\u0426',
34730
            tscy: '\u0446',
34731
            TSHcy: '\u040B',
34732
            tshcy: '\u045B',
34733
            Tstrok: '\u0166',
34734
            tstrok: '\u0167',
34735
            twixt: '\u226C',
34736
            twoheadleftarrow: '\u219E',
34737
            twoheadrightarrow: '\u21A0',
34738
            Uacute: '\u00DA',
34739
            uacute: '\u00FA',
34740
            Uarr: '\u219F',
34741
            uArr: '\u21D1',
34742
            uarr: '\u2191',
34743
            Uarrocir: '\u2949',
34744
            Ubrcy: '\u040E',
34745
            ubrcy: '\u045E',
34746
            Ubreve: '\u016C',
34747
            ubreve: '\u016D',
34748
            Ucirc: '\u00DB',
34749
            ucirc: '\u00FB',
34750
            Ucy: '\u0423',
34751
            ucy: '\u0443',
34752
            udarr: '\u21C5',
34753
            Udblac: '\u0170',
34754
            udblac: '\u0171',
34755
            udhar: '\u296E',
34756
            ufisht: '\u297E',
34757
            Ufr: '\uD835\uDD18',
34758
            ufr: '\uD835\uDD32',
34759
            Ugrave: '\u00D9',
34760
            ugrave: '\u00F9',
34761
            uHar: '\u2963',
34762
            uharl: '\u21BF',
34763
            uharr: '\u21BE',
34764
            uhblk: '\u2580',
34765
            ulcorn: '\u231C',
34766
            ulcorner: '\u231C',
34767
            ulcrop: '\u230F',
34768
            ultri: '\u25F8',
34769
            Umacr: '\u016A',
34770
            umacr: '\u016B',
34771
            uml: '\u00A8',
34772
            UnderBar: '\u005F',
34773
            UnderBrace: '\u23DF',
34774
            UnderBracket: '\u23B5',
34775
            UnderParenthesis: '\u23DD',
34776
            Union: '\u22C3',
34777
            UnionPlus: '\u228E',
34778
            Uogon: '\u0172',
34779
            uogon: '\u0173',
34780
            Uopf: '\uD835\uDD4C',
34781
            uopf: '\uD835\uDD66',
34782
            UpArrow: '\u2191',
34783
            Uparrow: '\u21D1',
34784
            uparrow: '\u2191',
34785
            UpArrowBar: '\u2912',
34786
            UpArrowDownArrow: '\u21C5',
34787
            UpDownArrow: '\u2195',
34788
            Updownarrow: '\u21D5',
34789
            updownarrow: '\u2195',
34790
            UpEquilibrium: '\u296E',
34791
            upharpoonleft: '\u21BF',
34792
            upharpoonright: '\u21BE',
34793
            uplus: '\u228E',
34794
            UpperLeftArrow: '\u2196',
34795
            UpperRightArrow: '\u2197',
34796
            Upsi: '\u03D2',
34797
            upsi: '\u03C5',
34798
            upsih: '\u03D2',
34799
            Upsilon: '\u03A5',
34800
            upsilon: '\u03C5',
34801
            UpTee: '\u22A5',
34802
            UpTeeArrow: '\u21A5',
34803
            upuparrows: '\u21C8',
34804
            urcorn: '\u231D',
34805
            urcorner: '\u231D',
34806
            urcrop: '\u230E',
34807
            Uring: '\u016E',
34808
            uring: '\u016F',
34809
            urtri: '\u25F9',
34810
            Uscr: '\uD835\uDCB0',
34811
            uscr: '\uD835\uDCCA',
34812
            utdot: '\u22F0',
34813
            Utilde: '\u0168',
34814
            utilde: '\u0169',
34815
            utri: '\u25B5',
34816
            utrif: '\u25B4',
34817
            uuarr: '\u21C8',
34818
            Uuml: '\u00DC',
34819
            uuml: '\u00FC',
34820
            uwangle: '\u29A7',
34821
            vangrt: '\u299C',
34822
            varepsilon: '\u03F5',
34823
            varkappa: '\u03F0',
34824
            varnothing: '\u2205',
34825
            varphi: '\u03D5',
34826
            varpi: '\u03D6',
34827
            varpropto: '\u221D',
34828
            vArr: '\u21D5',
34829
            varr: '\u2195',
34830
            varrho: '\u03F1',
34831
            varsigma: '\u03C2',
34832
            varsubsetneq: '\u228A\uFE00',
34833
            varsubsetneqq: '\u2ACB\uFE00',
34834
            varsupsetneq: '\u228B\uFE00',
34835
            varsupsetneqq: '\u2ACC\uFE00',
34836
            vartheta: '\u03D1',
34837
            vartriangleleft: '\u22B2',
34838
            vartriangleright: '\u22B3',
34839
            Vbar: '\u2AEB',
34840
            vBar: '\u2AE8',
34841
            vBarv: '\u2AE9',
34842
            Vcy: '\u0412',
34843
            vcy: '\u0432',
34844
            VDash: '\u22AB',
34845
            Vdash: '\u22A9',
34846
            vDash: '\u22A8',
34847
            vdash: '\u22A2',
34848
            Vdashl: '\u2AE6',
34849
            Vee: '\u22C1',
34850
            vee: '\u2228',
34851
            veebar: '\u22BB',
34852
            veeeq: '\u225A',
34853
            vellip: '\u22EE',
34854
            Verbar: '\u2016',
34855
            verbar: '\u007C',
34856
            Vert: '\u2016',
34857
            vert: '\u007C',
34858
            VerticalBar: '\u2223',
34859
            VerticalLine: '\u007C',
34860
            VerticalSeparator: '\u2758',
34861
            VerticalTilde: '\u2240',
34862
            VeryThinSpace: '\u200A',
34863
            Vfr: '\uD835\uDD19',
34864
            vfr: '\uD835\uDD33',
34865
            vltri: '\u22B2',
34866
            vnsub: '\u2282\u20D2',
34867
            vnsup: '\u2283\u20D2',
34868
            Vopf: '\uD835\uDD4D',
34869
            vopf: '\uD835\uDD67',
34870
            vprop: '\u221D',
34871
            vrtri: '\u22B3',
34872
            Vscr: '\uD835\uDCB1',
34873
            vscr: '\uD835\uDCCB',
34874
            vsubnE: '\u2ACB\uFE00',
34875
            vsubne: '\u228A\uFE00',
34876
            vsupnE: '\u2ACC\uFE00',
34877
            vsupne: '\u228B\uFE00',
34878
            Vvdash: '\u22AA',
34879
            vzigzag: '\u299A',
34880
            Wcirc: '\u0174',
34881
            wcirc: '\u0175',
34882
            wedbar: '\u2A5F',
34883
            Wedge: '\u22C0',
34884
            wedge: '\u2227',
34885
            wedgeq: '\u2259',
34886
            weierp: '\u2118',
34887
            Wfr: '\uD835\uDD1A',
34888
            wfr: '\uD835\uDD34',
34889
            Wopf: '\uD835\uDD4E',
34890
            wopf: '\uD835\uDD68',
34891
            wp: '\u2118',
34892
            wr: '\u2240',
34893
            wreath: '\u2240',
34894
            Wscr: '\uD835\uDCB2',
34895
            wscr: '\uD835\uDCCC',
34896
            xcap: '\u22C2',
34897
            xcirc: '\u25EF',
34898
            xcup: '\u22C3',
34899
            xdtri: '\u25BD',
34900
            Xfr: '\uD835\uDD1B',
34901
            xfr: '\uD835\uDD35',
34902
            xhArr: '\u27FA',
34903
            xharr: '\u27F7',
34904
            Xi: '\u039E',
34905
            xi: '\u03BE',
34906
            xlArr: '\u27F8',
34907
            xlarr: '\u27F5',
34908
            xmap: '\u27FC',
34909
            xnis: '\u22FB',
34910
            xodot: '\u2A00',
34911
            Xopf: '\uD835\uDD4F',
34912
            xopf: '\uD835\uDD69',
34913
            xoplus: '\u2A01',
34914
            xotime: '\u2A02',
34915
            xrArr: '\u27F9',
34916
            xrarr: '\u27F6',
34917
            Xscr: '\uD835\uDCB3',
34918
            xscr: '\uD835\uDCCD',
34919
            xsqcup: '\u2A06',
34920
            xuplus: '\u2A04',
34921
            xutri: '\u25B3',
34922
            xvee: '\u22C1',
34923
            xwedge: '\u22C0',
34924
            Yacute: '\u00DD',
34925
            yacute: '\u00FD',
34926
            YAcy: '\u042F',
34927
            yacy: '\u044F',
34928
            Ycirc: '\u0176',
34929
            ycirc: '\u0177',
34930
            Ycy: '\u042B',
34931
            ycy: '\u044B',
34932
            yen: '\u00A5',
34933
            Yfr: '\uD835\uDD1C',
34934
            yfr: '\uD835\uDD36',
34935
            YIcy: '\u0407',
34936
            yicy: '\u0457',
34937
            Yopf: '\uD835\uDD50',
34938
            yopf: '\uD835\uDD6A',
34939
            Yscr: '\uD835\uDCB4',
34940
            yscr: '\uD835\uDCCE',
34941
            YUcy: '\u042E',
34942
            yucy: '\u044E',
34943
            Yuml: '\u0178',
34944
            yuml: '\u00FF',
34945
            Zacute: '\u0179',
34946
            zacute: '\u017A',
34947
            Zcaron: '\u017D',
34948
            zcaron: '\u017E',
34949
            Zcy: '\u0417',
34950
            zcy: '\u0437',
34951
            Zdot: '\u017B',
34952
            zdot: '\u017C',
34953
            zeetrf: '\u2128',
34954
            ZeroWidthSpace: '\u200B',
34955
            Zeta: '\u0396',
34956
            zeta: '\u03B6',
34957
            Zfr: '\u2128',
34958
            zfr: '\uD835\uDD37',
34959
            ZHcy: '\u0416',
34960
            zhcy: '\u0436',
34961
            zigrarr: '\u21DD',
34962
            Zopf: '\u2124',
34963
            zopf: '\uD835\uDD6B',
34964
            Zscr: '\uD835\uDCB5',
34965
            zscr: '\uD835\uDCCF',
34966
            zwj: '\u200D',
34967
            zwnj: '\u200C'
34968
        });
34969
 
34970
        /**
34971
         * @deprecated use `HTML_ENTITIES` instead
34972
         * @see HTML_ENTITIES
34973
         */
34974
        exports.entityMap = exports.HTML_ENTITIES;
34975
    });
34976
    entities.XML_ENTITIES;
34977
    entities.HTML_ENTITIES;
34978
    entities.entityMap;
34979
 
34980
    var NAMESPACE$1 = conventions.NAMESPACE;
34981
 
34982
    //[4]   	NameStartChar	   ::=   	":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
34983
    //[4a]   	NameChar	   ::=   	NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
34984
    //[5]   	Name	   ::=   	NameStartChar (NameChar)*
34985
    var nameStartChar = /[A-Z_a-z\xC0-\xD6\xD8-\xF6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/; //\u10000-\uEFFFF
34986
    var nameChar = new RegExp("[\\-\\.0-9" + nameStartChar.source.slice(1, -1) + "\\u00B7\\u0300-\\u036F\\u203F-\\u2040]");
34987
    var tagNamePattern = new RegExp('^' + nameStartChar.source + nameChar.source + '*(?:\:' + nameStartChar.source + nameChar.source + '*)?$');
34988
    //var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/
34989
    //var handlers = 'resolveEntity,getExternalSubset,characters,endDocument,endElement,endPrefixMapping,ignorableWhitespace,processingInstruction,setDocumentLocator,skippedEntity,startDocument,startElement,startPrefixMapping,notationDecl,unparsedEntityDecl,error,fatalError,warning,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,comment,endCDATA,endDTD,endEntity,startCDATA,startDTD,startEntity'.split(',')
34990
 
34991
    //S_TAG,	S_ATTR,	S_EQ,	S_ATTR_NOQUOT_VALUE
34992
    //S_ATTR_SPACE,	S_ATTR_END,	S_TAG_SPACE, S_TAG_CLOSE
34993
    var S_TAG = 0; //tag name offerring
34994
    var S_ATTR = 1; //attr name offerring
34995
    var S_ATTR_SPACE = 2; //attr name end and space offer
34996
    var S_EQ = 3; //=space?
34997
    var S_ATTR_NOQUOT_VALUE = 4; //attr value(no quot value only)
34998
    var S_ATTR_END = 5; //attr value end and no space(quot end)
34999
    var S_TAG_SPACE = 6; //(attr value end || tag end ) && (space offer)
35000
    var S_TAG_CLOSE = 7; //closed el<el />
35001
 
35002
    /**
35003
     * Creates an error that will not be caught by XMLReader aka the SAX parser.
35004
     *
35005
     * @param {string} message
35006
     * @param {any?} locator Optional, can provide details about the location in the source
35007
     * @constructor
35008
     */
35009
    function ParseError$1(message, locator) {
35010
        this.message = message;
35011
        this.locator = locator;
35012
        if (Error.captureStackTrace) Error.captureStackTrace(this, ParseError$1);
35013
    }
35014
    ParseError$1.prototype = new Error();
35015
    ParseError$1.prototype.name = ParseError$1.name;
35016
    function XMLReader$1() {}
35017
    XMLReader$1.prototype = {
35018
        parse: function (source, defaultNSMap, entityMap) {
35019
            var domBuilder = this.domBuilder;
35020
            domBuilder.startDocument();
35021
            _copy(defaultNSMap, defaultNSMap = {});
35022
            parse$1(source, defaultNSMap, entityMap, domBuilder, this.errorHandler);
35023
            domBuilder.endDocument();
35024
        }
35025
    };
35026
    function parse$1(source, defaultNSMapCopy, entityMap, domBuilder, errorHandler) {
35027
        function fixedFromCharCode(code) {
35028
            // String.prototype.fromCharCode does not supports
35029
            // > 2 bytes unicode chars directly
35030
            if (code > 0xffff) {
35031
                code -= 0x10000;
35032
                var surrogate1 = 0xd800 + (code >> 10),
35033
                    surrogate2 = 0xdc00 + (code & 0x3ff);
35034
                return String.fromCharCode(surrogate1, surrogate2);
35035
            } else {
35036
                return String.fromCharCode(code);
35037
            }
35038
        }
35039
        function entityReplacer(a) {
35040
            var k = a.slice(1, -1);
35041
            if (Object.hasOwnProperty.call(entityMap, k)) {
35042
                return entityMap[k];
35043
            } else if (k.charAt(0) === '#') {
35044
                return fixedFromCharCode(parseInt(k.substr(1).replace('x', '0x')));
35045
            } else {
35046
                errorHandler.error('entity not found:' + a);
35047
                return a;
35048
            }
35049
        }
35050
        function appendText(end) {
35051
            //has some bugs
35052
            if (end > start) {
35053
                var xt = source.substring(start, end).replace(/&#?\w+;/g, entityReplacer);
35054
                locator && position(start);
35055
                domBuilder.characters(xt, 0, end - start);
35056
                start = end;
35057
            }
35058
        }
35059
        function position(p, m) {
35060
            while (p >= lineEnd && (m = linePattern.exec(source))) {
35061
                lineStart = m.index;
35062
                lineEnd = lineStart + m[0].length;
35063
                locator.lineNumber++;
35064
                //console.log('line++:',locator,startPos,endPos)
35065
            }
35066
 
35067
            locator.columnNumber = p - lineStart + 1;
35068
        }
35069
        var lineStart = 0;
35070
        var lineEnd = 0;
35071
        var linePattern = /.*(?:\r\n?|\n)|.*$/g;
35072
        var locator = domBuilder.locator;
35073
        var parseStack = [{
35074
            currentNSMap: defaultNSMapCopy
35075
        }];
35076
        var closeMap = {};
35077
        var start = 0;
35078
        while (true) {
35079
            try {
35080
                var tagStart = source.indexOf('<', start);
35081
                if (tagStart < 0) {
35082
                    if (!source.substr(start).match(/^\s*$/)) {
35083
                        var doc = domBuilder.doc;
35084
                        var text = doc.createTextNode(source.substr(start));
35085
                        doc.appendChild(text);
35086
                        domBuilder.currentElement = text;
35087
                    }
35088
                    return;
35089
                }
35090
                if (tagStart > start) {
35091
                    appendText(tagStart);
35092
                }
35093
                switch (source.charAt(tagStart + 1)) {
35094
                    case '/':
35095
                        var end = source.indexOf('>', tagStart + 3);
35096
                        var tagName = source.substring(tagStart + 2, end).replace(/[ \t\n\r]+$/g, '');
35097
                        var config = parseStack.pop();
35098
                        if (end < 0) {
35099
                            tagName = source.substring(tagStart + 2).replace(/[\s<].*/, '');
35100
                            errorHandler.error("end tag name: " + tagName + ' is not complete:' + config.tagName);
35101
                            end = tagStart + 1 + tagName.length;
35102
                        } else if (tagName.match(/\s</)) {
35103
                            tagName = tagName.replace(/[\s<].*/, '');
35104
                            errorHandler.error("end tag name: " + tagName + ' maybe not complete');
35105
                            end = tagStart + 1 + tagName.length;
35106
                        }
35107
                        var localNSMap = config.localNSMap;
35108
                        var endMatch = config.tagName == tagName;
35109
                        var endIgnoreCaseMach = endMatch || config.tagName && config.tagName.toLowerCase() == tagName.toLowerCase();
35110
                        if (endIgnoreCaseMach) {
35111
                            domBuilder.endElement(config.uri, config.localName, tagName);
35112
                            if (localNSMap) {
35113
                                for (var prefix in localNSMap) {
35114
                                    if (Object.prototype.hasOwnProperty.call(localNSMap, prefix)) {
35115
                                        domBuilder.endPrefixMapping(prefix);
35116
                                    }
35117
                                }
35118
                            }
35119
                            if (!endMatch) {
35120
                                errorHandler.fatalError("end tag name: " + tagName + ' is not match the current start tagName:' + config.tagName); // No known test case
35121
                            }
35122
                        } else {
35123
                            parseStack.push(config);
35124
                        }
35125
                        end++;
35126
                        break;
35127
                    // end elment
35128
                    case '?':
35129
                        // <?...?>
35130
                        locator && position(tagStart);
35131
                        end = parseInstruction(source, tagStart, domBuilder);
35132
                        break;
35133
                    case '!':
35134
                        // <!doctype,<![CDATA,<!--
35135
                        locator && position(tagStart);
35136
                        end = parseDCC(source, tagStart, domBuilder, errorHandler);
35137
                        break;
35138
                    default:
35139
                        locator && position(tagStart);
35140
                        var el = new ElementAttributes();
35141
                        var currentNSMap = parseStack[parseStack.length - 1].currentNSMap;
35142
                        //elStartEnd
35143
                        var end = parseElementStartPart(source, tagStart, el, currentNSMap, entityReplacer, errorHandler);
35144
                        var len = el.length;
35145
                        if (!el.closed && fixSelfClosed(source, end, el.tagName, closeMap)) {
35146
                            el.closed = true;
35147
                            if (!entityMap.nbsp) {
35148
                                errorHandler.warning('unclosed xml attribute');
35149
                            }
35150
                        }
35151
                        if (locator && len) {
35152
                            var locator2 = copyLocator(locator, {});
35153
                            //try{//attribute position fixed
35154
                            for (var i = 0; i < len; i++) {
35155
                                var a = el[i];
35156
                                position(a.offset);
35157
                                a.locator = copyLocator(locator, {});
35158
                            }
35159
                            domBuilder.locator = locator2;
35160
                            if (appendElement$1(el, domBuilder, currentNSMap)) {
35161
                                parseStack.push(el);
35162
                            }
35163
                            domBuilder.locator = locator;
35164
                        } else {
35165
                            if (appendElement$1(el, domBuilder, currentNSMap)) {
35166
                                parseStack.push(el);
35167
                            }
35168
                        }
35169
                        if (NAMESPACE$1.isHTML(el.uri) && !el.closed) {
35170
                            end = parseHtmlSpecialContent(source, end, el.tagName, entityReplacer, domBuilder);
35171
                        } else {
35172
                            end++;
35173
                        }
35174
                }
35175
            } catch (e) {
35176
                if (e instanceof ParseError$1) {
35177
                    throw e;
35178
                }
35179
                errorHandler.error('element parse error: ' + e);
35180
                end = -1;
35181
            }
35182
            if (end > start) {
35183
                start = end;
35184
            } else {
35185
                //TODO: 这里有可能sax回退,有位置错误风险
35186
                appendText(Math.max(tagStart, start) + 1);
35187
            }
35188
        }
35189
    }
35190
    function copyLocator(f, t) {
35191
        t.lineNumber = f.lineNumber;
35192
        t.columnNumber = f.columnNumber;
35193
        return t;
35194
    }
35195
 
35196
    /**
35197
     * @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack);
35198
     * @return end of the elementStartPart(end of elementEndPart for selfClosed el)
35199
     */
35200
    function parseElementStartPart(source, start, el, currentNSMap, entityReplacer, errorHandler) {
35201
        /**
35202
         * @param {string} qname
35203
         * @param {string} value
35204
         * @param {number} startIndex
35205
         */
35206
        function addAttribute(qname, value, startIndex) {
35207
            if (el.attributeNames.hasOwnProperty(qname)) {
35208
                errorHandler.fatalError('Attribute ' + qname + ' redefined');
35209
            }
35210
            el.addValue(qname,
35211
                // @see https://www.w3.org/TR/xml/#AVNormalize
35212
                // since the xmldom sax parser does not "interpret" DTD the following is not implemented:
35213
                // - recursive replacement of (DTD) entity references
35214
                // - trimming and collapsing multiple spaces into a single one for attributes that are not of type CDATA
35215
                value.replace(/[\t\n\r]/g, ' ').replace(/&#?\w+;/g, entityReplacer), startIndex);
35216
        }
35217
        var attrName;
35218
        var value;
35219
        var p = ++start;
35220
        var s = S_TAG; //status
35221
        while (true) {
35222
            var c = source.charAt(p);
35223
            switch (c) {
35224
                case '=':
35225
                    if (s === S_ATTR) {
35226
                        //attrName
35227
                        attrName = source.slice(start, p);
35228
                        s = S_EQ;
35229
                    } else if (s === S_ATTR_SPACE) {
35230
                        s = S_EQ;
35231
                    } else {
35232
                        //fatalError: equal must after attrName or space after attrName
35233
                        throw new Error('attribute equal must after attrName'); // No known test case
35234
                    }
35235
 
35236
                    break;
35237
                case '\'':
35238
                case '"':
35239
                    if (s === S_EQ || s === S_ATTR //|| s == S_ATTR_SPACE
35240
                    ) {
35241
                        //equal
35242
                        if (s === S_ATTR) {
35243
                            errorHandler.warning('attribute value must after "="');
35244
                            attrName = source.slice(start, p);
35245
                        }
35246
                        start = p + 1;
35247
                        p = source.indexOf(c, start);
35248
                        if (p > 0) {
35249
                            value = source.slice(start, p);
35250
                            addAttribute(attrName, value, start - 1);
35251
                            s = S_ATTR_END;
35252
                        } else {
35253
                            //fatalError: no end quot match
35254
                            throw new Error('attribute value no end \'' + c + '\' match');
35255
                        }
35256
                    } else if (s == S_ATTR_NOQUOT_VALUE) {
35257
                        value = source.slice(start, p);
35258
                        addAttribute(attrName, value, start);
35259
                        errorHandler.warning('attribute "' + attrName + '" missed start quot(' + c + ')!!');
35260
                        start = p + 1;
35261
                        s = S_ATTR_END;
35262
                    } else {
35263
                        //fatalError: no equal before
35264
                        throw new Error('attribute value must after "="'); // No known test case
35265
                    }
35266
 
35267
                    break;
35268
                case '/':
35269
                    switch (s) {
35270
                        case S_TAG:
35271
                            el.setTagName(source.slice(start, p));
35272
                        case S_ATTR_END:
35273
                        case S_TAG_SPACE:
35274
                        case S_TAG_CLOSE:
35275
                            s = S_TAG_CLOSE;
35276
                            el.closed = true;
35277
                        case S_ATTR_NOQUOT_VALUE:
35278
                        case S_ATTR:
35279
                            break;
35280
                        case S_ATTR_SPACE:
35281
                            el.closed = true;
35282
                            break;
35283
                        //case S_EQ:
35284
                        default:
35285
                            throw new Error("attribute invalid close char('/')");
35286
                        // No known test case
35287
                    }
35288
 
35289
                    break;
35290
                case '':
35291
                    //end document
35292
                    errorHandler.error('unexpected end of input');
35293
                    if (s == S_TAG) {
35294
                        el.setTagName(source.slice(start, p));
35295
                    }
35296
                    return p;
35297
                case '>':
35298
                    switch (s) {
35299
                        case S_TAG:
35300
                            el.setTagName(source.slice(start, p));
35301
                        case S_ATTR_END:
35302
                        case S_TAG_SPACE:
35303
                        case S_TAG_CLOSE:
35304
                            break;
35305
                        //normal
35306
                        case S_ATTR_NOQUOT_VALUE: //Compatible state
35307
                        case S_ATTR:
35308
                            value = source.slice(start, p);
35309
                            if (value.slice(-1) === '/') {
35310
                                el.closed = true;
35311
                                value = value.slice(0, -1);
35312
                            }
35313
                        case S_ATTR_SPACE:
35314
                            if (s === S_ATTR_SPACE) {
35315
                                value = attrName;
35316
                            }
35317
                            if (s == S_ATTR_NOQUOT_VALUE) {
35318
                                errorHandler.warning('attribute "' + value + '" missed quot(")!');
35319
                                addAttribute(attrName, value, start);
35320
                            } else {
35321
                                if (!NAMESPACE$1.isHTML(currentNSMap['']) || !value.match(/^(?:disabled|checked|selected)$/i)) {
35322
                                    errorHandler.warning('attribute "' + value + '" missed value!! "' + value + '" instead!!');
35323
                                }
35324
                                addAttribute(value, value, start);
35325
                            }
35326
                            break;
35327
                        case S_EQ:
35328
                            throw new Error('attribute value missed!!');
35329
                    }
35330
                    //			console.log(tagName,tagNamePattern,tagNamePattern.test(tagName))
35331
                    return p;
35332
                /*xml space '\x20' | #x9 | #xD | #xA; */
35333
                case '\u0080':
35334
                    c = ' ';
35335
                default:
35336
                    if (c <= ' ') {
35337
                        //space
35338
                        switch (s) {
35339
                            case S_TAG:
35340
                                el.setTagName(source.slice(start, p)); //tagName
35341
                                s = S_TAG_SPACE;
35342
                                break;
35343
                            case S_ATTR:
35344
                                attrName = source.slice(start, p);
35345
                                s = S_ATTR_SPACE;
35346
                                break;
35347
                            case S_ATTR_NOQUOT_VALUE:
35348
                                var value = source.slice(start, p);
35349
                                errorHandler.warning('attribute "' + value + '" missed quot(")!!');
35350
                                addAttribute(attrName, value, start);
35351
                            case S_ATTR_END:
35352
                                s = S_TAG_SPACE;
35353
                                break;
35354
                            //case S_TAG_SPACE:
35355
                            //case S_EQ:
35356
                            //case S_ATTR_SPACE:
35357
                            //	void();break;
35358
                            //case S_TAG_CLOSE:
35359
                            //ignore warning
35360
                        }
35361
                    } else {
35362
                        //not space
35363
                        //S_TAG,	S_ATTR,	S_EQ,	S_ATTR_NOQUOT_VALUE
35364
                        //S_ATTR_SPACE,	S_ATTR_END,	S_TAG_SPACE, S_TAG_CLOSE
35365
                        switch (s) {
35366
                            //case S_TAG:void();break;
35367
                            //case S_ATTR:void();break;
35368
                            //case S_ATTR_NOQUOT_VALUE:void();break;
35369
                            case S_ATTR_SPACE:
35370
                                el.tagName;
35371
                                if (!NAMESPACE$1.isHTML(currentNSMap['']) || !attrName.match(/^(?:disabled|checked|selected)$/i)) {
35372
                                    errorHandler.warning('attribute "' + attrName + '" missed value!! "' + attrName + '" instead2!!');
35373
                                }
35374
                                addAttribute(attrName, attrName, start);
35375
                                start = p;
35376
                                s = S_ATTR;
35377
                                break;
35378
                            case S_ATTR_END:
35379
                                errorHandler.warning('attribute space is required"' + attrName + '"!!');
35380
                            case S_TAG_SPACE:
35381
                                s = S_ATTR;
35382
                                start = p;
35383
                                break;
35384
                            case S_EQ:
35385
                                s = S_ATTR_NOQUOT_VALUE;
35386
                                start = p;
35387
                                break;
35388
                            case S_TAG_CLOSE:
35389
                                throw new Error("elements closed character '/' and '>' must be connected to");
35390
                        }
35391
                    }
35392
            } //end outer switch
35393
            //console.log('p++',p)
35394
            p++;
35395
        }
35396
    }
35397
    /**
35398
     * @return true if has new namespace define
35399
     */
35400
    function appendElement$1(el, domBuilder, currentNSMap) {
35401
        var tagName = el.tagName;
35402
        var localNSMap = null;
35403
        //var currentNSMap = parseStack[parseStack.length-1].currentNSMap;
35404
        var i = el.length;
35405
        while (i--) {
35406
            var a = el[i];
35407
            var qName = a.qName;
35408
            var value = a.value;
35409
            var nsp = qName.indexOf(':');
35410
            if (nsp > 0) {
35411
                var prefix = a.prefix = qName.slice(0, nsp);
35412
                var localName = qName.slice(nsp + 1);
35413
                var nsPrefix = prefix === 'xmlns' && localName;
35414
            } else {
35415
                localName = qName;
35416
                prefix = null;
35417
                nsPrefix = qName === 'xmlns' && '';
35418
            }
35419
            //can not set prefix,because prefix !== ''
35420
            a.localName = localName;
35421
            //prefix == null for no ns prefix attribute
35422
            if (nsPrefix !== false) {
35423
                //hack!!
35424
                if (localNSMap == null) {
35425
                    localNSMap = {};
35426
                    //console.log(currentNSMap,0)
35427
                    _copy(currentNSMap, currentNSMap = {});
35428
                    //console.log(currentNSMap,1)
35429
                }
35430
 
35431
                currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value;
35432
                a.uri = NAMESPACE$1.XMLNS;
35433
                domBuilder.startPrefixMapping(nsPrefix, value);
35434
            }
35435
        }
35436
        var i = el.length;
35437
        while (i--) {
35438
            a = el[i];
35439
            var prefix = a.prefix;
35440
            if (prefix) {
35441
                //no prefix attribute has no namespace
35442
                if (prefix === 'xml') {
35443
                    a.uri = NAMESPACE$1.XML;
35444
                }
35445
                if (prefix !== 'xmlns') {
35446
                    a.uri = currentNSMap[prefix || ''];
35447
 
35448
                    //{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)}
35449
                }
35450
            }
35451
        }
35452
 
35453
        var nsp = tagName.indexOf(':');
35454
        if (nsp > 0) {
35455
            prefix = el.prefix = tagName.slice(0, nsp);
35456
            localName = el.localName = tagName.slice(nsp + 1);
35457
        } else {
35458
            prefix = null; //important!!
35459
            localName = el.localName = tagName;
35460
        }
35461
        //no prefix element has default namespace
35462
        var ns = el.uri = currentNSMap[prefix || ''];
35463
        domBuilder.startElement(ns, localName, tagName, el);
35464
        //endPrefixMapping and startPrefixMapping have not any help for dom builder
35465
        //localNSMap = null
35466
        if (el.closed) {
35467
            domBuilder.endElement(ns, localName, tagName);
35468
            if (localNSMap) {
35469
                for (prefix in localNSMap) {
35470
                    if (Object.prototype.hasOwnProperty.call(localNSMap, prefix)) {
35471
                        domBuilder.endPrefixMapping(prefix);
35472
                    }
35473
                }
35474
            }
35475
        } else {
35476
            el.currentNSMap = currentNSMap;
35477
            el.localNSMap = localNSMap;
35478
            //parseStack.push(el);
35479
            return true;
35480
        }
35481
    }
35482
    function parseHtmlSpecialContent(source, elStartEnd, tagName, entityReplacer, domBuilder) {
35483
        if (/^(?:script|textarea)$/i.test(tagName)) {
35484
            var elEndStart = source.indexOf('</' + tagName + '>', elStartEnd);
35485
            var text = source.substring(elStartEnd + 1, elEndStart);
35486
            if (/[&<]/.test(text)) {
35487
                if (/^script$/i.test(tagName)) {
35488
                    //if(!/\]\]>/.test(text)){
35489
                    //lexHandler.startCDATA();
35490
                    domBuilder.characters(text, 0, text.length);
35491
                    //lexHandler.endCDATA();
35492
                    return elEndStart;
35493
                    //}
35494
                } //}else{//text area
35495
                text = text.replace(/&#?\w+;/g, entityReplacer);
35496
                domBuilder.characters(text, 0, text.length);
35497
                return elEndStart;
35498
                //}
35499
            }
35500
        }
35501
 
35502
        return elStartEnd + 1;
35503
    }
35504
    function fixSelfClosed(source, elStartEnd, tagName, closeMap) {
35505
        //if(tagName in closeMap){
35506
        var pos = closeMap[tagName];
35507
        if (pos == null) {
35508
            //console.log(tagName)
35509
            pos = source.lastIndexOf('</' + tagName + '>');
35510
            if (pos < elStartEnd) {
35511
                //忘记闭合
35512
                pos = source.lastIndexOf('</' + tagName);
35513
            }
35514
            closeMap[tagName] = pos;
35515
        }
35516
        return pos < elStartEnd;
35517
        //}
35518
    }
35519
 
35520
    function _copy(source, target) {
35521
        for (var n in source) {
35522
            if (Object.prototype.hasOwnProperty.call(source, n)) {
35523
                target[n] = source[n];
35524
            }
35525
        }
35526
    }
35527
    function parseDCC(source, start, domBuilder, errorHandler) {
35528
        //sure start with '<!'
35529
        var next = source.charAt(start + 2);
35530
        switch (next) {
35531
            case '-':
35532
                if (source.charAt(start + 3) === '-') {
35533
                    var end = source.indexOf('-->', start + 4);
35534
                    //append comment source.substring(4,end)//<!--
35535
                    if (end > start) {
35536
                        domBuilder.comment(source, start + 4, end - start - 4);
35537
                        return end + 3;
35538
                    } else {
35539
                        errorHandler.error("Unclosed comment");
35540
                        return -1;
35541
                    }
35542
                } else {
35543
                    //error
35544
                    return -1;
35545
                }
35546
            default:
35547
                if (source.substr(start + 3, 6) == 'CDATA[') {
35548
                    var end = source.indexOf(']]>', start + 9);
35549
                    domBuilder.startCDATA();
35550
                    domBuilder.characters(source, start + 9, end - start - 9);
35551
                    domBuilder.endCDATA();
35552
                    return end + 3;
35553
                }
35554
                //<!DOCTYPE
35555
                //startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId)
35556
                var matchs = split(source, start);
35557
                var len = matchs.length;
35558
                if (len > 1 && /!doctype/i.test(matchs[0][0])) {
35559
                    var name = matchs[1][0];
35560
                    var pubid = false;
35561
                    var sysid = false;
35562
                    if (len > 3) {
35563
                        if (/^public$/i.test(matchs[2][0])) {
35564
                            pubid = matchs[3][0];
35565
                            sysid = len > 4 && matchs[4][0];
35566
                        } else if (/^system$/i.test(matchs[2][0])) {
35567
                            sysid = matchs[3][0];
35568
                        }
35569
                    }
35570
                    var lastMatch = matchs[len - 1];
35571
                    domBuilder.startDTD(name, pubid, sysid);
35572
                    domBuilder.endDTD();
35573
                    return lastMatch.index + lastMatch[0].length;
35574
                }
35575
        }
35576
        return -1;
35577
    }
35578
    function parseInstruction(source, start, domBuilder) {
35579
        var end = source.indexOf('?>', start);
35580
        if (end) {
35581
            var match = source.substring(start, end).match(/^<\?(\S*)\s*([\s\S]*?)\s*$/);
35582
            if (match) {
35583
                match[0].length;
35584
                domBuilder.processingInstruction(match[1], match[2]);
35585
                return end + 2;
35586
            } else {
35587
                //error
35588
                return -1;
35589
            }
35590
        }
35591
        return -1;
35592
    }
35593
    function ElementAttributes() {
35594
        this.attributeNames = {};
35595
    }
35596
    ElementAttributes.prototype = {
35597
        setTagName: function (tagName) {
35598
            if (!tagNamePattern.test(tagName)) {
35599
                throw new Error('invalid tagName:' + tagName);
35600
            }
35601
            this.tagName = tagName;
35602
        },
35603
        addValue: function (qName, value, offset) {
35604
            if (!tagNamePattern.test(qName)) {
35605
                throw new Error('invalid attribute:' + qName);
35606
            }
35607
            this.attributeNames[qName] = this.length;
35608
            this[this.length++] = {
35609
                qName: qName,
35610
                value: value,
35611
                offset: offset
35612
            };
35613
        },
35614
        length: 0,
35615
        getLocalName: function (i) {
35616
            return this[i].localName;
35617
        },
35618
        getLocator: function (i) {
35619
            return this[i].locator;
35620
        },
35621
        getQName: function (i) {
35622
            return this[i].qName;
35623
        },
35624
        getURI: function (i) {
35625
            return this[i].uri;
35626
        },
35627
        getValue: function (i) {
35628
            return this[i].value;
35629
        }
35630
        //	,getIndex:function(uri, localName)){
35631
        //		if(localName){
35632
        //
35633
        //		}else{
35634
        //			var qName = uri
35635
        //		}
35636
        //	},
35637
        //	getValue:function(){return this.getValue(this.getIndex.apply(this,arguments))},
35638
        //	getType:function(uri,localName){}
35639
        //	getType:function(i){},
35640
    };
35641
 
35642
    function split(source, start) {
35643
        var match;
35644
        var buf = [];
35645
        var reg = /'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/g;
35646
        reg.lastIndex = start;
35647
        reg.exec(source); //skip <
35648
        while (match = reg.exec(source)) {
35649
            buf.push(match);
35650
            if (match[1]) return buf;
35651
        }
35652
    }
35653
    var XMLReader_1 = XMLReader$1;
35654
    var ParseError_1 = ParseError$1;
35655
    var sax = {
35656
        XMLReader: XMLReader_1,
35657
        ParseError: ParseError_1
35658
    };
35659
 
35660
    var DOMImplementation = dom.DOMImplementation;
35661
    var NAMESPACE = conventions.NAMESPACE;
35662
    var ParseError = sax.ParseError;
35663
    var XMLReader = sax.XMLReader;
35664
 
35665
    /**
35666
     * Normalizes line ending according to https://www.w3.org/TR/xml11/#sec-line-ends:
35667
     *
35668
     * > XML parsed entities are often stored in computer files which,
35669
     * > for editing convenience, are organized into lines.
35670
     * > These lines are typically separated by some combination
35671
     * > of the characters CARRIAGE RETURN (#xD) and LINE FEED (#xA).
35672
     * >
35673
     * > To simplify the tasks of applications, the XML processor must behave
35674
     * > as if it normalized all line breaks in external parsed entities (including the document entity)
35675
     * > on input, before parsing, by translating all of the following to a single #xA character:
35676
     * >
35677
     * > 1. the two-character sequence #xD #xA
35678
     * > 2. the two-character sequence #xD #x85
35679
     * > 3. the single character #x85
35680
     * > 4. the single character #x2028
35681
     * > 5. any #xD character that is not immediately followed by #xA or #x85.
35682
     *
35683
     * @param {string} input
35684
     * @returns {string}
35685
     */
35686
    function normalizeLineEndings(input) {
35687
        return input.replace(/\r[\n\u0085]/g, '\n').replace(/[\r\u0085\u2028]/g, '\n');
35688
    }
35689
 
35690
    /**
35691
     * @typedef Locator
35692
     * @property {number} [columnNumber]
35693
     * @property {number} [lineNumber]
35694
     */
35695
 
35696
    /**
35697
     * @typedef DOMParserOptions
35698
     * @property {DOMHandler} [domBuilder]
35699
     * @property {Function} [errorHandler]
35700
     * @property {(string) => string} [normalizeLineEndings] used to replace line endings before parsing
35701
     * 						defaults to `normalizeLineEndings`
35702
     * @property {Locator} [locator]
35703
     * @property {Record<string, string>} [xmlns]
35704
     *
35705
     * @see normalizeLineEndings
35706
     */
35707
 
35708
    /**
35709
     * The DOMParser interface provides the ability to parse XML or HTML source code
35710
     * from a string into a DOM `Document`.
35711
     *
35712
     * _xmldom is different from the spec in that it allows an `options` parameter,
35713
     * to override the default behavior._
35714
     *
35715
     * @param {DOMParserOptions} [options]
35716
     * @constructor
35717
     *
35718
     * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
35719
     * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-parsing-and-serialization
35720
     */
35721
    function DOMParser$1(options) {
35722
        this.options = options || {
35723
            locator: {}
35724
        };
35725
    }
35726
    DOMParser$1.prototype.parseFromString = function (source, mimeType) {
35727
        var options = this.options;
35728
        var sax = new XMLReader();
35729
        var domBuilder = options.domBuilder || new DOMHandler(); //contentHandler and LexicalHandler
35730
        var errorHandler = options.errorHandler;
35731
        var locator = options.locator;
35732
        var defaultNSMap = options.xmlns || {};
35733
        var isHTML = /\/x?html?$/.test(mimeType); //mimeType.toLowerCase().indexOf('html') > -1;
35734
        var entityMap = isHTML ? entities.HTML_ENTITIES : entities.XML_ENTITIES;
35735
        if (locator) {
35736
            domBuilder.setDocumentLocator(locator);
35737
        }
35738
        sax.errorHandler = buildErrorHandler(errorHandler, domBuilder, locator);
35739
        sax.domBuilder = options.domBuilder || domBuilder;
35740
        if (isHTML) {
35741
            defaultNSMap[''] = NAMESPACE.HTML;
35742
        }
35743
        defaultNSMap.xml = defaultNSMap.xml || NAMESPACE.XML;
35744
        var normalize = options.normalizeLineEndings || normalizeLineEndings;
35745
        if (source && typeof source === 'string') {
35746
            sax.parse(normalize(source), defaultNSMap, entityMap);
35747
        } else {
35748
            sax.errorHandler.error('invalid doc source');
35749
        }
35750
        return domBuilder.doc;
35751
    };
35752
    function buildErrorHandler(errorImpl, domBuilder, locator) {
35753
        if (!errorImpl) {
35754
            if (domBuilder instanceof DOMHandler) {
35755
                return domBuilder;
35756
            }
35757
            errorImpl = domBuilder;
35758
        }
35759
        var errorHandler = {};
35760
        var isCallback = errorImpl instanceof Function;
35761
        locator = locator || {};
35762
        function build(key) {
35763
            var fn = errorImpl[key];
35764
            if (!fn && isCallback) {
35765
                fn = errorImpl.length == 2 ? function (msg) {
35766
                    errorImpl(key, msg);
35767
                } : errorImpl;
35768
            }
35769
            errorHandler[key] = fn && function (msg) {
35770
                fn('[xmldom ' + key + ']\t' + msg + _locator(locator));
35771
            } || function () {};
35772
        }
35773
        build('warning');
35774
        build('error');
35775
        build('fatalError');
35776
        return errorHandler;
35777
    }
35778
 
35779
    //console.log('#\n\n\n\n\n\n\n####')
35780
    /**
35781
     * +ContentHandler+ErrorHandler
35782
     * +LexicalHandler+EntityResolver2
35783
     * -DeclHandler-DTDHandler
35784
     *
35785
     * DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler
35786
     * DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2
35787
     * @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html
35788
     */
35789
    function DOMHandler() {
35790
        this.cdata = false;
35791
    }
35792
    function position(locator, node) {
35793
        node.lineNumber = locator.lineNumber;
35794
        node.columnNumber = locator.columnNumber;
35795
    }
35796
    /**
35797
     * @see org.xml.sax.ContentHandler#startDocument
35798
     * @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html
35799
     */
35800
    DOMHandler.prototype = {
35801
        startDocument: function () {
35802
            this.doc = new DOMImplementation().createDocument(null, null, null);
35803
            if (this.locator) {
35804
                this.doc.documentURI = this.locator.systemId;
35805
            }
35806
        },
35807
        startElement: function (namespaceURI, localName, qName, attrs) {
35808
            var doc = this.doc;
35809
            var el = doc.createElementNS(namespaceURI, qName || localName);
35810
            var len = attrs.length;
35811
            appendElement(this, el);
35812
            this.currentElement = el;
35813
            this.locator && position(this.locator, el);
35814
            for (var i = 0; i < len; i++) {
35815
                var namespaceURI = attrs.getURI(i);
35816
                var value = attrs.getValue(i);
35817
                var qName = attrs.getQName(i);
35818
                var attr = doc.createAttributeNS(namespaceURI, qName);
35819
                this.locator && position(attrs.getLocator(i), attr);
35820
                attr.value = attr.nodeValue = value;
35821
                el.setAttributeNode(attr);
35822
            }
35823
        },
35824
        endElement: function (namespaceURI, localName, qName) {
35825
            var current = this.currentElement;
35826
            current.tagName;
35827
            this.currentElement = current.parentNode;
35828
        },
35829
        startPrefixMapping: function (prefix, uri) {},
35830
        endPrefixMapping: function (prefix) {},
35831
        processingInstruction: function (target, data) {
35832
            var ins = this.doc.createProcessingInstruction(target, data);
35833
            this.locator && position(this.locator, ins);
35834
            appendElement(this, ins);
35835
        },
35836
        ignorableWhitespace: function (ch, start, length) {},
35837
        characters: function (chars, start, length) {
35838
            chars = _toString.apply(this, arguments);
35839
            //console.log(chars)
35840
            if (chars) {
35841
                if (this.cdata) {
35842
                    var charNode = this.doc.createCDATASection(chars);
35843
                } else {
35844
                    var charNode = this.doc.createTextNode(chars);
35845
                }
35846
                if (this.currentElement) {
35847
                    this.currentElement.appendChild(charNode);
35848
                } else if (/^\s*$/.test(chars)) {
35849
                    this.doc.appendChild(charNode);
35850
                    //process xml
35851
                }
35852
 
35853
                this.locator && position(this.locator, charNode);
35854
            }
35855
        },
35856
        skippedEntity: function (name) {},
35857
        endDocument: function () {
35858
            this.doc.normalize();
35859
        },
35860
        setDocumentLocator: function (locator) {
35861
            if (this.locator = locator) {
35862
                // && !('lineNumber' in locator)){
35863
                locator.lineNumber = 0;
35864
            }
35865
        },
35866
        //LexicalHandler
35867
        comment: function (chars, start, length) {
35868
            chars = _toString.apply(this, arguments);
35869
            var comm = this.doc.createComment(chars);
35870
            this.locator && position(this.locator, comm);
35871
            appendElement(this, comm);
35872
        },
35873
        startCDATA: function () {
35874
            //used in characters() methods
35875
            this.cdata = true;
35876
        },
35877
        endCDATA: function () {
35878
            this.cdata = false;
35879
        },
35880
        startDTD: function (name, publicId, systemId) {
35881
            var impl = this.doc.implementation;
35882
            if (impl && impl.createDocumentType) {
35883
                var dt = impl.createDocumentType(name, publicId, systemId);
35884
                this.locator && position(this.locator, dt);
35885
                appendElement(this, dt);
35886
                this.doc.doctype = dt;
35887
            }
35888
        },
35889
        /**
35890
         * @see org.xml.sax.ErrorHandler
35891
         * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html
35892
         */
35893
        warning: function (error) {
35894
            console.warn('[xmldom warning]\t' + error, _locator(this.locator));
35895
        },
35896
        error: function (error) {
35897
            console.error('[xmldom error]\t' + error, _locator(this.locator));
35898
        },
35899
        fatalError: function (error) {
35900
            throw new ParseError(error, this.locator);
35901
        }
35902
    };
35903
    function _locator(l) {
35904
        if (l) {
35905
            return '\n@' + (l.systemId || '') + '#[line:' + l.lineNumber + ',col:' + l.columnNumber + ']';
35906
        }
35907
    }
35908
    function _toString(chars, start, length) {
35909
        if (typeof chars == 'string') {
35910
            return chars.substr(start, length);
35911
        } else {
35912
            //java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)")
35913
            if (chars.length >= start + length || start) {
35914
                return new java.lang.String(chars, start, length) + '';
35915
            }
35916
            return chars;
35917
        }
35918
    }
35919
 
35920
    /*
35921
   * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html
35922
   * used method of org.xml.sax.ext.LexicalHandler:
35923
   *  #comment(chars, start, length)
35924
   *  #startCDATA()
35925
   *  #endCDATA()
35926
   *  #startDTD(name, publicId, systemId)
35927
   *
35928
   *
35929
   * IGNORED method of org.xml.sax.ext.LexicalHandler:
35930
   *  #endDTD()
35931
   *  #startEntity(name)
35932
   *  #endEntity(name)
35933
   *
35934
   *
35935
   * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html
35936
   * IGNORED method of org.xml.sax.ext.DeclHandler
35937
   * 	#attributeDecl(eName, aName, type, mode, value)
35938
   *  #elementDecl(name, model)
35939
   *  #externalEntityDecl(name, publicId, systemId)
35940
   *  #internalEntityDecl(name, value)
35941
   * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html
35942
   * IGNORED method of org.xml.sax.EntityResolver2
35943
   *  #resolveEntity(String name,String publicId,String baseURI,String systemId)
35944
   *  #resolveEntity(publicId, systemId)
35945
   *  #getExternalSubset(name, baseURI)
35946
   * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html
35947
   * IGNORED method of org.xml.sax.DTDHandler
35948
   *  #notationDecl(name, publicId, systemId) {};
35949
   *  #unparsedEntityDecl(name, publicId, systemId, notationName) {};
35950
   */
35951
    "endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g, function (key) {
35952
        DOMHandler.prototype[key] = function () {
35953
            return null;
35954
        };
35955
    });
35956
 
35957
    /* Private static helpers treated below as private instance methods, so don't need to add these to the public API; we might use a Relator to also get rid of non-standard public properties */
35958
    function appendElement(hander, node) {
35959
        if (!hander.currentElement) {
35960
            hander.doc.appendChild(node);
35961
        } else {
35962
            hander.currentElement.appendChild(node);
35963
        }
35964
    } //appendChild and setAttributeNS are preformance key
35965
 
35966
    var __DOMHandler = DOMHandler;
35967
    var normalizeLineEndings_1 = normalizeLineEndings;
35968
    var DOMParser_1 = DOMParser$1;
35969
    var domParser = {
35970
        __DOMHandler: __DOMHandler,
35971
        normalizeLineEndings: normalizeLineEndings_1,
35972
        DOMParser: DOMParser_1
35973
    };
35974
 
35975
    var DOMParser = domParser.DOMParser;
35976
 
35977
    /*! @name mpd-parser @version 1.3.0 @license Apache-2.0 */
35978
    const isObject = obj => {
35979
        return !!obj && typeof obj === 'object';
35980
    };
35981
    const merge$1 = (...objects) => {
35982
        return objects.reduce((result, source) => {
35983
            if (typeof source !== 'object') {
35984
                return result;
35985
            }
35986
            Object.keys(source).forEach(key => {
35987
                if (Array.isArray(result[key]) && Array.isArray(source[key])) {
35988
                    result[key] = result[key].concat(source[key]);
35989
                } else if (isObject(result[key]) && isObject(source[key])) {
35990
                    result[key] = merge$1(result[key], source[key]);
35991
                } else {
35992
                    result[key] = source[key];
35993
                }
35994
            });
35995
            return result;
35996
        }, {});
35997
    };
35998
    const values = o => Object.keys(o).map(k => o[k]);
35999
    const range = (start, end) => {
36000
        const result = [];
36001
        for (let i = start; i < end; i++) {
36002
            result.push(i);
36003
        }
36004
        return result;
36005
    };
36006
    const flatten = lists => lists.reduce((x, y) => x.concat(y), []);
36007
    const from = list => {
36008
        if (!list.length) {
36009
            return [];
36010
        }
36011
        const result = [];
36012
        for (let i = 0; i < list.length; i++) {
36013
            result.push(list[i]);
36014
        }
36015
        return result;
36016
    };
36017
    const findIndexes = (l, key) => l.reduce((a, e, i) => {
36018
        if (e[key]) {
36019
            a.push(i);
36020
        }
36021
        return a;
36022
    }, []);
36023
    /**
36024
     * Returns a union of the included lists provided each element can be identified by a key.
36025
     *
36026
     * @param {Array} list - list of lists to get the union of
36027
     * @param {Function} keyFunction - the function to use as a key for each element
36028
     *
36029
     * @return {Array} the union of the arrays
36030
     */
36031
 
36032
    const union = (lists, keyFunction) => {
36033
        return values(lists.reduce((acc, list) => {
36034
            list.forEach(el => {
36035
                acc[keyFunction(el)] = el;
36036
            });
36037
            return acc;
36038
        }, {}));
36039
    };
36040
    var errors = {
36041
        INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
36042
        INVALID_NUMBER_OF_CONTENT_STEERING: 'INVALID_NUMBER_OF_CONTENT_STEERING',
36043
        DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
36044
        DASH_INVALID_XML: 'DASH_INVALID_XML',
36045
        NO_BASE_URL: 'NO_BASE_URL',
36046
        MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
36047
        SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED',
36048
        UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME'
36049
    };
36050
 
36051
    /**
36052
     * @typedef {Object} SingleUri
36053
     * @property {string} uri - relative location of segment
36054
     * @property {string} resolvedUri - resolved location of segment
36055
     * @property {Object} byterange - Object containing information on how to make byte range
36056
     *   requests following byte-range-spec per RFC2616.
36057
     * @property {String} byterange.length - length of range request
36058
     * @property {String} byterange.offset - byte offset of range request
36059
     *
36060
     * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
36061
     */
36062
 
36063
    /**
36064
     * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object
36065
     * that conforms to how m3u8-parser is structured
36066
     *
36067
     * @see https://github.com/videojs/m3u8-parser
36068
     *
36069
     * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes
36070
     * @param {string} source - source url for segment
36071
     * @param {string} range - optional range used for range calls,
36072
     *   follows  RFC 2616, Clause 14.35.1
36073
     * @return {SingleUri} full segment information transformed into a format similar
36074
     *   to m3u8-parser
36075
     */
36076
 
36077
    const urlTypeToSegment = ({
36078
                                  baseUrl = '',
36079
                                  source = '',
36080
                                  range = '',
36081
                                  indexRange = ''
36082
                              }) => {
36083
        const segment = {
36084
            uri: source,
36085
            resolvedUri: resolveUrl$1(baseUrl || '', source)
36086
        };
36087
        if (range || indexRange) {
36088
            const rangeStr = range ? range : indexRange;
36089
            const ranges = rangeStr.split('-'); // default to parsing this as a BigInt if possible
36090
 
36091
            let startRange = window.BigInt ? window.BigInt(ranges[0]) : parseInt(ranges[0], 10);
36092
            let endRange = window.BigInt ? window.BigInt(ranges[1]) : parseInt(ranges[1], 10); // convert back to a number if less than MAX_SAFE_INTEGER
36093
 
36094
            if (startRange < Number.MAX_SAFE_INTEGER && typeof startRange === 'bigint') {
36095
                startRange = Number(startRange);
36096
            }
36097
            if (endRange < Number.MAX_SAFE_INTEGER && typeof endRange === 'bigint') {
36098
                endRange = Number(endRange);
36099
            }
36100
            let length;
36101
            if (typeof endRange === 'bigint' || typeof startRange === 'bigint') {
36102
                length = window.BigInt(endRange) - window.BigInt(startRange) + window.BigInt(1);
36103
            } else {
36104
                length = endRange - startRange + 1;
36105
            }
36106
            if (typeof length === 'bigint' && length < Number.MAX_SAFE_INTEGER) {
36107
                length = Number(length);
36108
            } // byterange should be inclusive according to
36109
            // RFC 2616, Clause 14.35.1
36110
 
36111
            segment.byterange = {
36112
                length,
36113
                offset: startRange
36114
            };
36115
        }
36116
        return segment;
36117
    };
36118
    const byteRangeToString = byterange => {
36119
        // `endRange` is one less than `offset + length` because the HTTP range
36120
        // header uses inclusive ranges
36121
        let endRange;
36122
        if (typeof byterange.offset === 'bigint' || typeof byterange.length === 'bigint') {
36123
            endRange = window.BigInt(byterange.offset) + window.BigInt(byterange.length) - window.BigInt(1);
36124
        } else {
36125
            endRange = byterange.offset + byterange.length - 1;
36126
        }
36127
        return `${byterange.offset}-${endRange}`;
36128
    };
36129
 
36130
    /**
36131
     * parse the end number attribue that can be a string
36132
     * number, or undefined.
36133
     *
36134
     * @param {string|number|undefined} endNumber
36135
     *        The end number attribute.
36136
     *
36137
     * @return {number|null}
36138
     *          The result of parsing the end number.
36139
     */
36140
 
36141
    const parseEndNumber = endNumber => {
36142
        if (endNumber && typeof endNumber !== 'number') {
36143
            endNumber = parseInt(endNumber, 10);
36144
        }
36145
        if (isNaN(endNumber)) {
36146
            return null;
36147
        }
36148
        return endNumber;
36149
    };
36150
    /**
36151
     * Functions for calculating the range of available segments in static and dynamic
36152
     * manifests.
36153
     */
36154
 
36155
    const segmentRange = {
36156
        /**
36157
         * Returns the entire range of available segments for a static MPD
36158
         *
36159
         * @param {Object} attributes
36160
         *        Inheritied MPD attributes
36161
         * @return {{ start: number, end: number }}
36162
         *         The start and end numbers for available segments
36163
         */
36164
        static(attributes) {
36165
            const {
36166
                duration,
36167
                timescale = 1,
36168
                sourceDuration,
36169
                periodDuration
36170
            } = attributes;
36171
            const endNumber = parseEndNumber(attributes.endNumber);
36172
            const segmentDuration = duration / timescale;
36173
            if (typeof endNumber === 'number') {
36174
                return {
36175
                    start: 0,
36176
                    end: endNumber
36177
                };
36178
            }
36179
            if (typeof periodDuration === 'number') {
36180
                return {
36181
                    start: 0,
36182
                    end: periodDuration / segmentDuration
36183
                };
36184
            }
36185
            return {
36186
                start: 0,
36187
                end: sourceDuration / segmentDuration
36188
            };
36189
        },
36190
        /**
36191
         * Returns the current live window range of available segments for a dynamic MPD
36192
         *
36193
         * @param {Object} attributes
36194
         *        Inheritied MPD attributes
36195
         * @return {{ start: number, end: number }}
36196
         *         The start and end numbers for available segments
36197
         */
36198
        dynamic(attributes) {
36199
            const {
36200
                NOW,
36201
                clientOffset,
36202
                availabilityStartTime,
36203
                timescale = 1,
36204
                duration,
36205
                periodStart = 0,
36206
                minimumUpdatePeriod = 0,
36207
                timeShiftBufferDepth = Infinity
36208
            } = attributes;
36209
            const endNumber = parseEndNumber(attributes.endNumber); // clientOffset is passed in at the top level of mpd-parser and is an offset calculated
36210
            // after retrieving UTC server time.
36211
 
36212
            const now = (NOW + clientOffset) / 1000; // WC stands for Wall Clock.
36213
            // Convert the period start time to EPOCH.
36214
 
36215
            const periodStartWC = availabilityStartTime + periodStart; // Period end in EPOCH is manifest's retrieval time + time until next update.
36216
 
36217
            const periodEndWC = now + minimumUpdatePeriod;
36218
            const periodDuration = periodEndWC - periodStartWC;
36219
            const segmentCount = Math.ceil(periodDuration * timescale / duration);
36220
            const availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration);
36221
            const availableEnd = Math.floor((now - periodStartWC) * timescale / duration);
36222
            return {
36223
                start: Math.max(0, availableStart),
36224
                end: typeof endNumber === 'number' ? endNumber : Math.min(segmentCount, availableEnd)
36225
            };
36226
        }
36227
    };
36228
    /**
36229
     * Maps a range of numbers to objects with information needed to build the corresponding
36230
     * segment list
36231
     *
36232
     * @name toSegmentsCallback
36233
     * @function
36234
     * @param {number} number
36235
     *        Number of the segment
36236
     * @param {number} index
36237
     *        Index of the number in the range list
36238
     * @return {{ number: Number, duration: Number, timeline: Number, time: Number }}
36239
     *         Object with segment timing and duration info
36240
     */
36241
 
36242
    /**
36243
     * Returns a callback for Array.prototype.map for mapping a range of numbers to
36244
     * information needed to build the segment list.
36245
     *
36246
     * @param {Object} attributes
36247
     *        Inherited MPD attributes
36248
     * @return {toSegmentsCallback}
36249
     *         Callback map function
36250
     */
36251
 
36252
    const toSegments = attributes => number => {
36253
        const {
36254
            duration,
36255
            timescale = 1,
36256
            periodStart,
36257
            startNumber = 1
36258
        } = attributes;
36259
        return {
36260
            number: startNumber + number,
36261
            duration: duration / timescale,
36262
            timeline: periodStart,
36263
            time: number * duration
36264
        };
36265
    };
36266
    /**
36267
     * Returns a list of objects containing segment timing and duration info used for
36268
     * building the list of segments. This uses the @duration attribute specified
36269
     * in the MPD manifest to derive the range of segments.
36270
     *
36271
     * @param {Object} attributes
36272
     *        Inherited MPD attributes
36273
     * @return {{number: number, duration: number, time: number, timeline: number}[]}
36274
     *         List of Objects with segment timing and duration info
36275
     */
36276
 
36277
    const parseByDuration = attributes => {
36278
        const {
36279
            type,
36280
            duration,
36281
            timescale = 1,
36282
            periodDuration,
36283
            sourceDuration
36284
        } = attributes;
36285
        const {
36286
            start,
36287
            end
36288
        } = segmentRange[type](attributes);
36289
        const segments = range(start, end).map(toSegments(attributes));
36290
        if (type === 'static') {
36291
            const index = segments.length - 1; // section is either a period or the full source
36292
 
36293
            const sectionDuration = typeof periodDuration === 'number' ? periodDuration : sourceDuration; // final segment may be less than full segment duration
36294
 
36295
            segments[index].duration = sectionDuration - duration / timescale * index;
36296
        }
36297
        return segments;
36298
    };
36299
 
36300
    /**
36301
     * Translates SegmentBase into a set of segments.
36302
     * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
36303
     * node should be translated into segment.
36304
     *
36305
     * @param {Object} attributes
36306
     *   Object containing all inherited attributes from parent elements with attribute
36307
     *   names as keys
36308
     * @return {Object.<Array>} list of segments
36309
     */
36310
 
36311
    const segmentsFromBase = attributes => {
36312
        const {
36313
            baseUrl,
36314
            initialization = {},
36315
            sourceDuration,
36316
            indexRange = '',
36317
            periodStart,
36318
            presentationTime,
36319
            number = 0,
36320
            duration
36321
        } = attributes; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
36322
 
36323
        if (!baseUrl) {
36324
            throw new Error(errors.NO_BASE_URL);
36325
        }
36326
        const initSegment = urlTypeToSegment({
36327
            baseUrl,
36328
            source: initialization.sourceURL,
36329
            range: initialization.range
36330
        });
36331
        const segment = urlTypeToSegment({
36332
            baseUrl,
36333
            source: baseUrl,
36334
            indexRange
36335
        });
36336
        segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source
36337
        // (since SegmentBase is only for one total segment)
36338
 
36339
        if (duration) {
36340
            const segmentTimeInfo = parseByDuration(attributes);
36341
            if (segmentTimeInfo.length) {
36342
                segment.duration = segmentTimeInfo[0].duration;
36343
                segment.timeline = segmentTimeInfo[0].timeline;
36344
            }
36345
        } else if (sourceDuration) {
36346
            segment.duration = sourceDuration;
36347
            segment.timeline = periodStart;
36348
        } // If presentation time is provided, these segments are being generated by SIDX
36349
        // references, and should use the time provided. For the general case of SegmentBase,
36350
        // there should only be one segment in the period, so its presentation time is the same
36351
        // as its period start.
36352
 
36353
        segment.presentationTime = presentationTime || periodStart;
36354
        segment.number = number;
36355
        return [segment];
36356
    };
36357
    /**
36358
     * Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist
36359
     * according to the sidx information given.
36360
     *
36361
     * playlist.sidx has metadadata about the sidx where-as the sidx param
36362
     * is the parsed sidx box itself.
36363
     *
36364
     * @param {Object} playlist the playlist to update the sidx information for
36365
     * @param {Object} sidx the parsed sidx box
36366
     * @return {Object} the playlist object with the updated sidx information
36367
     */
36368
 
36369
    const addSidxSegmentsToPlaylist$1 = (playlist, sidx, baseUrl) => {
36370
        // Retain init segment information
36371
        const initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial main manifest parsing
36372
 
36373
        const sourceDuration = playlist.sidx.duration; // Retain source timeline
36374
 
36375
        const timeline = playlist.timeline || 0;
36376
        const sidxByteRange = playlist.sidx.byterange;
36377
        const sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx
36378
 
36379
        const timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes
36380
 
36381
        const mediaReferences = sidx.references.filter(r => r.referenceType !== 1);
36382
        const segments = [];
36383
        const type = playlist.endList ? 'static' : 'dynamic';
36384
        const periodStart = playlist.sidx.timeline;
36385
        let presentationTime = periodStart;
36386
        let number = playlist.mediaSequence || 0; // firstOffset is the offset from the end of the sidx box
36387
 
36388
        let startIndex; // eslint-disable-next-line
36389
 
36390
        if (typeof sidx.firstOffset === 'bigint') {
36391
            startIndex = window.BigInt(sidxEnd) + sidx.firstOffset;
36392
        } else {
36393
            startIndex = sidxEnd + sidx.firstOffset;
36394
        }
36395
        for (let i = 0; i < mediaReferences.length; i++) {
36396
            const reference = sidx.references[i]; // size of the referenced (sub)segment
36397
 
36398
            const size = reference.referencedSize; // duration of the referenced (sub)segment, in  the  timescale
36399
            // this will be converted to seconds when generating segments
36400
 
36401
            const duration = reference.subsegmentDuration; // should be an inclusive range
36402
 
36403
            let endIndex; // eslint-disable-next-line
36404
 
36405
            if (typeof startIndex === 'bigint') {
36406
                endIndex = startIndex + window.BigInt(size) - window.BigInt(1);
36407
            } else {
36408
                endIndex = startIndex + size - 1;
36409
            }
36410
            const indexRange = `${startIndex}-${endIndex}`;
36411
            const attributes = {
36412
                baseUrl,
36413
                timescale,
36414
                timeline,
36415
                periodStart,
36416
                presentationTime,
36417
                number,
36418
                duration,
36419
                sourceDuration,
36420
                indexRange,
36421
                type
36422
            };
36423
            const segment = segmentsFromBase(attributes)[0];
36424
            if (initSegment) {
36425
                segment.map = initSegment;
36426
            }
36427
            segments.push(segment);
36428
            if (typeof startIndex === 'bigint') {
36429
                startIndex += window.BigInt(size);
36430
            } else {
36431
                startIndex += size;
36432
            }
36433
            presentationTime += duration / timescale;
36434
            number++;
36435
        }
36436
        playlist.segments = segments;
36437
        return playlist;
36438
    };
36439
    const SUPPORTED_MEDIA_TYPES = ['AUDIO', 'SUBTITLES']; // allow one 60fps frame as leniency (arbitrarily chosen)
36440
 
36441
    const TIME_FUDGE = 1 / 60;
36442
    /**
36443
     * Given a list of timelineStarts, combines, dedupes, and sorts them.
36444
     *
36445
     * @param {TimelineStart[]} timelineStarts - list of timeline starts
36446
     *
36447
     * @return {TimelineStart[]} the combined and deduped timeline starts
36448
     */
36449
 
36450
    const getUniqueTimelineStarts = timelineStarts => {
36451
        return union(timelineStarts, ({
36452
                                          timeline
36453
                                      }) => timeline).sort((a, b) => a.timeline > b.timeline ? 1 : -1);
36454
    };
36455
    /**
36456
     * Finds the playlist with the matching NAME attribute.
36457
     *
36458
     * @param {Array} playlists - playlists to search through
36459
     * @param {string} name - the NAME attribute to search for
36460
     *
36461
     * @return {Object|null} the matching playlist object, or null
36462
     */
36463
 
36464
    const findPlaylistWithName = (playlists, name) => {
36465
        for (let i = 0; i < playlists.length; i++) {
36466
            if (playlists[i].attributes.NAME === name) {
36467
                return playlists[i];
36468
            }
36469
        }
36470
        return null;
36471
    };
36472
    /**
36473
     * Gets a flattened array of media group playlists.
36474
     *
36475
     * @param {Object} manifest - the main manifest object
36476
     *
36477
     * @return {Array} the media group playlists
36478
     */
36479
 
36480
    const getMediaGroupPlaylists = manifest => {
36481
        let mediaGroupPlaylists = [];
36482
        forEachMediaGroup$1(manifest, SUPPORTED_MEDIA_TYPES, (properties, type, group, label) => {
36483
            mediaGroupPlaylists = mediaGroupPlaylists.concat(properties.playlists || []);
36484
        });
36485
        return mediaGroupPlaylists;
36486
    };
36487
    /**
36488
     * Updates the playlist's media sequence numbers.
36489
     *
36490
     * @param {Object} config - options object
36491
     * @param {Object} config.playlist - the playlist to update
36492
     * @param {number} config.mediaSequence - the mediaSequence number to start with
36493
     */
36494
 
36495
    const updateMediaSequenceForPlaylist = ({
36496
                                                playlist,
36497
                                                mediaSequence
36498
                                            }) => {
36499
        playlist.mediaSequence = mediaSequence;
36500
        playlist.segments.forEach((segment, index) => {
36501
            segment.number = playlist.mediaSequence + index;
36502
        });
36503
    };
36504
    /**
36505
     * Updates the media and discontinuity sequence numbers of newPlaylists given oldPlaylists
36506
     * and a complete list of timeline starts.
36507
     *
36508
     * If no matching playlist is found, only the discontinuity sequence number of the playlist
36509
     * will be updated.
36510
     *
36511
     * Since early available timelines are not supported, at least one segment must be present.
36512
     *
36513
     * @param {Object} config - options object
36514
     * @param {Object[]} oldPlaylists - the old playlists to use as a reference
36515
     * @param {Object[]} newPlaylists - the new playlists to update
36516
     * @param {Object} timelineStarts - all timelineStarts seen in the stream to this point
36517
     */
36518
 
36519
    const updateSequenceNumbers = ({
36520
                                       oldPlaylists,
36521
                                       newPlaylists,
36522
                                       timelineStarts
36523
                                   }) => {
36524
        newPlaylists.forEach(playlist => {
36525
            playlist.discontinuitySequence = timelineStarts.findIndex(function ({
36526
                                                                                    timeline
36527
                                                                                }) {
36528
                return timeline === playlist.timeline;
36529
            }); // Playlists NAMEs come from DASH Representation IDs, which are mandatory
36530
            // (see ISO_23009-1-2012 5.3.5.2).
36531
            //
36532
            // If the same Representation existed in a prior Period, it will retain the same NAME.
36533
 
36534
            const oldPlaylist = findPlaylistWithName(oldPlaylists, playlist.attributes.NAME);
36535
            if (!oldPlaylist) {
36536
                // Since this is a new playlist, the media sequence values can start from 0 without
36537
                // consequence.
36538
                return;
36539
            } // TODO better support for live SIDX
36540
            //
36541
            // As of this writing, mpd-parser does not support multiperiod SIDX (in live or VOD).
36542
            // This is evident by a playlist only having a single SIDX reference. In a multiperiod
36543
            // playlist there would need to be multiple SIDX references. In addition, live SIDX is
36544
            // not supported when the SIDX properties change on refreshes.
36545
            //
36546
            // In the future, if support needs to be added, the merging logic here can be called
36547
            // after SIDX references are resolved. For now, exit early to prevent exceptions being
36548
            // thrown due to undefined references.
36549
 
36550
            if (playlist.sidx) {
36551
                return;
36552
            } // Since we don't yet support early available timelines, we don't need to support
36553
            // playlists with no segments.
36554
 
36555
            const firstNewSegment = playlist.segments[0];
36556
            const oldMatchingSegmentIndex = oldPlaylist.segments.findIndex(function (oldSegment) {
36557
                return Math.abs(oldSegment.presentationTime - firstNewSegment.presentationTime) < TIME_FUDGE;
36558
            }); // No matching segment from the old playlist means the entire playlist was refreshed.
36559
            // In this case the media sequence should account for this update, and the new segments
36560
            // should be marked as discontinuous from the prior content, since the last prior
36561
            // timeline was removed.
36562
 
36563
            if (oldMatchingSegmentIndex === -1) {
36564
                updateMediaSequenceForPlaylist({
36565
                    playlist,
36566
                    mediaSequence: oldPlaylist.mediaSequence + oldPlaylist.segments.length
36567
                });
36568
                playlist.segments[0].discontinuity = true;
36569
                playlist.discontinuityStarts.unshift(0); // No matching segment does not necessarily mean there's missing content.
36570
                //
36571
                // If the new playlist's timeline is the same as the last seen segment's timeline,
36572
                // then a discontinuity can be added to identify that there's potentially missing
36573
                // content. If there's no missing content, the discontinuity should still be rather
36574
                // harmless. It's possible that if segment durations are accurate enough, that the
36575
                // existence of a gap can be determined using the presentation times and durations,
36576
                // but if the segment timing info is off, it may introduce more problems than simply
36577
                // adding the discontinuity.
36578
                //
36579
                // If the new playlist's timeline is different from the last seen segment's timeline,
36580
                // then a discontinuity can be added to identify that this is the first seen segment
36581
                // of a new timeline. However, the logic at the start of this function that
36582
                // determined the disconinuity sequence by timeline index is now off by one (the
36583
                // discontinuity of the newest timeline hasn't yet fallen off the manifest...since
36584
                // we added it), so the disconinuity sequence must be decremented.
36585
                //
36586
                // A period may also have a duration of zero, so the case of no segments is handled
36587
                // here even though we don't yet support early available periods.
36588
 
36589
                if (!oldPlaylist.segments.length && playlist.timeline > oldPlaylist.timeline || oldPlaylist.segments.length && playlist.timeline > oldPlaylist.segments[oldPlaylist.segments.length - 1].timeline) {
36590
                    playlist.discontinuitySequence--;
36591
                }
36592
                return;
36593
            } // If the first segment matched with a prior segment on a discontinuity (it's matching
36594
            // on the first segment of a period), then the discontinuitySequence shouldn't be the
36595
            // timeline's matching one, but instead should be the one prior, and the first segment
36596
            // of the new manifest should be marked with a discontinuity.
36597
            //
36598
            // The reason for this special case is that discontinuity sequence shows how many
36599
            // discontinuities have fallen off of the playlist, and discontinuities are marked on
36600
            // the first segment of a new "timeline." Because of this, while DASH will retain that
36601
            // Period while the "timeline" exists, HLS keeps track of it via the discontinuity
36602
            // sequence, and that first segment is an indicator, but can be removed before that
36603
            // timeline is gone.
36604
 
36605
            const oldMatchingSegment = oldPlaylist.segments[oldMatchingSegmentIndex];
36606
            if (oldMatchingSegment.discontinuity && !firstNewSegment.discontinuity) {
36607
                firstNewSegment.discontinuity = true;
36608
                playlist.discontinuityStarts.unshift(0);
36609
                playlist.discontinuitySequence--;
36610
            }
36611
            updateMediaSequenceForPlaylist({
36612
                playlist,
36613
                mediaSequence: oldPlaylist.segments[oldMatchingSegmentIndex].number
36614
            });
36615
        });
36616
    };
36617
    /**
36618
     * Given an old parsed manifest object and a new parsed manifest object, updates the
36619
     * sequence and timing values within the new manifest to ensure that it lines up with the
36620
     * old.
36621
     *
36622
     * @param {Array} oldManifest - the old main manifest object
36623
     * @param {Array} newManifest - the new main manifest object
36624
     *
36625
     * @return {Object} the updated new manifest object
36626
     */
36627
 
36628
    const positionManifestOnTimeline = ({
36629
                                            oldManifest,
36630
                                            newManifest
36631
                                        }) => {
36632
        // Starting from v4.1.2 of the IOP, section 4.4.3.3 states:
36633
        //
36634
        // "MPD@availabilityStartTime and Period@start shall not be changed over MPD updates."
36635
        //
36636
        // This was added from https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/160
36637
        //
36638
        // Because of this change, and the difficulty of supporting periods with changing start
36639
        // times, periods with changing start times are not supported. This makes the logic much
36640
        // simpler, since periods with the same start time can be considerred the same period
36641
        // across refreshes.
36642
        //
36643
        // To give an example as to the difficulty of handling periods where the start time may
36644
        // change, if a single period manifest is refreshed with another manifest with a single
36645
        // period, and both the start and end times are increased, then the only way to determine
36646
        // if it's a new period or an old one that has changed is to look through the segments of
36647
        // each playlist and determine the presentation time bounds to find a match. In addition,
36648
        // if the period start changed to exceed the old period end, then there would be no
36649
        // match, and it would not be possible to determine whether the refreshed period is a new
36650
        // one or the old one.
36651
        const oldPlaylists = oldManifest.playlists.concat(getMediaGroupPlaylists(oldManifest));
36652
        const newPlaylists = newManifest.playlists.concat(getMediaGroupPlaylists(newManifest)); // Save all seen timelineStarts to the new manifest. Although this potentially means that
36653
        // there's a "memory leak" in that it will never stop growing, in reality, only a couple
36654
        // of properties are saved for each seen Period. Even long running live streams won't
36655
        // generate too many Periods, unless the stream is watched for decades. In the future,
36656
        // this can be optimized by mapping to discontinuity sequence numbers for each timeline,
36657
        // but it may not become an issue, and the additional info can be useful for debugging.
36658
 
36659
        newManifest.timelineStarts = getUniqueTimelineStarts([oldManifest.timelineStarts, newManifest.timelineStarts]);
36660
        updateSequenceNumbers({
36661
            oldPlaylists,
36662
            newPlaylists,
36663
            timelineStarts: newManifest.timelineStarts
36664
        });
36665
        return newManifest;
36666
    };
36667
    const generateSidxKey = sidx => sidx && sidx.uri + '-' + byteRangeToString(sidx.byterange);
36668
    const mergeDiscontiguousPlaylists = playlists => {
36669
        // Break out playlists into groups based on their baseUrl
36670
        const playlistsByBaseUrl = playlists.reduce(function (acc, cur) {
36671
            if (!acc[cur.attributes.baseUrl]) {
36672
                acc[cur.attributes.baseUrl] = [];
36673
            }
36674
            acc[cur.attributes.baseUrl].push(cur);
36675
            return acc;
36676
        }, {});
36677
        let allPlaylists = [];
36678
        Object.values(playlistsByBaseUrl).forEach(playlistGroup => {
36679
            const mergedPlaylists = values(playlistGroup.reduce((acc, playlist) => {
36680
                // assuming playlist IDs are the same across periods
36681
                // TODO: handle multiperiod where representation sets are not the same
36682
                // across periods
36683
                const name = playlist.attributes.id + (playlist.attributes.lang || '');
36684
                if (!acc[name]) {
36685
                    // First Period
36686
                    acc[name] = playlist;
36687
                    acc[name].attributes.timelineStarts = [];
36688
                } else {
36689
                    // Subsequent Periods
36690
                    if (playlist.segments) {
36691
                        // first segment of subsequent periods signal a discontinuity
36692
                        if (playlist.segments[0]) {
36693
                            playlist.segments[0].discontinuity = true;
36694
                        }
36695
                        acc[name].segments.push(...playlist.segments);
36696
                    } // bubble up contentProtection, this assumes all DRM content
36697
                    // has the same contentProtection
36698
 
36699
                    if (playlist.attributes.contentProtection) {
36700
                        acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
36701
                    }
36702
                }
36703
                acc[name].attributes.timelineStarts.push({
36704
                    // Although they represent the same number, it's important to have both to make it
36705
                    // compatible with HLS potentially having a similar attribute.
36706
                    start: playlist.attributes.periodStart,
36707
                    timeline: playlist.attributes.periodStart
36708
                });
36709
                return acc;
36710
            }, {}));
36711
            allPlaylists = allPlaylists.concat(mergedPlaylists);
36712
        });
36713
        return allPlaylists.map(playlist => {
36714
            playlist.discontinuityStarts = findIndexes(playlist.segments || [], 'discontinuity');
36715
            return playlist;
36716
        });
36717
    };
36718
    const addSidxSegmentsToPlaylist = (playlist, sidxMapping) => {
36719
        const sidxKey = generateSidxKey(playlist.sidx);
36720
        const sidxMatch = sidxKey && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;
36721
        if (sidxMatch) {
36722
            addSidxSegmentsToPlaylist$1(playlist, sidxMatch, playlist.sidx.resolvedUri);
36723
        }
36724
        return playlist;
36725
    };
36726
    const addSidxSegmentsToPlaylists = (playlists, sidxMapping = {}) => {
36727
        if (!Object.keys(sidxMapping).length) {
36728
            return playlists;
36729
        }
36730
        for (const i in playlists) {
36731
            playlists[i] = addSidxSegmentsToPlaylist(playlists[i], sidxMapping);
36732
        }
36733
        return playlists;
36734
    };
36735
    const formatAudioPlaylist = ({
36736
                                     attributes,
36737
                                     segments,
36738
                                     sidx,
36739
                                     mediaSequence,
36740
                                     discontinuitySequence,
36741
                                     discontinuityStarts
36742
                                 }, isAudioOnly) => {
36743
        const playlist = {
36744
            attributes: {
36745
                NAME: attributes.id,
36746
                BANDWIDTH: attributes.bandwidth,
36747
                CODECS: attributes.codecs,
36748
                ['PROGRAM-ID']: 1
36749
            },
36750
            uri: '',
36751
            endList: attributes.type === 'static',
36752
            timeline: attributes.periodStart,
36753
            resolvedUri: attributes.baseUrl || '',
36754
            targetDuration: attributes.duration,
36755
            discontinuitySequence,
36756
            discontinuityStarts,
36757
            timelineStarts: attributes.timelineStarts,
36758
            mediaSequence,
36759
            segments
36760
        };
36761
        if (attributes.contentProtection) {
36762
            playlist.contentProtection = attributes.contentProtection;
36763
        }
36764
        if (attributes.serviceLocation) {
36765
            playlist.attributes.serviceLocation = attributes.serviceLocation;
36766
        }
36767
        if (sidx) {
36768
            playlist.sidx = sidx;
36769
        }
36770
        if (isAudioOnly) {
36771
            playlist.attributes.AUDIO = 'audio';
36772
            playlist.attributes.SUBTITLES = 'subs';
36773
        }
36774
        return playlist;
36775
    };
36776
    const formatVttPlaylist = ({
36777
                                   attributes,
36778
                                   segments,
36779
                                   mediaSequence,
36780
                                   discontinuityStarts,
36781
                                   discontinuitySequence
36782
                               }) => {
36783
        if (typeof segments === 'undefined') {
36784
            // vtt tracks may use single file in BaseURL
36785
            segments = [{
36786
                uri: attributes.baseUrl,
36787
                timeline: attributes.periodStart,
36788
                resolvedUri: attributes.baseUrl || '',
36789
                duration: attributes.sourceDuration,
36790
                number: 0
36791
            }]; // targetDuration should be the same duration as the only segment
36792
 
36793
            attributes.duration = attributes.sourceDuration;
36794
        }
36795
        const m3u8Attributes = {
36796
            NAME: attributes.id,
36797
            BANDWIDTH: attributes.bandwidth,
36798
            ['PROGRAM-ID']: 1
36799
        };
36800
        if (attributes.codecs) {
36801
            m3u8Attributes.CODECS = attributes.codecs;
36802
        }
36803
        const vttPlaylist = {
36804
            attributes: m3u8Attributes,
36805
            uri: '',
36806
            endList: attributes.type === 'static',
36807
            timeline: attributes.periodStart,
36808
            resolvedUri: attributes.baseUrl || '',
36809
            targetDuration: attributes.duration,
36810
            timelineStarts: attributes.timelineStarts,
36811
            discontinuityStarts,
36812
            discontinuitySequence,
36813
            mediaSequence,
36814
            segments
36815
        };
36816
        if (attributes.serviceLocation) {
36817
            vttPlaylist.attributes.serviceLocation = attributes.serviceLocation;
36818
        }
36819
        return vttPlaylist;
36820
    };
36821
    const organizeAudioPlaylists = (playlists, sidxMapping = {}, isAudioOnly = false) => {
36822
        let mainPlaylist;
36823
        const formattedPlaylists = playlists.reduce((a, playlist) => {
36824
            const role = playlist.attributes.role && playlist.attributes.role.value || '';
36825
            const language = playlist.attributes.lang || '';
36826
            let label = playlist.attributes.label || 'main';
36827
            if (language && !playlist.attributes.label) {
36828
                const roleLabel = role ? ` (${role})` : '';
36829
                label = `${playlist.attributes.lang}${roleLabel}`;
36830
            }
36831
            if (!a[label]) {
36832
                a[label] = {
36833
                    language,
36834
                    autoselect: true,
36835
                    default: role === 'main',
36836
                    playlists: [],
36837
                    uri: ''
36838
                };
36839
            }
36840
            const formatted = addSidxSegmentsToPlaylist(formatAudioPlaylist(playlist, isAudioOnly), sidxMapping);
36841
            a[label].playlists.push(formatted);
36842
            if (typeof mainPlaylist === 'undefined' && role === 'main') {
36843
                mainPlaylist = playlist;
36844
                mainPlaylist.default = true;
36845
            }
36846
            return a;
36847
        }, {}); // if no playlists have role "main", mark the first as main
36848
 
36849
        if (!mainPlaylist) {
36850
            const firstLabel = Object.keys(formattedPlaylists)[0];
36851
            formattedPlaylists[firstLabel].default = true;
36852
        }
36853
        return formattedPlaylists;
36854
    };
36855
    const organizeVttPlaylists = (playlists, sidxMapping = {}) => {
36856
        return playlists.reduce((a, playlist) => {
36857
            const label = playlist.attributes.label || playlist.attributes.lang || 'text';
36858
            if (!a[label]) {
36859
                a[label] = {
36860
                    language: label,
36861
                    default: false,
36862
                    autoselect: false,
36863
                    playlists: [],
36864
                    uri: ''
36865
                };
36866
            }
36867
            a[label].playlists.push(addSidxSegmentsToPlaylist(formatVttPlaylist(playlist), sidxMapping));
36868
            return a;
36869
        }, {});
36870
    };
36871
    const organizeCaptionServices = captionServices => captionServices.reduce((svcObj, svc) => {
36872
        if (!svc) {
36873
            return svcObj;
36874
        }
36875
        svc.forEach(service => {
36876
            const {
36877
                channel,
36878
                language
36879
            } = service;
36880
            svcObj[language] = {
36881
                autoselect: false,
36882
                default: false,
36883
                instreamId: channel,
36884
                language
36885
            };
36886
            if (service.hasOwnProperty('aspectRatio')) {
36887
                svcObj[language].aspectRatio = service.aspectRatio;
36888
            }
36889
            if (service.hasOwnProperty('easyReader')) {
36890
                svcObj[language].easyReader = service.easyReader;
36891
            }
36892
            if (service.hasOwnProperty('3D')) {
36893
                svcObj[language]['3D'] = service['3D'];
36894
            }
36895
        });
36896
        return svcObj;
36897
    }, {});
36898
    const formatVideoPlaylist = ({
36899
                                     attributes,
36900
                                     segments,
36901
                                     sidx,
36902
                                     discontinuityStarts
36903
                                 }) => {
36904
        const playlist = {
36905
            attributes: {
36906
                NAME: attributes.id,
36907
                AUDIO: 'audio',
36908
                SUBTITLES: 'subs',
36909
                RESOLUTION: {
36910
                    width: attributes.width,
36911
                    height: attributes.height
36912
                },
36913
                CODECS: attributes.codecs,
36914
                BANDWIDTH: attributes.bandwidth,
36915
                ['PROGRAM-ID']: 1
36916
            },
36917
            uri: '',
36918
            endList: attributes.type === 'static',
36919
            timeline: attributes.periodStart,
36920
            resolvedUri: attributes.baseUrl || '',
36921
            targetDuration: attributes.duration,
36922
            discontinuityStarts,
36923
            timelineStarts: attributes.timelineStarts,
36924
            segments
36925
        };
36926
        if (attributes.frameRate) {
36927
            playlist.attributes['FRAME-RATE'] = attributes.frameRate;
36928
        }
36929
        if (attributes.contentProtection) {
36930
            playlist.contentProtection = attributes.contentProtection;
36931
        }
36932
        if (attributes.serviceLocation) {
36933
            playlist.attributes.serviceLocation = attributes.serviceLocation;
36934
        }
36935
        if (sidx) {
36936
            playlist.sidx = sidx;
36937
        }
36938
        return playlist;
36939
    };
36940
    const videoOnly = ({
36941
                           attributes
36942
                       }) => attributes.mimeType === 'video/mp4' || attributes.mimeType === 'video/webm' || attributes.contentType === 'video';
36943
    const audioOnly = ({
36944
                           attributes
36945
                       }) => attributes.mimeType === 'audio/mp4' || attributes.mimeType === 'audio/webm' || attributes.contentType === 'audio';
36946
    const vttOnly = ({
36947
                         attributes
36948
                     }) => attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
36949
    /**
36950
     * Contains start and timeline properties denoting a timeline start. For DASH, these will
36951
     * be the same number.
36952
     *
36953
     * @typedef {Object} TimelineStart
36954
     * @property {number} start - the start time of the timeline
36955
     * @property {number} timeline - the timeline number
36956
     */
36957
 
36958
    /**
36959
     * Adds appropriate media and discontinuity sequence values to the segments and playlists.
36960
     *
36961
     * Throughout mpd-parser, the `number` attribute is used in relation to `startNumber`, a
36962
     * DASH specific attribute used in constructing segment URI's from templates. However, from
36963
     * an HLS perspective, the `number` attribute on a segment would be its `mediaSequence`
36964
     * value, which should start at the original media sequence value (or 0) and increment by 1
36965
     * for each segment thereafter. Since DASH's `startNumber` values are independent per
36966
     * period, it doesn't make sense to use it for `number`. Instead, assume everything starts
36967
     * from a 0 mediaSequence value and increment from there.
36968
     *
36969
     * Note that VHS currently doesn't use the `number` property, but it can be helpful for
36970
     * debugging and making sense of the manifest.
36971
     *
36972
     * For live playlists, to account for values increasing in manifests when periods are
36973
     * removed on refreshes, merging logic should be used to update the numbers to their
36974
     * appropriate values (to ensure they're sequential and increasing).
36975
     *
36976
     * @param {Object[]} playlists - the playlists to update
36977
     * @param {TimelineStart[]} timelineStarts - the timeline starts for the manifest
36978
     */
36979
 
36980
    const addMediaSequenceValues = (playlists, timelineStarts) => {
36981
        // increment all segments sequentially
36982
        playlists.forEach(playlist => {
36983
            playlist.mediaSequence = 0;
36984
            playlist.discontinuitySequence = timelineStarts.findIndex(function ({
36985
                                                                                    timeline
36986
                                                                                }) {
36987
                return timeline === playlist.timeline;
36988
            });
36989
            if (!playlist.segments) {
36990
                return;
36991
            }
36992
            playlist.segments.forEach((segment, index) => {
36993
                segment.number = index;
36994
            });
36995
        });
36996
    };
36997
    /**
36998
     * Given a media group object, flattens all playlists within the media group into a single
36999
     * array.
37000
     *
37001
     * @param {Object} mediaGroupObject - the media group object
37002
     *
37003
     * @return {Object[]}
37004
     *         The media group playlists
37005
     */
37006
 
37007
    const flattenMediaGroupPlaylists = mediaGroupObject => {
37008
        if (!mediaGroupObject) {
37009
            return [];
37010
        }
37011
        return Object.keys(mediaGroupObject).reduce((acc, label) => {
37012
            const labelContents = mediaGroupObject[label];
37013
            return acc.concat(labelContents.playlists);
37014
        }, []);
37015
    };
37016
    const toM3u8 = ({
37017
                        dashPlaylists,
37018
                        locations,
37019
                        contentSteering,
37020
                        sidxMapping = {},
37021
                        previousManifest,
37022
                        eventStream
37023
                    }) => {
37024
        if (!dashPlaylists.length) {
37025
            return {};
37026
        } // grab all main manifest attributes
37027
 
37028
        const {
37029
            sourceDuration: duration,
37030
            type,
37031
            suggestedPresentationDelay,
37032
            minimumUpdatePeriod
37033
        } = dashPlaylists[0].attributes;
37034
        const videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist);
37035
        const audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly));
37036
        const vttPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(vttOnly));
37037
        const captions = dashPlaylists.map(playlist => playlist.attributes.captionServices).filter(Boolean);
37038
        const manifest = {
37039
            allowCache: true,
37040
            discontinuityStarts: [],
37041
            segments: [],
37042
            endList: true,
37043
            mediaGroups: {
37044
                AUDIO: {},
37045
                VIDEO: {},
37046
                ['CLOSED-CAPTIONS']: {},
37047
                SUBTITLES: {}
37048
            },
37049
            uri: '',
37050
            duration,
37051
            playlists: addSidxSegmentsToPlaylists(videoPlaylists, sidxMapping)
37052
        };
37053
        if (minimumUpdatePeriod >= 0) {
37054
            manifest.minimumUpdatePeriod = minimumUpdatePeriod * 1000;
37055
        }
37056
        if (locations) {
37057
            manifest.locations = locations;
37058
        }
37059
        if (contentSteering) {
37060
            manifest.contentSteering = contentSteering;
37061
        }
37062
        if (type === 'dynamic') {
37063
            manifest.suggestedPresentationDelay = suggestedPresentationDelay;
37064
        }
37065
        if (eventStream && eventStream.length > 0) {
37066
            manifest.eventStream = eventStream;
37067
        }
37068
        const isAudioOnly = manifest.playlists.length === 0;
37069
        const organizedAudioGroup = audioPlaylists.length ? organizeAudioPlaylists(audioPlaylists, sidxMapping, isAudioOnly) : null;
37070
        const organizedVttGroup = vttPlaylists.length ? organizeVttPlaylists(vttPlaylists, sidxMapping) : null;
37071
        const formattedPlaylists = videoPlaylists.concat(flattenMediaGroupPlaylists(organizedAudioGroup), flattenMediaGroupPlaylists(organizedVttGroup));
37072
        const playlistTimelineStarts = formattedPlaylists.map(({
37073
                                                                   timelineStarts
37074
                                                               }) => timelineStarts);
37075
        manifest.timelineStarts = getUniqueTimelineStarts(playlistTimelineStarts);
37076
        addMediaSequenceValues(formattedPlaylists, manifest.timelineStarts);
37077
        if (organizedAudioGroup) {
37078
            manifest.mediaGroups.AUDIO.audio = organizedAudioGroup;
37079
        }
37080
        if (organizedVttGroup) {
37081
            manifest.mediaGroups.SUBTITLES.subs = organizedVttGroup;
37082
        }
37083
        if (captions.length) {
37084
            manifest.mediaGroups['CLOSED-CAPTIONS'].cc = organizeCaptionServices(captions);
37085
        }
37086
        if (previousManifest) {
37087
            return positionManifestOnTimeline({
37088
                oldManifest: previousManifest,
37089
                newManifest: manifest
37090
            });
37091
        }
37092
        return manifest;
37093
    };
37094
 
37095
    /**
37096
     * Calculates the R (repetition) value for a live stream (for the final segment
37097
     * in a manifest where the r value is negative 1)
37098
     *
37099
     * @param {Object} attributes
37100
     *        Object containing all inherited attributes from parent elements with attribute
37101
     *        names as keys
37102
     * @param {number} time
37103
     *        current time (typically the total time up until the final segment)
37104
     * @param {number} duration
37105
     *        duration property for the given <S />
37106
     *
37107
     * @return {number}
37108
     *        R value to reach the end of the given period
37109
     */
37110
    const getLiveRValue = (attributes, time, duration) => {
37111
        const {
37112
            NOW,
37113
            clientOffset,
37114
            availabilityStartTime,
37115
            timescale = 1,
37116
            periodStart = 0,
37117
            minimumUpdatePeriod = 0
37118
        } = attributes;
37119
        const now = (NOW + clientOffset) / 1000;
37120
        const periodStartWC = availabilityStartTime + periodStart;
37121
        const periodEndWC = now + minimumUpdatePeriod;
37122
        const periodDuration = periodEndWC - periodStartWC;
37123
        return Math.ceil((periodDuration * timescale - time) / duration);
37124
    };
37125
    /**
37126
     * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
37127
     * timing and duration
37128
     *
37129
     * @param {Object} attributes
37130
     *        Object containing all inherited attributes from parent elements with attribute
37131
     *        names as keys
37132
     * @param {Object[]} segmentTimeline
37133
     *        List of objects representing the attributes of each S element contained within
37134
     *
37135
     * @return {{number: number, duration: number, time: number, timeline: number}[]}
37136
     *         List of Objects with segment timing and duration info
37137
     */
37138
 
37139
    const parseByTimeline = (attributes, segmentTimeline) => {
37140
        const {
37141
            type,
37142
            minimumUpdatePeriod = 0,
37143
            media = '',
37144
            sourceDuration,
37145
            timescale = 1,
37146
            startNumber = 1,
37147
            periodStart: timeline
37148
        } = attributes;
37149
        const segments = [];
37150
        let time = -1;
37151
        for (let sIndex = 0; sIndex < segmentTimeline.length; sIndex++) {
37152
            const S = segmentTimeline[sIndex];
37153
            const duration = S.d;
37154
            const repeat = S.r || 0;
37155
            const segmentTime = S.t || 0;
37156
            if (time < 0) {
37157
                // first segment
37158
                time = segmentTime;
37159
            }
37160
            if (segmentTime && segmentTime > time) {
37161
                // discontinuity
37162
                // TODO: How to handle this type of discontinuity
37163
                // timeline++ here would treat it like HLS discontuity and content would
37164
                // get appended without gap
37165
                // E.G.
37166
                //  <S t="0" d="1" />
37167
                //  <S d="1" />
37168
                //  <S d="1" />
37169
                //  <S t="5" d="1" />
37170
                // would have $Time$ values of [0, 1, 2, 5]
37171
                // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY)
37172
                // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP)
37173
                // does the value of sourceDuration consider this when calculating arbitrary
37174
                // negative @r repeat value?
37175
                // E.G. Same elements as above with this added at the end
37176
                //  <S d="1" r="-1" />
37177
                //  with a sourceDuration of 10
37178
                // Would the 2 gaps be included in the time duration calculations resulting in
37179
                // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments
37180
                // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ?
37181
                time = segmentTime;
37182
            }
37183
            let count;
37184
            if (repeat < 0) {
37185
                const nextS = sIndex + 1;
37186
                if (nextS === segmentTimeline.length) {
37187
                    // last segment
37188
                    if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) {
37189
                        count = getLiveRValue(attributes, time, duration);
37190
                    } else {
37191
                        // TODO: This may be incorrect depending on conclusion of TODO above
37192
                        count = (sourceDuration * timescale - time) / duration;
37193
                    }
37194
                } else {
37195
                    count = (segmentTimeline[nextS].t - time) / duration;
37196
                }
37197
            } else {
37198
                count = repeat + 1;
37199
            }
37200
            const end = startNumber + segments.length + count;
37201
            let number = startNumber + segments.length;
37202
            while (number < end) {
37203
                segments.push({
37204
                    number,
37205
                    duration: duration / timescale,
37206
                    time,
37207
                    timeline
37208
                });
37209
                time += duration;
37210
                number++;
37211
            }
37212
        }
37213
        return segments;
37214
    };
37215
    const identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
37216
    /**
37217
     * Replaces template identifiers with corresponding values. To be used as the callback
37218
     * for String.prototype.replace
37219
     *
37220
     * @name replaceCallback
37221
     * @function
37222
     * @param {string} match
37223
     *        Entire match of identifier
37224
     * @param {string} identifier
37225
     *        Name of matched identifier
37226
     * @param {string} format
37227
     *        Format tag string. Its presence indicates that padding is expected
37228
     * @param {string} width
37229
     *        Desired length of the replaced value. Values less than this width shall be left
37230
     *        zero padded
37231
     * @return {string}
37232
     *         Replacement for the matched identifier
37233
     */
37234
 
37235
    /**
37236
     * Returns a function to be used as a callback for String.prototype.replace to replace
37237
     * template identifiers
37238
     *
37239
     * @param {Obect} values
37240
     *        Object containing values that shall be used to replace known identifiers
37241
     * @param {number} values.RepresentationID
37242
     *        Value of the Representation@id attribute
37243
     * @param {number} values.Number
37244
     *        Number of the corresponding segment
37245
     * @param {number} values.Bandwidth
37246
     *        Value of the Representation@bandwidth attribute.
37247
     * @param {number} values.Time
37248
     *        Timestamp value of the corresponding segment
37249
     * @return {replaceCallback}
37250
     *         Callback to be used with String.prototype.replace to replace identifiers
37251
     */
37252
 
37253
    const identifierReplacement = values => (match, identifier, format, width) => {
37254
        if (match === '$$') {
37255
            // escape sequence
37256
            return '$';
37257
        }
37258
        if (typeof values[identifier] === 'undefined') {
37259
            return match;
37260
        }
37261
        const value = '' + values[identifier];
37262
        if (identifier === 'RepresentationID') {
37263
            // Format tag shall not be present with RepresentationID
37264
            return value;
37265
        }
37266
        if (!format) {
37267
            width = 1;
37268
        } else {
37269
            width = parseInt(width, 10);
37270
        }
37271
        if (value.length >= width) {
37272
            return value;
37273
        }
37274
        return `${new Array(width - value.length + 1).join('0')}${value}`;
37275
    };
37276
    /**
37277
     * Constructs a segment url from a template string
37278
     *
37279
     * @param {string} url
37280
     *        Template string to construct url from
37281
     * @param {Obect} values
37282
     *        Object containing values that shall be used to replace known identifiers
37283
     * @param {number} values.RepresentationID
37284
     *        Value of the Representation@id attribute
37285
     * @param {number} values.Number
37286
     *        Number of the corresponding segment
37287
     * @param {number} values.Bandwidth
37288
     *        Value of the Representation@bandwidth attribute.
37289
     * @param {number} values.Time
37290
     *        Timestamp value of the corresponding segment
37291
     * @return {string}
37292
     *         Segment url with identifiers replaced
37293
     */
37294
 
37295
    const constructTemplateUrl = (url, values) => url.replace(identifierPattern, identifierReplacement(values));
37296
    /**
37297
     * Generates a list of objects containing timing and duration information about each
37298
     * segment needed to generate segment uris and the complete segment object
37299
     *
37300
     * @param {Object} attributes
37301
     *        Object containing all inherited attributes from parent elements with attribute
37302
     *        names as keys
37303
     * @param {Object[]|undefined} segmentTimeline
37304
     *        List of objects representing the attributes of each S element contained within
37305
     *        the SegmentTimeline element
37306
     * @return {{number: number, duration: number, time: number, timeline: number}[]}
37307
     *         List of Objects with segment timing and duration info
37308
     */
37309
 
37310
    const parseTemplateInfo = (attributes, segmentTimeline) => {
37311
        if (!attributes.duration && !segmentTimeline) {
37312
            // if neither @duration or SegmentTimeline are present, then there shall be exactly
37313
            // one media segment
37314
            return [{
37315
                number: attributes.startNumber || 1,
37316
                duration: attributes.sourceDuration,
37317
                time: 0,
37318
                timeline: attributes.periodStart
37319
            }];
37320
        }
37321
        if (attributes.duration) {
37322
            return parseByDuration(attributes);
37323
        }
37324
        return parseByTimeline(attributes, segmentTimeline);
37325
    };
37326
    /**
37327
     * Generates a list of segments using information provided by the SegmentTemplate element
37328
     *
37329
     * @param {Object} attributes
37330
     *        Object containing all inherited attributes from parent elements with attribute
37331
     *        names as keys
37332
     * @param {Object[]|undefined} segmentTimeline
37333
     *        List of objects representing the attributes of each S element contained within
37334
     *        the SegmentTimeline element
37335
     * @return {Object[]}
37336
     *         List of segment objects
37337
     */
37338
 
37339
    const segmentsFromTemplate = (attributes, segmentTimeline) => {
37340
        const templateValues = {
37341
            RepresentationID: attributes.id,
37342
            Bandwidth: attributes.bandwidth || 0
37343
        };
37344
        const {
37345
            initialization = {
37346
                sourceURL: '',
37347
                range: ''
37348
            }
37349
        } = attributes;
37350
        const mapSegment = urlTypeToSegment({
37351
            baseUrl: attributes.baseUrl,
37352
            source: constructTemplateUrl(initialization.sourceURL, templateValues),
37353
            range: initialization.range
37354
        });
37355
        const segments = parseTemplateInfo(attributes, segmentTimeline);
37356
        return segments.map(segment => {
37357
            templateValues.Number = segment.number;
37358
            templateValues.Time = segment.time;
37359
            const uri = constructTemplateUrl(attributes.media || '', templateValues); // See DASH spec section 5.3.9.2.2
37360
            // - if timescale isn't present on any level, default to 1.
37361
 
37362
            const timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0
37363
 
37364
            const presentationTimeOffset = attributes.presentationTimeOffset || 0;
37365
            const presentationTime =
37366
                // Even if the @t attribute is not specified for the segment, segment.time is
37367
                // calculated in mpd-parser prior to this, so it's assumed to be available.
37368
                attributes.periodStart + (segment.time - presentationTimeOffset) / timescale;
37369
            const map = {
37370
                uri,
37371
                timeline: segment.timeline,
37372
                duration: segment.duration,
37373
                resolvedUri: resolveUrl$1(attributes.baseUrl || '', uri),
37374
                map: mapSegment,
37375
                number: segment.number,
37376
                presentationTime
37377
            };
37378
            return map;
37379
        });
37380
    };
37381
 
37382
    /**
37383
     * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14)
37384
     * to an object that matches the output of a segment in videojs/mpd-parser
37385
     *
37386
     * @param {Object} attributes
37387
     *   Object containing all inherited attributes from parent elements with attribute
37388
     *   names as keys
37389
     * @param {Object} segmentUrl
37390
     *   <SegmentURL> node to translate into a segment object
37391
     * @return {Object} translated segment object
37392
     */
37393
 
37394
    const SegmentURLToSegmentObject = (attributes, segmentUrl) => {
37395
        const {
37396
            baseUrl,
37397
            initialization = {}
37398
        } = attributes;
37399
        const initSegment = urlTypeToSegment({
37400
            baseUrl,
37401
            source: initialization.sourceURL,
37402
            range: initialization.range
37403
        });
37404
        const segment = urlTypeToSegment({
37405
            baseUrl,
37406
            source: segmentUrl.media,
37407
            range: segmentUrl.mediaRange
37408
        });
37409
        segment.map = initSegment;
37410
        return segment;
37411
    };
37412
    /**
37413
     * Generates a list of segments using information provided by the SegmentList element
37414
     * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
37415
     * node should be translated into segment.
37416
     *
37417
     * @param {Object} attributes
37418
     *   Object containing all inherited attributes from parent elements with attribute
37419
     *   names as keys
37420
     * @param {Object[]|undefined} segmentTimeline
37421
     *        List of objects representing the attributes of each S element contained within
37422
     *        the SegmentTimeline element
37423
     * @return {Object.<Array>} list of segments
37424
     */
37425
 
37426
    const segmentsFromList = (attributes, segmentTimeline) => {
37427
        const {
37428
            duration,
37429
            segmentUrls = [],
37430
            periodStart
37431
        } = attributes; // Per spec (5.3.9.2.1) no way to determine segment duration OR
37432
        // if both SegmentTimeline and @duration are defined, it is outside of spec.
37433
 
37434
        if (!duration && !segmentTimeline || duration && segmentTimeline) {
37435
            throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
37436
        }
37437
        const segmentUrlMap = segmentUrls.map(segmentUrlObject => SegmentURLToSegmentObject(attributes, segmentUrlObject));
37438
        let segmentTimeInfo;
37439
        if (duration) {
37440
            segmentTimeInfo = parseByDuration(attributes);
37441
        }
37442
        if (segmentTimeline) {
37443
            segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
37444
        }
37445
        const segments = segmentTimeInfo.map((segmentTime, index) => {
37446
            if (segmentUrlMap[index]) {
37447
                const segment = segmentUrlMap[index]; // See DASH spec section 5.3.9.2.2
37448
                // - if timescale isn't present on any level, default to 1.
37449
 
37450
                const timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0
37451
 
37452
                const presentationTimeOffset = attributes.presentationTimeOffset || 0;
37453
                segment.timeline = segmentTime.timeline;
37454
                segment.duration = segmentTime.duration;
37455
                segment.number = segmentTime.number;
37456
                segment.presentationTime = periodStart + (segmentTime.time - presentationTimeOffset) / timescale;
37457
                return segment;
37458
            } // Since we're mapping we should get rid of any blank segments (in case
37459
            // the given SegmentTimeline is handling for more elements than we have
37460
            // SegmentURLs for).
37461
        }).filter(segment => segment);
37462
        return segments;
37463
    };
37464
    const generateSegments = ({
37465
                                  attributes,
37466
                                  segmentInfo
37467
                              }) => {
37468
        let segmentAttributes;
37469
        let segmentsFn;
37470
        if (segmentInfo.template) {
37471
            segmentsFn = segmentsFromTemplate;
37472
            segmentAttributes = merge$1(attributes, segmentInfo.template);
37473
        } else if (segmentInfo.base) {
37474
            segmentsFn = segmentsFromBase;
37475
            segmentAttributes = merge$1(attributes, segmentInfo.base);
37476
        } else if (segmentInfo.list) {
37477
            segmentsFn = segmentsFromList;
37478
            segmentAttributes = merge$1(attributes, segmentInfo.list);
37479
        }
37480
        const segmentsInfo = {
37481
            attributes
37482
        };
37483
        if (!segmentsFn) {
37484
            return segmentsInfo;
37485
        }
37486
        const segments = segmentsFn(segmentAttributes, segmentInfo.segmentTimeline); // The @duration attribute will be used to determin the playlist's targetDuration which
37487
        // must be in seconds. Since we've generated the segment list, we no longer need
37488
        // @duration to be in @timescale units, so we can convert it here.
37489
 
37490
        if (segmentAttributes.duration) {
37491
            const {
37492
                duration,
37493
                timescale = 1
37494
            } = segmentAttributes;
37495
            segmentAttributes.duration = duration / timescale;
37496
        } else if (segments.length) {
37497
            // if there is no @duration attribute, use the largest segment duration as
37498
            // as target duration
37499
            segmentAttributes.duration = segments.reduce((max, segment) => {
37500
                return Math.max(max, Math.ceil(segment.duration));
37501
            }, 0);
37502
        } else {
37503
            segmentAttributes.duration = 0;
37504
        }
37505
        segmentsInfo.attributes = segmentAttributes;
37506
        segmentsInfo.segments = segments; // This is a sidx box without actual segment information
37507
 
37508
        if (segmentInfo.base && segmentAttributes.indexRange) {
37509
            segmentsInfo.sidx = segments[0];
37510
            segmentsInfo.segments = [];
37511
        }
37512
        return segmentsInfo;
37513
    };
37514
    const toPlaylists = representations => representations.map(generateSegments);
37515
    const findChildren = (element, name) => from(element.childNodes).filter(({
37516
                                                                                 tagName
37517
                                                                             }) => tagName === name);
37518
    const getContent = element => element.textContent.trim();
37519
 
37520
    /**
37521
     * Converts the provided string that may contain a division operation to a number.
37522
     *
37523
     * @param {string} value - the provided string value
37524
     *
37525
     * @return {number} the parsed string value
37526
     */
37527
    const parseDivisionValue = value => {
37528
        return parseFloat(value.split('/').reduce((prev, current) => prev / current));
37529
    };
37530
    const parseDuration = str => {
37531
        const SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
37532
        const SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
37533
        const SECONDS_IN_DAY = 24 * 60 * 60;
37534
        const SECONDS_IN_HOUR = 60 * 60;
37535
        const SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S
37536
 
37537
        const durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
37538
        const match = durationRegex.exec(str);
37539
        if (!match) {
37540
            return 0;
37541
        }
37542
        const [year, month, day, hour, minute, second] = match.slice(1);
37543
        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);
37544
    };
37545
    const parseDate = str => {
37546
        // Date format without timezone according to ISO 8601
37547
        // YYY-MM-DDThh:mm:ss.ssssss
37548
        const dateRegex = /^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/; // If the date string does not specifiy a timezone, we must specifiy UTC. This is
37549
        // expressed by ending with 'Z'
37550
 
37551
        if (dateRegex.test(str)) {
37552
            str += 'Z';
37553
        }
37554
        return Date.parse(str);
37555
    };
37556
    const parsers = {
37557
        /**
37558
         * Specifies the duration of the entire Media Presentation. Format is a duration string
37559
         * as specified in ISO 8601
37560
         *
37561
         * @param {string} value
37562
         *        value of attribute as a string
37563
         * @return {number}
37564
         *         The duration in seconds
37565
         */
37566
        mediaPresentationDuration(value) {
37567
            return parseDuration(value);
37568
        },
37569
        /**
37570
         * Specifies the Segment availability start time for all Segments referred to in this
37571
         * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability
37572
         * time. Format is a date string as specified in ISO 8601
37573
         *
37574
         * @param {string} value
37575
         *        value of attribute as a string
37576
         * @return {number}
37577
         *         The date as seconds from unix epoch
37578
         */
37579
        availabilityStartTime(value) {
37580
            return parseDate(value) / 1000;
37581
        },
37582
        /**
37583
         * Specifies the smallest period between potential changes to the MPD. Format is a
37584
         * duration string as specified in ISO 8601
37585
         *
37586
         * @param {string} value
37587
         *        value of attribute as a string
37588
         * @return {number}
37589
         *         The duration in seconds
37590
         */
37591
        minimumUpdatePeriod(value) {
37592
            return parseDuration(value);
37593
        },
37594
        /**
37595
         * Specifies the suggested presentation delay. Format is a
37596
         * duration string as specified in ISO 8601
37597
         *
37598
         * @param {string} value
37599
         *        value of attribute as a string
37600
         * @return {number}
37601
         *         The duration in seconds
37602
         */
37603
        suggestedPresentationDelay(value) {
37604
            return parseDuration(value);
37605
        },
37606
        /**
37607
         * specifices the type of mpd. Can be either "static" or "dynamic"
37608
         *
37609
         * @param {string} value
37610
         *        value of attribute as a string
37611
         *
37612
         * @return {string}
37613
         *         The type as a string
37614
         */
37615
        type(value) {
37616
            return value;
37617
        },
37618
        /**
37619
         * Specifies the duration of the smallest time shifting buffer for any Representation
37620
         * in the MPD. Format is a duration string as specified in ISO 8601
37621
         *
37622
         * @param {string} value
37623
         *        value of attribute as a string
37624
         * @return {number}
37625
         *         The duration in seconds
37626
         */
37627
        timeShiftBufferDepth(value) {
37628
            return parseDuration(value);
37629
        },
37630
        /**
37631
         * Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
37632
         * Format is a duration string as specified in ISO 8601
37633
         *
37634
         * @param {string} value
37635
         *        value of attribute as a string
37636
         * @return {number}
37637
         *         The duration in seconds
37638
         */
37639
        start(value) {
37640
            return parseDuration(value);
37641
        },
37642
        /**
37643
         * Specifies the width of the visual presentation
37644
         *
37645
         * @param {string} value
37646
         *        value of attribute as a string
37647
         * @return {number}
37648
         *         The parsed width
37649
         */
37650
        width(value) {
37651
            return parseInt(value, 10);
37652
        },
37653
        /**
37654
         * Specifies the height of the visual presentation
37655
         *
37656
         * @param {string} value
37657
         *        value of attribute as a string
37658
         * @return {number}
37659
         *         The parsed height
37660
         */
37661
        height(value) {
37662
            return parseInt(value, 10);
37663
        },
37664
        /**
37665
         * Specifies the bitrate of the representation
37666
         *
37667
         * @param {string} value
37668
         *        value of attribute as a string
37669
         * @return {number}
37670
         *         The parsed bandwidth
37671
         */
37672
        bandwidth(value) {
37673
            return parseInt(value, 10);
37674
        },
37675
        /**
37676
         * Specifies the frame rate of the representation
37677
         *
37678
         * @param {string} value
37679
         *        value of attribute as a string
37680
         * @return {number}
37681
         *         The parsed frame rate
37682
         */
37683
        frameRate(value) {
37684
            return parseDivisionValue(value);
37685
        },
37686
        /**
37687
         * Specifies the number of the first Media Segment in this Representation in the Period
37688
         *
37689
         * @param {string} value
37690
         *        value of attribute as a string
37691
         * @return {number}
37692
         *         The parsed number
37693
         */
37694
        startNumber(value) {
37695
            return parseInt(value, 10);
37696
        },
37697
        /**
37698
         * Specifies the timescale in units per seconds
37699
         *
37700
         * @param {string} value
37701
         *        value of attribute as a string
37702
         * @return {number}
37703
         *         The parsed timescale
37704
         */
37705
        timescale(value) {
37706
            return parseInt(value, 10);
37707
        },
37708
        /**
37709
         * Specifies the presentationTimeOffset.
37710
         *
37711
         * @param {string} value
37712
         *        value of the attribute as a string
37713
         *
37714
         * @return {number}
37715
         *         The parsed presentationTimeOffset
37716
         */
37717
        presentationTimeOffset(value) {
37718
            return parseInt(value, 10);
37719
        },
37720
        /**
37721
         * Specifies the constant approximate Segment duration
37722
         * NOTE: The <Period> element also contains an @duration attribute. This duration
37723
         *       specifies the duration of the Period. This attribute is currently not
37724
         *       supported by the rest of the parser, however we still check for it to prevent
37725
         *       errors.
37726
         *
37727
         * @param {string} value
37728
         *        value of attribute as a string
37729
         * @return {number}
37730
         *         The parsed duration
37731
         */
37732
        duration(value) {
37733
            const parsedValue = parseInt(value, 10);
37734
            if (isNaN(parsedValue)) {
37735
                return parseDuration(value);
37736
            }
37737
            return parsedValue;
37738
        },
37739
        /**
37740
         * Specifies the Segment duration, in units of the value of the @timescale.
37741
         *
37742
         * @param {string} value
37743
         *        value of attribute as a string
37744
         * @return {number}
37745
         *         The parsed duration
37746
         */
37747
        d(value) {
37748
            return parseInt(value, 10);
37749
        },
37750
        /**
37751
         * Specifies the MPD start time, in @timescale units, the first Segment in the series
37752
         * starts relative to the beginning of the Period
37753
         *
37754
         * @param {string} value
37755
         *        value of attribute as a string
37756
         * @return {number}
37757
         *         The parsed time
37758
         */
37759
        t(value) {
37760
            return parseInt(value, 10);
37761
        },
37762
        /**
37763
         * Specifies the repeat count of the number of following contiguous Segments with the
37764
         * same duration expressed by the value of @d
37765
         *
37766
         * @param {string} value
37767
         *        value of attribute as a string
37768
         * @return {number}
37769
         *         The parsed number
37770
         */
37771
        r(value) {
37772
            return parseInt(value, 10);
37773
        },
37774
        /**
37775
         * Specifies the presentationTime.
37776
         *
37777
         * @param {string} value
37778
         *        value of the attribute as a string
37779
         *
37780
         * @return {number}
37781
         *         The parsed presentationTime
37782
         */
37783
        presentationTime(value) {
37784
            return parseInt(value, 10);
37785
        },
37786
        /**
37787
         * Default parser for all other attributes. Acts as a no-op and just returns the value
37788
         * as a string
37789
         *
37790
         * @param {string} value
37791
         *        value of attribute as a string
37792
         * @return {string}
37793
         *         Unparsed value
37794
         */
37795
        DEFAULT(value) {
37796
            return value;
37797
        }
37798
    };
37799
    /**
37800
     * Gets all the attributes and values of the provided node, parses attributes with known
37801
     * types, and returns an object with attribute names mapped to values.
37802
     *
37803
     * @param {Node} el
37804
     *        The node to parse attributes from
37805
     * @return {Object}
37806
     *         Object with all attributes of el parsed
37807
     */
37808
 
37809
    const parseAttributes = el => {
37810
        if (!(el && el.attributes)) {
37811
            return {};
37812
        }
37813
        return from(el.attributes).reduce((a, e) => {
37814
            const parseFn = parsers[e.name] || parsers.DEFAULT;
37815
            a[e.name] = parseFn(e.value);
37816
            return a;
37817
        }, {});
37818
    };
37819
    const keySystemsMap = {
37820
        'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
37821
        'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
37822
        'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
37823
        'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime',
37824
        // ISO_IEC 23009-1_2022 5.8.5.2.2 The mp4 Protection Scheme
37825
        'urn:mpeg:dash:mp4protection:2011': 'mp4protection'
37826
    };
37827
    /**
37828
     * Builds a list of urls that is the product of the reference urls and BaseURL values
37829
     *
37830
     * @param {Object[]} references
37831
     *        List of objects containing the reference URL as well as its attributes
37832
     * @param {Node[]} baseUrlElements
37833
     *        List of BaseURL nodes from the mpd
37834
     * @return {Object[]}
37835
     *         List of objects with resolved urls and attributes
37836
     */
37837
 
37838
    const buildBaseUrls = (references, baseUrlElements) => {
37839
        if (!baseUrlElements.length) {
37840
            return references;
37841
        }
37842
        return flatten(references.map(function (reference) {
37843
            return baseUrlElements.map(function (baseUrlElement) {
37844
                const initialBaseUrl = getContent(baseUrlElement);
37845
                const resolvedBaseUrl = resolveUrl$1(reference.baseUrl, initialBaseUrl);
37846
                const finalBaseUrl = merge$1(parseAttributes(baseUrlElement), {
37847
                    baseUrl: resolvedBaseUrl
37848
                }); // If the URL is resolved, we want to get the serviceLocation from the reference
37849
                // assuming there is no serviceLocation on the initialBaseUrl
37850
 
37851
                if (resolvedBaseUrl !== initialBaseUrl && !finalBaseUrl.serviceLocation && reference.serviceLocation) {
37852
                    finalBaseUrl.serviceLocation = reference.serviceLocation;
37853
                }
37854
                return finalBaseUrl;
37855
            });
37856
        }));
37857
    };
37858
    /**
37859
     * Contains all Segment information for its containing AdaptationSet
37860
     *
37861
     * @typedef {Object} SegmentInformation
37862
     * @property {Object|undefined} template
37863
     *           Contains the attributes for the SegmentTemplate node
37864
     * @property {Object[]|undefined} segmentTimeline
37865
     *           Contains a list of atrributes for each S node within the SegmentTimeline node
37866
     * @property {Object|undefined} list
37867
     *           Contains the attributes for the SegmentList node
37868
     * @property {Object|undefined} base
37869
     *           Contains the attributes for the SegmentBase node
37870
     */
37871
 
37872
    /**
37873
     * Returns all available Segment information contained within the AdaptationSet node
37874
     *
37875
     * @param {Node} adaptationSet
37876
     *        The AdaptationSet node to get Segment information from
37877
     * @return {SegmentInformation}
37878
     *         The Segment information contained within the provided AdaptationSet
37879
     */
37880
 
37881
    const getSegmentInformation = adaptationSet => {
37882
        const segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
37883
        const segmentList = findChildren(adaptationSet, 'SegmentList')[0];
37884
        const segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(s => merge$1({
37885
            tag: 'SegmentURL'
37886
        }, parseAttributes(s)));
37887
        const segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];
37888
        const segmentTimelineParentNode = segmentList || segmentTemplate;
37889
        const segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0];
37890
        const segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate;
37891
        const segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both
37892
        // @initialization and an <Initialization> node.  @initialization can be templated,
37893
        // while the node can have a url and range specified.  If the <SegmentTemplate> has
37894
        // both @initialization and an <Initialization> subelement we opt to override with
37895
        // the node, as this interaction is not defined in the spec.
37896
 
37897
        const template = segmentTemplate && parseAttributes(segmentTemplate);
37898
        if (template && segmentInitialization) {
37899
            template.initialization = segmentInitialization && parseAttributes(segmentInitialization);
37900
        } else if (template && template.initialization) {
37901
            // If it is @initialization we convert it to an object since this is the format that
37902
            // later functions will rely on for the initialization segment.  This is only valid
37903
            // for <SegmentTemplate>
37904
            template.initialization = {
37905
                sourceURL: template.initialization
37906
            };
37907
        }
37908
        const segmentInfo = {
37909
            template,
37910
            segmentTimeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(s => parseAttributes(s)),
37911
            list: segmentList && merge$1(parseAttributes(segmentList), {
37912
                segmentUrls,
37913
                initialization: parseAttributes(segmentInitialization)
37914
            }),
37915
            base: segmentBase && merge$1(parseAttributes(segmentBase), {
37916
                initialization: parseAttributes(segmentInitialization)
37917
            })
37918
        };
37919
        Object.keys(segmentInfo).forEach(key => {
37920
            if (!segmentInfo[key]) {
37921
                delete segmentInfo[key];
37922
            }
37923
        });
37924
        return segmentInfo;
37925
    };
37926
    /**
37927
     * Contains Segment information and attributes needed to construct a Playlist object
37928
     * from a Representation
37929
     *
37930
     * @typedef {Object} RepresentationInformation
37931
     * @property {SegmentInformation} segmentInfo
37932
     *           Segment information for this Representation
37933
     * @property {Object} attributes
37934
     *           Inherited attributes for this Representation
37935
     */
37936
 
37937
    /**
37938
     * Maps a Representation node to an object containing Segment information and attributes
37939
     *
37940
     * @name inheritBaseUrlsCallback
37941
     * @function
37942
     * @param {Node} representation
37943
     *        Representation node from the mpd
37944
     * @return {RepresentationInformation}
37945
     *         Representation information needed to construct a Playlist object
37946
     */
37947
 
37948
    /**
37949
     * Returns a callback for Array.prototype.map for mapping Representation nodes to
37950
     * Segment information and attributes using inherited BaseURL nodes.
37951
     *
37952
     * @param {Object} adaptationSetAttributes
37953
     *        Contains attributes inherited by the AdaptationSet
37954
     * @param {Object[]} adaptationSetBaseUrls
37955
     *        List of objects containing resolved base URLs and attributes
37956
     *        inherited by the AdaptationSet
37957
     * @param {SegmentInformation} adaptationSetSegmentInfo
37958
     *        Contains Segment information for the AdaptationSet
37959
     * @return {inheritBaseUrlsCallback}
37960
     *         Callback map function
37961
     */
37962
 
37963
    const inheritBaseUrls = (adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) => representation => {
37964
        const repBaseUrlElements = findChildren(representation, 'BaseURL');
37965
        const repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
37966
        const attributes = merge$1(adaptationSetAttributes, parseAttributes(representation));
37967
        const representationSegmentInfo = getSegmentInformation(representation);
37968
        return repBaseUrls.map(baseUrl => {
37969
            return {
37970
                segmentInfo: merge$1(adaptationSetSegmentInfo, representationSegmentInfo),
37971
                attributes: merge$1(attributes, baseUrl)
37972
            };
37973
        });
37974
    };
37975
    /**
37976
     * Tranforms a series of content protection nodes to
37977
     * an object containing pssh data by key system
37978
     *
37979
     * @param {Node[]} contentProtectionNodes
37980
     *        Content protection nodes
37981
     * @return {Object}
37982
     *        Object containing pssh data by key system
37983
     */
37984
 
37985
    const generateKeySystemInformation = contentProtectionNodes => {
37986
        return contentProtectionNodes.reduce((acc, node) => {
37987
            const attributes = parseAttributes(node); // Although it could be argued that according to the UUID RFC spec the UUID string (a-f chars) should be generated
37988
            // as a lowercase string it also mentions it should be treated as case-insensitive on input. Since the key system
37989
            // UUIDs in the keySystemsMap are hardcoded as lowercase in the codebase there isn't any reason not to do
37990
            // .toLowerCase() on the input UUID string from the manifest (at least I could not think of one).
37991
 
37992
            if (attributes.schemeIdUri) {
37993
                attributes.schemeIdUri = attributes.schemeIdUri.toLowerCase();
37994
            }
37995
            const keySystem = keySystemsMap[attributes.schemeIdUri];
37996
            if (keySystem) {
37997
                acc[keySystem] = {
37998
                    attributes
37999
                };
38000
                const psshNode = findChildren(node, 'cenc:pssh')[0];
38001
                if (psshNode) {
38002
                    const pssh = getContent(psshNode);
38003
                    acc[keySystem].pssh = pssh && decodeB64ToUint8Array(pssh);
38004
                }
38005
            }
38006
            return acc;
38007
        }, {});
38008
    }; // defined in ANSI_SCTE 214-1 2016
38009
 
38010
    const parseCaptionServiceMetadata = service => {
38011
        // 608 captions
38012
        if (service.schemeIdUri === 'urn:scte:dash:cc:cea-608:2015') {
38013
            const values = typeof service.value !== 'string' ? [] : service.value.split(';');
38014
            return values.map(value => {
38015
                let channel;
38016
                let language; // default language to value
38017
 
38018
                language = value;
38019
                if (/^CC\d=/.test(value)) {
38020
                    [channel, language] = value.split('=');
38021
                } else if (/^CC\d$/.test(value)) {
38022
                    channel = value;
38023
                }
38024
                return {
38025
                    channel,
38026
                    language
38027
                };
38028
            });
38029
        } else if (service.schemeIdUri === 'urn:scte:dash:cc:cea-708:2015') {
38030
            const values = typeof service.value !== 'string' ? [] : service.value.split(';');
38031
            return values.map(value => {
38032
                const flags = {
38033
                    // service or channel number 1-63
38034
                    'channel': undefined,
38035
                    // language is a 3ALPHA per ISO 639.2/B
38036
                    // field is required
38037
                    'language': undefined,
38038
                    // BIT 1/0 or ?
38039
                    // default value is 1, meaning 16:9 aspect ratio, 0 is 4:3, ? is unknown
38040
                    'aspectRatio': 1,
38041
                    // BIT 1/0
38042
                    // easy reader flag indicated the text is tailed to the needs of beginning readers
38043
                    // default 0, or off
38044
                    'easyReader': 0,
38045
                    // BIT 1/0
38046
                    // If 3d metadata is present (CEA-708.1) then 1
38047
                    // default 0
38048
                    '3D': 0
38049
                };
38050
                if (/=/.test(value)) {
38051
                    const [channel, opts = ''] = value.split('=');
38052
                    flags.channel = channel;
38053
                    flags.language = value;
38054
                    opts.split(',').forEach(opt => {
38055
                        const [name, val] = opt.split(':');
38056
                        if (name === 'lang') {
38057
                            flags.language = val; // er for easyReadery
38058
                        } else if (name === 'er') {
38059
                            flags.easyReader = Number(val); // war for wide aspect ratio
38060
                        } else if (name === 'war') {
38061
                            flags.aspectRatio = Number(val);
38062
                        } else if (name === '3D') {
38063
                            flags['3D'] = Number(val);
38064
                        }
38065
                    });
38066
                } else {
38067
                    flags.language = value;
38068
                }
38069
                if (flags.channel) {
38070
                    flags.channel = 'SERVICE' + flags.channel;
38071
                }
38072
                return flags;
38073
            });
38074
        }
38075
    };
38076
    /**
38077
     * A map callback that will parse all event stream data for a collection of periods
38078
     * DASH ISO_IEC_23009 5.10.2.2
38079
     * https://dashif-documents.azurewebsites.net/Events/master/event.html#mpd-event-timing
38080
     *
38081
     * @param {PeriodInformation} period object containing necessary period information
38082
     * @return a collection of parsed eventstream event objects
38083
     */
38084
 
38085
    const toEventStream = period => {
38086
        // get and flatten all EventStreams tags and parse attributes and children
38087
        return flatten(findChildren(period.node, 'EventStream').map(eventStream => {
38088
            const eventStreamAttributes = parseAttributes(eventStream);
38089
            const schemeIdUri = eventStreamAttributes.schemeIdUri; // find all Events per EventStream tag and map to return objects
38090
 
38091
            return findChildren(eventStream, 'Event').map(event => {
38092
                const eventAttributes = parseAttributes(event);
38093
                const presentationTime = eventAttributes.presentationTime || 0;
38094
                const timescale = eventStreamAttributes.timescale || 1;
38095
                const duration = eventAttributes.duration || 0;
38096
                const start = presentationTime / timescale + period.attributes.start;
38097
                return {
38098
                    schemeIdUri,
38099
                    value: eventStreamAttributes.value,
38100
                    id: eventAttributes.id,
38101
                    start,
38102
                    end: start + duration / timescale,
38103
                    messageData: getContent(event) || eventAttributes.messageData,
38104
                    contentEncoding: eventStreamAttributes.contentEncoding,
38105
                    presentationTimeOffset: eventStreamAttributes.presentationTimeOffset || 0
38106
                };
38107
            });
38108
        }));
38109
    };
38110
    /**
38111
     * Maps an AdaptationSet node to a list of Representation information objects
38112
     *
38113
     * @name toRepresentationsCallback
38114
     * @function
38115
     * @param {Node} adaptationSet
38116
     *        AdaptationSet node from the mpd
38117
     * @return {RepresentationInformation[]}
38118
     *         List of objects containing Representaion information
38119
     */
38120
 
38121
    /**
38122
     * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of
38123
     * Representation information objects
38124
     *
38125
     * @param {Object} periodAttributes
38126
     *        Contains attributes inherited by the Period
38127
     * @param {Object[]} periodBaseUrls
38128
     *        Contains list of objects with resolved base urls and attributes
38129
     *        inherited by the Period
38130
     * @param {string[]} periodSegmentInfo
38131
     *        Contains Segment Information at the period level
38132
     * @return {toRepresentationsCallback}
38133
     *         Callback map function
38134
     */
38135
 
38136
    const toRepresentations = (periodAttributes, periodBaseUrls, periodSegmentInfo) => adaptationSet => {
38137
        const adaptationSetAttributes = parseAttributes(adaptationSet);
38138
        const adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL'));
38139
        const role = findChildren(adaptationSet, 'Role')[0];
38140
        const roleAttributes = {
38141
            role: parseAttributes(role)
38142
        };
38143
        let attrs = merge$1(periodAttributes, adaptationSetAttributes, roleAttributes);
38144
        const accessibility = findChildren(adaptationSet, 'Accessibility')[0];
38145
        const captionServices = parseCaptionServiceMetadata(parseAttributes(accessibility));
38146
        if (captionServices) {
38147
            attrs = merge$1(attrs, {
38148
                captionServices
38149
            });
38150
        }
38151
        const label = findChildren(adaptationSet, 'Label')[0];
38152
        if (label && label.childNodes.length) {
38153
            const labelVal = label.childNodes[0].nodeValue.trim();
38154
            attrs = merge$1(attrs, {
38155
                label: labelVal
38156
            });
38157
        }
38158
        const contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection'));
38159
        if (Object.keys(contentProtection).length) {
38160
            attrs = merge$1(attrs, {
38161
                contentProtection
38162
            });
38163
        }
38164
        const segmentInfo = getSegmentInformation(adaptationSet);
38165
        const representations = findChildren(adaptationSet, 'Representation');
38166
        const adaptationSetSegmentInfo = merge$1(periodSegmentInfo, segmentInfo);
38167
        return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo)));
38168
    };
38169
    /**
38170
     * Contains all period information for mapping nodes onto adaptation sets.
38171
     *
38172
     * @typedef {Object} PeriodInformation
38173
     * @property {Node} period.node
38174
     *           Period node from the mpd
38175
     * @property {Object} period.attributes
38176
     *           Parsed period attributes from node plus any added
38177
     */
38178
 
38179
    /**
38180
     * Maps a PeriodInformation object to a list of Representation information objects for all
38181
     * AdaptationSet nodes contained within the Period.
38182
     *
38183
     * @name toAdaptationSetsCallback
38184
     * @function
38185
     * @param {PeriodInformation} period
38186
     *        Period object containing necessary period information
38187
     * @param {number} periodStart
38188
     *        Start time of the Period within the mpd
38189
     * @return {RepresentationInformation[]}
38190
     *         List of objects containing Representaion information
38191
     */
38192
 
38193
    /**
38194
     * Returns a callback for Array.prototype.map for mapping Period nodes to a list of
38195
     * Representation information objects
38196
     *
38197
     * @param {Object} mpdAttributes
38198
     *        Contains attributes inherited by the mpd
38199
     * @param {Object[]} mpdBaseUrls
38200
     *        Contains list of objects with resolved base urls and attributes
38201
     *        inherited by the mpd
38202
     * @return {toAdaptationSetsCallback}
38203
     *         Callback map function
38204
     */
38205
 
38206
    const toAdaptationSets = (mpdAttributes, mpdBaseUrls) => (period, index) => {
38207
        const periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period.node, 'BaseURL'));
38208
        const periodAttributes = merge$1(mpdAttributes, {
38209
            periodStart: period.attributes.start
38210
        });
38211
        if (typeof period.attributes.duration === 'number') {
38212
            periodAttributes.periodDuration = period.attributes.duration;
38213
        }
38214
        const adaptationSets = findChildren(period.node, 'AdaptationSet');
38215
        const periodSegmentInfo = getSegmentInformation(period.node);
38216
        return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo)));
38217
    };
38218
    /**
38219
     * Tranforms an array of content steering nodes into an object
38220
     * containing CDN content steering information from the MPD manifest.
38221
     *
38222
     * For more information on the DASH spec for Content Steering parsing, see:
38223
     * https://dashif.org/docs/DASH-IF-CTS-00XX-Content-Steering-Community-Review.pdf
38224
     *
38225
     * @param {Node[]} contentSteeringNodes
38226
     *        Content steering nodes
38227
     * @param {Function} eventHandler
38228
     *        The event handler passed into the parser options to handle warnings
38229
     * @return {Object}
38230
     *        Object containing content steering data
38231
     */
38232
 
38233
    const generateContentSteeringInformation = (contentSteeringNodes, eventHandler) => {
38234
        // If there are more than one ContentSteering tags, throw an error
38235
        if (contentSteeringNodes.length > 1) {
38236
            eventHandler({
38237
                type: 'warn',
38238
                message: 'The MPD manifest should contain no more than one ContentSteering tag'
38239
            });
38240
        } // Return a null value if there are no ContentSteering tags
38241
 
38242
        if (!contentSteeringNodes.length) {
38243
            return null;
38244
        }
38245
        const infoFromContentSteeringTag = merge$1({
38246
            serverURL: getContent(contentSteeringNodes[0])
38247
        }, parseAttributes(contentSteeringNodes[0])); // Converts `queryBeforeStart` to a boolean, as well as setting the default value
38248
        // to `false` if it doesn't exist
38249
 
38250
        infoFromContentSteeringTag.queryBeforeStart = infoFromContentSteeringTag.queryBeforeStart === 'true';
38251
        return infoFromContentSteeringTag;
38252
    };
38253
    /**
38254
     * Gets Period@start property for a given period.
38255
     *
38256
     * @param {Object} options
38257
     *        Options object
38258
     * @param {Object} options.attributes
38259
     *        Period attributes
38260
     * @param {Object} [options.priorPeriodAttributes]
38261
     *        Prior period attributes (if prior period is available)
38262
     * @param {string} options.mpdType
38263
     *        The MPD@type these periods came from
38264
     * @return {number|null}
38265
     *         The period start, or null if it's an early available period or error
38266
     */
38267
 
38268
    const getPeriodStart = ({
38269
                                attributes,
38270
                                priorPeriodAttributes,
38271
                                mpdType
38272
                            }) => {
38273
        // Summary of period start time calculation from DASH spec section 5.3.2.1
38274
        //
38275
        // A period's start is the first period's start + time elapsed after playing all
38276
        // prior periods to this one. Periods continue one after the other in time (without
38277
        // gaps) until the end of the presentation.
38278
        //
38279
        // The value of Period@start should be:
38280
        // 1. if Period@start is present: value of Period@start
38281
        // 2. if previous period exists and it has @duration: previous Period@start +
38282
        //    previous Period@duration
38283
        // 3. if this is first period and MPD@type is 'static': 0
38284
        // 4. in all other cases, consider the period an "early available period" (note: not
38285
        //    currently supported)
38286
        // (1)
38287
        if (typeof attributes.start === 'number') {
38288
            return attributes.start;
38289
        } // (2)
38290
 
38291
        if (priorPeriodAttributes && typeof priorPeriodAttributes.start === 'number' && typeof priorPeriodAttributes.duration === 'number') {
38292
            return priorPeriodAttributes.start + priorPeriodAttributes.duration;
38293
        } // (3)
38294
 
38295
        if (!priorPeriodAttributes && mpdType === 'static') {
38296
            return 0;
38297
        } // (4)
38298
        // There is currently no logic for calculating the Period@start value if there is
38299
        // no Period@start or prior Period@start and Period@duration available. This is not made
38300
        // explicit by the DASH interop guidelines or the DASH spec, however, since there's
38301
        // nothing about any other resolution strategies, it's implied. Thus, this case should
38302
        // be considered an early available period, or error, and null should suffice for both
38303
        // of those cases.
38304
 
38305
        return null;
38306
    };
38307
    /**
38308
     * Traverses the mpd xml tree to generate a list of Representation information objects
38309
     * that have inherited attributes from parent nodes
38310
     *
38311
     * @param {Node} mpd
38312
     *        The root node of the mpd
38313
     * @param {Object} options
38314
     *        Available options for inheritAttributes
38315
     * @param {string} options.manifestUri
38316
     *        The uri source of the mpd
38317
     * @param {number} options.NOW
38318
     *        Current time per DASH IOP.  Default is current time in ms since epoch
38319
     * @param {number} options.clientOffset
38320
     *        Client time difference from NOW (in milliseconds)
38321
     * @return {RepresentationInformation[]}
38322
     *         List of objects containing Representation information
38323
     */
38324
 
38325
    const inheritAttributes = (mpd, options = {}) => {
38326
        const {
38327
            manifestUri = '',
38328
            NOW = Date.now(),
38329
            clientOffset = 0,
38330
            // TODO: For now, we are expecting an eventHandler callback function
38331
            // to be passed into the mpd parser as an option.
38332
            // In the future, we should enable stream parsing by using the Stream class from vhs-utils.
38333
            // This will support new features including a standardized event handler.
38334
            // See the m3u8 parser for examples of how stream parsing is currently used for HLS parsing.
38335
            // https://github.com/videojs/vhs-utils/blob/88d6e10c631e57a5af02c5a62bc7376cd456b4f5/src/stream.js#L9
38336
            eventHandler = function () {}
38337
        } = options;
38338
        const periodNodes = findChildren(mpd, 'Period');
38339
        if (!periodNodes.length) {
38340
            throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
38341
        }
38342
        const locations = findChildren(mpd, 'Location');
38343
        const mpdAttributes = parseAttributes(mpd);
38344
        const mpdBaseUrls = buildBaseUrls([{
38345
            baseUrl: manifestUri
38346
        }], findChildren(mpd, 'BaseURL'));
38347
        const contentSteeringNodes = findChildren(mpd, 'ContentSteering'); // See DASH spec section 5.3.1.2, Semantics of MPD element. Default type to 'static'.
38348
 
38349
        mpdAttributes.type = mpdAttributes.type || 'static';
38350
        mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
38351
        mpdAttributes.NOW = NOW;
38352
        mpdAttributes.clientOffset = clientOffset;
38353
        if (locations.length) {
38354
            mpdAttributes.locations = locations.map(getContent);
38355
        }
38356
        const periods = []; // Since toAdaptationSets acts on individual periods right now, the simplest approach to
38357
        // adding properties that require looking at prior periods is to parse attributes and add
38358
        // missing ones before toAdaptationSets is called. If more such properties are added, it
38359
        // may be better to refactor toAdaptationSets.
38360
 
38361
        periodNodes.forEach((node, index) => {
38362
            const attributes = parseAttributes(node); // Use the last modified prior period, as it may contain added information necessary
38363
            // for this period.
38364
 
38365
            const priorPeriod = periods[index - 1];
38366
            attributes.start = getPeriodStart({
38367
                attributes,
38368
                priorPeriodAttributes: priorPeriod ? priorPeriod.attributes : null,
38369
                mpdType: mpdAttributes.type
38370
            });
38371
            periods.push({
38372
                node,
38373
                attributes
38374
            });
38375
        });
38376
        return {
38377
            locations: mpdAttributes.locations,
38378
            contentSteeringInfo: generateContentSteeringInformation(contentSteeringNodes, eventHandler),
38379
            // TODO: There are occurences where this `representationInfo` array contains undesired
38380
            // duplicates. This generally occurs when there are multiple BaseURL nodes that are
38381
            // direct children of the MPD node. When we attempt to resolve URLs from a combination of the
38382
            // parent BaseURL and a child BaseURL, and the value does not resolve,
38383
            // we end up returning the child BaseURL multiple times.
38384
            // We need to determine a way to remove these duplicates in a safe way.
38385
            // See: https://github.com/videojs/mpd-parser/pull/17#discussion_r162750527
38386
            representationInfo: flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls))),
38387
            eventStream: flatten(periods.map(toEventStream))
38388
        };
38389
    };
38390
    const stringToMpdXml = manifestString => {
38391
        if (manifestString === '') {
38392
            throw new Error(errors.DASH_EMPTY_MANIFEST);
38393
        }
38394
        const parser = new DOMParser();
38395
        let xml;
38396
        let mpd;
38397
        try {
38398
            xml = parser.parseFromString(manifestString, 'application/xml');
38399
            mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null;
38400
        } catch (e) {// ie 11 throws on invalid xml
38401
        }
38402
        if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) {
38403
            throw new Error(errors.DASH_INVALID_XML);
38404
        }
38405
        return mpd;
38406
    };
38407
 
38408
    /**
38409
     * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
38410
     *
38411
     * @param {string} mpd
38412
     *        XML string of the MPD manifest
38413
     * @return {Object|null}
38414
     *         Attributes of UTCTiming node specified in the manifest. Null if none found
38415
     */
38416
 
38417
    const parseUTCTimingScheme = mpd => {
38418
        const UTCTimingNode = findChildren(mpd, 'UTCTiming')[0];
38419
        if (!UTCTimingNode) {
38420
            return null;
38421
        }
38422
        const attributes = parseAttributes(UTCTimingNode);
38423
        switch (attributes.schemeIdUri) {
38424
            case 'urn:mpeg:dash:utc:http-head:2014':
38425
            case 'urn:mpeg:dash:utc:http-head:2012':
38426
                attributes.method = 'HEAD';
38427
                break;
38428
            case 'urn:mpeg:dash:utc:http-xsdate:2014':
38429
            case 'urn:mpeg:dash:utc:http-iso:2014':
38430
            case 'urn:mpeg:dash:utc:http-xsdate:2012':
38431
            case 'urn:mpeg:dash:utc:http-iso:2012':
38432
                attributes.method = 'GET';
38433
                break;
38434
            case 'urn:mpeg:dash:utc:direct:2014':
38435
            case 'urn:mpeg:dash:utc:direct:2012':
38436
                attributes.method = 'DIRECT';
38437
                attributes.value = Date.parse(attributes.value);
38438
                break;
38439
            case 'urn:mpeg:dash:utc:http-ntp:2014':
38440
            case 'urn:mpeg:dash:utc:ntp:2014':
38441
            case 'urn:mpeg:dash:utc:sntp:2014':
38442
            default:
38443
                throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME);
38444
        }
38445
        return attributes;
38446
    };
38447
    /*
38448
   * Given a DASH manifest string and options, parses the DASH manifest into an object in the
38449
   * form outputed by m3u8-parser and accepted by videojs/http-streaming.
38450
   *
38451
   * For live DASH manifests, if `previousManifest` is provided in options, then the newly
38452
   * parsed DASH manifest will have its media sequence and discontinuity sequence values
38453
   * updated to reflect its position relative to the prior manifest.
38454
   *
38455
   * @param {string} manifestString - the DASH manifest as a string
38456
   * @param {options} [options] - any options
38457
   *
38458
   * @return {Object} the manifest object
38459
   */
38460
 
38461
    const parse = (manifestString, options = {}) => {
38462
        const parsedManifestInfo = inheritAttributes(stringToMpdXml(manifestString), options);
38463
        const playlists = toPlaylists(parsedManifestInfo.representationInfo);
38464
        return toM3u8({
38465
            dashPlaylists: playlists,
38466
            locations: parsedManifestInfo.locations,
38467
            contentSteering: parsedManifestInfo.contentSteeringInfo,
38468
            sidxMapping: options.sidxMapping,
38469
            previousManifest: options.previousManifest,
38470
            eventStream: parsedManifestInfo.eventStream
38471
        });
38472
    };
38473
    /**
38474
     * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
38475
     *
38476
     * @param {string} manifestString
38477
     *        XML string of the MPD manifest
38478
     * @return {Object|null}
38479
     *         Attributes of UTCTiming node specified in the manifest. Null if none found
38480
     */
38481
 
38482
    const parseUTCTiming = manifestString => parseUTCTimingScheme(stringToMpdXml(manifestString));
38483
 
38484
    var MAX_UINT32 = Math.pow(2, 32);
38485
    var getUint64$1 = function (uint8) {
38486
        var dv = new DataView(uint8.buffer, uint8.byteOffset, uint8.byteLength);
38487
        var value;
38488
        if (dv.getBigUint64) {
38489
            value = dv.getBigUint64(0);
38490
            if (value < Number.MAX_SAFE_INTEGER) {
38491
                return Number(value);
38492
            }
38493
            return value;
38494
        }
38495
        return dv.getUint32(0) * MAX_UINT32 + dv.getUint32(4);
38496
    };
38497
    var numbers = {
38498
        getUint64: getUint64$1,
38499
        MAX_UINT32: MAX_UINT32
38500
    };
38501
 
38502
    var getUint64 = numbers.getUint64;
38503
    var parseSidx = function (data) {
38504
        var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
38505
            result = {
38506
                version: data[0],
38507
                flags: new Uint8Array(data.subarray(1, 4)),
38508
                references: [],
38509
                referenceId: view.getUint32(4),
38510
                timescale: view.getUint32(8)
38511
            },
38512
            i = 12;
38513
        if (result.version === 0) {
38514
            result.earliestPresentationTime = view.getUint32(i);
38515
            result.firstOffset = view.getUint32(i + 4);
38516
            i += 8;
38517
        } else {
38518
            // read 64 bits
38519
            result.earliestPresentationTime = getUint64(data.subarray(i));
38520
            result.firstOffset = getUint64(data.subarray(i + 8));
38521
            i += 16;
38522
        }
38523
        i += 2; // reserved
38524
 
38525
        var referenceCount = view.getUint16(i);
38526
        i += 2; // start of references
38527
 
38528
        for (; referenceCount > 0; i += 12, referenceCount--) {
38529
            result.references.push({
38530
                referenceType: (data[i] & 0x80) >>> 7,
38531
                referencedSize: view.getUint32(i) & 0x7FFFFFFF,
38532
                subsegmentDuration: view.getUint32(i + 4),
38533
                startsWithSap: !!(data[i + 8] & 0x80),
38534
                sapType: (data[i + 8] & 0x70) >>> 4,
38535
                sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
38536
            });
38537
        }
38538
        return result;
38539
    };
38540
    var parseSidx_1 = parseSidx;
38541
 
38542
    var ID3 = toUint8([0x49, 0x44, 0x33]);
38543
    var getId3Size = function getId3Size(bytes, offset) {
38544
        if (offset === void 0) {
38545
            offset = 0;
38546
        }
38547
        bytes = toUint8(bytes);
38548
        var flags = bytes[offset + 5];
38549
        var returnSize = bytes[offset + 6] << 21 | bytes[offset + 7] << 14 | bytes[offset + 8] << 7 | bytes[offset + 9];
38550
        var footerPresent = (flags & 16) >> 4;
38551
        if (footerPresent) {
38552
            return returnSize + 20;
38553
        }
38554
        return returnSize + 10;
38555
    };
38556
    var getId3Offset = function getId3Offset(bytes, offset) {
38557
        if (offset === void 0) {
38558
            offset = 0;
38559
        }
38560
        bytes = toUint8(bytes);
38561
        if (bytes.length - offset < 10 || !bytesMatch(bytes, ID3, {
38562
            offset: offset
38563
        })) {
38564
            return offset;
38565
        }
38566
        offset += getId3Size(bytes, offset); // recursive check for id3 tags as some files
38567
        // have multiple ID3 tag sections even though
38568
        // they should not.
38569
 
38570
        return getId3Offset(bytes, offset);
38571
    };
38572
 
38573
    var normalizePath$1 = function normalizePath(path) {
38574
        if (typeof path === 'string') {
38575
            return stringToBytes(path);
38576
        }
38577
        if (typeof path === 'number') {
38578
            return path;
38579
        }
38580
        return path;
38581
    };
38582
    var normalizePaths$1 = function normalizePaths(paths) {
38583
        if (!Array.isArray(paths)) {
38584
            return [normalizePath$1(paths)];
38585
        }
38586
        return paths.map(function (p) {
38587
            return normalizePath$1(p);
38588
        });
38589
    };
38590
    /**
38591
     * find any number of boxes by name given a path to it in an iso bmff
38592
     * such as mp4.
38593
     *
38594
     * @param {TypedArray} bytes
38595
     *        bytes for the iso bmff to search for boxes in
38596
     *
38597
     * @param {Uint8Array[]|string[]|string|Uint8Array} name
38598
     *        An array of paths or a single path representing the name
38599
     *        of boxes to search through in bytes. Paths may be
38600
     *        uint8 (character codes) or strings.
38601
     *
38602
     * @param {boolean} [complete=false]
38603
     *        Should we search only for complete boxes on the final path.
38604
     *        This is very useful when you do not want to get back partial boxes
38605
     *        in the case of streaming files.
38606
     *
38607
     * @return {Uint8Array[]}
38608
     *         An array of the end paths that we found.
38609
     */
38610
 
38611
    var findBox = function findBox(bytes, paths, complete) {
38612
        if (complete === void 0) {
38613
            complete = false;
38614
        }
38615
        paths = normalizePaths$1(paths);
38616
        bytes = toUint8(bytes);
38617
        var results = [];
38618
        if (!paths.length) {
38619
            // short-circuit the search for empty paths
38620
            return results;
38621
        }
38622
        var i = 0;
38623
        while (i < bytes.length) {
38624
            var size = (bytes[i] << 24 | bytes[i + 1] << 16 | bytes[i + 2] << 8 | bytes[i + 3]) >>> 0;
38625
            var type = bytes.subarray(i + 4, i + 8); // invalid box format.
38626
 
38627
            if (size === 0) {
38628
                break;
38629
            }
38630
            var end = i + size;
38631
            if (end > bytes.length) {
38632
                // this box is bigger than the number of bytes we have
38633
                // and complete is set, we cannot find any more boxes.
38634
                if (complete) {
38635
                    break;
38636
                }
38637
                end = bytes.length;
38638
            }
38639
            var data = bytes.subarray(i + 8, end);
38640
            if (bytesMatch(type, paths[0])) {
38641
                if (paths.length === 1) {
38642
                    // this is the end of the path and we've found the box we were
38643
                    // looking for
38644
                    results.push(data);
38645
                } else {
38646
                    // recursively search for the next box along the path
38647
                    results.push.apply(results, findBox(data, paths.slice(1), complete));
38648
                }
38649
            }
38650
            i = end;
38651
        } // we've finished searching all of bytes
38652
 
38653
        return results;
38654
    };
38655
 
38656
    // https://matroska-org.github.io/libebml/specs.html
38657
    // https://www.matroska.org/technical/elements.html
38658
    // https://www.webmproject.org/docs/container/
38659
 
38660
    var EBML_TAGS = {
38661
        EBML: toUint8([0x1A, 0x45, 0xDF, 0xA3]),
38662
        DocType: toUint8([0x42, 0x82]),
38663
        Segment: toUint8([0x18, 0x53, 0x80, 0x67]),
38664
        SegmentInfo: toUint8([0x15, 0x49, 0xA9, 0x66]),
38665
        Tracks: toUint8([0x16, 0x54, 0xAE, 0x6B]),
38666
        Track: toUint8([0xAE]),
38667
        TrackNumber: toUint8([0xd7]),
38668
        DefaultDuration: toUint8([0x23, 0xe3, 0x83]),
38669
        TrackEntry: toUint8([0xAE]),
38670
        TrackType: toUint8([0x83]),
38671
        FlagDefault: toUint8([0x88]),
38672
        CodecID: toUint8([0x86]),
38673
        CodecPrivate: toUint8([0x63, 0xA2]),
38674
        VideoTrack: toUint8([0xe0]),
38675
        AudioTrack: toUint8([0xe1]),
38676
        // Not used yet, but will be used for live webm/mkv
38677
        // see https://www.matroska.org/technical/basics.html#block-structure
38678
        // see https://www.matroska.org/technical/basics.html#simpleblock-structure
38679
        Cluster: toUint8([0x1F, 0x43, 0xB6, 0x75]),
38680
        Timestamp: toUint8([0xE7]),
38681
        TimestampScale: toUint8([0x2A, 0xD7, 0xB1]),
38682
        BlockGroup: toUint8([0xA0]),
38683
        BlockDuration: toUint8([0x9B]),
38684
        Block: toUint8([0xA1]),
38685
        SimpleBlock: toUint8([0xA3])
38686
    };
38687
    /**
38688
     * This is a simple table to determine the length
38689
     * of things in ebml. The length is one based (starts at 1,
38690
     * rather than zero) and for every zero bit before a one bit
38691
     * we add one to length. We also need this table because in some
38692
     * case we have to xor all the length bits from another value.
38693
     */
38694
 
38695
    var LENGTH_TABLE = [128, 64, 32, 16, 8, 4, 2, 1];
38696
    var getLength = function getLength(byte) {
38697
        var len = 1;
38698
        for (var i = 0; i < LENGTH_TABLE.length; i++) {
38699
            if (byte & LENGTH_TABLE[i]) {
38700
                break;
38701
            }
38702
            len++;
38703
        }
38704
        return len;
38705
    }; // length in ebml is stored in the first 4 to 8 bits
38706
    // of the first byte. 4 for the id length and 8 for the
38707
    // data size length. Length is measured by converting the number to binary
38708
    // then 1 + the number of zeros before a 1 is encountered starting
38709
    // from the left.
38710
 
38711
    var getvint = function getvint(bytes, offset, removeLength, signed) {
38712
        if (removeLength === void 0) {
38713
            removeLength = true;
38714
        }
38715
        if (signed === void 0) {
38716
            signed = false;
38717
        }
38718
        var length = getLength(bytes[offset]);
38719
        var valueBytes = bytes.subarray(offset, offset + length); // NOTE that we do **not** subarray here because we need to copy these bytes
38720
        // as they will be modified below to remove the dataSizeLen bits and we do not
38721
        // want to modify the original data. normally we could just call slice on
38722
        // uint8array but ie 11 does not support that...
38723
 
38724
        if (removeLength) {
38725
            valueBytes = Array.prototype.slice.call(bytes, offset, offset + length);
38726
            valueBytes[0] ^= LENGTH_TABLE[length - 1];
38727
        }
38728
        return {
38729
            length: length,
38730
            value: bytesToNumber(valueBytes, {
38731
                signed: signed
38732
            }),
38733
            bytes: valueBytes
38734
        };
38735
    };
38736
    var normalizePath = function normalizePath(path) {
38737
        if (typeof path === 'string') {
38738
            return path.match(/.{1,2}/g).map(function (p) {
38739
                return normalizePath(p);
38740
            });
38741
        }
38742
        if (typeof path === 'number') {
38743
            return numberToBytes(path);
38744
        }
38745
        return path;
38746
    };
38747
    var normalizePaths = function normalizePaths(paths) {
38748
        if (!Array.isArray(paths)) {
38749
            return [normalizePath(paths)];
38750
        }
38751
        return paths.map(function (p) {
38752
            return normalizePath(p);
38753
        });
38754
    };
38755
    var getInfinityDataSize = function getInfinityDataSize(id, bytes, offset) {
38756
        if (offset >= bytes.length) {
38757
            return bytes.length;
38758
        }
38759
        var innerid = getvint(bytes, offset, false);
38760
        if (bytesMatch(id.bytes, innerid.bytes)) {
38761
            return offset;
38762
        }
38763
        var dataHeader = getvint(bytes, offset + innerid.length);
38764
        return getInfinityDataSize(id, bytes, offset + dataHeader.length + dataHeader.value + innerid.length);
38765
    };
38766
    /**
38767
     * Notes on the EBLM format.
38768
     *
38769
     * EBLM uses "vints" tags. Every vint tag contains
38770
     * two parts
38771
     *
38772
     * 1. The length from the first byte. You get this by
38773
     *    converting the byte to binary and counting the zeros
38774
     *    before a 1. Then you add 1 to that. Examples
38775
     *    00011111 = length 4 because there are 3 zeros before a 1.
38776
     *    00100000 = length 3 because there are 2 zeros before a 1.
38777
     *    00000011 = length 7 because there are 6 zeros before a 1.
38778
     *
38779
     * 2. The bits used for length are removed from the first byte
38780
     *    Then all the bytes are merged into a value. NOTE: this
38781
     *    is not the case for id ebml tags as there id includes
38782
     *    length bits.
38783
     *
38784
     */
38785
 
38786
    var findEbml = function findEbml(bytes, paths) {
38787
        paths = normalizePaths(paths);
38788
        bytes = toUint8(bytes);
38789
        var results = [];
38790
        if (!paths.length) {
38791
            return results;
38792
        }
38793
        var i = 0;
38794
        while (i < bytes.length) {
38795
            var id = getvint(bytes, i, false);
38796
            var dataHeader = getvint(bytes, i + id.length);
38797
            var dataStart = i + id.length + dataHeader.length; // dataSize is unknown or this is a live stream
38798
 
38799
            if (dataHeader.value === 0x7f) {
38800
                dataHeader.value = getInfinityDataSize(id, bytes, dataStart);
38801
                if (dataHeader.value !== bytes.length) {
38802
                    dataHeader.value -= dataStart;
38803
                }
38804
            }
38805
            var dataEnd = dataStart + dataHeader.value > bytes.length ? bytes.length : dataStart + dataHeader.value;
38806
            var data = bytes.subarray(dataStart, dataEnd);
38807
            if (bytesMatch(paths[0], id.bytes)) {
38808
                if (paths.length === 1) {
38809
                    // this is the end of the paths and we've found the tag we were
38810
                    // looking for
38811
                    results.push(data);
38812
                } else {
38813
                    // recursively search for the next tag inside of the data
38814
                    // of this one
38815
                    results = results.concat(findEbml(data, paths.slice(1)));
38816
                }
38817
            }
38818
            var totalLength = id.length + dataHeader.length + data.length; // move past this tag entirely, we are not looking for it
38819
 
38820
            i += totalLength;
38821
        }
38822
        return results;
38823
    }; // see https://www.matroska.org/technical/basics.html#block-structure
38824
 
38825
    var NAL_TYPE_ONE = toUint8([0x00, 0x00, 0x00, 0x01]);
38826
    var NAL_TYPE_TWO = toUint8([0x00, 0x00, 0x01]);
38827
    var EMULATION_PREVENTION = toUint8([0x00, 0x00, 0x03]);
38828
    /**
38829
     * Expunge any "Emulation Prevention" bytes from a "Raw Byte
38830
     * Sequence Payload"
38831
     *
38832
     * @param data {Uint8Array} the bytes of a RBSP from a NAL
38833
     * unit
38834
     * @return {Uint8Array} the RBSP without any Emulation
38835
     * Prevention Bytes
38836
     */
38837
 
38838
    var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(bytes) {
38839
        var positions = [];
38840
        var i = 1; // Find all `Emulation Prevention Bytes`
38841
 
38842
        while (i < bytes.length - 2) {
38843
            if (bytesMatch(bytes.subarray(i, i + 3), EMULATION_PREVENTION)) {
38844
                positions.push(i + 2);
38845
                i++;
38846
            }
38847
            i++;
38848
        } // If no Emulation Prevention Bytes were found just return the original
38849
        // array
38850
 
38851
        if (positions.length === 0) {
38852
            return bytes;
38853
        } // Create a new array to hold the NAL unit data
38854
 
38855
        var newLength = bytes.length - positions.length;
38856
        var newData = new Uint8Array(newLength);
38857
        var sourceIndex = 0;
38858
        for (i = 0; i < newLength; sourceIndex++, i++) {
38859
            if (sourceIndex === positions[0]) {
38860
                // Skip this byte
38861
                sourceIndex++; // Remove this position index
38862
 
38863
                positions.shift();
38864
            }
38865
            newData[i] = bytes[sourceIndex];
38866
        }
38867
        return newData;
38868
    };
38869
    var findNal = function findNal(bytes, dataType, types, nalLimit) {
38870
        if (nalLimit === void 0) {
38871
            nalLimit = Infinity;
38872
        }
38873
        bytes = toUint8(bytes);
38874
        types = [].concat(types);
38875
        var i = 0;
38876
        var nalStart;
38877
        var nalsFound = 0; // keep searching until:
38878
        // we reach the end of bytes
38879
        // we reach the maximum number of nals they want to seach
38880
        // NOTE: that we disregard nalLimit when we have found the start
38881
        // of the nal we want so that we can find the end of the nal we want.
38882
 
38883
        while (i < bytes.length && (nalsFound < nalLimit || nalStart)) {
38884
            var nalOffset = void 0;
38885
            if (bytesMatch(bytes.subarray(i), NAL_TYPE_ONE)) {
38886
                nalOffset = 4;
38887
            } else if (bytesMatch(bytes.subarray(i), NAL_TYPE_TWO)) {
38888
                nalOffset = 3;
38889
            } // we are unsynced,
38890
            // find the next nal unit
38891
 
38892
            if (!nalOffset) {
38893
                i++;
38894
                continue;
38895
            }
38896
            nalsFound++;
38897
            if (nalStart) {
38898
                return discardEmulationPreventionBytes(bytes.subarray(nalStart, i));
38899
            }
38900
            var nalType = void 0;
38901
            if (dataType === 'h264') {
38902
                nalType = bytes[i + nalOffset] & 0x1f;
38903
            } else if (dataType === 'h265') {
38904
                nalType = bytes[i + nalOffset] >> 1 & 0x3f;
38905
            }
38906
            if (types.indexOf(nalType) !== -1) {
38907
                nalStart = i + nalOffset;
38908
            } // nal header is 1 length for h264, and 2 for h265
38909
 
38910
            i += nalOffset + (dataType === 'h264' ? 1 : 2);
38911
        }
38912
        return bytes.subarray(0, 0);
38913
    };
38914
    var findH264Nal = function findH264Nal(bytes, type, nalLimit) {
38915
        return findNal(bytes, 'h264', type, nalLimit);
38916
    };
38917
    var findH265Nal = function findH265Nal(bytes, type, nalLimit) {
38918
        return findNal(bytes, 'h265', type, nalLimit);
38919
    };
38920
 
38921
    var CONSTANTS = {
38922
        // "webm" string literal in hex
38923
        'webm': toUint8([0x77, 0x65, 0x62, 0x6d]),
38924
        // "matroska" string literal in hex
38925
        'matroska': toUint8([0x6d, 0x61, 0x74, 0x72, 0x6f, 0x73, 0x6b, 0x61]),
38926
        // "fLaC" string literal in hex
38927
        'flac': toUint8([0x66, 0x4c, 0x61, 0x43]),
38928
        // "OggS" string literal in hex
38929
        'ogg': toUint8([0x4f, 0x67, 0x67, 0x53]),
38930
        // ac-3 sync byte, also works for ec-3 as that is simply a codec
38931
        // of ac-3
38932
        'ac3': toUint8([0x0b, 0x77]),
38933
        // "RIFF" string literal in hex used for wav and avi
38934
        'riff': toUint8([0x52, 0x49, 0x46, 0x46]),
38935
        // "AVI" string literal in hex
38936
        'avi': toUint8([0x41, 0x56, 0x49]),
38937
        // "WAVE" string literal in hex
38938
        'wav': toUint8([0x57, 0x41, 0x56, 0x45]),
38939
        // "ftyp3g" string literal in hex
38940
        '3gp': toUint8([0x66, 0x74, 0x79, 0x70, 0x33, 0x67]),
38941
        // "ftyp" string literal in hex
38942
        'mp4': toUint8([0x66, 0x74, 0x79, 0x70]),
38943
        // "styp" string literal in hex
38944
        'fmp4': toUint8([0x73, 0x74, 0x79, 0x70]),
38945
        // "ftypqt" string literal in hex
38946
        'mov': toUint8([0x66, 0x74, 0x79, 0x70, 0x71, 0x74]),
38947
        // moov string literal in hex
38948
        'moov': toUint8([0x6D, 0x6F, 0x6F, 0x76]),
38949
        // moof string literal in hex
38950
        'moof': toUint8([0x6D, 0x6F, 0x6F, 0x66])
38951
    };
38952
    var _isLikely = {
38953
        aac: function aac(bytes) {
38954
            var offset = getId3Offset(bytes);
38955
            return bytesMatch(bytes, [0xFF, 0x10], {
38956
                offset: offset,
38957
                mask: [0xFF, 0x16]
38958
            });
38959
        },
38960
        mp3: function mp3(bytes) {
38961
            var offset = getId3Offset(bytes);
38962
            return bytesMatch(bytes, [0xFF, 0x02], {
38963
                offset: offset,
38964
                mask: [0xFF, 0x06]
38965
            });
38966
        },
38967
        webm: function webm(bytes) {
38968
            var docType = findEbml(bytes, [EBML_TAGS.EBML, EBML_TAGS.DocType])[0]; // check if DocType EBML tag is webm
38969
 
38970
            return bytesMatch(docType, CONSTANTS.webm);
38971
        },
38972
        mkv: function mkv(bytes) {
38973
            var docType = findEbml(bytes, [EBML_TAGS.EBML, EBML_TAGS.DocType])[0]; // check if DocType EBML tag is matroska
38974
 
38975
            return bytesMatch(docType, CONSTANTS.matroska);
38976
        },
38977
        mp4: function mp4(bytes) {
38978
            // if this file is another base media file format, it is not mp4
38979
            if (_isLikely['3gp'](bytes) || _isLikely.mov(bytes)) {
38980
                return false;
38981
            } // if this file starts with a ftyp or styp box its mp4
38982
 
38983
            if (bytesMatch(bytes, CONSTANTS.mp4, {
38984
                offset: 4
38985
            }) || bytesMatch(bytes, CONSTANTS.fmp4, {
38986
                offset: 4
38987
            })) {
38988
                return true;
38989
            } // if this file starts with a moof/moov box its mp4
38990
 
38991
            if (bytesMatch(bytes, CONSTANTS.moof, {
38992
                offset: 4
38993
            }) || bytesMatch(bytes, CONSTANTS.moov, {
38994
                offset: 4
38995
            })) {
38996
                return true;
38997
            }
38998
        },
38999
        mov: function mov(bytes) {
39000
            return bytesMatch(bytes, CONSTANTS.mov, {
39001
                offset: 4
39002
            });
39003
        },
39004
        '3gp': function gp(bytes) {
39005
            return bytesMatch(bytes, CONSTANTS['3gp'], {
39006
                offset: 4
39007
            });
39008
        },
39009
        ac3: function ac3(bytes) {
39010
            var offset = getId3Offset(bytes);
39011
            return bytesMatch(bytes, CONSTANTS.ac3, {
39012
                offset: offset
39013
            });
39014
        },
39015
        ts: function ts(bytes) {
39016
            if (bytes.length < 189 && bytes.length >= 1) {
39017
                return bytes[0] === 0x47;
39018
            }
39019
            var i = 0; // check the first 376 bytes for two matching sync bytes
39020
 
39021
            while (i + 188 < bytes.length && i < 188) {
39022
                if (bytes[i] === 0x47 && bytes[i + 188] === 0x47) {
39023
                    return true;
39024
                }
39025
                i += 1;
39026
            }
39027
            return false;
39028
        },
39029
        flac: function flac(bytes) {
39030
            var offset = getId3Offset(bytes);
39031
            return bytesMatch(bytes, CONSTANTS.flac, {
39032
                offset: offset
39033
            });
39034
        },
39035
        ogg: function ogg(bytes) {
39036
            return bytesMatch(bytes, CONSTANTS.ogg);
39037
        },
39038
        avi: function avi(bytes) {
39039
            return bytesMatch(bytes, CONSTANTS.riff) && bytesMatch(bytes, CONSTANTS.avi, {
39040
                offset: 8
39041
            });
39042
        },
39043
        wav: function wav(bytes) {
39044
            return bytesMatch(bytes, CONSTANTS.riff) && bytesMatch(bytes, CONSTANTS.wav, {
39045
                offset: 8
39046
            });
39047
        },
39048
        'h264': function h264(bytes) {
39049
            // find seq_parameter_set_rbsp
39050
            return findH264Nal(bytes, 7, 3).length;
39051
        },
39052
        'h265': function h265(bytes) {
39053
            // find video_parameter_set_rbsp or seq_parameter_set_rbsp
39054
            return findH265Nal(bytes, [32, 33], 3).length;
39055
        }
39056
    }; // get all the isLikely functions
39057
    // but make sure 'ts' is above h264 and h265
39058
    // but below everything else as it is the least specific
39059
 
39060
    var isLikelyTypes = Object.keys(_isLikely) // remove ts, h264, h265
39061
        .filter(function (t) {
39062
            return t !== 'ts' && t !== 'h264' && t !== 'h265';
39063
        }) // add it back to the bottom
39064
        .concat(['ts', 'h264', 'h265']); // make sure we are dealing with uint8 data.
39065
 
39066
    isLikelyTypes.forEach(function (type) {
39067
        var isLikelyFn = _isLikely[type];
39068
        _isLikely[type] = function (bytes) {
39069
            return isLikelyFn(toUint8(bytes));
39070
        };
39071
    }); // export after wrapping
39072
 
39073
    var isLikely = _isLikely; // A useful list of file signatures can be found here
39074
    // https://en.wikipedia.org/wiki/List_of_file_signatures
39075
 
39076
    var detectContainerForBytes = function detectContainerForBytes(bytes) {
39077
        bytes = toUint8(bytes);
39078
        for (var i = 0; i < isLikelyTypes.length; i++) {
39079
            var type = isLikelyTypes[i];
39080
            if (isLikely[type](bytes)) {
39081
                return type;
39082
            }
39083
        }
39084
        return '';
39085
    }; // fmp4 is not a container
39086
 
39087
    var isLikelyFmp4MediaSegment = function isLikelyFmp4MediaSegment(bytes) {
39088
        return findBox(bytes, ['moof']).length > 0;
39089
    };
39090
 
39091
    /**
39092
     * mux.js
39093
     *
39094
     * Copyright (c) Brightcove
39095
     * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
39096
     */
39097
    var ONE_SECOND_IN_TS = 90000,
39098
        // 90kHz clock
39099
        secondsToVideoTs,
39100
        secondsToAudioTs,
39101
        videoTsToSeconds,
39102
        audioTsToSeconds,
39103
        audioTsToVideoTs,
39104
        videoTsToAudioTs,
39105
        metadataTsToSeconds;
39106
    secondsToVideoTs = function (seconds) {
39107
        return seconds * ONE_SECOND_IN_TS;
39108
    };
39109
    secondsToAudioTs = function (seconds, sampleRate) {
39110
        return seconds * sampleRate;
39111
    };
39112
    videoTsToSeconds = function (timestamp) {
39113
        return timestamp / ONE_SECOND_IN_TS;
39114
    };
39115
    audioTsToSeconds = function (timestamp, sampleRate) {
39116
        return timestamp / sampleRate;
39117
    };
39118
    audioTsToVideoTs = function (timestamp, sampleRate) {
39119
        return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
39120
    };
39121
    videoTsToAudioTs = function (timestamp, sampleRate) {
39122
        return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
39123
    };
39124
 
39125
    /**
39126
     * Adjust ID3 tag or caption timing information by the timeline pts values
39127
     * (if keepOriginalTimestamps is false) and convert to seconds
39128
     */
39129
    metadataTsToSeconds = function (timestamp, timelineStartPts, keepOriginalTimestamps) {
39130
        return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
39131
    };
39132
    var clock = {
39133
        ONE_SECOND_IN_TS: ONE_SECOND_IN_TS,
39134
        secondsToVideoTs: secondsToVideoTs,
39135
        secondsToAudioTs: secondsToAudioTs,
39136
        videoTsToSeconds: videoTsToSeconds,
39137
        audioTsToSeconds: audioTsToSeconds,
39138
        audioTsToVideoTs: audioTsToVideoTs,
39139
        videoTsToAudioTs: videoTsToAudioTs,
39140
        metadataTsToSeconds: metadataTsToSeconds
39141
    };
39142
    var clock_1 = clock.ONE_SECOND_IN_TS;
39143
 
39144
    /*! @name @videojs/http-streaming @version 3.10.0 @license Apache-2.0 */
39145
 
39146
    /**
39147
     * @file resolve-url.js - Handling how URLs are resolved and manipulated
39148
     */
39149
    const resolveUrl = resolveUrl$1;
39150
    /**
39151
     * If the xhr request was redirected, return the responseURL, otherwise,
39152
     * return the original url.
39153
     *
39154
     * @api private
39155
     *
39156
     * @param  {string} url - an url being requested
39157
     * @param  {XMLHttpRequest} req - xhr request result
39158
     *
39159
     * @return {string}
39160
     */
39161
 
39162
    const resolveManifestRedirect = (url, req) => {
39163
        // To understand how the responseURL below is set and generated:
39164
        // - https://fetch.spec.whatwg.org/#concept-response-url
39165
        // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
39166
        if (req && req.responseURL && url !== req.responseURL) {
39167
            return req.responseURL;
39168
        }
39169
        return url;
39170
    };
39171
    const logger = source => {
39172
        if (videojs.log.debug) {
39173
            return videojs.log.debug.bind(videojs, 'VHS:', `${source} >`);
39174
        }
39175
        return function () {};
39176
    };
39177
 
39178
    /**
39179
     * Provides a compatibility layer between Video.js 7 and 8 API changes for VHS.
39180
     */
39181
    /**
39182
     * Delegates to videojs.obj.merge (Video.js 8) or
39183
     * videojs.mergeOptions (Video.js 7).
39184
     */
39185
 
39186
    function merge(...args) {
39187
        const context = videojs.obj || videojs;
39188
        const fn = context.merge || context.mergeOptions;
39189
        return fn.apply(context, args);
39190
    }
39191
    /**
39192
     * Delegates to videojs.time.createTimeRanges (Video.js 8) or
39193
     * videojs.createTimeRanges (Video.js 7).
39194
     */
39195
 
39196
    function createTimeRanges(...args) {
39197
        const context = videojs.time || videojs;
39198
        const fn = context.createTimeRanges || context.createTimeRanges;
39199
        return fn.apply(context, args);
39200
    }
39201
 
39202
    /**
39203
     * ranges
39204
     *
39205
     * Utilities for working with TimeRanges.
39206
     *
39207
     */
39208
 
39209
    const TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range
39210
    // can be misleading because of precision differences or when the current media has poorly
39211
    // aligned audio and video, which can cause values to be slightly off from what you would
39212
    // expect. This value is what we consider to be safe to use in such comparisons to account
39213
    // for these scenarios.
39214
 
39215
    const SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
39216
    const filterRanges = function (timeRanges, predicate) {
39217
        const results = [];
39218
        let i;
39219
        if (timeRanges && timeRanges.length) {
39220
            // Search for ranges that match the predicate
39221
            for (i = 0; i < timeRanges.length; i++) {
39222
                if (predicate(timeRanges.start(i), timeRanges.end(i))) {
39223
                    results.push([timeRanges.start(i), timeRanges.end(i)]);
39224
                }
39225
            }
39226
        }
39227
        return createTimeRanges(results);
39228
    };
39229
    /**
39230
     * Attempts to find the buffered TimeRange that contains the specified
39231
     * time.
39232
     *
39233
     * @param {TimeRanges} buffered - the TimeRanges object to query
39234
     * @param {number} time  - the time to filter on.
39235
     * @return {TimeRanges} a new TimeRanges object
39236
     */
39237
 
39238
    const findRange = function (buffered, time) {
39239
        return filterRanges(buffered, function (start, end) {
39240
            return start - SAFE_TIME_DELTA <= time && end + SAFE_TIME_DELTA >= time;
39241
        });
39242
    };
39243
    /**
39244
     * Returns the TimeRanges that begin later than the specified time.
39245
     *
39246
     * @param {TimeRanges} timeRanges - the TimeRanges object to query
39247
     * @param {number} time - the time to filter on.
39248
     * @return {TimeRanges} a new TimeRanges object.
39249
     */
39250
 
39251
    const findNextRange = function (timeRanges, time) {
39252
        return filterRanges(timeRanges, function (start) {
39253
            return start - TIME_FUDGE_FACTOR >= time;
39254
        });
39255
    };
39256
    /**
39257
     * Returns gaps within a list of TimeRanges
39258
     *
39259
     * @param {TimeRanges} buffered - the TimeRanges object
39260
     * @return {TimeRanges} a TimeRanges object of gaps
39261
     */
39262
 
39263
    const findGaps = function (buffered) {
39264
        if (buffered.length < 2) {
39265
            return createTimeRanges();
39266
        }
39267
        const ranges = [];
39268
        for (let i = 1; i < buffered.length; i++) {
39269
            const start = buffered.end(i - 1);
39270
            const end = buffered.start(i);
39271
            ranges.push([start, end]);
39272
        }
39273
        return createTimeRanges(ranges);
39274
    };
39275
    /**
39276
     * Calculate the intersection of two TimeRanges
39277
     *
39278
     * @param {TimeRanges} bufferA
39279
     * @param {TimeRanges} bufferB
39280
     * @return {TimeRanges} The interesection of `bufferA` with `bufferB`
39281
     */
39282
 
39283
    const bufferIntersection = function (bufferA, bufferB) {
39284
        let start = null;
39285
        let end = null;
39286
        let arity = 0;
39287
        const extents = [];
39288
        const ranges = [];
39289
        if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) {
39290
            return createTimeRanges();
39291
        } // Handle the case where we have both buffers and create an
39292
        // intersection of the two
39293
 
39294
        let count = bufferA.length; // A) Gather up all start and end times
39295
 
39296
        while (count--) {
39297
            extents.push({
39298
                time: bufferA.start(count),
39299
                type: 'start'
39300
            });
39301
            extents.push({
39302
                time: bufferA.end(count),
39303
                type: 'end'
39304
            });
39305
        }
39306
        count = bufferB.length;
39307
        while (count--) {
39308
            extents.push({
39309
                time: bufferB.start(count),
39310
                type: 'start'
39311
            });
39312
            extents.push({
39313
                time: bufferB.end(count),
39314
                type: 'end'
39315
            });
39316
        } // B) Sort them by time
39317
 
39318
        extents.sort(function (a, b) {
39319
            return a.time - b.time;
39320
        }); // C) Go along one by one incrementing arity for start and decrementing
39321
        //    arity for ends
39322
 
39323
        for (count = 0; count < extents.length; count++) {
39324
            if (extents[count].type === 'start') {
39325
                arity++; // D) If arity is ever incremented to 2 we are entering an
39326
                //    overlapping range
39327
 
39328
                if (arity === 2) {
39329
                    start = extents[count].time;
39330
                }
39331
            } else if (extents[count].type === 'end') {
39332
                arity--; // E) If arity is ever decremented to 1 we leaving an
39333
                //    overlapping range
39334
 
39335
                if (arity === 1) {
39336
                    end = extents[count].time;
39337
                }
39338
            } // F) Record overlapping ranges
39339
 
39340
            if (start !== null && end !== null) {
39341
                ranges.push([start, end]);
39342
                start = null;
39343
                end = null;
39344
            }
39345
        }
39346
        return createTimeRanges(ranges);
39347
    };
39348
    /**
39349
     * Gets a human readable string for a TimeRange
39350
     *
39351
     * @param {TimeRange} range
39352
     * @return {string} a human readable string
39353
     */
39354
 
39355
    const printableRange = range => {
39356
        const strArr = [];
39357
        if (!range || !range.length) {
39358
            return '';
39359
        }
39360
        for (let i = 0; i < range.length; i++) {
39361
            strArr.push(range.start(i) + ' => ' + range.end(i));
39362
        }
39363
        return strArr.join(', ');
39364
    };
39365
    /**
39366
     * Calculates the amount of time left in seconds until the player hits the end of the
39367
     * buffer and causes a rebuffer
39368
     *
39369
     * @param {TimeRange} buffered
39370
     *        The state of the buffer
39371
     * @param {Numnber} currentTime
39372
     *        The current time of the player
39373
     * @param {number} playbackRate
39374
     *        The current playback rate of the player. Defaults to 1.
39375
     * @return {number}
39376
     *         Time until the player has to start rebuffering in seconds.
39377
     * @function timeUntilRebuffer
39378
     */
39379
 
39380
    const timeUntilRebuffer = function (buffered, currentTime, playbackRate = 1) {
39381
        const bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
39382
        return (bufferedEnd - currentTime) / playbackRate;
39383
    };
39384
    /**
39385
     * Converts a TimeRanges object into an array representation
39386
     *
39387
     * @param {TimeRanges} timeRanges
39388
     * @return {Array}
39389
     */
39390
 
39391
    const timeRangesToArray = timeRanges => {
39392
        const timeRangesList = [];
39393
        for (let i = 0; i < timeRanges.length; i++) {
39394
            timeRangesList.push({
39395
                start: timeRanges.start(i),
39396
                end: timeRanges.end(i)
39397
            });
39398
        }
39399
        return timeRangesList;
39400
    };
39401
    /**
39402
     * Determines if two time range objects are different.
39403
     *
39404
     * @param {TimeRange} a
39405
     *        the first time range object to check
39406
     *
39407
     * @param {TimeRange} b
39408
     *        the second time range object to check
39409
     *
39410
     * @return {Boolean}
39411
     *         Whether the time range objects differ
39412
     */
39413
 
39414
    const isRangeDifferent = function (a, b) {
39415
        // same object
39416
        if (a === b) {
39417
            return false;
39418
        } // one or the other is undefined
39419
 
39420
        if (!a && b || !b && a) {
39421
            return true;
39422
        } // length is different
39423
 
39424
        if (a.length !== b.length) {
39425
            return true;
39426
        } // see if any start/end pair is different
39427
 
39428
        for (let i = 0; i < a.length; i++) {
39429
            if (a.start(i) !== b.start(i) || a.end(i) !== b.end(i)) {
39430
                return true;
39431
            }
39432
        } // if the length and every pair is the same
39433
        // this is the same time range
39434
 
39435
        return false;
39436
    };
39437
    const lastBufferedEnd = function (a) {
39438
        if (!a || !a.length || !a.end) {
39439
            return;
39440
        }
39441
        return a.end(a.length - 1);
39442
    };
39443
    /**
39444
     * A utility function to add up the amount of time in a timeRange
39445
     * after a specified startTime.
39446
     * ie:[[0, 10], [20, 40], [50, 60]] with a startTime 0
39447
     *     would return 40 as there are 40s seconds after 0 in the timeRange
39448
     *
39449
     * @param {TimeRange} range
39450
     *        The range to check against
39451
     * @param {number} startTime
39452
     *        The time in the time range that you should start counting from
39453
     *
39454
     * @return {number}
39455
     *          The number of seconds in the buffer passed the specified time.
39456
     */
39457
 
39458
    const timeAheadOf = function (range, startTime) {
39459
        let time = 0;
39460
        if (!range || !range.length) {
39461
            return time;
39462
        }
39463
        for (let i = 0; i < range.length; i++) {
39464
            const start = range.start(i);
39465
            const end = range.end(i); // startTime is after this range entirely
39466
 
39467
            if (startTime > end) {
39468
                continue;
39469
            } // startTime is within this range
39470
 
39471
            if (startTime > start && startTime <= end) {
39472
                time += end - startTime;
39473
                continue;
39474
            } // startTime is before this range.
39475
 
39476
            time += end - start;
39477
        }
39478
        return time;
39479
    };
39480
 
39481
    /**
39482
     * @file playlist.js
39483
     *
39484
     * Playlist related utilities.
39485
     */
39486
    /**
39487
     * Get the duration of a segment, with special cases for
39488
     * llhls segments that do not have a duration yet.
39489
     *
39490
     * @param {Object} playlist
39491
     *        the playlist that the segment belongs to.
39492
     * @param {Object} segment
39493
     *        the segment to get a duration for.
39494
     *
39495
     * @return {number}
39496
     *          the segment duration
39497
     */
39498
 
39499
    const segmentDurationWithParts = (playlist, segment) => {
39500
        // if this isn't a preload segment
39501
        // then we will have a segment duration that is accurate.
39502
        if (!segment.preload) {
39503
            return segment.duration;
39504
        } // otherwise we have to add up parts and preload hints
39505
        // to get an up to date duration.
39506
 
39507
        let result = 0;
39508
        (segment.parts || []).forEach(function (p) {
39509
            result += p.duration;
39510
        }); // for preload hints we have to use partTargetDuration
39511
        // as they won't even have a duration yet.
39512
 
39513
        (segment.preloadHints || []).forEach(function (p) {
39514
            if (p.type === 'PART') {
39515
                result += playlist.partTargetDuration;
39516
            }
39517
        });
39518
        return result;
39519
    };
39520
    /**
39521
     * A function to get a combined list of parts and segments with durations
39522
     * and indexes.
39523
     *
39524
     * @param {Playlist} playlist the playlist to get the list for.
39525
     *
39526
     * @return {Array} The part/segment list.
39527
     */
39528
 
39529
    const getPartsAndSegments = playlist => (playlist.segments || []).reduce((acc, segment, si) => {
39530
        if (segment.parts) {
39531
            segment.parts.forEach(function (part, pi) {
39532
                acc.push({
39533
                    duration: part.duration,
39534
                    segmentIndex: si,
39535
                    partIndex: pi,
39536
                    part,
39537
                    segment
39538
                });
39539
            });
39540
        } else {
39541
            acc.push({
39542
                duration: segment.duration,
39543
                segmentIndex: si,
39544
                partIndex: null,
39545
                segment,
39546
                part: null
39547
            });
39548
        }
39549
        return acc;
39550
    }, []);
39551
    const getLastParts = media => {
39552
        const lastSegment = media.segments && media.segments.length && media.segments[media.segments.length - 1];
39553
        return lastSegment && lastSegment.parts || [];
39554
    };
39555
    const getKnownPartCount = ({
39556
                                   preloadSegment
39557
                               }) => {
39558
        if (!preloadSegment) {
39559
            return;
39560
        }
39561
        const {
39562
            parts,
39563
            preloadHints
39564
        } = preloadSegment;
39565
        let partCount = (preloadHints || []).reduce((count, hint) => count + (hint.type === 'PART' ? 1 : 0), 0);
39566
        partCount += parts && parts.length ? parts.length : 0;
39567
        return partCount;
39568
    };
39569
    /**
39570
     * Get the number of seconds to delay from the end of a
39571
     * live playlist.
39572
     *
39573
     * @param {Playlist} main the main playlist
39574
     * @param {Playlist} media the media playlist
39575
     * @return {number} the hold back in seconds.
39576
     */
39577
 
39578
    const liveEdgeDelay = (main, media) => {
39579
        if (media.endList) {
39580
            return 0;
39581
        } // dash suggestedPresentationDelay trumps everything
39582
 
39583
        if (main && main.suggestedPresentationDelay) {
39584
            return main.suggestedPresentationDelay;
39585
        }
39586
        const hasParts = getLastParts(media).length > 0; // look for "part" delays from ll-hls first
39587
 
39588
        if (hasParts && media.serverControl && media.serverControl.partHoldBack) {
39589
            return media.serverControl.partHoldBack;
39590
        } else if (hasParts && media.partTargetDuration) {
39591
            return media.partTargetDuration * 3; // finally look for full segment delays
39592
        } else if (media.serverControl && media.serverControl.holdBack) {
39593
            return media.serverControl.holdBack;
39594
        } else if (media.targetDuration) {
39595
            return media.targetDuration * 3;
39596
        }
39597
        return 0;
39598
    };
39599
    /**
39600
     * walk backward until we find a duration we can use
39601
     * or return a failure
39602
     *
39603
     * @param {Playlist} playlist the playlist to walk through
39604
     * @param {Number} endSequence the mediaSequence to stop walking on
39605
     */
39606
 
39607
    const backwardDuration = function (playlist, endSequence) {
39608
        let result = 0;
39609
        let i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following
39610
        // the interval, use it
39611
 
39612
        let segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline
39613
        // information that is earlier than endSequence
39614
 
39615
        if (segment) {
39616
            if (typeof segment.start !== 'undefined') {
39617
                return {
39618
                    result: segment.start,
39619
                    precise: true
39620
                };
39621
            }
39622
            if (typeof segment.end !== 'undefined') {
39623
                return {
39624
                    result: segment.end - segment.duration,
39625
                    precise: true
39626
                };
39627
            }
39628
        }
39629
        while (i--) {
39630
            segment = playlist.segments[i];
39631
            if (typeof segment.end !== 'undefined') {
39632
                return {
39633
                    result: result + segment.end,
39634
                    precise: true
39635
                };
39636
            }
39637
            result += segmentDurationWithParts(playlist, segment);
39638
            if (typeof segment.start !== 'undefined') {
39639
                return {
39640
                    result: result + segment.start,
39641
                    precise: true
39642
                };
39643
            }
39644
        }
39645
        return {
39646
            result,
39647
            precise: false
39648
        };
39649
    };
39650
    /**
39651
     * walk forward until we find a duration we can use
39652
     * or return a failure
39653
     *
39654
     * @param {Playlist} playlist the playlist to walk through
39655
     * @param {number} endSequence the mediaSequence to stop walking on
39656
     */
39657
 
39658
    const forwardDuration = function (playlist, endSequence) {
39659
        let result = 0;
39660
        let segment;
39661
        let i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline
39662
        // information
39663
 
39664
        for (; i < playlist.segments.length; i++) {
39665
            segment = playlist.segments[i];
39666
            if (typeof segment.start !== 'undefined') {
39667
                return {
39668
                    result: segment.start - result,
39669
                    precise: true
39670
                };
39671
            }
39672
            result += segmentDurationWithParts(playlist, segment);
39673
            if (typeof segment.end !== 'undefined') {
39674
                return {
39675
                    result: segment.end - result,
39676
                    precise: true
39677
                };
39678
            }
39679
        } // indicate we didn't find a useful duration estimate
39680
 
39681
        return {
39682
            result: -1,
39683
            precise: false
39684
        };
39685
    };
39686
    /**
39687
     * Calculate the media duration from the segments associated with a
39688
     * playlist. The duration of a subinterval of the available segments
39689
     * may be calculated by specifying an end index.
39690
     *
39691
     * @param {Object} playlist a media playlist object
39692
     * @param {number=} endSequence an exclusive upper boundary
39693
     * for the playlist.  Defaults to playlist length.
39694
     * @param {number} expired the amount of time that has dropped
39695
     * off the front of the playlist in a live scenario
39696
     * @return {number} the duration between the first available segment
39697
     * and end index.
39698
     */
39699
 
39700
    const intervalDuration = function (playlist, endSequence, expired) {
39701
        if (typeof endSequence === 'undefined') {
39702
            endSequence = playlist.mediaSequence + playlist.segments.length;
39703
        }
39704
        if (endSequence < playlist.mediaSequence) {
39705
            return 0;
39706
        } // do a backward walk to estimate the duration
39707
 
39708
        const backward = backwardDuration(playlist, endSequence);
39709
        if (backward.precise) {
39710
            // if we were able to base our duration estimate on timing
39711
            // information provided directly from the Media Source, return
39712
            // it
39713
            return backward.result;
39714
        } // walk forward to see if a precise duration estimate can be made
39715
        // that way
39716
 
39717
        const forward = forwardDuration(playlist, endSequence);
39718
        if (forward.precise) {
39719
            // we found a segment that has been buffered and so it's
39720
            // position is known precisely
39721
            return forward.result;
39722
        } // return the less-precise, playlist-based duration estimate
39723
 
39724
        return backward.result + expired;
39725
    };
39726
    /**
39727
     * Calculates the duration of a playlist. If a start and end index
39728
     * are specified, the duration will be for the subset of the media
39729
     * timeline between those two indices. The total duration for live
39730
     * playlists is always Infinity.
39731
     *
39732
     * @param {Object} playlist a media playlist object
39733
     * @param {number=} endSequence an exclusive upper
39734
     * boundary for the playlist. Defaults to the playlist media
39735
     * sequence number plus its length.
39736
     * @param {number=} expired the amount of time that has
39737
     * dropped off the front of the playlist in a live scenario
39738
     * @return {number} the duration between the start index and end
39739
     * index.
39740
     */
39741
 
39742
    const duration = function (playlist, endSequence, expired) {
39743
        if (!playlist) {
39744
            return 0;
39745
        }
39746
        if (typeof expired !== 'number') {
39747
            expired = 0;
39748
        } // if a slice of the total duration is not requested, use
39749
        // playlist-level duration indicators when they're present
39750
 
39751
        if (typeof endSequence === 'undefined') {
39752
            // if present, use the duration specified in the playlist
39753
            if (playlist.totalDuration) {
39754
                return playlist.totalDuration;
39755
            } // duration should be Infinity for live playlists
39756
 
39757
            if (!playlist.endList) {
39758
                return window.Infinity;
39759
            }
39760
        } // calculate the total duration based on the segment durations
39761
 
39762
        return intervalDuration(playlist, endSequence, expired);
39763
    };
39764
    /**
39765
     * Calculate the time between two indexes in the current playlist
39766
     * neight the start- nor the end-index need to be within the current
39767
     * playlist in which case, the targetDuration of the playlist is used
39768
     * to approximate the durations of the segments
39769
     *
39770
     * @param {Array} options.durationList list to iterate over for durations.
39771
     * @param {number} options.defaultDuration duration to use for elements before or after the durationList
39772
     * @param {number} options.startIndex partsAndSegments index to start
39773
     * @param {number} options.endIndex partsAndSegments index to end.
39774
     * @return {number} the number of seconds between startIndex and endIndex
39775
     */
39776
 
39777
    const sumDurations = function ({
39778
                                       defaultDuration,
39779
                                       durationList,
39780
                                       startIndex,
39781
                                       endIndex
39782
                                   }) {
39783
        let durations = 0;
39784
        if (startIndex > endIndex) {
39785
            [startIndex, endIndex] = [endIndex, startIndex];
39786
        }
39787
        if (startIndex < 0) {
39788
            for (let i = startIndex; i < Math.min(0, endIndex); i++) {
39789
                durations += defaultDuration;
39790
            }
39791
            startIndex = 0;
39792
        }
39793
        for (let i = startIndex; i < endIndex; i++) {
39794
            durations += durationList[i].duration;
39795
        }
39796
        return durations;
39797
    };
39798
    /**
39799
     * Calculates the playlist end time
39800
     *
39801
     * @param {Object} playlist a media playlist object
39802
     * @param {number=} expired the amount of time that has
39803
     *                  dropped off the front of the playlist in a live scenario
39804
     * @param {boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
39805
     *                        playlist end calculation should consider the safe live end
39806
     *                        (truncate the playlist end by three segments). This is normally
39807
     *                        used for calculating the end of the playlist's seekable range.
39808
     *                        This takes into account the value of liveEdgePadding.
39809
     *                        Setting liveEdgePadding to 0 is equivalent to setting this to false.
39810
     * @param {number} liveEdgePadding a number indicating how far from the end of the playlist we should be in seconds.
39811
     *                 If this is provided, it is used in the safe live end calculation.
39812
     *                 Setting useSafeLiveEnd=false or liveEdgePadding=0 are equivalent.
39813
     *                 Corresponds to suggestedPresentationDelay in DASH manifests.
39814
     * @return {number} the end time of playlist
39815
     * @function playlistEnd
39816
     */
39817
 
39818
    const playlistEnd = function (playlist, expired, useSafeLiveEnd, liveEdgePadding) {
39819
        if (!playlist || !playlist.segments) {
39820
            return null;
39821
        }
39822
        if (playlist.endList) {
39823
            return duration(playlist);
39824
        }
39825
        if (expired === null) {
39826
            return null;
39827
        }
39828
        expired = expired || 0;
39829
        let lastSegmentEndTime = intervalDuration(playlist, playlist.mediaSequence + playlist.segments.length, expired);
39830
        if (useSafeLiveEnd) {
39831
            liveEdgePadding = typeof liveEdgePadding === 'number' ? liveEdgePadding : liveEdgeDelay(null, playlist);
39832
            lastSegmentEndTime -= liveEdgePadding;
39833
        } // don't return a time less than zero
39834
 
39835
        return Math.max(0, lastSegmentEndTime);
39836
    };
39837
    /**
39838
     * Calculates the interval of time that is currently seekable in a
39839
     * playlist. The returned time ranges are relative to the earliest
39840
     * moment in the specified playlist that is still available. A full
39841
     * seekable implementation for live streams would need to offset
39842
     * these values by the duration of content that has expired from the
39843
     * stream.
39844
     *
39845
     * @param {Object} playlist a media playlist object
39846
     * dropped off the front of the playlist in a live scenario
39847
     * @param {number=} expired the amount of time that has
39848
     * dropped off the front of the playlist in a live scenario
39849
     * @param {number} liveEdgePadding how far from the end of the playlist we should be in seconds.
39850
     *        Corresponds to suggestedPresentationDelay in DASH manifests.
39851
     * @return {TimeRanges} the periods of time that are valid targets
39852
     * for seeking
39853
     */
39854
 
39855
    const seekable = function (playlist, expired, liveEdgePadding) {
39856
        const useSafeLiveEnd = true;
39857
        const seekableStart = expired || 0;
39858
        let seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding);
39859
        if (seekableEnd === null) {
39860
            return createTimeRanges();
39861
        } // Clamp seekable end since it can not be less than the seekable start
39862
 
39863
        if (seekableEnd < seekableStart) {
39864
            seekableEnd = seekableStart;
39865
        }
39866
        return createTimeRanges(seekableStart, seekableEnd);
39867
    };
39868
    /**
39869
     * Determine the index and estimated starting time of the segment that
39870
     * contains a specified playback position in a media playlist.
39871
     *
39872
     * @param {Object} options.playlist the media playlist to query
39873
     * @param {number} options.currentTime The number of seconds since the earliest
39874
     * possible position to determine the containing segment for
39875
     * @param {number} options.startTime the time when the segment/part starts
39876
     * @param {number} options.startingSegmentIndex the segment index to start looking at.
39877
     * @param {number?} [options.startingPartIndex] the part index to look at within the segment.
39878
     *
39879
     * @return {Object} an object with partIndex, segmentIndex, and startTime.
39880
     */
39881
 
39882
    const getMediaInfoForTime = function ({
39883
                                              playlist,
39884
                                              currentTime,
39885
                                              startingSegmentIndex,
39886
                                              startingPartIndex,
39887
                                              startTime,
39888
                                              exactManifestTimings
39889
                                          }) {
39890
        let time = currentTime - startTime;
39891
        const partsAndSegments = getPartsAndSegments(playlist);
39892
        let startIndex = 0;
39893
        for (let i = 0; i < partsAndSegments.length; i++) {
39894
            const partAndSegment = partsAndSegments[i];
39895
            if (startingSegmentIndex !== partAndSegment.segmentIndex) {
39896
                continue;
39897
            } // skip this if part index does not match.
39898
 
39899
            if (typeof startingPartIndex === 'number' && typeof partAndSegment.partIndex === 'number' && startingPartIndex !== partAndSegment.partIndex) {
39900
                continue;
39901
            }
39902
            startIndex = i;
39903
            break;
39904
        }
39905
        if (time < 0) {
39906
            // Walk backward from startIndex in the playlist, adding durations
39907
            // until we find a segment that contains `time` and return it
39908
            if (startIndex > 0) {
39909
                for (let i = startIndex - 1; i >= 0; i--) {
39910
                    const partAndSegment = partsAndSegments[i];
39911
                    time += partAndSegment.duration;
39912
                    if (exactManifestTimings) {
39913
                        if (time < 0) {
39914
                            continue;
39915
                        }
39916
                    } else if (time + TIME_FUDGE_FACTOR <= 0) {
39917
                        continue;
39918
                    }
39919
                    return {
39920
                        partIndex: partAndSegment.partIndex,
39921
                        segmentIndex: partAndSegment.segmentIndex,
39922
                        startTime: startTime - sumDurations({
39923
                            defaultDuration: playlist.targetDuration,
39924
                            durationList: partsAndSegments,
39925
                            startIndex,
39926
                            endIndex: i
39927
                        })
39928
                    };
39929
                }
39930
            } // We were unable to find a good segment within the playlist
39931
            // so select the first segment
39932
 
39933
            return {
39934
                partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null,
39935
                segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
39936
                startTime: currentTime
39937
            };
39938
        } // When startIndex is negative, we first walk forward to first segment
39939
        // adding target durations. If we "run out of time" before getting to
39940
        // the first segment, return the first segment
39941
 
39942
        if (startIndex < 0) {
39943
            for (let i = startIndex; i < 0; i++) {
39944
                time -= playlist.targetDuration;
39945
                if (time < 0) {
39946
                    return {
39947
                        partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null,
39948
                        segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
39949
                        startTime: currentTime
39950
                    };
39951
                }
39952
            }
39953
            startIndex = 0;
39954
        } // Walk forward from startIndex in the playlist, subtracting durations
39955
        // until we find a segment that contains `time` and return it
39956
 
39957
        for (let i = startIndex; i < partsAndSegments.length; i++) {
39958
            const partAndSegment = partsAndSegments[i];
39959
            time -= partAndSegment.duration;
39960
            const canUseFudgeFactor = partAndSegment.duration > TIME_FUDGE_FACTOR;
39961
            const isExactlyAtTheEnd = time === 0;
39962
            const isExtremelyCloseToTheEnd = canUseFudgeFactor && time + TIME_FUDGE_FACTOR >= 0;
39963
            if (isExactlyAtTheEnd || isExtremelyCloseToTheEnd) {
39964
                // 1) We are exactly at the end of the current segment.
39965
                // 2) We are extremely close to the end of the current segment (The difference is less than  1 / 30).
39966
                //    We may encounter this situation when
39967
                //    we don't have exact match between segment duration info in the manifest and the actual duration of the segment
39968
                //    For example:
39969
                //    We appended 3 segments 10 seconds each, meaning we should have 30 sec buffered,
39970
                //    but we the actual buffered is 29.99999
39971
                //
39972
                // In both cases:
39973
                // if we passed current time -> it means that we already played current segment
39974
                // if we passed buffered.end -> it means that this segment is already loaded and buffered
39975
                // we should select the next segment if we have one:
39976
                if (i !== partsAndSegments.length - 1) {
39977
                    continue;
39978
                }
39979
            }
39980
            if (exactManifestTimings) {
39981
                if (time > 0) {
39982
                    continue;
39983
                }
39984
            } else if (time - TIME_FUDGE_FACTOR >= 0) {
39985
                continue;
39986
            }
39987
            return {
39988
                partIndex: partAndSegment.partIndex,
39989
                segmentIndex: partAndSegment.segmentIndex,
39990
                startTime: startTime + sumDurations({
39991
                    defaultDuration: playlist.targetDuration,
39992
                    durationList: partsAndSegments,
39993
                    startIndex,
39994
                    endIndex: i
39995
                })
39996
            };
39997
        } // We are out of possible candidates so load the last one...
39998
 
39999
        return {
40000
            segmentIndex: partsAndSegments[partsAndSegments.length - 1].segmentIndex,
40001
            partIndex: partsAndSegments[partsAndSegments.length - 1].partIndex,
40002
            startTime: currentTime
40003
        };
40004
    };
40005
    /**
40006
     * Check whether the playlist is excluded or not.
40007
     *
40008
     * @param {Object} playlist the media playlist object
40009
     * @return {boolean} whether the playlist is excluded or not
40010
     * @function isExcluded
40011
     */
40012
 
40013
    const isExcluded = function (playlist) {
40014
        return playlist.excludeUntil && playlist.excludeUntil > Date.now();
40015
    };
40016
    /**
40017
     * Check whether the playlist is compatible with current playback configuration or has
40018
     * been excluded permanently for being incompatible.
40019
     *
40020
     * @param {Object} playlist the media playlist object
40021
     * @return {boolean} whether the playlist is incompatible or not
40022
     * @function isIncompatible
40023
     */
40024
 
40025
    const isIncompatible = function (playlist) {
40026
        return playlist.excludeUntil && playlist.excludeUntil === Infinity;
40027
    };
40028
    /**
40029
     * Check whether the playlist is enabled or not.
40030
     *
40031
     * @param {Object} playlist the media playlist object
40032
     * @return {boolean} whether the playlist is enabled or not
40033
     * @function isEnabled
40034
     */
40035
 
40036
    const isEnabled = function (playlist) {
40037
        const excluded = isExcluded(playlist);
40038
        return !playlist.disabled && !excluded;
40039
    };
40040
    /**
40041
     * Check whether the playlist has been manually disabled through the representations api.
40042
     *
40043
     * @param {Object} playlist the media playlist object
40044
     * @return {boolean} whether the playlist is disabled manually or not
40045
     * @function isDisabled
40046
     */
40047
 
40048
    const isDisabled = function (playlist) {
40049
        return playlist.disabled;
40050
    };
40051
    /**
40052
     * Returns whether the current playlist is an AES encrypted HLS stream
40053
     *
40054
     * @return {boolean} true if it's an AES encrypted HLS stream
40055
     */
40056
 
40057
    const isAes = function (media) {
40058
        for (let i = 0; i < media.segments.length; i++) {
40059
            if (media.segments[i].key) {
40060
                return true;
40061
            }
40062
        }
40063
        return false;
40064
    };
40065
    /**
40066
     * Checks if the playlist has a value for the specified attribute
40067
     *
40068
     * @param {string} attr
40069
     *        Attribute to check for
40070
     * @param {Object} playlist
40071
     *        The media playlist object
40072
     * @return {boolean}
40073
     *         Whether the playlist contains a value for the attribute or not
40074
     * @function hasAttribute
40075
     */
40076
 
40077
    const hasAttribute = function (attr, playlist) {
40078
        return playlist.attributes && playlist.attributes[attr];
40079
    };
40080
    /**
40081
     * Estimates the time required to complete a segment download from the specified playlist
40082
     *
40083
     * @param {number} segmentDuration
40084
     *        Duration of requested segment
40085
     * @param {number} bandwidth
40086
     *        Current measured bandwidth of the player
40087
     * @param {Object} playlist
40088
     *        The media playlist object
40089
     * @param {number=} bytesReceived
40090
     *        Number of bytes already received for the request. Defaults to 0
40091
     * @return {number|NaN}
40092
     *         The estimated time to request the segment. NaN if bandwidth information for
40093
     *         the given playlist is unavailable
40094
     * @function estimateSegmentRequestTime
40095
     */
40096
 
40097
    const estimateSegmentRequestTime = function (segmentDuration, bandwidth, playlist, bytesReceived = 0) {
40098
        if (!hasAttribute('BANDWIDTH', playlist)) {
40099
            return NaN;
40100
        }
40101
        const size = segmentDuration * playlist.attributes.BANDWIDTH;
40102
        return (size - bytesReceived * 8) / bandwidth;
40103
    };
40104
    /*
40105
   * Returns whether the current playlist is the lowest rendition
40106
   *
40107
   * @return {Boolean} true if on lowest rendition
40108
   */
40109
 
40110
    const isLowestEnabledRendition = (main, media) => {
40111
        if (main.playlists.length === 1) {
40112
            return true;
40113
        }
40114
        const currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
40115
        return main.playlists.filter(playlist => {
40116
            if (!isEnabled(playlist)) {
40117
                return false;
40118
            }
40119
            return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
40120
        }).length === 0;
40121
    };
40122
    const playlistMatch = (a, b) => {
40123
        // both playlits are null
40124
        // or only one playlist is non-null
40125
        // no match
40126
        if (!a && !b || !a && b || a && !b) {
40127
            return false;
40128
        } // playlist objects are the same, match
40129
 
40130
        if (a === b) {
40131
            return true;
40132
        } // first try to use id as it should be the most
40133
        // accurate
40134
 
40135
        if (a.id && b.id && a.id === b.id) {
40136
            return true;
40137
        } // next try to use reslovedUri as it should be the
40138
        // second most accurate.
40139
 
40140
        if (a.resolvedUri && b.resolvedUri && a.resolvedUri === b.resolvedUri) {
40141
            return true;
40142
        } // finally try to use uri as it should be accurate
40143
        // but might miss a few cases for relative uris
40144
 
40145
        if (a.uri && b.uri && a.uri === b.uri) {
40146
            return true;
40147
        }
40148
        return false;
40149
    };
40150
    const someAudioVariant = function (main, callback) {
40151
        const AUDIO = main && main.mediaGroups && main.mediaGroups.AUDIO || {};
40152
        let found = false;
40153
        for (const groupName in AUDIO) {
40154
            for (const label in AUDIO[groupName]) {
40155
                found = callback(AUDIO[groupName][label]);
40156
                if (found) {
40157
                    break;
40158
                }
40159
            }
40160
            if (found) {
40161
                break;
40162
            }
40163
        }
40164
        return !!found;
40165
    };
40166
    const isAudioOnly = main => {
40167
        // we are audio only if we have no main playlists but do
40168
        // have media group playlists.
40169
        if (!main || !main.playlists || !main.playlists.length) {
40170
            // without audio variants or playlists this
40171
            // is not an audio only main.
40172
            const found = someAudioVariant(main, variant => variant.playlists && variant.playlists.length || variant.uri);
40173
            return found;
40174
        } // if every playlist has only an audio codec it is audio only
40175
 
40176
        for (let i = 0; i < main.playlists.length; i++) {
40177
            const playlist = main.playlists[i];
40178
            const CODECS = playlist.attributes && playlist.attributes.CODECS; // all codecs are audio, this is an audio playlist.
40179
 
40180
            if (CODECS && CODECS.split(',').every(c => isAudioCodec(c))) {
40181
                continue;
40182
            } // playlist is in an audio group it is audio only
40183
 
40184
            const found = someAudioVariant(main, variant => playlistMatch(playlist, variant));
40185
            if (found) {
40186
                continue;
40187
            } // if we make it here this playlist isn't audio and we
40188
            // are not audio only
40189
 
40190
            return false;
40191
        } // if we make it past every playlist without returning, then
40192
        // this is an audio only playlist.
40193
 
40194
        return true;
40195
    }; // exports
40196
 
40197
    var Playlist = {
40198
        liveEdgeDelay,
40199
        duration,
40200
        seekable,
40201
        getMediaInfoForTime,
40202
        isEnabled,
40203
        isDisabled,
40204
        isExcluded,
40205
        isIncompatible,
40206
        playlistEnd,
40207
        isAes,
40208
        hasAttribute,
40209
        estimateSegmentRequestTime,
40210
        isLowestEnabledRendition,
40211
        isAudioOnly,
40212
        playlistMatch,
40213
        segmentDurationWithParts
40214
    };
40215
    const {
40216
        log
40217
    } = videojs;
40218
    const createPlaylistID = (index, uri) => {
40219
        return `${index}-${uri}`;
40220
    }; // default function for creating a group id
40221
 
40222
    const groupID = (type, group, label) => {
40223
        return `placeholder-uri-${type}-${group}-${label}`;
40224
    };
40225
    /**
40226
     * Parses a given m3u8 playlist
40227
     *
40228
     * @param {Function} [onwarn]
40229
     *        a function to call when the parser triggers a warning event.
40230
     * @param {Function} [oninfo]
40231
     *        a function to call when the parser triggers an info event.
40232
     * @param {string} manifestString
40233
     *        The downloaded manifest string
40234
     * @param {Object[]} [customTagParsers]
40235
     *        An array of custom tag parsers for the m3u8-parser instance
40236
     * @param {Object[]} [customTagMappers]
40237
     *        An array of custom tag mappers for the m3u8-parser instance
40238
     * @param {boolean} [llhls]
40239
     *        Whether to keep ll-hls features in the manifest after parsing.
40240
     * @return {Object}
40241
     *         The manifest object
40242
     */
40243
 
40244
    const parseManifest = ({
40245
                               onwarn,
40246
                               oninfo,
40247
                               manifestString,
40248
                               customTagParsers = [],
40249
                               customTagMappers = [],
40250
                               llhls
40251
                           }) => {
40252
        const parser = new Parser();
40253
        if (onwarn) {
40254
            parser.on('warn', onwarn);
40255
        }
40256
        if (oninfo) {
40257
            parser.on('info', oninfo);
40258
        }
40259
        customTagParsers.forEach(customParser => parser.addParser(customParser));
40260
        customTagMappers.forEach(mapper => parser.addTagMapper(mapper));
40261
        parser.push(manifestString);
40262
        parser.end();
40263
        const manifest = parser.manifest; // remove llhls features from the parsed manifest
40264
        // if we don't want llhls support.
40265
 
40266
        if (!llhls) {
40267
            ['preloadSegment', 'skip', 'serverControl', 'renditionReports', 'partInf', 'partTargetDuration'].forEach(function (k) {
40268
                if (manifest.hasOwnProperty(k)) {
40269
                    delete manifest[k];
40270
                }
40271
            });
40272
            if (manifest.segments) {
40273
                manifest.segments.forEach(function (segment) {
40274
                    ['parts', 'preloadHints'].forEach(function (k) {
40275
                        if (segment.hasOwnProperty(k)) {
40276
                            delete segment[k];
40277
                        }
40278
                    });
40279
                });
40280
            }
40281
        }
40282
        if (!manifest.targetDuration) {
40283
            let targetDuration = 10;
40284
            if (manifest.segments && manifest.segments.length) {
40285
                targetDuration = manifest.segments.reduce((acc, s) => Math.max(acc, s.duration), 0);
40286
            }
40287
            if (onwarn) {
40288
                onwarn({
40289
                    message: `manifest has no targetDuration defaulting to ${targetDuration}`
40290
                });
40291
            }
40292
            manifest.targetDuration = targetDuration;
40293
        }
40294
        const parts = getLastParts(manifest);
40295
        if (parts.length && !manifest.partTargetDuration) {
40296
            const partTargetDuration = parts.reduce((acc, p) => Math.max(acc, p.duration), 0);
40297
            if (onwarn) {
40298
                onwarn({
40299
                    message: `manifest has no partTargetDuration defaulting to ${partTargetDuration}`
40300
                });
40301
                log.error('LL-HLS manifest has parts but lacks required #EXT-X-PART-INF:PART-TARGET value. See https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-09#section-4.4.3.7. Playback is not guaranteed.');
40302
            }
40303
            manifest.partTargetDuration = partTargetDuration;
40304
        }
40305
        return manifest;
40306
    };
40307
    /**
40308
     * Loops through all supported media groups in main and calls the provided
40309
     * callback for each group
40310
     *
40311
     * @param {Object} main
40312
     *        The parsed main manifest object
40313
     * @param {Function} callback
40314
     *        Callback to call for each media group
40315
     */
40316
 
40317
    const forEachMediaGroup = (main, callback) => {
40318
        if (!main.mediaGroups) {
40319
            return;
40320
        }
40321
        ['AUDIO', 'SUBTITLES'].forEach(mediaType => {
40322
            if (!main.mediaGroups[mediaType]) {
40323
                return;
40324
            }
40325
            for (const groupKey in main.mediaGroups[mediaType]) {
40326
                for (const labelKey in main.mediaGroups[mediaType][groupKey]) {
40327
                    const mediaProperties = main.mediaGroups[mediaType][groupKey][labelKey];
40328
                    callback(mediaProperties, mediaType, groupKey, labelKey);
40329
                }
40330
            }
40331
        });
40332
    };
40333
    /**
40334
     * Adds properties and attributes to the playlist to keep consistent functionality for
40335
     * playlists throughout VHS.
40336
     *
40337
     * @param {Object} config
40338
     *        Arguments object
40339
     * @param {Object} config.playlist
40340
     *        The media playlist
40341
     * @param {string} [config.uri]
40342
     *        The uri to the media playlist (if media playlist is not from within a main
40343
     *        playlist)
40344
     * @param {string} id
40345
     *        ID to use for the playlist
40346
     */
40347
 
40348
    const setupMediaPlaylist = ({
40349
                                    playlist,
40350
                                    uri,
40351
                                    id
40352
                                }) => {
40353
        playlist.id = id;
40354
        playlist.playlistErrors_ = 0;
40355
        if (uri) {
40356
            // For media playlists, m3u8-parser does not have access to a URI, as HLS media
40357
            // playlists do not contain their own source URI, but one is needed for consistency in
40358
            // VHS.
40359
            playlist.uri = uri;
40360
        } // For HLS main playlists, even though certain attributes MUST be defined, the
40361
        // stream may still be played without them.
40362
        // For HLS media playlists, m3u8-parser does not attach an attributes object to the
40363
        // manifest.
40364
        //
40365
        // To avoid undefined reference errors through the project, and make the code easier
40366
        // to write/read, add an empty attributes object for these cases.
40367
 
40368
        playlist.attributes = playlist.attributes || {};
40369
    };
40370
    /**
40371
     * Adds ID, resolvedUri, and attributes properties to each playlist of the main, where
40372
     * necessary. In addition, creates playlist IDs for each playlist and adds playlist ID to
40373
     * playlist references to the playlists array.
40374
     *
40375
     * @param {Object} main
40376
     *        The main playlist
40377
     */
40378
 
40379
    const setupMediaPlaylists = main => {
40380
        let i = main.playlists.length;
40381
        while (i--) {
40382
            const playlist = main.playlists[i];
40383
            setupMediaPlaylist({
40384
                playlist,
40385
                id: createPlaylistID(i, playlist.uri)
40386
            });
40387
            playlist.resolvedUri = resolveUrl(main.uri, playlist.uri);
40388
            main.playlists[playlist.id] = playlist; // URI reference added for backwards compatibility
40389
 
40390
            main.playlists[playlist.uri] = playlist; // Although the spec states an #EXT-X-STREAM-INF tag MUST have a BANDWIDTH attribute,
40391
            // the stream can be played without it. Although an attributes property may have been
40392
            // added to the playlist to prevent undefined references, issue a warning to fix the
40393
            // manifest.
40394
 
40395
            if (!playlist.attributes.BANDWIDTH) {
40396
                log.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
40397
            }
40398
        }
40399
    };
40400
    /**
40401
     * Adds resolvedUri properties to each media group.
40402
     *
40403
     * @param {Object} main
40404
     *        The main playlist
40405
     */
40406
 
40407
    const resolveMediaGroupUris = main => {
40408
        forEachMediaGroup(main, properties => {
40409
            if (properties.uri) {
40410
                properties.resolvedUri = resolveUrl(main.uri, properties.uri);
40411
            }
40412
        });
40413
    };
40414
    /**
40415
     * Creates a main playlist wrapper to insert a sole media playlist into.
40416
     *
40417
     * @param {Object} media
40418
     *        Media playlist
40419
     * @param {string} uri
40420
     *        The media URI
40421
     *
40422
     * @return {Object}
40423
     *         main playlist
40424
     */
40425
 
40426
    const mainForMedia = (media, uri) => {
40427
        const id = createPlaylistID(0, uri);
40428
        const main = {
40429
            mediaGroups: {
40430
                'AUDIO': {},
40431
                'VIDEO': {},
40432
                'CLOSED-CAPTIONS': {},
40433
                'SUBTITLES': {}
40434
            },
40435
            uri: window.location.href,
40436
            resolvedUri: window.location.href,
40437
            playlists: [{
40438
                uri,
40439
                id,
40440
                resolvedUri: uri,
40441
                // m3u8-parser does not attach an attributes property to media playlists so make
40442
                // sure that the property is attached to avoid undefined reference errors
40443
                attributes: {}
40444
            }]
40445
        }; // set up ID reference
40446
 
40447
        main.playlists[id] = main.playlists[0]; // URI reference added for backwards compatibility
40448
 
40449
        main.playlists[uri] = main.playlists[0];
40450
        return main;
40451
    };
40452
    /**
40453
     * Does an in-place update of the main manifest to add updated playlist URI references
40454
     * as well as other properties needed by VHS that aren't included by the parser.
40455
     *
40456
     * @param {Object} main
40457
     *        main manifest object
40458
     * @param {string} uri
40459
     *        The source URI
40460
     * @param {function} createGroupID
40461
     *        A function to determine how to create the groupID for mediaGroups
40462
     */
40463
 
40464
    const addPropertiesToMain = (main, uri, createGroupID = groupID) => {
40465
        main.uri = uri;
40466
        for (let i = 0; i < main.playlists.length; i++) {
40467
            if (!main.playlists[i].uri) {
40468
                // Set up phony URIs for the playlists since playlists are referenced by their URIs
40469
                // throughout VHS, but some formats (e.g., DASH) don't have external URIs
40470
                // TODO: consider adding dummy URIs in mpd-parser
40471
                const phonyUri = `placeholder-uri-${i}`;
40472
                main.playlists[i].uri = phonyUri;
40473
            }
40474
        }
40475
        const audioOnlyMain = isAudioOnly(main);
40476
        forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
40477
            // add a playlist array under properties
40478
            if (!properties.playlists || !properties.playlists.length) {
40479
                // If the manifest is audio only and this media group does not have a uri, check
40480
                // if the media group is located in the main list of playlists. If it is, don't add
40481
                // placeholder properties as it shouldn't be considered an alternate audio track.
40482
                if (audioOnlyMain && mediaType === 'AUDIO' && !properties.uri) {
40483
                    for (let i = 0; i < main.playlists.length; i++) {
40484
                        const p = main.playlists[i];
40485
                        if (p.attributes && p.attributes.AUDIO && p.attributes.AUDIO === groupKey) {
40486
                            return;
40487
                        }
40488
                    }
40489
                }
40490
                properties.playlists = [_extends$1({}, properties)];
40491
            }
40492
            properties.playlists.forEach(function (p, i) {
40493
                const groupId = createGroupID(mediaType, groupKey, labelKey, p);
40494
                const id = createPlaylistID(i, groupId);
40495
                if (p.uri) {
40496
                    p.resolvedUri = p.resolvedUri || resolveUrl(main.uri, p.uri);
40497
                } else {
40498
                    // DEPRECATED, this has been added to prevent a breaking change.
40499
                    // previously we only ever had a single media group playlist, so
40500
                    // we mark the first playlist uri without prepending the index as we used to
40501
                    // ideally we would do all of the playlists the same way.
40502
                    p.uri = i === 0 ? groupId : id; // don't resolve a placeholder uri to an absolute url, just use
40503
                    // the placeholder again
40504
 
40505
                    p.resolvedUri = p.uri;
40506
                }
40507
                p.id = p.id || id; // add an empty attributes object, all playlists are
40508
                // expected to have this.
40509
 
40510
                p.attributes = p.attributes || {}; // setup ID and URI references (URI for backwards compatibility)
40511
 
40512
                main.playlists[p.id] = p;
40513
                main.playlists[p.uri] = p;
40514
            });
40515
        });
40516
        setupMediaPlaylists(main);
40517
        resolveMediaGroupUris(main);
40518
    };
40519
    class DateRangesStorage {
40520
        constructor() {
40521
            this.offset_ = null;
40522
            this.pendingDateRanges_ = new Map();
40523
            this.processedDateRanges_ = new Map();
40524
        }
40525
        setOffset(segments = []) {
40526
            // already set
40527
            if (this.offset_ !== null) {
40528
                return;
40529
            } // no segment to process
40530
 
40531
            if (!segments.length) {
40532
                return;
40533
            }
40534
            const [firstSegment] = segments; // no program date time
40535
 
40536
            if (firstSegment.programDateTime === undefined) {
40537
                return;
40538
            } // Set offset as ProgramDateTime for the very first segment of the very first playlist load:
40539
 
40540
            this.offset_ = firstSegment.programDateTime / 1000;
40541
        }
40542
        setPendingDateRanges(dateRanges = []) {
40543
            if (!dateRanges.length) {
40544
                return;
40545
            }
40546
            const [dateRange] = dateRanges;
40547
            const startTime = dateRange.startDate.getTime();
40548
            this.trimProcessedDateRanges_(startTime);
40549
            this.pendingDateRanges_ = dateRanges.reduce((map, pendingDateRange) => {
40550
                map.set(pendingDateRange.id, pendingDateRange);
40551
                return map;
40552
            }, new Map());
40553
        }
40554
        processDateRange(dateRange) {
40555
            this.pendingDateRanges_.delete(dateRange.id);
40556
            this.processedDateRanges_.set(dateRange.id, dateRange);
40557
        }
40558
        getDateRangesToProcess() {
40559
            if (this.offset_ === null) {
40560
                return [];
40561
            }
40562
            const dateRangeClasses = {};
40563
            const dateRangesToProcess = [];
40564
            this.pendingDateRanges_.forEach((dateRange, id) => {
40565
                if (this.processedDateRanges_.has(id)) {
40566
                    return;
40567
                }
40568
                dateRange.startTime = dateRange.startDate.getTime() / 1000 - this.offset_;
40569
                dateRange.processDateRange = () => this.processDateRange(dateRange);
40570
                dateRangesToProcess.push(dateRange);
40571
                if (!dateRange.class) {
40572
                    return;
40573
                }
40574
                if (dateRangeClasses[dateRange.class]) {
40575
                    const length = dateRangeClasses[dateRange.class].push(dateRange);
40576
                    dateRange.classListIndex = length - 1;
40577
                } else {
40578
                    dateRangeClasses[dateRange.class] = [dateRange];
40579
                    dateRange.classListIndex = 0;
40580
                }
40581
            });
40582
            for (const dateRange of dateRangesToProcess) {
40583
                const classList = dateRangeClasses[dateRange.class] || [];
40584
                if (dateRange.endDate) {
40585
                    dateRange.endTime = dateRange.endDate.getTime() / 1000 - this.offset_;
40586
                } else if (dateRange.endOnNext && classList[dateRange.classListIndex + 1]) {
40587
                    dateRange.endTime = classList[dateRange.classListIndex + 1].startTime;
40588
                } else if (dateRange.duration) {
40589
                    dateRange.endTime = dateRange.startTime + dateRange.duration;
40590
                } else if (dateRange.plannedDuration) {
40591
                    dateRange.endTime = dateRange.startTime + dateRange.plannedDuration;
40592
                } else {
40593
                    dateRange.endTime = dateRange.startTime;
40594
                }
40595
            }
40596
            return dateRangesToProcess;
40597
        }
40598
        trimProcessedDateRanges_(startTime) {
40599
            const copy = new Map(this.processedDateRanges_);
40600
            copy.forEach((dateRange, id) => {
40601
                if (dateRange.startDate.getTime() < startTime) {
40602
                    this.processedDateRanges_.delete(id);
40603
                }
40604
            });
40605
        }
40606
    }
40607
    const {
40608
        EventTarget: EventTarget$1
40609
    } = videojs;
40610
    const addLLHLSQueryDirectives = (uri, media) => {
40611
        if (media.endList || !media.serverControl) {
40612
            return uri;
40613
        }
40614
        const parameters = {};
40615
        if (media.serverControl.canBlockReload) {
40616
            const {
40617
                preloadSegment
40618
            } = media; // next msn is a zero based value, length is not.
40619
 
40620
            let nextMSN = media.mediaSequence + media.segments.length; // If preload segment has parts then it is likely
40621
            // that we are going to request a part of that preload segment.
40622
            // the logic below is used to determine that.
40623
 
40624
            if (preloadSegment) {
40625
                const parts = preloadSegment.parts || []; // _HLS_part is a zero based index
40626
 
40627
                const nextPart = getKnownPartCount(media) - 1; // if nextPart is > -1 and not equal to just the
40628
                // length of parts, then we know we had part preload hints
40629
                // and we need to add the _HLS_part= query
40630
 
40631
                if (nextPart > -1 && nextPart !== parts.length - 1) {
40632
                    // add existing parts to our preload hints
40633
                    // eslint-disable-next-line
40634
                    parameters._HLS_part = nextPart;
40635
                } // this if statement makes sure that we request the msn
40636
                // of the preload segment if:
40637
                // 1. the preload segment had parts (and was not yet a full segment)
40638
                //    but was added to our segments array
40639
                // 2. the preload segment had preload hints for parts that are not in
40640
                //    the manifest yet.
40641
                // in all other cases we want the segment after the preload segment
40642
                // which will be given by using media.segments.length because it is 1 based
40643
                // rather than 0 based.
40644
 
40645
                if (nextPart > -1 || parts.length) {
40646
                    nextMSN--;
40647
                }
40648
            } // add _HLS_msn= in front of any _HLS_part query
40649
            // eslint-disable-next-line
40650
 
40651
            parameters._HLS_msn = nextMSN;
40652
        }
40653
        if (media.serverControl && media.serverControl.canSkipUntil) {
40654
            // add _HLS_skip= infront of all other queries.
40655
            // eslint-disable-next-line
40656
            parameters._HLS_skip = media.serverControl.canSkipDateranges ? 'v2' : 'YES';
40657
        }
40658
        if (Object.keys(parameters).length) {
40659
            const parsedUri = new window.URL(uri);
40660
            ['_HLS_skip', '_HLS_msn', '_HLS_part'].forEach(function (name) {
40661
                if (!parameters.hasOwnProperty(name)) {
40662
                    return;
40663
                }
40664
                parsedUri.searchParams.set(name, parameters[name]);
40665
            });
40666
            uri = parsedUri.toString();
40667
        }
40668
        return uri;
40669
    };
40670
    /**
40671
     * Returns a new segment object with properties and
40672
     * the parts array merged.
40673
     *
40674
     * @param {Object} a the old segment
40675
     * @param {Object} b the new segment
40676
     *
40677
     * @return {Object} the merged segment
40678
     */
40679
 
40680
    const updateSegment = (a, b) => {
40681
        if (!a) {
40682
            return b;
40683
        }
40684
        const result = merge(a, b); // if only the old segment has preload hints
40685
        // and the new one does not, remove preload hints.
40686
 
40687
        if (a.preloadHints && !b.preloadHints) {
40688
            delete result.preloadHints;
40689
        } // if only the old segment has parts
40690
        // then the parts are no longer valid
40691
 
40692
        if (a.parts && !b.parts) {
40693
            delete result.parts; // if both segments have parts
40694
            // copy part propeties from the old segment
40695
            // to the new one.
40696
        } else if (a.parts && b.parts) {
40697
            for (let i = 0; i < b.parts.length; i++) {
40698
                if (a.parts && a.parts[i]) {
40699
                    result.parts[i] = merge(a.parts[i], b.parts[i]);
40700
                }
40701
            }
40702
        } // set skipped to false for segments that have
40703
        // have had information merged from the old segment.
40704
 
40705
        if (!a.skipped && b.skipped) {
40706
            result.skipped = false;
40707
        } // set preload to false for segments that have
40708
        // had information added in the new segment.
40709
 
40710
        if (a.preload && !b.preload) {
40711
            result.preload = false;
40712
        }
40713
        return result;
40714
    };
40715
    /**
40716
     * Returns a new array of segments that is the result of merging
40717
     * properties from an older list of segments onto an updated
40718
     * list. No properties on the updated playlist will be ovewritten.
40719
     *
40720
     * @param {Array} original the outdated list of segments
40721
     * @param {Array} update the updated list of segments
40722
     * @param {number=} offset the index of the first update
40723
     * segment in the original segment list. For non-live playlists,
40724
     * this should always be zero and does not need to be
40725
     * specified. For live playlists, it should be the difference
40726
     * between the media sequence numbers in the original and updated
40727
     * playlists.
40728
     * @return {Array} a list of merged segment objects
40729
     */
40730
 
40731
    const updateSegments = (original, update, offset) => {
40732
        const oldSegments = original.slice();
40733
        const newSegments = update.slice();
40734
        offset = offset || 0;
40735
        const result = [];
40736
        let currentMap;
40737
        for (let newIndex = 0; newIndex < newSegments.length; newIndex++) {
40738
            const oldSegment = oldSegments[newIndex + offset];
40739
            const newSegment = newSegments[newIndex];
40740
            if (oldSegment) {
40741
                currentMap = oldSegment.map || currentMap;
40742
                result.push(updateSegment(oldSegment, newSegment));
40743
            } else {
40744
                // carry over map to new segment if it is missing
40745
                if (currentMap && !newSegment.map) {
40746
                    newSegment.map = currentMap;
40747
                }
40748
                result.push(newSegment);
40749
            }
40750
        }
40751
        return result;
40752
    };
40753
    const resolveSegmentUris = (segment, baseUri) => {
40754
        // preloadSegment will not have a uri at all
40755
        // as the segment isn't actually in the manifest yet, only parts
40756
        if (!segment.resolvedUri && segment.uri) {
40757
            segment.resolvedUri = resolveUrl(baseUri, segment.uri);
40758
        }
40759
        if (segment.key && !segment.key.resolvedUri) {
40760
            segment.key.resolvedUri = resolveUrl(baseUri, segment.key.uri);
40761
        }
40762
        if (segment.map && !segment.map.resolvedUri) {
40763
            segment.map.resolvedUri = resolveUrl(baseUri, segment.map.uri);
40764
        }
40765
        if (segment.map && segment.map.key && !segment.map.key.resolvedUri) {
40766
            segment.map.key.resolvedUri = resolveUrl(baseUri, segment.map.key.uri);
40767
        }
40768
        if (segment.parts && segment.parts.length) {
40769
            segment.parts.forEach(p => {
40770
                if (p.resolvedUri) {
40771
                    return;
40772
                }
40773
                p.resolvedUri = resolveUrl(baseUri, p.uri);
40774
            });
40775
        }
40776
        if (segment.preloadHints && segment.preloadHints.length) {
40777
            segment.preloadHints.forEach(p => {
40778
                if (p.resolvedUri) {
40779
                    return;
40780
                }
40781
                p.resolvedUri = resolveUrl(baseUri, p.uri);
40782
            });
40783
        }
40784
    };
40785
    const getAllSegments = function (media) {
40786
        const segments = media.segments || [];
40787
        const preloadSegment = media.preloadSegment; // a preloadSegment with only preloadHints is not currently
40788
        // a usable segment, only include a preloadSegment that has
40789
        // parts.
40790
 
40791
        if (preloadSegment && preloadSegment.parts && preloadSegment.parts.length) {
40792
            // if preloadHints has a MAP that means that the
40793
            // init segment is going to change. We cannot use any of the parts
40794
            // from this preload segment.
40795
            if (preloadSegment.preloadHints) {
40796
                for (let i = 0; i < preloadSegment.preloadHints.length; i++) {
40797
                    if (preloadSegment.preloadHints[i].type === 'MAP') {
40798
                        return segments;
40799
                    }
40800
                }
40801
            } // set the duration for our preload segment to target duration.
40802
 
40803
            preloadSegment.duration = media.targetDuration;
40804
            preloadSegment.preload = true;
40805
            segments.push(preloadSegment);
40806
        }
40807
        return segments;
40808
    }; // consider the playlist unchanged if the playlist object is the same or
40809
    // the number of segments is equal, the media sequence number is unchanged,
40810
    // and this playlist hasn't become the end of the playlist
40811
 
40812
    const isPlaylistUnchanged = (a, b) => a === b || a.segments && b.segments && a.segments.length === b.segments.length && a.endList === b.endList && a.mediaSequence === b.mediaSequence && a.preloadSegment === b.preloadSegment;
40813
    /**
40814
     * Returns a new main playlist that is the result of merging an
40815
     * updated media playlist into the original version. If the
40816
     * updated media playlist does not match any of the playlist
40817
     * entries in the original main playlist, null is returned.
40818
     *
40819
     * @param {Object} main a parsed main M3U8 object
40820
     * @param {Object} media a parsed media M3U8 object
40821
     * @return {Object} a new object that represents the original
40822
     * main playlist with the updated media playlist merged in, or
40823
     * null if the merge produced no change.
40824
     */
40825
 
40826
    const updateMain$1 = (main, newMedia, unchangedCheck = isPlaylistUnchanged) => {
40827
        const result = merge(main, {});
40828
        const oldMedia = result.playlists[newMedia.id];
40829
        if (!oldMedia) {
40830
            return null;
40831
        }
40832
        if (unchangedCheck(oldMedia, newMedia)) {
40833
            return null;
40834
        }
40835
        newMedia.segments = getAllSegments(newMedia);
40836
        const mergedPlaylist = merge(oldMedia, newMedia); // always use the new media's preload segment
40837
 
40838
        if (mergedPlaylist.preloadSegment && !newMedia.preloadSegment) {
40839
            delete mergedPlaylist.preloadSegment;
40840
        } // if the update could overlap existing segment information, merge the two segment lists
40841
 
40842
        if (oldMedia.segments) {
40843
            if (newMedia.skip) {
40844
                newMedia.segments = newMedia.segments || []; // add back in objects for skipped segments, so that we merge
40845
                // old properties into the new segments
40846
 
40847
                for (let i = 0; i < newMedia.skip.skippedSegments; i++) {
40848
                    newMedia.segments.unshift({
40849
                        skipped: true
40850
                    });
40851
                }
40852
            }
40853
            mergedPlaylist.segments = updateSegments(oldMedia.segments, newMedia.segments, newMedia.mediaSequence - oldMedia.mediaSequence);
40854
        } // resolve any segment URIs to prevent us from having to do it later
40855
 
40856
        mergedPlaylist.segments.forEach(segment => {
40857
            resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
40858
        }); // TODO Right now in the playlists array there are two references to each playlist, one
40859
        // that is referenced by index, and one by URI. The index reference may no longer be
40860
        // necessary.
40861
 
40862
        for (let i = 0; i < result.playlists.length; i++) {
40863
            if (result.playlists[i].id === newMedia.id) {
40864
                result.playlists[i] = mergedPlaylist;
40865
            }
40866
        }
40867
        result.playlists[newMedia.id] = mergedPlaylist; // URI reference added for backwards compatibility
40868
 
40869
        result.playlists[newMedia.uri] = mergedPlaylist; // update media group playlist references.
40870
 
40871
        forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
40872
            if (!properties.playlists) {
40873
                return;
40874
            }
40875
            for (let i = 0; i < properties.playlists.length; i++) {
40876
                if (newMedia.id === properties.playlists[i].id) {
40877
                    properties.playlists[i] = mergedPlaylist;
40878
                }
40879
            }
40880
        });
40881
        return result;
40882
    };
40883
    /**
40884
     * Calculates the time to wait before refreshing a live playlist
40885
     *
40886
     * @param {Object} media
40887
     *        The current media
40888
     * @param {boolean} update
40889
     *        True if there were any updates from the last refresh, false otherwise
40890
     * @return {number}
40891
     *         The time in ms to wait before refreshing the live playlist
40892
     */
40893
 
40894
    const refreshDelay = (media, update) => {
40895
        const segments = media.segments || [];
40896
        const lastSegment = segments[segments.length - 1];
40897
        const lastPart = lastSegment && lastSegment.parts && lastSegment.parts[lastSegment.parts.length - 1];
40898
        const lastDuration = lastPart && lastPart.duration || lastSegment && lastSegment.duration;
40899
        if (update && lastDuration) {
40900
            return lastDuration * 1000;
40901
        } // if the playlist is unchanged since the last reload or last segment duration
40902
        // cannot be determined, try again after half the target duration
40903
 
40904
        return (media.partTargetDuration || media.targetDuration || 10) * 500;
40905
    };
40906
    /**
40907
     * Load a playlist from a remote location
40908
     *
40909
     * @class PlaylistLoader
40910
     * @extends Stream
40911
     * @param {string|Object} src url or object of manifest
40912
     * @param {boolean} withCredentials the withCredentials xhr option
40913
     * @class
40914
     */
40915
 
40916
    class PlaylistLoader extends EventTarget$1 {
40917
        constructor(src, vhs, options = {}) {
40918
            super();
40919
            if (!src) {
40920
                throw new Error('A non-empty playlist URL or object is required');
40921
            }
40922
            this.logger_ = logger('PlaylistLoader');
40923
            const {
40924
                withCredentials = false
40925
            } = options;
40926
            this.src = src;
40927
            this.vhs_ = vhs;
40928
            this.withCredentials = withCredentials;
40929
            this.addDateRangesToTextTrack_ = options.addDateRangesToTextTrack;
40930
            const vhsOptions = vhs.options_;
40931
            this.customTagParsers = vhsOptions && vhsOptions.customTagParsers || [];
40932
            this.customTagMappers = vhsOptions && vhsOptions.customTagMappers || [];
40933
            this.llhls = vhsOptions && vhsOptions.llhls;
40934
            this.dateRangesStorage_ = new DateRangesStorage(); // initialize the loader state
40935
 
40936
            this.state = 'HAVE_NOTHING'; // live playlist staleness timeout
40937
 
40938
            this.handleMediaupdatetimeout_ = this.handleMediaupdatetimeout_.bind(this);
40939
            this.on('mediaupdatetimeout', this.handleMediaupdatetimeout_);
40940
            this.on('loadedplaylist', this.handleLoadedPlaylist_.bind(this));
40941
        }
40942
        handleLoadedPlaylist_() {
40943
            const mediaPlaylist = this.media();
40944
            if (!mediaPlaylist) {
40945
                return;
40946
            }
40947
            this.dateRangesStorage_.setOffset(mediaPlaylist.segments);
40948
            this.dateRangesStorage_.setPendingDateRanges(mediaPlaylist.dateRanges);
40949
            const availableDateRanges = this.dateRangesStorage_.getDateRangesToProcess();
40950
            if (!availableDateRanges.length || !this.addDateRangesToTextTrack_) {
40951
                return;
40952
            }
40953
            this.addDateRangesToTextTrack_(availableDateRanges);
40954
        }
40955
        handleMediaupdatetimeout_() {
40956
            if (this.state !== 'HAVE_METADATA') {
40957
                // only refresh the media playlist if no other activity is going on
40958
                return;
40959
            }
40960
            const media = this.media();
40961
            let uri = resolveUrl(this.main.uri, media.uri);
40962
            if (this.llhls) {
40963
                uri = addLLHLSQueryDirectives(uri, media);
40964
            }
40965
            this.state = 'HAVE_CURRENT_METADATA';
40966
            this.request = this.vhs_.xhr({
40967
                uri,
40968
                withCredentials: this.withCredentials
40969
            }, (error, req) => {
40970
                // disposed
40971
                if (!this.request) {
40972
                    return;
40973
                }
40974
                if (error) {
40975
                    return this.playlistRequestError(this.request, this.media(), 'HAVE_METADATA');
40976
                }
40977
                this.haveMetadata({
40978
                    playlistString: this.request.responseText,
40979
                    url: this.media().uri,
40980
                    id: this.media().id
40981
                });
40982
            });
40983
        }
40984
        playlistRequestError(xhr, playlist, startingState) {
40985
            const {
40986
                uri,
40987
                id
40988
            } = playlist; // any in-flight request is now finished
40989
 
40990
            this.request = null;
40991
            if (startingState) {
40992
                this.state = startingState;
40993
            }
40994
            this.error = {
40995
                playlist: this.main.playlists[id],
40996
                status: xhr.status,
40997
                message: `HLS playlist request error at URL: ${uri}.`,
40998
                responseText: xhr.responseText,
40999
                code: xhr.status >= 500 ? 4 : 2
41000
            };
41001
            this.trigger('error');
41002
        }
41003
        parseManifest_({
41004
                           url,
41005
                           manifestString
41006
                       }) {
41007
            return parseManifest({
41008
                onwarn: ({
41009
                             message
41010
                         }) => this.logger_(`m3u8-parser warn for ${url}: ${message}`),
41011
                oninfo: ({
41012
                             message
41013
                         }) => this.logger_(`m3u8-parser info for ${url}: ${message}`),
41014
                manifestString,
41015
                customTagParsers: this.customTagParsers,
41016
                customTagMappers: this.customTagMappers,
41017
                llhls: this.llhls
41018
            });
41019
        }
41020
        /**
41021
         * Update the playlist loader's state in response to a new or updated playlist.
41022
         *
41023
         * @param {string} [playlistString]
41024
         *        Playlist string (if playlistObject is not provided)
41025
         * @param {Object} [playlistObject]
41026
         *        Playlist object (if playlistString is not provided)
41027
         * @param {string} url
41028
         *        URL of playlist
41029
         * @param {string} id
41030
         *        ID to use for playlist
41031
         */
41032
 
41033
        haveMetadata({
41034
                         playlistString,
41035
                         playlistObject,
41036
                         url,
41037
                         id
41038
                     }) {
41039
            // any in-flight request is now finished
41040
            this.request = null;
41041
            this.state = 'HAVE_METADATA';
41042
            const playlist = playlistObject || this.parseManifest_({
41043
                url,
41044
                manifestString: playlistString
41045
            });
41046
            playlist.lastRequest = Date.now();
41047
            setupMediaPlaylist({
41048
                playlist,
41049
                uri: url,
41050
                id
41051
            }); // merge this playlist into the main manifest
41052
 
41053
            const update = updateMain$1(this.main, playlist);
41054
            this.targetDuration = playlist.partTargetDuration || playlist.targetDuration;
41055
            this.pendingMedia_ = null;
41056
            if (update) {
41057
                this.main = update;
41058
                this.media_ = this.main.playlists[id];
41059
            } else {
41060
                this.trigger('playlistunchanged');
41061
            }
41062
            this.updateMediaUpdateTimeout_(refreshDelay(this.media(), !!update));
41063
            this.trigger('loadedplaylist');
41064
        }
41065
        /**
41066
         * Abort any outstanding work and clean up.
41067
         */
41068
 
41069
        dispose() {
41070
            this.trigger('dispose');
41071
            this.stopRequest();
41072
            window.clearTimeout(this.mediaUpdateTimeout);
41073
            window.clearTimeout(this.finalRenditionTimeout);
41074
            this.dateRangesStorage_ = new DateRangesStorage();
41075
            this.off();
41076
        }
41077
        stopRequest() {
41078
            if (this.request) {
41079
                const oldRequest = this.request;
41080
                this.request = null;
41081
                oldRequest.onreadystatechange = null;
41082
                oldRequest.abort();
41083
            }
41084
        }
41085
        /**
41086
         * When called without any arguments, returns the currently
41087
         * active media playlist. When called with a single argument,
41088
         * triggers the playlist loader to asynchronously switch to the
41089
         * specified media playlist. Calling this method while the
41090
         * loader is in the HAVE_NOTHING causes an error to be emitted
41091
         * but otherwise has no effect.
41092
         *
41093
         * @param {Object=} playlist the parsed media playlist
41094
         * object to switch to
41095
         * @param {boolean=} shouldDelay whether we should delay the request by half target duration
41096
         *
41097
         * @return {Playlist} the current loaded media
41098
         */
41099
 
41100
        media(playlist, shouldDelay) {
41101
            // getter
41102
            if (!playlist) {
41103
                return this.media_;
41104
            } // setter
41105
 
41106
            if (this.state === 'HAVE_NOTHING') {
41107
                throw new Error('Cannot switch media playlist from ' + this.state);
41108
            } // find the playlist object if the target playlist has been
41109
            // specified by URI
41110
 
41111
            if (typeof playlist === 'string') {
41112
                if (!this.main.playlists[playlist]) {
41113
                    throw new Error('Unknown playlist URI: ' + playlist);
41114
                }
41115
                playlist = this.main.playlists[playlist];
41116
            }
41117
            window.clearTimeout(this.finalRenditionTimeout);
41118
            if (shouldDelay) {
41119
                const delay = (playlist.partTargetDuration || playlist.targetDuration) / 2 * 1000 || 5 * 1000;
41120
                this.finalRenditionTimeout = window.setTimeout(this.media.bind(this, playlist, false), delay);
41121
                return;
41122
            }
41123
            const startingState = this.state;
41124
            const mediaChange = !this.media_ || playlist.id !== this.media_.id;
41125
            const mainPlaylistRef = this.main.playlists[playlist.id]; // switch to fully loaded playlists immediately
41126
 
41127
            if (mainPlaylistRef && mainPlaylistRef.endList ||
41128
                // handle the case of a playlist object (e.g., if using vhs-json with a resolved
41129
                // media playlist or, for the case of demuxed audio, a resolved audio media group)
41130
                playlist.endList && playlist.segments.length) {
41131
                // abort outstanding playlist requests
41132
                if (this.request) {
41133
                    this.request.onreadystatechange = null;
41134
                    this.request.abort();
41135
                    this.request = null;
41136
                }
41137
                this.state = 'HAVE_METADATA';
41138
                this.media_ = playlist; // trigger media change if the active media has been updated
41139
 
41140
                if (mediaChange) {
41141
                    this.trigger('mediachanging');
41142
                    if (startingState === 'HAVE_MAIN_MANIFEST') {
41143
                        // The initial playlist was a main manifest, and the first media selected was
41144
                        // also provided (in the form of a resolved playlist object) as part of the
41145
                        // source object (rather than just a URL). Therefore, since the media playlist
41146
                        // doesn't need to be requested, loadedmetadata won't trigger as part of the
41147
                        // normal flow, and needs an explicit trigger here.
41148
                        this.trigger('loadedmetadata');
41149
                    } else {
41150
                        this.trigger('mediachange');
41151
                    }
41152
                }
41153
                return;
41154
            } // We update/set the timeout here so that live playlists
41155
            // that are not a media change will "start" the loader as expected.
41156
            // We expect that this function will start the media update timeout
41157
            // cycle again. This also prevents a playlist switch failure from
41158
            // causing us to stall during live.
41159
 
41160
            this.updateMediaUpdateTimeout_(refreshDelay(playlist, true)); // switching to the active playlist is a no-op
41161
 
41162
            if (!mediaChange) {
41163
                return;
41164
            }
41165
            this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request
41166
 
41167
            if (this.request) {
41168
                if (playlist.resolvedUri === this.request.url) {
41169
                    // requesting to switch to the same playlist multiple times
41170
                    // has no effect after the first
41171
                    return;
41172
                }
41173
                this.request.onreadystatechange = null;
41174
                this.request.abort();
41175
                this.request = null;
41176
            } // request the new playlist
41177
 
41178
            if (this.media_) {
41179
                this.trigger('mediachanging');
41180
            }
41181
            this.pendingMedia_ = playlist;
41182
            this.request = this.vhs_.xhr({
41183
                uri: playlist.resolvedUri,
41184
                withCredentials: this.withCredentials
41185
            }, (error, req) => {
41186
                // disposed
41187
                if (!this.request) {
41188
                    return;
41189
                }
41190
                playlist.lastRequest = Date.now();
41191
                playlist.resolvedUri = resolveManifestRedirect(playlist.resolvedUri, req);
41192
                if (error) {
41193
                    return this.playlistRequestError(this.request, playlist, startingState);
41194
                }
41195
                this.haveMetadata({
41196
                    playlistString: req.responseText,
41197
                    url: playlist.uri,
41198
                    id: playlist.id
41199
                }); // fire loadedmetadata the first time a media playlist is loaded
41200
 
41201
                if (startingState === 'HAVE_MAIN_MANIFEST') {
41202
                    this.trigger('loadedmetadata');
41203
                } else {
41204
                    this.trigger('mediachange');
41205
                }
41206
            });
41207
        }
41208
        /**
41209
         * pause loading of the playlist
41210
         */
41211
 
41212
        pause() {
41213
            if (this.mediaUpdateTimeout) {
41214
                window.clearTimeout(this.mediaUpdateTimeout);
41215
                this.mediaUpdateTimeout = null;
41216
            }
41217
            this.stopRequest();
41218
            if (this.state === 'HAVE_NOTHING') {
41219
                // If we pause the loader before any data has been retrieved, its as if we never
41220
                // started, so reset to an unstarted state.
41221
                this.started = false;
41222
            } // Need to restore state now that no activity is happening
41223
 
41224
            if (this.state === 'SWITCHING_MEDIA') {
41225
                // if the loader was in the process of switching media, it should either return to
41226
                // HAVE_MAIN_MANIFEST or HAVE_METADATA depending on if the loader has loaded a media
41227
                // playlist yet. This is determined by the existence of loader.media_
41228
                if (this.media_) {
41229
                    this.state = 'HAVE_METADATA';
41230
                } else {
41231
                    this.state = 'HAVE_MAIN_MANIFEST';
41232
                }
41233
            } else if (this.state === 'HAVE_CURRENT_METADATA') {
41234
                this.state = 'HAVE_METADATA';
41235
            }
41236
        }
41237
        /**
41238
         * start loading of the playlist
41239
         */
41240
 
41241
        load(shouldDelay) {
41242
            if (this.mediaUpdateTimeout) {
41243
                window.clearTimeout(this.mediaUpdateTimeout);
41244
                this.mediaUpdateTimeout = null;
41245
            }
41246
            const media = this.media();
41247
            if (shouldDelay) {
41248
                const delay = media ? (media.partTargetDuration || media.targetDuration) / 2 * 1000 : 5 * 1000;
41249
                this.mediaUpdateTimeout = window.setTimeout(() => {
41250
                    this.mediaUpdateTimeout = null;
41251
                    this.load();
41252
                }, delay);
41253
                return;
41254
            }
41255
            if (!this.started) {
41256
                this.start();
41257
                return;
41258
            }
41259
            if (media && !media.endList) {
41260
                this.trigger('mediaupdatetimeout');
41261
            } else {
41262
                this.trigger('loadedplaylist');
41263
            }
41264
        }
41265
        updateMediaUpdateTimeout_(delay) {
41266
            if (this.mediaUpdateTimeout) {
41267
                window.clearTimeout(this.mediaUpdateTimeout);
41268
                this.mediaUpdateTimeout = null;
41269
            } // we only have use mediaupdatetimeout for live playlists.
41270
 
41271
            if (!this.media() || this.media().endList) {
41272
                return;
41273
            }
41274
            this.mediaUpdateTimeout = window.setTimeout(() => {
41275
                this.mediaUpdateTimeout = null;
41276
                this.trigger('mediaupdatetimeout');
41277
                this.updateMediaUpdateTimeout_(delay);
41278
            }, delay);
41279
        }
41280
        /**
41281
         * start loading of the playlist
41282
         */
41283
 
41284
        start() {
41285
            this.started = true;
41286
            if (typeof this.src === 'object') {
41287
                // in the case of an entirely constructed manifest object (meaning there's no actual
41288
                // manifest on a server), default the uri to the page's href
41289
                if (!this.src.uri) {
41290
                    this.src.uri = window.location.href;
41291
                } // resolvedUri is added on internally after the initial request. Since there's no
41292
                // request for pre-resolved manifests, add on resolvedUri here.
41293
 
41294
                this.src.resolvedUri = this.src.uri; // Since a manifest object was passed in as the source (instead of a URL), the first
41295
                // request can be skipped (since the top level of the manifest, at a minimum, is
41296
                // already available as a parsed manifest object). However, if the manifest object
41297
                // represents a main playlist, some media playlists may need to be resolved before
41298
                // the starting segment list is available. Therefore, go directly to setup of the
41299
                // initial playlist, and let the normal flow continue from there.
41300
                //
41301
                // Note that the call to setup is asynchronous, as other sections of VHS may assume
41302
                // that the first request is asynchronous.
41303
 
41304
                setTimeout(() => {
41305
                    this.setupInitialPlaylist(this.src);
41306
                }, 0);
41307
                return;
41308
            } // request the specified URL
41309
 
41310
            this.request = this.vhs_.xhr({
41311
                uri: this.src,
41312
                withCredentials: this.withCredentials
41313
            }, (error, req) => {
41314
                // disposed
41315
                if (!this.request) {
41316
                    return;
41317
                } // clear the loader's request reference
41318
 
41319
                this.request = null;
41320
                if (error) {
41321
                    this.error = {
41322
                        status: req.status,
41323
                        message: `HLS playlist request error at URL: ${this.src}.`,
41324
                        responseText: req.responseText,
41325
                        // MEDIA_ERR_NETWORK
41326
                        code: 2
41327
                    };
41328
                    if (this.state === 'HAVE_NOTHING') {
41329
                        this.started = false;
41330
                    }
41331
                    return this.trigger('error');
41332
                }
41333
                this.src = resolveManifestRedirect(this.src, req);
41334
                const manifest = this.parseManifest_({
41335
                    manifestString: req.responseText,
41336
                    url: this.src
41337
                });
41338
                this.setupInitialPlaylist(manifest);
41339
            });
41340
        }
41341
        srcUri() {
41342
            return typeof this.src === 'string' ? this.src : this.src.uri;
41343
        }
41344
        /**
41345
         * Given a manifest object that's either a main or media playlist, trigger the proper
41346
         * events and set the state of the playlist loader.
41347
         *
41348
         * If the manifest object represents a main playlist, `loadedplaylist` will be
41349
         * triggered to allow listeners to select a playlist. If none is selected, the loader
41350
         * will default to the first one in the playlists array.
41351
         *
41352
         * If the manifest object represents a media playlist, `loadedplaylist` will be
41353
         * triggered followed by `loadedmetadata`, as the only available playlist is loaded.
41354
         *
41355
         * In the case of a media playlist, a main playlist object wrapper with one playlist
41356
         * will be created so that all logic can handle playlists in the same fashion (as an
41357
         * assumed manifest object schema).
41358
         *
41359
         * @param {Object} manifest
41360
         *        The parsed manifest object
41361
         */
41362
 
41363
        setupInitialPlaylist(manifest) {
41364
            this.state = 'HAVE_MAIN_MANIFEST';
41365
            if (manifest.playlists) {
41366
                this.main = manifest;
41367
                addPropertiesToMain(this.main, this.srcUri()); // If the initial main playlist has playlists wtih segments already resolved,
41368
                // then resolve URIs in advance, as they are usually done after a playlist request,
41369
                // which may not happen if the playlist is resolved.
41370
 
41371
                manifest.playlists.forEach(playlist => {
41372
                    playlist.segments = getAllSegments(playlist);
41373
                    playlist.segments.forEach(segment => {
41374
                        resolveSegmentUris(segment, playlist.resolvedUri);
41375
                    });
41376
                });
41377
                this.trigger('loadedplaylist');
41378
                if (!this.request) {
41379
                    // no media playlist was specifically selected so start
41380
                    // from the first listed one
41381
                    this.media(this.main.playlists[0]);
41382
                }
41383
                return;
41384
            } // In order to support media playlists passed in as vhs-json, the case where the uri
41385
            // is not provided as part of the manifest should be considered, and an appropriate
41386
            // default used.
41387
 
41388
            const uri = this.srcUri() || window.location.href;
41389
            this.main = mainForMedia(manifest, uri);
41390
            this.haveMetadata({
41391
                playlistObject: manifest,
41392
                url: uri,
41393
                id: this.main.playlists[0].id
41394
            });
41395
            this.trigger('loadedmetadata');
41396
        }
41397
        /**
41398
         * Updates or deletes a preexisting pathway clone.
41399
         * Ensures that all playlists related to the old pathway clone are
41400
         * either updated or deleted.
41401
         *
41402
         * @param {Object} clone On update, the pathway clone object for the newly updated pathway clone.
41403
         *        On delete, the old pathway clone object to be deleted.
41404
         * @param {boolean} isUpdate True if the pathway is to be updated,
41405
         *        false if it is meant to be deleted.
41406
         */
41407
 
41408
        updateOrDeleteClone(clone, isUpdate) {
41409
            const main = this.main;
41410
            const pathway = clone.ID;
41411
            let i = main.playlists.length; // Iterate backwards through the playlist so we can remove playlists if necessary.
41412
 
41413
            while (i--) {
41414
                const p = main.playlists[i];
41415
                if (p.attributes['PATHWAY-ID'] === pathway) {
41416
                    const oldPlaylistUri = p.resolvedUri;
41417
                    const oldPlaylistId = p.id; // update the indexed playlist and add new playlists by ID and URI
41418
 
41419
                    if (isUpdate) {
41420
                        const newPlaylistUri = this.createCloneURI_(p.resolvedUri, clone);
41421
                        const newPlaylistId = createPlaylistID(pathway, newPlaylistUri);
41422
                        const attributes = this.createCloneAttributes_(pathway, p.attributes);
41423
                        const updatedPlaylist = this.createClonePlaylist_(p, newPlaylistId, clone, attributes);
41424
                        main.playlists[i] = updatedPlaylist;
41425
                        main.playlists[newPlaylistId] = updatedPlaylist;
41426
                        main.playlists[newPlaylistUri] = updatedPlaylist;
41427
                    } else {
41428
                        // Remove the indexed playlist.
41429
                        main.playlists.splice(i, 1);
41430
                    } // Remove playlists by the old ID and URI.
41431
 
41432
                    delete main.playlists[oldPlaylistId];
41433
                    delete main.playlists[oldPlaylistUri];
41434
                }
41435
            }
41436
            this.updateOrDeleteCloneMedia(clone, isUpdate);
41437
        }
41438
        /**
41439
         * Updates or deletes media data based on the pathway clone object.
41440
         * Due to the complexity of the media groups and playlists, in all cases
41441
         * we remove all of the old media groups and playlists.
41442
         * On updates, we then create new media groups and playlists based on the
41443
         * new pathway clone object.
41444
         *
41445
         * @param {Object} clone The pathway clone object for the newly updated pathway clone.
41446
         * @param {boolean} isUpdate True if the pathway is to be updated,
41447
         *        false if it is meant to be deleted.
41448
         */
41449
 
41450
        updateOrDeleteCloneMedia(clone, isUpdate) {
41451
            const main = this.main;
41452
            const id = clone.ID;
41453
            ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(mediaType => {
41454
                if (!main.mediaGroups[mediaType] || !main.mediaGroups[mediaType][id]) {
41455
                    return;
41456
                }
41457
                for (const groupKey in main.mediaGroups[mediaType]) {
41458
                    // Remove all media playlists for the media group for this pathway clone.
41459
                    if (groupKey === id) {
41460
                        for (const labelKey in main.mediaGroups[mediaType][groupKey]) {
41461
                            const oldMedia = main.mediaGroups[mediaType][groupKey][labelKey];
41462
                            oldMedia.playlists.forEach((p, i) => {
41463
                                const oldMediaPlaylist = main.playlists[p.id];
41464
                                const oldPlaylistId = oldMediaPlaylist.id;
41465
                                const oldPlaylistUri = oldMediaPlaylist.resolvedUri;
41466
                                delete main.playlists[oldPlaylistId];
41467
                                delete main.playlists[oldPlaylistUri];
41468
                            });
41469
                        } // Delete the old media group.
41470
 
41471
                        delete main.mediaGroups[mediaType][groupKey];
41472
                    }
41473
                }
41474
            }); // Create the new media groups and playlists if there is an update.
41475
 
41476
            if (isUpdate) {
41477
                this.createClonedMediaGroups_(clone);
41478
            }
41479
        }
41480
        /**
41481
         * Given a pathway clone object, clones all necessary playlists.
41482
         *
41483
         * @param {Object} clone The pathway clone object.
41484
         * @param {Object} basePlaylist The original playlist to clone from.
41485
         */
41486
 
41487
        addClonePathway(clone, basePlaylist = {}) {
41488
            const main = this.main;
41489
            const index = main.playlists.length;
41490
            const uri = this.createCloneURI_(basePlaylist.resolvedUri, clone);
41491
            const playlistId = createPlaylistID(clone.ID, uri);
41492
            const attributes = this.createCloneAttributes_(clone.ID, basePlaylist.attributes);
41493
            const playlist = this.createClonePlaylist_(basePlaylist, playlistId, clone, attributes);
41494
            main.playlists[index] = playlist; // add playlist by ID and URI
41495
 
41496
            main.playlists[playlistId] = playlist;
41497
            main.playlists[uri] = playlist;
41498
            this.createClonedMediaGroups_(clone);
41499
        }
41500
        /**
41501
         * Given a pathway clone object we create clones of all media.
41502
         * In this function, all necessary information and updated playlists
41503
         * are added to the `mediaGroup` object.
41504
         * Playlists are also added to the `playlists` array so the media groups
41505
         * will be properly linked.
41506
         *
41507
         * @param {Object} clone The pathway clone object.
41508
         */
41509
 
41510
        createClonedMediaGroups_(clone) {
41511
            const id = clone.ID;
41512
            const baseID = clone['BASE-ID'];
41513
            const main = this.main;
41514
            ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(mediaType => {
41515
                // If the media type doesn't exist, or there is already a clone, skip
41516
                // to the next media type.
41517
                if (!main.mediaGroups[mediaType] || main.mediaGroups[mediaType][id]) {
41518
                    return;
41519
                }
41520
                for (const groupKey in main.mediaGroups[mediaType]) {
41521
                    if (groupKey === baseID) {
41522
                        // Create the group.
41523
                        main.mediaGroups[mediaType][id] = {};
41524
                    } else {
41525
                        // There is no need to iterate over label keys in this case.
41526
                        continue;
41527
                    }
41528
                    for (const labelKey in main.mediaGroups[mediaType][groupKey]) {
41529
                        const oldMedia = main.mediaGroups[mediaType][groupKey][labelKey];
41530
                        main.mediaGroups[mediaType][id][labelKey] = _extends$1({}, oldMedia);
41531
                        const newMedia = main.mediaGroups[mediaType][id][labelKey]; // update URIs on the media
41532
 
41533
                        const newUri = this.createCloneURI_(oldMedia.resolvedUri, clone);
41534
                        newMedia.resolvedUri = newUri;
41535
                        newMedia.uri = newUri; // Reset playlists in the new media group.
41536
 
41537
                        newMedia.playlists = []; // Create new playlists in the newly cloned media group.
41538
 
41539
                        oldMedia.playlists.forEach((p, i) => {
41540
                            const oldMediaPlaylist = main.playlists[p.id];
41541
                            const group = groupID(mediaType, id, labelKey);
41542
                            const newPlaylistID = createPlaylistID(id, group); // Check to see if it already exists
41543
 
41544
                            if (oldMediaPlaylist && !main.playlists[newPlaylistID]) {
41545
                                const newMediaPlaylist = this.createClonePlaylist_(oldMediaPlaylist, newPlaylistID, clone);
41546
                                const newPlaylistUri = newMediaPlaylist.resolvedUri;
41547
                                main.playlists[newPlaylistID] = newMediaPlaylist;
41548
                                main.playlists[newPlaylistUri] = newMediaPlaylist;
41549
                            }
41550
                            newMedia.playlists[i] = this.createClonePlaylist_(p, newPlaylistID, clone);
41551
                        });
41552
                    }
41553
                }
41554
            });
41555
        }
41556
        /**
41557
         * Using the original playlist to be cloned, and the pathway clone object
41558
         * information, we create a new playlist.
41559
         *
41560
         * @param {Object} basePlaylist  The original playlist to be cloned from.
41561
         * @param {string} id The desired id of the newly cloned playlist.
41562
         * @param {Object} clone The pathway clone object.
41563
         * @param {Object} attributes An optional object to populate the `attributes` property in the playlist.
41564
         *
41565
         * @return {Object} The combined cloned playlist.
41566
         */
41567
 
41568
        createClonePlaylist_(basePlaylist, id, clone, attributes) {
41569
            const uri = this.createCloneURI_(basePlaylist.resolvedUri, clone);
41570
            const newProps = {
41571
                resolvedUri: uri,
41572
                uri,
41573
                id
41574
            }; // Remove all segments from previous playlist in the clone.
41575
 
41576
            if (basePlaylist.segments) {
41577
                newProps.segments = [];
41578
            }
41579
            if (attributes) {
41580
                newProps.attributes = attributes;
41581
            }
41582
            return merge(basePlaylist, newProps);
41583
        }
41584
        /**
41585
         * Generates an updated URI for a cloned pathway based on the original
41586
         * pathway's URI and the paramaters from the pathway clone object in the
41587
         * content steering server response.
41588
         *
41589
         * @param {string} baseUri URI to be updated in the cloned pathway.
41590
         * @param {Object} clone The pathway clone object.
41591
         *
41592
         * @return {string} The updated URI for the cloned pathway.
41593
         */
41594
 
41595
        createCloneURI_(baseURI, clone) {
41596
            const uri = new URL(baseURI);
41597
            uri.hostname = clone['URI-REPLACEMENT'].HOST;
41598
            const params = clone['URI-REPLACEMENT'].PARAMS; // Add params to the cloned URL.
41599
 
41600
            for (const key of Object.keys(params)) {
41601
                uri.searchParams.set(key, params[key]);
41602
            }
41603
            return uri.href;
41604
        }
41605
        /**
41606
         * Helper function to create the attributes needed for the new clone.
41607
         * This mainly adds the necessary media attributes.
41608
         *
41609
         * @param {string} id The pathway clone object ID.
41610
         * @param {Object} oldAttributes The old attributes to compare to.
41611
         * @return {Object} The new attributes to add to the playlist.
41612
         */
41613
 
41614
        createCloneAttributes_(id, oldAttributes) {
41615
            const attributes = {
41616
                ['PATHWAY-ID']: id
41617
            };
41618
            ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(mediaType => {
41619
                if (oldAttributes[mediaType]) {
41620
                    attributes[mediaType] = id;
41621
                }
41622
            });
41623
            return attributes;
41624
        }
41625
        /**
41626
         * Returns the key ID set from a playlist
41627
         *
41628
         * @param {playlist} playlist to fetch the key ID set from.
41629
         * @return a Set of 32 digit hex strings that represent the unique keyIds for that playlist.
41630
         */
41631
 
41632
        getKeyIdSet(playlist) {
41633
            if (playlist.contentProtection) {
41634
                const keyIds = new Set();
41635
                for (const keysystem in playlist.contentProtection) {
41636
                    const keyId = playlist.contentProtection[keysystem].attributes.keyId;
41637
                    if (keyId) {
41638
                        keyIds.add(keyId.toLowerCase());
41639
                    }
41640
                }
41641
                return keyIds;
41642
            }
41643
        }
41644
    }
41645
 
41646
    /**
41647
     * @file xhr.js
41648
     */
41649
    const {
41650
        xhr: videojsXHR
41651
    } = videojs;
41652
    const callbackWrapper = function (request, error, response, callback) {
41653
        const reqResponse = request.responseType === 'arraybuffer' ? request.response : request.responseText;
41654
        if (!error && reqResponse) {
41655
            request.responseTime = Date.now();
41656
            request.roundTripTime = request.responseTime - request.requestTime;
41657
            request.bytesReceived = reqResponse.byteLength || reqResponse.length;
41658
            if (!request.bandwidth) {
41659
                request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
41660
            }
41661
        }
41662
        if (response.headers) {
41663
            request.responseHeaders = response.headers;
41664
        } // videojs.xhr now uses a specific code on the error
41665
        // object to signal that a request has timed out instead
41666
        // of setting a boolean on the request object
41667
 
41668
        if (error && error.code === 'ETIMEDOUT') {
41669
            request.timedout = true;
41670
        } // videojs.xhr no longer considers status codes outside of 200 and 0
41671
        // (for file uris) to be errors, but the old XHR did, so emulate that
41672
        // behavior. Status 206 may be used in response to byterange requests.
41673
 
41674
        if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
41675
            error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText)));
41676
        }
41677
        callback(error, request);
41678
    };
41679
    /**
41680
     * Iterates over the request hooks Set and calls them in order
41681
     *
41682
     * @param {Set} hooks the hook Set to iterate over
41683
     * @param {Object} options the request options to pass to the xhr wrapper
41684
     * @return the callback hook function return value, the modified or new options Object.
41685
     */
41686
 
41687
    const callAllRequestHooks = (requestSet, options) => {
41688
        if (!requestSet || !requestSet.size) {
41689
            return;
41690
        }
41691
        let newOptions = options;
41692
        requestSet.forEach(requestCallback => {
41693
            newOptions = requestCallback(newOptions);
41694
        });
41695
        return newOptions;
41696
    };
41697
    /**
41698
     * Iterates over the response hooks Set and calls them in order.
41699
     *
41700
     * @param {Set} hooks the hook Set to iterate over
41701
     * @param {Object} request the xhr request object
41702
     * @param {Object} error the xhr error object
41703
     * @param {Object} response the xhr response object
41704
     */
41705
 
41706
    const callAllResponseHooks = (responseSet, request, error, response) => {
41707
        if (!responseSet || !responseSet.size) {
41708
            return;
41709
        }
41710
        responseSet.forEach(responseCallback => {
41711
            responseCallback(request, error, response);
41712
        });
41713
    };
41714
    const xhrFactory = function () {
41715
        const xhr = function XhrFunction(options, callback) {
41716
            // Add a default timeout
41717
            options = merge({
41718
                timeout: 45e3
41719
            }, options); // Allow an optional user-specified function to modify the option
41720
            // object before we construct the xhr request
41721
            // TODO: Remove beforeRequest in the next major release.
41722
 
41723
            const beforeRequest = XhrFunction.beforeRequest || videojs.Vhs.xhr.beforeRequest; // onRequest and onResponse hooks as a Set, at either the player or global level.
41724
            // TODO: new Set added here for beforeRequest alias. Remove this when beforeRequest is removed.
41725
 
41726
            const _requestCallbackSet = XhrFunction._requestCallbackSet || videojs.Vhs.xhr._requestCallbackSet || new Set();
41727
            const _responseCallbackSet = XhrFunction._responseCallbackSet || videojs.Vhs.xhr._responseCallbackSet;
41728
            if (beforeRequest && typeof beforeRequest === 'function') {
41729
                videojs.log.warn('beforeRequest is deprecated, use onRequest instead.');
41730
                _requestCallbackSet.add(beforeRequest);
41731
            } // Use the standard videojs.xhr() method unless `videojs.Vhs.xhr` has been overriden
41732
            // TODO: switch back to videojs.Vhs.xhr.name === 'XhrFunction' when we drop IE11
41733
 
41734
            const xhrMethod = videojs.Vhs.xhr.original === true ? videojsXHR : videojs.Vhs.xhr; // call all registered onRequest hooks, assign new options.
41735
 
41736
            const beforeRequestOptions = callAllRequestHooks(_requestCallbackSet, options); // Remove the beforeRequest function from the hooks set so stale beforeRequest functions are not called.
41737
 
41738
            _requestCallbackSet.delete(beforeRequest); // xhrMethod will call XMLHttpRequest.open and XMLHttpRequest.send
41739
 
41740
            const request = xhrMethod(beforeRequestOptions || options, function (error, response) {
41741
                // call all registered onResponse hooks
41742
                callAllResponseHooks(_responseCallbackSet, request, error, response);
41743
                return callbackWrapper(request, error, response, callback);
41744
            });
41745
            const originalAbort = request.abort;
41746
            request.abort = function () {
41747
                request.aborted = true;
41748
                return originalAbort.apply(request, arguments);
41749
            };
41750
            request.uri = options.uri;
41751
            request.requestTime = Date.now();
41752
            return request;
41753
        };
41754
        xhr.original = true;
41755
        return xhr;
41756
    };
41757
    /**
41758
     * Turns segment byterange into a string suitable for use in
41759
     * HTTP Range requests
41760
     *
41761
     * @param {Object} byterange - an object with two values defining the start and end
41762
     *                             of a byte-range
41763
     */
41764
 
41765
    const byterangeStr = function (byterange) {
41766
        // `byterangeEnd` is one less than `offset + length` because the HTTP range
41767
        // header uses inclusive ranges
41768
        let byterangeEnd;
41769
        const byterangeStart = byterange.offset;
41770
        if (typeof byterange.offset === 'bigint' || typeof byterange.length === 'bigint') {
41771
            byterangeEnd = window.BigInt(byterange.offset) + window.BigInt(byterange.length) - window.BigInt(1);
41772
        } else {
41773
            byterangeEnd = byterange.offset + byterange.length - 1;
41774
        }
41775
        return 'bytes=' + byterangeStart + '-' + byterangeEnd;
41776
    };
41777
    /**
41778
     * Defines headers for use in the xhr request for a particular segment.
41779
     *
41780
     * @param {Object} segment - a simplified copy of the segmentInfo object
41781
     *                           from SegmentLoader
41782
     */
41783
 
41784
    const segmentXhrHeaders = function (segment) {
41785
        const headers = {};
41786
        if (segment.byterange) {
41787
            headers.Range = byterangeStr(segment.byterange);
41788
        }
41789
        return headers;
41790
    };
41791
 
41792
    /**
41793
     * @file bin-utils.js
41794
     */
41795
 
41796
    /**
41797
     * convert a TimeRange to text
41798
     *
41799
     * @param {TimeRange} range the timerange to use for conversion
41800
     * @param {number} i the iterator on the range to convert
41801
     * @return {string} the range in string format
41802
     */
41803
 
41804
    const textRange = function (range, i) {
41805
        return range.start(i) + '-' + range.end(i);
41806
    };
41807
    /**
41808
     * format a number as hex string
41809
     *
41810
     * @param {number} e The number
41811
     * @param {number} i the iterator
41812
     * @return {string} the hex formatted number as a string
41813
     */
41814
 
41815
    const formatHexString = function (e, i) {
41816
        const value = e.toString(16);
41817
        return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
41818
    };
41819
    const formatAsciiString = function (e) {
41820
        if (e >= 0x20 && e < 0x7e) {
41821
            return String.fromCharCode(e);
41822
        }
41823
        return '.';
41824
    };
41825
    /**
41826
     * Creates an object for sending to a web worker modifying properties that are TypedArrays
41827
     * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
41828
     *
41829
     * @param {Object} message
41830
     *        Object of properties and values to send to the web worker
41831
     * @return {Object}
41832
     *         Modified message with TypedArray values expanded
41833
     * @function createTransferableMessage
41834
     */
41835
 
41836
    const createTransferableMessage = function (message) {
41837
        const transferable = {};
41838
        Object.keys(message).forEach(key => {
41839
            const value = message[key];
41840
            if (isArrayBufferView(value)) {
41841
                transferable[key] = {
41842
                    bytes: value.buffer,
41843
                    byteOffset: value.byteOffset,
41844
                    byteLength: value.byteLength
41845
                };
41846
            } else {
41847
                transferable[key] = value;
41848
            }
41849
        });
41850
        return transferable;
41851
    };
41852
    /**
41853
     * Returns a unique string identifier for a media initialization
41854
     * segment.
41855
     *
41856
     * @param {Object} initSegment
41857
     *        the init segment object.
41858
     *
41859
     * @return {string} the generated init segment id
41860
     */
41861
 
41862
    const initSegmentId = function (initSegment) {
41863
        const byterange = initSegment.byterange || {
41864
            length: Infinity,
41865
            offset: 0
41866
        };
41867
        return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
41868
    };
41869
    /**
41870
     * Returns a unique string identifier for a media segment key.
41871
     *
41872
     * @param {Object} key the encryption key
41873
     * @return {string} the unique id for the media segment key.
41874
     */
41875
 
41876
    const segmentKeyId = function (key) {
41877
        return key.resolvedUri;
41878
    };
41879
    /**
41880
     * utils to help dump binary data to the console
41881
     *
41882
     * @param {Array|TypedArray} data
41883
     *        data to dump to a string
41884
     *
41885
     * @return {string} the data as a hex string.
41886
     */
41887
 
41888
    const hexDump = data => {
41889
        const bytes = Array.prototype.slice.call(data);
41890
        const step = 16;
41891
        let result = '';
41892
        let hex;
41893
        let ascii;
41894
        for (let j = 0; j < bytes.length / step; j++) {
41895
            hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
41896
            ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
41897
            result += hex + ' ' + ascii + '\n';
41898
        }
41899
        return result;
41900
    };
41901
    const tagDump = ({
41902
                         bytes
41903
                     }) => hexDump(bytes);
41904
    const textRanges = ranges => {
41905
        let result = '';
41906
        let i;
41907
        for (i = 0; i < ranges.length; i++) {
41908
            result += textRange(ranges, i) + ' ';
41909
        }
41910
        return result;
41911
    };
41912
    var utils = /*#__PURE__*/Object.freeze({
41913
        __proto__: null,
41914
        createTransferableMessage: createTransferableMessage,
41915
        initSegmentId: initSegmentId,
41916
        segmentKeyId: segmentKeyId,
41917
        hexDump: hexDump,
41918
        tagDump: tagDump,
41919
        textRanges: textRanges
41920
    });
41921
 
41922
    // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux
41923
    // 25% was arbitrarily chosen, and may need to be refined over time.
41924
 
41925
    const SEGMENT_END_FUDGE_PERCENT = 0.25;
41926
    /**
41927
     * Converts a player time (any time that can be gotten/set from player.currentTime(),
41928
     * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a
41929
     * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)).
41930
     *
41931
     * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor
41932
     * point" (a point where we have a mapping from program time to player time, with player
41933
     * time being the post transmux start of the segment).
41934
     *
41935
     * For more details, see [this doc](../../docs/program-time-from-player-time.md).
41936
     *
41937
     * @param {number} playerTime the player time
41938
     * @param {Object} segment the segment which contains the player time
41939
     * @return {Date} program time
41940
     */
41941
 
41942
    const playerTimeToProgramTime = (playerTime, segment) => {
41943
        if (!segment.dateTimeObject) {
41944
            // Can't convert without an "anchor point" for the program time (i.e., a time that can
41945
            // be used to map the start of a segment with a real world time).
41946
            return null;
41947
        }
41948
        const transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds;
41949
        const transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended
41950
 
41951
        const startOfSegment = transmuxedStart + transmuxerPrependedSeconds;
41952
        const offsetFromSegmentStart = playerTime - startOfSegment;
41953
        return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000);
41954
    };
41955
    const originalSegmentVideoDuration = videoTimingInfo => {
41956
        return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds;
41957
    };
41958
    /**
41959
     * Finds a segment that contains the time requested given as an ISO-8601 string. The
41960
     * returned segment might be an estimate or an accurate match.
41961
     *
41962
     * @param {string} programTime The ISO-8601 programTime to find a match for
41963
     * @param {Object} playlist A playlist object to search within
41964
     */
41965
 
41966
    const findSegmentForProgramTime = (programTime, playlist) => {
41967
        // Assumptions:
41968
        //  - verifyProgramDateTimeTags has already been run
41969
        //  - live streams have been started
41970
        let dateTimeObject;
41971
        try {
41972
            dateTimeObject = new Date(programTime);
41973
        } catch (e) {
41974
            return null;
41975
        }
41976
        if (!playlist || !playlist.segments || playlist.segments.length === 0) {
41977
            return null;
41978
        }
41979
        let segment = playlist.segments[0];
41980
        if (dateTimeObject < new Date(segment.dateTimeObject)) {
41981
            // Requested time is before stream start.
41982
            return null;
41983
        }
41984
        for (let i = 0; i < playlist.segments.length - 1; i++) {
41985
            segment = playlist.segments[i];
41986
            const nextSegmentStart = new Date(playlist.segments[i + 1].dateTimeObject);
41987
            if (dateTimeObject < nextSegmentStart) {
41988
                break;
41989
            }
41990
        }
41991
        const lastSegment = playlist.segments[playlist.segments.length - 1];
41992
        const lastSegmentStart = lastSegment.dateTimeObject;
41993
        const lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT;
41994
        const lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000);
41995
        if (dateTimeObject > lastSegmentEnd) {
41996
            // Beyond the end of the stream, or our best guess of the end of the stream.
41997
            return null;
41998
        }
41999
        if (dateTimeObject > new Date(lastSegmentStart)) {
42000
            segment = lastSegment;
42001
        }
42002
        return {
42003
            segment,
42004
            estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)),
42005
            // Although, given that all segments have accurate date time objects, the segment
42006
            // selected should be accurate, unless the video has been transmuxed at some point
42007
            // (determined by the presence of the videoTimingInfo object), the segment's "player
42008
            // time" (the start time in the player) can't be considered accurate.
42009
            type: segment.videoTimingInfo ? 'accurate' : 'estimate'
42010
        };
42011
    };
42012
    /**
42013
     * Finds a segment that contains the given player time(in seconds).
42014
     *
42015
     * @param {number} time The player time to find a match for
42016
     * @param {Object} playlist A playlist object to search within
42017
     */
42018
 
42019
    const findSegmentForPlayerTime = (time, playlist) => {
42020
        // Assumptions:
42021
        // - there will always be a segment.duration
42022
        // - we can start from zero
42023
        // - segments are in time order
42024
        if (!playlist || !playlist.segments || playlist.segments.length === 0) {
42025
            return null;
42026
        }
42027
        let segmentEnd = 0;
42028
        let segment;
42029
        for (let i = 0; i < playlist.segments.length; i++) {
42030
            segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and
42031
            // should contain the most accurate values we have for the segment's player times.
42032
            //
42033
            // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall
42034
            // back to an estimate based on the manifest derived (inaccurate) segment.duration, to
42035
            // calculate an end value.
42036
 
42037
            segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration;
42038
            if (time <= segmentEnd) {
42039
                break;
42040
            }
42041
        }
42042
        const lastSegment = playlist.segments[playlist.segments.length - 1];
42043
        if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) {
42044
            // The time requested is beyond the stream end.
42045
            return null;
42046
        }
42047
        if (time > segmentEnd) {
42048
            // The time is within or beyond the last segment.
42049
            //
42050
            // Check to see if the time is beyond a reasonable guess of the end of the stream.
42051
            if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) {
42052
                // Technically, because the duration value is only an estimate, the time may still
42053
                // exist in the last segment, however, there isn't enough information to make even
42054
                // a reasonable estimate.
42055
                return null;
42056
            }
42057
            segment = lastSegment;
42058
        }
42059
        return {
42060
            segment,
42061
            estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration,
42062
            // Because videoTimingInfo is only set after transmux, it is the only way to get
42063
            // accurate timing values.
42064
            type: segment.videoTimingInfo ? 'accurate' : 'estimate'
42065
        };
42066
    };
42067
    /**
42068
     * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds.
42069
     * If the offset returned is positive, the programTime occurs after the
42070
     * comparisonTimestamp.
42071
     * If the offset is negative, the programTime occurs before the comparisonTimestamp.
42072
     *
42073
     * @param {string} comparisonTimeStamp An ISO-8601 timestamp to compare against
42074
     * @param {string} programTime The programTime as an ISO-8601 string
42075
     * @return {number} offset
42076
     */
42077
 
42078
    const getOffsetFromTimestamp = (comparisonTimeStamp, programTime) => {
42079
        let segmentDateTime;
42080
        let programDateTime;
42081
        try {
42082
            segmentDateTime = new Date(comparisonTimeStamp);
42083
            programDateTime = new Date(programTime);
42084
        } catch (e) {// TODO handle error
42085
        }
42086
        const segmentTimeEpoch = segmentDateTime.getTime();
42087
        const programTimeEpoch = programDateTime.getTime();
42088
        return (programTimeEpoch - segmentTimeEpoch) / 1000;
42089
    };
42090
    /**
42091
     * Checks that all segments in this playlist have programDateTime tags.
42092
     *
42093
     * @param {Object} playlist A playlist object
42094
     */
42095
 
42096
    const verifyProgramDateTimeTags = playlist => {
42097
        if (!playlist.segments || playlist.segments.length === 0) {
42098
            return false;
42099
        }
42100
        for (let i = 0; i < playlist.segments.length; i++) {
42101
            const segment = playlist.segments[i];
42102
            if (!segment.dateTimeObject) {
42103
                return false;
42104
            }
42105
        }
42106
        return true;
42107
    };
42108
    /**
42109
     * Returns the programTime of the media given a playlist and a playerTime.
42110
     * The playlist must have programDateTime tags for a programDateTime tag to be returned.
42111
     * If the segments containing the time requested have not been buffered yet, an estimate
42112
     * may be returned to the callback.
42113
     *
42114
     * @param {Object} args
42115
     * @param {Object} args.playlist A playlist object to search within
42116
     * @param {number} time A playerTime in seconds
42117
     * @param {Function} callback(err, programTime)
42118
     * @return {string} err.message A detailed error message
42119
     * @return {Object} programTime
42120
     * @return {number} programTime.mediaSeconds The streamTime in seconds
42121
     * @return {string} programTime.programDateTime The programTime as an ISO-8601 String
42122
     */
42123
 
42124
    const getProgramTime = ({
42125
                                playlist,
42126
                                time = undefined,
42127
                                callback
42128
                            }) => {
42129
        if (!callback) {
42130
            throw new Error('getProgramTime: callback must be provided');
42131
        }
42132
        if (!playlist || time === undefined) {
42133
            return callback({
42134
                message: 'getProgramTime: playlist and time must be provided'
42135
            });
42136
        }
42137
        const matchedSegment = findSegmentForPlayerTime(time, playlist);
42138
        if (!matchedSegment) {
42139
            return callback({
42140
                message: 'valid programTime was not found'
42141
            });
42142
        }
42143
        if (matchedSegment.type === 'estimate') {
42144
            return callback({
42145
                message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again',
42146
                seekTime: matchedSegment.estimatedStart
42147
            });
42148
        }
42149
        const programTimeObject = {
42150
            mediaSeconds: time
42151
        };
42152
        const programTime = playerTimeToProgramTime(time, matchedSegment.segment);
42153
        if (programTime) {
42154
            programTimeObject.programDateTime = programTime.toISOString();
42155
        }
42156
        return callback(null, programTimeObject);
42157
    };
42158
    /**
42159
     * Seeks in the player to a time that matches the given programTime ISO-8601 string.
42160
     *
42161
     * @param {Object} args
42162
     * @param {string} args.programTime A programTime to seek to as an ISO-8601 String
42163
     * @param {Object} args.playlist A playlist to look within
42164
     * @param {number} args.retryCount The number of times to try for an accurate seek. Default is 2.
42165
     * @param {Function} args.seekTo A method to perform a seek
42166
     * @param {boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true.
42167
     * @param {Object} args.tech The tech to seek on
42168
     * @param {Function} args.callback(err, newTime) A callback to return the new time to
42169
     * @return {string} err.message A detailed error message
42170
     * @return {number} newTime The exact time that was seeked to in seconds
42171
     */
42172
 
42173
    const seekToProgramTime = ({
42174
                                   programTime,
42175
                                   playlist,
42176
                                   retryCount = 2,
42177
                                   seekTo,
42178
                                   pauseAfterSeek = true,
42179
                                   tech,
42180
                                   callback
42181
                               }) => {
42182
        if (!callback) {
42183
            throw new Error('seekToProgramTime: callback must be provided');
42184
        }
42185
        if (typeof programTime === 'undefined' || !playlist || !seekTo) {
42186
            return callback({
42187
                message: 'seekToProgramTime: programTime, seekTo and playlist must be provided'
42188
            });
42189
        }
42190
        if (!playlist.endList && !tech.hasStarted_) {
42191
            return callback({
42192
                message: 'player must be playing a live stream to start buffering'
42193
            });
42194
        }
42195
        if (!verifyProgramDateTimeTags(playlist)) {
42196
            return callback({
42197
                message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri
42198
            });
42199
        }
42200
        const matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match
42201
 
42202
        if (!matchedSegment) {
42203
            return callback({
42204
                message: `${programTime} was not found in the stream`
42205
            });
42206
        }
42207
        const segment = matchedSegment.segment;
42208
        const mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime);
42209
        if (matchedSegment.type === 'estimate') {
42210
            // we've run out of retries
42211
            if (retryCount === 0) {
42212
                return callback({
42213
                    message: `${programTime} is not buffered yet. Try again`
42214
                });
42215
            }
42216
            seekTo(matchedSegment.estimatedStart + mediaOffset);
42217
            tech.one('seeked', () => {
42218
                seekToProgramTime({
42219
                    programTime,
42220
                    playlist,
42221
                    retryCount: retryCount - 1,
42222
                    seekTo,
42223
                    pauseAfterSeek,
42224
                    tech,
42225
                    callback
42226
                });
42227
            });
42228
            return;
42229
        } // Since the segment.start value is determined from the buffered end or ending time
42230
        // of the prior segment, the seekToTime doesn't need to account for any transmuxer
42231
        // modifications.
42232
 
42233
        const seekToTime = segment.start + mediaOffset;
42234
        const seekedCallback = () => {
42235
            return callback(null, tech.currentTime());
42236
        }; // listen for seeked event
42237
 
42238
        tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state
42239
 
42240
        if (pauseAfterSeek) {
42241
            tech.pause();
42242
        }
42243
        seekTo(seekToTime);
42244
    };
42245
 
42246
    // which will only happen if the request is complete.
42247
 
42248
    const callbackOnCompleted = (request, cb) => {
42249
        if (request.readyState === 4) {
42250
            return cb();
42251
        }
42252
        return;
42253
    };
42254
    const containerRequest = (uri, xhr, cb) => {
42255
        let bytes = [];
42256
        let id3Offset;
42257
        let finished = false;
42258
        const endRequestAndCallback = function (err, req, type, _bytes) {
42259
            req.abort();
42260
            finished = true;
42261
            return cb(err, req, type, _bytes);
42262
        };
42263
        const progressListener = function (error, request) {
42264
            if (finished) {
42265
                return;
42266
            }
42267
            if (error) {
42268
                return endRequestAndCallback(error, request, '', bytes);
42269
            } // grap the new part of content that was just downloaded
42270
 
42271
            const newPart = request.responseText.substring(bytes && bytes.byteLength || 0, request.responseText.length); // add that onto bytes
42272
 
42273
            bytes = concatTypedArrays(bytes, stringToBytes(newPart, true));
42274
            id3Offset = id3Offset || getId3Offset(bytes); // we need at least 10 bytes to determine a type
42275
            // or we need at least two bytes after an id3Offset
42276
 
42277
            if (bytes.length < 10 || id3Offset && bytes.length < id3Offset + 2) {
42278
                return callbackOnCompleted(request, () => endRequestAndCallback(error, request, '', bytes));
42279
            }
42280
            const type = detectContainerForBytes(bytes); // if this looks like a ts segment but we don't have enough data
42281
            // to see the second sync byte, wait until we have enough data
42282
            // before declaring it ts
42283
 
42284
            if (type === 'ts' && bytes.length < 188) {
42285
                return callbackOnCompleted(request, () => endRequestAndCallback(error, request, '', bytes));
42286
            } // this may be an unsynced ts segment
42287
            // wait for 376 bytes before detecting no container
42288
 
42289
            if (!type && bytes.length < 376) {
42290
                return callbackOnCompleted(request, () => endRequestAndCallback(error, request, '', bytes));
42291
            }
42292
            return endRequestAndCallback(null, request, type, bytes);
42293
        };
42294
        const options = {
42295
            uri,
42296
            beforeSend(request) {
42297
                // this forces the browser to pass the bytes to us unprocessed
42298
                request.overrideMimeType('text/plain; charset=x-user-defined');
42299
                request.addEventListener('progress', function ({
42300
                                                                   total,
42301
                                                                   loaded
42302
                                                               }) {
42303
                    return callbackWrapper(request, null, {
42304
                        statusCode: request.status
42305
                    }, progressListener);
42306
                });
42307
            }
42308
        };
42309
        const request = xhr(options, function (error, response) {
42310
            return callbackWrapper(request, error, response, progressListener);
42311
        });
42312
        return request;
42313
    };
42314
    const {
42315
        EventTarget
42316
    } = videojs;
42317
    const dashPlaylistUnchanged = function (a, b) {
42318
        if (!isPlaylistUnchanged(a, b)) {
42319
            return false;
42320
        } // for dash the above check will often return true in scenarios where
42321
        // the playlist actually has changed because mediaSequence isn't a
42322
        // dash thing, and we often set it to 1. So if the playlists have the same amount
42323
        // of segments we return true.
42324
        // So for dash we need to make sure that the underlying segments are different.
42325
        // if sidx changed then the playlists are different.
42326
 
42327
        if (a.sidx && b.sidx && (a.sidx.offset !== b.sidx.offset || a.sidx.length !== b.sidx.length)) {
42328
            return false;
42329
        } else if (!a.sidx && b.sidx || a.sidx && !b.sidx) {
42330
            return false;
42331
        } // one or the other does not have segments
42332
        // there was a change.
42333
 
42334
        if (a.segments && !b.segments || !a.segments && b.segments) {
42335
            return false;
42336
        } // neither has segments nothing changed
42337
 
42338
        if (!a.segments && !b.segments) {
42339
            return true;
42340
        } // check segments themselves
42341
 
42342
        for (let i = 0; i < a.segments.length; i++) {
42343
            const aSegment = a.segments[i];
42344
            const bSegment = b.segments[i]; // if uris are different between segments there was a change
42345
 
42346
            if (aSegment.uri !== bSegment.uri) {
42347
                return false;
42348
            } // neither segment has a byterange, there will be no byterange change.
42349
 
42350
            if (!aSegment.byterange && !bSegment.byterange) {
42351
                continue;
42352
            }
42353
            const aByterange = aSegment.byterange;
42354
            const bByterange = bSegment.byterange; // if byterange only exists on one of the segments, there was a change.
42355
 
42356
            if (aByterange && !bByterange || !aByterange && bByterange) {
42357
                return false;
42358
            } // if both segments have byterange with different offsets, there was a change.
42359
 
42360
            if (aByterange.offset !== bByterange.offset || aByterange.length !== bByterange.length) {
42361
                return false;
42362
            }
42363
        } // if everything was the same with segments, this is the same playlist.
42364
 
42365
        return true;
42366
    };
42367
    /**
42368
     * Use the representation IDs from the mpd object to create groupIDs, the NAME is set to mandatory representation
42369
     * ID in the parser. This allows for continuous playout across periods with the same representation IDs
42370
     * (continuous periods as defined in DASH-IF 3.2.12). This is assumed in the mpd-parser as well. If we want to support
42371
     * periods without continuous playback this function may need modification as well as the parser.
42372
     */
42373
 
42374
    const dashGroupId = (type, group, label, playlist) => {
42375
        // If the manifest somehow does not have an ID (non-dash compliant), use the label.
42376
        const playlistId = playlist.attributes.NAME || label;
42377
        return `placeholder-uri-${type}-${group}-${playlistId}`;
42378
    };
42379
    /**
42380
     * Parses the main XML string and updates playlist URI references.
42381
     *
42382
     * @param {Object} config
42383
     *        Object of arguments
42384
     * @param {string} config.mainXml
42385
     *        The mpd XML
42386
     * @param {string} config.srcUrl
42387
     *        The mpd URL
42388
     * @param {Date} config.clientOffset
42389
     *         A time difference between server and client
42390
     * @param {Object} config.sidxMapping
42391
     *        SIDX mappings for moof/mdat URIs and byte ranges
42392
     * @return {Object}
42393
     *         The parsed mpd manifest object
42394
     */
42395
 
42396
    const parseMainXml = ({
42397
                              mainXml,
42398
                              srcUrl,
42399
                              clientOffset,
42400
                              sidxMapping,
42401
                              previousManifest
42402
                          }) => {
42403
        const manifest = parse(mainXml, {
42404
            manifestUri: srcUrl,
42405
            clientOffset,
42406
            sidxMapping,
42407
            previousManifest
42408
        });
42409
        addPropertiesToMain(manifest, srcUrl, dashGroupId);
42410
        return manifest;
42411
    };
42412
    /**
42413
     * Removes any mediaGroup labels that no longer exist in the newMain
42414
     *
42415
     * @param {Object} update
42416
     *         The previous mpd object being updated
42417
     * @param {Object} newMain
42418
     *         The new mpd object
42419
     */
42420
 
42421
    const removeOldMediaGroupLabels = (update, newMain) => {
42422
        forEachMediaGroup(update, (properties, type, group, label) => {
42423
            if (!(label in newMain.mediaGroups[type][group])) {
42424
                delete update.mediaGroups[type][group][label];
42425
            }
42426
        });
42427
    };
42428
    /**
42429
     * Returns a new main manifest that is the result of merging an updated main manifest
42430
     * into the original version.
42431
     *
42432
     * @param {Object} oldMain
42433
     *        The old parsed mpd object
42434
     * @param {Object} newMain
42435
     *        The updated parsed mpd object
42436
     * @return {Object}
42437
     *         A new object representing the original main manifest with the updated media
42438
     *         playlists merged in
42439
     */
42440
 
42441
    const updateMain = (oldMain, newMain, sidxMapping) => {
42442
        let noChanges = true;
42443
        let update = merge(oldMain, {
42444
            // These are top level properties that can be updated
42445
            duration: newMain.duration,
42446
            minimumUpdatePeriod: newMain.minimumUpdatePeriod,
42447
            timelineStarts: newMain.timelineStarts
42448
        }); // First update the playlists in playlist list
42449
 
42450
        for (let i = 0; i < newMain.playlists.length; i++) {
42451
            const playlist = newMain.playlists[i];
42452
            if (playlist.sidx) {
42453
                const sidxKey = generateSidxKey(playlist.sidx); // add sidx segments to the playlist if we have all the sidx info already
42454
 
42455
                if (sidxMapping && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx) {
42456
                    addSidxSegmentsToPlaylist$1(playlist, sidxMapping[sidxKey].sidx, playlist.sidx.resolvedUri);
42457
                }
42458
            }
42459
            const playlistUpdate = updateMain$1(update, playlist, dashPlaylistUnchanged);
42460
            if (playlistUpdate) {
42461
                update = playlistUpdate;
42462
                noChanges = false;
42463
            }
42464
        } // Then update media group playlists
42465
 
42466
        forEachMediaGroup(newMain, (properties, type, group, label) => {
42467
            if (properties.playlists && properties.playlists.length) {
42468
                const id = properties.playlists[0].id;
42469
                const playlistUpdate = updateMain$1(update, properties.playlists[0], dashPlaylistUnchanged);
42470
                if (playlistUpdate) {
42471
                    update = playlistUpdate; // add new mediaGroup label if it doesn't exist and assign the new mediaGroup.
42472
 
42473
                    if (!(label in update.mediaGroups[type][group])) {
42474
                        update.mediaGroups[type][group][label] = properties;
42475
                    } // update the playlist reference within media groups
42476
 
42477
                    update.mediaGroups[type][group][label].playlists[0] = update.playlists[id];
42478
                    noChanges = false;
42479
                }
42480
            }
42481
        }); // remove mediaGroup labels and references that no longer exist in the newMain
42482
 
42483
        removeOldMediaGroupLabels(update, newMain);
42484
        if (newMain.minimumUpdatePeriod !== oldMain.minimumUpdatePeriod) {
42485
            noChanges = false;
42486
        }
42487
        if (noChanges) {
42488
            return null;
42489
        }
42490
        return update;
42491
    }; // SIDX should be equivalent if the URI and byteranges of the SIDX match.
42492
    // If the SIDXs have maps, the two maps should match,
42493
    // both `a` and `b` missing SIDXs is considered matching.
42494
    // If `a` or `b` but not both have a map, they aren't matching.
42495
 
42496
    const equivalentSidx = (a, b) => {
42497
        const neitherMap = Boolean(!a.map && !b.map);
42498
        const equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length);
42499
        return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length;
42500
    }; // exported for testing
42501
 
42502
    const compareSidxEntry = (playlists, oldSidxMapping) => {
42503
        const newSidxMapping = {};
42504
        for (const id in playlists) {
42505
            const playlist = playlists[id];
42506
            const currentSidxInfo = playlist.sidx;
42507
            if (currentSidxInfo) {
42508
                const key = generateSidxKey(currentSidxInfo);
42509
                if (!oldSidxMapping[key]) {
42510
                    break;
42511
                }
42512
                const savedSidxInfo = oldSidxMapping[key].sidxInfo;
42513
                if (equivalentSidx(savedSidxInfo, currentSidxInfo)) {
42514
                    newSidxMapping[key] = oldSidxMapping[key];
42515
                }
42516
            }
42517
        }
42518
        return newSidxMapping;
42519
    };
42520
    /**
42521
     *  A function that filters out changed items as they need to be requested separately.
42522
     *
42523
     *  The method is exported for testing
42524
     *
42525
     *  @param {Object} main the parsed mpd XML returned via mpd-parser
42526
     *  @param {Object} oldSidxMapping the SIDX to compare against
42527
     */
42528
 
42529
    const filterChangedSidxMappings = (main, oldSidxMapping) => {
42530
        const videoSidx = compareSidxEntry(main.playlists, oldSidxMapping);
42531
        let mediaGroupSidx = videoSidx;
42532
        forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
42533
            if (properties.playlists && properties.playlists.length) {
42534
                const playlists = properties.playlists;
42535
                mediaGroupSidx = merge(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping));
42536
            }
42537
        });
42538
        return mediaGroupSidx;
42539
    };
42540
    class DashPlaylistLoader extends EventTarget {
42541
        // DashPlaylistLoader must accept either a src url or a playlist because subsequent
42542
        // playlist loader setups from media groups will expect to be able to pass a playlist
42543
        // (since there aren't external URLs to media playlists with DASH)
42544
        constructor(srcUrlOrPlaylist, vhs, options = {}, mainPlaylistLoader) {
42545
            super();
42546
            this.mainPlaylistLoader_ = mainPlaylistLoader || this;
42547
            if (!mainPlaylistLoader) {
42548
                this.isMain_ = true;
42549
            }
42550
            const {
42551
                withCredentials = false
42552
            } = options;
42553
            this.vhs_ = vhs;
42554
            this.withCredentials = withCredentials;
42555
            this.addMetadataToTextTrack = options.addMetadataToTextTrack;
42556
            if (!srcUrlOrPlaylist) {
42557
                throw new Error('A non-empty playlist URL or object is required');
42558
            } // event naming?
42559
 
42560
            this.on('minimumUpdatePeriod', () => {
42561
                this.refreshXml_();
42562
            }); // live playlist staleness timeout
42563
 
42564
            this.on('mediaupdatetimeout', () => {
42565
                this.refreshMedia_(this.media().id);
42566
            });
42567
            this.state = 'HAVE_NOTHING';
42568
            this.loadedPlaylists_ = {};
42569
            this.logger_ = logger('DashPlaylistLoader'); // initialize the loader state
42570
            // The mainPlaylistLoader will be created with a string
42571
 
42572
            if (this.isMain_) {
42573
                this.mainPlaylistLoader_.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes
42574
                // once multi-period is refactored
42575
 
42576
                this.mainPlaylistLoader_.sidxMapping_ = {};
42577
            } else {
42578
                this.childPlaylist_ = srcUrlOrPlaylist;
42579
            }
42580
        }
42581
        requestErrored_(err, request, startingState) {
42582
            // disposed
42583
            if (!this.request) {
42584
                return true;
42585
            } // pending request is cleared
42586
 
42587
            this.request = null;
42588
            if (err) {
42589
                // use the provided error object or create one
42590
                // based on the request/response
42591
                this.error = typeof err === 'object' && !(err instanceof Error) ? err : {
42592
                    status: request.status,
42593
                    message: 'DASH request error at URL: ' + request.uri,
42594
                    response: request.response,
42595
                    // MEDIA_ERR_NETWORK
42596
                    code: 2
42597
                };
42598
                if (startingState) {
42599
                    this.state = startingState;
42600
                }
42601
                this.trigger('error');
42602
                return true;
42603
            }
42604
        }
42605
        /**
42606
         * Verify that the container of the sidx segment can be parsed
42607
         * and if it can, get and parse that segment.
42608
         */
42609
 
42610
        addSidxSegments_(playlist, startingState, cb) {
42611
            const sidxKey = playlist.sidx && generateSidxKey(playlist.sidx); // playlist lacks sidx or sidx segments were added to this playlist already.
42612
 
42613
            if (!playlist.sidx || !sidxKey || this.mainPlaylistLoader_.sidxMapping_[sidxKey]) {
42614
                // keep this function async
42615
                this.mediaRequest_ = window.setTimeout(() => cb(false), 0);
42616
                return;
42617
            } // resolve the segment URL relative to the playlist
42618
 
42619
            const uri = resolveManifestRedirect(playlist.sidx.resolvedUri);
42620
            const fin = (err, request) => {
42621
                if (this.requestErrored_(err, request, startingState)) {
42622
                    return;
42623
                }
42624
                const sidxMapping = this.mainPlaylistLoader_.sidxMapping_;
42625
                let sidx;
42626
                try {
42627
                    sidx = parseSidx_1(toUint8(request.response).subarray(8));
42628
                } catch (e) {
42629
                    // sidx parsing failed.
42630
                    this.requestErrored_(e, request, startingState);
42631
                    return;
42632
                }
42633
                sidxMapping[sidxKey] = {
42634
                    sidxInfo: playlist.sidx,
42635
                    sidx
42636
                };
42637
                addSidxSegmentsToPlaylist$1(playlist, sidx, playlist.sidx.resolvedUri);
42638
                return cb(true);
42639
            };
42640
            this.request = containerRequest(uri, this.vhs_.xhr, (err, request, container, bytes) => {
42641
                if (err) {
42642
                    return fin(err, request);
42643
                }
42644
                if (!container || container !== 'mp4') {
42645
                    return fin({
42646
                        status: request.status,
42647
                        message: `Unsupported ${container || 'unknown'} container type for sidx segment at URL: ${uri}`,
42648
                        // response is just bytes in this case
42649
                        // but we really don't want to return that.
42650
                        response: '',
42651
                        playlist,
42652
                        internal: true,
42653
                        playlistExclusionDuration: Infinity,
42654
                        // MEDIA_ERR_NETWORK
42655
                        code: 2
42656
                    }, request);
42657
                } // if we already downloaded the sidx bytes in the container request, use them
42658
 
42659
                const {
42660
                    offset,
42661
                    length
42662
                } = playlist.sidx.byterange;
42663
                if (bytes.length >= length + offset) {
42664
                    return fin(err, {
42665
                        response: bytes.subarray(offset, offset + length),
42666
                        status: request.status,
42667
                        uri: request.uri
42668
                    });
42669
                } // otherwise request sidx bytes
42670
 
42671
                this.request = this.vhs_.xhr({
42672
                    uri,
42673
                    responseType: 'arraybuffer',
42674
                    headers: segmentXhrHeaders({
42675
                        byterange: playlist.sidx.byterange
42676
                    })
42677
                }, fin);
42678
            });
42679
        }
42680
        dispose() {
42681
            this.trigger('dispose');
42682
            this.stopRequest();
42683
            this.loadedPlaylists_ = {};
42684
            window.clearTimeout(this.minimumUpdatePeriodTimeout_);
42685
            window.clearTimeout(this.mediaRequest_);
42686
            window.clearTimeout(this.mediaUpdateTimeout);
42687
            this.mediaUpdateTimeout = null;
42688
            this.mediaRequest_ = null;
42689
            this.minimumUpdatePeriodTimeout_ = null;
42690
            if (this.mainPlaylistLoader_.createMupOnMedia_) {
42691
                this.off('loadedmetadata', this.mainPlaylistLoader_.createMupOnMedia_);
42692
                this.mainPlaylistLoader_.createMupOnMedia_ = null;
42693
            }
42694
            this.off();
42695
        }
42696
        hasPendingRequest() {
42697
            return this.request || this.mediaRequest_;
42698
        }
42699
        stopRequest() {
42700
            if (this.request) {
42701
                const oldRequest = this.request;
42702
                this.request = null;
42703
                oldRequest.onreadystatechange = null;
42704
                oldRequest.abort();
42705
            }
42706
        }
42707
        media(playlist) {
42708
            // getter
42709
            if (!playlist) {
42710
                return this.media_;
42711
            } // setter
42712
 
42713
            if (this.state === 'HAVE_NOTHING') {
42714
                throw new Error('Cannot switch media playlist from ' + this.state);
42715
            }
42716
            const startingState = this.state; // find the playlist object if the target playlist has been specified by URI
42717
 
42718
            if (typeof playlist === 'string') {
42719
                if (!this.mainPlaylistLoader_.main.playlists[playlist]) {
42720
                    throw new Error('Unknown playlist URI: ' + playlist);
42721
                }
42722
                playlist = this.mainPlaylistLoader_.main.playlists[playlist];
42723
            }
42724
            const mediaChange = !this.media_ || playlist.id !== this.media_.id; // switch to previously loaded playlists immediately
42725
 
42726
            if (mediaChange && this.loadedPlaylists_[playlist.id] && this.loadedPlaylists_[playlist.id].endList) {
42727
                this.state = 'HAVE_METADATA';
42728
                this.media_ = playlist; // trigger media change if the active media has been updated
42729
 
42730
                if (mediaChange) {
42731
                    this.trigger('mediachanging');
42732
                    this.trigger('mediachange');
42733
                }
42734
                return;
42735
            } // switching to the active playlist is a no-op
42736
 
42737
            if (!mediaChange) {
42738
                return;
42739
            } // switching from an already loaded playlist
42740
 
42741
            if (this.media_) {
42742
                this.trigger('mediachanging');
42743
            }
42744
            this.addSidxSegments_(playlist, startingState, sidxChanged => {
42745
                // everything is ready just continue to haveMetadata
42746
                this.haveMetadata({
42747
                    startingState,
42748
                    playlist
42749
                });
42750
            });
42751
        }
42752
        haveMetadata({
42753
                         startingState,
42754
                         playlist
42755
                     }) {
42756
            this.state = 'HAVE_METADATA';
42757
            this.loadedPlaylists_[playlist.id] = playlist;
42758
            this.mediaRequest_ = null; // This will trigger loadedplaylist
42759
 
42760
            this.refreshMedia_(playlist.id); // fire loadedmetadata the first time a media playlist is loaded
42761
            // to resolve setup of media groups
42762
 
42763
            if (startingState === 'HAVE_MAIN_MANIFEST') {
42764
                this.trigger('loadedmetadata');
42765
            } else {
42766
                // trigger media change if the active media has been updated
42767
                this.trigger('mediachange');
42768
            }
42769
        }
42770
        pause() {
42771
            if (this.mainPlaylistLoader_.createMupOnMedia_) {
42772
                this.off('loadedmetadata', this.mainPlaylistLoader_.createMupOnMedia_);
42773
                this.mainPlaylistLoader_.createMupOnMedia_ = null;
42774
            }
42775
            this.stopRequest();
42776
            window.clearTimeout(this.mediaUpdateTimeout);
42777
            this.mediaUpdateTimeout = null;
42778
            if (this.isMain_) {
42779
                window.clearTimeout(this.mainPlaylistLoader_.minimumUpdatePeriodTimeout_);
42780
                this.mainPlaylistLoader_.minimumUpdatePeriodTimeout_ = null;
42781
            }
42782
            if (this.state === 'HAVE_NOTHING') {
42783
                // If we pause the loader before any data has been retrieved, its as if we never
42784
                // started, so reset to an unstarted state.
42785
                this.started = false;
42786
            }
42787
        }
42788
        load(isFinalRendition) {
42789
            window.clearTimeout(this.mediaUpdateTimeout);
42790
            this.mediaUpdateTimeout = null;
42791
            const media = this.media();
42792
            if (isFinalRendition) {
42793
                const delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
42794
                this.mediaUpdateTimeout = window.setTimeout(() => this.load(), delay);
42795
                return;
42796
            } // because the playlists are internal to the manifest, load should either load the
42797
            // main manifest, or do nothing but trigger an event
42798
 
42799
            if (!this.started) {
42800
                this.start();
42801
                return;
42802
            }
42803
            if (media && !media.endList) {
42804
                // Check to see if this is the main loader and the MUP was cleared (this happens
42805
                // when the loader was paused). `media` should be set at this point since one is always
42806
                // set during `start()`.
42807
                if (this.isMain_ && !this.minimumUpdatePeriodTimeout_) {
42808
                    // Trigger minimumUpdatePeriod to refresh the main manifest
42809
                    this.trigger('minimumUpdatePeriod'); // Since there was no prior minimumUpdatePeriodTimeout it should be recreated
42810
 
42811
                    this.updateMinimumUpdatePeriodTimeout_();
42812
                }
42813
                this.trigger('mediaupdatetimeout');
42814
            } else {
42815
                this.trigger('loadedplaylist');
42816
            }
42817
        }
42818
        start() {
42819
            this.started = true; // We don't need to request the main manifest again
42820
            // Call this asynchronously to match the xhr request behavior below
42821
 
42822
            if (!this.isMain_) {
42823
                this.mediaRequest_ = window.setTimeout(() => this.haveMain_(), 0);
42824
                return;
42825
            }
42826
            this.requestMain_((req, mainChanged) => {
42827
                this.haveMain_();
42828
                if (!this.hasPendingRequest() && !this.media_) {
42829
                    this.media(this.mainPlaylistLoader_.main.playlists[0]);
42830
                }
42831
            });
42832
        }
42833
        requestMain_(cb) {
42834
            this.request = this.vhs_.xhr({
42835
                uri: this.mainPlaylistLoader_.srcUrl,
42836
                withCredentials: this.withCredentials
42837
            }, (error, req) => {
42838
                if (this.requestErrored_(error, req)) {
42839
                    if (this.state === 'HAVE_NOTHING') {
42840
                        this.started = false;
42841
                    }
42842
                    return;
42843
                }
42844
                const mainChanged = req.responseText !== this.mainPlaylistLoader_.mainXml_;
42845
                this.mainPlaylistLoader_.mainXml_ = req.responseText;
42846
                if (req.responseHeaders && req.responseHeaders.date) {
42847
                    this.mainLoaded_ = Date.parse(req.responseHeaders.date);
42848
                } else {
42849
                    this.mainLoaded_ = Date.now();
42850
                }
42851
                this.mainPlaylistLoader_.srcUrl = resolveManifestRedirect(this.mainPlaylistLoader_.srcUrl, req);
42852
                if (mainChanged) {
42853
                    this.handleMain_();
42854
                    this.syncClientServerClock_(() => {
42855
                        return cb(req, mainChanged);
42856
                    });
42857
                    return;
42858
                }
42859
                return cb(req, mainChanged);
42860
            });
42861
        }
42862
        /**
42863
         * Parses the main xml for UTCTiming node to sync the client clock to the server
42864
         * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
42865
         *
42866
         * @param {Function} done
42867
         *        Function to call when clock sync has completed
42868
         */
42869
 
42870
        syncClientServerClock_(done) {
42871
            const utcTiming = parseUTCTiming(this.mainPlaylistLoader_.mainXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the
42872
            // server clock
42873
 
42874
            if (utcTiming === null) {
42875
                this.mainPlaylistLoader_.clientOffset_ = this.mainLoaded_ - Date.now();
42876
                return done();
42877
            }
42878
            if (utcTiming.method === 'DIRECT') {
42879
                this.mainPlaylistLoader_.clientOffset_ = utcTiming.value - Date.now();
42880
                return done();
42881
            }
42882
            this.request = this.vhs_.xhr({
42883
                uri: resolveUrl(this.mainPlaylistLoader_.srcUrl, utcTiming.value),
42884
                method: utcTiming.method,
42885
                withCredentials: this.withCredentials
42886
            }, (error, req) => {
42887
                // disposed
42888
                if (!this.request) {
42889
                    return;
42890
                }
42891
                if (error) {
42892
                    // sync request failed, fall back to using date header from mpd
42893
                    // TODO: log warning
42894
                    this.mainPlaylistLoader_.clientOffset_ = this.mainLoaded_ - Date.now();
42895
                    return done();
42896
                }
42897
                let serverTime;
42898
                if (utcTiming.method === 'HEAD') {
42899
                    if (!req.responseHeaders || !req.responseHeaders.date) {
42900
                        // expected date header not preset, fall back to using date header from mpd
42901
                        // TODO: log warning
42902
                        serverTime = this.mainLoaded_;
42903
                    } else {
42904
                        serverTime = Date.parse(req.responseHeaders.date);
42905
                    }
42906
                } else {
42907
                    serverTime = Date.parse(req.responseText);
42908
                }
42909
                this.mainPlaylistLoader_.clientOffset_ = serverTime - Date.now();
42910
                done();
42911
            });
42912
        }
42913
        haveMain_() {
42914
            this.state = 'HAVE_MAIN_MANIFEST';
42915
            if (this.isMain_) {
42916
                // We have the main playlist at this point, so
42917
                // trigger this to allow PlaylistController
42918
                // to make an initial playlist selection
42919
                this.trigger('loadedplaylist');
42920
            } else if (!this.media_) {
42921
                // no media playlist was specifically selected so select
42922
                // the one the child playlist loader was created with
42923
                this.media(this.childPlaylist_);
42924
            }
42925
        }
42926
        handleMain_() {
42927
            // clear media request
42928
            this.mediaRequest_ = null;
42929
            const oldMain = this.mainPlaylistLoader_.main;
42930
            let newMain = parseMainXml({
42931
                mainXml: this.mainPlaylistLoader_.mainXml_,
42932
                srcUrl: this.mainPlaylistLoader_.srcUrl,
42933
                clientOffset: this.mainPlaylistLoader_.clientOffset_,
42934
                sidxMapping: this.mainPlaylistLoader_.sidxMapping_,
42935
                previousManifest: oldMain
42936
            }); // if we have an old main to compare the new main against
42937
 
42938
            if (oldMain) {
42939
                newMain = updateMain(oldMain, newMain, this.mainPlaylistLoader_.sidxMapping_);
42940
            } // only update main if we have a new main
42941
 
42942
            this.mainPlaylistLoader_.main = newMain ? newMain : oldMain;
42943
            const location = this.mainPlaylistLoader_.main.locations && this.mainPlaylistLoader_.main.locations[0];
42944
            if (location && location !== this.mainPlaylistLoader_.srcUrl) {
42945
                this.mainPlaylistLoader_.srcUrl = location;
42946
            }
42947
            if (!oldMain || newMain && newMain.minimumUpdatePeriod !== oldMain.minimumUpdatePeriod) {
42948
                this.updateMinimumUpdatePeriodTimeout_();
42949
            }
42950
            this.addEventStreamToMetadataTrack_(newMain);
42951
            return Boolean(newMain);
42952
        }
42953
        updateMinimumUpdatePeriodTimeout_() {
42954
            const mpl = this.mainPlaylistLoader_; // cancel any pending creation of mup on media
42955
            // a new one will be added if needed.
42956
 
42957
            if (mpl.createMupOnMedia_) {
42958
                mpl.off('loadedmetadata', mpl.createMupOnMedia_);
42959
                mpl.createMupOnMedia_ = null;
42960
            } // clear any pending timeouts
42961
 
42962
            if (mpl.minimumUpdatePeriodTimeout_) {
42963
                window.clearTimeout(mpl.minimumUpdatePeriodTimeout_);
42964
                mpl.minimumUpdatePeriodTimeout_ = null;
42965
            }
42966
            let mup = mpl.main && mpl.main.minimumUpdatePeriod; // If the minimumUpdatePeriod has a value of 0, that indicates that the current
42967
            // MPD has no future validity, so a new one will need to be acquired when new
42968
            // media segments are to be made available. Thus, we use the target duration
42969
            // in this case
42970
 
42971
            if (mup === 0) {
42972
                if (mpl.media()) {
42973
                    mup = mpl.media().targetDuration * 1000;
42974
                } else {
42975
                    mpl.createMupOnMedia_ = mpl.updateMinimumUpdatePeriodTimeout_;
42976
                    mpl.one('loadedmetadata', mpl.createMupOnMedia_);
42977
                }
42978
            } // if minimumUpdatePeriod is invalid or <= zero, which
42979
            // can happen when a live video becomes VOD. skip timeout
42980
            // creation.
42981
 
42982
            if (typeof mup !== 'number' || mup <= 0) {
42983
                if (mup < 0) {
42984
                    this.logger_(`found invalid minimumUpdatePeriod of ${mup}, not setting a timeout`);
42985
                }
42986
                return;
42987
            }
42988
            this.createMUPTimeout_(mup);
42989
        }
42990
        createMUPTimeout_(mup) {
42991
            const mpl = this.mainPlaylistLoader_;
42992
            mpl.minimumUpdatePeriodTimeout_ = window.setTimeout(() => {
42993
                mpl.minimumUpdatePeriodTimeout_ = null;
42994
                mpl.trigger('minimumUpdatePeriod');
42995
                mpl.createMUPTimeout_(mup);
42996
            }, mup);
42997
        }
42998
        /**
42999
         * Sends request to refresh the main xml and updates the parsed main manifest
43000
         */
43001
 
43002
        refreshXml_() {
43003
            this.requestMain_((req, mainChanged) => {
43004
                if (!mainChanged) {
43005
                    return;
43006
                }
43007
                if (this.media_) {
43008
                    this.media_ = this.mainPlaylistLoader_.main.playlists[this.media_.id];
43009
                } // This will filter out updated sidx info from the mapping
43010
 
43011
                this.mainPlaylistLoader_.sidxMapping_ = filterChangedSidxMappings(this.mainPlaylistLoader_.main, this.mainPlaylistLoader_.sidxMapping_);
43012
                this.addSidxSegments_(this.media(), this.state, sidxChanged => {
43013
                    // TODO: do we need to reload the current playlist?
43014
                    this.refreshMedia_(this.media().id);
43015
                });
43016
            });
43017
        }
43018
        /**
43019
         * Refreshes the media playlist by re-parsing the main xml and updating playlist
43020
         * references. If this is an alternate loader, the updated parsed manifest is retrieved
43021
         * from the main loader.
43022
         */
43023
 
43024
        refreshMedia_(mediaID) {
43025
            if (!mediaID) {
43026
                throw new Error('refreshMedia_ must take a media id');
43027
            } // for main we have to reparse the main xml
43028
            // to re-create segments based on current timing values
43029
            // which may change media. We only skip updating the main manifest
43030
            // if this is the first time this.media_ is being set.
43031
            // as main was just parsed in that case.
43032
 
43033
            if (this.media_ && this.isMain_) {
43034
                this.handleMain_();
43035
            }
43036
            const playlists = this.mainPlaylistLoader_.main.playlists;
43037
            const mediaChanged = !this.media_ || this.media_ !== playlists[mediaID];
43038
            if (mediaChanged) {
43039
                this.media_ = playlists[mediaID];
43040
            } else {
43041
                this.trigger('playlistunchanged');
43042
            }
43043
            if (!this.mediaUpdateTimeout) {
43044
                const createMediaUpdateTimeout = () => {
43045
                    if (this.media().endList) {
43046
                        return;
43047
                    }
43048
                    this.mediaUpdateTimeout = window.setTimeout(() => {
43049
                        this.trigger('mediaupdatetimeout');
43050
                        createMediaUpdateTimeout();
43051
                    }, refreshDelay(this.media(), Boolean(mediaChanged)));
43052
                };
43053
                createMediaUpdateTimeout();
43054
            }
43055
            this.trigger('loadedplaylist');
43056
        }
43057
        /**
43058
         * Takes eventstream data from a parsed DASH manifest and adds it to the metadata text track.
43059
         *
43060
         * @param {manifest} newMain the newly parsed manifest
43061
         */
43062
 
43063
        addEventStreamToMetadataTrack_(newMain) {
43064
            // Only add new event stream metadata if we have a new manifest.
43065
            if (newMain && this.mainPlaylistLoader_.main.eventStream) {
43066
                // convert EventStream to ID3-like data.
43067
                const metadataArray = this.mainPlaylistLoader_.main.eventStream.map(eventStreamNode => {
43068
                    return {
43069
                        cueTime: eventStreamNode.start,
43070
                        frames: [{
43071
                            data: eventStreamNode.messageData
43072
                        }]
43073
                    };
43074
                });
43075
                this.addMetadataToTextTrack('EventStream', metadataArray, this.mainPlaylistLoader_.main.duration);
43076
            }
43077
        }
43078
        /**
43079
         * Returns the key ID set from a playlist
43080
         *
43081
         * @param {playlist} playlist to fetch the key ID set from.
43082
         * @return a Set of 32 digit hex strings that represent the unique keyIds for that playlist.
43083
         */
43084
 
43085
        getKeyIdSet(playlist) {
43086
            if (playlist.contentProtection) {
43087
                const keyIds = new Set();
43088
                for (const keysystem in playlist.contentProtection) {
43089
                    const defaultKID = playlist.contentProtection[keysystem].attributes['cenc:default_KID'];
43090
                    if (defaultKID) {
43091
                        // DASH keyIds are separated by dashes.
43092
                        keyIds.add(defaultKID.replace(/-/g, '').toLowerCase());
43093
                    }
43094
                }
43095
                return keyIds;
43096
            }
43097
        }
43098
    }
43099
    var Config = {
43100
        GOAL_BUFFER_LENGTH: 30,
43101
        MAX_GOAL_BUFFER_LENGTH: 60,
43102
        BACK_BUFFER_LENGTH: 30,
43103
        GOAL_BUFFER_LENGTH_RATE: 1,
43104
        // 0.5 MB/s
43105
        INITIAL_BANDWIDTH: 4194304,
43106
        // A fudge factor to apply to advertised playlist bitrates to account for
43107
        // temporary flucations in client bandwidth
43108
        BANDWIDTH_VARIANCE: 1.2,
43109
        // How much of the buffer must be filled before we consider upswitching
43110
        BUFFER_LOW_WATER_LINE: 0,
43111
        MAX_BUFFER_LOW_WATER_LINE: 30,
43112
        // TODO: Remove this when experimentalBufferBasedABR is removed
43113
        EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE: 16,
43114
        BUFFER_LOW_WATER_LINE_RATE: 1,
43115
        // If the buffer is greater than the high water line, we won't switch down
43116
        BUFFER_HIGH_WATER_LINE: 30
43117
    };
43118
    const stringToArrayBuffer = string => {
43119
        const view = new Uint8Array(new ArrayBuffer(string.length));
43120
        for (let i = 0; i < string.length; i++) {
43121
            view[i] = string.charCodeAt(i);
43122
        }
43123
        return view.buffer;
43124
    };
43125
 
43126
    /* global Blob, BlobBuilder, Worker */
43127
    // unify worker interface
43128
    const browserWorkerPolyFill = function (workerObj) {
43129
        // node only supports on/off
43130
        workerObj.on = workerObj.addEventListener;
43131
        workerObj.off = workerObj.removeEventListener;
43132
        return workerObj;
43133
    };
43134
    const createObjectURL = function (str) {
43135
        try {
43136
            return URL.createObjectURL(new Blob([str], {
43137
                type: 'application/javascript'
43138
            }));
43139
        } catch (e) {
43140
            const blob = new BlobBuilder();
43141
            blob.append(str);
43142
            return URL.createObjectURL(blob.getBlob());
43143
        }
43144
    };
43145
    const factory = function (code) {
43146
        return function () {
43147
            const objectUrl = createObjectURL(code);
43148
            const worker = browserWorkerPolyFill(new Worker(objectUrl));
43149
            worker.objURL = objectUrl;
43150
            const terminate = worker.terminate;
43151
            worker.on = worker.addEventListener;
43152
            worker.off = worker.removeEventListener;
43153
            worker.terminate = function () {
43154
                URL.revokeObjectURL(objectUrl);
43155
                return terminate.call(this);
43156
            };
43157
            return worker;
43158
        };
43159
    };
43160
    const transform = function (code) {
43161
        return `var browserWorkerPolyFill = ${browserWorkerPolyFill.toString()};\n` + 'browserWorkerPolyFill(self);\n' + code;
43162
    };
43163
    const getWorkerString = function (fn) {
43164
        return fn.toString().replace(/^function.+?{/, '').slice(0, -1);
43165
    };
43166
 
43167
    /* rollup-plugin-worker-factory start for worker!/home/runner/work/http-streaming/http-streaming/src/transmuxer-worker.js */
43168
    const workerCode$1 = transform(getWorkerString(function () {
43169
        var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
43170
        /**
43171
         * mux.js
43172
         *
43173
         * Copyright (c) Brightcove
43174
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
43175
         *
43176
         * A lightweight readable stream implemention that handles event dispatching.
43177
         * Objects that inherit from streams should call init in their constructors.
43178
         */
43179
 
43180
        var Stream$8 = function () {
43181
            this.init = function () {
43182
                var listeners = {};
43183
                /**
43184
                 * Add a listener for a specified event type.
43185
                 * @param type {string} the event name
43186
                 * @param listener {function} the callback to be invoked when an event of
43187
                 * the specified type occurs
43188
                 */
43189
 
43190
                this.on = function (type, listener) {
43191
                    if (!listeners[type]) {
43192
                        listeners[type] = [];
43193
                    }
43194
                    listeners[type] = listeners[type].concat(listener);
43195
                };
43196
                /**
43197
                 * Remove a listener for a specified event type.
43198
                 * @param type {string} the event name
43199
                 * @param listener {function} a function previously registered for this
43200
                 * type of event through `on`
43201
                 */
43202
 
43203
                this.off = function (type, listener) {
43204
                    var index;
43205
                    if (!listeners[type]) {
43206
                        return false;
43207
                    }
43208
                    index = listeners[type].indexOf(listener);
43209
                    listeners[type] = listeners[type].slice();
43210
                    listeners[type].splice(index, 1);
43211
                    return index > -1;
43212
                };
43213
                /**
43214
                 * Trigger an event of the specified type on this stream. Any additional
43215
                 * arguments to this function are passed as parameters to event listeners.
43216
                 * @param type {string} the event name
43217
                 */
43218
 
43219
                this.trigger = function (type) {
43220
                    var callbacks, i, length, args;
43221
                    callbacks = listeners[type];
43222
                    if (!callbacks) {
43223
                        return;
43224
                    } // Slicing the arguments on every invocation of this method
43225
                    // can add a significant amount of overhead. Avoid the
43226
                    // intermediate object creation for the common case of a
43227
                    // single callback argument
43228
 
43229
                    if (arguments.length === 2) {
43230
                        length = callbacks.length;
43231
                        for (i = 0; i < length; ++i) {
43232
                            callbacks[i].call(this, arguments[1]);
43233
                        }
43234
                    } else {
43235
                        args = [];
43236
                        i = arguments.length;
43237
                        for (i = 1; i < arguments.length; ++i) {
43238
                            args.push(arguments[i]);
43239
                        }
43240
                        length = callbacks.length;
43241
                        for (i = 0; i < length; ++i) {
43242
                            callbacks[i].apply(this, args);
43243
                        }
43244
                    }
43245
                };
43246
                /**
43247
                 * Destroys the stream and cleans up.
43248
                 */
43249
 
43250
                this.dispose = function () {
43251
                    listeners = {};
43252
                };
43253
            };
43254
        };
43255
        /**
43256
         * Forwards all `data` events on this stream to the destination stream. The
43257
         * destination stream should provide a method `push` to receive the data
43258
         * events as they arrive.
43259
         * @param destination {stream} the stream that will receive all `data` events
43260
         * @param autoFlush {boolean} if false, we will not call `flush` on the destination
43261
         *                            when the current stream emits a 'done' event
43262
         * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
43263
         */
43264
 
43265
        Stream$8.prototype.pipe = function (destination) {
43266
            this.on('data', function (data) {
43267
                destination.push(data);
43268
            });
43269
            this.on('done', function (flushSource) {
43270
                destination.flush(flushSource);
43271
            });
43272
            this.on('partialdone', function (flushSource) {
43273
                destination.partialFlush(flushSource);
43274
            });
43275
            this.on('endedtimeline', function (flushSource) {
43276
                destination.endTimeline(flushSource);
43277
            });
43278
            this.on('reset', function (flushSource) {
43279
                destination.reset(flushSource);
43280
            });
43281
            return destination;
43282
        }; // Default stream functions that are expected to be overridden to perform
43283
        // actual work. These are provided by the prototype as a sort of no-op
43284
        // implementation so that we don't have to check for their existence in the
43285
        // `pipe` function above.
43286
 
43287
        Stream$8.prototype.push = function (data) {
43288
            this.trigger('data', data);
43289
        };
43290
        Stream$8.prototype.flush = function (flushSource) {
43291
            this.trigger('done', flushSource);
43292
        };
43293
        Stream$8.prototype.partialFlush = function (flushSource) {
43294
            this.trigger('partialdone', flushSource);
43295
        };
43296
        Stream$8.prototype.endTimeline = function (flushSource) {
43297
            this.trigger('endedtimeline', flushSource);
43298
        };
43299
        Stream$8.prototype.reset = function (flushSource) {
43300
            this.trigger('reset', flushSource);
43301
        };
43302
        var stream = Stream$8;
43303
        var MAX_UINT32$1 = Math.pow(2, 32);
43304
        var getUint64$3 = function (uint8) {
43305
            var dv = new DataView(uint8.buffer, uint8.byteOffset, uint8.byteLength);
43306
            var value;
43307
            if (dv.getBigUint64) {
43308
                value = dv.getBigUint64(0);
43309
                if (value < Number.MAX_SAFE_INTEGER) {
43310
                    return Number(value);
43311
                }
43312
                return value;
43313
            }
43314
            return dv.getUint32(0) * MAX_UINT32$1 + dv.getUint32(4);
43315
        };
43316
        var numbers = {
43317
            getUint64: getUint64$3,
43318
            MAX_UINT32: MAX_UINT32$1
43319
        };
43320
        /**
43321
         * mux.js
43322
         *
43323
         * Copyright (c) Brightcove
43324
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
43325
         *
43326
         * Functions that generate fragmented MP4s suitable for use with Media
43327
         * Source Extensions.
43328
         */
43329
 
43330
        var MAX_UINT32 = numbers.MAX_UINT32;
43331
        var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex, trun$1, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS; // pre-calculate constants
43332
 
43333
        (function () {
43334
            var i;
43335
            types = {
43336
                avc1: [],
43337
                // codingname
43338
                avcC: [],
43339
                btrt: [],
43340
                dinf: [],
43341
                dref: [],
43342
                esds: [],
43343
                ftyp: [],
43344
                hdlr: [],
43345
                mdat: [],
43346
                mdhd: [],
43347
                mdia: [],
43348
                mfhd: [],
43349
                minf: [],
43350
                moof: [],
43351
                moov: [],
43352
                mp4a: [],
43353
                // codingname
43354
                mvex: [],
43355
                mvhd: [],
43356
                pasp: [],
43357
                sdtp: [],
43358
                smhd: [],
43359
                stbl: [],
43360
                stco: [],
43361
                stsc: [],
43362
                stsd: [],
43363
                stsz: [],
43364
                stts: [],
43365
                styp: [],
43366
                tfdt: [],
43367
                tfhd: [],
43368
                traf: [],
43369
                trak: [],
43370
                trun: [],
43371
                trex: [],
43372
                tkhd: [],
43373
                vmhd: []
43374
            }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
43375
            // don't throw an error
43376
 
43377
            if (typeof Uint8Array === 'undefined') {
43378
                return;
43379
            }
43380
            for (i in types) {
43381
                if (types.hasOwnProperty(i)) {
43382
                    types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
43383
                }
43384
            }
43385
            MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
43386
            AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
43387
            MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
43388
            VIDEO_HDLR = new Uint8Array([0x00,
43389
                // version 0
43390
                0x00, 0x00, 0x00,
43391
                // flags
43392
                0x00, 0x00, 0x00, 0x00,
43393
                // pre_defined
43394
                0x76, 0x69, 0x64, 0x65,
43395
                // handler_type: 'vide'
43396
                0x00, 0x00, 0x00, 0x00,
43397
                // reserved
43398
                0x00, 0x00, 0x00, 0x00,
43399
                // reserved
43400
                0x00, 0x00, 0x00, 0x00,
43401
                // reserved
43402
                0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
43403
            ]);
43404
 
43405
            AUDIO_HDLR = new Uint8Array([0x00,
43406
                // version 0
43407
                0x00, 0x00, 0x00,
43408
                // flags
43409
                0x00, 0x00, 0x00, 0x00,
43410
                // pre_defined
43411
                0x73, 0x6f, 0x75, 0x6e,
43412
                // handler_type: 'soun'
43413
                0x00, 0x00, 0x00, 0x00,
43414
                // reserved
43415
                0x00, 0x00, 0x00, 0x00,
43416
                // reserved
43417
                0x00, 0x00, 0x00, 0x00,
43418
                // reserved
43419
                0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
43420
            ]);
43421
 
43422
            HDLR_TYPES = {
43423
                video: VIDEO_HDLR,
43424
                audio: AUDIO_HDLR
43425
            };
43426
            DREF = new Uint8Array([0x00,
43427
                // version 0
43428
                0x00, 0x00, 0x00,
43429
                // flags
43430
                0x00, 0x00, 0x00, 0x01,
43431
                // entry_count
43432
                0x00, 0x00, 0x00, 0x0c,
43433
                // entry_size
43434
                0x75, 0x72, 0x6c, 0x20,
43435
                // 'url' type
43436
                0x00,
43437
                // version 0
43438
                0x00, 0x00, 0x01 // entry_flags
43439
            ]);
43440
 
43441
            SMHD = new Uint8Array([0x00,
43442
                // version
43443
                0x00, 0x00, 0x00,
43444
                // flags
43445
                0x00, 0x00,
43446
                // balance, 0 means centered
43447
                0x00, 0x00 // reserved
43448
            ]);
43449
 
43450
            STCO = new Uint8Array([0x00,
43451
                // version
43452
                0x00, 0x00, 0x00,
43453
                // flags
43454
                0x00, 0x00, 0x00, 0x00 // entry_count
43455
            ]);
43456
 
43457
            STSC = STCO;
43458
            STSZ = new Uint8Array([0x00,
43459
                // version
43460
                0x00, 0x00, 0x00,
43461
                // flags
43462
                0x00, 0x00, 0x00, 0x00,
43463
                // sample_size
43464
                0x00, 0x00, 0x00, 0x00 // sample_count
43465
            ]);
43466
 
43467
            STTS = STCO;
43468
            VMHD = new Uint8Array([0x00,
43469
                // version
43470
                0x00, 0x00, 0x01,
43471
                // flags
43472
                0x00, 0x00,
43473
                // graphicsmode
43474
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
43475
            ]);
43476
        })();
43477
 
43478
        box = function (type) {
43479
            var payload = [],
43480
                size = 0,
43481
                i,
43482
                result,
43483
                view;
43484
            for (i = 1; i < arguments.length; i++) {
43485
                payload.push(arguments[i]);
43486
            }
43487
            i = payload.length; // calculate the total size we need to allocate
43488
 
43489
            while (i--) {
43490
                size += payload[i].byteLength;
43491
            }
43492
            result = new Uint8Array(size + 8);
43493
            view = new DataView(result.buffer, result.byteOffset, result.byteLength);
43494
            view.setUint32(0, result.byteLength);
43495
            result.set(type, 4); // copy the payload into the result
43496
 
43497
            for (i = 0, size = 8; i < payload.length; i++) {
43498
                result.set(payload[i], size);
43499
                size += payload[i].byteLength;
43500
            }
43501
            return result;
43502
        };
43503
        dinf = function () {
43504
            return box(types.dinf, box(types.dref, DREF));
43505
        };
43506
        esds = function (track) {
43507
            return box(types.esds, new Uint8Array([0x00,
43508
                // version
43509
                0x00, 0x00, 0x00,
43510
                // flags
43511
                // ES_Descriptor
43512
                0x03,
43513
                // tag, ES_DescrTag
43514
                0x19,
43515
                // length
43516
                0x00, 0x00,
43517
                // ES_ID
43518
                0x00,
43519
                // streamDependenceFlag, URL_flag, reserved, streamPriority
43520
                // DecoderConfigDescriptor
43521
                0x04,
43522
                // tag, DecoderConfigDescrTag
43523
                0x11,
43524
                // length
43525
                0x40,
43526
                // object type
43527
                0x15,
43528
                // streamType
43529
                0x00, 0x06, 0x00,
43530
                // bufferSizeDB
43531
                0x00, 0x00, 0xda, 0xc0,
43532
                // maxBitrate
43533
                0x00, 0x00, 0xda, 0xc0,
43534
                // avgBitrate
43535
                // DecoderSpecificInfo
43536
                0x05,
43537
                // tag, DecoderSpecificInfoTag
43538
                0x02,
43539
                // length
43540
                // ISO/IEC 14496-3, AudioSpecificConfig
43541
                // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
43542
                track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
43543
            ]));
43544
        };
43545
 
43546
        ftyp = function () {
43547
            return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
43548
        };
43549
        hdlr = function (type) {
43550
            return box(types.hdlr, HDLR_TYPES[type]);
43551
        };
43552
        mdat = function (data) {
43553
            return box(types.mdat, data);
43554
        };
43555
        mdhd = function (track) {
43556
            var result = new Uint8Array([0x00,
43557
                // version 0
43558
                0x00, 0x00, 0x00,
43559
                // flags
43560
                0x00, 0x00, 0x00, 0x02,
43561
                // creation_time
43562
                0x00, 0x00, 0x00, 0x03,
43563
                // modification_time
43564
                0x00, 0x01, 0x5f, 0x90,
43565
                // timescale, 90,000 "ticks" per second
43566
                track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF,
43567
                // duration
43568
                0x55, 0xc4,
43569
                // 'und' language (undetermined)
43570
                0x00, 0x00]); // Use the sample rate from the track metadata, when it is
43571
            // defined. The sample rate can be parsed out of an ADTS header, for
43572
            // instance.
43573
 
43574
            if (track.samplerate) {
43575
                result[12] = track.samplerate >>> 24 & 0xFF;
43576
                result[13] = track.samplerate >>> 16 & 0xFF;
43577
                result[14] = track.samplerate >>> 8 & 0xFF;
43578
                result[15] = track.samplerate & 0xFF;
43579
            }
43580
            return box(types.mdhd, result);
43581
        };
43582
        mdia = function (track) {
43583
            return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
43584
        };
43585
        mfhd = function (sequenceNumber) {
43586
            return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00,
43587
                // flags
43588
                (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
43589
            ]));
43590
        };
43591
 
43592
        minf = function (track) {
43593
            return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
43594
        };
43595
        moof = function (sequenceNumber, tracks) {
43596
            var trackFragments = [],
43597
                i = tracks.length; // build traf boxes for each track fragment
43598
 
43599
            while (i--) {
43600
                trackFragments[i] = traf(tracks[i]);
43601
            }
43602
            return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
43603
        };
43604
        /**
43605
         * Returns a movie box.
43606
         * @param tracks {array} the tracks associated with this movie
43607
         * @see ISO/IEC 14496-12:2012(E), section 8.2.1
43608
         */
43609
 
43610
        moov = function (tracks) {
43611
            var i = tracks.length,
43612
                boxes = [];
43613
            while (i--) {
43614
                boxes[i] = trak(tracks[i]);
43615
            }
43616
            return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
43617
        };
43618
        mvex = function (tracks) {
43619
            var i = tracks.length,
43620
                boxes = [];
43621
            while (i--) {
43622
                boxes[i] = trex(tracks[i]);
43623
            }
43624
            return box.apply(null, [types.mvex].concat(boxes));
43625
        };
43626
        mvhd = function (duration) {
43627
            var bytes = new Uint8Array([0x00,
43628
                // version 0
43629
                0x00, 0x00, 0x00,
43630
                // flags
43631
                0x00, 0x00, 0x00, 0x01,
43632
                // creation_time
43633
                0x00, 0x00, 0x00, 0x02,
43634
                // modification_time
43635
                0x00, 0x01, 0x5f, 0x90,
43636
                // timescale, 90,000 "ticks" per second
43637
                (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF,
43638
                // duration
43639
                0x00, 0x01, 0x00, 0x00,
43640
                // 1.0 rate
43641
                0x01, 0x00,
43642
                // 1.0 volume
43643
                0x00, 0x00,
43644
                // reserved
43645
                0x00, 0x00, 0x00, 0x00,
43646
                // reserved
43647
                0x00, 0x00, 0x00, 0x00,
43648
                // reserved
43649
                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,
43650
                // transformation: unity matrix
43651
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43652
                // pre_defined
43653
                0xff, 0xff, 0xff, 0xff // next_track_ID
43654
            ]);
43655
 
43656
            return box(types.mvhd, bytes);
43657
        };
43658
        sdtp = function (track) {
43659
            var samples = track.samples || [],
43660
                bytes = new Uint8Array(4 + samples.length),
43661
                flags,
43662
                i; // leave the full box header (4 bytes) all zero
43663
            // write the sample table
43664
 
43665
            for (i = 0; i < samples.length; i++) {
43666
                flags = samples[i].flags;
43667
                bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
43668
            }
43669
            return box(types.sdtp, bytes);
43670
        };
43671
        stbl = function (track) {
43672
            return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
43673
        };
43674
        (function () {
43675
            var videoSample, audioSample;
43676
            stsd = function (track) {
43677
                return box(types.stsd, new Uint8Array([0x00,
43678
                    // version 0
43679
                    0x00, 0x00, 0x00,
43680
                    // flags
43681
                    0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
43682
            };
43683
            videoSample = function (track) {
43684
                var sps = track.sps || [],
43685
                    pps = track.pps || [],
43686
                    sequenceParameterSets = [],
43687
                    pictureParameterSets = [],
43688
                    i,
43689
                    avc1Box; // assemble the SPSs
43690
 
43691
                for (i = 0; i < sps.length; i++) {
43692
                    sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
43693
                    sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
43694
 
43695
                    sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
43696
                } // assemble the PPSs
43697
 
43698
                for (i = 0; i < pps.length; i++) {
43699
                    pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
43700
                    pictureParameterSets.push(pps[i].byteLength & 0xFF);
43701
                    pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
43702
                }
43703
                avc1Box = [types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43704
                    // reserved
43705
                    0x00, 0x01,
43706
                    // data_reference_index
43707
                    0x00, 0x00,
43708
                    // pre_defined
43709
                    0x00, 0x00,
43710
                    // reserved
43711
                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43712
                    // pre_defined
43713
                    (track.width & 0xff00) >> 8, track.width & 0xff,
43714
                    // width
43715
                    (track.height & 0xff00) >> 8, track.height & 0xff,
43716
                    // height
43717
                    0x00, 0x48, 0x00, 0x00,
43718
                    // horizresolution
43719
                    0x00, 0x48, 0x00, 0x00,
43720
                    // vertresolution
43721
                    0x00, 0x00, 0x00, 0x00,
43722
                    // reserved
43723
                    0x00, 0x01,
43724
                    // frame_count
43725
                    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,
43726
                    // compressorname
43727
                    0x00, 0x18,
43728
                    // depth = 24
43729
                    0x11, 0x11 // pre_defined = -1
43730
                ]), box(types.avcC, new Uint8Array([0x01,
43731
                    // configurationVersion
43732
                    track.profileIdc,
43733
                    // AVCProfileIndication
43734
                    track.profileCompatibility,
43735
                    // profile_compatibility
43736
                    track.levelIdc,
43737
                    // AVCLevelIndication
43738
                    0xff // lengthSizeMinusOne, hard-coded to 4 bytes
43739
                ].concat([sps.length],
43740
                    // numOfSequenceParameterSets
43741
                    sequenceParameterSets,
43742
                    // "SPS"
43743
                    [pps.length],
43744
                    // numOfPictureParameterSets
43745
                    pictureParameterSets // "PPS"
43746
                ))), box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
43747
                    // bufferSizeDB
43748
                    0x00, 0x2d, 0xc6, 0xc0,
43749
                    // maxBitrate
43750
                    0x00, 0x2d, 0xc6, 0xc0 // avgBitrate
43751
                ]))];
43752
 
43753
                if (track.sarRatio) {
43754
                    var hSpacing = track.sarRatio[0],
43755
                        vSpacing = track.sarRatio[1];
43756
                    avc1Box.push(box(types.pasp, new Uint8Array([(hSpacing & 0xFF000000) >> 24, (hSpacing & 0xFF0000) >> 16, (hSpacing & 0xFF00) >> 8, hSpacing & 0xFF, (vSpacing & 0xFF000000) >> 24, (vSpacing & 0xFF0000) >> 16, (vSpacing & 0xFF00) >> 8, vSpacing & 0xFF])));
43757
                }
43758
                return box.apply(null, avc1Box);
43759
            };
43760
            audioSample = function (track) {
43761
                return box(types.mp4a, new Uint8Array([
43762
                    // SampleEntry, ISO/IEC 14496-12
43763
                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43764
                    // reserved
43765
                    0x00, 0x01,
43766
                    // data_reference_index
43767
                    // AudioSampleEntry, ISO/IEC 14496-12
43768
                    0x00, 0x00, 0x00, 0x00,
43769
                    // reserved
43770
                    0x00, 0x00, 0x00, 0x00,
43771
                    // reserved
43772
                    (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff,
43773
                    // channelcount
43774
                    (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff,
43775
                    // samplesize
43776
                    0x00, 0x00,
43777
                    // pre_defined
43778
                    0x00, 0x00,
43779
                    // reserved
43780
                    (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
43781
                    // MP4AudioSampleEntry, ISO/IEC 14496-14
43782
                ]), esds(track));
43783
            };
43784
        })();
43785
        tkhd = function (track) {
43786
            var result = new Uint8Array([0x00,
43787
                // version 0
43788
                0x00, 0x00, 0x07,
43789
                // flags
43790
                0x00, 0x00, 0x00, 0x00,
43791
                // creation_time
43792
                0x00, 0x00, 0x00, 0x00,
43793
                // modification_time
43794
                (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF,
43795
                // track_ID
43796
                0x00, 0x00, 0x00, 0x00,
43797
                // reserved
43798
                (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF,
43799
                // duration
43800
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43801
                // reserved
43802
                0x00, 0x00,
43803
                // layer
43804
                0x00, 0x00,
43805
                // alternate_group
43806
                0x01, 0x00,
43807
                // non-audio track volume
43808
                0x00, 0x00,
43809
                // reserved
43810
                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,
43811
                // transformation: unity matrix
43812
                (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00,
43813
                // width
43814
                (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
43815
            ]);
43816
 
43817
            return box(types.tkhd, result);
43818
        };
43819
        /**
43820
         * Generate a track fragment (traf) box. A traf box collects metadata
43821
         * about tracks in a movie fragment (moof) box.
43822
         */
43823
 
43824
        traf = function (track) {
43825
            var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
43826
            trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00,
43827
                // version 0
43828
                0x00, 0x00, 0x3a,
43829
                // flags
43830
                (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF,
43831
                // track_ID
43832
                0x00, 0x00, 0x00, 0x01,
43833
                // sample_description_index
43834
                0x00, 0x00, 0x00, 0x00,
43835
                // default_sample_duration
43836
                0x00, 0x00, 0x00, 0x00,
43837
                // default_sample_size
43838
                0x00, 0x00, 0x00, 0x00 // default_sample_flags
43839
            ]));
43840
 
43841
            upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / MAX_UINT32);
43842
            lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % MAX_UINT32);
43843
            trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01,
43844
                // version 1
43845
                0x00, 0x00, 0x00,
43846
                // flags
43847
                // baseMediaDecodeTime
43848
                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
43849
            // the containing moof to the first payload byte of the associated
43850
            // mdat
43851
 
43852
            dataOffset = 32 +
43853
                // tfhd
43854
                20 +
43855
                // tfdt
43856
                8 +
43857
                // traf header
43858
                16 +
43859
                // mfhd
43860
                8 +
43861
                // moof header
43862
                8; // mdat header
43863
            // audio tracks require less metadata
43864
 
43865
            if (track.type === 'audio') {
43866
                trackFragmentRun = trun$1(track, dataOffset);
43867
                return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
43868
            } // video tracks should contain an independent and disposable samples
43869
            // box (sdtp)
43870
            // generate one and adjust offsets to match
43871
 
43872
            sampleDependencyTable = sdtp(track);
43873
            trackFragmentRun = trun$1(track, sampleDependencyTable.length + dataOffset);
43874
            return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
43875
        };
43876
        /**
43877
         * Generate a track box.
43878
         * @param track {object} a track definition
43879
         * @return {Uint8Array} the track box
43880
         */
43881
 
43882
        trak = function (track) {
43883
            track.duration = track.duration || 0xffffffff;
43884
            return box(types.trak, tkhd(track), mdia(track));
43885
        };
43886
        trex = function (track) {
43887
            var result = new Uint8Array([0x00,
43888
                // version 0
43889
                0x00, 0x00, 0x00,
43890
                // flags
43891
                (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF,
43892
                // track_ID
43893
                0x00, 0x00, 0x00, 0x01,
43894
                // default_sample_description_index
43895
                0x00, 0x00, 0x00, 0x00,
43896
                // default_sample_duration
43897
                0x00, 0x00, 0x00, 0x00,
43898
                // default_sample_size
43899
                0x00, 0x01, 0x00, 0x01 // default_sample_flags
43900
            ]); // the last two bytes of default_sample_flags is the sample
43901
            // degradation priority, a hint about the importance of this sample
43902
            // relative to others. Lower the degradation priority for all sample
43903
            // types other than video.
43904
 
43905
            if (track.type !== 'video') {
43906
                result[result.length - 1] = 0x00;
43907
            }
43908
            return box(types.trex, result);
43909
        };
43910
        (function () {
43911
            var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
43912
            // duration is present for the first sample, it will be present for
43913
            // all subsequent samples.
43914
            // see ISO/IEC 14496-12:2012, Section 8.8.8.1
43915
 
43916
            trunHeader = function (samples, offset) {
43917
                var durationPresent = 0,
43918
                    sizePresent = 0,
43919
                    flagsPresent = 0,
43920
                    compositionTimeOffset = 0; // trun flag constants
43921
 
43922
                if (samples.length) {
43923
                    if (samples[0].duration !== undefined) {
43924
                        durationPresent = 0x1;
43925
                    }
43926
                    if (samples[0].size !== undefined) {
43927
                        sizePresent = 0x2;
43928
                    }
43929
                    if (samples[0].flags !== undefined) {
43930
                        flagsPresent = 0x4;
43931
                    }
43932
                    if (samples[0].compositionTimeOffset !== undefined) {
43933
                        compositionTimeOffset = 0x8;
43934
                    }
43935
                }
43936
                return [0x00,
43937
                    // version 0
43938
                    0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01,
43939
                    // flags
43940
                    (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF,
43941
                    // sample_count
43942
                    (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
43943
                ];
43944
            };
43945
 
43946
            videoTrun = function (track, offset) {
43947
                var bytesOffest, bytes, header, samples, sample, i;
43948
                samples = track.samples || [];
43949
                offset += 8 + 12 + 16 * samples.length;
43950
                header = trunHeader(samples, offset);
43951
                bytes = new Uint8Array(header.length + samples.length * 16);
43952
                bytes.set(header);
43953
                bytesOffest = header.length;
43954
                for (i = 0; i < samples.length; i++) {
43955
                    sample = samples[i];
43956
                    bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
43957
                    bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
43958
                    bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
43959
                    bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
43960
 
43961
                    bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
43962
                    bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
43963
                    bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
43964
                    bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
43965
 
43966
                    bytes[bytesOffest++] = sample.flags.isLeading << 2 | sample.flags.dependsOn;
43967
                    bytes[bytesOffest++] = sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample;
43968
                    bytes[bytesOffest++] = sample.flags.degradationPriority & 0xF0 << 8;
43969
                    bytes[bytesOffest++] = sample.flags.degradationPriority & 0x0F; // sample_flags
43970
 
43971
                    bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF000000) >>> 24;
43972
                    bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF0000) >>> 16;
43973
                    bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF00) >>> 8;
43974
                    bytes[bytesOffest++] = sample.compositionTimeOffset & 0xFF; // sample_composition_time_offset
43975
                }
43976
 
43977
                return box(types.trun, bytes);
43978
            };
43979
            audioTrun = function (track, offset) {
43980
                var bytes, bytesOffest, header, samples, sample, i;
43981
                samples = track.samples || [];
43982
                offset += 8 + 12 + 8 * samples.length;
43983
                header = trunHeader(samples, offset);
43984
                bytes = new Uint8Array(header.length + samples.length * 8);
43985
                bytes.set(header);
43986
                bytesOffest = header.length;
43987
                for (i = 0; i < samples.length; i++) {
43988
                    sample = samples[i];
43989
                    bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
43990
                    bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
43991
                    bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
43992
                    bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
43993
 
43994
                    bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
43995
                    bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
43996
                    bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
43997
                    bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
43998
                }
43999
 
44000
                return box(types.trun, bytes);
44001
            };
44002
            trun$1 = function (track, offset) {
44003
                if (track.type === 'audio') {
44004
                    return audioTrun(track, offset);
44005
                }
44006
                return videoTrun(track, offset);
44007
            };
44008
        })();
44009
        var mp4Generator = {
44010
            ftyp: ftyp,
44011
            mdat: mdat,
44012
            moof: moof,
44013
            moov: moov,
44014
            initSegment: function (tracks) {
44015
                var fileType = ftyp(),
44016
                    movie = moov(tracks),
44017
                    result;
44018
                result = new Uint8Array(fileType.byteLength + movie.byteLength);
44019
                result.set(fileType);
44020
                result.set(movie, fileType.byteLength);
44021
                return result;
44022
            }
44023
        };
44024
        /**
44025
         * mux.js
44026
         *
44027
         * Copyright (c) Brightcove
44028
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44029
         */
44030
            // composed of the nal units that make up that frame
44031
            // Also keep track of cummulative data about the frame from the nal units such
44032
            // as the frame duration, starting pts, etc.
44033
 
44034
        var groupNalsIntoFrames = function (nalUnits) {
44035
                var i,
44036
                    currentNal,
44037
                    currentFrame = [],
44038
                    frames = []; // TODO added for LHLS, make sure this is OK
44039
 
44040
                frames.byteLength = 0;
44041
                frames.nalCount = 0;
44042
                frames.duration = 0;
44043
                currentFrame.byteLength = 0;
44044
                for (i = 0; i < nalUnits.length; i++) {
44045
                    currentNal = nalUnits[i]; // Split on 'aud'-type nal units
44046
 
44047
                    if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
44048
                        // Since the very first nal unit is expected to be an AUD
44049
                        // only push to the frames array when currentFrame is not empty
44050
                        if (currentFrame.length) {
44051
                            currentFrame.duration = currentNal.dts - currentFrame.dts; // TODO added for LHLS, make sure this is OK
44052
 
44053
                            frames.byteLength += currentFrame.byteLength;
44054
                            frames.nalCount += currentFrame.length;
44055
                            frames.duration += currentFrame.duration;
44056
                            frames.push(currentFrame);
44057
                        }
44058
                        currentFrame = [currentNal];
44059
                        currentFrame.byteLength = currentNal.data.byteLength;
44060
                        currentFrame.pts = currentNal.pts;
44061
                        currentFrame.dts = currentNal.dts;
44062
                    } else {
44063
                        // Specifically flag key frames for ease of use later
44064
                        if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
44065
                            currentFrame.keyFrame = true;
44066
                        }
44067
                        currentFrame.duration = currentNal.dts - currentFrame.dts;
44068
                        currentFrame.byteLength += currentNal.data.byteLength;
44069
                        currentFrame.push(currentNal);
44070
                    }
44071
                } // For the last frame, use the duration of the previous frame if we
44072
                // have nothing better to go on
44073
 
44074
                if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
44075
                    currentFrame.duration = frames[frames.length - 1].duration;
44076
                } // Push the final frame
44077
                // TODO added for LHLS, make sure this is OK
44078
 
44079
                frames.byteLength += currentFrame.byteLength;
44080
                frames.nalCount += currentFrame.length;
44081
                frames.duration += currentFrame.duration;
44082
                frames.push(currentFrame);
44083
                return frames;
44084
            }; // Convert an array of frames into an array of Gop with each Gop being composed
44085
        // of the frames that make up that Gop
44086
        // Also keep track of cummulative data about the Gop from the frames such as the
44087
        // Gop duration, starting pts, etc.
44088
 
44089
        var groupFramesIntoGops = function (frames) {
44090
            var i,
44091
                currentFrame,
44092
                currentGop = [],
44093
                gops = []; // We must pre-set some of the values on the Gop since we
44094
            // keep running totals of these values
44095
 
44096
            currentGop.byteLength = 0;
44097
            currentGop.nalCount = 0;
44098
            currentGop.duration = 0;
44099
            currentGop.pts = frames[0].pts;
44100
            currentGop.dts = frames[0].dts; // store some metadata about all the Gops
44101
 
44102
            gops.byteLength = 0;
44103
            gops.nalCount = 0;
44104
            gops.duration = 0;
44105
            gops.pts = frames[0].pts;
44106
            gops.dts = frames[0].dts;
44107
            for (i = 0; i < frames.length; i++) {
44108
                currentFrame = frames[i];
44109
                if (currentFrame.keyFrame) {
44110
                    // Since the very first frame is expected to be an keyframe
44111
                    // only push to the gops array when currentGop is not empty
44112
                    if (currentGop.length) {
44113
                        gops.push(currentGop);
44114
                        gops.byteLength += currentGop.byteLength;
44115
                        gops.nalCount += currentGop.nalCount;
44116
                        gops.duration += currentGop.duration;
44117
                    }
44118
                    currentGop = [currentFrame];
44119
                    currentGop.nalCount = currentFrame.length;
44120
                    currentGop.byteLength = currentFrame.byteLength;
44121
                    currentGop.pts = currentFrame.pts;
44122
                    currentGop.dts = currentFrame.dts;
44123
                    currentGop.duration = currentFrame.duration;
44124
                } else {
44125
                    currentGop.duration += currentFrame.duration;
44126
                    currentGop.nalCount += currentFrame.length;
44127
                    currentGop.byteLength += currentFrame.byteLength;
44128
                    currentGop.push(currentFrame);
44129
                }
44130
            }
44131
            if (gops.length && currentGop.duration <= 0) {
44132
                currentGop.duration = gops[gops.length - 1].duration;
44133
            }
44134
            gops.byteLength += currentGop.byteLength;
44135
            gops.nalCount += currentGop.nalCount;
44136
            gops.duration += currentGop.duration; // push the final Gop
44137
 
44138
            gops.push(currentGop);
44139
            return gops;
44140
        };
44141
        /*
44142
     * Search for the first keyframe in the GOPs and throw away all frames
44143
     * until that keyframe. Then extend the duration of the pulled keyframe
44144
     * and pull the PTS and DTS of the keyframe so that it covers the time
44145
     * range of the frames that were disposed.
44146
     *
44147
     * @param {Array} gops video GOPs
44148
     * @returns {Array} modified video GOPs
44149
     */
44150
 
44151
        var extendFirstKeyFrame = function (gops) {
44152
            var currentGop;
44153
            if (!gops[0][0].keyFrame && gops.length > 1) {
44154
                // Remove the first GOP
44155
                currentGop = gops.shift();
44156
                gops.byteLength -= currentGop.byteLength;
44157
                gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
44158
                // first gop to cover the time period of the
44159
                // frames we just removed
44160
 
44161
                gops[0][0].dts = currentGop.dts;
44162
                gops[0][0].pts = currentGop.pts;
44163
                gops[0][0].duration += currentGop.duration;
44164
            }
44165
            return gops;
44166
        };
44167
        /**
44168
         * Default sample object
44169
         * see ISO/IEC 14496-12:2012, section 8.6.4.3
44170
         */
44171
 
44172
        var createDefaultSample = function () {
44173
            return {
44174
                size: 0,
44175
                flags: {
44176
                    isLeading: 0,
44177
                    dependsOn: 1,
44178
                    isDependedOn: 0,
44179
                    hasRedundancy: 0,
44180
                    degradationPriority: 0,
44181
                    isNonSyncSample: 1
44182
                }
44183
            };
44184
        };
44185
        /*
44186
     * Collates information from a video frame into an object for eventual
44187
     * entry into an MP4 sample table.
44188
     *
44189
     * @param {Object} frame the video frame
44190
     * @param {Number} dataOffset the byte offset to position the sample
44191
     * @return {Object} object containing sample table info for a frame
44192
     */
44193
 
44194
        var sampleForFrame = function (frame, dataOffset) {
44195
            var sample = createDefaultSample();
44196
            sample.dataOffset = dataOffset;
44197
            sample.compositionTimeOffset = frame.pts - frame.dts;
44198
            sample.duration = frame.duration;
44199
            sample.size = 4 * frame.length; // Space for nal unit size
44200
 
44201
            sample.size += frame.byteLength;
44202
            if (frame.keyFrame) {
44203
                sample.flags.dependsOn = 2;
44204
                sample.flags.isNonSyncSample = 0;
44205
            }
44206
            return sample;
44207
        }; // generate the track's sample table from an array of gops
44208
 
44209
        var generateSampleTable$1 = function (gops, baseDataOffset) {
44210
            var h,
44211
                i,
44212
                sample,
44213
                currentGop,
44214
                currentFrame,
44215
                dataOffset = baseDataOffset || 0,
44216
                samples = [];
44217
            for (h = 0; h < gops.length; h++) {
44218
                currentGop = gops[h];
44219
                for (i = 0; i < currentGop.length; i++) {
44220
                    currentFrame = currentGop[i];
44221
                    sample = sampleForFrame(currentFrame, dataOffset);
44222
                    dataOffset += sample.size;
44223
                    samples.push(sample);
44224
                }
44225
            }
44226
            return samples;
44227
        }; // generate the track's raw mdat data from an array of gops
44228
 
44229
        var concatenateNalData = function (gops) {
44230
            var h,
44231
                i,
44232
                j,
44233
                currentGop,
44234
                currentFrame,
44235
                currentNal,
44236
                dataOffset = 0,
44237
                nalsByteLength = gops.byteLength,
44238
                numberOfNals = gops.nalCount,
44239
                totalByteLength = nalsByteLength + 4 * numberOfNals,
44240
                data = new Uint8Array(totalByteLength),
44241
                view = new DataView(data.buffer); // For each Gop..
44242
 
44243
            for (h = 0; h < gops.length; h++) {
44244
                currentGop = gops[h]; // For each Frame..
44245
 
44246
                for (i = 0; i < currentGop.length; i++) {
44247
                    currentFrame = currentGop[i]; // For each NAL..
44248
 
44249
                    for (j = 0; j < currentFrame.length; j++) {
44250
                        currentNal = currentFrame[j];
44251
                        view.setUint32(dataOffset, currentNal.data.byteLength);
44252
                        dataOffset += 4;
44253
                        data.set(currentNal.data, dataOffset);
44254
                        dataOffset += currentNal.data.byteLength;
44255
                    }
44256
                }
44257
            }
44258
            return data;
44259
        }; // generate the track's sample table from a frame
44260
 
44261
        var generateSampleTableForFrame = function (frame, baseDataOffset) {
44262
            var sample,
44263
                dataOffset = baseDataOffset || 0,
44264
                samples = [];
44265
            sample = sampleForFrame(frame, dataOffset);
44266
            samples.push(sample);
44267
            return samples;
44268
        }; // generate the track's raw mdat data from a frame
44269
 
44270
        var concatenateNalDataForFrame = function (frame) {
44271
            var i,
44272
                currentNal,
44273
                dataOffset = 0,
44274
                nalsByteLength = frame.byteLength,
44275
                numberOfNals = frame.length,
44276
                totalByteLength = nalsByteLength + 4 * numberOfNals,
44277
                data = new Uint8Array(totalByteLength),
44278
                view = new DataView(data.buffer); // For each NAL..
44279
 
44280
            for (i = 0; i < frame.length; i++) {
44281
                currentNal = frame[i];
44282
                view.setUint32(dataOffset, currentNal.data.byteLength);
44283
                dataOffset += 4;
44284
                data.set(currentNal.data, dataOffset);
44285
                dataOffset += currentNal.data.byteLength;
44286
            }
44287
            return data;
44288
        };
44289
        var frameUtils$1 = {
44290
            groupNalsIntoFrames: groupNalsIntoFrames,
44291
            groupFramesIntoGops: groupFramesIntoGops,
44292
            extendFirstKeyFrame: extendFirstKeyFrame,
44293
            generateSampleTable: generateSampleTable$1,
44294
            concatenateNalData: concatenateNalData,
44295
            generateSampleTableForFrame: generateSampleTableForFrame,
44296
            concatenateNalDataForFrame: concatenateNalDataForFrame
44297
        };
44298
        /**
44299
         * mux.js
44300
         *
44301
         * Copyright (c) Brightcove
44302
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44303
         */
44304
 
44305
        var highPrefix = [33, 16, 5, 32, 164, 27];
44306
        var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
44307
        var zeroFill = function (count) {
44308
            var a = [];
44309
            while (count--) {
44310
                a.push(0);
44311
            }
44312
            return a;
44313
        };
44314
        var makeTable = function (metaTable) {
44315
            return Object.keys(metaTable).reduce(function (obj, key) {
44316
                obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
44317
                    return arr.concat(part);
44318
                }, []));
44319
                return obj;
44320
            }, {});
44321
        };
44322
        var silence;
44323
        var silence_1 = function () {
44324
            if (!silence) {
44325
                // Frames-of-silence to use for filling in missing AAC frames
44326
                var coneOfSilence = {
44327
                    96000: [highPrefix, [227, 64], zeroFill(154), [56]],
44328
                    88200: [highPrefix, [231], zeroFill(170), [56]],
44329
                    64000: [highPrefix, [248, 192], zeroFill(240), [56]],
44330
                    48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
44331
                    44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
44332
                    32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
44333
                    24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
44334
                    16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
44335
                    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]],
44336
                    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]],
44337
                    8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
44338
                };
44339
                silence = makeTable(coneOfSilence);
44340
            }
44341
            return silence;
44342
        };
44343
        /**
44344
         * mux.js
44345
         *
44346
         * Copyright (c) Brightcove
44347
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44348
         */
44349
 
44350
        var ONE_SECOND_IN_TS$4 = 90000,
44351
            // 90kHz clock
44352
            secondsToVideoTs,
44353
            secondsToAudioTs,
44354
            videoTsToSeconds,
44355
            audioTsToSeconds,
44356
            audioTsToVideoTs,
44357
            videoTsToAudioTs,
44358
            metadataTsToSeconds;
44359
        secondsToVideoTs = function (seconds) {
44360
            return seconds * ONE_SECOND_IN_TS$4;
44361
        };
44362
        secondsToAudioTs = function (seconds, sampleRate) {
44363
            return seconds * sampleRate;
44364
        };
44365
        videoTsToSeconds = function (timestamp) {
44366
            return timestamp / ONE_SECOND_IN_TS$4;
44367
        };
44368
        audioTsToSeconds = function (timestamp, sampleRate) {
44369
            return timestamp / sampleRate;
44370
        };
44371
        audioTsToVideoTs = function (timestamp, sampleRate) {
44372
            return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
44373
        };
44374
        videoTsToAudioTs = function (timestamp, sampleRate) {
44375
            return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
44376
        };
44377
        /**
44378
         * Adjust ID3 tag or caption timing information by the timeline pts values
44379
         * (if keepOriginalTimestamps is false) and convert to seconds
44380
         */
44381
 
44382
        metadataTsToSeconds = function (timestamp, timelineStartPts, keepOriginalTimestamps) {
44383
            return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
44384
        };
44385
        var clock$2 = {
44386
            ONE_SECOND_IN_TS: ONE_SECOND_IN_TS$4,
44387
            secondsToVideoTs: secondsToVideoTs,
44388
            secondsToAudioTs: secondsToAudioTs,
44389
            videoTsToSeconds: videoTsToSeconds,
44390
            audioTsToSeconds: audioTsToSeconds,
44391
            audioTsToVideoTs: audioTsToVideoTs,
44392
            videoTsToAudioTs: videoTsToAudioTs,
44393
            metadataTsToSeconds: metadataTsToSeconds
44394
        };
44395
        /**
44396
         * mux.js
44397
         *
44398
         * Copyright (c) Brightcove
44399
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44400
         */
44401
 
44402
        var coneOfSilence = silence_1;
44403
        var clock$1 = clock$2;
44404
        /**
44405
         * Sum the `byteLength` properties of the data in each AAC frame
44406
         */
44407
 
44408
        var sumFrameByteLengths = function (array) {
44409
            var i,
44410
                currentObj,
44411
                sum = 0; // sum the byteLength's all each nal unit in the frame
44412
 
44413
            for (i = 0; i < array.length; i++) {
44414
                currentObj = array[i];
44415
                sum += currentObj.data.byteLength;
44416
            }
44417
            return sum;
44418
        }; // Possibly pad (prefix) the audio track with silence if appending this track
44419
        // would lead to the introduction of a gap in the audio buffer
44420
 
44421
        var prefixWithSilence = function (track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
44422
            var baseMediaDecodeTimeTs,
44423
                frameDuration = 0,
44424
                audioGapDuration = 0,
44425
                audioFillFrameCount = 0,
44426
                audioFillDuration = 0,
44427
                silentFrame,
44428
                i,
44429
                firstFrame;
44430
            if (!frames.length) {
44431
                return;
44432
            }
44433
            baseMediaDecodeTimeTs = clock$1.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
44434
 
44435
            frameDuration = Math.ceil(clock$1.ONE_SECOND_IN_TS / (track.samplerate / 1024));
44436
            if (audioAppendStartTs && videoBaseMediaDecodeTime) {
44437
                // insert the shortest possible amount (audio gap or audio to video gap)
44438
                audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
44439
 
44440
                audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
44441
                audioFillDuration = audioFillFrameCount * frameDuration;
44442
            } // don't attempt to fill gaps smaller than a single frame or larger
44443
            // than a half second
44444
 
44445
            if (audioFillFrameCount < 1 || audioFillDuration > clock$1.ONE_SECOND_IN_TS / 2) {
44446
                return;
44447
            }
44448
            silentFrame = coneOfSilence()[track.samplerate];
44449
            if (!silentFrame) {
44450
                // we don't have a silent frame pregenerated for the sample rate, so use a frame
44451
                // from the content instead
44452
                silentFrame = frames[0].data;
44453
            }
44454
            for (i = 0; i < audioFillFrameCount; i++) {
44455
                firstFrame = frames[0];
44456
                frames.splice(0, 0, {
44457
                    data: silentFrame,
44458
                    dts: firstFrame.dts - frameDuration,
44459
                    pts: firstFrame.pts - frameDuration
44460
                });
44461
            }
44462
            track.baseMediaDecodeTime -= Math.floor(clock$1.videoTsToAudioTs(audioFillDuration, track.samplerate));
44463
            return audioFillDuration;
44464
        }; // If the audio segment extends before the earliest allowed dts
44465
        // value, remove AAC frames until starts at or after the earliest
44466
        // allowed DTS so that we don't end up with a negative baseMedia-
44467
        // DecodeTime for the audio track
44468
 
44469
        var trimAdtsFramesByEarliestDts = function (adtsFrames, track, earliestAllowedDts) {
44470
            if (track.minSegmentDts >= earliestAllowedDts) {
44471
                return adtsFrames;
44472
            } // We will need to recalculate the earliest segment Dts
44473
 
44474
            track.minSegmentDts = Infinity;
44475
            return adtsFrames.filter(function (currentFrame) {
44476
                // If this is an allowed frame, keep it and record it's Dts
44477
                if (currentFrame.dts >= earliestAllowedDts) {
44478
                    track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
44479
                    track.minSegmentPts = track.minSegmentDts;
44480
                    return true;
44481
                } // Otherwise, discard it
44482
 
44483
                return false;
44484
            });
44485
        }; // generate the track's raw mdat data from an array of frames
44486
 
44487
        var generateSampleTable = function (frames) {
44488
            var i,
44489
                currentFrame,
44490
                samples = [];
44491
            for (i = 0; i < frames.length; i++) {
44492
                currentFrame = frames[i];
44493
                samples.push({
44494
                    size: currentFrame.data.byteLength,
44495
                    duration: 1024 // For AAC audio, all samples contain 1024 samples
44496
                });
44497
            }
44498
 
44499
            return samples;
44500
        }; // generate the track's sample table from an array of frames
44501
 
44502
        var concatenateFrameData = function (frames) {
44503
            var i,
44504
                currentFrame,
44505
                dataOffset = 0,
44506
                data = new Uint8Array(sumFrameByteLengths(frames));
44507
            for (i = 0; i < frames.length; i++) {
44508
                currentFrame = frames[i];
44509
                data.set(currentFrame.data, dataOffset);
44510
                dataOffset += currentFrame.data.byteLength;
44511
            }
44512
            return data;
44513
        };
44514
        var audioFrameUtils$1 = {
44515
            prefixWithSilence: prefixWithSilence,
44516
            trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
44517
            generateSampleTable: generateSampleTable,
44518
            concatenateFrameData: concatenateFrameData
44519
        };
44520
        /**
44521
         * mux.js
44522
         *
44523
         * Copyright (c) Brightcove
44524
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44525
         */
44526
 
44527
        var ONE_SECOND_IN_TS$3 = clock$2.ONE_SECOND_IN_TS;
44528
        /**
44529
         * Store information about the start and end of the track and the
44530
         * duration for each frame/sample we process in order to calculate
44531
         * the baseMediaDecodeTime
44532
         */
44533
 
44534
        var collectDtsInfo = function (track, data) {
44535
            if (typeof data.pts === 'number') {
44536
                if (track.timelineStartInfo.pts === undefined) {
44537
                    track.timelineStartInfo.pts = data.pts;
44538
                }
44539
                if (track.minSegmentPts === undefined) {
44540
                    track.minSegmentPts = data.pts;
44541
                } else {
44542
                    track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
44543
                }
44544
                if (track.maxSegmentPts === undefined) {
44545
                    track.maxSegmentPts = data.pts;
44546
                } else {
44547
                    track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
44548
                }
44549
            }
44550
            if (typeof data.dts === 'number') {
44551
                if (track.timelineStartInfo.dts === undefined) {
44552
                    track.timelineStartInfo.dts = data.dts;
44553
                }
44554
                if (track.minSegmentDts === undefined) {
44555
                    track.minSegmentDts = data.dts;
44556
                } else {
44557
                    track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
44558
                }
44559
                if (track.maxSegmentDts === undefined) {
44560
                    track.maxSegmentDts = data.dts;
44561
                } else {
44562
                    track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
44563
                }
44564
            }
44565
        };
44566
        /**
44567
         * Clear values used to calculate the baseMediaDecodeTime between
44568
         * tracks
44569
         */
44570
 
44571
        var clearDtsInfo = function (track) {
44572
            delete track.minSegmentDts;
44573
            delete track.maxSegmentDts;
44574
            delete track.minSegmentPts;
44575
            delete track.maxSegmentPts;
44576
        };
44577
        /**
44578
         * Calculate the track's baseMediaDecodeTime based on the earliest
44579
         * DTS the transmuxer has ever seen and the minimum DTS for the
44580
         * current track
44581
         * @param track {object} track metadata configuration
44582
         * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
44583
         *        in the source; false to adjust the first segment to start at 0.
44584
         */
44585
 
44586
        var calculateTrackBaseMediaDecodeTime = function (track, keepOriginalTimestamps) {
44587
            var baseMediaDecodeTime,
44588
                scale,
44589
                minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
44590
 
44591
            if (!keepOriginalTimestamps) {
44592
                minSegmentDts -= track.timelineStartInfo.dts;
44593
            } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
44594
            // we want the start of the first segment to be placed
44595
 
44596
            baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
44597
 
44598
            baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
44599
 
44600
            baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
44601
            if (track.type === 'audio') {
44602
                // Audio has a different clock equal to the sampling_rate so we need to
44603
                // scale the PTS values into the clock rate of the track
44604
                scale = track.samplerate / ONE_SECOND_IN_TS$3;
44605
                baseMediaDecodeTime *= scale;
44606
                baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
44607
            }
44608
            return baseMediaDecodeTime;
44609
        };
44610
        var trackDecodeInfo$1 = {
44611
            clearDtsInfo: clearDtsInfo,
44612
            calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
44613
            collectDtsInfo: collectDtsInfo
44614
        };
44615
        /**
44616
         * mux.js
44617
         *
44618
         * Copyright (c) Brightcove
44619
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44620
         *
44621
         * Reads in-band caption information from a video elementary
44622
         * stream. Captions must follow the CEA-708 standard for injection
44623
         * into an MPEG-2 transport streams.
44624
         * @see https://en.wikipedia.org/wiki/CEA-708
44625
         * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
44626
         */
44627
            // payload type field to indicate how they are to be
44628
            // interpreted. CEAS-708 caption content is always transmitted with
44629
            // payload type 0x04.
44630
 
44631
        var USER_DATA_REGISTERED_ITU_T_T35 = 4,
44632
            RBSP_TRAILING_BITS = 128;
44633
        /**
44634
         * Parse a supplemental enhancement information (SEI) NAL unit.
44635
         * Stops parsing once a message of type ITU T T35 has been found.
44636
         *
44637
         * @param bytes {Uint8Array} the bytes of a SEI NAL unit
44638
         * @return {object} the parsed SEI payload
44639
         * @see Rec. ITU-T H.264, 7.3.2.3.1
44640
         */
44641
 
44642
        var parseSei = function (bytes) {
44643
            var i = 0,
44644
                result = {
44645
                    payloadType: -1,
44646
                    payloadSize: 0
44647
                },
44648
                payloadType = 0,
44649
                payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
44650
 
44651
            while (i < bytes.byteLength) {
44652
                // stop once we have hit the end of the sei_rbsp
44653
                if (bytes[i] === RBSP_TRAILING_BITS) {
44654
                    break;
44655
                } // Parse payload type
44656
 
44657
                while (bytes[i] === 0xFF) {
44658
                    payloadType += 255;
44659
                    i++;
44660
                }
44661
                payloadType += bytes[i++]; // Parse payload size
44662
 
44663
                while (bytes[i] === 0xFF) {
44664
                    payloadSize += 255;
44665
                    i++;
44666
                }
44667
                payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
44668
                // there can only ever be one caption message in a frame's sei
44669
 
44670
                if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
44671
                    var userIdentifier = String.fromCharCode(bytes[i + 3], bytes[i + 4], bytes[i + 5], bytes[i + 6]);
44672
                    if (userIdentifier === 'GA94') {
44673
                        result.payloadType = payloadType;
44674
                        result.payloadSize = payloadSize;
44675
                        result.payload = bytes.subarray(i, i + payloadSize);
44676
                        break;
44677
                    } else {
44678
                        result.payload = void 0;
44679
                    }
44680
                } // skip the payload and parse the next message
44681
 
44682
                i += payloadSize;
44683
                payloadType = 0;
44684
                payloadSize = 0;
44685
            }
44686
            return result;
44687
        }; // see ANSI/SCTE 128-1 (2013), section 8.1
44688
 
44689
        var parseUserData = function (sei) {
44690
            // itu_t_t35_contry_code must be 181 (United States) for
44691
            // captions
44692
            if (sei.payload[0] !== 181) {
44693
                return null;
44694
            } // itu_t_t35_provider_code should be 49 (ATSC) for captions
44695
 
44696
            if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
44697
                return null;
44698
            } // the user_identifier should be "GA94" to indicate ATSC1 data
44699
 
44700
            if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
44701
                return null;
44702
            } // finally, user_data_type_code should be 0x03 for caption data
44703
 
44704
            if (sei.payload[7] !== 0x03) {
44705
                return null;
44706
            } // return the user_data_type_structure and strip the trailing
44707
            // marker bits
44708
 
44709
            return sei.payload.subarray(8, sei.payload.length - 1);
44710
        }; // see CEA-708-D, section 4.4
44711
 
44712
        var parseCaptionPackets = function (pts, userData) {
44713
            var results = [],
44714
                i,
44715
                count,
44716
                offset,
44717
                data; // if this is just filler, return immediately
44718
 
44719
            if (!(userData[0] & 0x40)) {
44720
                return results;
44721
            } // parse out the cc_data_1 and cc_data_2 fields
44722
 
44723
            count = userData[0] & 0x1f;
44724
            for (i = 0; i < count; i++) {
44725
                offset = i * 3;
44726
                data = {
44727
                    type: userData[offset + 2] & 0x03,
44728
                    pts: pts
44729
                }; // capture cc data when cc_valid is 1
44730
 
44731
                if (userData[offset + 2] & 0x04) {
44732
                    data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
44733
                    results.push(data);
44734
                }
44735
            }
44736
            return results;
44737
        };
44738
        var discardEmulationPreventionBytes$1 = function (data) {
44739
            var length = data.byteLength,
44740
                emulationPreventionBytesPositions = [],
44741
                i = 1,
44742
                newLength,
44743
                newData; // Find all `Emulation Prevention Bytes`
44744
 
44745
            while (i < length - 2) {
44746
                if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
44747
                    emulationPreventionBytesPositions.push(i + 2);
44748
                    i += 2;
44749
                } else {
44750
                    i++;
44751
                }
44752
            } // If no Emulation Prevention Bytes were found just return the original
44753
            // array
44754
 
44755
            if (emulationPreventionBytesPositions.length === 0) {
44756
                return data;
44757
            } // Create a new array to hold the NAL unit data
44758
 
44759
            newLength = length - emulationPreventionBytesPositions.length;
44760
            newData = new Uint8Array(newLength);
44761
            var sourceIndex = 0;
44762
            for (i = 0; i < newLength; sourceIndex++, i++) {
44763
                if (sourceIndex === emulationPreventionBytesPositions[0]) {
44764
                    // Skip this byte
44765
                    sourceIndex++; // Remove this position index
44766
 
44767
                    emulationPreventionBytesPositions.shift();
44768
                }
44769
                newData[i] = data[sourceIndex];
44770
            }
44771
            return newData;
44772
        }; // exports
44773
 
44774
        var captionPacketParser = {
44775
            parseSei: parseSei,
44776
            parseUserData: parseUserData,
44777
            parseCaptionPackets: parseCaptionPackets,
44778
            discardEmulationPreventionBytes: discardEmulationPreventionBytes$1,
44779
            USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
44780
        };
44781
        /**
44782
         * mux.js
44783
         *
44784
         * Copyright (c) Brightcove
44785
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
44786
         *
44787
         * Reads in-band caption information from a video elementary
44788
         * stream. Captions must follow the CEA-708 standard for injection
44789
         * into an MPEG-2 transport streams.
44790
         * @see https://en.wikipedia.org/wiki/CEA-708
44791
         * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
44792
         */
44793
            // Link To Transport
44794
            // -----------------
44795
 
44796
        var Stream$7 = stream;
44797
        var cea708Parser = captionPacketParser;
44798
        var CaptionStream$2 = function (options) {
44799
            options = options || {};
44800
            CaptionStream$2.prototype.init.call(this); // parse708captions flag, default to true
44801
 
44802
            this.parse708captions_ = typeof options.parse708captions === 'boolean' ? options.parse708captions : true;
44803
            this.captionPackets_ = [];
44804
            this.ccStreams_ = [new Cea608Stream(0, 0),
44805
                // eslint-disable-line no-use-before-define
44806
                new Cea608Stream(0, 1),
44807
                // eslint-disable-line no-use-before-define
44808
                new Cea608Stream(1, 0),
44809
                // eslint-disable-line no-use-before-define
44810
                new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
44811
            ];
44812
 
44813
            if (this.parse708captions_) {
44814
                this.cc708Stream_ = new Cea708Stream({
44815
                    captionServices: options.captionServices
44816
                }); // eslint-disable-line no-use-before-define
44817
            }
44818
 
44819
            this.reset(); // forward data and done events from CCs to this CaptionStream
44820
 
44821
            this.ccStreams_.forEach(function (cc) {
44822
                cc.on('data', this.trigger.bind(this, 'data'));
44823
                cc.on('partialdone', this.trigger.bind(this, 'partialdone'));
44824
                cc.on('done', this.trigger.bind(this, 'done'));
44825
            }, this);
44826
            if (this.parse708captions_) {
44827
                this.cc708Stream_.on('data', this.trigger.bind(this, 'data'));
44828
                this.cc708Stream_.on('partialdone', this.trigger.bind(this, 'partialdone'));
44829
                this.cc708Stream_.on('done', this.trigger.bind(this, 'done'));
44830
            }
44831
        };
44832
        CaptionStream$2.prototype = new Stream$7();
44833
        CaptionStream$2.prototype.push = function (event) {
44834
            var sei, userData, newCaptionPackets; // only examine SEI NALs
44835
 
44836
            if (event.nalUnitType !== 'sei_rbsp') {
44837
                return;
44838
            } // parse the sei
44839
 
44840
            sei = cea708Parser.parseSei(event.escapedRBSP); // no payload data, skip
44841
 
44842
            if (!sei.payload) {
44843
                return;
44844
            } // ignore everything but user_data_registered_itu_t_t35
44845
 
44846
            if (sei.payloadType !== cea708Parser.USER_DATA_REGISTERED_ITU_T_T35) {
44847
                return;
44848
            } // parse out the user data payload
44849
 
44850
            userData = cea708Parser.parseUserData(sei); // ignore unrecognized userData
44851
 
44852
            if (!userData) {
44853
                return;
44854
            } // Sometimes, the same segment # will be downloaded twice. To stop the
44855
            // caption data from being processed twice, we track the latest dts we've
44856
            // received and ignore everything with a dts before that. However, since
44857
            // data for a specific dts can be split across packets on either side of
44858
            // a segment boundary, we need to make sure we *don't* ignore the packets
44859
            // from the *next* segment that have dts === this.latestDts_. By constantly
44860
            // tracking the number of packets received with dts === this.latestDts_, we
44861
            // know how many should be ignored once we start receiving duplicates.
44862
 
44863
            if (event.dts < this.latestDts_) {
44864
                // We've started getting older data, so set the flag.
44865
                this.ignoreNextEqualDts_ = true;
44866
                return;
44867
            } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
44868
                this.numSameDts_--;
44869
                if (!this.numSameDts_) {
44870
                    // We've received the last duplicate packet, time to start processing again
44871
                    this.ignoreNextEqualDts_ = false;
44872
                }
44873
                return;
44874
            } // parse out CC data packets and save them for later
44875
 
44876
            newCaptionPackets = cea708Parser.parseCaptionPackets(event.pts, userData);
44877
            this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
44878
            if (this.latestDts_ !== event.dts) {
44879
                this.numSameDts_ = 0;
44880
            }
44881
            this.numSameDts_++;
44882
            this.latestDts_ = event.dts;
44883
        };
44884
        CaptionStream$2.prototype.flushCCStreams = function (flushType) {
44885
            this.ccStreams_.forEach(function (cc) {
44886
                return flushType === 'flush' ? cc.flush() : cc.partialFlush();
44887
            }, this);
44888
        };
44889
        CaptionStream$2.prototype.flushStream = function (flushType) {
44890
            // make sure we actually parsed captions before proceeding
44891
            if (!this.captionPackets_.length) {
44892
                this.flushCCStreams(flushType);
44893
                return;
44894
            } // In Chrome, the Array#sort function is not stable so add a
44895
            // presortIndex that we can use to ensure we get a stable-sort
44896
 
44897
            this.captionPackets_.forEach(function (elem, idx) {
44898
                elem.presortIndex = idx;
44899
            }); // sort caption byte-pairs based on their PTS values
44900
 
44901
            this.captionPackets_.sort(function (a, b) {
44902
                if (a.pts === b.pts) {
44903
                    return a.presortIndex - b.presortIndex;
44904
                }
44905
                return a.pts - b.pts;
44906
            });
44907
            this.captionPackets_.forEach(function (packet) {
44908
                if (packet.type < 2) {
44909
                    // Dispatch packet to the right Cea608Stream
44910
                    this.dispatchCea608Packet(packet);
44911
                } else {
44912
                    // Dispatch packet to the Cea708Stream
44913
                    this.dispatchCea708Packet(packet);
44914
                }
44915
            }, this);
44916
            this.captionPackets_.length = 0;
44917
            this.flushCCStreams(flushType);
44918
        };
44919
        CaptionStream$2.prototype.flush = function () {
44920
            return this.flushStream('flush');
44921
        }; // Only called if handling partial data
44922
 
44923
        CaptionStream$2.prototype.partialFlush = function () {
44924
            return this.flushStream('partialFlush');
44925
        };
44926
        CaptionStream$2.prototype.reset = function () {
44927
            this.latestDts_ = null;
44928
            this.ignoreNextEqualDts_ = false;
44929
            this.numSameDts_ = 0;
44930
            this.activeCea608Channel_ = [null, null];
44931
            this.ccStreams_.forEach(function (ccStream) {
44932
                ccStream.reset();
44933
            });
44934
        }; // From the CEA-608 spec:
44935
 
44936
        /*
44937
     * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed
44938
     * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is
44939
     * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair
44940
     * and subsequent data should then be processed according to the FCC rules. It may be necessary for the
44941
     * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD)
44942
     * to switch to captioning or Text.
44943
    */
44944
        // With that in mind, we ignore any data between an XDS control code and a
44945
        // subsequent closed-captioning control code.
44946
 
44947
        CaptionStream$2.prototype.dispatchCea608Packet = function (packet) {
44948
            // NOTE: packet.type is the CEA608 field
44949
            if (this.setsTextOrXDSActive(packet)) {
44950
                this.activeCea608Channel_[packet.type] = null;
44951
            } else if (this.setsChannel1Active(packet)) {
44952
                this.activeCea608Channel_[packet.type] = 0;
44953
            } else if (this.setsChannel2Active(packet)) {
44954
                this.activeCea608Channel_[packet.type] = 1;
44955
            }
44956
            if (this.activeCea608Channel_[packet.type] === null) {
44957
                // If we haven't received anything to set the active channel, or the
44958
                // packets are Text/XDS data, discard the data; we don't want jumbled
44959
                // captions
44960
                return;
44961
            }
44962
            this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
44963
        };
44964
        CaptionStream$2.prototype.setsChannel1Active = function (packet) {
44965
            return (packet.ccData & 0x7800) === 0x1000;
44966
        };
44967
        CaptionStream$2.prototype.setsChannel2Active = function (packet) {
44968
            return (packet.ccData & 0x7800) === 0x1800;
44969
        };
44970
        CaptionStream$2.prototype.setsTextOrXDSActive = function (packet) {
44971
            return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a;
44972
        };
44973
        CaptionStream$2.prototype.dispatchCea708Packet = function (packet) {
44974
            if (this.parse708captions_) {
44975
                this.cc708Stream_.push(packet);
44976
            }
44977
        }; // ----------------------
44978
        // Session to Application
44979
        // ----------------------
44980
        // This hash maps special and extended character codes to their
44981
        // proper Unicode equivalent. The first one-byte key is just a
44982
        // non-standard character code. The two-byte keys that follow are
44983
        // the extended CEA708 character codes, along with the preceding
44984
        // 0x10 extended character byte to distinguish these codes from
44985
        // non-extended character codes. Every CEA708 character code that
44986
        // is not in this object maps directly to a standard unicode
44987
        // character code.
44988
        // The transparent space and non-breaking transparent space are
44989
        // technically not fully supported since there is no code to
44990
        // make them transparent, so they have normal non-transparent
44991
        // stand-ins.
44992
        // The special closed caption (CC) character isn't a standard
44993
        // unicode character, so a fairly similar unicode character was
44994
        // chosen in it's place.
44995
 
44996
        var CHARACTER_TRANSLATION_708 = {
44997
            0x7f: 0x266a,
44998
            // ♪
44999
            0x1020: 0x20,
45000
            // Transparent Space
45001
            0x1021: 0xa0,
45002
            // Nob-breaking Transparent Space
45003
            0x1025: 0x2026,
45004
            // …
45005
            0x102a: 0x0160,
45006
            // Š
45007
            0x102c: 0x0152,
45008
            // Å’
45009
            0x1030: 0x2588,
45010
            // █
45011
            0x1031: 0x2018,
45012
            // ‘
45013
            0x1032: 0x2019,
45014
            // ’
45015
            0x1033: 0x201c,
45016
            // “
45017
            0x1034: 0x201d,
45018
            // ”
45019
            0x1035: 0x2022,
45020
            // •
45021
            0x1039: 0x2122,
45022
            // â„¢
45023
            0x103a: 0x0161,
45024
            // š
45025
            0x103c: 0x0153,
45026
            // Å“
45027
            0x103d: 0x2120,
45028
            // ℠
45029
            0x103f: 0x0178,
45030
            // Ÿ
45031
            0x1076: 0x215b,
45032
            // ⅛
45033
            0x1077: 0x215c,
45034
            // ⅜
45035
            0x1078: 0x215d,
45036
            // ⅝
45037
            0x1079: 0x215e,
45038
            // ⅞
45039
            0x107a: 0x23d0,
45040
            // ⏐
45041
            0x107b: 0x23a4,
45042
            // ⎤
45043
            0x107c: 0x23a3,
45044
            // ⎣
45045
            0x107d: 0x23af,
45046
            // ⎯
45047
            0x107e: 0x23a6,
45048
            // ⎦
45049
            0x107f: 0x23a1,
45050
            // ⎡
45051
            0x10a0: 0x3138 // ㄸ (CC char)
45052
        };
45053
 
45054
        var get708CharFromCode = function (code) {
45055
            var newCode = CHARACTER_TRANSLATION_708[code] || code;
45056
            if (code & 0x1000 && code === newCode) {
45057
                // Invalid extended code
45058
                return '';
45059
            }
45060
            return String.fromCharCode(newCode);
45061
        };
45062
        var within708TextBlock = function (b) {
45063
            return 0x20 <= b && b <= 0x7f || 0xa0 <= b && b <= 0xff;
45064
        };
45065
        var Cea708Window = function (windowNum) {
45066
            this.windowNum = windowNum;
45067
            this.reset();
45068
        };
45069
        Cea708Window.prototype.reset = function () {
45070
            this.clearText();
45071
            this.pendingNewLine = false;
45072
            this.winAttr = {};
45073
            this.penAttr = {};
45074
            this.penLoc = {};
45075
            this.penColor = {}; // These default values are arbitrary,
45076
            // defineWindow will usually override them
45077
 
45078
            this.visible = 0;
45079
            this.rowLock = 0;
45080
            this.columnLock = 0;
45081
            this.priority = 0;
45082
            this.relativePositioning = 0;
45083
            this.anchorVertical = 0;
45084
            this.anchorHorizontal = 0;
45085
            this.anchorPoint = 0;
45086
            this.rowCount = 1;
45087
            this.virtualRowCount = this.rowCount + 1;
45088
            this.columnCount = 41;
45089
            this.windowStyle = 0;
45090
            this.penStyle = 0;
45091
        };
45092
        Cea708Window.prototype.getText = function () {
45093
            return this.rows.join('\n');
45094
        };
45095
        Cea708Window.prototype.clearText = function () {
45096
            this.rows = [''];
45097
            this.rowIdx = 0;
45098
        };
45099
        Cea708Window.prototype.newLine = function (pts) {
45100
            if (this.rows.length >= this.virtualRowCount && typeof this.beforeRowOverflow === 'function') {
45101
                this.beforeRowOverflow(pts);
45102
            }
45103
            if (this.rows.length > 0) {
45104
                this.rows.push('');
45105
                this.rowIdx++;
45106
            } // Show all virtual rows since there's no visible scrolling
45107
 
45108
            while (this.rows.length > this.virtualRowCount) {
45109
                this.rows.shift();
45110
                this.rowIdx--;
45111
            }
45112
        };
45113
        Cea708Window.prototype.isEmpty = function () {
45114
            if (this.rows.length === 0) {
45115
                return true;
45116
            } else if (this.rows.length === 1) {
45117
                return this.rows[0] === '';
45118
            }
45119
            return false;
45120
        };
45121
        Cea708Window.prototype.addText = function (text) {
45122
            this.rows[this.rowIdx] += text;
45123
        };
45124
        Cea708Window.prototype.backspace = function () {
45125
            if (!this.isEmpty()) {
45126
                var row = this.rows[this.rowIdx];
45127
                this.rows[this.rowIdx] = row.substr(0, row.length - 1);
45128
            }
45129
        };
45130
        var Cea708Service = function (serviceNum, encoding, stream) {
45131
            this.serviceNum = serviceNum;
45132
            this.text = '';
45133
            this.currentWindow = new Cea708Window(-1);
45134
            this.windows = [];
45135
            this.stream = stream; // Try to setup a TextDecoder if an `encoding` value was provided
45136
 
45137
            if (typeof encoding === 'string') {
45138
                this.createTextDecoder(encoding);
45139
            }
45140
        };
45141
        /**
45142
         * Initialize service windows
45143
         * Must be run before service use
45144
         *
45145
         * @param  {Integer}  pts               PTS value
45146
         * @param  {Function} beforeRowOverflow Function to execute before row overflow of a window
45147
         */
45148
 
45149
        Cea708Service.prototype.init = function (pts, beforeRowOverflow) {
45150
            this.startPts = pts;
45151
            for (var win = 0; win < 8; win++) {
45152
                this.windows[win] = new Cea708Window(win);
45153
                if (typeof beforeRowOverflow === 'function') {
45154
                    this.windows[win].beforeRowOverflow = beforeRowOverflow;
45155
                }
45156
            }
45157
        };
45158
        /**
45159
         * Set current window of service to be affected by commands
45160
         *
45161
         * @param  {Integer} windowNum Window number
45162
         */
45163
 
45164
        Cea708Service.prototype.setCurrentWindow = function (windowNum) {
45165
            this.currentWindow = this.windows[windowNum];
45166
        };
45167
        /**
45168
         * Try to create a TextDecoder if it is natively supported
45169
         */
45170
 
45171
        Cea708Service.prototype.createTextDecoder = function (encoding) {
45172
            if (typeof TextDecoder === 'undefined') {
45173
                this.stream.trigger('log', {
45174
                    level: 'warn',
45175
                    message: 'The `encoding` option is unsupported without TextDecoder support'
45176
                });
45177
            } else {
45178
                try {
45179
                    this.textDecoder_ = new TextDecoder(encoding);
45180
                } catch (error) {
45181
                    this.stream.trigger('log', {
45182
                        level: 'warn',
45183
                        message: 'TextDecoder could not be created with ' + encoding + ' encoding. ' + error
45184
                    });
45185
                }
45186
            }
45187
        };
45188
        var Cea708Stream = function (options) {
45189
            options = options || {};
45190
            Cea708Stream.prototype.init.call(this);
45191
            var self = this;
45192
            var captionServices = options.captionServices || {};
45193
            var captionServiceEncodings = {};
45194
            var serviceProps; // Get service encodings from captionServices option block
45195
 
45196
            Object.keys(captionServices).forEach(serviceName => {
45197
                serviceProps = captionServices[serviceName];
45198
                if (/^SERVICE/.test(serviceName)) {
45199
                    captionServiceEncodings[serviceName] = serviceProps.encoding;
45200
                }
45201
            });
45202
            this.serviceEncodings = captionServiceEncodings;
45203
            this.current708Packet = null;
45204
            this.services = {};
45205
            this.push = function (packet) {
45206
                if (packet.type === 3) {
45207
                    // 708 packet start
45208
                    self.new708Packet();
45209
                    self.add708Bytes(packet);
45210
                } else {
45211
                    if (self.current708Packet === null) {
45212
                        // This should only happen at the start of a file if there's no packet start.
45213
                        self.new708Packet();
45214
                    }
45215
                    self.add708Bytes(packet);
45216
                }
45217
            };
45218
        };
45219
        Cea708Stream.prototype = new Stream$7();
45220
        /**
45221
         * Push current 708 packet, create new 708 packet.
45222
         */
45223
 
45224
        Cea708Stream.prototype.new708Packet = function () {
45225
            if (this.current708Packet !== null) {
45226
                this.push708Packet();
45227
            }
45228
            this.current708Packet = {
45229
                data: [],
45230
                ptsVals: []
45231
            };
45232
        };
45233
        /**
45234
         * Add pts and both bytes from packet into current 708 packet.
45235
         */
45236
 
45237
        Cea708Stream.prototype.add708Bytes = function (packet) {
45238
            var data = packet.ccData;
45239
            var byte0 = data >>> 8;
45240
            var byte1 = data & 0xff; // I would just keep a list of packets instead of bytes, but it isn't clear in the spec
45241
            // that service blocks will always line up with byte pairs.
45242
 
45243
            this.current708Packet.ptsVals.push(packet.pts);
45244
            this.current708Packet.data.push(byte0);
45245
            this.current708Packet.data.push(byte1);
45246
        };
45247
        /**
45248
         * Parse completed 708 packet into service blocks and push each service block.
45249
         */
45250
 
45251
        Cea708Stream.prototype.push708Packet = function () {
45252
            var packet708 = this.current708Packet;
45253
            var packetData = packet708.data;
45254
            var serviceNum = null;
45255
            var blockSize = null;
45256
            var i = 0;
45257
            var b = packetData[i++];
45258
            packet708.seq = b >> 6;
45259
            packet708.sizeCode = b & 0x3f; // 0b00111111;
45260
 
45261
            for (; i < packetData.length; i++) {
45262
                b = packetData[i++];
45263
                serviceNum = b >> 5;
45264
                blockSize = b & 0x1f; // 0b00011111
45265
 
45266
                if (serviceNum === 7 && blockSize > 0) {
45267
                    // Extended service num
45268
                    b = packetData[i++];
45269
                    serviceNum = b;
45270
                }
45271
                this.pushServiceBlock(serviceNum, i, blockSize);
45272
                if (blockSize > 0) {
45273
                    i += blockSize - 1;
45274
                }
45275
            }
45276
        };
45277
        /**
45278
         * Parse service block, execute commands, read text.
45279
         *
45280
         * Note: While many of these commands serve important purposes,
45281
         * many others just parse out the parameters or attributes, but
45282
         * nothing is done with them because this is not a full and complete
45283
         * implementation of the entire 708 spec.
45284
         *
45285
         * @param  {Integer} serviceNum Service number
45286
         * @param  {Integer} start      Start index of the 708 packet data
45287
         * @param  {Integer} size       Block size
45288
         */
45289
 
45290
        Cea708Stream.prototype.pushServiceBlock = function (serviceNum, start, size) {
45291
            var b;
45292
            var i = start;
45293
            var packetData = this.current708Packet.data;
45294
            var service = this.services[serviceNum];
45295
            if (!service) {
45296
                service = this.initService(serviceNum, i);
45297
            }
45298
            for (; i < start + size && i < packetData.length; i++) {
45299
                b = packetData[i];
45300
                if (within708TextBlock(b)) {
45301
                    i = this.handleText(i, service);
45302
                } else if (b === 0x18) {
45303
                    i = this.multiByteCharacter(i, service);
45304
                } else if (b === 0x10) {
45305
                    i = this.extendedCommands(i, service);
45306
                } else if (0x80 <= b && b <= 0x87) {
45307
                    i = this.setCurrentWindow(i, service);
45308
                } else if (0x98 <= b && b <= 0x9f) {
45309
                    i = this.defineWindow(i, service);
45310
                } else if (b === 0x88) {
45311
                    i = this.clearWindows(i, service);
45312
                } else if (b === 0x8c) {
45313
                    i = this.deleteWindows(i, service);
45314
                } else if (b === 0x89) {
45315
                    i = this.displayWindows(i, service);
45316
                } else if (b === 0x8a) {
45317
                    i = this.hideWindows(i, service);
45318
                } else if (b === 0x8b) {
45319
                    i = this.toggleWindows(i, service);
45320
                } else if (b === 0x97) {
45321
                    i = this.setWindowAttributes(i, service);
45322
                } else if (b === 0x90) {
45323
                    i = this.setPenAttributes(i, service);
45324
                } else if (b === 0x91) {
45325
                    i = this.setPenColor(i, service);
45326
                } else if (b === 0x92) {
45327
                    i = this.setPenLocation(i, service);
45328
                } else if (b === 0x8f) {
45329
                    service = this.reset(i, service);
45330
                } else if (b === 0x08) {
45331
                    // BS: Backspace
45332
                    service.currentWindow.backspace();
45333
                } else if (b === 0x0c) {
45334
                    // FF: Form feed
45335
                    service.currentWindow.clearText();
45336
                } else if (b === 0x0d) {
45337
                    // CR: Carriage return
45338
                    service.currentWindow.pendingNewLine = true;
45339
                } else if (b === 0x0e) {
45340
                    // HCR: Horizontal carriage return
45341
                    service.currentWindow.clearText();
45342
                } else if (b === 0x8d) {
45343
                    // DLY: Delay, nothing to do
45344
                    i++;
45345
                } else ;
45346
            }
45347
        };
45348
        /**
45349
         * Execute an extended command
45350
         *
45351
         * @param  {Integer} i        Current index in the 708 packet
45352
         * @param  {Service} service  The service object to be affected
45353
         * @return {Integer}          New index after parsing
45354
         */
45355
 
45356
        Cea708Stream.prototype.extendedCommands = function (i, service) {
45357
            var packetData = this.current708Packet.data;
45358
            var b = packetData[++i];
45359
            if (within708TextBlock(b)) {
45360
                i = this.handleText(i, service, {
45361
                    isExtended: true
45362
                });
45363
            }
45364
            return i;
45365
        };
45366
        /**
45367
         * Get PTS value of a given byte index
45368
         *
45369
         * @param  {Integer} byteIndex  Index of the byte
45370
         * @return {Integer}            PTS
45371
         */
45372
 
45373
        Cea708Stream.prototype.getPts = function (byteIndex) {
45374
            // There's 1 pts value per 2 bytes
45375
            return this.current708Packet.ptsVals[Math.floor(byteIndex / 2)];
45376
        };
45377
        /**
45378
         * Initializes a service
45379
         *
45380
         * @param  {Integer} serviceNum Service number
45381
         * @return {Service}            Initialized service object
45382
         */
45383
 
45384
        Cea708Stream.prototype.initService = function (serviceNum, i) {
45385
            var serviceName = 'SERVICE' + serviceNum;
45386
            var self = this;
45387
            var serviceName;
45388
            var encoding;
45389
            if (serviceName in this.serviceEncodings) {
45390
                encoding = this.serviceEncodings[serviceName];
45391
            }
45392
            this.services[serviceNum] = new Cea708Service(serviceNum, encoding, self);
45393
            this.services[serviceNum].init(this.getPts(i), function (pts) {
45394
                self.flushDisplayed(pts, self.services[serviceNum]);
45395
            });
45396
            return this.services[serviceNum];
45397
        };
45398
        /**
45399
         * Execute text writing to current window
45400
         *
45401
         * @param  {Integer} i        Current index in the 708 packet
45402
         * @param  {Service} service  The service object to be affected
45403
         * @return {Integer}          New index after parsing
45404
         */
45405
 
45406
        Cea708Stream.prototype.handleText = function (i, service, options) {
45407
            var isExtended = options && options.isExtended;
45408
            var isMultiByte = options && options.isMultiByte;
45409
            var packetData = this.current708Packet.data;
45410
            var extended = isExtended ? 0x1000 : 0x0000;
45411
            var currentByte = packetData[i];
45412
            var nextByte = packetData[i + 1];
45413
            var win = service.currentWindow;
45414
            var char;
45415
            var charCodeArray; // Converts an array of bytes to a unicode hex string.
45416
 
45417
            function toHexString(byteArray) {
45418
                return byteArray.map(byte => {
45419
                    return ('0' + (byte & 0xFF).toString(16)).slice(-2);
45420
                }).join('');
45421
            }
45422
            if (isMultiByte) {
45423
                charCodeArray = [currentByte, nextByte];
45424
                i++;
45425
            } else {
45426
                charCodeArray = [currentByte];
45427
            } // Use the TextDecoder if one was created for this service
45428
 
45429
            if (service.textDecoder_ && !isExtended) {
45430
                char = service.textDecoder_.decode(new Uint8Array(charCodeArray));
45431
            } else {
45432
                // We assume any multi-byte char without a decoder is unicode.
45433
                if (isMultiByte) {
45434
                    const unicode = toHexString(charCodeArray); // Takes a unicode hex string and creates a single character.
45435
 
45436
                    char = String.fromCharCode(parseInt(unicode, 16));
45437
                } else {
45438
                    char = get708CharFromCode(extended | currentByte);
45439
                }
45440
            }
45441
            if (win.pendingNewLine && !win.isEmpty()) {
45442
                win.newLine(this.getPts(i));
45443
            }
45444
            win.pendingNewLine = false;
45445
            win.addText(char);
45446
            return i;
45447
        };
45448
        /**
45449
         * Handle decoding of multibyte character
45450
         *
45451
         * @param  {Integer} i        Current index in the 708 packet
45452
         * @param  {Service} service  The service object to be affected
45453
         * @return {Integer}          New index after parsing
45454
         */
45455
 
45456
        Cea708Stream.prototype.multiByteCharacter = function (i, service) {
45457
            var packetData = this.current708Packet.data;
45458
            var firstByte = packetData[i + 1];
45459
            var secondByte = packetData[i + 2];
45460
            if (within708TextBlock(firstByte) && within708TextBlock(secondByte)) {
45461
                i = this.handleText(++i, service, {
45462
                    isMultiByte: true
45463
                });
45464
            }
45465
            return i;
45466
        };
45467
        /**
45468
         * Parse and execute the CW# command.
45469
         *
45470
         * Set the current window.
45471
         *
45472
         * @param  {Integer} i        Current index in the 708 packet
45473
         * @param  {Service} service  The service object to be affected
45474
         * @return {Integer}          New index after parsing
45475
         */
45476
 
45477
        Cea708Stream.prototype.setCurrentWindow = function (i, service) {
45478
            var packetData = this.current708Packet.data;
45479
            var b = packetData[i];
45480
            var windowNum = b & 0x07;
45481
            service.setCurrentWindow(windowNum);
45482
            return i;
45483
        };
45484
        /**
45485
         * Parse and execute the DF# command.
45486
         *
45487
         * Define a window and set it as the current window.
45488
         *
45489
         * @param  {Integer} i        Current index in the 708 packet
45490
         * @param  {Service} service  The service object to be affected
45491
         * @return {Integer}          New index after parsing
45492
         */
45493
 
45494
        Cea708Stream.prototype.defineWindow = function (i, service) {
45495
            var packetData = this.current708Packet.data;
45496
            var b = packetData[i];
45497
            var windowNum = b & 0x07;
45498
            service.setCurrentWindow(windowNum);
45499
            var win = service.currentWindow;
45500
            b = packetData[++i];
45501
            win.visible = (b & 0x20) >> 5; // v
45502
 
45503
            win.rowLock = (b & 0x10) >> 4; // rl
45504
 
45505
            win.columnLock = (b & 0x08) >> 3; // cl
45506
 
45507
            win.priority = b & 0x07; // p
45508
 
45509
            b = packetData[++i];
45510
            win.relativePositioning = (b & 0x80) >> 7; // rp
45511
 
45512
            win.anchorVertical = b & 0x7f; // av
45513
 
45514
            b = packetData[++i];
45515
            win.anchorHorizontal = b; // ah
45516
 
45517
            b = packetData[++i];
45518
            win.anchorPoint = (b & 0xf0) >> 4; // ap
45519
 
45520
            win.rowCount = b & 0x0f; // rc
45521
 
45522
            b = packetData[++i];
45523
            win.columnCount = b & 0x3f; // cc
45524
 
45525
            b = packetData[++i];
45526
            win.windowStyle = (b & 0x38) >> 3; // ws
45527
 
45528
            win.penStyle = b & 0x07; // ps
45529
            // The spec says there are (rowCount+1) "virtual rows"
45530
 
45531
            win.virtualRowCount = win.rowCount + 1;
45532
            return i;
45533
        };
45534
        /**
45535
         * Parse and execute the SWA command.
45536
         *
45537
         * Set attributes of the current window.
45538
         *
45539
         * @param  {Integer} i        Current index in the 708 packet
45540
         * @param  {Service} service  The service object to be affected
45541
         * @return {Integer}          New index after parsing
45542
         */
45543
 
45544
        Cea708Stream.prototype.setWindowAttributes = function (i, service) {
45545
            var packetData = this.current708Packet.data;
45546
            var b = packetData[i];
45547
            var winAttr = service.currentWindow.winAttr;
45548
            b = packetData[++i];
45549
            winAttr.fillOpacity = (b & 0xc0) >> 6; // fo
45550
 
45551
            winAttr.fillRed = (b & 0x30) >> 4; // fr
45552
 
45553
            winAttr.fillGreen = (b & 0x0c) >> 2; // fg
45554
 
45555
            winAttr.fillBlue = b & 0x03; // fb
45556
 
45557
            b = packetData[++i];
45558
            winAttr.borderType = (b & 0xc0) >> 6; // bt
45559
 
45560
            winAttr.borderRed = (b & 0x30) >> 4; // br
45561
 
45562
            winAttr.borderGreen = (b & 0x0c) >> 2; // bg
45563
 
45564
            winAttr.borderBlue = b & 0x03; // bb
45565
 
45566
            b = packetData[++i];
45567
            winAttr.borderType += (b & 0x80) >> 5; // bt
45568
 
45569
            winAttr.wordWrap = (b & 0x40) >> 6; // ww
45570
 
45571
            winAttr.printDirection = (b & 0x30) >> 4; // pd
45572
 
45573
            winAttr.scrollDirection = (b & 0x0c) >> 2; // sd
45574
 
45575
            winAttr.justify = b & 0x03; // j
45576
 
45577
            b = packetData[++i];
45578
            winAttr.effectSpeed = (b & 0xf0) >> 4; // es
45579
 
45580
            winAttr.effectDirection = (b & 0x0c) >> 2; // ed
45581
 
45582
            winAttr.displayEffect = b & 0x03; // de
45583
 
45584
            return i;
45585
        };
45586
        /**
45587
         * Gather text from all displayed windows and push a caption to output.
45588
         *
45589
         * @param  {Integer} i        Current index in the 708 packet
45590
         * @param  {Service} service  The service object to be affected
45591
         */
45592
 
45593
        Cea708Stream.prototype.flushDisplayed = function (pts, service) {
45594
            var displayedText = []; // TODO: Positioning not supported, displaying multiple windows will not necessarily
45595
            // display text in the correct order, but sample files so far have not shown any issue.
45596
 
45597
            for (var winId = 0; winId < 8; winId++) {
45598
                if (service.windows[winId].visible && !service.windows[winId].isEmpty()) {
45599
                    displayedText.push(service.windows[winId].getText());
45600
                }
45601
            }
45602
            service.endPts = pts;
45603
            service.text = displayedText.join('\n\n');
45604
            this.pushCaption(service);
45605
            service.startPts = pts;
45606
        };
45607
        /**
45608
         * Push a caption to output if the caption contains text.
45609
         *
45610
         * @param  {Service} service  The service object to be affected
45611
         */
45612
 
45613
        Cea708Stream.prototype.pushCaption = function (service) {
45614
            if (service.text !== '') {
45615
                this.trigger('data', {
45616
                    startPts: service.startPts,
45617
                    endPts: service.endPts,
45618
                    text: service.text,
45619
                    stream: 'cc708_' + service.serviceNum
45620
                });
45621
                service.text = '';
45622
                service.startPts = service.endPts;
45623
            }
45624
        };
45625
        /**
45626
         * Parse and execute the DSW command.
45627
         *
45628
         * Set visible property of windows based on the parsed bitmask.
45629
         *
45630
         * @param  {Integer} i        Current index in the 708 packet
45631
         * @param  {Service} service  The service object to be affected
45632
         * @return {Integer}          New index after parsing
45633
         */
45634
 
45635
        Cea708Stream.prototype.displayWindows = function (i, service) {
45636
            var packetData = this.current708Packet.data;
45637
            var b = packetData[++i];
45638
            var pts = this.getPts(i);
45639
            this.flushDisplayed(pts, service);
45640
            for (var winId = 0; winId < 8; winId++) {
45641
                if (b & 0x01 << winId) {
45642
                    service.windows[winId].visible = 1;
45643
                }
45644
            }
45645
            return i;
45646
        };
45647
        /**
45648
         * Parse and execute the HDW command.
45649
         *
45650
         * Set visible property of windows based on the parsed bitmask.
45651
         *
45652
         * @param  {Integer} i        Current index in the 708 packet
45653
         * @param  {Service} service  The service object to be affected
45654
         * @return {Integer}          New index after parsing
45655
         */
45656
 
45657
        Cea708Stream.prototype.hideWindows = function (i, service) {
45658
            var packetData = this.current708Packet.data;
45659
            var b = packetData[++i];
45660
            var pts = this.getPts(i);
45661
            this.flushDisplayed(pts, service);
45662
            for (var winId = 0; winId < 8; winId++) {
45663
                if (b & 0x01 << winId) {
45664
                    service.windows[winId].visible = 0;
45665
                }
45666
            }
45667
            return i;
45668
        };
45669
        /**
45670
         * Parse and execute the TGW command.
45671
         *
45672
         * Set visible property of windows based on the parsed bitmask.
45673
         *
45674
         * @param  {Integer} i        Current index in the 708 packet
45675
         * @param  {Service} service  The service object to be affected
45676
         * @return {Integer}          New index after parsing
45677
         */
45678
 
45679
        Cea708Stream.prototype.toggleWindows = function (i, service) {
45680
            var packetData = this.current708Packet.data;
45681
            var b = packetData[++i];
45682
            var pts = this.getPts(i);
45683
            this.flushDisplayed(pts, service);
45684
            for (var winId = 0; winId < 8; winId++) {
45685
                if (b & 0x01 << winId) {
45686
                    service.windows[winId].visible ^= 1;
45687
                }
45688
            }
45689
            return i;
45690
        };
45691
        /**
45692
         * Parse and execute the CLW command.
45693
         *
45694
         * Clear text of windows based on the parsed bitmask.
45695
         *
45696
         * @param  {Integer} i        Current index in the 708 packet
45697
         * @param  {Service} service  The service object to be affected
45698
         * @return {Integer}          New index after parsing
45699
         */
45700
 
45701
        Cea708Stream.prototype.clearWindows = function (i, service) {
45702
            var packetData = this.current708Packet.data;
45703
            var b = packetData[++i];
45704
            var pts = this.getPts(i);
45705
            this.flushDisplayed(pts, service);
45706
            for (var winId = 0; winId < 8; winId++) {
45707
                if (b & 0x01 << winId) {
45708
                    service.windows[winId].clearText();
45709
                }
45710
            }
45711
            return i;
45712
        };
45713
        /**
45714
         * Parse and execute the DLW command.
45715
         *
45716
         * Re-initialize windows based on the parsed bitmask.
45717
         *
45718
         * @param  {Integer} i        Current index in the 708 packet
45719
         * @param  {Service} service  The service object to be affected
45720
         * @return {Integer}          New index after parsing
45721
         */
45722
 
45723
        Cea708Stream.prototype.deleteWindows = function (i, service) {
45724
            var packetData = this.current708Packet.data;
45725
            var b = packetData[++i];
45726
            var pts = this.getPts(i);
45727
            this.flushDisplayed(pts, service);
45728
            for (var winId = 0; winId < 8; winId++) {
45729
                if (b & 0x01 << winId) {
45730
                    service.windows[winId].reset();
45731
                }
45732
            }
45733
            return i;
45734
        };
45735
        /**
45736
         * Parse and execute the SPA command.
45737
         *
45738
         * Set pen attributes of the current window.
45739
         *
45740
         * @param  {Integer} i        Current index in the 708 packet
45741
         * @param  {Service} service  The service object to be affected
45742
         * @return {Integer}          New index after parsing
45743
         */
45744
 
45745
        Cea708Stream.prototype.setPenAttributes = function (i, service) {
45746
            var packetData = this.current708Packet.data;
45747
            var b = packetData[i];
45748
            var penAttr = service.currentWindow.penAttr;
45749
            b = packetData[++i];
45750
            penAttr.textTag = (b & 0xf0) >> 4; // tt
45751
 
45752
            penAttr.offset = (b & 0x0c) >> 2; // o
45753
 
45754
            penAttr.penSize = b & 0x03; // s
45755
 
45756
            b = packetData[++i];
45757
            penAttr.italics = (b & 0x80) >> 7; // i
45758
 
45759
            penAttr.underline = (b & 0x40) >> 6; // u
45760
 
45761
            penAttr.edgeType = (b & 0x38) >> 3; // et
45762
 
45763
            penAttr.fontStyle = b & 0x07; // fs
45764
 
45765
            return i;
45766
        };
45767
        /**
45768
         * Parse and execute the SPC command.
45769
         *
45770
         * Set pen color of the current window.
45771
         *
45772
         * @param  {Integer} i        Current index in the 708 packet
45773
         * @param  {Service} service  The service object to be affected
45774
         * @return {Integer}          New index after parsing
45775
         */
45776
 
45777
        Cea708Stream.prototype.setPenColor = function (i, service) {
45778
            var packetData = this.current708Packet.data;
45779
            var b = packetData[i];
45780
            var penColor = service.currentWindow.penColor;
45781
            b = packetData[++i];
45782
            penColor.fgOpacity = (b & 0xc0) >> 6; // fo
45783
 
45784
            penColor.fgRed = (b & 0x30) >> 4; // fr
45785
 
45786
            penColor.fgGreen = (b & 0x0c) >> 2; // fg
45787
 
45788
            penColor.fgBlue = b & 0x03; // fb
45789
 
45790
            b = packetData[++i];
45791
            penColor.bgOpacity = (b & 0xc0) >> 6; // bo
45792
 
45793
            penColor.bgRed = (b & 0x30) >> 4; // br
45794
 
45795
            penColor.bgGreen = (b & 0x0c) >> 2; // bg
45796
 
45797
            penColor.bgBlue = b & 0x03; // bb
45798
 
45799
            b = packetData[++i];
45800
            penColor.edgeRed = (b & 0x30) >> 4; // er
45801
 
45802
            penColor.edgeGreen = (b & 0x0c) >> 2; // eg
45803
 
45804
            penColor.edgeBlue = b & 0x03; // eb
45805
 
45806
            return i;
45807
        };
45808
        /**
45809
         * Parse and execute the SPL command.
45810
         *
45811
         * Set pen location of the current window.
45812
         *
45813
         * @param  {Integer} i        Current index in the 708 packet
45814
         * @param  {Service} service  The service object to be affected
45815
         * @return {Integer}          New index after parsing
45816
         */
45817
 
45818
        Cea708Stream.prototype.setPenLocation = function (i, service) {
45819
            var packetData = this.current708Packet.data;
45820
            var b = packetData[i];
45821
            var penLoc = service.currentWindow.penLoc; // Positioning isn't really supported at the moment, so this essentially just inserts a linebreak
45822
 
45823
            service.currentWindow.pendingNewLine = true;
45824
            b = packetData[++i];
45825
            penLoc.row = b & 0x0f; // r
45826
 
45827
            b = packetData[++i];
45828
            penLoc.column = b & 0x3f; // c
45829
 
45830
            return i;
45831
        };
45832
        /**
45833
         * Execute the RST command.
45834
         *
45835
         * Reset service to a clean slate. Re-initialize.
45836
         *
45837
         * @param  {Integer} i        Current index in the 708 packet
45838
         * @param  {Service} service  The service object to be affected
45839
         * @return {Service}          Re-initialized service
45840
         */
45841
 
45842
        Cea708Stream.prototype.reset = function (i, service) {
45843
            var pts = this.getPts(i);
45844
            this.flushDisplayed(pts, service);
45845
            return this.initService(service.serviceNum, i);
45846
        }; // This hash maps non-ASCII, special, and extended character codes to their
45847
        // proper Unicode equivalent. The first keys that are only a single byte
45848
        // are the non-standard ASCII characters, which simply map the CEA608 byte
45849
        // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
45850
        // character codes, but have their MSB bitmasked with 0x03 so that a lookup
45851
        // can be performed regardless of the field and data channel on which the
45852
        // character code was received.
45853
 
45854
        var CHARACTER_TRANSLATION = {
45855
            0x2a: 0xe1,
45856
            // á
45857
            0x5c: 0xe9,
45858
            // é
45859
            0x5e: 0xed,
45860
            // í
45861
            0x5f: 0xf3,
45862
            // ó
45863
            0x60: 0xfa,
45864
            // ú
45865
            0x7b: 0xe7,
45866
            // ç
45867
            0x7c: 0xf7,
45868
            // ÷
45869
            0x7d: 0xd1,
45870
            // Ñ
45871
            0x7e: 0xf1,
45872
            // ñ
45873
            0x7f: 0x2588,
45874
            // █
45875
            0x0130: 0xae,
45876
            // ®
45877
            0x0131: 0xb0,
45878
            // °
45879
            0x0132: 0xbd,
45880
            // ½
45881
            0x0133: 0xbf,
45882
            // ¿
45883
            0x0134: 0x2122,
45884
            // â„¢
45885
            0x0135: 0xa2,
45886
            // ¢
45887
            0x0136: 0xa3,
45888
            // £
45889
            0x0137: 0x266a,
45890
            // ♪
45891
            0x0138: 0xe0,
45892
            // à
45893
            0x0139: 0xa0,
45894
            //
45895
            0x013a: 0xe8,
45896
            // è
45897
            0x013b: 0xe2,
45898
            // â
45899
            0x013c: 0xea,
45900
            // ê
45901
            0x013d: 0xee,
45902
            // î
45903
            0x013e: 0xf4,
45904
            // ô
45905
            0x013f: 0xfb,
45906
            // û
45907
            0x0220: 0xc1,
45908
            // Á
45909
            0x0221: 0xc9,
45910
            // É
45911
            0x0222: 0xd3,
45912
            // Ó
45913
            0x0223: 0xda,
45914
            // Ú
45915
            0x0224: 0xdc,
45916
            // Ü
45917
            0x0225: 0xfc,
45918
            // ü
45919
            0x0226: 0x2018,
45920
            // ‘
45921
            0x0227: 0xa1,
45922
            // ¡
45923
            0x0228: 0x2a,
45924
            // *
45925
            0x0229: 0x27,
45926
            // '
45927
            0x022a: 0x2014,
45928
            // —
45929
            0x022b: 0xa9,
45930
            // ©
45931
            0x022c: 0x2120,
45932
            // ℠
45933
            0x022d: 0x2022,
45934
            // •
45935
            0x022e: 0x201c,
45936
            // “
45937
            0x022f: 0x201d,
45938
            // ”
45939
            0x0230: 0xc0,
45940
            // À
45941
            0x0231: 0xc2,
45942
            // Â
45943
            0x0232: 0xc7,
45944
            // Ç
45945
            0x0233: 0xc8,
45946
            // È
45947
            0x0234: 0xca,
45948
            // Ê
45949
            0x0235: 0xcb,
45950
            // Ë
45951
            0x0236: 0xeb,
45952
            // ë
45953
            0x0237: 0xce,
45954
            // Î
45955
            0x0238: 0xcf,
45956
            // Ï
45957
            0x0239: 0xef,
45958
            // ï
45959
            0x023a: 0xd4,
45960
            // Ô
45961
            0x023b: 0xd9,
45962
            // Ù
45963
            0x023c: 0xf9,
45964
            // ù
45965
            0x023d: 0xdb,
45966
            // Û
45967
            0x023e: 0xab,
45968
            // «
45969
            0x023f: 0xbb,
45970
            // »
45971
            0x0320: 0xc3,
45972
            // Ã
45973
            0x0321: 0xe3,
45974
            // ã
45975
            0x0322: 0xcd,
45976
            // Í
45977
            0x0323: 0xcc,
45978
            // Ì
45979
            0x0324: 0xec,
45980
            // ì
45981
            0x0325: 0xd2,
45982
            // Ò
45983
            0x0326: 0xf2,
45984
            // ò
45985
            0x0327: 0xd5,
45986
            // Õ
45987
            0x0328: 0xf5,
45988
            // õ
45989
            0x0329: 0x7b,
45990
            // {
45991
            0x032a: 0x7d,
45992
            // }
45993
            0x032b: 0x5c,
45994
            // \
45995
            0x032c: 0x5e,
45996
            // ^
45997
            0x032d: 0x5f,
45998
            // _
45999
            0x032e: 0x7c,
46000
            // |
46001
            0x032f: 0x7e,
46002
            // ~
46003
            0x0330: 0xc4,
46004
            // Ä
46005
            0x0331: 0xe4,
46006
            // ä
46007
            0x0332: 0xd6,
46008
            // Ö
46009
            0x0333: 0xf6,
46010
            // ö
46011
            0x0334: 0xdf,
46012
            // ß
46013
            0x0335: 0xa5,
46014
            // ¥
46015
            0x0336: 0xa4,
46016
            // ¤
46017
            0x0337: 0x2502,
46018
            // │
46019
            0x0338: 0xc5,
46020
            // Å
46021
            0x0339: 0xe5,
46022
            // å
46023
            0x033a: 0xd8,
46024
            // Ø
46025
            0x033b: 0xf8,
46026
            // ø
46027
            0x033c: 0x250c,
46028
            // ┌
46029
            0x033d: 0x2510,
46030
            // ┐
46031
            0x033e: 0x2514,
46032
            // â””
46033
            0x033f: 0x2518 // ┘
46034
        };
46035
 
46036
        var getCharFromCode = function (code) {
46037
            if (code === null) {
46038
                return '';
46039
            }
46040
            code = CHARACTER_TRANSLATION[code] || code;
46041
            return String.fromCharCode(code);
46042
        }; // the index of the last row in a CEA-608 display buffer
46043
 
46044
        var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
46045
        // getting it through bit logic.
46046
 
46047
        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
46048
        // cells. The "bottom" row is the last element in the outer array.
46049
        // We keep track of positioning information as we go by storing the
46050
        // number of indentations and the tab offset in this buffer.
46051
 
46052
        var createDisplayBuffer = function () {
46053
            var result = [],
46054
                i = BOTTOM_ROW + 1;
46055
            while (i--) {
46056
                result.push({
46057
                    text: '',
46058
                    indent: 0,
46059
                    offset: 0
46060
                });
46061
            }
46062
            return result;
46063
        };
46064
        var Cea608Stream = function (field, dataChannel) {
46065
            Cea608Stream.prototype.init.call(this);
46066
            this.field_ = field || 0;
46067
            this.dataChannel_ = dataChannel || 0;
46068
            this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
46069
            this.setConstants();
46070
            this.reset();
46071
            this.push = function (packet) {
46072
                var data, swap, char0, char1, text; // remove the parity bits
46073
 
46074
                data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
46075
 
46076
                if (data === this.lastControlCode_) {
46077
                    this.lastControlCode_ = null;
46078
                    return;
46079
                } // Store control codes
46080
 
46081
                if ((data & 0xf000) === 0x1000) {
46082
                    this.lastControlCode_ = data;
46083
                } else if (data !== this.PADDING_) {
46084
                    this.lastControlCode_ = null;
46085
                }
46086
                char0 = data >>> 8;
46087
                char1 = data & 0xff;
46088
                if (data === this.PADDING_) {
46089
                    return;
46090
                } else if (data === this.RESUME_CAPTION_LOADING_) {
46091
                    this.mode_ = 'popOn';
46092
                } else if (data === this.END_OF_CAPTION_) {
46093
                    // If an EOC is received while in paint-on mode, the displayed caption
46094
                    // text should be swapped to non-displayed memory as if it was a pop-on
46095
                    // caption. Because of that, we should explicitly switch back to pop-on
46096
                    // mode
46097
                    this.mode_ = 'popOn';
46098
                    this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
46099
 
46100
                    this.flushDisplayed(packet.pts); // flip memory
46101
 
46102
                    swap = this.displayed_;
46103
                    this.displayed_ = this.nonDisplayed_;
46104
                    this.nonDisplayed_ = swap; // start measuring the time to display the caption
46105
 
46106
                    this.startPts_ = packet.pts;
46107
                } else if (data === this.ROLL_UP_2_ROWS_) {
46108
                    this.rollUpRows_ = 2;
46109
                    this.setRollUp(packet.pts);
46110
                } else if (data === this.ROLL_UP_3_ROWS_) {
46111
                    this.rollUpRows_ = 3;
46112
                    this.setRollUp(packet.pts);
46113
                } else if (data === this.ROLL_UP_4_ROWS_) {
46114
                    this.rollUpRows_ = 4;
46115
                    this.setRollUp(packet.pts);
46116
                } else if (data === this.CARRIAGE_RETURN_) {
46117
                    this.clearFormatting(packet.pts);
46118
                    this.flushDisplayed(packet.pts);
46119
                    this.shiftRowsUp_();
46120
                    this.startPts_ = packet.pts;
46121
                } else if (data === this.BACKSPACE_) {
46122
                    if (this.mode_ === 'popOn') {
46123
                        this.nonDisplayed_[this.row_].text = this.nonDisplayed_[this.row_].text.slice(0, -1);
46124
                    } else {
46125
                        this.displayed_[this.row_].text = this.displayed_[this.row_].text.slice(0, -1);
46126
                    }
46127
                } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
46128
                    this.flushDisplayed(packet.pts);
46129
                    this.displayed_ = createDisplayBuffer();
46130
                } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
46131
                    this.nonDisplayed_ = createDisplayBuffer();
46132
                } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
46133
                    if (this.mode_ !== 'paintOn') {
46134
                        // NOTE: This should be removed when proper caption positioning is
46135
                        // implemented
46136
                        this.flushDisplayed(packet.pts);
46137
                        this.displayed_ = createDisplayBuffer();
46138
                    }
46139
                    this.mode_ = 'paintOn';
46140
                    this.startPts_ = packet.pts; // Append special characters to caption text
46141
                } else if (this.isSpecialCharacter(char0, char1)) {
46142
                    // Bitmask char0 so that we can apply character transformations
46143
                    // regardless of field and data channel.
46144
                    // Then byte-shift to the left and OR with char1 so we can pass the
46145
                    // entire character code to `getCharFromCode`.
46146
                    char0 = (char0 & 0x03) << 8;
46147
                    text = getCharFromCode(char0 | char1);
46148
                    this[this.mode_](packet.pts, text);
46149
                    this.column_++; // Append extended characters to caption text
46150
                } else if (this.isExtCharacter(char0, char1)) {
46151
                    // Extended characters always follow their "non-extended" equivalents.
46152
                    // IE if a "è" is desired, you'll always receive "eè"; non-compliant
46153
                    // decoders are supposed to drop the "è", while compliant decoders
46154
                    // backspace the "e" and insert "è".
46155
                    // Delete the previous character
46156
                    if (this.mode_ === 'popOn') {
46157
                        this.nonDisplayed_[this.row_].text = this.nonDisplayed_[this.row_].text.slice(0, -1);
46158
                    } else {
46159
                        this.displayed_[this.row_].text = this.displayed_[this.row_].text.slice(0, -1);
46160
                    } // Bitmask char0 so that we can apply character transformations
46161
                    // regardless of field and data channel.
46162
                    // Then byte-shift to the left and OR with char1 so we can pass the
46163
                    // entire character code to `getCharFromCode`.
46164
 
46165
                    char0 = (char0 & 0x03) << 8;
46166
                    text = getCharFromCode(char0 | char1);
46167
                    this[this.mode_](packet.pts, text);
46168
                    this.column_++; // Process mid-row codes
46169
                } else if (this.isMidRowCode(char0, char1)) {
46170
                    // Attributes are not additive, so clear all formatting
46171
                    this.clearFormatting(packet.pts); // According to the standard, mid-row codes
46172
                    // should be replaced with spaces, so add one now
46173
 
46174
                    this[this.mode_](packet.pts, ' ');
46175
                    this.column_++;
46176
                    if ((char1 & 0xe) === 0xe) {
46177
                        this.addFormatting(packet.pts, ['i']);
46178
                    }
46179
                    if ((char1 & 0x1) === 0x1) {
46180
                        this.addFormatting(packet.pts, ['u']);
46181
                    } // Detect offset control codes and adjust cursor
46182
                } else if (this.isOffsetControlCode(char0, char1)) {
46183
                    // Cursor position is set by indent PAC (see below) in 4-column
46184
                    // increments, with an additional offset code of 1-3 to reach any
46185
                    // of the 32 columns specified by CEA-608. So all we need to do
46186
                    // here is increment the column cursor by the given offset.
46187
                    const offset = char1 & 0x03; // For an offest value 1-3, set the offset for that caption
46188
                    // in the non-displayed array.
46189
 
46190
                    this.nonDisplayed_[this.row_].offset = offset;
46191
                    this.column_ += offset; // Detect PACs (Preamble Address Codes)
46192
                } else if (this.isPAC(char0, char1)) {
46193
                    // There's no logic for PAC -> row mapping, so we have to just
46194
                    // find the row code in an array and use its index :(
46195
                    var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
46196
 
46197
                    if (this.mode_ === 'rollUp') {
46198
                        // This implies that the base row is incorrectly set.
46199
                        // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
46200
                        // of roll-up rows set.
46201
                        if (row - this.rollUpRows_ + 1 < 0) {
46202
                            row = this.rollUpRows_ - 1;
46203
                        }
46204
                        this.setRollUp(packet.pts, row);
46205
                    }
46206
                    if (row !== this.row_) {
46207
                        // formatting is only persistent for current row
46208
                        this.clearFormatting(packet.pts);
46209
                        this.row_ = row;
46210
                    } // All PACs can apply underline, so detect and apply
46211
                    // (All odd-numbered second bytes set underline)
46212
 
46213
                    if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
46214
                        this.addFormatting(packet.pts, ['u']);
46215
                    }
46216
                    if ((data & 0x10) === 0x10) {
46217
                        // We've got an indent level code. Each successive even number
46218
                        // increments the column cursor by 4, so we can get the desired
46219
                        // column position by bit-shifting to the right (to get n/2)
46220
                        // and multiplying by 4.
46221
                        const indentations = (data & 0xe) >> 1;
46222
                        this.column_ = indentations * 4; // add to the number of indentations for positioning
46223
 
46224
                        this.nonDisplayed_[this.row_].indent += indentations;
46225
                    }
46226
                    if (this.isColorPAC(char1)) {
46227
                        // it's a color code, though we only support white, which
46228
                        // can be either normal or italicized. white italics can be
46229
                        // either 0x4e or 0x6e depending on the row, so we just
46230
                        // bitwise-and with 0xe to see if italics should be turned on
46231
                        if ((char1 & 0xe) === 0xe) {
46232
                            this.addFormatting(packet.pts, ['i']);
46233
                        }
46234
                    } // We have a normal character in char0, and possibly one in char1
46235
                } else if (this.isNormalChar(char0)) {
46236
                    if (char1 === 0x00) {
46237
                        char1 = null;
46238
                    }
46239
                    text = getCharFromCode(char0);
46240
                    text += getCharFromCode(char1);
46241
                    this[this.mode_](packet.pts, text);
46242
                    this.column_ += text.length;
46243
                } // finish data processing
46244
            };
46245
        };
46246
 
46247
        Cea608Stream.prototype = new Stream$7(); // Trigger a cue point that captures the current state of the
46248
        // display buffer
46249
 
46250
        Cea608Stream.prototype.flushDisplayed = function (pts) {
46251
            const logWarning = index => {
46252
                this.trigger('log', {
46253
                    level: 'warn',
46254
                    message: 'Skipping a malformed 608 caption at index ' + index + '.'
46255
                });
46256
            };
46257
            const content = [];
46258
            this.displayed_.forEach((row, i) => {
46259
                if (row && row.text && row.text.length) {
46260
                    try {
46261
                        // remove spaces from the start and end of the string
46262
                        row.text = row.text.trim();
46263
                    } catch (e) {
46264
                        // Ordinarily, this shouldn't happen. However, caption
46265
                        // parsing errors should not throw exceptions and
46266
                        // break playback.
46267
                        logWarning(i);
46268
                    } // See the below link for more details on the following fields:
46269
                    // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-608
46270
 
46271
                    if (row.text.length) {
46272
                        content.push({
46273
                            // The text to be displayed in the caption from this specific row, with whitespace removed.
46274
                            text: row.text,
46275
                            // Value between 1 and 15 representing the PAC row used to calculate line height.
46276
                            line: i + 1,
46277
                            // A number representing the indent position by percentage (CEA-608 PAC indent code).
46278
                            // The value will be a number between 10 and 80. Offset is used to add an aditional
46279
                            // value to the position if necessary.
46280
                            position: 10 + Math.min(70, row.indent * 10) + row.offset * 2.5
46281
                        });
46282
                    }
46283
                } else if (row === undefined || row === null) {
46284
                    logWarning(i);
46285
                }
46286
            });
46287
            if (content.length) {
46288
                this.trigger('data', {
46289
                    startPts: this.startPts_,
46290
                    endPts: pts,
46291
                    content,
46292
                    stream: this.name_
46293
                });
46294
            }
46295
        };
46296
        /**
46297
         * Zero out the data, used for startup and on seek
46298
         */
46299
 
46300
        Cea608Stream.prototype.reset = function () {
46301
            this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
46302
            // actually display captions. If a caption is shifted to a row
46303
            // with a lower index than this, it is cleared from the display
46304
            // buffer
46305
 
46306
            this.topRow_ = 0;
46307
            this.startPts_ = 0;
46308
            this.displayed_ = createDisplayBuffer();
46309
            this.nonDisplayed_ = createDisplayBuffer();
46310
            this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
46311
 
46312
            this.column_ = 0;
46313
            this.row_ = BOTTOM_ROW;
46314
            this.rollUpRows_ = 2; // This variable holds currently-applied formatting
46315
 
46316
            this.formatting_ = [];
46317
        };
46318
        /**
46319
         * Sets up control code and related constants for this instance
46320
         */
46321
 
46322
        Cea608Stream.prototype.setConstants = function () {
46323
            // The following attributes have these uses:
46324
            // ext_ :    char0 for mid-row codes, and the base for extended
46325
            //           chars (ext_+0, ext_+1, and ext_+2 are char0s for
46326
            //           extended codes)
46327
            // control_: char0 for control codes, except byte-shifted to the
46328
            //           left so that we can do this.control_ | CONTROL_CODE
46329
            // offset_:  char0 for tab offset codes
46330
            //
46331
            // It's also worth noting that control codes, and _only_ control codes,
46332
            // differ between field 1 and field2. Field 2 control codes are always
46333
            // their field 1 value plus 1. That's why there's the "| field" on the
46334
            // control value.
46335
            if (this.dataChannel_ === 0) {
46336
                this.BASE_ = 0x10;
46337
                this.EXT_ = 0x11;
46338
                this.CONTROL_ = (0x14 | this.field_) << 8;
46339
                this.OFFSET_ = 0x17;
46340
            } else if (this.dataChannel_ === 1) {
46341
                this.BASE_ = 0x18;
46342
                this.EXT_ = 0x19;
46343
                this.CONTROL_ = (0x1c | this.field_) << 8;
46344
                this.OFFSET_ = 0x1f;
46345
            } // Constants for the LSByte command codes recognized by Cea608Stream. This
46346
            // list is not exhaustive. For a more comprehensive listing and semantics see
46347
            // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
46348
            // Padding
46349
 
46350
            this.PADDING_ = 0x0000; // Pop-on Mode
46351
 
46352
            this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
46353
            this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
46354
 
46355
            this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
46356
            this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
46357
            this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
46358
            this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
46359
 
46360
            this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
46361
 
46362
            this.BACKSPACE_ = this.CONTROL_ | 0x21;
46363
            this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
46364
            this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
46365
        };
46366
        /**
46367
         * Detects if the 2-byte packet data is a special character
46368
         *
46369
         * Special characters have a second byte in the range 0x30 to 0x3f,
46370
         * with the first byte being 0x11 (for data channel 1) or 0x19 (for
46371
         * data channel 2).
46372
         *
46373
         * @param  {Integer} char0 The first byte
46374
         * @param  {Integer} char1 The second byte
46375
         * @return {Boolean}       Whether the 2 bytes are an special character
46376
         */
46377
 
46378
        Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
46379
            return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
46380
        };
46381
        /**
46382
         * Detects if the 2-byte packet data is an extended character
46383
         *
46384
         * Extended characters have a second byte in the range 0x20 to 0x3f,
46385
         * with the first byte being 0x12 or 0x13 (for data channel 1) or
46386
         * 0x1a or 0x1b (for data channel 2).
46387
         *
46388
         * @param  {Integer} char0 The first byte
46389
         * @param  {Integer} char1 The second byte
46390
         * @return {Boolean}       Whether the 2 bytes are an extended character
46391
         */
46392
 
46393
        Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
46394
            return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
46395
        };
46396
        /**
46397
         * Detects if the 2-byte packet is a mid-row code
46398
         *
46399
         * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
46400
         * the first byte being 0x11 (for data channel 1) or 0x19 (for data
46401
         * channel 2).
46402
         *
46403
         * @param  {Integer} char0 The first byte
46404
         * @param  {Integer} char1 The second byte
46405
         * @return {Boolean}       Whether the 2 bytes are a mid-row code
46406
         */
46407
 
46408
        Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
46409
            return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
46410
        };
46411
        /**
46412
         * Detects if the 2-byte packet is an offset control code
46413
         *
46414
         * Offset control codes have a second byte in the range 0x21 to 0x23,
46415
         * with the first byte being 0x17 (for data channel 1) or 0x1f (for
46416
         * data channel 2).
46417
         *
46418
         * @param  {Integer} char0 The first byte
46419
         * @param  {Integer} char1 The second byte
46420
         * @return {Boolean}       Whether the 2 bytes are an offset control code
46421
         */
46422
 
46423
        Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
46424
            return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
46425
        };
46426
        /**
46427
         * Detects if the 2-byte packet is a Preamble Address Code
46428
         *
46429
         * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
46430
         * or 0x18 to 0x1f (for data channel 2), with the second byte in the
46431
         * range 0x40 to 0x7f.
46432
         *
46433
         * @param  {Integer} char0 The first byte
46434
         * @param  {Integer} char1 The second byte
46435
         * @return {Boolean}       Whether the 2 bytes are a PAC
46436
         */
46437
 
46438
        Cea608Stream.prototype.isPAC = function (char0, char1) {
46439
            return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
46440
        };
46441
        /**
46442
         * Detects if a packet's second byte is in the range of a PAC color code
46443
         *
46444
         * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
46445
         * 0x60 to 0x6f.
46446
         *
46447
         * @param  {Integer} char1 The second byte
46448
         * @return {Boolean}       Whether the byte is a color PAC
46449
         */
46450
 
46451
        Cea608Stream.prototype.isColorPAC = function (char1) {
46452
            return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
46453
        };
46454
        /**
46455
         * Detects if a single byte is in the range of a normal character
46456
         *
46457
         * Normal text bytes are in the range 0x20 to 0x7f.
46458
         *
46459
         * @param  {Integer} char  The byte
46460
         * @return {Boolean}       Whether the byte is a normal character
46461
         */
46462
 
46463
        Cea608Stream.prototype.isNormalChar = function (char) {
46464
            return char >= 0x20 && char <= 0x7f;
46465
        };
46466
        /**
46467
         * Configures roll-up
46468
         *
46469
         * @param  {Integer} pts         Current PTS
46470
         * @param  {Integer} newBaseRow  Used by PACs to slide the current window to
46471
         *                               a new position
46472
         */
46473
 
46474
        Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
46475
            // Reset the base row to the bottom row when switching modes
46476
            if (this.mode_ !== 'rollUp') {
46477
                this.row_ = BOTTOM_ROW;
46478
                this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
46479
 
46480
                this.flushDisplayed(pts);
46481
                this.nonDisplayed_ = createDisplayBuffer();
46482
                this.displayed_ = createDisplayBuffer();
46483
            }
46484
            if (newBaseRow !== undefined && newBaseRow !== this.row_) {
46485
                // move currently displayed captions (up or down) to the new base row
46486
                for (var i = 0; i < this.rollUpRows_; i++) {
46487
                    this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
46488
                    this.displayed_[this.row_ - i] = {
46489
                        text: '',
46490
                        indent: 0,
46491
                        offset: 0
46492
                    };
46493
                }
46494
            }
46495
            if (newBaseRow === undefined) {
46496
                newBaseRow = this.row_;
46497
            }
46498
            this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
46499
        }; // Adds the opening HTML tag for the passed character to the caption text,
46500
        // and keeps track of it for later closing
46501
 
46502
        Cea608Stream.prototype.addFormatting = function (pts, format) {
46503
            this.formatting_ = this.formatting_.concat(format);
46504
            var text = format.reduce(function (text, format) {
46505
                return text + '<' + format + '>';
46506
            }, '');
46507
            this[this.mode_](pts, text);
46508
        }; // Adds HTML closing tags for current formatting to caption text and
46509
        // clears remembered formatting
46510
 
46511
        Cea608Stream.prototype.clearFormatting = function (pts) {
46512
            if (!this.formatting_.length) {
46513
                return;
46514
            }
46515
            var text = this.formatting_.reverse().reduce(function (text, format) {
46516
                return text + '</' + format + '>';
46517
            }, '');
46518
            this.formatting_ = [];
46519
            this[this.mode_](pts, text);
46520
        }; // Mode Implementations
46521
 
46522
        Cea608Stream.prototype.popOn = function (pts, text) {
46523
            var baseRow = this.nonDisplayed_[this.row_].text; // buffer characters
46524
 
46525
            baseRow += text;
46526
            this.nonDisplayed_[this.row_].text = baseRow;
46527
        };
46528
        Cea608Stream.prototype.rollUp = function (pts, text) {
46529
            var baseRow = this.displayed_[this.row_].text;
46530
            baseRow += text;
46531
            this.displayed_[this.row_].text = baseRow;
46532
        };
46533
        Cea608Stream.prototype.shiftRowsUp_ = function () {
46534
            var i; // clear out inactive rows
46535
 
46536
            for (i = 0; i < this.topRow_; i++) {
46537
                this.displayed_[i] = {
46538
                    text: '',
46539
                    indent: 0,
46540
                    offset: 0
46541
                };
46542
            }
46543
            for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
46544
                this.displayed_[i] = {
46545
                    text: '',
46546
                    indent: 0,
46547
                    offset: 0
46548
                };
46549
            } // shift displayed rows up
46550
 
46551
            for (i = this.topRow_; i < this.row_; i++) {
46552
                this.displayed_[i] = this.displayed_[i + 1];
46553
            } // clear out the bottom row
46554
 
46555
            this.displayed_[this.row_] = {
46556
                text: '',
46557
                indent: 0,
46558
                offset: 0
46559
            };
46560
        };
46561
        Cea608Stream.prototype.paintOn = function (pts, text) {
46562
            var baseRow = this.displayed_[this.row_].text;
46563
            baseRow += text;
46564
            this.displayed_[this.row_].text = baseRow;
46565
        }; // exports
46566
 
46567
        var captionStream = {
46568
            CaptionStream: CaptionStream$2,
46569
            Cea608Stream: Cea608Stream,
46570
            Cea708Stream: Cea708Stream
46571
        };
46572
        /**
46573
         * mux.js
46574
         *
46575
         * Copyright (c) Brightcove
46576
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
46577
         */
46578
 
46579
        var streamTypes = {
46580
            H264_STREAM_TYPE: 0x1B,
46581
            ADTS_STREAM_TYPE: 0x0F,
46582
            METADATA_STREAM_TYPE: 0x15
46583
        };
46584
        /**
46585
         * mux.js
46586
         *
46587
         * Copyright (c) Brightcove
46588
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
46589
         *
46590
         * Accepts program elementary stream (PES) data events and corrects
46591
         * decode and presentation time stamps to account for a rollover
46592
         * of the 33 bit value.
46593
         */
46594
 
46595
        var Stream$6 = stream;
46596
        var MAX_TS = 8589934592;
46597
        var RO_THRESH = 4294967296;
46598
        var TYPE_SHARED = 'shared';
46599
        var handleRollover$1 = function (value, reference) {
46600
            var direction = 1;
46601
            if (value > reference) {
46602
                // If the current timestamp value is greater than our reference timestamp and we detect a
46603
                // timestamp rollover, this means the roll over is happening in the opposite direction.
46604
                // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
46605
                // point will be set to a small number, e.g. 1. The user then seeks backwards over the
46606
                // rollover point. In loading this segment, the timestamp values will be very large,
46607
                // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
46608
                // the time stamp to be `value - 2^33`.
46609
                direction = -1;
46610
            } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
46611
            // cause an incorrect adjustment.
46612
 
46613
            while (Math.abs(reference - value) > RO_THRESH) {
46614
                value += direction * MAX_TS;
46615
            }
46616
            return value;
46617
        };
46618
        var TimestampRolloverStream$1 = function (type) {
46619
            var lastDTS, referenceDTS;
46620
            TimestampRolloverStream$1.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed
46621
            // video and audio. We could use `undefined` here, but having a string
46622
            // makes debugging a little clearer.
46623
 
46624
            this.type_ = type || TYPE_SHARED;
46625
            this.push = function (data) {
46626
                /**
46627
                 * Rollover stream expects data from elementary stream.
46628
                 * Elementary stream can push forward 2 types of data
46629
                 * - Parsed Video/Audio/Timed-metadata PES (packetized elementary stream) packets
46630
                 * - Tracks metadata from PMT (Program Map Table)
46631
                 * Rollover stream expects pts/dts info to be available, since it stores lastDTS
46632
                 * We should ignore non-PES packets since they may override lastDTS to undefined.
46633
                 * lastDTS is important to signal the next segments
46634
                 * about rollover from the previous segments.
46635
                 */
46636
                if (data.type === 'metadata') {
46637
                    this.trigger('data', data);
46638
                    return;
46639
                } // Any "shared" rollover streams will accept _all_ data. Otherwise,
46640
                // streams will only accept data that matches their type.
46641
 
46642
                if (this.type_ !== TYPE_SHARED && data.type !== this.type_) {
46643
                    return;
46644
                }
46645
                if (referenceDTS === undefined) {
46646
                    referenceDTS = data.dts;
46647
                }
46648
                data.dts = handleRollover$1(data.dts, referenceDTS);
46649
                data.pts = handleRollover$1(data.pts, referenceDTS);
46650
                lastDTS = data.dts;
46651
                this.trigger('data', data);
46652
            };
46653
            this.flush = function () {
46654
                referenceDTS = lastDTS;
46655
                this.trigger('done');
46656
            };
46657
            this.endTimeline = function () {
46658
                this.flush();
46659
                this.trigger('endedtimeline');
46660
            };
46661
            this.discontinuity = function () {
46662
                referenceDTS = void 0;
46663
                lastDTS = void 0;
46664
            };
46665
            this.reset = function () {
46666
                this.discontinuity();
46667
                this.trigger('reset');
46668
            };
46669
        };
46670
        TimestampRolloverStream$1.prototype = new Stream$6();
46671
        var timestampRolloverStream = {
46672
            TimestampRolloverStream: TimestampRolloverStream$1,
46673
            handleRollover: handleRollover$1
46674
        }; // Once IE11 support is dropped, this function should be removed.
46675
 
46676
        var typedArrayIndexOf$1 = (typedArray, element, fromIndex) => {
46677
            if (!typedArray) {
46678
                return -1;
46679
            }
46680
            var currentIndex = fromIndex;
46681
            for (; currentIndex < typedArray.length; currentIndex++) {
46682
                if (typedArray[currentIndex] === element) {
46683
                    return currentIndex;
46684
                }
46685
            }
46686
            return -1;
46687
        };
46688
        var typedArray = {
46689
            typedArrayIndexOf: typedArrayIndexOf$1
46690
        };
46691
        /**
46692
         * mux.js
46693
         *
46694
         * Copyright (c) Brightcove
46695
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
46696
         *
46697
         * Tools for parsing ID3 frame data
46698
         * @see http://id3.org/id3v2.3.0
46699
         */
46700
 
46701
        var typedArrayIndexOf = typedArray.typedArrayIndexOf,
46702
            // Frames that allow different types of text encoding contain a text
46703
            // encoding description byte [ID3v2.4.0 section 4.]
46704
            textEncodingDescriptionByte = {
46705
                Iso88591: 0x00,
46706
                // ISO-8859-1, terminated with \0.
46707
                Utf16: 0x01,
46708
                // UTF-16 encoded Unicode BOM, terminated with \0\0
46709
                Utf16be: 0x02,
46710
                // UTF-16BE encoded Unicode, without BOM, terminated with \0\0
46711
                Utf8: 0x03 // UTF-8 encoded Unicode, terminated with \0
46712
            },
46713
            // return a percent-encoded representation of the specified byte range
46714
            // @see http://en.wikipedia.org/wiki/Percent-encoding
46715
            percentEncode$1 = function (bytes, start, end) {
46716
                var i,
46717
                    result = '';
46718
                for (i = start; i < end; i++) {
46719
                    result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
46720
                }
46721
                return result;
46722
            },
46723
            // return the string representation of the specified byte range,
46724
            // interpreted as UTf-8.
46725
            parseUtf8 = function (bytes, start, end) {
46726
                return decodeURIComponent(percentEncode$1(bytes, start, end));
46727
            },
46728
            // return the string representation of the specified byte range,
46729
            // interpreted as ISO-8859-1.
46730
            parseIso88591$1 = function (bytes, start, end) {
46731
                return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
46732
            },
46733
            parseSyncSafeInteger$1 = function (data) {
46734
                return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
46735
            },
46736
            frameParsers = {
46737
                'APIC': function (frame) {
46738
                    var i = 1,
46739
                        mimeTypeEndIndex,
46740
                        descriptionEndIndex,
46741
                        LINK_MIME_TYPE = '-->';
46742
                    if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
46743
                        // ignore frames with unrecognized character encodings
46744
                        return;
46745
                    } // parsing fields [ID3v2.4.0 section 4.14.]
46746
 
46747
                    mimeTypeEndIndex = typedArrayIndexOf(frame.data, 0, i);
46748
                    if (mimeTypeEndIndex < 0) {
46749
                        // malformed frame
46750
                        return;
46751
                    } // parsing Mime type field (terminated with \0)
46752
 
46753
                    frame.mimeType = parseIso88591$1(frame.data, i, mimeTypeEndIndex);
46754
                    i = mimeTypeEndIndex + 1; // parsing 1-byte Picture Type field
46755
 
46756
                    frame.pictureType = frame.data[i];
46757
                    i++;
46758
                    descriptionEndIndex = typedArrayIndexOf(frame.data, 0, i);
46759
                    if (descriptionEndIndex < 0) {
46760
                        // malformed frame
46761
                        return;
46762
                    } // parsing Description field (terminated with \0)
46763
 
46764
                    frame.description = parseUtf8(frame.data, i, descriptionEndIndex);
46765
                    i = descriptionEndIndex + 1;
46766
                    if (frame.mimeType === LINK_MIME_TYPE) {
46767
                        // parsing Picture Data field as URL (always represented as ISO-8859-1 [ID3v2.4.0 section 4.])
46768
                        frame.url = parseIso88591$1(frame.data, i, frame.data.length);
46769
                    } else {
46770
                        // parsing Picture Data field as binary data
46771
                        frame.pictureData = frame.data.subarray(i, frame.data.length);
46772
                    }
46773
                },
46774
                'T*': function (frame) {
46775
                    if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
46776
                        // ignore frames with unrecognized character encodings
46777
                        return;
46778
                    } // parse text field, do not include null terminator in the frame value
46779
                    // frames that allow different types of encoding contain terminated text [ID3v2.4.0 section 4.]
46780
 
46781
                    frame.value = parseUtf8(frame.data, 1, frame.data.length).replace(/\0*$/, ''); // text information frames supports multiple strings, stored as a terminator separated list [ID3v2.4.0 section 4.2.]
46782
 
46783
                    frame.values = frame.value.split('\0');
46784
                },
46785
                'TXXX': function (frame) {
46786
                    var descriptionEndIndex;
46787
                    if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
46788
                        // ignore frames with unrecognized character encodings
46789
                        return;
46790
                    }
46791
                    descriptionEndIndex = typedArrayIndexOf(frame.data, 0, 1);
46792
                    if (descriptionEndIndex === -1) {
46793
                        return;
46794
                    } // parse the text fields
46795
 
46796
                    frame.description = parseUtf8(frame.data, 1, descriptionEndIndex); // do not include the null terminator in the tag value
46797
                    // frames that allow different types of encoding contain terminated text
46798
                    // [ID3v2.4.0 section 4.]
46799
 
46800
                    frame.value = parseUtf8(frame.data, descriptionEndIndex + 1, frame.data.length).replace(/\0*$/, '');
46801
                    frame.data = frame.value;
46802
                },
46803
                'W*': function (frame) {
46804
                    // parse URL field; URL fields are always represented as ISO-8859-1 [ID3v2.4.0 section 4.]
46805
                    // if the value is followed by a string termination all the following information should be ignored [ID3v2.4.0 section 4.3]
46806
                    frame.url = parseIso88591$1(frame.data, 0, frame.data.length).replace(/\0.*$/, '');
46807
                },
46808
                'WXXX': function (frame) {
46809
                    var descriptionEndIndex;
46810
                    if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
46811
                        // ignore frames with unrecognized character encodings
46812
                        return;
46813
                    }
46814
                    descriptionEndIndex = typedArrayIndexOf(frame.data, 0, 1);
46815
                    if (descriptionEndIndex === -1) {
46816
                        return;
46817
                    } // parse the description and URL fields
46818
 
46819
                    frame.description = parseUtf8(frame.data, 1, descriptionEndIndex); // URL fields are always represented as ISO-8859-1 [ID3v2.4.0 section 4.]
46820
                    // if the value is followed by a string termination all the following information
46821
                    // should be ignored [ID3v2.4.0 section 4.3]
46822
 
46823
                    frame.url = parseIso88591$1(frame.data, descriptionEndIndex + 1, frame.data.length).replace(/\0.*$/, '');
46824
                },
46825
                'PRIV': function (frame) {
46826
                    var i;
46827
                    for (i = 0; i < frame.data.length; i++) {
46828
                        if (frame.data[i] === 0) {
46829
                            // parse the description and URL fields
46830
                            frame.owner = parseIso88591$1(frame.data, 0, i);
46831
                            break;
46832
                        }
46833
                    }
46834
                    frame.privateData = frame.data.subarray(i + 1);
46835
                    frame.data = frame.privateData;
46836
                }
46837
            };
46838
        var parseId3Frames$1 = function (data) {
46839
            var frameSize,
46840
                frameHeader,
46841
                frameStart = 10,
46842
                tagSize = 0,
46843
                frames = []; // If we don't have enough data for a header, 10 bytes,
46844
            // or 'ID3' in the first 3 bytes this is not a valid ID3 tag.
46845
 
46846
            if (data.length < 10 || data[0] !== 'I'.charCodeAt(0) || data[1] !== 'D'.charCodeAt(0) || data[2] !== '3'.charCodeAt(0)) {
46847
                return;
46848
            } // the frame size is transmitted as a 28-bit integer in the
46849
            // last four bytes of the ID3 header.
46850
            // The most significant bit of each byte is dropped and the
46851
            // results concatenated to recover the actual value.
46852
 
46853
            tagSize = parseSyncSafeInteger$1(data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
46854
            // convenient for our comparisons to include it
46855
 
46856
            tagSize += 10; // check bit 6 of byte 5 for the extended header flag.
46857
 
46858
            var hasExtendedHeader = data[5] & 0x40;
46859
            if (hasExtendedHeader) {
46860
                // advance the frame start past the extended header
46861
                frameStart += 4; // header size field
46862
 
46863
                frameStart += parseSyncSafeInteger$1(data.subarray(10, 14));
46864
                tagSize -= parseSyncSafeInteger$1(data.subarray(16, 20)); // clip any padding off the end
46865
            } // parse one or more ID3 frames
46866
            // http://id3.org/id3v2.3.0#ID3v2_frame_overview
46867
 
46868
            do {
46869
                // determine the number of bytes in this frame
46870
                frameSize = parseSyncSafeInteger$1(data.subarray(frameStart + 4, frameStart + 8));
46871
                if (frameSize < 1) {
46872
                    break;
46873
                }
46874
                frameHeader = String.fromCharCode(data[frameStart], data[frameStart + 1], data[frameStart + 2], data[frameStart + 3]);
46875
                var frame = {
46876
                    id: frameHeader,
46877
                    data: data.subarray(frameStart + 10, frameStart + frameSize + 10)
46878
                };
46879
                frame.key = frame.id; // parse frame values
46880
 
46881
                if (frameParsers[frame.id]) {
46882
                    // use frame specific parser
46883
                    frameParsers[frame.id](frame);
46884
                } else if (frame.id[0] === 'T') {
46885
                    // use text frame generic parser
46886
                    frameParsers['T*'](frame);
46887
                } else if (frame.id[0] === 'W') {
46888
                    // use URL link frame generic parser
46889
                    frameParsers['W*'](frame);
46890
                }
46891
                frames.push(frame);
46892
                frameStart += 10; // advance past the frame header
46893
 
46894
                frameStart += frameSize; // advance past the frame body
46895
            } while (frameStart < tagSize);
46896
            return frames;
46897
        };
46898
        var parseId3 = {
46899
            parseId3Frames: parseId3Frames$1,
46900
            parseSyncSafeInteger: parseSyncSafeInteger$1,
46901
            frameParsers: frameParsers
46902
        };
46903
        /**
46904
         * mux.js
46905
         *
46906
         * Copyright (c) Brightcove
46907
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
46908
         *
46909
         * Accepts program elementary stream (PES) data events and parses out
46910
         * ID3 metadata from them, if present.
46911
         * @see http://id3.org/id3v2.3.0
46912
         */
46913
 
46914
        var Stream$5 = stream,
46915
            StreamTypes$3 = streamTypes,
46916
            id3 = parseId3,
46917
            MetadataStream;
46918
        MetadataStream = function (options) {
46919
            var settings = {
46920
                    // the bytes of the program-level descriptor field in MP2T
46921
                    // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
46922
                    // program element descriptors"
46923
                    descriptor: options && options.descriptor
46924
                },
46925
                // the total size in bytes of the ID3 tag being parsed
46926
                tagSize = 0,
46927
                // tag data that is not complete enough to be parsed
46928
                buffer = [],
46929
                // the total number of bytes currently in the buffer
46930
                bufferSize = 0,
46931
                i;
46932
            MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
46933
            // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
46934
 
46935
            this.dispatchType = StreamTypes$3.METADATA_STREAM_TYPE.toString(16);
46936
            if (settings.descriptor) {
46937
                for (i = 0; i < settings.descriptor.length; i++) {
46938
                    this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
46939
                }
46940
            }
46941
            this.push = function (chunk) {
46942
                var tag, frameStart, frameSize, frame, i, frameHeader;
46943
                if (chunk.type !== 'timed-metadata') {
46944
                    return;
46945
                } // if data_alignment_indicator is set in the PES header,
46946
                // we must have the start of a new ID3 tag. Assume anything
46947
                // remaining in the buffer was malformed and throw it out
46948
 
46949
                if (chunk.dataAlignmentIndicator) {
46950
                    bufferSize = 0;
46951
                    buffer.length = 0;
46952
                } // ignore events that don't look like ID3 data
46953
 
46954
                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))) {
46955
                    this.trigger('log', {
46956
                        level: 'warn',
46957
                        message: 'Skipping unrecognized metadata packet'
46958
                    });
46959
                    return;
46960
                } // add this chunk to the data we've collected so far
46961
 
46962
                buffer.push(chunk);
46963
                bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
46964
 
46965
                if (buffer.length === 1) {
46966
                    // the frame size is transmitted as a 28-bit integer in the
46967
                    // last four bytes of the ID3 header.
46968
                    // The most significant bit of each byte is dropped and the
46969
                    // results concatenated to recover the actual value.
46970
                    tagSize = id3.parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
46971
                    // convenient for our comparisons to include it
46972
 
46973
                    tagSize += 10;
46974
                } // if the entire frame has not arrived, wait for more data
46975
 
46976
                if (bufferSize < tagSize) {
46977
                    return;
46978
                } // collect the entire frame so it can be parsed
46979
 
46980
                tag = {
46981
                    data: new Uint8Array(tagSize),
46982
                    frames: [],
46983
                    pts: buffer[0].pts,
46984
                    dts: buffer[0].dts
46985
                };
46986
                for (i = 0; i < tagSize;) {
46987
                    tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
46988
                    i += buffer[0].data.byteLength;
46989
                    bufferSize -= buffer[0].data.byteLength;
46990
                    buffer.shift();
46991
                } // find the start of the first frame and the end of the tag
46992
 
46993
                frameStart = 10;
46994
                if (tag.data[5] & 0x40) {
46995
                    // advance the frame start past the extended header
46996
                    frameStart += 4; // header size field
46997
 
46998
                    frameStart += id3.parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
46999
 
47000
                    tagSize -= id3.parseSyncSafeInteger(tag.data.subarray(16, 20));
47001
                } // parse one or more ID3 frames
47002
                // http://id3.org/id3v2.3.0#ID3v2_frame_overview
47003
 
47004
                do {
47005
                    // determine the number of bytes in this frame
47006
                    frameSize = id3.parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
47007
                    if (frameSize < 1) {
47008
                        this.trigger('log', {
47009
                            level: 'warn',
47010
                            message: 'Malformed ID3 frame encountered. Skipping remaining metadata parsing.'
47011
                        }); // If the frame is malformed, don't parse any further frames but allow previous valid parsed frames
47012
                        // to be sent along.
47013
 
47014
                        break;
47015
                    }
47016
                    frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
47017
                    frame = {
47018
                        id: frameHeader,
47019
                        data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
47020
                    };
47021
                    frame.key = frame.id; // parse frame values
47022
 
47023
                    if (id3.frameParsers[frame.id]) {
47024
                        // use frame specific parser
47025
                        id3.frameParsers[frame.id](frame);
47026
                    } else if (frame.id[0] === 'T') {
47027
                        // use text frame generic parser
47028
                        id3.frameParsers['T*'](frame);
47029
                    } else if (frame.id[0] === 'W') {
47030
                        // use URL link frame generic parser
47031
                        id3.frameParsers['W*'](frame);
47032
                    } // handle the special PRIV frame used to indicate the start
47033
                    // time for raw AAC data
47034
 
47035
                    if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
47036
                        var d = frame.data,
47037
                            size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
47038
                        size *= 4;
47039
                        size += d[7] & 0x03;
47040
                        frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
47041
                        // on the value of this frame
47042
                        // we couldn't have known the appropriate pts and dts before
47043
                        // parsing this ID3 tag so set those values now
47044
 
47045
                        if (tag.pts === undefined && tag.dts === undefined) {
47046
                            tag.pts = frame.timeStamp;
47047
                            tag.dts = frame.timeStamp;
47048
                        }
47049
                        this.trigger('timestamp', frame);
47050
                    }
47051
                    tag.frames.push(frame);
47052
                    frameStart += 10; // advance past the frame header
47053
 
47054
                    frameStart += frameSize; // advance past the frame body
47055
                } while (frameStart < tagSize);
47056
                this.trigger('data', tag);
47057
            };
47058
        };
47059
        MetadataStream.prototype = new Stream$5();
47060
        var metadataStream = MetadataStream;
47061
        /**
47062
         * mux.js
47063
         *
47064
         * Copyright (c) Brightcove
47065
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
47066
         *
47067
         * A stream-based mp2t to mp4 converter. This utility can be used to
47068
         * deliver mp4s to a SourceBuffer on platforms that support native
47069
         * Media Source Extensions.
47070
         */
47071
 
47072
        var Stream$4 = stream,
47073
            CaptionStream$1 = captionStream,
47074
            StreamTypes$2 = streamTypes,
47075
            TimestampRolloverStream = timestampRolloverStream.TimestampRolloverStream; // object types
47076
 
47077
        var TransportPacketStream, TransportParseStream, ElementaryStream; // constants
47078
 
47079
        var MP2T_PACKET_LENGTH$1 = 188,
47080
            // bytes
47081
            SYNC_BYTE$1 = 0x47;
47082
        /**
47083
         * Splits an incoming stream of binary data into MPEG-2 Transport
47084
         * Stream packets.
47085
         */
47086
 
47087
        TransportPacketStream = function () {
47088
            var buffer = new Uint8Array(MP2T_PACKET_LENGTH$1),
47089
                bytesInBuffer = 0;
47090
            TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
47091
 
47092
            /**
47093
             * Split a stream of data into M2TS packets
47094
             **/
47095
 
47096
            this.push = function (bytes) {
47097
                var startIndex = 0,
47098
                    endIndex = MP2T_PACKET_LENGTH$1,
47099
                    everything; // If there are bytes remaining from the last segment, prepend them to the
47100
                // bytes that were pushed in
47101
 
47102
                if (bytesInBuffer) {
47103
                    everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
47104
                    everything.set(buffer.subarray(0, bytesInBuffer));
47105
                    everything.set(bytes, bytesInBuffer);
47106
                    bytesInBuffer = 0;
47107
                } else {
47108
                    everything = bytes;
47109
                } // While we have enough data for a packet
47110
 
47111
                while (endIndex < everything.byteLength) {
47112
                    // Look for a pair of start and end sync bytes in the data..
47113
                    if (everything[startIndex] === SYNC_BYTE$1 && everything[endIndex] === SYNC_BYTE$1) {
47114
                        // We found a packet so emit it and jump one whole packet forward in
47115
                        // the stream
47116
                        this.trigger('data', everything.subarray(startIndex, endIndex));
47117
                        startIndex += MP2T_PACKET_LENGTH$1;
47118
                        endIndex += MP2T_PACKET_LENGTH$1;
47119
                        continue;
47120
                    } // If we get here, we have somehow become de-synchronized and we need to step
47121
                    // forward one byte at a time until we find a pair of sync bytes that denote
47122
                    // a packet
47123
 
47124
                    startIndex++;
47125
                    endIndex++;
47126
                } // If there was some data left over at the end of the segment that couldn't
47127
                // possibly be a whole packet, keep it because it might be the start of a packet
47128
                // that continues in the next segment
47129
 
47130
                if (startIndex < everything.byteLength) {
47131
                    buffer.set(everything.subarray(startIndex), 0);
47132
                    bytesInBuffer = everything.byteLength - startIndex;
47133
                }
47134
            };
47135
            /**
47136
             * Passes identified M2TS packets to the TransportParseStream to be parsed
47137
             **/
47138
 
47139
            this.flush = function () {
47140
                // If the buffer contains a whole packet when we are being flushed, emit it
47141
                // and empty the buffer. Otherwise hold onto the data because it may be
47142
                // important for decoding the next segment
47143
                if (bytesInBuffer === MP2T_PACKET_LENGTH$1 && buffer[0] === SYNC_BYTE$1) {
47144
                    this.trigger('data', buffer);
47145
                    bytesInBuffer = 0;
47146
                }
47147
                this.trigger('done');
47148
            };
47149
            this.endTimeline = function () {
47150
                this.flush();
47151
                this.trigger('endedtimeline');
47152
            };
47153
            this.reset = function () {
47154
                bytesInBuffer = 0;
47155
                this.trigger('reset');
47156
            };
47157
        };
47158
        TransportPacketStream.prototype = new Stream$4();
47159
        /**
47160
         * Accepts an MP2T TransportPacketStream and emits data events with parsed
47161
         * forms of the individual transport stream packets.
47162
         */
47163
 
47164
        TransportParseStream = function () {
47165
            var parsePsi, parsePat, parsePmt, self;
47166
            TransportParseStream.prototype.init.call(this);
47167
            self = this;
47168
            this.packetsWaitingForPmt = [];
47169
            this.programMapTable = undefined;
47170
            parsePsi = function (payload, psi) {
47171
                var offset = 0; // PSI packets may be split into multiple sections and those
47172
                // sections may be split into multiple packets. If a PSI
47173
                // section starts in this packet, the payload_unit_start_indicator
47174
                // will be true and the first byte of the payload will indicate
47175
                // the offset from the current position to the start of the
47176
                // section.
47177
 
47178
                if (psi.payloadUnitStartIndicator) {
47179
                    offset += payload[offset] + 1;
47180
                }
47181
                if (psi.type === 'pat') {
47182
                    parsePat(payload.subarray(offset), psi);
47183
                } else {
47184
                    parsePmt(payload.subarray(offset), psi);
47185
                }
47186
            };
47187
            parsePat = function (payload, pat) {
47188
                pat.section_number = payload[7]; // eslint-disable-line camelcase
47189
 
47190
                pat.last_section_number = payload[8]; // eslint-disable-line camelcase
47191
                // skip the PSI header and parse the first PMT entry
47192
 
47193
                self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
47194
                pat.pmtPid = self.pmtPid;
47195
            };
47196
            /**
47197
             * Parse out the relevant fields of a Program Map Table (PMT).
47198
             * @param payload {Uint8Array} the PMT-specific portion of an MP2T
47199
             * packet. The first byte in this array should be the table_id
47200
             * field.
47201
             * @param pmt {object} the object that should be decorated with
47202
             * fields parsed from the PMT.
47203
             */
47204
 
47205
            parsePmt = function (payload, pmt) {
47206
                var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
47207
                // take effect. We don't believe this should ever be the case
47208
                // for HLS but we'll ignore "forward" PMT declarations if we see
47209
                // them. Future PMT declarations have the current_next_indicator
47210
                // set to zero.
47211
 
47212
                if (!(payload[5] & 0x01)) {
47213
                    return;
47214
                } // overwrite any existing program map table
47215
 
47216
                self.programMapTable = {
47217
                    video: null,
47218
                    audio: null,
47219
                    'timed-metadata': {}
47220
                }; // the mapping table ends at the end of the current section
47221
 
47222
                sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
47223
                tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
47224
                // long the program info descriptors are
47225
 
47226
                programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
47227
 
47228
                offset = 12 + programInfoLength;
47229
                while (offset < tableEnd) {
47230
                    var streamType = payload[offset];
47231
                    var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
47232
                    // TODO: should this be done for metadata too? for now maintain behavior of
47233
                    //       multiple metadata streams
47234
 
47235
                    if (streamType === StreamTypes$2.H264_STREAM_TYPE && self.programMapTable.video === null) {
47236
                        self.programMapTable.video = pid;
47237
                    } else if (streamType === StreamTypes$2.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
47238
                        self.programMapTable.audio = pid;
47239
                    } else if (streamType === StreamTypes$2.METADATA_STREAM_TYPE) {
47240
                        // map pid to stream type for metadata streams
47241
                        self.programMapTable['timed-metadata'][pid] = streamType;
47242
                    } // move to the next table entry
47243
                    // skip past the elementary stream descriptors, if present
47244
 
47245
                    offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
47246
                } // record the map on the packet as well
47247
 
47248
                pmt.programMapTable = self.programMapTable;
47249
            };
47250
            /**
47251
             * Deliver a new MP2T packet to the next stream in the pipeline.
47252
             */
47253
 
47254
            this.push = function (packet) {
47255
                var result = {},
47256
                    offset = 4;
47257
                result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
47258
 
47259
                result.pid = packet[1] & 0x1f;
47260
                result.pid <<= 8;
47261
                result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
47262
                // fifth byte of the TS packet header. The adaptation field is
47263
                // used to add stuffing to PES packets that don't fill a complete
47264
                // TS packet, and to specify some forms of timing and control data
47265
                // that we do not currently use.
47266
 
47267
                if ((packet[3] & 0x30) >>> 4 > 0x01) {
47268
                    offset += packet[offset] + 1;
47269
                } // parse the rest of the packet based on the type
47270
 
47271
                if (result.pid === 0) {
47272
                    result.type = 'pat';
47273
                    parsePsi(packet.subarray(offset), result);
47274
                    this.trigger('data', result);
47275
                } else if (result.pid === this.pmtPid) {
47276
                    result.type = 'pmt';
47277
                    parsePsi(packet.subarray(offset), result);
47278
                    this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
47279
 
47280
                    while (this.packetsWaitingForPmt.length) {
47281
                        this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
47282
                    }
47283
                } else if (this.programMapTable === undefined) {
47284
                    // When we have not seen a PMT yet, defer further processing of
47285
                    // PES packets until one has been parsed
47286
                    this.packetsWaitingForPmt.push([packet, offset, result]);
47287
                } else {
47288
                    this.processPes_(packet, offset, result);
47289
                }
47290
            };
47291
            this.processPes_ = function (packet, offset, result) {
47292
                // set the appropriate stream type
47293
                if (result.pid === this.programMapTable.video) {
47294
                    result.streamType = StreamTypes$2.H264_STREAM_TYPE;
47295
                } else if (result.pid === this.programMapTable.audio) {
47296
                    result.streamType = StreamTypes$2.ADTS_STREAM_TYPE;
47297
                } else {
47298
                    // if not video or audio, it is timed-metadata or unknown
47299
                    // if unknown, streamType will be undefined
47300
                    result.streamType = this.programMapTable['timed-metadata'][result.pid];
47301
                }
47302
                result.type = 'pes';
47303
                result.data = packet.subarray(offset);
47304
                this.trigger('data', result);
47305
            };
47306
        };
47307
        TransportParseStream.prototype = new Stream$4();
47308
        TransportParseStream.STREAM_TYPES = {
47309
            h264: 0x1b,
47310
            adts: 0x0f
47311
        };
47312
        /**
47313
         * Reconsistutes program elementary stream (PES) packets from parsed
47314
         * transport stream packets. That is, if you pipe an
47315
         * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
47316
         * events will be events which capture the bytes for individual PES
47317
         * packets plus relevant metadata that has been extracted from the
47318
         * container.
47319
         */
47320
 
47321
        ElementaryStream = function () {
47322
            var self = this,
47323
                segmentHadPmt = false,
47324
                // PES packet fragments
47325
                video = {
47326
                    data: [],
47327
                    size: 0
47328
                },
47329
                audio = {
47330
                    data: [],
47331
                    size: 0
47332
                },
47333
                timedMetadata = {
47334
                    data: [],
47335
                    size: 0
47336
                },
47337
                programMapTable,
47338
                parsePes = function (payload, pes) {
47339
                    var ptsDtsFlags;
47340
                    const startPrefix = payload[0] << 16 | payload[1] << 8 | payload[2]; // default to an empty array
47341
 
47342
                    pes.data = new Uint8Array(); // In certain live streams, the start of a TS fragment has ts packets
47343
                    // that are frame data that is continuing from the previous fragment. This
47344
                    // is to check that the pes data is the start of a new pes payload
47345
 
47346
                    if (startPrefix !== 1) {
47347
                        return;
47348
                    } // get the packet length, this will be 0 for video
47349
 
47350
                    pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
47351
 
47352
                    pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
47353
                    // and a DTS value. Determine what combination of values is
47354
                    // available to work with.
47355
 
47356
                    ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
47357
                    // performs all bitwise operations on 32-bit integers but javascript
47358
                    // supports a much greater range (52-bits) of integer using standard
47359
                    // mathematical operations.
47360
                    // We construct a 31-bit value using bitwise operators over the 31
47361
                    // most significant bits and then multiply by 4 (equal to a left-shift
47362
                    // of 2) before we add the final 2 least significant bits of the
47363
                    // timestamp (equal to an OR.)
47364
 
47365
                    if (ptsDtsFlags & 0xC0) {
47366
                        // the PTS and DTS are not written out directly. For information
47367
                        // on how they are encoded, see
47368
                        // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
47369
                        pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
47370
                        pes.pts *= 4; // Left shift by 2
47371
 
47372
                        pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
47373
 
47374
                        pes.dts = pes.pts;
47375
                        if (ptsDtsFlags & 0x40) {
47376
                            pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
47377
                            pes.dts *= 4; // Left shift by 2
47378
 
47379
                            pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
47380
                        }
47381
                    } // the data section starts immediately after the PES header.
47382
                    // pes_header_data_length specifies the number of header bytes
47383
                    // that follow the last byte of the field.
47384
 
47385
                    pes.data = payload.subarray(9 + payload[8]);
47386
                },
47387
                /**
47388
                 * Pass completely parsed PES packets to the next stream in the pipeline
47389
                 **/
47390
                flushStream = function (stream, type, forceFlush) {
47391
                    var packetData = new Uint8Array(stream.size),
47392
                        event = {
47393
                            type: type
47394
                        },
47395
                        i = 0,
47396
                        offset = 0,
47397
                        packetFlushable = false,
47398
                        fragment; // do nothing if there is not enough buffered data for a complete
47399
                    // PES header
47400
 
47401
                    if (!stream.data.length || stream.size < 9) {
47402
                        return;
47403
                    }
47404
                    event.trackId = stream.data[0].pid; // reassemble the packet
47405
 
47406
                    for (i = 0; i < stream.data.length; i++) {
47407
                        fragment = stream.data[i];
47408
                        packetData.set(fragment.data, offset);
47409
                        offset += fragment.data.byteLength;
47410
                    } // parse assembled packet's PES header
47411
 
47412
                    parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
47413
                    // check that there is enough stream data to fill the packet
47414
 
47415
                    packetFlushable = type === 'video' || event.packetLength <= stream.size; // flush pending packets if the conditions are right
47416
 
47417
                    if (forceFlush || packetFlushable) {
47418
                        stream.size = 0;
47419
                        stream.data.length = 0;
47420
                    } // only emit packets that are complete. this is to avoid assembling
47421
                    // incomplete PES packets due to poor segmentation
47422
 
47423
                    if (packetFlushable) {
47424
                        self.trigger('data', event);
47425
                    }
47426
                };
47427
            ElementaryStream.prototype.init.call(this);
47428
            /**
47429
             * Identifies M2TS packet types and parses PES packets using metadata
47430
             * parsed from the PMT
47431
             **/
47432
 
47433
            this.push = function (data) {
47434
                ({
47435
                    pat: function () {// we have to wait for the PMT to arrive as well before we
47436
                        // have any meaningful metadata
47437
                    },
47438
                    pes: function () {
47439
                        var stream, streamType;
47440
                        switch (data.streamType) {
47441
                            case StreamTypes$2.H264_STREAM_TYPE:
47442
                                stream = video;
47443
                                streamType = 'video';
47444
                                break;
47445
                            case StreamTypes$2.ADTS_STREAM_TYPE:
47446
                                stream = audio;
47447
                                streamType = 'audio';
47448
                                break;
47449
                            case StreamTypes$2.METADATA_STREAM_TYPE:
47450
                                stream = timedMetadata;
47451
                                streamType = 'timed-metadata';
47452
                                break;
47453
                            default:
47454
                                // ignore unknown stream types
47455
                                return;
47456
                        } // if a new packet is starting, we can flush the completed
47457
                        // packet
47458
 
47459
                        if (data.payloadUnitStartIndicator) {
47460
                            flushStream(stream, streamType, true);
47461
                        } // buffer this fragment until we are sure we've received the
47462
                        // complete payload
47463
 
47464
                        stream.data.push(data);
47465
                        stream.size += data.data.byteLength;
47466
                    },
47467
                    pmt: function () {
47468
                        var event = {
47469
                            type: 'metadata',
47470
                            tracks: []
47471
                        };
47472
                        programMapTable = data.programMapTable; // translate audio and video streams to tracks
47473
 
47474
                        if (programMapTable.video !== null) {
47475
                            event.tracks.push({
47476
                                timelineStartInfo: {
47477
                                    baseMediaDecodeTime: 0
47478
                                },
47479
                                id: +programMapTable.video,
47480
                                codec: 'avc',
47481
                                type: 'video'
47482
                            });
47483
                        }
47484
                        if (programMapTable.audio !== null) {
47485
                            event.tracks.push({
47486
                                timelineStartInfo: {
47487
                                    baseMediaDecodeTime: 0
47488
                                },
47489
                                id: +programMapTable.audio,
47490
                                codec: 'adts',
47491
                                type: 'audio'
47492
                            });
47493
                        }
47494
                        segmentHadPmt = true;
47495
                        self.trigger('data', event);
47496
                    }
47497
                })[data.type]();
47498
            };
47499
            this.reset = function () {
47500
                video.size = 0;
47501
                video.data.length = 0;
47502
                audio.size = 0;
47503
                audio.data.length = 0;
47504
                this.trigger('reset');
47505
            };
47506
            /**
47507
             * Flush any remaining input. Video PES packets may be of variable
47508
             * length. Normally, the start of a new video packet can trigger the
47509
             * finalization of the previous packet. That is not possible if no
47510
             * more video is forthcoming, however. In that case, some other
47511
             * mechanism (like the end of the file) has to be employed. When it is
47512
             * clear that no additional data is forthcoming, calling this method
47513
             * will flush the buffered packets.
47514
             */
47515
 
47516
            this.flushStreams_ = function () {
47517
                // !!THIS ORDER IS IMPORTANT!!
47518
                // video first then audio
47519
                flushStream(video, 'video');
47520
                flushStream(audio, 'audio');
47521
                flushStream(timedMetadata, 'timed-metadata');
47522
            };
47523
            this.flush = function () {
47524
                // if on flush we haven't had a pmt emitted
47525
                // and we have a pmt to emit. emit the pmt
47526
                // so that we trigger a trackinfo downstream.
47527
                if (!segmentHadPmt && programMapTable) {
47528
                    var pmt = {
47529
                        type: 'metadata',
47530
                        tracks: []
47531
                    }; // translate audio and video streams to tracks
47532
 
47533
                    if (programMapTable.video !== null) {
47534
                        pmt.tracks.push({
47535
                            timelineStartInfo: {
47536
                                baseMediaDecodeTime: 0
47537
                            },
47538
                            id: +programMapTable.video,
47539
                            codec: 'avc',
47540
                            type: 'video'
47541
                        });
47542
                    }
47543
                    if (programMapTable.audio !== null) {
47544
                        pmt.tracks.push({
47545
                            timelineStartInfo: {
47546
                                baseMediaDecodeTime: 0
47547
                            },
47548
                            id: +programMapTable.audio,
47549
                            codec: 'adts',
47550
                            type: 'audio'
47551
                        });
47552
                    }
47553
                    self.trigger('data', pmt);
47554
                }
47555
                segmentHadPmt = false;
47556
                this.flushStreams_();
47557
                this.trigger('done');
47558
            };
47559
        };
47560
        ElementaryStream.prototype = new Stream$4();
47561
        var m2ts$1 = {
47562
            PAT_PID: 0x0000,
47563
            MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH$1,
47564
            TransportPacketStream: TransportPacketStream,
47565
            TransportParseStream: TransportParseStream,
47566
            ElementaryStream: ElementaryStream,
47567
            TimestampRolloverStream: TimestampRolloverStream,
47568
            CaptionStream: CaptionStream$1.CaptionStream,
47569
            Cea608Stream: CaptionStream$1.Cea608Stream,
47570
            Cea708Stream: CaptionStream$1.Cea708Stream,
47571
            MetadataStream: metadataStream
47572
        };
47573
        for (var type in StreamTypes$2) {
47574
            if (StreamTypes$2.hasOwnProperty(type)) {
47575
                m2ts$1[type] = StreamTypes$2[type];
47576
            }
47577
        }
47578
        var m2ts_1 = m2ts$1;
47579
        /**
47580
         * mux.js
47581
         *
47582
         * Copyright (c) Brightcove
47583
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
47584
         */
47585
 
47586
        var Stream$3 = stream;
47587
        var ONE_SECOND_IN_TS$2 = clock$2.ONE_SECOND_IN_TS;
47588
        var AdtsStream$1;
47589
        var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
47590
        /*
47591
     * Accepts a ElementaryStream and emits data events with parsed
47592
     * AAC Audio Frames of the individual packets. Input audio in ADTS
47593
     * format is unpacked and re-emitted as AAC frames.
47594
     *
47595
     * @see http://wiki.multimedia.cx/index.php?title=ADTS
47596
     * @see http://wiki.multimedia.cx/?title=Understanding_AAC
47597
     */
47598
 
47599
        AdtsStream$1 = function (handlePartialSegments) {
47600
            var buffer,
47601
                frameNum = 0;
47602
            AdtsStream$1.prototype.init.call(this);
47603
            this.skipWarn_ = function (start, end) {
47604
                this.trigger('log', {
47605
                    level: 'warn',
47606
                    message: `adts skiping bytes ${start} to ${end} in frame ${frameNum} outside syncword`
47607
                });
47608
            };
47609
            this.push = function (packet) {
47610
                var i = 0,
47611
                    frameLength,
47612
                    protectionSkipBytes,
47613
                    oldBuffer,
47614
                    sampleCount,
47615
                    adtsFrameDuration;
47616
                if (!handlePartialSegments) {
47617
                    frameNum = 0;
47618
                }
47619
                if (packet.type !== 'audio') {
47620
                    // ignore non-audio data
47621
                    return;
47622
                } // Prepend any data in the buffer to the input data so that we can parse
47623
                // aac frames the cross a PES packet boundary
47624
 
47625
                if (buffer && buffer.length) {
47626
                    oldBuffer = buffer;
47627
                    buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
47628
                    buffer.set(oldBuffer);
47629
                    buffer.set(packet.data, oldBuffer.byteLength);
47630
                } else {
47631
                    buffer = packet.data;
47632
                } // unpack any ADTS frames which have been fully received
47633
                // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
47634
 
47635
                var skip; // We use i + 7 here because we want to be able to parse the entire header.
47636
                // If we don't have enough bytes to do that, then we definitely won't have a full frame.
47637
 
47638
                while (i + 7 < buffer.length) {
47639
                    // Look for the start of an ADTS header..
47640
                    if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
47641
                        if (typeof skip !== 'number') {
47642
                            skip = i;
47643
                        } // If a valid header was not found,  jump one forward and attempt to
47644
                        // find a valid ADTS header starting at the next byte
47645
 
47646
                        i++;
47647
                        continue;
47648
                    }
47649
                    if (typeof skip === 'number') {
47650
                        this.skipWarn_(skip, i);
47651
                        skip = null;
47652
                    } // The protection skip bit tells us if we have 2 bytes of CRC data at the
47653
                    // end of the ADTS header
47654
 
47655
                    protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
47656
                    // end of the sync sequence
47657
                    // NOTE: frame length includes the size of the header
47658
 
47659
                    frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
47660
                    sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
47661
                    adtsFrameDuration = sampleCount * ONE_SECOND_IN_TS$2 / ADTS_SAMPLING_FREQUENCIES$1[(buffer[i + 2] & 0x3c) >>> 2]; // If we don't have enough data to actually finish this ADTS frame,
47662
                    // then we have to wait for more data
47663
 
47664
                    if (buffer.byteLength - i < frameLength) {
47665
                        break;
47666
                    } // Otherwise, deliver the complete AAC frame
47667
 
47668
                    this.trigger('data', {
47669
                        pts: packet.pts + frameNum * adtsFrameDuration,
47670
                        dts: packet.dts + frameNum * adtsFrameDuration,
47671
                        sampleCount: sampleCount,
47672
                        audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
47673
                        channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
47674
                        samplerate: ADTS_SAMPLING_FREQUENCIES$1[(buffer[i + 2] & 0x3c) >>> 2],
47675
                        samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
47676
                        // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
47677
                        samplesize: 16,
47678
                        // data is the frame without it's header
47679
                        data: buffer.subarray(i + 7 + protectionSkipBytes, i + frameLength)
47680
                    });
47681
                    frameNum++;
47682
                    i += frameLength;
47683
                }
47684
                if (typeof skip === 'number') {
47685
                    this.skipWarn_(skip, i);
47686
                    skip = null;
47687
                } // remove processed bytes from the buffer.
47688
 
47689
                buffer = buffer.subarray(i);
47690
            };
47691
            this.flush = function () {
47692
                frameNum = 0;
47693
                this.trigger('done');
47694
            };
47695
            this.reset = function () {
47696
                buffer = void 0;
47697
                this.trigger('reset');
47698
            };
47699
            this.endTimeline = function () {
47700
                buffer = void 0;
47701
                this.trigger('endedtimeline');
47702
            };
47703
        };
47704
        AdtsStream$1.prototype = new Stream$3();
47705
        var adts = AdtsStream$1;
47706
        /**
47707
         * mux.js
47708
         *
47709
         * Copyright (c) Brightcove
47710
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
47711
         */
47712
 
47713
        var ExpGolomb$1;
47714
        /**
47715
         * Parser for exponential Golomb codes, a variable-bitwidth number encoding
47716
         * scheme used by h264.
47717
         */
47718
 
47719
        ExpGolomb$1 = function (workingData) {
47720
            var
47721
                // the number of bytes left to examine in workingData
47722
                workingBytesAvailable = workingData.byteLength,
47723
                // the current word being examined
47724
                workingWord = 0,
47725
                // :uint
47726
                // the number of bits left to examine in the current word
47727
                workingBitsAvailable = 0; // :uint;
47728
            // ():uint
47729
 
47730
            this.length = function () {
47731
                return 8 * workingBytesAvailable;
47732
            }; // ():uint
47733
 
47734
            this.bitsAvailable = function () {
47735
                return 8 * workingBytesAvailable + workingBitsAvailable;
47736
            }; // ():void
47737
 
47738
            this.loadWord = function () {
47739
                var position = workingData.byteLength - workingBytesAvailable,
47740
                    workingBytes = new Uint8Array(4),
47741
                    availableBytes = Math.min(4, workingBytesAvailable);
47742
                if (availableBytes === 0) {
47743
                    throw new Error('no bytes available');
47744
                }
47745
                workingBytes.set(workingData.subarray(position, position + availableBytes));
47746
                workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
47747
 
47748
                workingBitsAvailable = availableBytes * 8;
47749
                workingBytesAvailable -= availableBytes;
47750
            }; // (count:int):void
47751
 
47752
            this.skipBits = function (count) {
47753
                var skipBytes; // :int
47754
 
47755
                if (workingBitsAvailable > count) {
47756
                    workingWord <<= count;
47757
                    workingBitsAvailable -= count;
47758
                } else {
47759
                    count -= workingBitsAvailable;
47760
                    skipBytes = Math.floor(count / 8);
47761
                    count -= skipBytes * 8;
47762
                    workingBytesAvailable -= skipBytes;
47763
                    this.loadWord();
47764
                    workingWord <<= count;
47765
                    workingBitsAvailable -= count;
47766
                }
47767
            }; // (size:int):uint
47768
 
47769
            this.readBits = function (size) {
47770
                var bits = Math.min(workingBitsAvailable, size),
47771
                    // :uint
47772
                    valu = workingWord >>> 32 - bits; // :uint
47773
                // if size > 31, handle error
47774
 
47775
                workingBitsAvailable -= bits;
47776
                if (workingBitsAvailable > 0) {
47777
                    workingWord <<= bits;
47778
                } else if (workingBytesAvailable > 0) {
47779
                    this.loadWord();
47780
                }
47781
                bits = size - bits;
47782
                if (bits > 0) {
47783
                    return valu << bits | this.readBits(bits);
47784
                }
47785
                return valu;
47786
            }; // ():uint
47787
 
47788
            this.skipLeadingZeros = function () {
47789
                var leadingZeroCount; // :uint
47790
 
47791
                for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
47792
                    if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
47793
                        // the first bit of working word is 1
47794
                        workingWord <<= leadingZeroCount;
47795
                        workingBitsAvailable -= leadingZeroCount;
47796
                        return leadingZeroCount;
47797
                    }
47798
                } // we exhausted workingWord and still have not found a 1
47799
 
47800
                this.loadWord();
47801
                return leadingZeroCount + this.skipLeadingZeros();
47802
            }; // ():void
47803
 
47804
            this.skipUnsignedExpGolomb = function () {
47805
                this.skipBits(1 + this.skipLeadingZeros());
47806
            }; // ():void
47807
 
47808
            this.skipExpGolomb = function () {
47809
                this.skipBits(1 + this.skipLeadingZeros());
47810
            }; // ():uint
47811
 
47812
            this.readUnsignedExpGolomb = function () {
47813
                var clz = this.skipLeadingZeros(); // :uint
47814
 
47815
                return this.readBits(clz + 1) - 1;
47816
            }; // ():int
47817
 
47818
            this.readExpGolomb = function () {
47819
                var valu = this.readUnsignedExpGolomb(); // :int
47820
 
47821
                if (0x01 & valu) {
47822
                    // the number is odd if the low order bit is set
47823
                    return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
47824
                }
47825
 
47826
                return -1 * (valu >>> 1); // divide by two then make it negative
47827
            }; // Some convenience functions
47828
            // :Boolean
47829
 
47830
            this.readBoolean = function () {
47831
                return this.readBits(1) === 1;
47832
            }; // ():int
47833
 
47834
            this.readUnsignedByte = function () {
47835
                return this.readBits(8);
47836
            };
47837
            this.loadWord();
47838
        };
47839
        var expGolomb = ExpGolomb$1;
47840
        /**
47841
         * mux.js
47842
         *
47843
         * Copyright (c) Brightcove
47844
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
47845
         */
47846
 
47847
        var Stream$2 = stream;
47848
        var ExpGolomb = expGolomb;
47849
        var H264Stream$1, NalByteStream;
47850
        var PROFILES_WITH_OPTIONAL_SPS_DATA;
47851
        /**
47852
         * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
47853
         */
47854
 
47855
        NalByteStream = function () {
47856
            var syncPoint = 0,
47857
                i,
47858
                buffer;
47859
            NalByteStream.prototype.init.call(this);
47860
            /*
47861
       * Scans a byte stream and triggers a data event with the NAL units found.
47862
       * @param {Object} data Event received from H264Stream
47863
       * @param {Uint8Array} data.data The h264 byte stream to be scanned
47864
       *
47865
       * @see H264Stream.push
47866
       */
47867
 
47868
            this.push = function (data) {
47869
                var swapBuffer;
47870
                if (!buffer) {
47871
                    buffer = data.data;
47872
                } else {
47873
                    swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
47874
                    swapBuffer.set(buffer);
47875
                    swapBuffer.set(data.data, buffer.byteLength);
47876
                    buffer = swapBuffer;
47877
                }
47878
                var len = buffer.byteLength; // Rec. ITU-T H.264, Annex B
47879
                // scan for NAL unit boundaries
47880
                // a match looks like this:
47881
                // 0 0 1 .. NAL .. 0 0 1
47882
                // ^ sync point        ^ i
47883
                // or this:
47884
                // 0 0 1 .. NAL .. 0 0 0
47885
                // ^ sync point        ^ i
47886
                // advance the sync point to a NAL start, if necessary
47887
 
47888
                for (; syncPoint < len - 3; syncPoint++) {
47889
                    if (buffer[syncPoint + 2] === 1) {
47890
                        // the sync point is properly aligned
47891
                        i = syncPoint + 5;
47892
                        break;
47893
                    }
47894
                }
47895
                while (i < len) {
47896
                    // look at the current byte to determine if we've hit the end of
47897
                    // a NAL unit boundary
47898
                    switch (buffer[i]) {
47899
                        case 0:
47900
                            // skip past non-sync sequences
47901
                            if (buffer[i - 1] !== 0) {
47902
                                i += 2;
47903
                                break;
47904
                            } else if (buffer[i - 2] !== 0) {
47905
                                i++;
47906
                                break;
47907
                            } // deliver the NAL unit if it isn't empty
47908
 
47909
                            if (syncPoint + 3 !== i - 2) {
47910
                                this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
47911
                            } // drop trailing zeroes
47912
 
47913
                            do {
47914
                                i++;
47915
                            } while (buffer[i] !== 1 && i < len);
47916
                            syncPoint = i - 2;
47917
                            i += 3;
47918
                            break;
47919
                        case 1:
47920
                            // skip past non-sync sequences
47921
                            if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
47922
                                i += 3;
47923
                                break;
47924
                            } // deliver the NAL unit
47925
 
47926
                            this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
47927
                            syncPoint = i - 2;
47928
                            i += 3;
47929
                            break;
47930
                        default:
47931
                            // the current byte isn't a one or zero, so it cannot be part
47932
                            // of a sync sequence
47933
                            i += 3;
47934
                            break;
47935
                    }
47936
                } // filter out the NAL units that were delivered
47937
 
47938
                buffer = buffer.subarray(syncPoint);
47939
                i -= syncPoint;
47940
                syncPoint = 0;
47941
            };
47942
            this.reset = function () {
47943
                buffer = null;
47944
                syncPoint = 0;
47945
                this.trigger('reset');
47946
            };
47947
            this.flush = function () {
47948
                // deliver the last buffered NAL unit
47949
                if (buffer && buffer.byteLength > 3) {
47950
                    this.trigger('data', buffer.subarray(syncPoint + 3));
47951
                } // reset the stream state
47952
 
47953
                buffer = null;
47954
                syncPoint = 0;
47955
                this.trigger('done');
47956
            };
47957
            this.endTimeline = function () {
47958
                this.flush();
47959
                this.trigger('endedtimeline');
47960
            };
47961
        };
47962
        NalByteStream.prototype = new Stream$2(); // values of profile_idc that indicate additional fields are included in the SPS
47963
        // see Recommendation ITU-T H.264 (4/2013),
47964
        // 7.3.2.1.1 Sequence parameter set data syntax
47965
 
47966
        PROFILES_WITH_OPTIONAL_SPS_DATA = {
47967
            100: true,
47968
            110: true,
47969
            122: true,
47970
            244: true,
47971
            44: true,
47972
            83: true,
47973
            86: true,
47974
            118: true,
47975
            128: true,
47976
            // TODO: the three profiles below don't
47977
            // appear to have sps data in the specificiation anymore?
47978
            138: true,
47979
            139: true,
47980
            134: true
47981
        };
47982
        /**
47983
         * Accepts input from a ElementaryStream and produces H.264 NAL unit data
47984
         * events.
47985
         */
47986
 
47987
        H264Stream$1 = function () {
47988
            var nalByteStream = new NalByteStream(),
47989
                self,
47990
                trackId,
47991
                currentPts,
47992
                currentDts,
47993
                discardEmulationPreventionBytes,
47994
                readSequenceParameterSet,
47995
                skipScalingList;
47996
            H264Stream$1.prototype.init.call(this);
47997
            self = this;
47998
            /*
47999
       * Pushes a packet from a stream onto the NalByteStream
48000
       *
48001
       * @param {Object} packet - A packet received from a stream
48002
       * @param {Uint8Array} packet.data - The raw bytes of the packet
48003
       * @param {Number} packet.dts - Decode timestamp of the packet
48004
       * @param {Number} packet.pts - Presentation timestamp of the packet
48005
       * @param {Number} packet.trackId - The id of the h264 track this packet came from
48006
       * @param {('video'|'audio')} packet.type - The type of packet
48007
       *
48008
       */
48009
 
48010
            this.push = function (packet) {
48011
                if (packet.type !== 'video') {
48012
                    return;
48013
                }
48014
                trackId = packet.trackId;
48015
                currentPts = packet.pts;
48016
                currentDts = packet.dts;
48017
                nalByteStream.push(packet);
48018
            };
48019
            /*
48020
       * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
48021
       * for the NALUs to the next stream component.
48022
       * Also, preprocess caption and sequence parameter NALUs.
48023
       *
48024
       * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
48025
       * @see NalByteStream.push
48026
       */
48027
 
48028
            nalByteStream.on('data', function (data) {
48029
                var event = {
48030
                    trackId: trackId,
48031
                    pts: currentPts,
48032
                    dts: currentDts,
48033
                    data: data,
48034
                    nalUnitTypeCode: data[0] & 0x1f
48035
                };
48036
                switch (event.nalUnitTypeCode) {
48037
                    case 0x05:
48038
                        event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
48039
                        break;
48040
                    case 0x06:
48041
                        event.nalUnitType = 'sei_rbsp';
48042
                        event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
48043
                        break;
48044
                    case 0x07:
48045
                        event.nalUnitType = 'seq_parameter_set_rbsp';
48046
                        event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
48047
                        event.config = readSequenceParameterSet(event.escapedRBSP);
48048
                        break;
48049
                    case 0x08:
48050
                        event.nalUnitType = 'pic_parameter_set_rbsp';
48051
                        break;
48052
                    case 0x09:
48053
                        event.nalUnitType = 'access_unit_delimiter_rbsp';
48054
                        break;
48055
                } // This triggers data on the H264Stream
48056
 
48057
                self.trigger('data', event);
48058
            });
48059
            nalByteStream.on('done', function () {
48060
                self.trigger('done');
48061
            });
48062
            nalByteStream.on('partialdone', function () {
48063
                self.trigger('partialdone');
48064
            });
48065
            nalByteStream.on('reset', function () {
48066
                self.trigger('reset');
48067
            });
48068
            nalByteStream.on('endedtimeline', function () {
48069
                self.trigger('endedtimeline');
48070
            });
48071
            this.flush = function () {
48072
                nalByteStream.flush();
48073
            };
48074
            this.partialFlush = function () {
48075
                nalByteStream.partialFlush();
48076
            };
48077
            this.reset = function () {
48078
                nalByteStream.reset();
48079
            };
48080
            this.endTimeline = function () {
48081
                nalByteStream.endTimeline();
48082
            };
48083
            /**
48084
             * Advance the ExpGolomb decoder past a scaling list. The scaling
48085
             * list is optionally transmitted as part of a sequence parameter
48086
             * set and is not relevant to transmuxing.
48087
             * @param count {number} the number of entries in this scaling list
48088
             * @param expGolombDecoder {object} an ExpGolomb pointed to the
48089
             * start of a scaling list
48090
             * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
48091
             */
48092
 
48093
            skipScalingList = function (count, expGolombDecoder) {
48094
                var lastScale = 8,
48095
                    nextScale = 8,
48096
                    j,
48097
                    deltaScale;
48098
                for (j = 0; j < count; j++) {
48099
                    if (nextScale !== 0) {
48100
                        deltaScale = expGolombDecoder.readExpGolomb();
48101
                        nextScale = (lastScale + deltaScale + 256) % 256;
48102
                    }
48103
                    lastScale = nextScale === 0 ? lastScale : nextScale;
48104
                }
48105
            };
48106
            /**
48107
             * Expunge any "Emulation Prevention" bytes from a "Raw Byte
48108
             * Sequence Payload"
48109
             * @param data {Uint8Array} the bytes of a RBSP from a NAL
48110
             * unit
48111
             * @return {Uint8Array} the RBSP without any Emulation
48112
             * Prevention Bytes
48113
             */
48114
 
48115
            discardEmulationPreventionBytes = function (data) {
48116
                var length = data.byteLength,
48117
                    emulationPreventionBytesPositions = [],
48118
                    i = 1,
48119
                    newLength,
48120
                    newData; // Find all `Emulation Prevention Bytes`
48121
 
48122
                while (i < length - 2) {
48123
                    if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
48124
                        emulationPreventionBytesPositions.push(i + 2);
48125
                        i += 2;
48126
                    } else {
48127
                        i++;
48128
                    }
48129
                } // If no Emulation Prevention Bytes were found just return the original
48130
                // array
48131
 
48132
                if (emulationPreventionBytesPositions.length === 0) {
48133
                    return data;
48134
                } // Create a new array to hold the NAL unit data
48135
 
48136
                newLength = length - emulationPreventionBytesPositions.length;
48137
                newData = new Uint8Array(newLength);
48138
                var sourceIndex = 0;
48139
                for (i = 0; i < newLength; sourceIndex++, i++) {
48140
                    if (sourceIndex === emulationPreventionBytesPositions[0]) {
48141
                        // Skip this byte
48142
                        sourceIndex++; // Remove this position index
48143
 
48144
                        emulationPreventionBytesPositions.shift();
48145
                    }
48146
                    newData[i] = data[sourceIndex];
48147
                }
48148
                return newData;
48149
            };
48150
            /**
48151
             * Read a sequence parameter set and return some interesting video
48152
             * properties. A sequence parameter set is the H264 metadata that
48153
             * describes the properties of upcoming video frames.
48154
             * @param data {Uint8Array} the bytes of a sequence parameter set
48155
             * @return {object} an object with configuration parsed from the
48156
             * sequence parameter set, including the dimensions of the
48157
             * associated video frames.
48158
             */
48159
 
48160
            readSequenceParameterSet = function (data) {
48161
                var frameCropLeftOffset = 0,
48162
                    frameCropRightOffset = 0,
48163
                    frameCropTopOffset = 0,
48164
                    frameCropBottomOffset = 0,
48165
                    expGolombDecoder,
48166
                    profileIdc,
48167
                    levelIdc,
48168
                    profileCompatibility,
48169
                    chromaFormatIdc,
48170
                    picOrderCntType,
48171
                    numRefFramesInPicOrderCntCycle,
48172
                    picWidthInMbsMinus1,
48173
                    picHeightInMapUnitsMinus1,
48174
                    frameMbsOnlyFlag,
48175
                    scalingListCount,
48176
                    sarRatio = [1, 1],
48177
                    aspectRatioIdc,
48178
                    i;
48179
                expGolombDecoder = new ExpGolomb(data);
48180
                profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
48181
 
48182
                profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
48183
 
48184
                levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
48185
 
48186
                expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
48187
                // some profiles have more optional data we don't need
48188
 
48189
                if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
48190
                    chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
48191
                    if (chromaFormatIdc === 3) {
48192
                        expGolombDecoder.skipBits(1); // separate_colour_plane_flag
48193
                    }
48194
 
48195
                    expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
48196
 
48197
                    expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
48198
 
48199
                    expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
48200
 
48201
                    if (expGolombDecoder.readBoolean()) {
48202
                        // seq_scaling_matrix_present_flag
48203
                        scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
48204
                        for (i = 0; i < scalingListCount; i++) {
48205
                            if (expGolombDecoder.readBoolean()) {
48206
                                // seq_scaling_list_present_flag[ i ]
48207
                                if (i < 6) {
48208
                                    skipScalingList(16, expGolombDecoder);
48209
                                } else {
48210
                                    skipScalingList(64, expGolombDecoder);
48211
                                }
48212
                            }
48213
                        }
48214
                    }
48215
                }
48216
                expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
48217
 
48218
                picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
48219
                if (picOrderCntType === 0) {
48220
                    expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
48221
                } else if (picOrderCntType === 1) {
48222
                    expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
48223
 
48224
                    expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
48225
 
48226
                    expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
48227
 
48228
                    numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
48229
                    for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
48230
                        expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
48231
                    }
48232
                }
48233
 
48234
                expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
48235
 
48236
                expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
48237
 
48238
                picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
48239
                picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
48240
                frameMbsOnlyFlag = expGolombDecoder.readBits(1);
48241
                if (frameMbsOnlyFlag === 0) {
48242
                    expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
48243
                }
48244
 
48245
                expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
48246
 
48247
                if (expGolombDecoder.readBoolean()) {
48248
                    // frame_cropping_flag
48249
                    frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
48250
                    frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
48251
                    frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
48252
                    frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
48253
                }
48254
                if (expGolombDecoder.readBoolean()) {
48255
                    // vui_parameters_present_flag
48256
                    if (expGolombDecoder.readBoolean()) {
48257
                        // aspect_ratio_info_present_flag
48258
                        aspectRatioIdc = expGolombDecoder.readUnsignedByte();
48259
                        switch (aspectRatioIdc) {
48260
                            case 1:
48261
                                sarRatio = [1, 1];
48262
                                break;
48263
                            case 2:
48264
                                sarRatio = [12, 11];
48265
                                break;
48266
                            case 3:
48267
                                sarRatio = [10, 11];
48268
                                break;
48269
                            case 4:
48270
                                sarRatio = [16, 11];
48271
                                break;
48272
                            case 5:
48273
                                sarRatio = [40, 33];
48274
                                break;
48275
                            case 6:
48276
                                sarRatio = [24, 11];
48277
                                break;
48278
                            case 7:
48279
                                sarRatio = [20, 11];
48280
                                break;
48281
                            case 8:
48282
                                sarRatio = [32, 11];
48283
                                break;
48284
                            case 9:
48285
                                sarRatio = [80, 33];
48286
                                break;
48287
                            case 10:
48288
                                sarRatio = [18, 11];
48289
                                break;
48290
                            case 11:
48291
                                sarRatio = [15, 11];
48292
                                break;
48293
                            case 12:
48294
                                sarRatio = [64, 33];
48295
                                break;
48296
                            case 13:
48297
                                sarRatio = [160, 99];
48298
                                break;
48299
                            case 14:
48300
                                sarRatio = [4, 3];
48301
                                break;
48302
                            case 15:
48303
                                sarRatio = [3, 2];
48304
                                break;
48305
                            case 16:
48306
                                sarRatio = [2, 1];
48307
                                break;
48308
                            case 255:
48309
                            {
48310
                                sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
48311
                                break;
48312
                            }
48313
                        }
48314
                        if (sarRatio) {
48315
                            sarRatio[0] / sarRatio[1];
48316
                        }
48317
                    }
48318
                }
48319
                return {
48320
                    profileIdc: profileIdc,
48321
                    levelIdc: levelIdc,
48322
                    profileCompatibility: profileCompatibility,
48323
                    width: (picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2,
48324
                    height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2,
48325
                    // sar is sample aspect ratio
48326
                    sarRatio: sarRatio
48327
                };
48328
            };
48329
        };
48330
        H264Stream$1.prototype = new Stream$2();
48331
        var h264 = {
48332
            H264Stream: H264Stream$1,
48333
            NalByteStream: NalByteStream
48334
        };
48335
        /**
48336
         * mux.js
48337
         *
48338
         * Copyright (c) Brightcove
48339
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
48340
         *
48341
         * Utilities to detect basic properties and metadata about Aac data.
48342
         */
48343
 
48344
        var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
48345
        var parseId3TagSize = function (header, byteIndex) {
48346
            var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
48347
                flags = header[byteIndex + 5],
48348
                footerPresent = (flags & 16) >> 4; // if we get a negative returnSize clamp it to 0
48349
 
48350
            returnSize = returnSize >= 0 ? returnSize : 0;
48351
            if (footerPresent) {
48352
                return returnSize + 20;
48353
            }
48354
            return returnSize + 10;
48355
        };
48356
        var getId3Offset = function (data, offset) {
48357
            if (data.length - offset < 10 || data[offset] !== 'I'.charCodeAt(0) || data[offset + 1] !== 'D'.charCodeAt(0) || data[offset + 2] !== '3'.charCodeAt(0)) {
48358
                return offset;
48359
            }
48360
            offset += parseId3TagSize(data, offset);
48361
            return getId3Offset(data, offset);
48362
        }; // TODO: use vhs-utils
48363
 
48364
        var isLikelyAacData$1 = function (data) {
48365
            var offset = getId3Offset(data, 0);
48366
            return data.length >= offset + 2 && (data[offset] & 0xFF) === 0xFF && (data[offset + 1] & 0xF0) === 0xF0 &&
48367
                // verify that the 2 layer bits are 0, aka this
48368
                // is not mp3 data but aac data.
48369
                (data[offset + 1] & 0x16) === 0x10;
48370
        };
48371
        var parseSyncSafeInteger = function (data) {
48372
            return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
48373
        }; // return a percent-encoded representation of the specified byte range
48374
        // @see http://en.wikipedia.org/wiki/Percent-encoding
48375
 
48376
        var percentEncode = function (bytes, start, end) {
48377
            var i,
48378
                result = '';
48379
            for (i = start; i < end; i++) {
48380
                result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
48381
            }
48382
            return result;
48383
        }; // return the string representation of the specified byte range,
48384
        // interpreted as ISO-8859-1.
48385
 
48386
        var parseIso88591 = function (bytes, start, end) {
48387
            return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
48388
        };
48389
 
48390
        var parseAdtsSize = function (header, byteIndex) {
48391
            var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
48392
                middle = header[byteIndex + 4] << 3,
48393
                highTwo = header[byteIndex + 3] & 0x3 << 11;
48394
            return highTwo | middle | lowThree;
48395
        };
48396
        var parseType$4 = function (header, byteIndex) {
48397
            if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
48398
                return 'timed-metadata';
48399
            } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
48400
                return 'audio';
48401
            }
48402
            return null;
48403
        };
48404
        var parseSampleRate = function (packet) {
48405
            var i = 0;
48406
            while (i + 5 < packet.length) {
48407
                if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
48408
                    // If a valid header was not found,  jump one forward and attempt to
48409
                    // find a valid ADTS header starting at the next byte
48410
                    i++;
48411
                    continue;
48412
                }
48413
                return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2];
48414
            }
48415
            return null;
48416
        };
48417
        var parseAacTimestamp = function (packet) {
48418
            var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
48419
 
48420
            frameStart = 10;
48421
            if (packet[5] & 0x40) {
48422
                // advance the frame start past the extended header
48423
                frameStart += 4; // header size field
48424
 
48425
                frameStart += parseSyncSafeInteger(packet.subarray(10, 14));
48426
            } // parse one or more ID3 frames
48427
            // http://id3.org/id3v2.3.0#ID3v2_frame_overview
48428
 
48429
            do {
48430
                // determine the number of bytes in this frame
48431
                frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8));
48432
                if (frameSize < 1) {
48433
                    return null;
48434
                }
48435
                frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
48436
                if (frameHeader === 'PRIV') {
48437
                    frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
48438
                    for (var i = 0; i < frame.byteLength; i++) {
48439
                        if (frame[i] === 0) {
48440
                            var owner = parseIso88591(frame, 0, i);
48441
                            if (owner === 'com.apple.streaming.transportStreamTimestamp') {
48442
                                var d = frame.subarray(i + 1);
48443
                                var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
48444
                                size *= 4;
48445
                                size += d[7] & 0x03;
48446
                                return size;
48447
                            }
48448
                            break;
48449
                        }
48450
                    }
48451
                }
48452
                frameStart += 10; // advance past the frame header
48453
 
48454
                frameStart += frameSize; // advance past the frame body
48455
            } while (frameStart < packet.byteLength);
48456
            return null;
48457
        };
48458
        var utils = {
48459
            isLikelyAacData: isLikelyAacData$1,
48460
            parseId3TagSize: parseId3TagSize,
48461
            parseAdtsSize: parseAdtsSize,
48462
            parseType: parseType$4,
48463
            parseSampleRate: parseSampleRate,
48464
            parseAacTimestamp: parseAacTimestamp
48465
        };
48466
        /**
48467
         * mux.js
48468
         *
48469
         * Copyright (c) Brightcove
48470
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
48471
         *
48472
         * A stream-based aac to mp4 converter. This utility can be used to
48473
         * deliver mp4s to a SourceBuffer on platforms that support native
48474
         * Media Source Extensions.
48475
         */
48476
 
48477
        var Stream$1 = stream;
48478
        var aacUtils = utils; // Constants
48479
 
48480
        var AacStream$1;
48481
        /**
48482
         * Splits an incoming stream of binary data into ADTS and ID3 Frames.
48483
         */
48484
 
48485
        AacStream$1 = function () {
48486
            var everything = new Uint8Array(),
48487
                timeStamp = 0;
48488
            AacStream$1.prototype.init.call(this);
48489
            this.setTimestamp = function (timestamp) {
48490
                timeStamp = timestamp;
48491
            };
48492
            this.push = function (bytes) {
48493
                var frameSize = 0,
48494
                    byteIndex = 0,
48495
                    bytesLeft,
48496
                    chunk,
48497
                    packet,
48498
                    tempLength; // If there are bytes remaining from the last segment, prepend them to the
48499
                // bytes that were pushed in
48500
 
48501
                if (everything.length) {
48502
                    tempLength = everything.length;
48503
                    everything = new Uint8Array(bytes.byteLength + tempLength);
48504
                    everything.set(everything.subarray(0, tempLength));
48505
                    everything.set(bytes, tempLength);
48506
                } else {
48507
                    everything = bytes;
48508
                }
48509
                while (everything.length - byteIndex >= 3) {
48510
                    if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
48511
                        // Exit early because we don't have enough to parse
48512
                        // the ID3 tag header
48513
                        if (everything.length - byteIndex < 10) {
48514
                            break;
48515
                        } // check framesize
48516
 
48517
                        frameSize = aacUtils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
48518
                        // to emit a full packet
48519
                        // Add to byteIndex to support multiple ID3 tags in sequence
48520
 
48521
                        if (byteIndex + frameSize > everything.length) {
48522
                            break;
48523
                        }
48524
                        chunk = {
48525
                            type: 'timed-metadata',
48526
                            data: everything.subarray(byteIndex, byteIndex + frameSize)
48527
                        };
48528
                        this.trigger('data', chunk);
48529
                        byteIndex += frameSize;
48530
                        continue;
48531
                    } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
48532
                        // Exit early because we don't have enough to parse
48533
                        // the ADTS frame header
48534
                        if (everything.length - byteIndex < 7) {
48535
                            break;
48536
                        }
48537
                        frameSize = aacUtils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
48538
                        // to emit a full packet
48539
 
48540
                        if (byteIndex + frameSize > everything.length) {
48541
                            break;
48542
                        }
48543
                        packet = {
48544
                            type: 'audio',
48545
                            data: everything.subarray(byteIndex, byteIndex + frameSize),
48546
                            pts: timeStamp,
48547
                            dts: timeStamp
48548
                        };
48549
                        this.trigger('data', packet);
48550
                        byteIndex += frameSize;
48551
                        continue;
48552
                    }
48553
                    byteIndex++;
48554
                }
48555
                bytesLeft = everything.length - byteIndex;
48556
                if (bytesLeft > 0) {
48557
                    everything = everything.subarray(byteIndex);
48558
                } else {
48559
                    everything = new Uint8Array();
48560
                }
48561
            };
48562
            this.reset = function () {
48563
                everything = new Uint8Array();
48564
                this.trigger('reset');
48565
            };
48566
            this.endTimeline = function () {
48567
                everything = new Uint8Array();
48568
                this.trigger('endedtimeline');
48569
            };
48570
        };
48571
        AacStream$1.prototype = new Stream$1();
48572
        var aac = AacStream$1;
48573
        var AUDIO_PROPERTIES$1 = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
48574
        var audioProperties = AUDIO_PROPERTIES$1;
48575
        var VIDEO_PROPERTIES$1 = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility', 'sarRatio'];
48576
        var videoProperties = VIDEO_PROPERTIES$1;
48577
        /**
48578
         * mux.js
48579
         *
48580
         * Copyright (c) Brightcove
48581
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
48582
         *
48583
         * A stream-based mp2t to mp4 converter. This utility can be used to
48584
         * deliver mp4s to a SourceBuffer on platforms that support native
48585
         * Media Source Extensions.
48586
         */
48587
 
48588
        var Stream = stream;
48589
        var mp4 = mp4Generator;
48590
        var frameUtils = frameUtils$1;
48591
        var audioFrameUtils = audioFrameUtils$1;
48592
        var trackDecodeInfo = trackDecodeInfo$1;
48593
        var m2ts = m2ts_1;
48594
        var clock = clock$2;
48595
        var AdtsStream = adts;
48596
        var H264Stream = h264.H264Stream;
48597
        var AacStream = aac;
48598
        var isLikelyAacData = utils.isLikelyAacData;
48599
        var ONE_SECOND_IN_TS$1 = clock$2.ONE_SECOND_IN_TS;
48600
        var AUDIO_PROPERTIES = audioProperties;
48601
        var VIDEO_PROPERTIES = videoProperties; // object types
48602
 
48603
        var VideoSegmentStream, AudioSegmentStream, Transmuxer, CoalesceStream;
48604
        var retriggerForStream = function (key, event) {
48605
            event.stream = key;
48606
            this.trigger('log', event);
48607
        };
48608
        var addPipelineLogRetriggers = function (transmuxer, pipeline) {
48609
            var keys = Object.keys(pipeline);
48610
            for (var i = 0; i < keys.length; i++) {
48611
                var key = keys[i]; // skip non-stream keys and headOfPipeline
48612
                // which is just a duplicate
48613
 
48614
                if (key === 'headOfPipeline' || !pipeline[key].on) {
48615
                    continue;
48616
                }
48617
                pipeline[key].on('log', retriggerForStream.bind(transmuxer, key));
48618
            }
48619
        };
48620
        /**
48621
         * Compare two arrays (even typed) for same-ness
48622
         */
48623
 
48624
        var arrayEquals = function (a, b) {
48625
            var i;
48626
            if (a.length !== b.length) {
48627
                return false;
48628
            } // compare the value of each element in the array
48629
 
48630
            for (i = 0; i < a.length; i++) {
48631
                if (a[i] !== b[i]) {
48632
                    return false;
48633
                }
48634
            }
48635
            return true;
48636
        };
48637
        var generateSegmentTimingInfo = function (baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
48638
            var ptsOffsetFromDts = startPts - startDts,
48639
                decodeDuration = endDts - startDts,
48640
                presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
48641
            // however, the player time values will reflect a start from the baseMediaDecodeTime.
48642
            // In order to provide relevant values for the player times, base timing info on the
48643
            // baseMediaDecodeTime and the DTS and PTS durations of the segment.
48644
 
48645
            return {
48646
                start: {
48647
                    dts: baseMediaDecodeTime,
48648
                    pts: baseMediaDecodeTime + ptsOffsetFromDts
48649
                },
48650
                end: {
48651
                    dts: baseMediaDecodeTime + decodeDuration,
48652
                    pts: baseMediaDecodeTime + presentationDuration
48653
                },
48654
                prependedContentDuration: prependedContentDuration,
48655
                baseMediaDecodeTime: baseMediaDecodeTime
48656
            };
48657
        };
48658
        /**
48659
         * Constructs a single-track, ISO BMFF media segment from AAC data
48660
         * events. The output of this stream can be fed to a SourceBuffer
48661
         * configured with a suitable initialization segment.
48662
         * @param track {object} track metadata configuration
48663
         * @param options {object} transmuxer options object
48664
         * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
48665
         *        in the source; false to adjust the first segment to start at 0.
48666
         */
48667
 
48668
        AudioSegmentStream = function (track, options) {
48669
            var adtsFrames = [],
48670
                sequenceNumber,
48671
                earliestAllowedDts = 0,
48672
                audioAppendStartTs = 0,
48673
                videoBaseMediaDecodeTime = Infinity;
48674
            options = options || {};
48675
            sequenceNumber = options.firstSequenceNumber || 0;
48676
            AudioSegmentStream.prototype.init.call(this);
48677
            this.push = function (data) {
48678
                trackDecodeInfo.collectDtsInfo(track, data);
48679
                if (track) {
48680
                    AUDIO_PROPERTIES.forEach(function (prop) {
48681
                        track[prop] = data[prop];
48682
                    });
48683
                } // buffer audio data until end() is called
48684
 
48685
                adtsFrames.push(data);
48686
            };
48687
            this.setEarliestDts = function (earliestDts) {
48688
                earliestAllowedDts = earliestDts;
48689
            };
48690
            this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
48691
                videoBaseMediaDecodeTime = baseMediaDecodeTime;
48692
            };
48693
            this.setAudioAppendStart = function (timestamp) {
48694
                audioAppendStartTs = timestamp;
48695
            };
48696
            this.flush = function () {
48697
                var frames, moof, mdat, boxes, frameDuration, segmentDuration, videoClockCyclesOfSilencePrefixed; // return early if no audio data has been observed
48698
 
48699
                if (adtsFrames.length === 0) {
48700
                    this.trigger('done', 'AudioSegmentStream');
48701
                    return;
48702
                }
48703
                frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
48704
                track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); // amount of audio filled but the value is in video clock rather than audio clock
48705
 
48706
                videoClockCyclesOfSilencePrefixed = audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
48707
                // samples (that is, adts frames) in the audio data
48708
 
48709
                track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
48710
 
48711
                mdat = mp4.mdat(audioFrameUtils.concatenateFrameData(frames));
48712
                adtsFrames = [];
48713
                moof = mp4.moof(sequenceNumber, [track]);
48714
                boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
48715
 
48716
                sequenceNumber++;
48717
                boxes.set(moof);
48718
                boxes.set(mdat, moof.byteLength);
48719
                trackDecodeInfo.clearDtsInfo(track);
48720
                frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 * 1024 / track.samplerate); // TODO this check was added to maintain backwards compatibility (particularly with
48721
                // tests) on adding the timingInfo event. However, it seems unlikely that there's a
48722
                // valid use-case where an init segment/data should be triggered without associated
48723
                // frames. Leaving for now, but should be looked into.
48724
 
48725
                if (frames.length) {
48726
                    segmentDuration = frames.length * frameDuration;
48727
                    this.trigger('segmentTimingInfo', generateSegmentTimingInfo(
48728
                        // The audio track's baseMediaDecodeTime is in audio clock cycles, but the
48729
                        // frame info is in video clock cycles. Convert to match expectation of
48730
                        // listeners (that all timestamps will be based on video clock cycles).
48731
                        clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate),
48732
                        // frame times are already in video clock, as is segment duration
48733
                        frames[0].dts, frames[0].pts, frames[0].dts + segmentDuration, frames[0].pts + segmentDuration, videoClockCyclesOfSilencePrefixed || 0));
48734
                    this.trigger('timingInfo', {
48735
                        start: frames[0].pts,
48736
                        end: frames[0].pts + segmentDuration
48737
                    });
48738
                }
48739
                this.trigger('data', {
48740
                    track: track,
48741
                    boxes: boxes
48742
                });
48743
                this.trigger('done', 'AudioSegmentStream');
48744
            };
48745
            this.reset = function () {
48746
                trackDecodeInfo.clearDtsInfo(track);
48747
                adtsFrames = [];
48748
                this.trigger('reset');
48749
            };
48750
        };
48751
        AudioSegmentStream.prototype = new Stream();
48752
        /**
48753
         * Constructs a single-track, ISO BMFF media segment from H264 data
48754
         * events. The output of this stream can be fed to a SourceBuffer
48755
         * configured with a suitable initialization segment.
48756
         * @param track {object} track metadata configuration
48757
         * @param options {object} transmuxer options object
48758
         * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
48759
         *        gopsToAlignWith list when attempting to align gop pts
48760
         * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
48761
         *        in the source; false to adjust the first segment to start at 0.
48762
         */
48763
 
48764
        VideoSegmentStream = function (track, options) {
48765
            var sequenceNumber,
48766
                nalUnits = [],
48767
                gopsToAlignWith = [],
48768
                config,
48769
                pps;
48770
            options = options || {};
48771
            sequenceNumber = options.firstSequenceNumber || 0;
48772
            VideoSegmentStream.prototype.init.call(this);
48773
            delete track.minPTS;
48774
            this.gopCache_ = [];
48775
            /**
48776
             * Constructs a ISO BMFF segment given H264 nalUnits
48777
             * @param {Object} nalUnit A data event representing a nalUnit
48778
             * @param {String} nalUnit.nalUnitType
48779
             * @param {Object} nalUnit.config Properties for a mp4 track
48780
             * @param {Uint8Array} nalUnit.data The nalUnit bytes
48781
             * @see lib/codecs/h264.js
48782
             **/
48783
 
48784
            this.push = function (nalUnit) {
48785
                trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
48786
 
48787
                if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
48788
                    config = nalUnit.config;
48789
                    track.sps = [nalUnit.data];
48790
                    VIDEO_PROPERTIES.forEach(function (prop) {
48791
                        track[prop] = config[prop];
48792
                    }, this);
48793
                }
48794
                if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
48795
                    pps = nalUnit.data;
48796
                    track.pps = [nalUnit.data];
48797
                } // buffer video until flush() is called
48798
 
48799
                nalUnits.push(nalUnit);
48800
            };
48801
            /**
48802
             * Pass constructed ISO BMFF track and boxes on to the
48803
             * next stream in the pipeline
48804
             **/
48805
 
48806
            this.flush = function () {
48807
                var frames,
48808
                    gopForFusion,
48809
                    gops,
48810
                    moof,
48811
                    mdat,
48812
                    boxes,
48813
                    prependedContentDuration = 0,
48814
                    firstGop,
48815
                    lastGop; // Throw away nalUnits at the start of the byte stream until
48816
                // we find the first AUD
48817
 
48818
                while (nalUnits.length) {
48819
                    if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
48820
                        break;
48821
                    }
48822
                    nalUnits.shift();
48823
                } // Return early if no video data has been observed
48824
 
48825
                if (nalUnits.length === 0) {
48826
                    this.resetStream_();
48827
                    this.trigger('done', 'VideoSegmentStream');
48828
                    return;
48829
                } // Organize the raw nal-units into arrays that represent
48830
                // higher-level constructs such as frames and gops
48831
                // (group-of-pictures)
48832
 
48833
                frames = frameUtils.groupNalsIntoFrames(nalUnits);
48834
                gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
48835
                // a problem since MSE (on Chrome) requires a leading keyframe.
48836
                //
48837
                // We have two approaches to repairing this situation:
48838
                // 1) GOP-FUSION:
48839
                //    This is where we keep track of the GOPS (group-of-pictures)
48840
                //    from previous fragments and attempt to find one that we can
48841
                //    prepend to the current fragment in order to create a valid
48842
                //    fragment.
48843
                // 2) KEYFRAME-PULLING:
48844
                //    Here we search for the first keyframe in the fragment and
48845
                //    throw away all the frames between the start of the fragment
48846
                //    and that keyframe. We then extend the duration and pull the
48847
                //    PTS of the keyframe forward so that it covers the time range
48848
                //    of the frames that were disposed of.
48849
                //
48850
                // #1 is far prefereable over #2 which can cause "stuttering" but
48851
                // requires more things to be just right.
48852
 
48853
                if (!gops[0][0].keyFrame) {
48854
                    // Search for a gop for fusion from our gopCache
48855
                    gopForFusion = this.getGopForFusion_(nalUnits[0], track);
48856
                    if (gopForFusion) {
48857
                        // in order to provide more accurate timing information about the segment, save
48858
                        // the number of seconds prepended to the original segment due to GOP fusion
48859
                        prependedContentDuration = gopForFusion.duration;
48860
                        gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
48861
                        // new gop at the beginning
48862
 
48863
                        gops.byteLength += gopForFusion.byteLength;
48864
                        gops.nalCount += gopForFusion.nalCount;
48865
                        gops.pts = gopForFusion.pts;
48866
                        gops.dts = gopForFusion.dts;
48867
                        gops.duration += gopForFusion.duration;
48868
                    } else {
48869
                        // If we didn't find a candidate gop fall back to keyframe-pulling
48870
                        gops = frameUtils.extendFirstKeyFrame(gops);
48871
                    }
48872
                } // Trim gops to align with gopsToAlignWith
48873
 
48874
                if (gopsToAlignWith.length) {
48875
                    var alignedGops;
48876
                    if (options.alignGopsAtEnd) {
48877
                        alignedGops = this.alignGopsAtEnd_(gops);
48878
                    } else {
48879
                        alignedGops = this.alignGopsAtStart_(gops);
48880
                    }
48881
                    if (!alignedGops) {
48882
                        // save all the nals in the last GOP into the gop cache
48883
                        this.gopCache_.unshift({
48884
                            gop: gops.pop(),
48885
                            pps: track.pps,
48886
                            sps: track.sps
48887
                        }); // Keep a maximum of 6 GOPs in the cache
48888
 
48889
                        this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
48890
 
48891
                        nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
48892
 
48893
                        this.resetStream_();
48894
                        this.trigger('done', 'VideoSegmentStream');
48895
                        return;
48896
                    } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
48897
                    // when recalculated before sending off to CoalesceStream
48898
 
48899
                    trackDecodeInfo.clearDtsInfo(track);
48900
                    gops = alignedGops;
48901
                }
48902
                trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
48903
                // samples (that is, frames) in the video data
48904
 
48905
                track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
48906
 
48907
                mdat = mp4.mdat(frameUtils.concatenateNalData(gops));
48908
                track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
48909
                this.trigger('processedGopsInfo', gops.map(function (gop) {
48910
                    return {
48911
                        pts: gop.pts,
48912
                        dts: gop.dts,
48913
                        byteLength: gop.byteLength
48914
                    };
48915
                }));
48916
                firstGop = gops[0];
48917
                lastGop = gops[gops.length - 1];
48918
                this.trigger('segmentTimingInfo', generateSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration));
48919
                this.trigger('timingInfo', {
48920
                    start: gops[0].pts,
48921
                    end: gops[gops.length - 1].pts + gops[gops.length - 1].duration
48922
                }); // save all the nals in the last GOP into the gop cache
48923
 
48924
                this.gopCache_.unshift({
48925
                    gop: gops.pop(),
48926
                    pps: track.pps,
48927
                    sps: track.sps
48928
                }); // Keep a maximum of 6 GOPs in the cache
48929
 
48930
                this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
48931
 
48932
                nalUnits = [];
48933
                this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
48934
                this.trigger('timelineStartInfo', track.timelineStartInfo);
48935
                moof = mp4.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
48936
                // throwing away hundreds of media segment fragments
48937
 
48938
                boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
48939
 
48940
                sequenceNumber++;
48941
                boxes.set(moof);
48942
                boxes.set(mdat, moof.byteLength);
48943
                this.trigger('data', {
48944
                    track: track,
48945
                    boxes: boxes
48946
                });
48947
                this.resetStream_(); // Continue with the flush process now
48948
 
48949
                this.trigger('done', 'VideoSegmentStream');
48950
            };
48951
            this.reset = function () {
48952
                this.resetStream_();
48953
                nalUnits = [];
48954
                this.gopCache_.length = 0;
48955
                gopsToAlignWith.length = 0;
48956
                this.trigger('reset');
48957
            };
48958
            this.resetStream_ = function () {
48959
                trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
48960
                // for instance, when we are rendition switching
48961
 
48962
                config = undefined;
48963
                pps = undefined;
48964
            }; // Search for a candidate Gop for gop-fusion from the gop cache and
48965
            // return it or return null if no good candidate was found
48966
 
48967
            this.getGopForFusion_ = function (nalUnit) {
48968
                var halfSecond = 45000,
48969
                    // Half-a-second in a 90khz clock
48970
                    allowableOverlap = 10000,
48971
                    // About 3 frames @ 30fps
48972
                    nearestDistance = Infinity,
48973
                    dtsDistance,
48974
                    nearestGopObj,
48975
                    currentGop,
48976
                    currentGopObj,
48977
                    i; // Search for the GOP nearest to the beginning of this nal unit
48978
 
48979
                for (i = 0; i < this.gopCache_.length; i++) {
48980
                    currentGopObj = this.gopCache_[i];
48981
                    currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
48982
 
48983
                    if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
48984
                        continue;
48985
                    } // Reject Gops that would require a negative baseMediaDecodeTime
48986
 
48987
                    if (currentGop.dts < track.timelineStartInfo.dts) {
48988
                        continue;
48989
                    } // The distance between the end of the gop and the start of the nalUnit
48990
 
48991
                    dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
48992
                    // a half-second of the nal unit
48993
 
48994
                    if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
48995
                        // Always use the closest GOP we found if there is more than
48996
                        // one candidate
48997
                        if (!nearestGopObj || nearestDistance > dtsDistance) {
48998
                            nearestGopObj = currentGopObj;
48999
                            nearestDistance = dtsDistance;
49000
                        }
49001
                    }
49002
                }
49003
                if (nearestGopObj) {
49004
                    return nearestGopObj.gop;
49005
                }
49006
                return null;
49007
            }; // trim gop list to the first gop found that has a matching pts with a gop in the list
49008
            // of gopsToAlignWith starting from the START of the list
49009
 
49010
            this.alignGopsAtStart_ = function (gops) {
49011
                var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
49012
                byteLength = gops.byteLength;
49013
                nalCount = gops.nalCount;
49014
                duration = gops.duration;
49015
                alignIndex = gopIndex = 0;
49016
                while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
49017
                    align = gopsToAlignWith[alignIndex];
49018
                    gop = gops[gopIndex];
49019
                    if (align.pts === gop.pts) {
49020
                        break;
49021
                    }
49022
                    if (gop.pts > align.pts) {
49023
                        // this current gop starts after the current gop we want to align on, so increment
49024
                        // align index
49025
                        alignIndex++;
49026
                        continue;
49027
                    } // current gop starts before the current gop we want to align on. so increment gop
49028
                    // index
49029
 
49030
                    gopIndex++;
49031
                    byteLength -= gop.byteLength;
49032
                    nalCount -= gop.nalCount;
49033
                    duration -= gop.duration;
49034
                }
49035
                if (gopIndex === 0) {
49036
                    // no gops to trim
49037
                    return gops;
49038
                }
49039
                if (gopIndex === gops.length) {
49040
                    // all gops trimmed, skip appending all gops
49041
                    return null;
49042
                }
49043
                alignedGops = gops.slice(gopIndex);
49044
                alignedGops.byteLength = byteLength;
49045
                alignedGops.duration = duration;
49046
                alignedGops.nalCount = nalCount;
49047
                alignedGops.pts = alignedGops[0].pts;
49048
                alignedGops.dts = alignedGops[0].dts;
49049
                return alignedGops;
49050
            }; // trim gop list to the first gop found that has a matching pts with a gop in the list
49051
            // of gopsToAlignWith starting from the END of the list
49052
 
49053
            this.alignGopsAtEnd_ = function (gops) {
49054
                var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
49055
                alignIndex = gopsToAlignWith.length - 1;
49056
                gopIndex = gops.length - 1;
49057
                alignEndIndex = null;
49058
                matchFound = false;
49059
                while (alignIndex >= 0 && gopIndex >= 0) {
49060
                    align = gopsToAlignWith[alignIndex];
49061
                    gop = gops[gopIndex];
49062
                    if (align.pts === gop.pts) {
49063
                        matchFound = true;
49064
                        break;
49065
                    }
49066
                    if (align.pts > gop.pts) {
49067
                        alignIndex--;
49068
                        continue;
49069
                    }
49070
                    if (alignIndex === gopsToAlignWith.length - 1) {
49071
                        // gop.pts is greater than the last alignment candidate. If no match is found
49072
                        // by the end of this loop, we still want to append gops that come after this
49073
                        // point
49074
                        alignEndIndex = gopIndex;
49075
                    }
49076
                    gopIndex--;
49077
                }
49078
                if (!matchFound && alignEndIndex === null) {
49079
                    return null;
49080
                }
49081
                var trimIndex;
49082
                if (matchFound) {
49083
                    trimIndex = gopIndex;
49084
                } else {
49085
                    trimIndex = alignEndIndex;
49086
                }
49087
                if (trimIndex === 0) {
49088
                    return gops;
49089
                }
49090
                var alignedGops = gops.slice(trimIndex);
49091
                var metadata = alignedGops.reduce(function (total, gop) {
49092
                    total.byteLength += gop.byteLength;
49093
                    total.duration += gop.duration;
49094
                    total.nalCount += gop.nalCount;
49095
                    return total;
49096
                }, {
49097
                    byteLength: 0,
49098
                    duration: 0,
49099
                    nalCount: 0
49100
                });
49101
                alignedGops.byteLength = metadata.byteLength;
49102
                alignedGops.duration = metadata.duration;
49103
                alignedGops.nalCount = metadata.nalCount;
49104
                alignedGops.pts = alignedGops[0].pts;
49105
                alignedGops.dts = alignedGops[0].dts;
49106
                return alignedGops;
49107
            };
49108
            this.alignGopsWith = function (newGopsToAlignWith) {
49109
                gopsToAlignWith = newGopsToAlignWith;
49110
            };
49111
        };
49112
        VideoSegmentStream.prototype = new Stream();
49113
        /**
49114
         * A Stream that can combine multiple streams (ie. audio & video)
49115
         * into a single output segment for MSE. Also supports audio-only
49116
         * and video-only streams.
49117
         * @param options {object} transmuxer options object
49118
         * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
49119
         *        in the source; false to adjust the first segment to start at media timeline start.
49120
         */
49121
 
49122
        CoalesceStream = function (options, metadataStream) {
49123
            // Number of Tracks per output segment
49124
            // If greater than 1, we combine multiple
49125
            // tracks into a single segment
49126
            this.numberOfTracks = 0;
49127
            this.metadataStream = metadataStream;
49128
            options = options || {};
49129
            if (typeof options.remux !== 'undefined') {
49130
                this.remuxTracks = !!options.remux;
49131
            } else {
49132
                this.remuxTracks = true;
49133
            }
49134
            if (typeof options.keepOriginalTimestamps === 'boolean') {
49135
                this.keepOriginalTimestamps = options.keepOriginalTimestamps;
49136
            } else {
49137
                this.keepOriginalTimestamps = false;
49138
            }
49139
            this.pendingTracks = [];
49140
            this.videoTrack = null;
49141
            this.pendingBoxes = [];
49142
            this.pendingCaptions = [];
49143
            this.pendingMetadata = [];
49144
            this.pendingBytes = 0;
49145
            this.emittedTracks = 0;
49146
            CoalesceStream.prototype.init.call(this); // Take output from multiple
49147
 
49148
            this.push = function (output) {
49149
                // buffer incoming captions until the associated video segment
49150
                // finishes
49151
                if (output.content || output.text) {
49152
                    return this.pendingCaptions.push(output);
49153
                } // buffer incoming id3 tags until the final flush
49154
 
49155
                if (output.frames) {
49156
                    return this.pendingMetadata.push(output);
49157
                } // Add this track to the list of pending tracks and store
49158
                // important information required for the construction of
49159
                // the final segment
49160
 
49161
                this.pendingTracks.push(output.track);
49162
                this.pendingBytes += output.boxes.byteLength; // TODO: is there an issue for this against chrome?
49163
                // We unshift audio and push video because
49164
                // as of Chrome 75 when switching from
49165
                // one init segment to another if the video
49166
                // mdat does not appear after the audio mdat
49167
                // only audio will play for the duration of our transmux.
49168
 
49169
                if (output.track.type === 'video') {
49170
                    this.videoTrack = output.track;
49171
                    this.pendingBoxes.push(output.boxes);
49172
                }
49173
                if (output.track.type === 'audio') {
49174
                    this.audioTrack = output.track;
49175
                    this.pendingBoxes.unshift(output.boxes);
49176
                }
49177
            };
49178
        };
49179
        CoalesceStream.prototype = new Stream();
49180
        CoalesceStream.prototype.flush = function (flushSource) {
49181
            var offset = 0,
49182
                event = {
49183
                    captions: [],
49184
                    captionStreams: {},
49185
                    metadata: [],
49186
                    info: {}
49187
                },
49188
                caption,
49189
                id3,
49190
                initSegment,
49191
                timelineStartPts = 0,
49192
                i;
49193
            if (this.pendingTracks.length < this.numberOfTracks) {
49194
                if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
49195
                    // Return because we haven't received a flush from a data-generating
49196
                    // portion of the segment (meaning that we have only recieved meta-data
49197
                    // or captions.)
49198
                    return;
49199
                } else if (this.remuxTracks) {
49200
                    // Return until we have enough tracks from the pipeline to remux (if we
49201
                    // are remuxing audio and video into a single MP4)
49202
                    return;
49203
                } else if (this.pendingTracks.length === 0) {
49204
                    // In the case where we receive a flush without any data having been
49205
                    // received we consider it an emitted track for the purposes of coalescing
49206
                    // `done` events.
49207
                    // We do this for the case where there is an audio and video track in the
49208
                    // segment but no audio data. (seen in several playlists with alternate
49209
                    // audio tracks and no audio present in the main TS segments.)
49210
                    this.emittedTracks++;
49211
                    if (this.emittedTracks >= this.numberOfTracks) {
49212
                        this.trigger('done');
49213
                        this.emittedTracks = 0;
49214
                    }
49215
                    return;
49216
                }
49217
            }
49218
            if (this.videoTrack) {
49219
                timelineStartPts = this.videoTrack.timelineStartInfo.pts;
49220
                VIDEO_PROPERTIES.forEach(function (prop) {
49221
                    event.info[prop] = this.videoTrack[prop];
49222
                }, this);
49223
            } else if (this.audioTrack) {
49224
                timelineStartPts = this.audioTrack.timelineStartInfo.pts;
49225
                AUDIO_PROPERTIES.forEach(function (prop) {
49226
                    event.info[prop] = this.audioTrack[prop];
49227
                }, this);
49228
            }
49229
            if (this.videoTrack || this.audioTrack) {
49230
                if (this.pendingTracks.length === 1) {
49231
                    event.type = this.pendingTracks[0].type;
49232
                } else {
49233
                    event.type = 'combined';
49234
                }
49235
                this.emittedTracks += this.pendingTracks.length;
49236
                initSegment = mp4.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
49237
 
49238
                event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
49239
                // and track definitions
49240
 
49241
                event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
49242
 
49243
                event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
49244
 
49245
                for (i = 0; i < this.pendingBoxes.length; i++) {
49246
                    event.data.set(this.pendingBoxes[i], offset);
49247
                    offset += this.pendingBoxes[i].byteLength;
49248
                } // Translate caption PTS times into second offsets to match the
49249
                // video timeline for the segment, and add track info
49250
 
49251
                for (i = 0; i < this.pendingCaptions.length; i++) {
49252
                    caption = this.pendingCaptions[i];
49253
                    caption.startTime = clock.metadataTsToSeconds(caption.startPts, timelineStartPts, this.keepOriginalTimestamps);
49254
                    caption.endTime = clock.metadataTsToSeconds(caption.endPts, timelineStartPts, this.keepOriginalTimestamps);
49255
                    event.captionStreams[caption.stream] = true;
49256
                    event.captions.push(caption);
49257
                } // Translate ID3 frame PTS times into second offsets to match the
49258
                // video timeline for the segment
49259
 
49260
                for (i = 0; i < this.pendingMetadata.length; i++) {
49261
                    id3 = this.pendingMetadata[i];
49262
                    id3.cueTime = clock.metadataTsToSeconds(id3.pts, timelineStartPts, this.keepOriginalTimestamps);
49263
                    event.metadata.push(id3);
49264
                } // We add this to every single emitted segment even though we only need
49265
                // it for the first
49266
 
49267
                event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
49268
 
49269
                this.pendingTracks.length = 0;
49270
                this.videoTrack = null;
49271
                this.pendingBoxes.length = 0;
49272
                this.pendingCaptions.length = 0;
49273
                this.pendingBytes = 0;
49274
                this.pendingMetadata.length = 0; // Emit the built segment
49275
                // We include captions and ID3 tags for backwards compatibility,
49276
                // ideally we should send only video and audio in the data event
49277
 
49278
                this.trigger('data', event); // Emit each caption to the outside world
49279
                // Ideally, this would happen immediately on parsing captions,
49280
                // but we need to ensure that video data is sent back first
49281
                // so that caption timing can be adjusted to match video timing
49282
 
49283
                for (i = 0; i < event.captions.length; i++) {
49284
                    caption = event.captions[i];
49285
                    this.trigger('caption', caption);
49286
                } // Emit each id3 tag to the outside world
49287
                // Ideally, this would happen immediately on parsing the tag,
49288
                // but we need to ensure that video data is sent back first
49289
                // so that ID3 frame timing can be adjusted to match video timing
49290
 
49291
                for (i = 0; i < event.metadata.length; i++) {
49292
                    id3 = event.metadata[i];
49293
                    this.trigger('id3Frame', id3);
49294
                }
49295
            } // Only emit `done` if all tracks have been flushed and emitted
49296
 
49297
            if (this.emittedTracks >= this.numberOfTracks) {
49298
                this.trigger('done');
49299
                this.emittedTracks = 0;
49300
            }
49301
        };
49302
        CoalesceStream.prototype.setRemux = function (val) {
49303
            this.remuxTracks = val;
49304
        };
49305
        /**
49306
         * A Stream that expects MP2T binary data as input and produces
49307
         * corresponding media segments, suitable for use with Media Source
49308
         * Extension (MSE) implementations that support the ISO BMFF byte
49309
         * stream format, like Chrome.
49310
         */
49311
 
49312
        Transmuxer = function (options) {
49313
            var self = this,
49314
                hasFlushed = true,
49315
                videoTrack,
49316
                audioTrack;
49317
            Transmuxer.prototype.init.call(this);
49318
            options = options || {};
49319
            this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
49320
            this.transmuxPipeline_ = {};
49321
            this.setupAacPipeline = function () {
49322
                var pipeline = {};
49323
                this.transmuxPipeline_ = pipeline;
49324
                pipeline.type = 'aac';
49325
                pipeline.metadataStream = new m2ts.MetadataStream(); // set up the parsing pipeline
49326
 
49327
                pipeline.aacStream = new AacStream();
49328
                pipeline.audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio');
49329
                pipeline.timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream('timed-metadata');
49330
                pipeline.adtsStream = new AdtsStream();
49331
                pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
49332
                pipeline.headOfPipeline = pipeline.aacStream;
49333
                pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
49334
                pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
49335
                pipeline.metadataStream.on('timestamp', function (frame) {
49336
                    pipeline.aacStream.setTimestamp(frame.timeStamp);
49337
                });
49338
                pipeline.aacStream.on('data', function (data) {
49339
                    if (data.type !== 'timed-metadata' && data.type !== 'audio' || pipeline.audioSegmentStream) {
49340
                        return;
49341
                    }
49342
                    audioTrack = audioTrack || {
49343
                        timelineStartInfo: {
49344
                            baseMediaDecodeTime: self.baseMediaDecodeTime
49345
                        },
49346
                        codec: 'adts',
49347
                        type: 'audio'
49348
                    }; // hook up the audio segment stream to the first track with aac data
49349
 
49350
                    pipeline.coalesceStream.numberOfTracks++;
49351
                    pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack, options);
49352
                    pipeline.audioSegmentStream.on('log', self.getLogTrigger_('audioSegmentStream'));
49353
                    pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline
49354
 
49355
                    pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); // emit pmt info
49356
 
49357
                    self.trigger('trackinfo', {
49358
                        hasAudio: !!audioTrack,
49359
                        hasVideo: !!videoTrack
49360
                    });
49361
                }); // Re-emit any data coming from the coalesce stream to the outside world
49362
 
49363
                pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
49364
 
49365
                pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
49366
                addPipelineLogRetriggers(this, pipeline);
49367
            };
49368
            this.setupTsPipeline = function () {
49369
                var pipeline = {};
49370
                this.transmuxPipeline_ = pipeline;
49371
                pipeline.type = 'ts';
49372
                pipeline.metadataStream = new m2ts.MetadataStream(); // set up the parsing pipeline
49373
 
49374
                pipeline.packetStream = new m2ts.TransportPacketStream();
49375
                pipeline.parseStream = new m2ts.TransportParseStream();
49376
                pipeline.elementaryStream = new m2ts.ElementaryStream();
49377
                pipeline.timestampRolloverStream = new m2ts.TimestampRolloverStream();
49378
                pipeline.adtsStream = new AdtsStream();
49379
                pipeline.h264Stream = new H264Stream();
49380
                pipeline.captionStream = new m2ts.CaptionStream(options);
49381
                pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
49382
                pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
49383
 
49384
                pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream).pipe(pipeline.timestampRolloverStream); // !!THIS ORDER IS IMPORTANT!!
49385
                // demux the streams
49386
 
49387
                pipeline.timestampRolloverStream.pipe(pipeline.h264Stream);
49388
                pipeline.timestampRolloverStream.pipe(pipeline.adtsStream);
49389
                pipeline.timestampRolloverStream.pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
49390
 
49391
                pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
49392
                pipeline.elementaryStream.on('data', function (data) {
49393
                    var i;
49394
                    if (data.type === 'metadata') {
49395
                        i = data.tracks.length; // scan the tracks listed in the metadata
49396
 
49397
                        while (i--) {
49398
                            if (!videoTrack && data.tracks[i].type === 'video') {
49399
                                videoTrack = data.tracks[i];
49400
                                videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
49401
                            } else if (!audioTrack && data.tracks[i].type === 'audio') {
49402
                                audioTrack = data.tracks[i];
49403
                                audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
49404
                            }
49405
                        } // hook up the video segment stream to the first track with h264 data
49406
 
49407
                        if (videoTrack && !pipeline.videoSegmentStream) {
49408
                            pipeline.coalesceStream.numberOfTracks++;
49409
                            pipeline.videoSegmentStream = new VideoSegmentStream(videoTrack, options);
49410
                            pipeline.videoSegmentStream.on('log', self.getLogTrigger_('videoSegmentStream'));
49411
                            pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
49412
                                // When video emits timelineStartInfo data after a flush, we forward that
49413
                                // info to the AudioSegmentStream, if it exists, because video timeline
49414
                                // data takes precedence.  Do not do this if keepOriginalTimestamps is set,
49415
                                // because this is a particularly subtle form of timestamp alteration.
49416
                                if (audioTrack && !options.keepOriginalTimestamps) {
49417
                                    audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
49418
                                    // very earliest DTS we have seen in video because Chrome will
49419
                                    // interpret any video track with a baseMediaDecodeTime that is
49420
                                    // non-zero as a gap.
49421
 
49422
                                    pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts - self.baseMediaDecodeTime);
49423
                                }
49424
                            });
49425
                            pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
49426
                            pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
49427
                            pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
49428
                                if (audioTrack) {
49429
                                    pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
49430
                                }
49431
                            });
49432
                            pipeline.videoSegmentStream.on('timingInfo', self.trigger.bind(self, 'videoTimingInfo')); // Set up the final part of the video pipeline
49433
 
49434
                            pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
49435
                        }
49436
                        if (audioTrack && !pipeline.audioSegmentStream) {
49437
                            // hook up the audio segment stream to the first track with aac data
49438
                            pipeline.coalesceStream.numberOfTracks++;
49439
                            pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack, options);
49440
                            pipeline.audioSegmentStream.on('log', self.getLogTrigger_('audioSegmentStream'));
49441
                            pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo'));
49442
                            pipeline.audioSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'audioSegmentTimingInfo')); // Set up the final part of the audio pipeline
49443
 
49444
                            pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
49445
                        } // emit pmt info
49446
 
49447
                        self.trigger('trackinfo', {
49448
                            hasAudio: !!audioTrack,
49449
                            hasVideo: !!videoTrack
49450
                        });
49451
                    }
49452
                }); // Re-emit any data coming from the coalesce stream to the outside world
49453
 
49454
                pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
49455
                pipeline.coalesceStream.on('id3Frame', function (id3Frame) {
49456
                    id3Frame.dispatchType = pipeline.metadataStream.dispatchType;
49457
                    self.trigger('id3Frame', id3Frame);
49458
                });
49459
                pipeline.coalesceStream.on('caption', this.trigger.bind(this, 'caption')); // Let the consumer know we have finished flushing the entire pipeline
49460
 
49461
                pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
49462
                addPipelineLogRetriggers(this, pipeline);
49463
            }; // hook up the segment streams once track metadata is delivered
49464
 
49465
            this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
49466
                var pipeline = this.transmuxPipeline_;
49467
                if (!options.keepOriginalTimestamps) {
49468
                    this.baseMediaDecodeTime = baseMediaDecodeTime;
49469
                }
49470
                if (audioTrack) {
49471
                    audioTrack.timelineStartInfo.dts = undefined;
49472
                    audioTrack.timelineStartInfo.pts = undefined;
49473
                    trackDecodeInfo.clearDtsInfo(audioTrack);
49474
                    if (pipeline.audioTimestampRolloverStream) {
49475
                        pipeline.audioTimestampRolloverStream.discontinuity();
49476
                    }
49477
                }
49478
                if (videoTrack) {
49479
                    if (pipeline.videoSegmentStream) {
49480
                        pipeline.videoSegmentStream.gopCache_ = [];
49481
                    }
49482
                    videoTrack.timelineStartInfo.dts = undefined;
49483
                    videoTrack.timelineStartInfo.pts = undefined;
49484
                    trackDecodeInfo.clearDtsInfo(videoTrack);
49485
                    pipeline.captionStream.reset();
49486
                }
49487
                if (pipeline.timestampRolloverStream) {
49488
                    pipeline.timestampRolloverStream.discontinuity();
49489
                }
49490
            };
49491
            this.setAudioAppendStart = function (timestamp) {
49492
                if (audioTrack) {
49493
                    this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
49494
                }
49495
            };
49496
            this.setRemux = function (val) {
49497
                var pipeline = this.transmuxPipeline_;
49498
                options.remux = val;
49499
                if (pipeline && pipeline.coalesceStream) {
49500
                    pipeline.coalesceStream.setRemux(val);
49501
                }
49502
            };
49503
            this.alignGopsWith = function (gopsToAlignWith) {
49504
                if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
49505
                    this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
49506
                }
49507
            };
49508
            this.getLogTrigger_ = function (key) {
49509
                var self = this;
49510
                return function (event) {
49511
                    event.stream = key;
49512
                    self.trigger('log', event);
49513
                };
49514
            }; // feed incoming data to the front of the parsing pipeline
49515
 
49516
            this.push = function (data) {
49517
                if (hasFlushed) {
49518
                    var isAac = isLikelyAacData(data);
49519
                    if (isAac && this.transmuxPipeline_.type !== 'aac') {
49520
                        this.setupAacPipeline();
49521
                    } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
49522
                        this.setupTsPipeline();
49523
                    }
49524
                    hasFlushed = false;
49525
                }
49526
                this.transmuxPipeline_.headOfPipeline.push(data);
49527
            }; // flush any buffered data
49528
 
49529
            this.flush = function () {
49530
                hasFlushed = true; // Start at the top of the pipeline and flush all pending work
49531
 
49532
                this.transmuxPipeline_.headOfPipeline.flush();
49533
            };
49534
            this.endTimeline = function () {
49535
                this.transmuxPipeline_.headOfPipeline.endTimeline();
49536
            };
49537
            this.reset = function () {
49538
                if (this.transmuxPipeline_.headOfPipeline) {
49539
                    this.transmuxPipeline_.headOfPipeline.reset();
49540
                }
49541
            }; // Caption data has to be reset when seeking outside buffered range
49542
 
49543
            this.resetCaptions = function () {
49544
                if (this.transmuxPipeline_.captionStream) {
49545
                    this.transmuxPipeline_.captionStream.reset();
49546
                }
49547
            };
49548
        };
49549
        Transmuxer.prototype = new Stream();
49550
        var transmuxer = {
49551
            Transmuxer: Transmuxer,
49552
            VideoSegmentStream: VideoSegmentStream,
49553
            AudioSegmentStream: AudioSegmentStream,
49554
            AUDIO_PROPERTIES: AUDIO_PROPERTIES,
49555
            VIDEO_PROPERTIES: VIDEO_PROPERTIES,
49556
            // exported for testing
49557
            generateSegmentTimingInfo: generateSegmentTimingInfo
49558
        };
49559
        /**
49560
         * mux.js
49561
         *
49562
         * Copyright (c) Brightcove
49563
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
49564
         */
49565
 
49566
        var toUnsigned$3 = function (value) {
49567
            return value >>> 0;
49568
        };
49569
        var toHexString$1 = function (value) {
49570
            return ('00' + value.toString(16)).slice(-2);
49571
        };
49572
        var bin = {
49573
            toUnsigned: toUnsigned$3,
49574
            toHexString: toHexString$1
49575
        };
49576
        var parseType$3 = function (buffer) {
49577
            var result = '';
49578
            result += String.fromCharCode(buffer[0]);
49579
            result += String.fromCharCode(buffer[1]);
49580
            result += String.fromCharCode(buffer[2]);
49581
            result += String.fromCharCode(buffer[3]);
49582
            return result;
49583
        };
49584
        var parseType_1 = parseType$3;
49585
        var toUnsigned$2 = bin.toUnsigned;
49586
        var parseType$2 = parseType_1;
49587
        var findBox$2 = function (data, path) {
49588
            var results = [],
49589
                i,
49590
                size,
49591
                type,
49592
                end,
49593
                subresults;
49594
            if (!path.length) {
49595
                // short-circuit the search for empty paths
49596
                return null;
49597
            }
49598
            for (i = 0; i < data.byteLength;) {
49599
                size = toUnsigned$2(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
49600
                type = parseType$2(data.subarray(i + 4, i + 8));
49601
                end = size > 1 ? i + size : data.byteLength;
49602
                if (type === path[0]) {
49603
                    if (path.length === 1) {
49604
                        // this is the end of the path and we've found the box we were
49605
                        // looking for
49606
                        results.push(data.subarray(i + 8, end));
49607
                    } else {
49608
                        // recursively search for the next box along the path
49609
                        subresults = findBox$2(data.subarray(i + 8, end), path.slice(1));
49610
                        if (subresults.length) {
49611
                            results = results.concat(subresults);
49612
                        }
49613
                    }
49614
                }
49615
                i = end;
49616
            } // we've finished searching all of data
49617
 
49618
            return results;
49619
        };
49620
        var findBox_1 = findBox$2;
49621
        var toUnsigned$1 = bin.toUnsigned;
49622
        var getUint64$2 = numbers.getUint64;
49623
        var tfdt = function (data) {
49624
            var result = {
49625
                version: data[0],
49626
                flags: new Uint8Array(data.subarray(1, 4))
49627
            };
49628
            if (result.version === 1) {
49629
                result.baseMediaDecodeTime = getUint64$2(data.subarray(4));
49630
            } else {
49631
                result.baseMediaDecodeTime = toUnsigned$1(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]);
49632
            }
49633
            return result;
49634
        };
49635
        var parseTfdt$2 = tfdt;
49636
        var parseSampleFlags$1 = function (flags) {
49637
            return {
49638
                isLeading: (flags[0] & 0x0c) >>> 2,
49639
                dependsOn: flags[0] & 0x03,
49640
                isDependedOn: (flags[1] & 0xc0) >>> 6,
49641
                hasRedundancy: (flags[1] & 0x30) >>> 4,
49642
                paddingValue: (flags[1] & 0x0e) >>> 1,
49643
                isNonSyncSample: flags[1] & 0x01,
49644
                degradationPriority: flags[2] << 8 | flags[3]
49645
            };
49646
        };
49647
        var parseSampleFlags_1 = parseSampleFlags$1;
49648
        var parseSampleFlags = parseSampleFlags_1;
49649
        var trun = function (data) {
49650
            var result = {
49651
                    version: data[0],
49652
                    flags: new Uint8Array(data.subarray(1, 4)),
49653
                    samples: []
49654
                },
49655
                view = new DataView(data.buffer, data.byteOffset, data.byteLength),
49656
                // Flag interpretation
49657
                dataOffsetPresent = result.flags[2] & 0x01,
49658
                // compare with 2nd byte of 0x1
49659
                firstSampleFlagsPresent = result.flags[2] & 0x04,
49660
                // compare with 2nd byte of 0x4
49661
                sampleDurationPresent = result.flags[1] & 0x01,
49662
                // compare with 2nd byte of 0x100
49663
                sampleSizePresent = result.flags[1] & 0x02,
49664
                // compare with 2nd byte of 0x200
49665
                sampleFlagsPresent = result.flags[1] & 0x04,
49666
                // compare with 2nd byte of 0x400
49667
                sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
49668
                // compare with 2nd byte of 0x800
49669
                sampleCount = view.getUint32(4),
49670
                offset = 8,
49671
                sample;
49672
            if (dataOffsetPresent) {
49673
                // 32 bit signed integer
49674
                result.dataOffset = view.getInt32(offset);
49675
                offset += 4;
49676
            } // Overrides the flags for the first sample only. The order of
49677
            // optional values will be: duration, size, compositionTimeOffset
49678
 
49679
            if (firstSampleFlagsPresent && sampleCount) {
49680
                sample = {
49681
                    flags: parseSampleFlags(data.subarray(offset, offset + 4))
49682
                };
49683
                offset += 4;
49684
                if (sampleDurationPresent) {
49685
                    sample.duration = view.getUint32(offset);
49686
                    offset += 4;
49687
                }
49688
                if (sampleSizePresent) {
49689
                    sample.size = view.getUint32(offset);
49690
                    offset += 4;
49691
                }
49692
                if (sampleCompositionTimeOffsetPresent) {
49693
                    if (result.version === 1) {
49694
                        sample.compositionTimeOffset = view.getInt32(offset);
49695
                    } else {
49696
                        sample.compositionTimeOffset = view.getUint32(offset);
49697
                    }
49698
                    offset += 4;
49699
                }
49700
                result.samples.push(sample);
49701
                sampleCount--;
49702
            }
49703
            while (sampleCount--) {
49704
                sample = {};
49705
                if (sampleDurationPresent) {
49706
                    sample.duration = view.getUint32(offset);
49707
                    offset += 4;
49708
                }
49709
                if (sampleSizePresent) {
49710
                    sample.size = view.getUint32(offset);
49711
                    offset += 4;
49712
                }
49713
                if (sampleFlagsPresent) {
49714
                    sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
49715
                    offset += 4;
49716
                }
49717
                if (sampleCompositionTimeOffsetPresent) {
49718
                    if (result.version === 1) {
49719
                        sample.compositionTimeOffset = view.getInt32(offset);
49720
                    } else {
49721
                        sample.compositionTimeOffset = view.getUint32(offset);
49722
                    }
49723
                    offset += 4;
49724
                }
49725
                result.samples.push(sample);
49726
            }
49727
            return result;
49728
        };
49729
        var parseTrun$2 = trun;
49730
        var tfhd = function (data) {
49731
            var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
49732
                result = {
49733
                    version: data[0],
49734
                    flags: new Uint8Array(data.subarray(1, 4)),
49735
                    trackId: view.getUint32(4)
49736
                },
49737
                baseDataOffsetPresent = result.flags[2] & 0x01,
49738
                sampleDescriptionIndexPresent = result.flags[2] & 0x02,
49739
                defaultSampleDurationPresent = result.flags[2] & 0x08,
49740
                defaultSampleSizePresent = result.flags[2] & 0x10,
49741
                defaultSampleFlagsPresent = result.flags[2] & 0x20,
49742
                durationIsEmpty = result.flags[0] & 0x010000,
49743
                defaultBaseIsMoof = result.flags[0] & 0x020000,
49744
                i;
49745
            i = 8;
49746
            if (baseDataOffsetPresent) {
49747
                i += 4; // truncate top 4 bytes
49748
                // FIXME: should we read the full 64 bits?
49749
 
49750
                result.baseDataOffset = view.getUint32(12);
49751
                i += 4;
49752
            }
49753
            if (sampleDescriptionIndexPresent) {
49754
                result.sampleDescriptionIndex = view.getUint32(i);
49755
                i += 4;
49756
            }
49757
            if (defaultSampleDurationPresent) {
49758
                result.defaultSampleDuration = view.getUint32(i);
49759
                i += 4;
49760
            }
49761
            if (defaultSampleSizePresent) {
49762
                result.defaultSampleSize = view.getUint32(i);
49763
                i += 4;
49764
            }
49765
            if (defaultSampleFlagsPresent) {
49766
                result.defaultSampleFlags = view.getUint32(i);
49767
            }
49768
            if (durationIsEmpty) {
49769
                result.durationIsEmpty = true;
49770
            }
49771
            if (!baseDataOffsetPresent && defaultBaseIsMoof) {
49772
                result.baseDataOffsetIsMoof = true;
49773
            }
49774
            return result;
49775
        };
49776
        var parseTfhd$2 = tfhd;
49777
        var win;
49778
        if (typeof window !== "undefined") {
49779
            win = window;
49780
        } else if (typeof commonjsGlobal !== "undefined") {
49781
            win = commonjsGlobal;
49782
        } else if (typeof self !== "undefined") {
49783
            win = self;
49784
        } else {
49785
            win = {};
49786
        }
49787
        var window_1 = win;
49788
        /**
49789
         * mux.js
49790
         *
49791
         * Copyright (c) Brightcove
49792
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
49793
         *
49794
         * Reads in-band CEA-708 captions out of FMP4 segments.
49795
         * @see https://en.wikipedia.org/wiki/CEA-708
49796
         */
49797
 
49798
        var discardEmulationPreventionBytes = captionPacketParser.discardEmulationPreventionBytes;
49799
        var CaptionStream = captionStream.CaptionStream;
49800
        var findBox$1 = findBox_1;
49801
        var parseTfdt$1 = parseTfdt$2;
49802
        var parseTrun$1 = parseTrun$2;
49803
        var parseTfhd$1 = parseTfhd$2;
49804
        var window$2 = window_1;
49805
        /**
49806
         * Maps an offset in the mdat to a sample based on the the size of the samples.
49807
         * Assumes that `parseSamples` has been called first.
49808
         *
49809
         * @param {Number} offset - The offset into the mdat
49810
         * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
49811
         * @return {?Object} The matching sample, or null if no match was found.
49812
         *
49813
         * @see ISO-BMFF-12/2015, Section 8.8.8
49814
         **/
49815
 
49816
        var mapToSample = function (offset, samples) {
49817
            var approximateOffset = offset;
49818
            for (var i = 0; i < samples.length; i++) {
49819
                var sample = samples[i];
49820
                if (approximateOffset < sample.size) {
49821
                    return sample;
49822
                }
49823
                approximateOffset -= sample.size;
49824
            }
49825
            return null;
49826
        };
49827
        /**
49828
         * Finds SEI nal units contained in a Media Data Box.
49829
         * Assumes that `parseSamples` has been called first.
49830
         *
49831
         * @param {Uint8Array} avcStream - The bytes of the mdat
49832
         * @param {Object[]} samples - The samples parsed out by `parseSamples`
49833
         * @param {Number} trackId - The trackId of this video track
49834
         * @return {Object[]} seiNals - the parsed SEI NALUs found.
49835
         *   The contents of the seiNal should match what is expected by
49836
         *   CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
49837
         *
49838
         * @see ISO-BMFF-12/2015, Section 8.1.1
49839
         * @see Rec. ITU-T H.264, 7.3.2.3.1
49840
         **/
49841
 
49842
        var findSeiNals = function (avcStream, samples, trackId) {
49843
            var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
49844
                result = {
49845
                    logs: [],
49846
                    seiNals: []
49847
                },
49848
                seiNal,
49849
                i,
49850
                length,
49851
                lastMatchedSample;
49852
            for (i = 0; i + 4 < avcStream.length; i += length) {
49853
                length = avcView.getUint32(i);
49854
                i += 4; // Bail if this doesn't appear to be an H264 stream
49855
 
49856
                if (length <= 0) {
49857
                    continue;
49858
                }
49859
                switch (avcStream[i] & 0x1F) {
49860
                    case 0x06:
49861
                        var data = avcStream.subarray(i + 1, i + 1 + length);
49862
                        var matchingSample = mapToSample(i, samples);
49863
                        seiNal = {
49864
                            nalUnitType: 'sei_rbsp',
49865
                            size: length,
49866
                            data: data,
49867
                            escapedRBSP: discardEmulationPreventionBytes(data),
49868
                            trackId: trackId
49869
                        };
49870
                        if (matchingSample) {
49871
                            seiNal.pts = matchingSample.pts;
49872
                            seiNal.dts = matchingSample.dts;
49873
                            lastMatchedSample = matchingSample;
49874
                        } else if (lastMatchedSample) {
49875
                            // If a matching sample cannot be found, use the last
49876
                            // sample's values as they should be as close as possible
49877
                            seiNal.pts = lastMatchedSample.pts;
49878
                            seiNal.dts = lastMatchedSample.dts;
49879
                        } else {
49880
                            result.logs.push({
49881
                                level: 'warn',
49882
                                message: 'We\'ve encountered a nal unit without data at ' + i + ' for trackId ' + trackId + '. See mux.js#223.'
49883
                            });
49884
                            break;
49885
                        }
49886
                        result.seiNals.push(seiNal);
49887
                        break;
49888
                }
49889
            }
49890
            return result;
49891
        };
49892
        /**
49893
         * Parses sample information out of Track Run Boxes and calculates
49894
         * the absolute presentation and decode timestamps of each sample.
49895
         *
49896
         * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
49897
         * @param {Number|BigInt} baseMediaDecodeTime - base media decode time from tfdt
49898
         @see ISO-BMFF-12/2015, Section 8.8.12
49899
         * @param {Object} tfhd - The parsed Track Fragment Header
49900
         *   @see inspect.parseTfhd
49901
         * @return {Object[]} the parsed samples
49902
         *
49903
         * @see ISO-BMFF-12/2015, Section 8.8.8
49904
         **/
49905
 
49906
        var parseSamples = function (truns, baseMediaDecodeTime, tfhd) {
49907
            var currentDts = baseMediaDecodeTime;
49908
            var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
49909
            var defaultSampleSize = tfhd.defaultSampleSize || 0;
49910
            var trackId = tfhd.trackId;
49911
            var allSamples = [];
49912
            truns.forEach(function (trun) {
49913
                // Note: We currently do not parse the sample table as well
49914
                // as the trun. It's possible some sources will require this.
49915
                // moov > trak > mdia > minf > stbl
49916
                var trackRun = parseTrun$1(trun);
49917
                var samples = trackRun.samples;
49918
                samples.forEach(function (sample) {
49919
                    if (sample.duration === undefined) {
49920
                        sample.duration = defaultSampleDuration;
49921
                    }
49922
                    if (sample.size === undefined) {
49923
                        sample.size = defaultSampleSize;
49924
                    }
49925
                    sample.trackId = trackId;
49926
                    sample.dts = currentDts;
49927
                    if (sample.compositionTimeOffset === undefined) {
49928
                        sample.compositionTimeOffset = 0;
49929
                    }
49930
                    if (typeof currentDts === 'bigint') {
49931
                        sample.pts = currentDts + window$2.BigInt(sample.compositionTimeOffset);
49932
                        currentDts += window$2.BigInt(sample.duration);
49933
                    } else {
49934
                        sample.pts = currentDts + sample.compositionTimeOffset;
49935
                        currentDts += sample.duration;
49936
                    }
49937
                });
49938
                allSamples = allSamples.concat(samples);
49939
            });
49940
            return allSamples;
49941
        };
49942
        /**
49943
         * Parses out caption nals from an FMP4 segment's video tracks.
49944
         *
49945
         * @param {Uint8Array} segment - The bytes of a single segment
49946
         * @param {Number} videoTrackId - The trackId of a video track in the segment
49947
         * @return {Object.<Number, Object[]>} A mapping of video trackId to
49948
         *   a list of seiNals found in that track
49949
         **/
49950
 
49951
        var parseCaptionNals = function (segment, videoTrackId) {
49952
            // To get the samples
49953
            var trafs = findBox$1(segment, ['moof', 'traf']); // To get SEI NAL units
49954
 
49955
            var mdats = findBox$1(segment, ['mdat']);
49956
            var captionNals = {};
49957
            var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
49958
 
49959
            mdats.forEach(function (mdat, index) {
49960
                var matchingTraf = trafs[index];
49961
                mdatTrafPairs.push({
49962
                    mdat: mdat,
49963
                    traf: matchingTraf
49964
                });
49965
            });
49966
            mdatTrafPairs.forEach(function (pair) {
49967
                var mdat = pair.mdat;
49968
                var traf = pair.traf;
49969
                var tfhd = findBox$1(traf, ['tfhd']); // Exactly 1 tfhd per traf
49970
 
49971
                var headerInfo = parseTfhd$1(tfhd[0]);
49972
                var trackId = headerInfo.trackId;
49973
                var tfdt = findBox$1(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
49974
 
49975
                var baseMediaDecodeTime = tfdt.length > 0 ? parseTfdt$1(tfdt[0]).baseMediaDecodeTime : 0;
49976
                var truns = findBox$1(traf, ['trun']);
49977
                var samples;
49978
                var result; // Only parse video data for the chosen video track
49979
 
49980
                if (videoTrackId === trackId && truns.length > 0) {
49981
                    samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
49982
                    result = findSeiNals(mdat, samples, trackId);
49983
                    if (!captionNals[trackId]) {
49984
                        captionNals[trackId] = {
49985
                            seiNals: [],
49986
                            logs: []
49987
                        };
49988
                    }
49989
                    captionNals[trackId].seiNals = captionNals[trackId].seiNals.concat(result.seiNals);
49990
                    captionNals[trackId].logs = captionNals[trackId].logs.concat(result.logs);
49991
                }
49992
            });
49993
            return captionNals;
49994
        };
49995
        /**
49996
         * Parses out inband captions from an MP4 container and returns
49997
         * caption objects that can be used by WebVTT and the TextTrack API.
49998
         * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
49999
         * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
50000
         * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
50001
         *
50002
         * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
50003
         * @param {Number} trackId - The id of the video track to parse
50004
         * @param {Number} timescale - The timescale for the video track from the init segment
50005
         *
50006
         * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
50007
         * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
50008
         * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
50009
         * @return {Object[]} parsedCaptions[].content - A list of individual caption segments
50010
         * @return {String} parsedCaptions[].content.text - The visible content of the caption segment
50011
         * @return {Number} parsedCaptions[].content.line - The line height from 1-15 for positioning of the caption segment
50012
         * @return {Number} parsedCaptions[].content.position - The column indent percentage for cue positioning from 10-80
50013
         **/
50014
 
50015
        var parseEmbeddedCaptions = function (segment, trackId, timescale) {
50016
            var captionNals; // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
50017
 
50018
            if (trackId === null) {
50019
                return null;
50020
            }
50021
            captionNals = parseCaptionNals(segment, trackId);
50022
            var trackNals = captionNals[trackId] || {};
50023
            return {
50024
                seiNals: trackNals.seiNals,
50025
                logs: trackNals.logs,
50026
                timescale: timescale
50027
            };
50028
        };
50029
        /**
50030
         * Converts SEI NALUs into captions that can be used by video.js
50031
         **/
50032
 
50033
        var CaptionParser = function () {
50034
            var isInitialized = false;
50035
            var captionStream; // Stores segments seen before trackId and timescale are set
50036
 
50037
            var segmentCache; // Stores video track ID of the track being parsed
50038
 
50039
            var trackId; // Stores the timescale of the track being parsed
50040
 
50041
            var timescale; // Stores captions parsed so far
50042
 
50043
            var parsedCaptions; // Stores whether we are receiving partial data or not
50044
 
50045
            var parsingPartial;
50046
            /**
50047
             * A method to indicate whether a CaptionParser has been initalized
50048
             * @returns {Boolean}
50049
             **/
50050
 
50051
            this.isInitialized = function () {
50052
                return isInitialized;
50053
            };
50054
            /**
50055
             * Initializes the underlying CaptionStream, SEI NAL parsing
50056
             * and management, and caption collection
50057
             **/
50058
 
50059
            this.init = function (options) {
50060
                captionStream = new CaptionStream();
50061
                isInitialized = true;
50062
                parsingPartial = options ? options.isPartial : false; // Collect dispatched captions
50063
 
50064
                captionStream.on('data', function (event) {
50065
                    // Convert to seconds in the source's timescale
50066
                    event.startTime = event.startPts / timescale;
50067
                    event.endTime = event.endPts / timescale;
50068
                    parsedCaptions.captions.push(event);
50069
                    parsedCaptions.captionStreams[event.stream] = true;
50070
                });
50071
                captionStream.on('log', function (log) {
50072
                    parsedCaptions.logs.push(log);
50073
                });
50074
            };
50075
            /**
50076
             * Determines if a new video track will be selected
50077
             * or if the timescale changed
50078
             * @return {Boolean}
50079
             **/
50080
 
50081
            this.isNewInit = function (videoTrackIds, timescales) {
50082
                if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
50083
                    return false;
50084
                }
50085
                return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
50086
            };
50087
            /**
50088
             * Parses out SEI captions and interacts with underlying
50089
             * CaptionStream to return dispatched captions
50090
             *
50091
             * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
50092
             * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
50093
             * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
50094
             * @see parseEmbeddedCaptions
50095
             * @see m2ts/caption-stream.js
50096
             **/
50097
 
50098
            this.parse = function (segment, videoTrackIds, timescales) {
50099
                var parsedData;
50100
                if (!this.isInitialized()) {
50101
                    return null; // This is not likely to be a video segment
50102
                } else if (!videoTrackIds || !timescales) {
50103
                    return null;
50104
                } else if (this.isNewInit(videoTrackIds, timescales)) {
50105
                    // Use the first video track only as there is no
50106
                    // mechanism to switch to other video tracks
50107
                    trackId = videoTrackIds[0];
50108
                    timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
50109
                    // data until we have one.
50110
                    // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
50111
                } else if (trackId === null || !timescale) {
50112
                    segmentCache.push(segment);
50113
                    return null;
50114
                } // Now that a timescale and trackId is set, parse cached segments
50115
 
50116
                while (segmentCache.length > 0) {
50117
                    var cachedSegment = segmentCache.shift();
50118
                    this.parse(cachedSegment, videoTrackIds, timescales);
50119
                }
50120
                parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
50121
                if (parsedData && parsedData.logs) {
50122
                    parsedCaptions.logs = parsedCaptions.logs.concat(parsedData.logs);
50123
                }
50124
                if (parsedData === null || !parsedData.seiNals) {
50125
                    if (parsedCaptions.logs.length) {
50126
                        return {
50127
                            logs: parsedCaptions.logs,
50128
                            captions: [],
50129
                            captionStreams: []
50130
                        };
50131
                    }
50132
                    return null;
50133
                }
50134
                this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
50135
 
50136
                this.flushStream();
50137
                return parsedCaptions;
50138
            };
50139
            /**
50140
             * Pushes SEI NALUs onto CaptionStream
50141
             * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
50142
             * Assumes that `parseCaptionNals` has been called first
50143
             * @see m2ts/caption-stream.js
50144
             **/
50145
 
50146
            this.pushNals = function (nals) {
50147
                if (!this.isInitialized() || !nals || nals.length === 0) {
50148
                    return null;
50149
                }
50150
                nals.forEach(function (nal) {
50151
                    captionStream.push(nal);
50152
                });
50153
            };
50154
            /**
50155
             * Flushes underlying CaptionStream to dispatch processed, displayable captions
50156
             * @see m2ts/caption-stream.js
50157
             **/
50158
 
50159
            this.flushStream = function () {
50160
                if (!this.isInitialized()) {
50161
                    return null;
50162
                }
50163
                if (!parsingPartial) {
50164
                    captionStream.flush();
50165
                } else {
50166
                    captionStream.partialFlush();
50167
                }
50168
            };
50169
            /**
50170
             * Reset caption buckets for new data
50171
             **/
50172
 
50173
            this.clearParsedCaptions = function () {
50174
                parsedCaptions.captions = [];
50175
                parsedCaptions.captionStreams = {};
50176
                parsedCaptions.logs = [];
50177
            };
50178
            /**
50179
             * Resets underlying CaptionStream
50180
             * @see m2ts/caption-stream.js
50181
             **/
50182
 
50183
            this.resetCaptionStream = function () {
50184
                if (!this.isInitialized()) {
50185
                    return null;
50186
                }
50187
                captionStream.reset();
50188
            };
50189
            /**
50190
             * Convenience method to clear all captions flushed from the
50191
             * CaptionStream and still being parsed
50192
             * @see m2ts/caption-stream.js
50193
             **/
50194
 
50195
            this.clearAllCaptions = function () {
50196
                this.clearParsedCaptions();
50197
                this.resetCaptionStream();
50198
            };
50199
            /**
50200
             * Reset caption parser
50201
             **/
50202
 
50203
            this.reset = function () {
50204
                segmentCache = [];
50205
                trackId = null;
50206
                timescale = null;
50207
                if (!parsedCaptions) {
50208
                    parsedCaptions = {
50209
                        captions: [],
50210
                        // CC1, CC2, CC3, CC4
50211
                        captionStreams: {},
50212
                        logs: []
50213
                    };
50214
                } else {
50215
                    this.clearParsedCaptions();
50216
                }
50217
                this.resetCaptionStream();
50218
            };
50219
            this.reset();
50220
        };
50221
        var captionParser = CaptionParser;
50222
        /**
50223
         * Returns the first string in the data array ending with a null char '\0'
50224
         * @param {UInt8} data
50225
         * @returns the string with the null char
50226
         */
50227
 
50228
        var uint8ToCString$1 = function (data) {
50229
            var index = 0;
50230
            var curChar = String.fromCharCode(data[index]);
50231
            var retString = '';
50232
            while (curChar !== '\0') {
50233
                retString += curChar;
50234
                index++;
50235
                curChar = String.fromCharCode(data[index]);
50236
            } // Add nullChar
50237
 
50238
            retString += curChar;
50239
            return retString;
50240
        };
50241
        var string = {
50242
            uint8ToCString: uint8ToCString$1
50243
        };
50244
        var uint8ToCString = string.uint8ToCString;
50245
        var getUint64$1 = numbers.getUint64;
50246
        /**
50247
         * Based on: ISO/IEC 23009 Section: 5.10.3.3
50248
         * References:
50249
         * https://dashif-documents.azurewebsites.net/Events/master/event.html#emsg-format
50250
         * https://aomediacodec.github.io/id3-emsg/
50251
         *
50252
         * Takes emsg box data as a uint8 array and returns a emsg box object
50253
         * @param {UInt8Array} boxData data from emsg box
50254
         * @returns A parsed emsg box object
50255
         */
50256
 
50257
        var parseEmsgBox = function (boxData) {
50258
            // version + flags
50259
            var offset = 4;
50260
            var version = boxData[0];
50261
            var scheme_id_uri, value, timescale, presentation_time, presentation_time_delta, event_duration, id, message_data;
50262
            if (version === 0) {
50263
                scheme_id_uri = uint8ToCString(boxData.subarray(offset));
50264
                offset += scheme_id_uri.length;
50265
                value = uint8ToCString(boxData.subarray(offset));
50266
                offset += value.length;
50267
                var dv = new DataView(boxData.buffer);
50268
                timescale = dv.getUint32(offset);
50269
                offset += 4;
50270
                presentation_time_delta = dv.getUint32(offset);
50271
                offset += 4;
50272
                event_duration = dv.getUint32(offset);
50273
                offset += 4;
50274
                id = dv.getUint32(offset);
50275
                offset += 4;
50276
            } else if (version === 1) {
50277
                var dv = new DataView(boxData.buffer);
50278
                timescale = dv.getUint32(offset);
50279
                offset += 4;
50280
                presentation_time = getUint64$1(boxData.subarray(offset));
50281
                offset += 8;
50282
                event_duration = dv.getUint32(offset);
50283
                offset += 4;
50284
                id = dv.getUint32(offset);
50285
                offset += 4;
50286
                scheme_id_uri = uint8ToCString(boxData.subarray(offset));
50287
                offset += scheme_id_uri.length;
50288
                value = uint8ToCString(boxData.subarray(offset));
50289
                offset += value.length;
50290
            }
50291
            message_data = new Uint8Array(boxData.subarray(offset, boxData.byteLength));
50292
            var emsgBox = {
50293
                scheme_id_uri,
50294
                value,
50295
                // if timescale is undefined or 0 set to 1
50296
                timescale: timescale ? timescale : 1,
50297
                presentation_time,
50298
                presentation_time_delta,
50299
                event_duration,
50300
                id,
50301
                message_data
50302
            };
50303
            return isValidEmsgBox(version, emsgBox) ? emsgBox : undefined;
50304
        };
50305
        /**
50306
         * Scales a presentation time or time delta with an offset with a provided timescale
50307
         * @param {number} presentationTime
50308
         * @param {number} timescale
50309
         * @param {number} timeDelta
50310
         * @param {number} offset
50311
         * @returns the scaled time as a number
50312
         */
50313
 
50314
        var scaleTime = function (presentationTime, timescale, timeDelta, offset) {
50315
            return presentationTime || presentationTime === 0 ? presentationTime / timescale : offset + timeDelta / timescale;
50316
        };
50317
        /**
50318
         * Checks the emsg box data for validity based on the version
50319
         * @param {number} version of the emsg box to validate
50320
         * @param {Object} emsg the emsg data to validate
50321
         * @returns if the box is valid as a boolean
50322
         */
50323
 
50324
        var isValidEmsgBox = function (version, emsg) {
50325
            var hasScheme = emsg.scheme_id_uri !== '\0';
50326
            var isValidV0Box = version === 0 && isDefined(emsg.presentation_time_delta) && hasScheme;
50327
            var isValidV1Box = version === 1 && isDefined(emsg.presentation_time) && hasScheme; // Only valid versions of emsg are 0 and 1
50328
 
50329
            return !(version > 1) && isValidV0Box || isValidV1Box;
50330
        }; // Utility function to check if an object is defined
50331
 
50332
        var isDefined = function (data) {
50333
            return data !== undefined || data !== null;
50334
        };
50335
        var emsg$1 = {
50336
            parseEmsgBox: parseEmsgBox,
50337
            scaleTime: scaleTime
50338
        };
50339
        /**
50340
         * mux.js
50341
         *
50342
         * Copyright (c) Brightcove
50343
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
50344
         *
50345
         * Utilities to detect basic properties and metadata about MP4s.
50346
         */
50347
 
50348
        var toUnsigned = bin.toUnsigned;
50349
        var toHexString = bin.toHexString;
50350
        var findBox = findBox_1;
50351
        var parseType$1 = parseType_1;
50352
        var emsg = emsg$1;
50353
        var parseTfhd = parseTfhd$2;
50354
        var parseTrun = parseTrun$2;
50355
        var parseTfdt = parseTfdt$2;
50356
        var getUint64 = numbers.getUint64;
50357
        var timescale, startTime, compositionStartTime, getVideoTrackIds, getTracks, getTimescaleFromMediaHeader, getEmsgID3;
50358
        var window$1 = window_1;
50359
        var parseId3Frames = parseId3.parseId3Frames;
50360
        /**
50361
         * Parses an MP4 initialization segment and extracts the timescale
50362
         * values for any declared tracks. Timescale values indicate the
50363
         * number of clock ticks per second to assume for time-based values
50364
         * elsewhere in the MP4.
50365
         *
50366
         * To determine the start time of an MP4, you need two pieces of
50367
         * information: the timescale unit and the earliest base media decode
50368
         * time. Multiple timescales can be specified within an MP4 but the
50369
         * base media decode time is always expressed in the timescale from
50370
         * the media header box for the track:
50371
         * ```
50372
         * moov > trak > mdia > mdhd.timescale
50373
         * ```
50374
         * @param init {Uint8Array} the bytes of the init segment
50375
         * @return {object} a hash of track ids to timescale values or null if
50376
         * the init segment is malformed.
50377
         */
50378
 
50379
        timescale = function (init) {
50380
            var result = {},
50381
                traks = findBox(init, ['moov', 'trak']); // mdhd timescale
50382
 
50383
            return traks.reduce(function (result, trak) {
50384
                var tkhd, version, index, id, mdhd;
50385
                tkhd = findBox(trak, ['tkhd'])[0];
50386
                if (!tkhd) {
50387
                    return null;
50388
                }
50389
                version = tkhd[0];
50390
                index = version === 0 ? 12 : 20;
50391
                id = toUnsigned(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
50392
                mdhd = findBox(trak, ['mdia', 'mdhd'])[0];
50393
                if (!mdhd) {
50394
                    return null;
50395
                }
50396
                version = mdhd[0];
50397
                index = version === 0 ? 12 : 20;
50398
                result[id] = toUnsigned(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
50399
                return result;
50400
            }, result);
50401
        };
50402
        /**
50403
         * Determine the base media decode start time, in seconds, for an MP4
50404
         * fragment. If multiple fragments are specified, the earliest time is
50405
         * returned.
50406
         *
50407
         * The base media decode time can be parsed from track fragment
50408
         * metadata:
50409
         * ```
50410
         * moof > traf > tfdt.baseMediaDecodeTime
50411
         * ```
50412
         * It requires the timescale value from the mdhd to interpret.
50413
         *
50414
         * @param timescale {object} a hash of track ids to timescale values.
50415
         * @return {number} the earliest base media decode start time for the
50416
         * fragment, in seconds
50417
         */
50418
 
50419
        startTime = function (timescale, fragment) {
50420
            var trafs; // we need info from two childrend of each track fragment box
50421
 
50422
            trafs = findBox(fragment, ['moof', 'traf']); // determine the start times for each track
50423
 
50424
            var lowestTime = trafs.reduce(function (acc, traf) {
50425
                var tfhd = findBox(traf, ['tfhd'])[0]; // get the track id from the tfhd
50426
 
50427
                var id = toUnsigned(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
50428
 
50429
                var scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
50430
 
50431
                var tfdt = findBox(traf, ['tfdt'])[0];
50432
                var dv = new DataView(tfdt.buffer, tfdt.byteOffset, tfdt.byteLength);
50433
                var baseTime; // version 1 is 64 bit
50434
 
50435
                if (tfdt[0] === 1) {
50436
                    baseTime = getUint64(tfdt.subarray(4, 12));
50437
                } else {
50438
                    baseTime = dv.getUint32(4);
50439
                } // convert base time to seconds if it is a valid number.
50440
 
50441
                let seconds;
50442
                if (typeof baseTime === 'bigint') {
50443
                    seconds = baseTime / window$1.BigInt(scale);
50444
                } else if (typeof baseTime === 'number' && !isNaN(baseTime)) {
50445
                    seconds = baseTime / scale;
50446
                }
50447
                if (seconds < Number.MAX_SAFE_INTEGER) {
50448
                    seconds = Number(seconds);
50449
                }
50450
                if (seconds < acc) {
50451
                    acc = seconds;
50452
                }
50453
                return acc;
50454
            }, Infinity);
50455
            return typeof lowestTime === 'bigint' || isFinite(lowestTime) ? lowestTime : 0;
50456
        };
50457
        /**
50458
         * Determine the composition start, in seconds, for an MP4
50459
         * fragment.
50460
         *
50461
         * The composition start time of a fragment can be calculated using the base
50462
         * media decode time, composition time offset, and timescale, as follows:
50463
         *
50464
         * compositionStartTime = (baseMediaDecodeTime + compositionTimeOffset) / timescale
50465
         *
50466
         * All of the aforementioned information is contained within a media fragment's
50467
         * `traf` box, except for timescale info, which comes from the initialization
50468
         * segment, so a track id (also contained within a `traf`) is also necessary to
50469
         * associate it with a timescale
50470
         *
50471
         *
50472
         * @param timescales {object} - a hash of track ids to timescale values.
50473
         * @param fragment {Unit8Array} - the bytes of a media segment
50474
         * @return {number} the composition start time for the fragment, in seconds
50475
         **/
50476
 
50477
        compositionStartTime = function (timescales, fragment) {
50478
            var trafBoxes = findBox(fragment, ['moof', 'traf']);
50479
            var baseMediaDecodeTime = 0;
50480
            var compositionTimeOffset = 0;
50481
            var trackId;
50482
            if (trafBoxes && trafBoxes.length) {
50483
                // The spec states that track run samples contained within a `traf` box are contiguous, but
50484
                // it does not explicitly state whether the `traf` boxes themselves are contiguous.
50485
                // We will assume that they are, so we only need the first to calculate start time.
50486
                var tfhd = findBox(trafBoxes[0], ['tfhd'])[0];
50487
                var trun = findBox(trafBoxes[0], ['trun'])[0];
50488
                var tfdt = findBox(trafBoxes[0], ['tfdt'])[0];
50489
                if (tfhd) {
50490
                    var parsedTfhd = parseTfhd(tfhd);
50491
                    trackId = parsedTfhd.trackId;
50492
                }
50493
                if (tfdt) {
50494
                    var parsedTfdt = parseTfdt(tfdt);
50495
                    baseMediaDecodeTime = parsedTfdt.baseMediaDecodeTime;
50496
                }
50497
                if (trun) {
50498
                    var parsedTrun = parseTrun(trun);
50499
                    if (parsedTrun.samples && parsedTrun.samples.length) {
50500
                        compositionTimeOffset = parsedTrun.samples[0].compositionTimeOffset || 0;
50501
                    }
50502
                }
50503
            } // Get timescale for this specific track. Assume a 90kHz clock if no timescale was
50504
            // specified.
50505
 
50506
            var timescale = timescales[trackId] || 90e3; // return the composition start time, in seconds
50507
 
50508
            if (typeof baseMediaDecodeTime === 'bigint') {
50509
                compositionTimeOffset = window$1.BigInt(compositionTimeOffset);
50510
                timescale = window$1.BigInt(timescale);
50511
            }
50512
            var result = (baseMediaDecodeTime + compositionTimeOffset) / timescale;
50513
            if (typeof result === 'bigint' && result < Number.MAX_SAFE_INTEGER) {
50514
                result = Number(result);
50515
            }
50516
            return result;
50517
        };
50518
        /**
50519
         * Find the trackIds of the video tracks in this source.
50520
         * Found by parsing the Handler Reference and Track Header Boxes:
50521
         *   moov > trak > mdia > hdlr
50522
         *   moov > trak > tkhd
50523
         *
50524
         * @param {Uint8Array} init - The bytes of the init segment for this source
50525
         * @return {Number[]} A list of trackIds
50526
         *
50527
         * @see ISO-BMFF-12/2015, Section 8.4.3
50528
         **/
50529
 
50530
        getVideoTrackIds = function (init) {
50531
            var traks = findBox(init, ['moov', 'trak']);
50532
            var videoTrackIds = [];
50533
            traks.forEach(function (trak) {
50534
                var hdlrs = findBox(trak, ['mdia', 'hdlr']);
50535
                var tkhds = findBox(trak, ['tkhd']);
50536
                hdlrs.forEach(function (hdlr, index) {
50537
                    var handlerType = parseType$1(hdlr.subarray(8, 12));
50538
                    var tkhd = tkhds[index];
50539
                    var view;
50540
                    var version;
50541
                    var trackId;
50542
                    if (handlerType === 'vide') {
50543
                        view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
50544
                        version = view.getUint8(0);
50545
                        trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
50546
                        videoTrackIds.push(trackId);
50547
                    }
50548
                });
50549
            });
50550
            return videoTrackIds;
50551
        };
50552
        getTimescaleFromMediaHeader = function (mdhd) {
50553
            // mdhd is a FullBox, meaning it will have its own version as the first byte
50554
            var version = mdhd[0];
50555
            var index = version === 0 ? 12 : 20;
50556
            return toUnsigned(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
50557
        };
50558
        /**
50559
         * Get all the video, audio, and hint tracks from a non fragmented
50560
         * mp4 segment
50561
         */
50562
 
50563
        getTracks = function (init) {
50564
            var traks = findBox(init, ['moov', 'trak']);
50565
            var tracks = [];
50566
            traks.forEach(function (trak) {
50567
                var track = {};
50568
                var tkhd = findBox(trak, ['tkhd'])[0];
50569
                var view, tkhdVersion; // id
50570
 
50571
                if (tkhd) {
50572
                    view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
50573
                    tkhdVersion = view.getUint8(0);
50574
                    track.id = tkhdVersion === 0 ? view.getUint32(12) : view.getUint32(20);
50575
                }
50576
                var hdlr = findBox(trak, ['mdia', 'hdlr'])[0]; // type
50577
 
50578
                if (hdlr) {
50579
                    var type = parseType$1(hdlr.subarray(8, 12));
50580
                    if (type === 'vide') {
50581
                        track.type = 'video';
50582
                    } else if (type === 'soun') {
50583
                        track.type = 'audio';
50584
                    } else {
50585
                        track.type = type;
50586
                    }
50587
                } // codec
50588
 
50589
                var stsd = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
50590
                if (stsd) {
50591
                    var sampleDescriptions = stsd.subarray(8); // gives the codec type string
50592
 
50593
                    track.codec = parseType$1(sampleDescriptions.subarray(4, 8));
50594
                    var codecBox = findBox(sampleDescriptions, [track.codec])[0];
50595
                    var codecConfig, codecConfigType;
50596
                    if (codecBox) {
50597
                        // https://tools.ietf.org/html/rfc6381#section-3.3
50598
                        if (/^[asm]vc[1-9]$/i.test(track.codec)) {
50599
                            // we don't need anything but the "config" parameter of the
50600
                            // avc1 codecBox
50601
                            codecConfig = codecBox.subarray(78);
50602
                            codecConfigType = parseType$1(codecConfig.subarray(4, 8));
50603
                            if (codecConfigType === 'avcC' && codecConfig.length > 11) {
50604
                                track.codec += '.'; // left padded with zeroes for single digit hex
50605
                                // profile idc
50606
 
50607
                                track.codec += toHexString(codecConfig[9]); // the byte containing the constraint_set flags
50608
 
50609
                                track.codec += toHexString(codecConfig[10]); // level idc
50610
 
50611
                                track.codec += toHexString(codecConfig[11]);
50612
                            } else {
50613
                                // TODO: show a warning that we couldn't parse the codec
50614
                                // and are using the default
50615
                                track.codec = 'avc1.4d400d';
50616
                            }
50617
                        } else if (/^mp4[a,v]$/i.test(track.codec)) {
50618
                            // we do not need anything but the streamDescriptor of the mp4a codecBox
50619
                            codecConfig = codecBox.subarray(28);
50620
                            codecConfigType = parseType$1(codecConfig.subarray(4, 8));
50621
                            if (codecConfigType === 'esds' && codecConfig.length > 20 && codecConfig[19] !== 0) {
50622
                                track.codec += '.' + toHexString(codecConfig[19]); // this value is only a single digit
50623
 
50624
                                track.codec += '.' + toHexString(codecConfig[20] >>> 2 & 0x3f).replace(/^0/, '');
50625
                            } else {
50626
                                // TODO: show a warning that we couldn't parse the codec
50627
                                // and are using the default
50628
                                track.codec = 'mp4a.40.2';
50629
                            }
50630
                        } else {
50631
                            // flac, opus, etc
50632
                            track.codec = track.codec.toLowerCase();
50633
                        }
50634
                    }
50635
                }
50636
                var mdhd = findBox(trak, ['mdia', 'mdhd'])[0];
50637
                if (mdhd) {
50638
                    track.timescale = getTimescaleFromMediaHeader(mdhd);
50639
                }
50640
                tracks.push(track);
50641
            });
50642
            return tracks;
50643
        };
50644
        /**
50645
         * Returns an array of emsg ID3 data from the provided segmentData.
50646
         * An offset can also be provided as the Latest Arrival Time to calculate
50647
         * the Event Start Time of v0 EMSG boxes.
50648
         * See: https://dashif-documents.azurewebsites.net/Events/master/event.html#Inband-event-timing
50649
         *
50650
         * @param {Uint8Array} segmentData the segment byte array.
50651
         * @param {number} offset the segment start time or Latest Arrival Time,
50652
         * @return {Object[]} an array of ID3 parsed from EMSG boxes
50653
         */
50654
 
50655
        getEmsgID3 = function (segmentData, offset = 0) {
50656
            var emsgBoxes = findBox(segmentData, ['emsg']);
50657
            return emsgBoxes.map(data => {
50658
                var parsedBox = emsg.parseEmsgBox(new Uint8Array(data));
50659
                var parsedId3Frames = parseId3Frames(parsedBox.message_data);
50660
                return {
50661
                    cueTime: emsg.scaleTime(parsedBox.presentation_time, parsedBox.timescale, parsedBox.presentation_time_delta, offset),
50662
                    duration: emsg.scaleTime(parsedBox.event_duration, parsedBox.timescale),
50663
                    frames: parsedId3Frames
50664
                };
50665
            });
50666
        };
50667
        var probe$2 = {
50668
            // export mp4 inspector's findBox and parseType for backwards compatibility
50669
            findBox: findBox,
50670
            parseType: parseType$1,
50671
            timescale: timescale,
50672
            startTime: startTime,
50673
            compositionStartTime: compositionStartTime,
50674
            videoTrackIds: getVideoTrackIds,
50675
            tracks: getTracks,
50676
            getTimescaleFromMediaHeader: getTimescaleFromMediaHeader,
50677
            getEmsgID3: getEmsgID3
50678
        };
50679
        /**
50680
         * mux.js
50681
         *
50682
         * Copyright (c) Brightcove
50683
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
50684
         *
50685
         * Utilities to detect basic properties and metadata about TS Segments.
50686
         */
50687
 
50688
        var StreamTypes$1 = streamTypes;
50689
        var parsePid = function (packet) {
50690
            var pid = packet[1] & 0x1f;
50691
            pid <<= 8;
50692
            pid |= packet[2];
50693
            return pid;
50694
        };
50695
        var parsePayloadUnitStartIndicator = function (packet) {
50696
            return !!(packet[1] & 0x40);
50697
        };
50698
        var parseAdaptionField = function (packet) {
50699
            var offset = 0; // if an adaption field is present, its length is specified by the
50700
            // fifth byte of the TS packet header. The adaptation field is
50701
            // used to add stuffing to PES packets that don't fill a complete
50702
            // TS packet, and to specify some forms of timing and control data
50703
            // that we do not currently use.
50704
 
50705
            if ((packet[3] & 0x30) >>> 4 > 0x01) {
50706
                offset += packet[4] + 1;
50707
            }
50708
            return offset;
50709
        };
50710
        var parseType = function (packet, pmtPid) {
50711
            var pid = parsePid(packet);
50712
            if (pid === 0) {
50713
                return 'pat';
50714
            } else if (pid === pmtPid) {
50715
                return 'pmt';
50716
            } else if (pmtPid) {
50717
                return 'pes';
50718
            }
50719
            return null;
50720
        };
50721
        var parsePat = function (packet) {
50722
            var pusi = parsePayloadUnitStartIndicator(packet);
50723
            var offset = 4 + parseAdaptionField(packet);
50724
            if (pusi) {
50725
                offset += packet[offset] + 1;
50726
            }
50727
            return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11];
50728
        };
50729
        var parsePmt = function (packet) {
50730
            var programMapTable = {};
50731
            var pusi = parsePayloadUnitStartIndicator(packet);
50732
            var payloadOffset = 4 + parseAdaptionField(packet);
50733
            if (pusi) {
50734
                payloadOffset += packet[payloadOffset] + 1;
50735
            } // PMTs can be sent ahead of the time when they should actually
50736
            // take effect. We don't believe this should ever be the case
50737
            // for HLS but we'll ignore "forward" PMT declarations if we see
50738
            // them. Future PMT declarations have the current_next_indicator
50739
            // set to zero.
50740
 
50741
            if (!(packet[payloadOffset + 5] & 0x01)) {
50742
                return;
50743
            }
50744
            var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section
50745
 
50746
            sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2];
50747
            tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
50748
            // long the program info descriptors are
50749
 
50750
            programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table
50751
 
50752
            var offset = 12 + programInfoLength;
50753
            while (offset < tableEnd) {
50754
                var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type
50755
 
50756
                programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry
50757
                // skip past the elementary stream descriptors, if present
50758
 
50759
                offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5;
50760
            }
50761
            return programMapTable;
50762
        };
50763
        var parsePesType = function (packet, programMapTable) {
50764
            var pid = parsePid(packet);
50765
            var type = programMapTable[pid];
50766
            switch (type) {
50767
                case StreamTypes$1.H264_STREAM_TYPE:
50768
                    return 'video';
50769
                case StreamTypes$1.ADTS_STREAM_TYPE:
50770
                    return 'audio';
50771
                case StreamTypes$1.METADATA_STREAM_TYPE:
50772
                    return 'timed-metadata';
50773
                default:
50774
                    return null;
50775
            }
50776
        };
50777
        var parsePesTime = function (packet) {
50778
            var pusi = parsePayloadUnitStartIndicator(packet);
50779
            if (!pusi) {
50780
                return null;
50781
            }
50782
            var offset = 4 + parseAdaptionField(packet);
50783
            if (offset >= packet.byteLength) {
50784
                // From the H 222.0 MPEG-TS spec
50785
                // "For transport stream packets carrying PES packets, stuffing is needed when there
50786
                //  is insufficient PES packet data to completely fill the transport stream packet
50787
                //  payload bytes. Stuffing is accomplished by defining an adaptation field longer than
50788
                //  the sum of the lengths of the data elements in it, so that the payload bytes
50789
                //  remaining after the adaptation field exactly accommodates the available PES packet
50790
                //  data."
50791
                //
50792
                // If the offset is >= the length of the packet, then the packet contains no data
50793
                // and instead is just adaption field stuffing bytes
50794
                return null;
50795
            }
50796
            var pes = null;
50797
            var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value
50798
            // and a DTS value. Determine what combination of values is
50799
            // available to work with.
50800
 
50801
            ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
50802
            // performs all bitwise operations on 32-bit integers but javascript
50803
            // supports a much greater range (52-bits) of integer using standard
50804
            // mathematical operations.
50805
            // We construct a 31-bit value using bitwise operators over the 31
50806
            // most significant bits and then multiply by 4 (equal to a left-shift
50807
            // of 2) before we add the final 2 least significant bits of the
50808
            // timestamp (equal to an OR.)
50809
 
50810
            if (ptsDtsFlags & 0xC0) {
50811
                pes = {}; // the PTS and DTS are not written out directly. For information
50812
                // on how they are encoded, see
50813
                // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
50814
 
50815
                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;
50816
                pes.pts *= 4; // Left shift by 2
50817
 
50818
                pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
50819
 
50820
                pes.dts = pes.pts;
50821
                if (ptsDtsFlags & 0x40) {
50822
                    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;
50823
                    pes.dts *= 4; // Left shift by 2
50824
 
50825
                    pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
50826
                }
50827
            }
50828
 
50829
            return pes;
50830
        };
50831
        var parseNalUnitType = function (type) {
50832
            switch (type) {
50833
                case 0x05:
50834
                    return 'slice_layer_without_partitioning_rbsp_idr';
50835
                case 0x06:
50836
                    return 'sei_rbsp';
50837
                case 0x07:
50838
                    return 'seq_parameter_set_rbsp';
50839
                case 0x08:
50840
                    return 'pic_parameter_set_rbsp';
50841
                case 0x09:
50842
                    return 'access_unit_delimiter_rbsp';
50843
                default:
50844
                    return null;
50845
            }
50846
        };
50847
        var videoPacketContainsKeyFrame = function (packet) {
50848
            var offset = 4 + parseAdaptionField(packet);
50849
            var frameBuffer = packet.subarray(offset);
50850
            var frameI = 0;
50851
            var frameSyncPoint = 0;
50852
            var foundKeyFrame = false;
50853
            var nalType; // advance the sync point to a NAL start, if necessary
50854
 
50855
            for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
50856
                if (frameBuffer[frameSyncPoint + 2] === 1) {
50857
                    // the sync point is properly aligned
50858
                    frameI = frameSyncPoint + 5;
50859
                    break;
50860
                }
50861
            }
50862
            while (frameI < frameBuffer.byteLength) {
50863
                // look at the current byte to determine if we've hit the end of
50864
                // a NAL unit boundary
50865
                switch (frameBuffer[frameI]) {
50866
                    case 0:
50867
                        // skip past non-sync sequences
50868
                        if (frameBuffer[frameI - 1] !== 0) {
50869
                            frameI += 2;
50870
                            break;
50871
                        } else if (frameBuffer[frameI - 2] !== 0) {
50872
                            frameI++;
50873
                            break;
50874
                        }
50875
                        if (frameSyncPoint + 3 !== frameI - 2) {
50876
                            nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
50877
                            if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
50878
                                foundKeyFrame = true;
50879
                            }
50880
                        } // drop trailing zeroes
50881
 
50882
                        do {
50883
                            frameI++;
50884
                        } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
50885
                        frameSyncPoint = frameI - 2;
50886
                        frameI += 3;
50887
                        break;
50888
                    case 1:
50889
                        // skip past non-sync sequences
50890
                        if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
50891
                            frameI += 3;
50892
                            break;
50893
                        }
50894
                        nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
50895
                        if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
50896
                            foundKeyFrame = true;
50897
                        }
50898
                        frameSyncPoint = frameI - 2;
50899
                        frameI += 3;
50900
                        break;
50901
                    default:
50902
                        // the current byte isn't a one or zero, so it cannot be part
50903
                        // of a sync sequence
50904
                        frameI += 3;
50905
                        break;
50906
                }
50907
            }
50908
            frameBuffer = frameBuffer.subarray(frameSyncPoint);
50909
            frameI -= frameSyncPoint;
50910
            frameSyncPoint = 0; // parse the final nal
50911
 
50912
            if (frameBuffer && frameBuffer.byteLength > 3) {
50913
                nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
50914
                if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
50915
                    foundKeyFrame = true;
50916
                }
50917
            }
50918
            return foundKeyFrame;
50919
        };
50920
        var probe$1 = {
50921
            parseType: parseType,
50922
            parsePat: parsePat,
50923
            parsePmt: parsePmt,
50924
            parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
50925
            parsePesType: parsePesType,
50926
            parsePesTime: parsePesTime,
50927
            videoPacketContainsKeyFrame: videoPacketContainsKeyFrame
50928
        };
50929
        /**
50930
         * mux.js
50931
         *
50932
         * Copyright (c) Brightcove
50933
         * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
50934
         *
50935
         * Parse mpeg2 transport stream packets to extract basic timing information
50936
         */
50937
 
50938
        var StreamTypes = streamTypes;
50939
        var handleRollover = timestampRolloverStream.handleRollover;
50940
        var probe = {};
50941
        probe.ts = probe$1;
50942
        probe.aac = utils;
50943
        var ONE_SECOND_IN_TS = clock$2.ONE_SECOND_IN_TS;
50944
        var MP2T_PACKET_LENGTH = 188,
50945
            // bytes
50946
            SYNC_BYTE = 0x47;
50947
        /**
50948
         * walks through segment data looking for pat and pmt packets to parse out
50949
         * program map table information
50950
         */
50951
 
50952
        var parsePsi_ = function (bytes, pmt) {
50953
            var startIndex = 0,
50954
                endIndex = MP2T_PACKET_LENGTH,
50955
                packet,
50956
                type;
50957
            while (endIndex < bytes.byteLength) {
50958
                // Look for a pair of start and end sync bytes in the data..
50959
                if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
50960
                    // We found a packet
50961
                    packet = bytes.subarray(startIndex, endIndex);
50962
                    type = probe.ts.parseType(packet, pmt.pid);
50963
                    switch (type) {
50964
                        case 'pat':
50965
                            pmt.pid = probe.ts.parsePat(packet);
50966
                            break;
50967
                        case 'pmt':
50968
                            var table = probe.ts.parsePmt(packet);
50969
                            pmt.table = pmt.table || {};
50970
                            Object.keys(table).forEach(function (key) {
50971
                                pmt.table[key] = table[key];
50972
                            });
50973
                            break;
50974
                    }
50975
                    startIndex += MP2T_PACKET_LENGTH;
50976
                    endIndex += MP2T_PACKET_LENGTH;
50977
                    continue;
50978
                } // If we get here, we have somehow become de-synchronized and we need to step
50979
                // forward one byte at a time until we find a pair of sync bytes that denote
50980
                // a packet
50981
 
50982
                startIndex++;
50983
                endIndex++;
50984
            }
50985
        };
50986
        /**
50987
         * walks through the segment data from the start and end to get timing information
50988
         * for the first and last audio pes packets
50989
         */
50990
 
50991
        var parseAudioPes_ = function (bytes, pmt, result) {
50992
            var startIndex = 0,
50993
                endIndex = MP2T_PACKET_LENGTH,
50994
                packet,
50995
                type,
50996
                pesType,
50997
                pusi,
50998
                parsed;
50999
            var endLoop = false; // Start walking from start of segment to get first audio packet
51000
 
51001
            while (endIndex <= bytes.byteLength) {
51002
                // Look for a pair of start and end sync bytes in the data..
51003
                if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
51004
                    // We found a packet
51005
                    packet = bytes.subarray(startIndex, endIndex);
51006
                    type = probe.ts.parseType(packet, pmt.pid);
51007
                    switch (type) {
51008
                        case 'pes':
51009
                            pesType = probe.ts.parsePesType(packet, pmt.table);
51010
                            pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
51011
                            if (pesType === 'audio' && pusi) {
51012
                                parsed = probe.ts.parsePesTime(packet);
51013
                                if (parsed) {
51014
                                    parsed.type = 'audio';
51015
                                    result.audio.push(parsed);
51016
                                    endLoop = true;
51017
                                }
51018
                            }
51019
                            break;
51020
                    }
51021
                    if (endLoop) {
51022
                        break;
51023
                    }
51024
                    startIndex += MP2T_PACKET_LENGTH;
51025
                    endIndex += MP2T_PACKET_LENGTH;
51026
                    continue;
51027
                } // If we get here, we have somehow become de-synchronized and we need to step
51028
                // forward one byte at a time until we find a pair of sync bytes that denote
51029
                // a packet
51030
 
51031
                startIndex++;
51032
                endIndex++;
51033
            } // Start walking from end of segment to get last audio packet
51034
 
51035
            endIndex = bytes.byteLength;
51036
            startIndex = endIndex - MP2T_PACKET_LENGTH;
51037
            endLoop = false;
51038
            while (startIndex >= 0) {
51039
                // Look for a pair of start and end sync bytes in the data..
51040
                if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
51041
                    // We found a packet
51042
                    packet = bytes.subarray(startIndex, endIndex);
51043
                    type = probe.ts.parseType(packet, pmt.pid);
51044
                    switch (type) {
51045
                        case 'pes':
51046
                            pesType = probe.ts.parsePesType(packet, pmt.table);
51047
                            pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
51048
                            if (pesType === 'audio' && pusi) {
51049
                                parsed = probe.ts.parsePesTime(packet);
51050
                                if (parsed) {
51051
                                    parsed.type = 'audio';
51052
                                    result.audio.push(parsed);
51053
                                    endLoop = true;
51054
                                }
51055
                            }
51056
                            break;
51057
                    }
51058
                    if (endLoop) {
51059
                        break;
51060
                    }
51061
                    startIndex -= MP2T_PACKET_LENGTH;
51062
                    endIndex -= MP2T_PACKET_LENGTH;
51063
                    continue;
51064
                } // If we get here, we have somehow become de-synchronized and we need to step
51065
                // forward one byte at a time until we find a pair of sync bytes that denote
51066
                // a packet
51067
 
51068
                startIndex--;
51069
                endIndex--;
51070
            }
51071
        };
51072
        /**
51073
         * walks through the segment data from the start and end to get timing information
51074
         * for the first and last video pes packets as well as timing information for the first
51075
         * key frame.
51076
         */
51077
 
51078
        var parseVideoPes_ = function (bytes, pmt, result) {
51079
            var startIndex = 0,
51080
                endIndex = MP2T_PACKET_LENGTH,
51081
                packet,
51082
                type,
51083
                pesType,
51084
                pusi,
51085
                parsed,
51086
                frame,
51087
                i,
51088
                pes;
51089
            var endLoop = false;
51090
            var currentFrame = {
51091
                data: [],
51092
                size: 0
51093
            }; // Start walking from start of segment to get first video packet
51094
 
51095
            while (endIndex < bytes.byteLength) {
51096
                // Look for a pair of start and end sync bytes in the data..
51097
                if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
51098
                    // We found a packet
51099
                    packet = bytes.subarray(startIndex, endIndex);
51100
                    type = probe.ts.parseType(packet, pmt.pid);
51101
                    switch (type) {
51102
                        case 'pes':
51103
                            pesType = probe.ts.parsePesType(packet, pmt.table);
51104
                            pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
51105
                            if (pesType === 'video') {
51106
                                if (pusi && !endLoop) {
51107
                                    parsed = probe.ts.parsePesTime(packet);
51108
                                    if (parsed) {
51109
                                        parsed.type = 'video';
51110
                                        result.video.push(parsed);
51111
                                        endLoop = true;
51112
                                    }
51113
                                }
51114
                                if (!result.firstKeyFrame) {
51115
                                    if (pusi) {
51116
                                        if (currentFrame.size !== 0) {
51117
                                            frame = new Uint8Array(currentFrame.size);
51118
                                            i = 0;
51119
                                            while (currentFrame.data.length) {
51120
                                                pes = currentFrame.data.shift();
51121
                                                frame.set(pes, i);
51122
                                                i += pes.byteLength;
51123
                                            }
51124
                                            if (probe.ts.videoPacketContainsKeyFrame(frame)) {
51125
                                                var firstKeyFrame = probe.ts.parsePesTime(frame); // PTS/DTS may not be available. Simply *not* setting
51126
                                                // the keyframe seems to work fine with HLS playback
51127
                                                // and definitely preferable to a crash with TypeError...
51128
 
51129
                                                if (firstKeyFrame) {
51130
                                                    result.firstKeyFrame = firstKeyFrame;
51131
                                                    result.firstKeyFrame.type = 'video';
51132
                                                } else {
51133
                                                    // eslint-disable-next-line
51134
                                                    console.warn('Failed to extract PTS/DTS from PES at first keyframe. ' + 'This could be an unusual TS segment, or else mux.js did not ' + 'parse your TS segment correctly. If you know your TS ' + 'segments do contain PTS/DTS on keyframes please file a bug ' + 'report! You can try ffprobe to double check for yourself.');
51135
                                                }
51136
                                            }
51137
                                            currentFrame.size = 0;
51138
                                        }
51139
                                    }
51140
                                    currentFrame.data.push(packet);
51141
                                    currentFrame.size += packet.byteLength;
51142
                                }
51143
                            }
51144
                            break;
51145
                    }
51146
                    if (endLoop && result.firstKeyFrame) {
51147
                        break;
51148
                    }
51149
                    startIndex += MP2T_PACKET_LENGTH;
51150
                    endIndex += MP2T_PACKET_LENGTH;
51151
                    continue;
51152
                } // If we get here, we have somehow become de-synchronized and we need to step
51153
                // forward one byte at a time until we find a pair of sync bytes that denote
51154
                // a packet
51155
 
51156
                startIndex++;
51157
                endIndex++;
51158
            } // Start walking from end of segment to get last video packet
51159
 
51160
            endIndex = bytes.byteLength;
51161
            startIndex = endIndex - MP2T_PACKET_LENGTH;
51162
            endLoop = false;
51163
            while (startIndex >= 0) {
51164
                // Look for a pair of start and end sync bytes in the data..
51165
                if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
51166
                    // We found a packet
51167
                    packet = bytes.subarray(startIndex, endIndex);
51168
                    type = probe.ts.parseType(packet, pmt.pid);
51169
                    switch (type) {
51170
                        case 'pes':
51171
                            pesType = probe.ts.parsePesType(packet, pmt.table);
51172
                            pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
51173
                            if (pesType === 'video' && pusi) {
51174
                                parsed = probe.ts.parsePesTime(packet);
51175
                                if (parsed) {
51176
                                    parsed.type = 'video';
51177
                                    result.video.push(parsed);
51178
                                    endLoop = true;
51179
                                }
51180
                            }
51181
                            break;
51182
                    }
51183
                    if (endLoop) {
51184
                        break;
51185
                    }
51186
                    startIndex -= MP2T_PACKET_LENGTH;
51187
                    endIndex -= MP2T_PACKET_LENGTH;
51188
                    continue;
51189
                } // If we get here, we have somehow become de-synchronized and we need to step
51190
                // forward one byte at a time until we find a pair of sync bytes that denote
51191
                // a packet
51192
 
51193
                startIndex--;
51194
                endIndex--;
51195
            }
51196
        };
51197
        /**
51198
         * Adjusts the timestamp information for the segment to account for
51199
         * rollover and convert to seconds based on pes packet timescale (90khz clock)
51200
         */
51201
 
51202
        var adjustTimestamp_ = function (segmentInfo, baseTimestamp) {
51203
            if (segmentInfo.audio && segmentInfo.audio.length) {
51204
                var audioBaseTimestamp = baseTimestamp;
51205
                if (typeof audioBaseTimestamp === 'undefined' || isNaN(audioBaseTimestamp)) {
51206
                    audioBaseTimestamp = segmentInfo.audio[0].dts;
51207
                }
51208
                segmentInfo.audio.forEach(function (info) {
51209
                    info.dts = handleRollover(info.dts, audioBaseTimestamp);
51210
                    info.pts = handleRollover(info.pts, audioBaseTimestamp); // time in seconds
51211
 
51212
                    info.dtsTime = info.dts / ONE_SECOND_IN_TS;
51213
                    info.ptsTime = info.pts / ONE_SECOND_IN_TS;
51214
                });
51215
            }
51216
            if (segmentInfo.video && segmentInfo.video.length) {
51217
                var videoBaseTimestamp = baseTimestamp;
51218
                if (typeof videoBaseTimestamp === 'undefined' || isNaN(videoBaseTimestamp)) {
51219
                    videoBaseTimestamp = segmentInfo.video[0].dts;
51220
                }
51221
                segmentInfo.video.forEach(function (info) {
51222
                    info.dts = handleRollover(info.dts, videoBaseTimestamp);
51223
                    info.pts = handleRollover(info.pts, videoBaseTimestamp); // time in seconds
51224
 
51225
                    info.dtsTime = info.dts / ONE_SECOND_IN_TS;
51226
                    info.ptsTime = info.pts / ONE_SECOND_IN_TS;
51227
                });
51228
                if (segmentInfo.firstKeyFrame) {
51229
                    var frame = segmentInfo.firstKeyFrame;
51230
                    frame.dts = handleRollover(frame.dts, videoBaseTimestamp);
51231
                    frame.pts = handleRollover(frame.pts, videoBaseTimestamp); // time in seconds
51232
 
51233
                    frame.dtsTime = frame.dts / ONE_SECOND_IN_TS;
51234
                    frame.ptsTime = frame.pts / ONE_SECOND_IN_TS;
51235
                }
51236
            }
51237
        };
51238
        /**
51239
         * inspects the aac data stream for start and end time information
51240
         */
51241
 
51242
        var inspectAac_ = function (bytes) {
51243
            var endLoop = false,
51244
                audioCount = 0,
51245
                sampleRate = null,
51246
                timestamp = null,
51247
                frameSize = 0,
51248
                byteIndex = 0,
51249
                packet;
51250
            while (bytes.length - byteIndex >= 3) {
51251
                var type = probe.aac.parseType(bytes, byteIndex);
51252
                switch (type) {
51253
                    case 'timed-metadata':
51254
                        // Exit early because we don't have enough to parse
51255
                        // the ID3 tag header
51256
                        if (bytes.length - byteIndex < 10) {
51257
                            endLoop = true;
51258
                            break;
51259
                        }
51260
                        frameSize = probe.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
51261
                        // to emit a full packet
51262
 
51263
                        if (frameSize > bytes.length) {
51264
                            endLoop = true;
51265
                            break;
51266
                        }
51267
                        if (timestamp === null) {
51268
                            packet = bytes.subarray(byteIndex, byteIndex + frameSize);
51269
                            timestamp = probe.aac.parseAacTimestamp(packet);
51270
                        }
51271
                        byteIndex += frameSize;
51272
                        break;
51273
                    case 'audio':
51274
                        // Exit early because we don't have enough to parse
51275
                        // the ADTS frame header
51276
                        if (bytes.length - byteIndex < 7) {
51277
                            endLoop = true;
51278
                            break;
51279
                        }
51280
                        frameSize = probe.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
51281
                        // to emit a full packet
51282
 
51283
                        if (frameSize > bytes.length) {
51284
                            endLoop = true;
51285
                            break;
51286
                        }
51287
                        if (sampleRate === null) {
51288
                            packet = bytes.subarray(byteIndex, byteIndex + frameSize);
51289
                            sampleRate = probe.aac.parseSampleRate(packet);
51290
                        }
51291
                        audioCount++;
51292
                        byteIndex += frameSize;
51293
                        break;
51294
                    default:
51295
                        byteIndex++;
51296
                        break;
51297
                }
51298
                if (endLoop) {
51299
                    return null;
51300
                }
51301
            }
51302
            if (sampleRate === null || timestamp === null) {
51303
                return null;
51304
            }
51305
            var audioTimescale = ONE_SECOND_IN_TS / sampleRate;
51306
            var result = {
51307
                audio: [{
51308
                    type: 'audio',
51309
                    dts: timestamp,
51310
                    pts: timestamp
51311
                }, {
51312
                    type: 'audio',
51313
                    dts: timestamp + audioCount * 1024 * audioTimescale,
51314
                    pts: timestamp + audioCount * 1024 * audioTimescale
51315
                }]
51316
            };
51317
            return result;
51318
        };
51319
        /**
51320
         * inspects the transport stream segment data for start and end time information
51321
         * of the audio and video tracks (when present) as well as the first key frame's
51322
         * start time.
51323
         */
51324
 
51325
        var inspectTs_ = function (bytes) {
51326
            var pmt = {
51327
                pid: null,
51328
                table: null
51329
            };
51330
            var result = {};
51331
            parsePsi_(bytes, pmt);
51332
            for (var pid in pmt.table) {
51333
                if (pmt.table.hasOwnProperty(pid)) {
51334
                    var type = pmt.table[pid];
51335
                    switch (type) {
51336
                        case StreamTypes.H264_STREAM_TYPE:
51337
                            result.video = [];
51338
                            parseVideoPes_(bytes, pmt, result);
51339
                            if (result.video.length === 0) {
51340
                                delete result.video;
51341
                            }
51342
                            break;
51343
                        case StreamTypes.ADTS_STREAM_TYPE:
51344
                            result.audio = [];
51345
                            parseAudioPes_(bytes, pmt, result);
51346
                            if (result.audio.length === 0) {
51347
                                delete result.audio;
51348
                            }
51349
                            break;
51350
                    }
51351
                }
51352
            }
51353
            return result;
51354
        };
51355
        /**
51356
         * Inspects segment byte data and returns an object with start and end timing information
51357
         *
51358
         * @param {Uint8Array} bytes The segment byte data
51359
         * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame
51360
         *  timestamps for rollover. This value must be in 90khz clock.
51361
         * @return {Object} Object containing start and end frame timing info of segment.
51362
         */
51363
 
51364
        var inspect = function (bytes, baseTimestamp) {
51365
            var isAacData = probe.aac.isLikelyAacData(bytes);
51366
            var result;
51367
            if (isAacData) {
51368
                result = inspectAac_(bytes);
51369
            } else {
51370
                result = inspectTs_(bytes);
51371
            }
51372
            if (!result || !result.audio && !result.video) {
51373
                return null;
51374
            }
51375
            adjustTimestamp_(result, baseTimestamp);
51376
            return result;
51377
        };
51378
        var tsInspector = {
51379
            inspect: inspect,
51380
            parseAudioPes_: parseAudioPes_
51381
        };
51382
        /* global self */
51383
 
51384
        /**
51385
         * Re-emits transmuxer events by converting them into messages to the
51386
         * world outside the worker.
51387
         *
51388
         * @param {Object} transmuxer the transmuxer to wire events on
51389
         * @private
51390
         */
51391
 
51392
        const wireTransmuxerEvents = function (self, transmuxer) {
51393
            transmuxer.on('data', function (segment) {
51394
                // transfer ownership of the underlying ArrayBuffer
51395
                // instead of doing a copy to save memory
51396
                // ArrayBuffers are transferable but generic TypedArrays are not
51397
                // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
51398
                const initArray = segment.initSegment;
51399
                segment.initSegment = {
51400
                    data: initArray.buffer,
51401
                    byteOffset: initArray.byteOffset,
51402
                    byteLength: initArray.byteLength
51403
                };
51404
                const typedArray = segment.data;
51405
                segment.data = typedArray.buffer;
51406
                self.postMessage({
51407
                    action: 'data',
51408
                    segment,
51409
                    byteOffset: typedArray.byteOffset,
51410
                    byteLength: typedArray.byteLength
51411
                }, [segment.data]);
51412
            });
51413
            transmuxer.on('done', function (data) {
51414
                self.postMessage({
51415
                    action: 'done'
51416
                });
51417
            });
51418
            transmuxer.on('gopInfo', function (gopInfo) {
51419
                self.postMessage({
51420
                    action: 'gopInfo',
51421
                    gopInfo
51422
                });
51423
            });
51424
            transmuxer.on('videoSegmentTimingInfo', function (timingInfo) {
51425
                const videoSegmentTimingInfo = {
51426
                    start: {
51427
                        decode: clock$2.videoTsToSeconds(timingInfo.start.dts),
51428
                        presentation: clock$2.videoTsToSeconds(timingInfo.start.pts)
51429
                    },
51430
                    end: {
51431
                        decode: clock$2.videoTsToSeconds(timingInfo.end.dts),
51432
                        presentation: clock$2.videoTsToSeconds(timingInfo.end.pts)
51433
                    },
51434
                    baseMediaDecodeTime: clock$2.videoTsToSeconds(timingInfo.baseMediaDecodeTime)
51435
                };
51436
                if (timingInfo.prependedContentDuration) {
51437
                    videoSegmentTimingInfo.prependedContentDuration = clock$2.videoTsToSeconds(timingInfo.prependedContentDuration);
51438
                }
51439
                self.postMessage({
51440
                    action: 'videoSegmentTimingInfo',
51441
                    videoSegmentTimingInfo
51442
                });
51443
            });
51444
            transmuxer.on('audioSegmentTimingInfo', function (timingInfo) {
51445
                // Note that all times for [audio/video]SegmentTimingInfo events are in video clock
51446
                const audioSegmentTimingInfo = {
51447
                    start: {
51448
                        decode: clock$2.videoTsToSeconds(timingInfo.start.dts),
51449
                        presentation: clock$2.videoTsToSeconds(timingInfo.start.pts)
51450
                    },
51451
                    end: {
51452
                        decode: clock$2.videoTsToSeconds(timingInfo.end.dts),
51453
                        presentation: clock$2.videoTsToSeconds(timingInfo.end.pts)
51454
                    },
51455
                    baseMediaDecodeTime: clock$2.videoTsToSeconds(timingInfo.baseMediaDecodeTime)
51456
                };
51457
                if (timingInfo.prependedContentDuration) {
51458
                    audioSegmentTimingInfo.prependedContentDuration = clock$2.videoTsToSeconds(timingInfo.prependedContentDuration);
51459
                }
51460
                self.postMessage({
51461
                    action: 'audioSegmentTimingInfo',
51462
                    audioSegmentTimingInfo
51463
                });
51464
            });
51465
            transmuxer.on('id3Frame', function (id3Frame) {
51466
                self.postMessage({
51467
                    action: 'id3Frame',
51468
                    id3Frame
51469
                });
51470
            });
51471
            transmuxer.on('caption', function (caption) {
51472
                self.postMessage({
51473
                    action: 'caption',
51474
                    caption
51475
                });
51476
            });
51477
            transmuxer.on('trackinfo', function (trackInfo) {
51478
                self.postMessage({
51479
                    action: 'trackinfo',
51480
                    trackInfo
51481
                });
51482
            });
51483
            transmuxer.on('audioTimingInfo', function (audioTimingInfo) {
51484
                // convert to video TS since we prioritize video time over audio
51485
                self.postMessage({
51486
                    action: 'audioTimingInfo',
51487
                    audioTimingInfo: {
51488
                        start: clock$2.videoTsToSeconds(audioTimingInfo.start),
51489
                        end: clock$2.videoTsToSeconds(audioTimingInfo.end)
51490
                    }
51491
                });
51492
            });
51493
            transmuxer.on('videoTimingInfo', function (videoTimingInfo) {
51494
                self.postMessage({
51495
                    action: 'videoTimingInfo',
51496
                    videoTimingInfo: {
51497
                        start: clock$2.videoTsToSeconds(videoTimingInfo.start),
51498
                        end: clock$2.videoTsToSeconds(videoTimingInfo.end)
51499
                    }
51500
                });
51501
            });
51502
            transmuxer.on('log', function (log) {
51503
                self.postMessage({
51504
                    action: 'log',
51505
                    log
51506
                });
51507
            });
51508
        };
51509
        /**
51510
         * All incoming messages route through this hash. If no function exists
51511
         * to handle an incoming message, then we ignore the message.
51512
         *
51513
         * @class MessageHandlers
51514
         * @param {Object} options the options to initialize with
51515
         */
51516
 
51517
        class MessageHandlers {
51518
            constructor(self, options) {
51519
                this.options = options || {};
51520
                this.self = self;
51521
                this.init();
51522
            }
51523
            /**
51524
             * initialize our web worker and wire all the events.
51525
             */
51526
 
51527
            init() {
51528
                if (this.transmuxer) {
51529
                    this.transmuxer.dispose();
51530
                }
51531
                this.transmuxer = new transmuxer.Transmuxer(this.options);
51532
                wireTransmuxerEvents(this.self, this.transmuxer);
51533
            }
51534
            pushMp4Captions(data) {
51535
                if (!this.captionParser) {
51536
                    this.captionParser = new captionParser();
51537
                    this.captionParser.init();
51538
                }
51539
                const segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
51540
                const parsed = this.captionParser.parse(segment, data.trackIds, data.timescales);
51541
                this.self.postMessage({
51542
                    action: 'mp4Captions',
51543
                    captions: parsed && parsed.captions || [],
51544
                    logs: parsed && parsed.logs || [],
51545
                    data: segment.buffer
51546
                }, [segment.buffer]);
51547
            }
51548
            probeMp4StartTime({
51549
                                  timescales,
51550
                                  data
51551
                              }) {
51552
                const startTime = probe$2.startTime(timescales, data);
51553
                this.self.postMessage({
51554
                    action: 'probeMp4StartTime',
51555
                    startTime,
51556
                    data
51557
                }, [data.buffer]);
51558
            }
51559
            probeMp4Tracks({
51560
                               data
51561
                           }) {
51562
                const tracks = probe$2.tracks(data);
51563
                this.self.postMessage({
51564
                    action: 'probeMp4Tracks',
51565
                    tracks,
51566
                    data
51567
                }, [data.buffer]);
51568
            }
51569
            /**
51570
             * Probes an mp4 segment for EMSG boxes containing ID3 data.
51571
             * https://aomediacodec.github.io/id3-emsg/
51572
             *
51573
             * @param {Uint8Array} data segment data
51574
             * @param {number} offset segment start time
51575
             * @return {Object[]} an array of ID3 frames
51576
             */
51577
 
51578
            probeEmsgID3({
51579
                             data,
51580
                             offset
51581
                         }) {
51582
                const id3Frames = probe$2.getEmsgID3(data, offset);
51583
                this.self.postMessage({
51584
                    action: 'probeEmsgID3',
51585
                    id3Frames,
51586
                    emsgData: data
51587
                }, [data.buffer]);
51588
            }
51589
            /**
51590
             * Probe an mpeg2-ts segment to determine the start time of the segment in it's
51591
             * internal "media time," as well as whether it contains video and/or audio.
51592
             *
51593
             * @private
51594
             * @param {Uint8Array} bytes - segment bytes
51595
             * @param {number} baseStartTime
51596
             *        Relative reference timestamp used when adjusting frame timestamps for rollover.
51597
             *        This value should be in seconds, as it's converted to a 90khz clock within the
51598
             *        function body.
51599
             * @return {Object} The start time of the current segment in "media time" as well as
51600
             *                  whether it contains video and/or audio
51601
             */
51602
 
51603
            probeTs({
51604
                        data,
51605
                        baseStartTime
51606
                    }) {
51607
                const tsStartTime = typeof baseStartTime === 'number' && !isNaN(baseStartTime) ? baseStartTime * clock$2.ONE_SECOND_IN_TS : void 0;
51608
                const timeInfo = tsInspector.inspect(data, tsStartTime);
51609
                let result = null;
51610
                if (timeInfo) {
51611
                    result = {
51612
                        // each type's time info comes back as an array of 2 times, start and end
51613
                        hasVideo: timeInfo.video && timeInfo.video.length === 2 || false,
51614
                        hasAudio: timeInfo.audio && timeInfo.audio.length === 2 || false
51615
                    };
51616
                    if (result.hasVideo) {
51617
                        result.videoStart = timeInfo.video[0].ptsTime;
51618
                    }
51619
                    if (result.hasAudio) {
51620
                        result.audioStart = timeInfo.audio[0].ptsTime;
51621
                    }
51622
                }
51623
                this.self.postMessage({
51624
                    action: 'probeTs',
51625
                    result,
51626
                    data
51627
                }, [data.buffer]);
51628
            }
51629
            clearAllMp4Captions() {
51630
                if (this.captionParser) {
51631
                    this.captionParser.clearAllCaptions();
51632
                }
51633
            }
51634
            clearParsedMp4Captions() {
51635
                if (this.captionParser) {
51636
                    this.captionParser.clearParsedCaptions();
51637
                }
51638
            }
51639
            /**
51640
             * Adds data (a ts segment) to the start of the transmuxer pipeline for
51641
             * processing.
51642
             *
51643
             * @param {ArrayBuffer} data data to push into the muxer
51644
             */
51645
 
51646
            push(data) {
51647
                // Cast array buffer to correct type for transmuxer
51648
                const segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
51649
                this.transmuxer.push(segment);
51650
            }
51651
            /**
51652
             * Recreate the transmuxer so that the next segment added via `push`
51653
             * start with a fresh transmuxer.
51654
             */
51655
 
51656
            reset() {
51657
                this.transmuxer.reset();
51658
            }
51659
            /**
51660
             * Set the value that will be used as the `baseMediaDecodeTime` time for the
51661
             * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
51662
             * set relative to the first based on the PTS values.
51663
             *
51664
             * @param {Object} data used to set the timestamp offset in the muxer
51665
             */
51666
 
51667
            setTimestampOffset(data) {
51668
                const timestampOffset = data.timestampOffset || 0;
51669
                this.transmuxer.setBaseMediaDecodeTime(Math.round(clock$2.secondsToVideoTs(timestampOffset)));
51670
            }
51671
            setAudioAppendStart(data) {
51672
                this.transmuxer.setAudioAppendStart(Math.ceil(clock$2.secondsToVideoTs(data.appendStart)));
51673
            }
51674
            setRemux(data) {
51675
                this.transmuxer.setRemux(data.remux);
51676
            }
51677
            /**
51678
             * Forces the pipeline to finish processing the last segment and emit it's
51679
             * results.
51680
             *
51681
             * @param {Object} data event data, not really used
51682
             */
51683
 
51684
            flush(data) {
51685
                this.transmuxer.flush(); // transmuxed done action is fired after both audio/video pipelines are flushed
51686
 
51687
                self.postMessage({
51688
                    action: 'done',
51689
                    type: 'transmuxed'
51690
                });
51691
            }
51692
            endTimeline() {
51693
                this.transmuxer.endTimeline(); // transmuxed endedtimeline action is fired after both audio/video pipelines end their
51694
                // timelines
51695
 
51696
                self.postMessage({
51697
                    action: 'endedtimeline',
51698
                    type: 'transmuxed'
51699
                });
51700
            }
51701
            alignGopsWith(data) {
51702
                this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
51703
            }
51704
        }
51705
        /**
51706
         * Our web worker interface so that things can talk to mux.js
51707
         * that will be running in a web worker. the scope is passed to this by
51708
         * webworkify.
51709
         *
51710
         * @param {Object} self the scope for the web worker
51711
         */
51712
 
51713
        self.onmessage = function (event) {
51714
            if (event.data.action === 'init' && event.data.options) {
51715
                this.messageHandlers = new MessageHandlers(self, event.data.options);
51716
                return;
51717
            }
51718
            if (!this.messageHandlers) {
51719
                this.messageHandlers = new MessageHandlers(self);
51720
            }
51721
            if (event.data && event.data.action && event.data.action !== 'init') {
51722
                if (this.messageHandlers[event.data.action]) {
51723
                    this.messageHandlers[event.data.action](event.data);
51724
                }
51725
            }
51726
        };
51727
    }));
51728
    var TransmuxWorker = factory(workerCode$1);
51729
    /* rollup-plugin-worker-factory end for worker!/home/runner/work/http-streaming/http-streaming/src/transmuxer-worker.js */
51730
 
51731
    const handleData_ = (event, transmuxedData, callback) => {
51732
        const {
51733
            type,
51734
            initSegment,
51735
            captions,
51736
            captionStreams,
51737
            metadata,
51738
            videoFrameDtsTime,
51739
            videoFramePtsTime
51740
        } = event.data.segment;
51741
        transmuxedData.buffer.push({
51742
            captions,
51743
            captionStreams,
51744
            metadata
51745
        });
51746
        const boxes = event.data.segment.boxes || {
51747
            data: event.data.segment.data
51748
        };
51749
        const result = {
51750
            type,
51751
            // cast ArrayBuffer to TypedArray
51752
            data: new Uint8Array(boxes.data, boxes.data.byteOffset, boxes.data.byteLength),
51753
            initSegment: new Uint8Array(initSegment.data, initSegment.byteOffset, initSegment.byteLength)
51754
        };
51755
        if (typeof videoFrameDtsTime !== 'undefined') {
51756
            result.videoFrameDtsTime = videoFrameDtsTime;
51757
        }
51758
        if (typeof videoFramePtsTime !== 'undefined') {
51759
            result.videoFramePtsTime = videoFramePtsTime;
51760
        }
51761
        callback(result);
51762
    };
51763
    const handleDone_ = ({
51764
                             transmuxedData,
51765
                             callback
51766
                         }) => {
51767
        // Previously we only returned data on data events,
51768
        // not on done events. Clear out the buffer to keep that consistent.
51769
        transmuxedData.buffer = []; // all buffers should have been flushed from the muxer, so start processing anything we
51770
        // have received
51771
 
51772
        callback(transmuxedData);
51773
    };
51774
    const handleGopInfo_ = (event, transmuxedData) => {
51775
        transmuxedData.gopInfo = event.data.gopInfo;
51776
    };
51777
    const processTransmux = options => {
51778
        const {
51779
            transmuxer,
51780
            bytes,
51781
            audioAppendStart,
51782
            gopsToAlignWith,
51783
            remux,
51784
            onData,
51785
            onTrackInfo,
51786
            onAudioTimingInfo,
51787
            onVideoTimingInfo,
51788
            onVideoSegmentTimingInfo,
51789
            onAudioSegmentTimingInfo,
51790
            onId3,
51791
            onCaptions,
51792
            onDone,
51793
            onEndedTimeline,
51794
            onTransmuxerLog,
51795
            isEndOfTimeline
51796
        } = options;
51797
        const transmuxedData = {
51798
            buffer: []
51799
        };
51800
        let waitForEndedTimelineEvent = isEndOfTimeline;
51801
        const handleMessage = event => {
51802
            if (transmuxer.currentTransmux !== options) {
51803
                // disposed
51804
                return;
51805
            }
51806
            if (event.data.action === 'data') {
51807
                handleData_(event, transmuxedData, onData);
51808
            }
51809
            if (event.data.action === 'trackinfo') {
51810
                onTrackInfo(event.data.trackInfo);
51811
            }
51812
            if (event.data.action === 'gopInfo') {
51813
                handleGopInfo_(event, transmuxedData);
51814
            }
51815
            if (event.data.action === 'audioTimingInfo') {
51816
                onAudioTimingInfo(event.data.audioTimingInfo);
51817
            }
51818
            if (event.data.action === 'videoTimingInfo') {
51819
                onVideoTimingInfo(event.data.videoTimingInfo);
51820
            }
51821
            if (event.data.action === 'videoSegmentTimingInfo') {
51822
                onVideoSegmentTimingInfo(event.data.videoSegmentTimingInfo);
51823
            }
51824
            if (event.data.action === 'audioSegmentTimingInfo') {
51825
                onAudioSegmentTimingInfo(event.data.audioSegmentTimingInfo);
51826
            }
51827
            if (event.data.action === 'id3Frame') {
51828
                onId3([event.data.id3Frame], event.data.id3Frame.dispatchType);
51829
            }
51830
            if (event.data.action === 'caption') {
51831
                onCaptions(event.data.caption);
51832
            }
51833
            if (event.data.action === 'endedtimeline') {
51834
                waitForEndedTimelineEvent = false;
51835
                onEndedTimeline();
51836
            }
51837
            if (event.data.action === 'log') {
51838
                onTransmuxerLog(event.data.log);
51839
            } // wait for the transmuxed event since we may have audio and video
51840
 
51841
            if (event.data.type !== 'transmuxed') {
51842
                return;
51843
            } // If the "endedtimeline" event has not yet fired, and this segment represents the end
51844
            // of a timeline, that means there may still be data events before the segment
51845
            // processing can be considerred complete. In that case, the final event should be
51846
            // an "endedtimeline" event with the type "transmuxed."
51847
 
51848
            if (waitForEndedTimelineEvent) {
51849
                return;
51850
            }
51851
            transmuxer.onmessage = null;
51852
            handleDone_({
51853
                transmuxedData,
51854
                callback: onDone
51855
            });
51856
            /* eslint-disable no-use-before-define */
51857
 
51858
            dequeue(transmuxer);
51859
            /* eslint-enable */
51860
        };
51861
 
51862
        transmuxer.onmessage = handleMessage;
51863
        if (audioAppendStart) {
51864
            transmuxer.postMessage({
51865
                action: 'setAudioAppendStart',
51866
                appendStart: audioAppendStart
51867
            });
51868
        } // allow empty arrays to be passed to clear out GOPs
51869
 
51870
        if (Array.isArray(gopsToAlignWith)) {
51871
            transmuxer.postMessage({
51872
                action: 'alignGopsWith',
51873
                gopsToAlignWith
51874
            });
51875
        }
51876
        if (typeof remux !== 'undefined') {
51877
            transmuxer.postMessage({
51878
                action: 'setRemux',
51879
                remux
51880
            });
51881
        }
51882
        if (bytes.byteLength) {
51883
            const buffer = bytes instanceof ArrayBuffer ? bytes : bytes.buffer;
51884
            const byteOffset = bytes instanceof ArrayBuffer ? 0 : bytes.byteOffset;
51885
            transmuxer.postMessage({
51886
                action: 'push',
51887
                // Send the typed-array of data as an ArrayBuffer so that
51888
                // it can be sent as a "Transferable" and avoid the costly
51889
                // memory copy
51890
                data: buffer,
51891
                // To recreate the original typed-array, we need information
51892
                // about what portion of the ArrayBuffer it was a view into
51893
                byteOffset,
51894
                byteLength: bytes.byteLength
51895
            }, [buffer]);
51896
        }
51897
        if (isEndOfTimeline) {
51898
            transmuxer.postMessage({
51899
                action: 'endTimeline'
51900
            });
51901
        } // even if we didn't push any bytes, we have to make sure we flush in case we reached
51902
        // the end of the segment
51903
 
51904
        transmuxer.postMessage({
51905
            action: 'flush'
51906
        });
51907
    };
51908
    const dequeue = transmuxer => {
51909
        transmuxer.currentTransmux = null;
51910
        if (transmuxer.transmuxQueue.length) {
51911
            transmuxer.currentTransmux = transmuxer.transmuxQueue.shift();
51912
            if (typeof transmuxer.currentTransmux === 'function') {
51913
                transmuxer.currentTransmux();
51914
            } else {
51915
                processTransmux(transmuxer.currentTransmux);
51916
            }
51917
        }
51918
    };
51919
    const processAction = (transmuxer, action) => {
51920
        transmuxer.postMessage({
51921
            action
51922
        });
51923
        dequeue(transmuxer);
51924
    };
51925
    const enqueueAction = (action, transmuxer) => {
51926
        if (!transmuxer.currentTransmux) {
51927
            transmuxer.currentTransmux = action;
51928
            processAction(transmuxer, action);
51929
            return;
51930
        }
51931
        transmuxer.transmuxQueue.push(processAction.bind(null, transmuxer, action));
51932
    };
51933
    const reset = transmuxer => {
51934
        enqueueAction('reset', transmuxer);
51935
    };
51936
    const endTimeline = transmuxer => {
51937
        enqueueAction('endTimeline', transmuxer);
51938
    };
51939
    const transmux = options => {
51940
        if (!options.transmuxer.currentTransmux) {
51941
            options.transmuxer.currentTransmux = options;
51942
            processTransmux(options);
51943
            return;
51944
        }
51945
        options.transmuxer.transmuxQueue.push(options);
51946
    };
51947
    const createTransmuxer = options => {
51948
        const transmuxer = new TransmuxWorker();
51949
        transmuxer.currentTransmux = null;
51950
        transmuxer.transmuxQueue = [];
51951
        const term = transmuxer.terminate;
51952
        transmuxer.terminate = () => {
51953
            transmuxer.currentTransmux = null;
51954
            transmuxer.transmuxQueue.length = 0;
51955
            return term.call(transmuxer);
51956
        };
51957
        transmuxer.postMessage({
51958
            action: 'init',
51959
            options
51960
        });
51961
        return transmuxer;
51962
    };
51963
    var segmentTransmuxer = {
51964
        reset,
51965
        endTimeline,
51966
        transmux,
51967
        createTransmuxer
51968
    };
51969
    const workerCallback = function (options) {
51970
        const transmuxer = options.transmuxer;
51971
        const endAction = options.endAction || options.action;
51972
        const callback = options.callback;
51973
        const message = _extends$1({}, options, {
51974
            endAction: null,
51975
            transmuxer: null,
51976
            callback: null
51977
        });
51978
        const listenForEndEvent = event => {
51979
            if (event.data.action !== endAction) {
51980
                return;
51981
            }
51982
            transmuxer.removeEventListener('message', listenForEndEvent); // transfer ownership of bytes back to us.
51983
 
51984
            if (event.data.data) {
51985
                event.data.data = new Uint8Array(event.data.data, options.byteOffset || 0, options.byteLength || event.data.data.byteLength);
51986
                if (options.data) {
51987
                    options.data = event.data.data;
51988
                }
51989
            }
51990
            callback(event.data);
51991
        };
51992
        transmuxer.addEventListener('message', listenForEndEvent);
51993
        if (options.data) {
51994
            const isArrayBuffer = options.data instanceof ArrayBuffer;
51995
            message.byteOffset = isArrayBuffer ? 0 : options.data.byteOffset;
51996
            message.byteLength = options.data.byteLength;
51997
            const transfers = [isArrayBuffer ? options.data : options.data.buffer];
51998
            transmuxer.postMessage(message, transfers);
51999
        } else {
52000
            transmuxer.postMessage(message);
52001
        }
52002
    };
52003
    const REQUEST_ERRORS = {
52004
        FAILURE: 2,
52005
        TIMEOUT: -101,
52006
        ABORTED: -102
52007
    };
52008
    /**
52009
     * Abort all requests
52010
     *
52011
     * @param {Object} activeXhrs - an object that tracks all XHR requests
52012
     */
52013
 
52014
    const abortAll = activeXhrs => {
52015
        activeXhrs.forEach(xhr => {
52016
            xhr.abort();
52017
        });
52018
    };
52019
    /**
52020
     * Gather important bandwidth stats once a request has completed
52021
     *
52022
     * @param {Object} request - the XHR request from which to gather stats
52023
     */
52024
 
52025
    const getRequestStats = request => {
52026
        return {
52027
            bandwidth: request.bandwidth,
52028
            bytesReceived: request.bytesReceived || 0,
52029
            roundTripTime: request.roundTripTime || 0
52030
        };
52031
    };
52032
    /**
52033
     * If possible gather bandwidth stats as a request is in
52034
     * progress
52035
     *
52036
     * @param {Event} progressEvent - an event object from an XHR's progress event
52037
     */
52038
 
52039
    const getProgressStats = progressEvent => {
52040
        const request = progressEvent.target;
52041
        const roundTripTime = Date.now() - request.requestTime;
52042
        const stats = {
52043
            bandwidth: Infinity,
52044
            bytesReceived: 0,
52045
            roundTripTime: roundTripTime || 0
52046
        };
52047
        stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok
52048
        // because we should only use bandwidth stats on progress to determine when
52049
        // abort a request early due to insufficient bandwidth
52050
 
52051
        stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000);
52052
        return stats;
52053
    };
52054
    /**
52055
     * Handle all error conditions in one place and return an object
52056
     * with all the information
52057
     *
52058
     * @param {Error|null} error - if non-null signals an error occured with the XHR
52059
     * @param {Object} request -  the XHR request that possibly generated the error
52060
     */
52061
 
52062
    const handleErrors = (error, request) => {
52063
        if (request.timedout) {
52064
            return {
52065
                status: request.status,
52066
                message: 'HLS request timed-out at URL: ' + request.uri,
52067
                code: REQUEST_ERRORS.TIMEOUT,
52068
                xhr: request
52069
            };
52070
        }
52071
        if (request.aborted) {
52072
            return {
52073
                status: request.status,
52074
                message: 'HLS request aborted at URL: ' + request.uri,
52075
                code: REQUEST_ERRORS.ABORTED,
52076
                xhr: request
52077
            };
52078
        }
52079
        if (error) {
52080
            return {
52081
                status: request.status,
52082
                message: 'HLS request errored at URL: ' + request.uri,
52083
                code: REQUEST_ERRORS.FAILURE,
52084
                xhr: request
52085
            };
52086
        }
52087
        if (request.responseType === 'arraybuffer' && request.response.byteLength === 0) {
52088
            return {
52089
                status: request.status,
52090
                message: 'Empty HLS response at URL: ' + request.uri,
52091
                code: REQUEST_ERRORS.FAILURE,
52092
                xhr: request
52093
            };
52094
        }
52095
        return null;
52096
    };
52097
    /**
52098
     * Handle responses for key data and convert the key data to the correct format
52099
     * for the decryption step later
52100
     *
52101
     * @param {Object} segment - a simplified copy of the segmentInfo object
52102
     *                           from SegmentLoader
52103
     * @param {Array} objects - objects to add the key bytes to.
52104
     * @param {Function} finishProcessingFn - a callback to execute to continue processing
52105
     *                                        this request
52106
     */
52107
 
52108
    const handleKeyResponse = (segment, objects, finishProcessingFn) => (error, request) => {
52109
        const response = request.response;
52110
        const errorObj = handleErrors(error, request);
52111
        if (errorObj) {
52112
            return finishProcessingFn(errorObj, segment);
52113
        }
52114
        if (response.byteLength !== 16) {
52115
            return finishProcessingFn({
52116
                status: request.status,
52117
                message: 'Invalid HLS key at URL: ' + request.uri,
52118
                code: REQUEST_ERRORS.FAILURE,
52119
                xhr: request
52120
            }, segment);
52121
        }
52122
        const view = new DataView(response);
52123
        const bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
52124
        for (let i = 0; i < objects.length; i++) {
52125
            objects[i].bytes = bytes;
52126
        }
52127
        return finishProcessingFn(null, segment);
52128
    };
52129
    const parseInitSegment = (segment, callback) => {
52130
        const type = detectContainerForBytes(segment.map.bytes); // TODO: We should also handle ts init segments here, but we
52131
        // only know how to parse mp4 init segments at the moment
52132
 
52133
        if (type !== 'mp4') {
52134
            const uri = segment.map.resolvedUri || segment.map.uri;
52135
            return callback({
52136
                internal: true,
52137
                message: `Found unsupported ${type || 'unknown'} container for initialization segment at URL: ${uri}`,
52138
                code: REQUEST_ERRORS.FAILURE
52139
            });
52140
        }
52141
        workerCallback({
52142
            action: 'probeMp4Tracks',
52143
            data: segment.map.bytes,
52144
            transmuxer: segment.transmuxer,
52145
            callback: ({
52146
                           tracks,
52147
                           data
52148
                       }) => {
52149
                // transfer bytes back to us
52150
                segment.map.bytes = data;
52151
                tracks.forEach(function (track) {
52152
                    segment.map.tracks = segment.map.tracks || {}; // only support one track of each type for now
52153
 
52154
                    if (segment.map.tracks[track.type]) {
52155
                        return;
52156
                    }
52157
                    segment.map.tracks[track.type] = track;
52158
                    if (typeof track.id === 'number' && track.timescale) {
52159
                        segment.map.timescales = segment.map.timescales || {};
52160
                        segment.map.timescales[track.id] = track.timescale;
52161
                    }
52162
                });
52163
                return callback(null);
52164
            }
52165
        });
52166
    };
52167
    /**
52168
     * Handle init-segment responses
52169
     *
52170
     * @param {Object} segment - a simplified copy of the segmentInfo object
52171
     *                           from SegmentLoader
52172
     * @param {Function} finishProcessingFn - a callback to execute to continue processing
52173
     *                                        this request
52174
     */
52175
 
52176
    const handleInitSegmentResponse = ({
52177
                                           segment,
52178
                                           finishProcessingFn
52179
                                       }) => (error, request) => {
52180
        const errorObj = handleErrors(error, request);
52181
        if (errorObj) {
52182
            return finishProcessingFn(errorObj, segment);
52183
        }
52184
        const bytes = new Uint8Array(request.response); // init segment is encypted, we will have to wait
52185
        // until the key request is done to decrypt.
52186
 
52187
        if (segment.map.key) {
52188
            segment.map.encryptedBytes = bytes;
52189
            return finishProcessingFn(null, segment);
52190
        }
52191
        segment.map.bytes = bytes;
52192
        parseInitSegment(segment, function (parseError) {
52193
            if (parseError) {
52194
                parseError.xhr = request;
52195
                parseError.status = request.status;
52196
                return finishProcessingFn(parseError, segment);
52197
            }
52198
            finishProcessingFn(null, segment);
52199
        });
52200
    };
52201
    /**
52202
     * Response handler for segment-requests being sure to set the correct
52203
     * property depending on whether the segment is encryped or not
52204
     * Also records and keeps track of stats that are used for ABR purposes
52205
     *
52206
     * @param {Object} segment - a simplified copy of the segmentInfo object
52207
     *                           from SegmentLoader
52208
     * @param {Function} finishProcessingFn - a callback to execute to continue processing
52209
     *                                        this request
52210
     */
52211
 
52212
    const handleSegmentResponse = ({
52213
                                       segment,
52214
                                       finishProcessingFn,
52215
                                       responseType
52216
                                   }) => (error, request) => {
52217
        const errorObj = handleErrors(error, request);
52218
        if (errorObj) {
52219
            return finishProcessingFn(errorObj, segment);
52220
        }
52221
        const newBytes =
52222
            // although responseText "should" exist, this guard serves to prevent an error being
52223
            // thrown for two primary cases:
52224
            // 1. the mime type override stops working, or is not implemented for a specific
52225
            //    browser
52226
            // 2. when using mock XHR libraries like sinon that do not allow the override behavior
52227
            responseType === 'arraybuffer' || !request.responseText ? request.response : stringToArrayBuffer(request.responseText.substring(segment.lastReachedChar || 0));
52228
        segment.stats = getRequestStats(request);
52229
        if (segment.key) {
52230
            segment.encryptedBytes = new Uint8Array(newBytes);
52231
        } else {
52232
            segment.bytes = new Uint8Array(newBytes);
52233
        }
52234
        return finishProcessingFn(null, segment);
52235
    };
52236
    const transmuxAndNotify = ({
52237
                                   segment,
52238
                                   bytes,
52239
                                   trackInfoFn,
52240
                                   timingInfoFn,
52241
                                   videoSegmentTimingInfoFn,
52242
                                   audioSegmentTimingInfoFn,
52243
                                   id3Fn,
52244
                                   captionsFn,
52245
                                   isEndOfTimeline,
52246
                                   endedTimelineFn,
52247
                                   dataFn,
52248
                                   doneFn,
52249
                                   onTransmuxerLog
52250
                               }) => {
52251
        const fmp4Tracks = segment.map && segment.map.tracks || {};
52252
        const isMuxed = Boolean(fmp4Tracks.audio && fmp4Tracks.video); // Keep references to each function so we can null them out after we're done with them.
52253
        // One reason for this is that in the case of full segments, we want to trust start
52254
        // times from the probe, rather than the transmuxer.
52255
 
52256
        let audioStartFn = timingInfoFn.bind(null, segment, 'audio', 'start');
52257
        const audioEndFn = timingInfoFn.bind(null, segment, 'audio', 'end');
52258
        let videoStartFn = timingInfoFn.bind(null, segment, 'video', 'start');
52259
        const videoEndFn = timingInfoFn.bind(null, segment, 'video', 'end');
52260
        const finish = () => transmux({
52261
            bytes,
52262
            transmuxer: segment.transmuxer,
52263
            audioAppendStart: segment.audioAppendStart,
52264
            gopsToAlignWith: segment.gopsToAlignWith,
52265
            remux: isMuxed,
52266
            onData: result => {
52267
                result.type = result.type === 'combined' ? 'video' : result.type;
52268
                dataFn(segment, result);
52269
            },
52270
            onTrackInfo: trackInfo => {
52271
                if (trackInfoFn) {
52272
                    if (isMuxed) {
52273
                        trackInfo.isMuxed = true;
52274
                    }
52275
                    trackInfoFn(segment, trackInfo);
52276
                }
52277
            },
52278
            onAudioTimingInfo: audioTimingInfo => {
52279
                // we only want the first start value we encounter
52280
                if (audioStartFn && typeof audioTimingInfo.start !== 'undefined') {
52281
                    audioStartFn(audioTimingInfo.start);
52282
                    audioStartFn = null;
52283
                } // we want to continually update the end time
52284
 
52285
                if (audioEndFn && typeof audioTimingInfo.end !== 'undefined') {
52286
                    audioEndFn(audioTimingInfo.end);
52287
                }
52288
            },
52289
            onVideoTimingInfo: videoTimingInfo => {
52290
                // we only want the first start value we encounter
52291
                if (videoStartFn && typeof videoTimingInfo.start !== 'undefined') {
52292
                    videoStartFn(videoTimingInfo.start);
52293
                    videoStartFn = null;
52294
                } // we want to continually update the end time
52295
 
52296
                if (videoEndFn && typeof videoTimingInfo.end !== 'undefined') {
52297
                    videoEndFn(videoTimingInfo.end);
52298
                }
52299
            },
52300
            onVideoSegmentTimingInfo: videoSegmentTimingInfo => {
52301
                videoSegmentTimingInfoFn(videoSegmentTimingInfo);
52302
            },
52303
            onAudioSegmentTimingInfo: audioSegmentTimingInfo => {
52304
                audioSegmentTimingInfoFn(audioSegmentTimingInfo);
52305
            },
52306
            onId3: (id3Frames, dispatchType) => {
52307
                id3Fn(segment, id3Frames, dispatchType);
52308
            },
52309
            onCaptions: captions => {
52310
                captionsFn(segment, [captions]);
52311
            },
52312
            isEndOfTimeline,
52313
            onEndedTimeline: () => {
52314
                endedTimelineFn();
52315
            },
52316
            onTransmuxerLog,
52317
            onDone: result => {
52318
                if (!doneFn) {
52319
                    return;
52320
                }
52321
                result.type = result.type === 'combined' ? 'video' : result.type;
52322
                doneFn(null, segment, result);
52323
            }
52324
        }); // In the transmuxer, we don't yet have the ability to extract a "proper" start time.
52325
        // Meaning cached frame data may corrupt our notion of where this segment
52326
        // really starts. To get around this, probe for the info needed.
52327
 
52328
        workerCallback({
52329
            action: 'probeTs',
52330
            transmuxer: segment.transmuxer,
52331
            data: bytes,
52332
            baseStartTime: segment.baseStartTime,
52333
            callback: data => {
52334
                segment.bytes = bytes = data.data;
52335
                const probeResult = data.result;
52336
                if (probeResult) {
52337
                    trackInfoFn(segment, {
52338
                        hasAudio: probeResult.hasAudio,
52339
                        hasVideo: probeResult.hasVideo,
52340
                        isMuxed
52341
                    });
52342
                    trackInfoFn = null;
52343
                }
52344
                finish();
52345
            }
52346
        });
52347
    };
52348
    const handleSegmentBytes = ({
52349
                                    segment,
52350
                                    bytes,
52351
                                    trackInfoFn,
52352
                                    timingInfoFn,
52353
                                    videoSegmentTimingInfoFn,
52354
                                    audioSegmentTimingInfoFn,
52355
                                    id3Fn,
52356
                                    captionsFn,
52357
                                    isEndOfTimeline,
52358
                                    endedTimelineFn,
52359
                                    dataFn,
52360
                                    doneFn,
52361
                                    onTransmuxerLog
52362
                                }) => {
52363
        let bytesAsUint8Array = new Uint8Array(bytes); // TODO:
52364
        // We should have a handler that fetches the number of bytes required
52365
        // to check if something is fmp4. This will allow us to save bandwidth
52366
        // because we can only exclude a playlist and abort requests
52367
        // by codec after trackinfo triggers.
52368
 
52369
        if (isLikelyFmp4MediaSegment(bytesAsUint8Array)) {
52370
            segment.isFmp4 = true;
52371
            const {
52372
                tracks
52373
            } = segment.map;
52374
            const trackInfo = {
52375
                isFmp4: true,
52376
                hasVideo: !!tracks.video,
52377
                hasAudio: !!tracks.audio
52378
            }; // if we have a audio track, with a codec that is not set to
52379
            // encrypted audio
52380
 
52381
            if (tracks.audio && tracks.audio.codec && tracks.audio.codec !== 'enca') {
52382
                trackInfo.audioCodec = tracks.audio.codec;
52383
            } // if we have a video track, with a codec that is not set to
52384
            // encrypted video
52385
 
52386
            if (tracks.video && tracks.video.codec && tracks.video.codec !== 'encv') {
52387
                trackInfo.videoCodec = tracks.video.codec;
52388
            }
52389
            if (tracks.video && tracks.audio) {
52390
                trackInfo.isMuxed = true;
52391
            } // since we don't support appending fmp4 data on progress, we know we have the full
52392
            // segment here
52393
 
52394
            trackInfoFn(segment, trackInfo); // The probe doesn't provide the segment end time, so only callback with the start
52395
            // time. The end time can be roughly calculated by the receiver using the duration.
52396
            //
52397
            // Note that the start time returned by the probe reflects the baseMediaDecodeTime, as
52398
            // that is the true start of the segment (where the playback engine should begin
52399
            // decoding).
52400
 
52401
            const finishLoading = (captions, id3Frames) => {
52402
                // if the track still has audio at this point it is only possible
52403
                // for it to be audio only. See `tracks.video && tracks.audio` if statement
52404
                // above.
52405
                // we make sure to use segment.bytes here as that
52406
                dataFn(segment, {
52407
                    data: bytesAsUint8Array,
52408
                    type: trackInfo.hasAudio && !trackInfo.isMuxed ? 'audio' : 'video'
52409
                });
52410
                if (id3Frames && id3Frames.length) {
52411
                    id3Fn(segment, id3Frames);
52412
                }
52413
                if (captions && captions.length) {
52414
                    captionsFn(segment, captions);
52415
                }
52416
                doneFn(null, segment, {});
52417
            };
52418
            workerCallback({
52419
                action: 'probeMp4StartTime',
52420
                timescales: segment.map.timescales,
52421
                data: bytesAsUint8Array,
52422
                transmuxer: segment.transmuxer,
52423
                callback: ({
52424
                               data,
52425
                               startTime
52426
                           }) => {
52427
                    // transfer bytes back to us
52428
                    bytes = data.buffer;
52429
                    segment.bytes = bytesAsUint8Array = data;
52430
                    if (trackInfo.hasAudio && !trackInfo.isMuxed) {
52431
                        timingInfoFn(segment, 'audio', 'start', startTime);
52432
                    }
52433
                    if (trackInfo.hasVideo) {
52434
                        timingInfoFn(segment, 'video', 'start', startTime);
52435
                    }
52436
                    workerCallback({
52437
                        action: 'probeEmsgID3',
52438
                        data: bytesAsUint8Array,
52439
                        transmuxer: segment.transmuxer,
52440
                        offset: startTime,
52441
                        callback: ({
52442
                                       emsgData,
52443
                                       id3Frames
52444
                                   }) => {
52445
                            // transfer bytes back to us
52446
                            bytes = emsgData.buffer;
52447
                            segment.bytes = bytesAsUint8Array = emsgData; // Run through the CaptionParser in case there are captions.
52448
                            // Initialize CaptionParser if it hasn't been yet
52449
 
52450
                            if (!tracks.video || !emsgData.byteLength || !segment.transmuxer) {
52451
                                finishLoading(undefined, id3Frames);
52452
                                return;
52453
                            }
52454
                            workerCallback({
52455
                                action: 'pushMp4Captions',
52456
                                endAction: 'mp4Captions',
52457
                                transmuxer: segment.transmuxer,
52458
                                data: bytesAsUint8Array,
52459
                                timescales: segment.map.timescales,
52460
                                trackIds: [tracks.video.id],
52461
                                callback: message => {
52462
                                    // transfer bytes back to us
52463
                                    bytes = message.data.buffer;
52464
                                    segment.bytes = bytesAsUint8Array = message.data;
52465
                                    message.logs.forEach(function (log) {
52466
                                        onTransmuxerLog(merge(log, {
52467
                                            stream: 'mp4CaptionParser'
52468
                                        }));
52469
                                    });
52470
                                    finishLoading(message.captions, id3Frames);
52471
                                }
52472
                            });
52473
                        }
52474
                    });
52475
                }
52476
            });
52477
            return;
52478
        } // VTT or other segments that don't need processing
52479
 
52480
        if (!segment.transmuxer) {
52481
            doneFn(null, segment, {});
52482
            return;
52483
        }
52484
        if (typeof segment.container === 'undefined') {
52485
            segment.container = detectContainerForBytes(bytesAsUint8Array);
52486
        }
52487
        if (segment.container !== 'ts' && segment.container !== 'aac') {
52488
            trackInfoFn(segment, {
52489
                hasAudio: false,
52490
                hasVideo: false
52491
            });
52492
            doneFn(null, segment, {});
52493
            return;
52494
        } // ts or aac
52495
 
52496
        transmuxAndNotify({
52497
            segment,
52498
            bytes,
52499
            trackInfoFn,
52500
            timingInfoFn,
52501
            videoSegmentTimingInfoFn,
52502
            audioSegmentTimingInfoFn,
52503
            id3Fn,
52504
            captionsFn,
52505
            isEndOfTimeline,
52506
            endedTimelineFn,
52507
            dataFn,
52508
            doneFn,
52509
            onTransmuxerLog
52510
        });
52511
    };
52512
    const decrypt = function ({
52513
                                  id,
52514
                                  key,
52515
                                  encryptedBytes,
52516
                                  decryptionWorker
52517
                              }, callback) {
52518
        const decryptionHandler = event => {
52519
            if (event.data.source === id) {
52520
                decryptionWorker.removeEventListener('message', decryptionHandler);
52521
                const decrypted = event.data.decrypted;
52522
                callback(new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength));
52523
            }
52524
        };
52525
        decryptionWorker.addEventListener('message', decryptionHandler);
52526
        let keyBytes;
52527
        if (key.bytes.slice) {
52528
            keyBytes = key.bytes.slice();
52529
        } else {
52530
            keyBytes = new Uint32Array(Array.prototype.slice.call(key.bytes));
52531
        } // incrementally decrypt the bytes
52532
 
52533
        decryptionWorker.postMessage(createTransferableMessage({
52534
            source: id,
52535
            encrypted: encryptedBytes,
52536
            key: keyBytes,
52537
            iv: key.iv
52538
        }), [encryptedBytes.buffer, keyBytes.buffer]);
52539
    };
52540
    /**
52541
     * Decrypt the segment via the decryption web worker
52542
     *
52543
     * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption
52544
     *                                       routines
52545
     * @param {Object} segment - a simplified copy of the segmentInfo object
52546
     *                           from SegmentLoader
52547
     * @param {Function} trackInfoFn - a callback that receives track info
52548
     * @param {Function} timingInfoFn - a callback that receives timing info
52549
     * @param {Function} videoSegmentTimingInfoFn
52550
     *                   a callback that receives video timing info based on media times and
52551
     *                   any adjustments made by the transmuxer
52552
     * @param {Function} audioSegmentTimingInfoFn
52553
     *                   a callback that receives audio timing info based on media times and
52554
     *                   any adjustments made by the transmuxer
52555
     * @param {boolean}  isEndOfTimeline
52556
     *                   true if this segment represents the last segment in a timeline
52557
     * @param {Function} endedTimelineFn
52558
     *                   a callback made when a timeline is ended, will only be called if
52559
     *                   isEndOfTimeline is true
52560
     * @param {Function} dataFn - a callback that is executed when segment bytes are available
52561
     *                            and ready to use
52562
     * @param {Function} doneFn - a callback that is executed after decryption has completed
52563
     */
52564
 
52565
    const decryptSegment = ({
52566
                                decryptionWorker,
52567
                                segment,
52568
                                trackInfoFn,
52569
                                timingInfoFn,
52570
                                videoSegmentTimingInfoFn,
52571
                                audioSegmentTimingInfoFn,
52572
                                id3Fn,
52573
                                captionsFn,
52574
                                isEndOfTimeline,
52575
                                endedTimelineFn,
52576
                                dataFn,
52577
                                doneFn,
52578
                                onTransmuxerLog
52579
                            }) => {
52580
        decrypt({
52581
            id: segment.requestId,
52582
            key: segment.key,
52583
            encryptedBytes: segment.encryptedBytes,
52584
            decryptionWorker
52585
        }, decryptedBytes => {
52586
            segment.bytes = decryptedBytes;
52587
            handleSegmentBytes({
52588
                segment,
52589
                bytes: segment.bytes,
52590
                trackInfoFn,
52591
                timingInfoFn,
52592
                videoSegmentTimingInfoFn,
52593
                audioSegmentTimingInfoFn,
52594
                id3Fn,
52595
                captionsFn,
52596
                isEndOfTimeline,
52597
                endedTimelineFn,
52598
                dataFn,
52599
                doneFn,
52600
                onTransmuxerLog
52601
            });
52602
        });
52603
    };
52604
    /**
52605
     * This function waits for all XHRs to finish (with either success or failure)
52606
     * before continueing processing via it's callback. The function gathers errors
52607
     * from each request into a single errors array so that the error status for
52608
     * each request can be examined later.
52609
     *
52610
     * @param {Object} activeXhrs - an object that tracks all XHR requests
52611
     * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption
52612
     *                                       routines
52613
     * @param {Function} trackInfoFn - a callback that receives track info
52614
     * @param {Function} timingInfoFn - a callback that receives timing info
52615
     * @param {Function} videoSegmentTimingInfoFn
52616
     *                   a callback that receives video timing info based on media times and
52617
     *                   any adjustments made by the transmuxer
52618
     * @param {Function} audioSegmentTimingInfoFn
52619
     *                   a callback that receives audio timing info based on media times and
52620
     *                   any adjustments made by the transmuxer
52621
     * @param {Function} id3Fn - a callback that receives ID3 metadata
52622
     * @param {Function} captionsFn - a callback that receives captions
52623
     * @param {boolean}  isEndOfTimeline
52624
     *                   true if this segment represents the last segment in a timeline
52625
     * @param {Function} endedTimelineFn
52626
     *                   a callback made when a timeline is ended, will only be called if
52627
     *                   isEndOfTimeline is true
52628
     * @param {Function} dataFn - a callback that is executed when segment bytes are available
52629
     *                            and ready to use
52630
     * @param {Function} doneFn - a callback that is executed after all resources have been
52631
     *                            downloaded and any decryption completed
52632
     */
52633
 
52634
    const waitForCompletion = ({
52635
                                   activeXhrs,
52636
                                   decryptionWorker,
52637
                                   trackInfoFn,
52638
                                   timingInfoFn,
52639
                                   videoSegmentTimingInfoFn,
52640
                                   audioSegmentTimingInfoFn,
52641
                                   id3Fn,
52642
                                   captionsFn,
52643
                                   isEndOfTimeline,
52644
                                   endedTimelineFn,
52645
                                   dataFn,
52646
                                   doneFn,
52647
                                   onTransmuxerLog
52648
                               }) => {
52649
        let count = 0;
52650
        let didError = false;
52651
        return (error, segment) => {
52652
            if (didError) {
52653
                return;
52654
            }
52655
            if (error) {
52656
                didError = true; // If there are errors, we have to abort any outstanding requests
52657
 
52658
                abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we
52659
                // handle the aborted events from those requests, there are some cases where we may
52660
                // never get an aborted event. For instance, if the network connection is lost and
52661
                // there were two requests, the first may have triggered an error immediately, while
52662
                // the second request remains unsent. In that case, the aborted algorithm will not
52663
                // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method
52664
                //
52665
                // We also can't rely on the ready state of the XHR, since the request that
52666
                // triggered the connection error may also show as a ready state of 0 (unsent).
52667
                // Therefore, we have to finish this group of requests immediately after the first
52668
                // seen error.
52669
 
52670
                return doneFn(error, segment);
52671
            }
52672
            count += 1;
52673
            if (count === activeXhrs.length) {
52674
                const segmentFinish = function () {
52675
                    if (segment.encryptedBytes) {
52676
                        return decryptSegment({
52677
                            decryptionWorker,
52678
                            segment,
52679
                            trackInfoFn,
52680
                            timingInfoFn,
52681
                            videoSegmentTimingInfoFn,
52682
                            audioSegmentTimingInfoFn,
52683
                            id3Fn,
52684
                            captionsFn,
52685
                            isEndOfTimeline,
52686
                            endedTimelineFn,
52687
                            dataFn,
52688
                            doneFn,
52689
                            onTransmuxerLog
52690
                        });
52691
                    } // Otherwise, everything is ready just continue
52692
 
52693
                    handleSegmentBytes({
52694
                        segment,
52695
                        bytes: segment.bytes,
52696
                        trackInfoFn,
52697
                        timingInfoFn,
52698
                        videoSegmentTimingInfoFn,
52699
                        audioSegmentTimingInfoFn,
52700
                        id3Fn,
52701
                        captionsFn,
52702
                        isEndOfTimeline,
52703
                        endedTimelineFn,
52704
                        dataFn,
52705
                        doneFn,
52706
                        onTransmuxerLog
52707
                    });
52708
                }; // Keep track of when *all* of the requests have completed
52709
 
52710
                segment.endOfAllRequests = Date.now();
52711
                if (segment.map && segment.map.encryptedBytes && !segment.map.bytes) {
52712
                    return decrypt({
52713
                        decryptionWorker,
52714
                        // add -init to the "id" to differentiate between segment
52715
                        // and init segment decryption, just in case they happen
52716
                        // at the same time at some point in the future.
52717
                        id: segment.requestId + '-init',
52718
                        encryptedBytes: segment.map.encryptedBytes,
52719
                        key: segment.map.key
52720
                    }, decryptedBytes => {
52721
                        segment.map.bytes = decryptedBytes;
52722
                        parseInitSegment(segment, parseError => {
52723
                            if (parseError) {
52724
                                abortAll(activeXhrs);
52725
                                return doneFn(parseError, segment);
52726
                            }
52727
                            segmentFinish();
52728
                        });
52729
                    });
52730
                }
52731
                segmentFinish();
52732
            }
52733
        };
52734
    };
52735
    /**
52736
     * Calls the abort callback if any request within the batch was aborted. Will only call
52737
     * the callback once per batch of requests, even if multiple were aborted.
52738
     *
52739
     * @param {Object} loadendState - state to check to see if the abort function was called
52740
     * @param {Function} abortFn - callback to call for abort
52741
     */
52742
 
52743
    const handleLoadEnd = ({
52744
                               loadendState,
52745
                               abortFn
52746
                           }) => event => {
52747
        const request = event.target;
52748
        if (request.aborted && abortFn && !loadendState.calledAbortFn) {
52749
            abortFn();
52750
            loadendState.calledAbortFn = true;
52751
        }
52752
    };
52753
    /**
52754
     * Simple progress event callback handler that gathers some stats before
52755
     * executing a provided callback with the `segment` object
52756
     *
52757
     * @param {Object} segment - a simplified copy of the segmentInfo object
52758
     *                           from SegmentLoader
52759
     * @param {Function} progressFn - a callback that is executed each time a progress event
52760
     *                                is received
52761
     * @param {Function} trackInfoFn - a callback that receives track info
52762
     * @param {Function} timingInfoFn - a callback that receives timing info
52763
     * @param {Function} videoSegmentTimingInfoFn
52764
     *                   a callback that receives video timing info based on media times and
52765
     *                   any adjustments made by the transmuxer
52766
     * @param {Function} audioSegmentTimingInfoFn
52767
     *                   a callback that receives audio timing info based on media times and
52768
     *                   any adjustments made by the transmuxer
52769
     * @param {boolean}  isEndOfTimeline
52770
     *                   true if this segment represents the last segment in a timeline
52771
     * @param {Function} endedTimelineFn
52772
     *                   a callback made when a timeline is ended, will only be called if
52773
     *                   isEndOfTimeline is true
52774
     * @param {Function} dataFn - a callback that is executed when segment bytes are available
52775
     *                            and ready to use
52776
     * @param {Event} event - the progress event object from XMLHttpRequest
52777
     */
52778
 
52779
    const handleProgress = ({
52780
                                segment,
52781
                                progressFn,
52782
                                trackInfoFn,
52783
                                timingInfoFn,
52784
                                videoSegmentTimingInfoFn,
52785
                                audioSegmentTimingInfoFn,
52786
                                id3Fn,
52787
                                captionsFn,
52788
                                isEndOfTimeline,
52789
                                endedTimelineFn,
52790
                                dataFn
52791
                            }) => event => {
52792
        const request = event.target;
52793
        if (request.aborted) {
52794
            return;
52795
        }
52796
        segment.stats = merge(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data
52797
 
52798
        if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
52799
            segment.stats.firstBytesReceivedAt = Date.now();
52800
        }
52801
        return progressFn(event, segment);
52802
    };
52803
    /**
52804
     * Load all resources and does any processing necessary for a media-segment
52805
     *
52806
     * Features:
52807
     *   decrypts the media-segment if it has a key uri and an iv
52808
     *   aborts *all* requests if *any* one request fails
52809
     *
52810
     * The segment object, at minimum, has the following format:
52811
     * {
52812
     *   resolvedUri: String,
52813
     *   [transmuxer]: Object,
52814
     *   [byterange]: {
52815
     *     offset: Number,
52816
     *     length: Number
52817
     *   },
52818
     *   [key]: {
52819
     *     resolvedUri: String
52820
     *     [byterange]: {
52821
     *       offset: Number,
52822
     *       length: Number
52823
     *     },
52824
     *     iv: {
52825
     *       bytes: Uint32Array
52826
     *     }
52827
     *   },
52828
     *   [map]: {
52829
     *     resolvedUri: String,
52830
     *     [byterange]: {
52831
     *       offset: Number,
52832
     *       length: Number
52833
     *     },
52834
     *     [bytes]: Uint8Array
52835
     *   }
52836
     * }
52837
     * ...where [name] denotes optional properties
52838
     *
52839
     * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
52840
     * @param {Object} xhrOptions - the base options to provide to all xhr requests
52841
     * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
52842
     *                                       decryption routines
52843
     * @param {Object} segment - a simplified copy of the segmentInfo object
52844
     *                           from SegmentLoader
52845
     * @param {Function} abortFn - a callback called (only once) if any piece of a request was
52846
     *                             aborted
52847
     * @param {Function} progressFn - a callback that receives progress events from the main
52848
     *                                segment's xhr request
52849
     * @param {Function} trackInfoFn - a callback that receives track info
52850
     * @param {Function} timingInfoFn - a callback that receives timing info
52851
     * @param {Function} videoSegmentTimingInfoFn
52852
     *                   a callback that receives video timing info based on media times and
52853
     *                   any adjustments made by the transmuxer
52854
     * @param {Function} audioSegmentTimingInfoFn
52855
     *                   a callback that receives audio timing info based on media times and
52856
     *                   any adjustments made by the transmuxer
52857
     * @param {Function} id3Fn - a callback that receives ID3 metadata
52858
     * @param {Function} captionsFn - a callback that receives captions
52859
     * @param {boolean}  isEndOfTimeline
52860
     *                   true if this segment represents the last segment in a timeline
52861
     * @param {Function} endedTimelineFn
52862
     *                   a callback made when a timeline is ended, will only be called if
52863
     *                   isEndOfTimeline is true
52864
     * @param {Function} dataFn - a callback that receives data from the main segment's xhr
52865
     *                            request, transmuxed if needed
52866
     * @param {Function} doneFn - a callback that is executed only once all requests have
52867
     *                            succeeded or failed
52868
     * @return {Function} a function that, when invoked, immediately aborts all
52869
     *                     outstanding requests
52870
     */
52871
 
52872
    const mediaSegmentRequest = ({
52873
                                     xhr,
52874
                                     xhrOptions,
52875
                                     decryptionWorker,
52876
                                     segment,
52877
                                     abortFn,
52878
                                     progressFn,
52879
                                     trackInfoFn,
52880
                                     timingInfoFn,
52881
                                     videoSegmentTimingInfoFn,
52882
                                     audioSegmentTimingInfoFn,
52883
                                     id3Fn,
52884
                                     captionsFn,
52885
                                     isEndOfTimeline,
52886
                                     endedTimelineFn,
52887
                                     dataFn,
52888
                                     doneFn,
52889
                                     onTransmuxerLog
52890
                                 }) => {
52891
        const activeXhrs = [];
52892
        const finishProcessingFn = waitForCompletion({
52893
            activeXhrs,
52894
            decryptionWorker,
52895
            trackInfoFn,
52896
            timingInfoFn,
52897
            videoSegmentTimingInfoFn,
52898
            audioSegmentTimingInfoFn,
52899
            id3Fn,
52900
            captionsFn,
52901
            isEndOfTimeline,
52902
            endedTimelineFn,
52903
            dataFn,
52904
            doneFn,
52905
            onTransmuxerLog
52906
        }); // optionally, request the decryption key
52907
 
52908
        if (segment.key && !segment.key.bytes) {
52909
            const objects = [segment.key];
52910
            if (segment.map && !segment.map.bytes && segment.map.key && segment.map.key.resolvedUri === segment.key.resolvedUri) {
52911
                objects.push(segment.map.key);
52912
            }
52913
            const keyRequestOptions = merge(xhrOptions, {
52914
                uri: segment.key.resolvedUri,
52915
                responseType: 'arraybuffer'
52916
            });
52917
            const keyRequestCallback = handleKeyResponse(segment, objects, finishProcessingFn);
52918
            const keyXhr = xhr(keyRequestOptions, keyRequestCallback);
52919
            activeXhrs.push(keyXhr);
52920
        } // optionally, request the associated media init segment
52921
 
52922
        if (segment.map && !segment.map.bytes) {
52923
            const differentMapKey = segment.map.key && (!segment.key || segment.key.resolvedUri !== segment.map.key.resolvedUri);
52924
            if (differentMapKey) {
52925
                const mapKeyRequestOptions = merge(xhrOptions, {
52926
                    uri: segment.map.key.resolvedUri,
52927
                    responseType: 'arraybuffer'
52928
                });
52929
                const mapKeyRequestCallback = handleKeyResponse(segment, [segment.map.key], finishProcessingFn);
52930
                const mapKeyXhr = xhr(mapKeyRequestOptions, mapKeyRequestCallback);
52931
                activeXhrs.push(mapKeyXhr);
52932
            }
52933
            const initSegmentOptions = merge(xhrOptions, {
52934
                uri: segment.map.resolvedUri,
52935
                responseType: 'arraybuffer',
52936
                headers: segmentXhrHeaders(segment.map)
52937
            });
52938
            const initSegmentRequestCallback = handleInitSegmentResponse({
52939
                segment,
52940
                finishProcessingFn
52941
            });
52942
            const initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback);
52943
            activeXhrs.push(initSegmentXhr);
52944
        }
52945
        const segmentRequestOptions = merge(xhrOptions, {
52946
            uri: segment.part && segment.part.resolvedUri || segment.resolvedUri,
52947
            responseType: 'arraybuffer',
52948
            headers: segmentXhrHeaders(segment)
52949
        });
52950
        const segmentRequestCallback = handleSegmentResponse({
52951
            segment,
52952
            finishProcessingFn,
52953
            responseType: segmentRequestOptions.responseType
52954
        });
52955
        const segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback);
52956
        segmentXhr.addEventListener('progress', handleProgress({
52957
            segment,
52958
            progressFn,
52959
            trackInfoFn,
52960
            timingInfoFn,
52961
            videoSegmentTimingInfoFn,
52962
            audioSegmentTimingInfoFn,
52963
            id3Fn,
52964
            captionsFn,
52965
            isEndOfTimeline,
52966
            endedTimelineFn,
52967
            dataFn
52968
        }));
52969
        activeXhrs.push(segmentXhr); // since all parts of the request must be considered, but should not make callbacks
52970
        // multiple times, provide a shared state object
52971
 
52972
        const loadendState = {};
52973
        activeXhrs.forEach(activeXhr => {
52974
            activeXhr.addEventListener('loadend', handleLoadEnd({
52975
                loadendState,
52976
                abortFn
52977
            }));
52978
        });
52979
        return () => abortAll(activeXhrs);
52980
    };
52981
 
52982
    /**
52983
     * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
52984
     * codec strings, or translating codec strings into objects that can be examined.
52985
     */
52986
    const logFn$1 = logger('CodecUtils');
52987
    /**
52988
     * Returns a set of codec strings parsed from the playlist or the default
52989
     * codec strings if no codecs were specified in the playlist
52990
     *
52991
     * @param {Playlist} media the current media playlist
52992
     * @return {Object} an object with the video and audio codecs
52993
     */
52994
 
52995
    const getCodecs = function (media) {
52996
        // if the codecs were explicitly specified, use them instead of the
52997
        // defaults
52998
        const mediaAttributes = media.attributes || {};
52999
        if (mediaAttributes.CODECS) {
53000
            return parseCodecs(mediaAttributes.CODECS);
53001
        }
53002
    };
53003
    const isMaat = (main, media) => {
53004
        const mediaAttributes = media.attributes || {};
53005
        return main && main.mediaGroups && main.mediaGroups.AUDIO && mediaAttributes.AUDIO && main.mediaGroups.AUDIO[mediaAttributes.AUDIO];
53006
    };
53007
    const isMuxed = (main, media) => {
53008
        if (!isMaat(main, media)) {
53009
            return true;
53010
        }
53011
        const mediaAttributes = media.attributes || {};
53012
        const audioGroup = main.mediaGroups.AUDIO[mediaAttributes.AUDIO];
53013
        for (const groupId in audioGroup) {
53014
            // If an audio group has a URI (the case for HLS, as HLS will use external playlists),
53015
            // or there are listed playlists (the case for DASH, as the manifest will have already
53016
            // provided all of the details necessary to generate the audio playlist, as opposed to
53017
            // HLS' externally requested playlists), then the content is demuxed.
53018
            if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
53019
                return true;
53020
            }
53021
        }
53022
        return false;
53023
    };
53024
    const unwrapCodecList = function (codecList) {
53025
        const codecs = {};
53026
        codecList.forEach(({
53027
                               mediaType,
53028
                               type,
53029
                               details
53030
                           }) => {
53031
            codecs[mediaType] = codecs[mediaType] || [];
53032
            codecs[mediaType].push(translateLegacyCodec(`${type}${details}`));
53033
        });
53034
        Object.keys(codecs).forEach(function (mediaType) {
53035
            if (codecs[mediaType].length > 1) {
53036
                logFn$1(`multiple ${mediaType} codecs found as attributes: ${codecs[mediaType].join(', ')}. Setting playlist codecs to null so that we wait for mux.js to probe segments for real codecs.`);
53037
                codecs[mediaType] = null;
53038
                return;
53039
            }
53040
            codecs[mediaType] = codecs[mediaType][0];
53041
        });
53042
        return codecs;
53043
    };
53044
    const codecCount = function (codecObj) {
53045
        let count = 0;
53046
        if (codecObj.audio) {
53047
            count++;
53048
        }
53049
        if (codecObj.video) {
53050
            count++;
53051
        }
53052
        return count;
53053
    };
53054
    /**
53055
     * Calculates the codec strings for a working configuration of
53056
     * SourceBuffers to play variant streams in a main playlist. If
53057
     * there is no possible working configuration, an empty object will be
53058
     * returned.
53059
     *
53060
     * @param main {Object} the m3u8 object for the main playlist
53061
     * @param media {Object} the m3u8 object for the variant playlist
53062
     * @return {Object} the codec strings.
53063
     *
53064
     * @private
53065
     */
53066
 
53067
    const codecsForPlaylist = function (main, media) {
53068
        const mediaAttributes = media.attributes || {};
53069
        const codecInfo = unwrapCodecList(getCodecs(media) || []); // HLS with multiple-audio tracks must always get an audio codec.
53070
        // Put another way, there is no way to have a video-only multiple-audio HLS!
53071
 
53072
        if (isMaat(main, media) && !codecInfo.audio) {
53073
            if (!isMuxed(main, media)) {
53074
                // It is possible for codecs to be specified on the audio media group playlist but
53075
                // not on the rendition playlist. This is mostly the case for DASH, where audio and
53076
                // video are always separate (and separately specified).
53077
                const defaultCodecs = unwrapCodecList(codecsFromDefault(main, mediaAttributes.AUDIO) || []);
53078
                if (defaultCodecs.audio) {
53079
                    codecInfo.audio = defaultCodecs.audio;
53080
                }
53081
            }
53082
        }
53083
        return codecInfo;
53084
    };
53085
    const logFn = logger('PlaylistSelector');
53086
    const representationToString = function (representation) {
53087
        if (!representation || !representation.playlist) {
53088
            return;
53089
        }
53090
        const playlist = representation.playlist;
53091
        return JSON.stringify({
53092
            id: playlist.id,
53093
            bandwidth: representation.bandwidth,
53094
            width: representation.width,
53095
            height: representation.height,
53096
            codecs: playlist.attributes && playlist.attributes.CODECS || ''
53097
        });
53098
    }; // Utilities
53099
 
53100
    /**
53101
     * Returns the CSS value for the specified property on an element
53102
     * using `getComputedStyle`. Firefox has a long-standing issue where
53103
     * getComputedStyle() may return null when running in an iframe with
53104
     * `display: none`.
53105
     *
53106
     * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
53107
     * @param {HTMLElement} el the htmlelement to work on
53108
     * @param {string} the proprety to get the style for
53109
     */
53110
 
53111
    const safeGetComputedStyle = function (el, property) {
53112
        if (!el) {
53113
            return '';
53114
        }
53115
        const result = window.getComputedStyle(el);
53116
        if (!result) {
53117
            return '';
53118
        }
53119
        return result[property];
53120
    };
53121
    /**
53122
     * Resuable stable sort function
53123
     *
53124
     * @param {Playlists} array
53125
     * @param {Function} sortFn Different comparators
53126
     * @function stableSort
53127
     */
53128
 
53129
    const stableSort = function (array, sortFn) {
53130
        const newArray = array.slice();
53131
        array.sort(function (left, right) {
53132
            const cmp = sortFn(left, right);
53133
            if (cmp === 0) {
53134
                return newArray.indexOf(left) - newArray.indexOf(right);
53135
            }
53136
            return cmp;
53137
        });
53138
    };
53139
    /**
53140
     * A comparator function to sort two playlist object by bandwidth.
53141
     *
53142
     * @param {Object} left a media playlist object
53143
     * @param {Object} right a media playlist object
53144
     * @return {number} Greater than zero if the bandwidth attribute of
53145
     * left is greater than the corresponding attribute of right. Less
53146
     * than zero if the bandwidth of right is greater than left and
53147
     * exactly zero if the two are equal.
53148
     */
53149
 
53150
    const comparePlaylistBandwidth = function (left, right) {
53151
        let leftBandwidth;
53152
        let rightBandwidth;
53153
        if (left.attributes.BANDWIDTH) {
53154
            leftBandwidth = left.attributes.BANDWIDTH;
53155
        }
53156
        leftBandwidth = leftBandwidth || window.Number.MAX_VALUE;
53157
        if (right.attributes.BANDWIDTH) {
53158
            rightBandwidth = right.attributes.BANDWIDTH;
53159
        }
53160
        rightBandwidth = rightBandwidth || window.Number.MAX_VALUE;
53161
        return leftBandwidth - rightBandwidth;
53162
    };
53163
    /**
53164
     * A comparator function to sort two playlist object by resolution (width).
53165
     *
53166
     * @param {Object} left a media playlist object
53167
     * @param {Object} right a media playlist object
53168
     * @return {number} Greater than zero if the resolution.width attribute of
53169
     * left is greater than the corresponding attribute of right. Less
53170
     * than zero if the resolution.width of right is greater than left and
53171
     * exactly zero if the two are equal.
53172
     */
53173
 
53174
    const comparePlaylistResolution = function (left, right) {
53175
        let leftWidth;
53176
        let rightWidth;
53177
        if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
53178
            leftWidth = left.attributes.RESOLUTION.width;
53179
        }
53180
        leftWidth = leftWidth || window.Number.MAX_VALUE;
53181
        if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
53182
            rightWidth = right.attributes.RESOLUTION.width;
53183
        }
53184
        rightWidth = rightWidth || window.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
53185
        // have the same media dimensions/ resolution
53186
 
53187
        if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
53188
            return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
53189
        }
53190
        return leftWidth - rightWidth;
53191
    };
53192
    /**
53193
     * Chooses the appropriate media playlist based on bandwidth and player size
53194
     *
53195
     * @param {Object} main
53196
     *        Object representation of the main manifest
53197
     * @param {number} playerBandwidth
53198
     *        Current calculated bandwidth of the player
53199
     * @param {number} playerWidth
53200
     *        Current width of the player element (should account for the device pixel ratio)
53201
     * @param {number} playerHeight
53202
     *        Current height of the player element (should account for the device pixel ratio)
53203
     * @param {boolean} limitRenditionByPlayerDimensions
53204
     *        True if the player width and height should be used during the selection, false otherwise
53205
     * @param {Object} playlistController
53206
     *        the current playlistController object
53207
     * @return {Playlist} the highest bitrate playlist less than the
53208
     * currently detected bandwidth, accounting for some amount of
53209
     * bandwidth variance
53210
     */
53211
 
53212
    let simpleSelector = function (main, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions, playlistController) {
53213
        // If we end up getting called before `main` is available, exit early
53214
        if (!main) {
53215
            return;
53216
        }
53217
        const options = {
53218
            bandwidth: playerBandwidth,
53219
            width: playerWidth,
53220
            height: playerHeight,
53221
            limitRenditionByPlayerDimensions
53222
        };
53223
        let playlists = main.playlists; // if playlist is audio only, select between currently active audio group playlists.
53224
 
53225
        if (Playlist.isAudioOnly(main)) {
53226
            playlists = playlistController.getAudioTrackPlaylists_(); // add audioOnly to options so that we log audioOnly: true
53227
            // at the buttom of this function for debugging.
53228
 
53229
            options.audioOnly = true;
53230
        } // convert the playlists to an intermediary representation to make comparisons easier
53231
 
53232
        let sortedPlaylistReps = playlists.map(playlist => {
53233
            let bandwidth;
53234
            const width = playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
53235
            const height = playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
53236
            bandwidth = playlist.attributes && playlist.attributes.BANDWIDTH;
53237
            bandwidth = bandwidth || window.Number.MAX_VALUE;
53238
            return {
53239
                bandwidth,
53240
                width,
53241
                height,
53242
                playlist
53243
            };
53244
        });
53245
        stableSort(sortedPlaylistReps, (left, right) => left.bandwidth - right.bandwidth); // filter out any playlists that have been excluded due to
53246
        // incompatible configurations
53247
 
53248
        sortedPlaylistReps = sortedPlaylistReps.filter(rep => !Playlist.isIncompatible(rep.playlist)); // filter out any playlists that have been disabled manually through the representations
53249
        // api or excluded temporarily due to playback errors.
53250
 
53251
        let enabledPlaylistReps = sortedPlaylistReps.filter(rep => Playlist.isEnabled(rep.playlist));
53252
        if (!enabledPlaylistReps.length) {
53253
            // if there are no enabled playlists, then they have all been excluded or disabled
53254
            // by the user through the representations api. In this case, ignore exclusion and
53255
            // fallback to what the user wants by using playlists the user has not disabled.
53256
            enabledPlaylistReps = sortedPlaylistReps.filter(rep => !Playlist.isDisabled(rep.playlist));
53257
        } // filter out any variant that has greater effective bitrate
53258
        // than the current estimated bandwidth
53259
 
53260
        const bandwidthPlaylistReps = enabledPlaylistReps.filter(rep => rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth);
53261
        let highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth
53262
        // and then taking the very first element
53263
 
53264
        const bandwidthBestRep = bandwidthPlaylistReps.filter(rep => rep.bandwidth === highestRemainingBandwidthRep.bandwidth)[0]; // if we're not going to limit renditions by player size, make an early decision.
53265
 
53266
        if (limitRenditionByPlayerDimensions === false) {
53267
            const chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
53268
            if (chosenRep && chosenRep.playlist) {
53269
                let type = 'sortedPlaylistReps';
53270
                if (bandwidthBestRep) {
53271
                    type = 'bandwidthBestRep';
53272
                }
53273
                if (enabledPlaylistReps[0]) {
53274
                    type = 'enabledPlaylistReps';
53275
                }
53276
                logFn(`choosing ${representationToString(chosenRep)} using ${type} with options`, options);
53277
                return chosenRep.playlist;
53278
            }
53279
            logFn('could not choose a playlist with options', options);
53280
            return null;
53281
        } // filter out playlists without resolution information
53282
 
53283
        const haveResolution = bandwidthPlaylistReps.filter(rep => rep.width && rep.height); // sort variants by resolution
53284
 
53285
        stableSort(haveResolution, (left, right) => left.width - right.width); // if we have the exact resolution as the player use it
53286
 
53287
        const resolutionBestRepList = haveResolution.filter(rep => rep.width === playerWidth && rep.height === playerHeight);
53288
        highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution
53289
 
53290
        const resolutionBestRep = resolutionBestRepList.filter(rep => rep.bandwidth === highestRemainingBandwidthRep.bandwidth)[0];
53291
        let resolutionPlusOneList;
53292
        let resolutionPlusOneSmallest;
53293
        let resolutionPlusOneRep; // find the smallest variant that is larger than the player
53294
        // if there is no match of exact resolution
53295
 
53296
        if (!resolutionBestRep) {
53297
            resolutionPlusOneList = haveResolution.filter(rep => rep.width > playerWidth || rep.height > playerHeight); // find all the variants have the same smallest resolution
53298
 
53299
            resolutionPlusOneSmallest = resolutionPlusOneList.filter(rep => rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height); // ensure that we also pick the highest bandwidth variant that
53300
            // is just-larger-than the video player
53301
 
53302
            highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
53303
            resolutionPlusOneRep = resolutionPlusOneSmallest.filter(rep => rep.bandwidth === highestRemainingBandwidthRep.bandwidth)[0];
53304
        }
53305
        let leastPixelDiffRep; // If this selector proves to be better than others,
53306
        // resolutionPlusOneRep and resolutionBestRep and all
53307
        // the code involving them should be removed.
53308
 
53309
        if (playlistController.leastPixelDiffSelector) {
53310
            // find the variant that is closest to the player's pixel size
53311
            const leastPixelDiffList = haveResolution.map(rep => {
53312
                rep.pixelDiff = Math.abs(rep.width - playerWidth) + Math.abs(rep.height - playerHeight);
53313
                return rep;
53314
            }); // get the highest bandwidth, closest resolution playlist
53315
 
53316
            stableSort(leastPixelDiffList, (left, right) => {
53317
                // sort by highest bandwidth if pixelDiff is the same
53318
                if (left.pixelDiff === right.pixelDiff) {
53319
                    return right.bandwidth - left.bandwidth;
53320
                }
53321
                return left.pixelDiff - right.pixelDiff;
53322
            });
53323
            leastPixelDiffRep = leastPixelDiffList[0];
53324
        } // fallback chain of variants
53325
 
53326
        const chosenRep = leastPixelDiffRep || resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
53327
        if (chosenRep && chosenRep.playlist) {
53328
            let type = 'sortedPlaylistReps';
53329
            if (leastPixelDiffRep) {
53330
                type = 'leastPixelDiffRep';
53331
            } else if (resolutionPlusOneRep) {
53332
                type = 'resolutionPlusOneRep';
53333
            } else if (resolutionBestRep) {
53334
                type = 'resolutionBestRep';
53335
            } else if (bandwidthBestRep) {
53336
                type = 'bandwidthBestRep';
53337
            } else if (enabledPlaylistReps[0]) {
53338
                type = 'enabledPlaylistReps';
53339
            }
53340
            logFn(`choosing ${representationToString(chosenRep)} using ${type} with options`, options);
53341
            return chosenRep.playlist;
53342
        }
53343
        logFn('could not choose a playlist with options', options);
53344
        return null;
53345
    };
53346
 
53347
    /**
53348
     * Chooses the appropriate media playlist based on the most recent
53349
     * bandwidth estimate and the player size.
53350
     *
53351
     * Expects to be called within the context of an instance of VhsHandler
53352
     *
53353
     * @return {Playlist} the highest bitrate playlist less than the
53354
     * currently detected bandwidth, accounting for some amount of
53355
     * bandwidth variance
53356
     */
53357
 
53358
    const lastBandwidthSelector = function () {
53359
        const pixelRatio = this.useDevicePixelRatio ? window.devicePixelRatio || 1 : 1;
53360
        return simpleSelector(this.playlists.main, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio, parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio, this.limitRenditionByPlayerDimensions, this.playlistController_);
53361
    };
53362
    /**
53363
     * Chooses the appropriate media playlist based on an
53364
     * exponential-weighted moving average of the bandwidth after
53365
     * filtering for player size.
53366
     *
53367
     * Expects to be called within the context of an instance of VhsHandler
53368
     *
53369
     * @param {number} decay - a number between 0 and 1. Higher values of
53370
     * this parameter will cause previous bandwidth estimates to lose
53371
     * significance more quickly.
53372
     * @return {Function} a function which can be invoked to create a new
53373
     * playlist selector function.
53374
     * @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
53375
     */
53376
 
53377
    const movingAverageBandwidthSelector = function (decay) {
53378
        let average = -1;
53379
        let lastSystemBandwidth = -1;
53380
        if (decay < 0 || decay > 1) {
53381
            throw new Error('Moving average bandwidth decay must be between 0 and 1.');
53382
        }
53383
        return function () {
53384
            const pixelRatio = this.useDevicePixelRatio ? window.devicePixelRatio || 1 : 1;
53385
            if (average < 0) {
53386
                average = this.systemBandwidth;
53387
                lastSystemBandwidth = this.systemBandwidth;
53388
            } // stop the average value from decaying for every 250ms
53389
            // when the systemBandwidth is constant
53390
            // and
53391
            // stop average from setting to a very low value when the
53392
            // systemBandwidth becomes 0 in case of chunk cancellation
53393
 
53394
            if (this.systemBandwidth > 0 && this.systemBandwidth !== lastSystemBandwidth) {
53395
                average = decay * this.systemBandwidth + (1 - decay) * average;
53396
                lastSystemBandwidth = this.systemBandwidth;
53397
            }
53398
            return simpleSelector(this.playlists.main, average, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio, parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio, this.limitRenditionByPlayerDimensions, this.playlistController_);
53399
        };
53400
    };
53401
    /**
53402
     * Chooses the appropriate media playlist based on the potential to rebuffer
53403
     *
53404
     * @param {Object} settings
53405
     *        Object of information required to use this selector
53406
     * @param {Object} settings.main
53407
     *        Object representation of the main manifest
53408
     * @param {number} settings.currentTime
53409
     *        The current time of the player
53410
     * @param {number} settings.bandwidth
53411
     *        Current measured bandwidth
53412
     * @param {number} settings.duration
53413
     *        Duration of the media
53414
     * @param {number} settings.segmentDuration
53415
     *        Segment duration to be used in round trip time calculations
53416
     * @param {number} settings.timeUntilRebuffer
53417
     *        Time left in seconds until the player has to rebuffer
53418
     * @param {number} settings.currentTimeline
53419
     *        The current timeline segments are being loaded from
53420
     * @param {SyncController} settings.syncController
53421
     *        SyncController for determining if we have a sync point for a given playlist
53422
     * @return {Object|null}
53423
     *         {Object} return.playlist
53424
     *         The highest bandwidth playlist with the least amount of rebuffering
53425
     *         {Number} return.rebufferingImpact
53426
     *         The amount of time in seconds switching to this playlist will rebuffer. A
53427
     *         negative value means that switching will cause zero rebuffering.
53428
     */
53429
 
53430
    const minRebufferMaxBandwidthSelector = function (settings) {
53431
        const {
53432
            main,
53433
            currentTime,
53434
            bandwidth,
53435
            duration,
53436
            segmentDuration,
53437
            timeUntilRebuffer,
53438
            currentTimeline,
53439
            syncController
53440
        } = settings; // filter out any playlists that have been excluded due to
53441
        // incompatible configurations
53442
 
53443
        const compatiblePlaylists = main.playlists.filter(playlist => !Playlist.isIncompatible(playlist)); // filter out any playlists that have been disabled manually through the representations
53444
        // api or excluded temporarily due to playback errors.
53445
 
53446
        let enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
53447
        if (!enabledPlaylists.length) {
53448
            // if there are no enabled playlists, then they have all been excluded or disabled
53449
            // by the user through the representations api. In this case, ignore exclusion and
53450
            // fallback to what the user wants by using playlists the user has not disabled.
53451
            enabledPlaylists = compatiblePlaylists.filter(playlist => !Playlist.isDisabled(playlist));
53452
        }
53453
        const bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
53454
        const rebufferingEstimates = bandwidthPlaylists.map(playlist => {
53455
            const syncPoint = syncController.getSyncPoint(playlist, duration, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a
53456
            // sync request first. This will double the request time
53457
 
53458
            const numRequests = syncPoint ? 1 : 2;
53459
            const requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
53460
            const rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
53461
            return {
53462
                playlist,
53463
                rebufferingImpact
53464
            };
53465
        });
53466
        const noRebufferingPlaylists = rebufferingEstimates.filter(estimate => estimate.rebufferingImpact <= 0); // Sort by bandwidth DESC
53467
 
53468
        stableSort(noRebufferingPlaylists, (a, b) => comparePlaylistBandwidth(b.playlist, a.playlist));
53469
        if (noRebufferingPlaylists.length) {
53470
            return noRebufferingPlaylists[0];
53471
        }
53472
        stableSort(rebufferingEstimates, (a, b) => a.rebufferingImpact - b.rebufferingImpact);
53473
        return rebufferingEstimates[0] || null;
53474
    };
53475
    /**
53476
     * Chooses the appropriate media playlist, which in this case is the lowest bitrate
53477
     * one with video.  If no renditions with video exist, return the lowest audio rendition.
53478
     *
53479
     * Expects to be called within the context of an instance of VhsHandler
53480
     *
53481
     * @return {Object|null}
53482
     *         {Object} return.playlist
53483
     *         The lowest bitrate playlist that contains a video codec.  If no such rendition
53484
     *         exists pick the lowest audio rendition.
53485
     */
53486
 
53487
    const lowestBitrateCompatibleVariantSelector = function () {
53488
        // filter out any playlists that have been excluded due to
53489
        // incompatible configurations or playback errors
53490
        const playlists = this.playlists.main.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate
53491
 
53492
        stableSort(playlists, (a, b) => comparePlaylistBandwidth(a, b)); // Parse and assume that playlists with no video codec have no video
53493
        // (this is not necessarily true, although it is generally true).
53494
        //
53495
        // If an entire manifest has no valid videos everything will get filtered
53496
        // out.
53497
 
53498
        const playlistsWithVideo = playlists.filter(playlist => !!codecsForPlaylist(this.playlists.main, playlist).video);
53499
        return playlistsWithVideo[0] || null;
53500
    };
53501
 
53502
    /**
53503
     * Combine all segments into a single Uint8Array
53504
     *
53505
     * @param {Object} segmentObj
53506
     * @return {Uint8Array} concatenated bytes
53507
     * @private
53508
     */
53509
    const concatSegments = segmentObj => {
53510
        let offset = 0;
53511
        let tempBuffer;
53512
        if (segmentObj.bytes) {
53513
            tempBuffer = new Uint8Array(segmentObj.bytes); // combine the individual segments into one large typed-array
53514
 
53515
            segmentObj.segments.forEach(segment => {
53516
                tempBuffer.set(segment, offset);
53517
                offset += segment.byteLength;
53518
            });
53519
        }
53520
        return tempBuffer;
53521
    };
53522
 
53523
    /**
53524
     * @file text-tracks.js
53525
     */
53526
    /**
53527
     * Create captions text tracks on video.js if they do not exist
53528
     *
53529
     * @param {Object} inbandTextTracks a reference to current inbandTextTracks
53530
     * @param {Object} tech the video.js tech
53531
     * @param {Object} captionStream the caption stream to create
53532
     * @private
53533
     */
53534
 
53535
    const createCaptionsTrackIfNotExists = function (inbandTextTracks, tech, captionStream) {
53536
        if (!inbandTextTracks[captionStream]) {
53537
            tech.trigger({
53538
                type: 'usage',
53539
                name: 'vhs-608'
53540
            });
53541
            let instreamId = captionStream; // we need to translate SERVICEn for 708 to how mux.js currently labels them
53542
 
53543
            if (/^cc708_/.test(captionStream)) {
53544
                instreamId = 'SERVICE' + captionStream.split('_')[1];
53545
            }
53546
            const track = tech.textTracks().getTrackById(instreamId);
53547
            if (track) {
53548
                // Resuse an existing track with a CC# id because this was
53549
                // very likely created by videojs-contrib-hls from information
53550
                // in the m3u8 for us to use
53551
                inbandTextTracks[captionStream] = track;
53552
            } else {
53553
                // This section gets called when we have caption services that aren't specified in the manifest.
53554
                // Manifest level caption services are handled in media-groups.js under CLOSED-CAPTIONS.
53555
                const captionServices = tech.options_.vhs && tech.options_.vhs.captionServices || {};
53556
                let label = captionStream;
53557
                let language = captionStream;
53558
                let def = false;
53559
                const captionService = captionServices[instreamId];
53560
                if (captionService) {
53561
                    label = captionService.label;
53562
                    language = captionService.language;
53563
                    def = captionService.default;
53564
                } // Otherwise, create a track with the default `CC#` label and
53565
                // without a language
53566
 
53567
                inbandTextTracks[captionStream] = tech.addRemoteTextTrack({
53568
                    kind: 'captions',
53569
                    id: instreamId,
53570
                    // TODO: investigate why this doesn't seem to turn the caption on by default
53571
                    default: def,
53572
                    label,
53573
                    language
53574
                }, false).track;
53575
            }
53576
        }
53577
    };
53578
    /**
53579
     * Add caption text track data to a source handler given an array of captions
53580
     *
53581
     * @param {Object}
53582
     *   @param {Object} inbandTextTracks the inband text tracks
53583
     *   @param {number} timestampOffset the timestamp offset of the source buffer
53584
     *   @param {Array} captionArray an array of caption data
53585
     * @private
53586
     */
53587
 
53588
    const addCaptionData = function ({
53589
                                         inbandTextTracks,
53590
                                         captionArray,
53591
                                         timestampOffset
53592
                                     }) {
53593
        if (!captionArray) {
53594
            return;
53595
        }
53596
        const Cue = window.WebKitDataCue || window.VTTCue;
53597
        captionArray.forEach(caption => {
53598
            const track = caption.stream; // in CEA 608 captions, video.js/mux.js sends a content array
53599
            // with positioning data
53600
 
53601
            if (caption.content) {
53602
                caption.content.forEach(value => {
53603
                    const cue = new Cue(caption.startTime + timestampOffset, caption.endTime + timestampOffset, value.text);
53604
                    cue.line = value.line;
53605
                    cue.align = 'left';
53606
                    cue.position = value.position;
53607
                    cue.positionAlign = 'line-left';
53608
                    inbandTextTracks[track].addCue(cue);
53609
                });
53610
            } else {
53611
                // otherwise, a text value with combined captions is sent
53612
                inbandTextTracks[track].addCue(new Cue(caption.startTime + timestampOffset, caption.endTime + timestampOffset, caption.text));
53613
            }
53614
        });
53615
    };
53616
    /**
53617
     * Define properties on a cue for backwards compatability,
53618
     * but warn the user that the way that they are using it
53619
     * is depricated and will be removed at a later date.
53620
     *
53621
     * @param {Cue} cue the cue to add the properties on
53622
     * @private
53623
     */
53624
 
53625
    const deprecateOldCue = function (cue) {
53626
        Object.defineProperties(cue.frame, {
53627
            id: {
53628
                get() {
53629
                    videojs.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
53630
                    return cue.value.key;
53631
                }
53632
            },
53633
            value: {
53634
                get() {
53635
                    videojs.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
53636
                    return cue.value.data;
53637
                }
53638
            },
53639
            privateData: {
53640
                get() {
53641
                    videojs.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
53642
                    return cue.value.data;
53643
                }
53644
            }
53645
        });
53646
    };
53647
    /**
53648
     * Add metadata text track data to a source handler given an array of metadata
53649
     *
53650
     * @param {Object}
53651
     *   @param {Object} inbandTextTracks the inband text tracks
53652
     *   @param {Array} metadataArray an array of meta data
53653
     *   @param {number} timestampOffset the timestamp offset of the source buffer
53654
     *   @param {number} videoDuration the duration of the video
53655
     * @private
53656
     */
53657
 
53658
    const addMetadata = ({
53659
                             inbandTextTracks,
53660
                             metadataArray,
53661
                             timestampOffset,
53662
                             videoDuration
53663
                         }) => {
53664
        if (!metadataArray) {
53665
            return;
53666
        }
53667
        const Cue = window.WebKitDataCue || window.VTTCue;
53668
        const metadataTrack = inbandTextTracks.metadataTrack_;
53669
        if (!metadataTrack) {
53670
            return;
53671
        }
53672
        metadataArray.forEach(metadata => {
53673
            const time = metadata.cueTime + timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN,
53674
            // ignore this bit of metadata.
53675
            // This likely occurs when you have an non-timed ID3 tag like TIT2,
53676
            // which is the "Title/Songname/Content description" frame
53677
 
53678
            if (typeof time !== 'number' || window.isNaN(time) || time < 0 || !(time < Infinity)) {
53679
                return;
53680
            } // If we have no frames, we can't create a cue.
53681
 
53682
            if (!metadata.frames || !metadata.frames.length) {
53683
                return;
53684
            }
53685
            metadata.frames.forEach(frame => {
53686
                const cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
53687
                cue.frame = frame;
53688
                cue.value = frame;
53689
                deprecateOldCue(cue);
53690
                metadataTrack.addCue(cue);
53691
            });
53692
        });
53693
        if (!metadataTrack.cues || !metadataTrack.cues.length) {
53694
            return;
53695
        } // Updating the metadeta cues so that
53696
        // the endTime of each cue is the startTime of the next cue
53697
        // the endTime of last cue is the duration of the video
53698
 
53699
        const cues = metadataTrack.cues;
53700
        const cuesArray = []; // Create a copy of the TextTrackCueList...
53701
        // ...disregarding cues with a falsey value
53702
 
53703
        for (let i = 0; i < cues.length; i++) {
53704
            if (cues[i]) {
53705
                cuesArray.push(cues[i]);
53706
            }
53707
        } // Group cues by their startTime value
53708
 
53709
        const cuesGroupedByStartTime = cuesArray.reduce((obj, cue) => {
53710
            const timeSlot = obj[cue.startTime] || [];
53711
            timeSlot.push(cue);
53712
            obj[cue.startTime] = timeSlot;
53713
            return obj;
53714
        }, {}); // Sort startTimes by ascending order
53715
 
53716
        const sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort((a, b) => Number(a) - Number(b)); // Map each cue group's endTime to the next group's startTime
53717
 
53718
        sortedStartTimes.forEach((startTime, idx) => {
53719
            const cueGroup = cuesGroupedByStartTime[startTime];
53720
            const finiteDuration = isFinite(videoDuration) ? videoDuration : startTime;
53721
            const nextTime = Number(sortedStartTimes[idx + 1]) || finiteDuration; // Map each cue's endTime the next group's startTime
53722
 
53723
            cueGroup.forEach(cue => {
53724
                cue.endTime = nextTime;
53725
            });
53726
        });
53727
    }; // object for mapping daterange attributes
53728
 
53729
    const dateRangeAttr = {
53730
        id: 'ID',
53731
        class: 'CLASS',
53732
        startDate: 'START-DATE',
53733
        duration: 'DURATION',
53734
        endDate: 'END-DATE',
53735
        endOnNext: 'END-ON-NEXT',
53736
        plannedDuration: 'PLANNED-DURATION',
53737
        scte35Out: 'SCTE35-OUT',
53738
        scte35In: 'SCTE35-IN'
53739
    };
53740
    const dateRangeKeysToOmit = new Set(['id', 'class', 'startDate', 'duration', 'endDate', 'endOnNext', 'startTime', 'endTime', 'processDateRange']);
53741
    /**
53742
     * Add DateRange metadata text track to a source handler given an array of metadata
53743
     *
53744
     * @param {Object}
53745
     *   @param {Object} inbandTextTracks the inband text tracks
53746
     *   @param {Array} dateRanges parsed media playlist
53747
     * @private
53748
     */
53749
 
53750
    const addDateRangeMetadata = ({
53751
                                      inbandTextTracks,
53752
                                      dateRanges
53753
                                  }) => {
53754
        const metadataTrack = inbandTextTracks.metadataTrack_;
53755
        if (!metadataTrack) {
53756
            return;
53757
        }
53758
        const Cue = window.WebKitDataCue || window.VTTCue;
53759
        dateRanges.forEach(dateRange => {
53760
            // we generate multiple cues for each date range with different attributes
53761
            for (const key of Object.keys(dateRange)) {
53762
                if (dateRangeKeysToOmit.has(key)) {
53763
                    continue;
53764
                }
53765
                const cue = new Cue(dateRange.startTime, dateRange.endTime, '');
53766
                cue.id = dateRange.id;
53767
                cue.type = 'com.apple.quicktime.HLS';
53768
                cue.value = {
53769
                    key: dateRangeAttr[key],
53770
                    data: dateRange[key]
53771
                };
53772
                if (key === 'scte35Out' || key === 'scte35In') {
53773
                    cue.value.data = new Uint8Array(cue.value.data.match(/[\da-f]{2}/gi)).buffer;
53774
                }
53775
                metadataTrack.addCue(cue);
53776
            }
53777
            dateRange.processDateRange();
53778
        });
53779
    };
53780
    /**
53781
     * Create metadata text track on video.js if it does not exist
53782
     *
53783
     * @param {Object} inbandTextTracks a reference to current inbandTextTracks
53784
     * @param {string} dispatchType the inband metadata track dispatch type
53785
     * @param {Object} tech the video.js tech
53786
     * @private
53787
     */
53788
 
53789
    const createMetadataTrackIfNotExists = (inbandTextTracks, dispatchType, tech) => {
53790
        if (inbandTextTracks.metadataTrack_) {
53791
            return;
53792
        }
53793
        inbandTextTracks.metadataTrack_ = tech.addRemoteTextTrack({
53794
            kind: 'metadata',
53795
            label: 'Timed Metadata'
53796
        }, false).track;
53797
        if (!videojs.browser.IS_ANY_SAFARI) {
53798
            inbandTextTracks.metadataTrack_.inBandMetadataTrackDispatchType = dispatchType;
53799
        }
53800
    };
53801
    /**
53802
     * Remove cues from a track on video.js.
53803
     *
53804
     * @param {Double} start start of where we should remove the cue
53805
     * @param {Double} end end of where the we should remove the cue
53806
     * @param {Object} track the text track to remove the cues from
53807
     * @private
53808
     */
53809
 
53810
    const removeCuesFromTrack = function (start, end, track) {
53811
        let i;
53812
        let cue;
53813
        if (!track) {
53814
            return;
53815
        }
53816
        if (!track.cues) {
53817
            return;
53818
        }
53819
        i = track.cues.length;
53820
        while (i--) {
53821
            cue = track.cues[i]; // Remove any cue within the provided start and end time
53822
 
53823
            if (cue.startTime >= start && cue.endTime <= end) {
53824
                track.removeCue(cue);
53825
            }
53826
        }
53827
    };
53828
    /**
53829
     * Remove duplicate cues from a track on video.js (a cue is considered a
53830
     * duplicate if it has the same time interval and text as another)
53831
     *
53832
     * @param {Object} track the text track to remove the duplicate cues from
53833
     * @private
53834
     */
53835
 
53836
    const removeDuplicateCuesFromTrack = function (track) {
53837
        const cues = track.cues;
53838
        if (!cues) {
53839
            return;
53840
        }
53841
        const uniqueCues = {};
53842
        for (let i = cues.length - 1; i >= 0; i--) {
53843
            const cue = cues[i];
53844
            const cueKey = `${cue.startTime}-${cue.endTime}-${cue.text}`;
53845
            if (uniqueCues[cueKey]) {
53846
                track.removeCue(cue);
53847
            } else {
53848
                uniqueCues[cueKey] = cue;
53849
            }
53850
        }
53851
    };
53852
 
53853
    /**
53854
     * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
53855
     * front of current time.
53856
     *
53857
     * @param {Array} buffer
53858
     *        The current buffer of gop information
53859
     * @param {number} currentTime
53860
     *        The current time
53861
     * @param {Double} mapping
53862
     *        Offset to map display time to stream presentation time
53863
     * @return {Array}
53864
     *         List of gops considered safe to append over
53865
     */
53866
 
53867
    const gopsSafeToAlignWith = (buffer, currentTime, mapping) => {
53868
        if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) {
53869
            return [];
53870
        } // pts value for current time + 3 seconds to give a bit more wiggle room
53871
 
53872
        const currentTimePts = Math.ceil((currentTime - mapping + 3) * clock_1);
53873
        let i;
53874
        for (i = 0; i < buffer.length; i++) {
53875
            if (buffer[i].pts > currentTimePts) {
53876
                break;
53877
            }
53878
        }
53879
        return buffer.slice(i);
53880
    };
53881
    /**
53882
     * Appends gop information (timing and byteLength) received by the transmuxer for the
53883
     * gops appended in the last call to appendBuffer
53884
     *
53885
     * @param {Array} buffer
53886
     *        The current buffer of gop information
53887
     * @param {Array} gops
53888
     *        List of new gop information
53889
     * @param {boolean} replace
53890
     *        If true, replace the buffer with the new gop information. If false, append the
53891
     *        new gop information to the buffer in the right location of time.
53892
     * @return {Array}
53893
     *         Updated list of gop information
53894
     */
53895
 
53896
    const updateGopBuffer = (buffer, gops, replace) => {
53897
        if (!gops.length) {
53898
            return buffer;
53899
        }
53900
        if (replace) {
53901
            // If we are in safe append mode, then completely overwrite the gop buffer
53902
            // with the most recent appeneded data. This will make sure that when appending
53903
            // future segments, we only try to align with gops that are both ahead of current
53904
            // time and in the last segment appended.
53905
            return gops.slice();
53906
        }
53907
        const start = gops[0].pts;
53908
        let i = 0;
53909
        for (i; i < buffer.length; i++) {
53910
            if (buffer[i].pts >= start) {
53911
                break;
53912
            }
53913
        }
53914
        return buffer.slice(0, i).concat(gops);
53915
    };
53916
    /**
53917
     * Removes gop information in buffer that overlaps with provided start and end
53918
     *
53919
     * @param {Array} buffer
53920
     *        The current buffer of gop information
53921
     * @param {Double} start
53922
     *        position to start the remove at
53923
     * @param {Double} end
53924
     *        position to end the remove at
53925
     * @param {Double} mapping
53926
     *        Offset to map display time to stream presentation time
53927
     */
53928
 
53929
    const removeGopBuffer = (buffer, start, end, mapping) => {
53930
        const startPts = Math.ceil((start - mapping) * clock_1);
53931
        const endPts = Math.ceil((end - mapping) * clock_1);
53932
        const updatedBuffer = buffer.slice();
53933
        let i = buffer.length;
53934
        while (i--) {
53935
            if (buffer[i].pts <= endPts) {
53936
                break;
53937
            }
53938
        }
53939
        if (i === -1) {
53940
            // no removal because end of remove range is before start of buffer
53941
            return updatedBuffer;
53942
        }
53943
        let j = i + 1;
53944
        while (j--) {
53945
            if (buffer[j].pts <= startPts) {
53946
                break;
53947
            }
53948
        } // clamp remove range start to 0 index
53949
 
53950
        j = Math.max(j, 0);
53951
        updatedBuffer.splice(j, i - j + 1);
53952
        return updatedBuffer;
53953
    };
53954
    const shallowEqual = function (a, b) {
53955
        // if both are undefined
53956
        // or one or the other is undefined
53957
        // they are not equal
53958
        if (!a && !b || !a && b || a && !b) {
53959
            return false;
53960
        } // they are the same object and thus, equal
53961
 
53962
        if (a === b) {
53963
            return true;
53964
        } // sort keys so we can make sure they have
53965
        // all the same keys later.
53966
 
53967
        const akeys = Object.keys(a).sort();
53968
        const bkeys = Object.keys(b).sort(); // different number of keys, not equal
53969
 
53970
        if (akeys.length !== bkeys.length) {
53971
            return false;
53972
        }
53973
        for (let i = 0; i < akeys.length; i++) {
53974
            const key = akeys[i]; // different sorted keys, not equal
53975
 
53976
            if (key !== bkeys[i]) {
53977
                return false;
53978
            } // different values, not equal
53979
 
53980
            if (a[key] !== b[key]) {
53981
                return false;
53982
            }
53983
        }
53984
        return true;
53985
    };
53986
 
53987
    // https://www.w3.org/TR/WebIDL-1/#quotaexceedederror
53988
    const QUOTA_EXCEEDED_ERR = 22;
53989
 
53990
    /**
53991
     * The segment loader has no recourse except to fetch a segment in the
53992
     * current playlist and use the internal timestamps in that segment to
53993
     * generate a syncPoint. This function returns a good candidate index
53994
     * for that process.
53995
     *
53996
     * @param {Array} segments - the segments array from a playlist.
53997
     * @return {number} An index of a segment from the playlist to load
53998
     */
53999
 
54000
    const getSyncSegmentCandidate = function (currentTimeline, segments, targetTime) {
54001
        segments = segments || [];
54002
        const timelineSegments = [];
54003
        let time = 0;
54004
        for (let i = 0; i < segments.length; i++) {
54005
            const segment = segments[i];
54006
            if (currentTimeline === segment.timeline) {
54007
                timelineSegments.push(i);
54008
                time += segment.duration;
54009
                if (time > targetTime) {
54010
                    return i;
54011
                }
54012
            }
54013
        }
54014
        if (timelineSegments.length === 0) {
54015
            return 0;
54016
        } // default to the last timeline segment
54017
 
54018
        return timelineSegments[timelineSegments.length - 1];
54019
    }; // In the event of a quota exceeded error, keep at least one second of back buffer. This
54020
    // number was arbitrarily chosen and may be updated in the future, but seemed reasonable
54021
    // as a start to prevent any potential issues with removing content too close to the
54022
    // playhead.
54023
 
54024
    const MIN_BACK_BUFFER = 1; // in ms
54025
 
54026
    const CHECK_BUFFER_DELAY = 500;
54027
    const finite = num => typeof num === 'number' && isFinite(num); // With most content hovering around 30fps, if a segment has a duration less than a half
54028
    // frame at 30fps or one frame at 60fps, the bandwidth and throughput calculations will
54029
    // not accurately reflect the rest of the content.
54030
 
54031
    const MIN_SEGMENT_DURATION_TO_SAVE_STATS = 1 / 60;
54032
    const illegalMediaSwitch = (loaderType, startingMedia, trackInfo) => {
54033
        // Although these checks should most likely cover non 'main' types, for now it narrows
54034
        // the scope of our checks.
54035
        if (loaderType !== 'main' || !startingMedia || !trackInfo) {
54036
            return null;
54037
        }
54038
        if (!trackInfo.hasAudio && !trackInfo.hasVideo) {
54039
            return 'Neither audio nor video found in segment.';
54040
        }
54041
        if (startingMedia.hasVideo && !trackInfo.hasVideo) {
54042
            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.';
54043
        }
54044
        if (!startingMedia.hasVideo && trackInfo.hasVideo) {
54045
            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.';
54046
        }
54047
        return null;
54048
    };
54049
    /**
54050
     * Calculates a time value that is safe to remove from the back buffer without interrupting
54051
     * playback.
54052
     *
54053
     * @param {TimeRange} seekable
54054
     *        The current seekable range
54055
     * @param {number} currentTime
54056
     *        The current time of the player
54057
     * @param {number} targetDuration
54058
     *        The target duration of the current playlist
54059
     * @return {number}
54060
     *         Time that is safe to remove from the back buffer without interrupting playback
54061
     */
54062
 
54063
    const safeBackBufferTrimTime = (seekable, currentTime, targetDuration) => {
54064
        // 30 seconds before the playhead provides a safe default for trimming.
54065
        //
54066
        // Choosing a reasonable default is particularly important for high bitrate content and
54067
        // VOD videos/live streams with large windows, as the buffer may end up overfilled and
54068
        // throw an APPEND_BUFFER_ERR.
54069
        let trimTime = currentTime - Config.BACK_BUFFER_LENGTH;
54070
        if (seekable.length) {
54071
            // Some live playlists may have a shorter window of content than the full allowed back
54072
            // buffer. For these playlists, don't save content that's no longer within the window.
54073
            trimTime = Math.max(trimTime, seekable.start(0));
54074
        } // Don't remove within target duration of the current time to avoid the possibility of
54075
        // removing the GOP currently being played, as removing it can cause playback stalls.
54076
 
54077
        const maxTrimTime = currentTime - targetDuration;
54078
        return Math.min(maxTrimTime, trimTime);
54079
    };
54080
    const segmentInfoString = segmentInfo => {
54081
        const {
54082
            startOfSegment,
54083
            duration,
54084
            segment,
54085
            part,
54086
            playlist: {
54087
                mediaSequence: seq,
54088
                id,
54089
                segments = []
54090
            },
54091
            mediaIndex: index,
54092
            partIndex,
54093
            timeline
54094
        } = segmentInfo;
54095
        const segmentLen = segments.length - 1;
54096
        let selection = 'mediaIndex/partIndex increment';
54097
        if (segmentInfo.getMediaInfoForTime) {
54098
            selection = `getMediaInfoForTime (${segmentInfo.getMediaInfoForTime})`;
54099
        } else if (segmentInfo.isSyncRequest) {
54100
            selection = 'getSyncSegmentCandidate (isSyncRequest)';
54101
        }
54102
        if (segmentInfo.independent) {
54103
            selection += ` with independent ${segmentInfo.independent}`;
54104
        }
54105
        const hasPartIndex = typeof partIndex === 'number';
54106
        const name = segmentInfo.segment.uri ? 'segment' : 'pre-segment';
54107
        const zeroBasedPartCount = hasPartIndex ? getKnownPartCount({
54108
            preloadSegment: segment
54109
        }) - 1 : 0;
54110
        return `${name} [${seq + index}/${seq + segmentLen}]` + (hasPartIndex ? ` part [${partIndex}/${zeroBasedPartCount}]` : '') + ` segment start/end [${segment.start} => ${segment.end}]` + (hasPartIndex ? ` part start/end [${part.start} => ${part.end}]` : '') + ` startOfSegment [${startOfSegment}]` + ` duration [${duration}]` + ` timeline [${timeline}]` + ` selected by [${selection}]` + ` playlist [${id}]`;
54111
    };
54112
    const timingInfoPropertyForMedia = mediaType => `${mediaType}TimingInfo`;
54113
    /**
54114
     * Returns the timestamp offset to use for the segment.
54115
     *
54116
     * @param {number} segmentTimeline
54117
     *        The timeline of the segment
54118
     * @param {number} currentTimeline
54119
     *        The timeline currently being followed by the loader
54120
     * @param {number} startOfSegment
54121
     *        The estimated segment start
54122
     * @param {TimeRange[]} buffered
54123
     *        The loader's buffer
54124
     * @param {boolean} overrideCheck
54125
     *        If true, no checks are made to see if the timestamp offset value should be set,
54126
     *        but sets it directly to a value.
54127
     *
54128
     * @return {number|null}
54129
     *         Either a number representing a new timestamp offset, or null if the segment is
54130
     *         part of the same timeline
54131
     */
54132
 
54133
    const timestampOffsetForSegment = ({
54134
                                           segmentTimeline,
54135
                                           currentTimeline,
54136
                                           startOfSegment,
54137
                                           buffered,
54138
                                           overrideCheck
54139
                                       }) => {
54140
        // Check to see if we are crossing a discontinuity to see if we need to set the
54141
        // timestamp offset on the transmuxer and source buffer.
54142
        //
54143
        // Previously, we changed the timestampOffset if the start of this segment was less than
54144
        // the currently set timestampOffset, but this isn't desirable as it can produce bad
54145
        // behavior, especially around long running live streams.
54146
        if (!overrideCheck && segmentTimeline === currentTimeline) {
54147
            return null;
54148
        } // When changing renditions, it's possible to request a segment on an older timeline. For
54149
        // instance, given two renditions with the following:
54150
        //
54151
        // #EXTINF:10
54152
        // segment1
54153
        // #EXT-X-DISCONTINUITY
54154
        // #EXTINF:10
54155
        // segment2
54156
        // #EXTINF:10
54157
        // segment3
54158
        //
54159
        // And the current player state:
54160
        //
54161
        // current time: 8
54162
        // buffer: 0 => 20
54163
        //
54164
        // The next segment on the current rendition would be segment3, filling the buffer from
54165
        // 20s onwards. However, if a rendition switch happens after segment2 was requested,
54166
        // then the next segment to be requested will be segment1 from the new rendition in
54167
        // order to fill time 8 and onwards. Using the buffered end would result in repeated
54168
        // content (since it would position segment1 of the new rendition starting at 20s). This
54169
        // case can be identified when the new segment's timeline is a prior value. Instead of
54170
        // using the buffered end, the startOfSegment can be used, which, hopefully, will be
54171
        // more accurate to the actual start time of the segment.
54172
 
54173
        if (segmentTimeline < currentTimeline) {
54174
            return startOfSegment;
54175
        } // segmentInfo.startOfSegment used to be used as the timestamp offset, however, that
54176
        // value uses the end of the last segment if it is available. While this value
54177
        // should often be correct, it's better to rely on the buffered end, as the new
54178
        // content post discontinuity should line up with the buffered end as if it were
54179
        // time 0 for the new content.
54180
 
54181
        return buffered.length ? buffered.end(buffered.length - 1) : startOfSegment;
54182
    };
54183
    /**
54184
     * Returns whether or not the loader should wait for a timeline change from the timeline
54185
     * change controller before processing the segment.
54186
     *
54187
     * Primary timing in VHS goes by video. This is different from most media players, as
54188
     * audio is more often used as the primary timing source. For the foreseeable future, VHS
54189
     * will continue to use video as the primary timing source, due to the current logic and
54190
     * expectations built around it.
54191
 
54192
     * Since the timing follows video, in order to maintain sync, the video loader is
54193
     * responsible for setting both audio and video source buffer timestamp offsets.
54194
     *
54195
     * Setting different values for audio and video source buffers could lead to
54196
     * desyncing. The following examples demonstrate some of the situations where this
54197
     * distinction is important. Note that all of these cases involve demuxed content. When
54198
     * content is muxed, the audio and video are packaged together, therefore syncing
54199
     * separate media playlists is not an issue.
54200
     *
54201
     * CASE 1: Audio prepares to load a new timeline before video:
54202
     *
54203
     * Timeline:       0                 1
54204
     * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54205
     * Audio Loader:                     ^
54206
     * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54207
     * Video Loader              ^
54208
     *
54209
     * In the above example, the audio loader is preparing to load the 6th segment, the first
54210
     * after a discontinuity, while the video loader is still loading the 5th segment, before
54211
     * the discontinuity.
54212
     *
54213
     * If the audio loader goes ahead and loads and appends the 6th segment before the video
54214
     * loader crosses the discontinuity, then when appended, the 6th audio segment will use
54215
     * the timestamp offset from timeline 0. This will likely lead to desyncing. In addition,
54216
     * the audio loader must provide the audioAppendStart value to trim the content in the
54217
     * transmuxer, and that value relies on the audio timestamp offset. Since the audio
54218
     * timestamp offset is set by the video (main) loader, the audio loader shouldn't load the
54219
     * segment until that value is provided.
54220
     *
54221
     * CASE 2: Video prepares to load a new timeline before audio:
54222
     *
54223
     * Timeline:       0                 1
54224
     * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54225
     * Audio Loader:             ^
54226
     * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54227
     * Video Loader                      ^
54228
     *
54229
     * In the above example, the video loader is preparing to load the 6th segment, the first
54230
     * after a discontinuity, while the audio loader is still loading the 5th segment, before
54231
     * the discontinuity.
54232
     *
54233
     * If the video loader goes ahead and loads and appends the 6th segment, then once the
54234
     * segment is loaded and processed, both the video and audio timestamp offsets will be
54235
     * set, since video is used as the primary timing source. This is to ensure content lines
54236
     * up appropriately, as any modifications to the video timing are reflected by audio when
54237
     * the video loader sets the audio and video timestamp offsets to the same value. However,
54238
     * setting the timestamp offset for audio before audio has had a chance to change
54239
     * timelines will likely lead to desyncing, as the audio loader will append segment 5 with
54240
     * a timestamp intended to apply to segments from timeline 1 rather than timeline 0.
54241
     *
54242
     * CASE 3: When seeking, audio prepares to load a new timeline before video
54243
     *
54244
     * Timeline:       0                 1
54245
     * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54246
     * Audio Loader:           ^
54247
     * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
54248
     * Video Loader            ^
54249
     *
54250
     * In the above example, both audio and video loaders are loading segments from timeline
54251
     * 0, but imagine that the seek originated from timeline 1.
54252
     *
54253
     * When seeking to a new timeline, the timestamp offset will be set based on the expected
54254
     * segment start of the loaded video segment. In order to maintain sync, the audio loader
54255
     * must wait for the video loader to load its segment and update both the audio and video
54256
     * timestamp offsets before it may load and append its own segment. This is the case
54257
     * whether the seek results in a mismatched segment request (e.g., the audio loader
54258
     * chooses to load segment 3 and the video loader chooses to load segment 4) or the
54259
     * loaders choose to load the same segment index from each playlist, as the segments may
54260
     * not be aligned perfectly, even for matching segment indexes.
54261
     *
54262
     * @param {Object} timelinechangeController
54263
     * @param {number} currentTimeline
54264
     *        The timeline currently being followed by the loader
54265
     * @param {number} segmentTimeline
54266
     *        The timeline of the segment being loaded
54267
     * @param {('main'|'audio')} loaderType
54268
     *        The loader type
54269
     * @param {boolean} audioDisabled
54270
     *        Whether the audio is disabled for the loader. This should only be true when the
54271
     *        loader may have muxed audio in its segment, but should not append it, e.g., for
54272
     *        the main loader when an alternate audio playlist is active.
54273
     *
54274
     * @return {boolean}
54275
     *         Whether the loader should wait for a timeline change from the timeline change
54276
     *         controller before processing the segment
54277
     */
54278
 
54279
    const shouldWaitForTimelineChange = ({
54280
                                             timelineChangeController,
54281
                                             currentTimeline,
54282
                                             segmentTimeline,
54283
                                             loaderType,
54284
                                             audioDisabled
54285
                                         }) => {
54286
        if (currentTimeline === segmentTimeline) {
54287
            return false;
54288
        }
54289
        if (loaderType === 'audio') {
54290
            const lastMainTimelineChange = timelineChangeController.lastTimelineChange({
54291
                type: 'main'
54292
            }); // Audio loader should wait if:
54293
            //
54294
            // * main hasn't had a timeline change yet (thus has not loaded its first segment)
54295
            // * main hasn't yet changed to the timeline audio is looking to load
54296
 
54297
            return !lastMainTimelineChange || lastMainTimelineChange.to !== segmentTimeline;
54298
        } // The main loader only needs to wait for timeline changes if there's demuxed audio.
54299
        // Otherwise, there's nothing to wait for, since audio would be muxed into the main
54300
        // loader's segments (or the content is audio/video only and handled by the main
54301
        // loader).
54302
 
54303
        if (loaderType === 'main' && audioDisabled) {
54304
            const pendingAudioTimelineChange = timelineChangeController.pendingTimelineChange({
54305
                type: 'audio'
54306
            }); // Main loader should wait for the audio loader if audio is not pending a timeline
54307
            // change to the current timeline.
54308
            //
54309
            // Since the main loader is responsible for setting the timestamp offset for both
54310
            // audio and video, the main loader must wait for audio to be about to change to its
54311
            // timeline before setting the offset, otherwise, if audio is behind in loading,
54312
            // segments from the previous timeline would be adjusted by the new timestamp offset.
54313
            //
54314
            // This requirement means that video will not cross a timeline until the audio is
54315
            // about to cross to it, so that way audio and video will always cross the timeline
54316
            // together.
54317
            //
54318
            // In addition to normal timeline changes, these rules also apply to the start of a
54319
            // stream (going from a non-existent timeline, -1, to timeline 0). It's important
54320
            // that these rules apply to the first timeline change because if they did not, it's
54321
            // possible that the main loader will cross two timelines before the audio loader has
54322
            // crossed one. Logic may be implemented to handle the startup as a special case, but
54323
            // it's easier to simply treat all timeline changes the same.
54324
 
54325
            if (pendingAudioTimelineChange && pendingAudioTimelineChange.to === segmentTimeline) {
54326
                return false;
54327
            }
54328
            return true;
54329
        }
54330
        return false;
54331
    };
54332
    const mediaDuration = timingInfos => {
54333
        let maxDuration = 0;
54334
        ['video', 'audio'].forEach(function (type) {
54335
            const typeTimingInfo = timingInfos[`${type}TimingInfo`];
54336
            if (!typeTimingInfo) {
54337
                return;
54338
            }
54339
            const {
54340
                start,
54341
                end
54342
            } = typeTimingInfo;
54343
            let duration;
54344
            if (typeof start === 'bigint' || typeof end === 'bigint') {
54345
                duration = window.BigInt(end) - window.BigInt(start);
54346
            } else if (typeof start === 'number' && typeof end === 'number') {
54347
                duration = end - start;
54348
            }
54349
            if (typeof duration !== 'undefined' && duration > maxDuration) {
54350
                maxDuration = duration;
54351
            }
54352
        }); // convert back to a number if it is lower than MAX_SAFE_INTEGER
54353
        // as we only need BigInt when we are above that.
54354
 
54355
        if (typeof maxDuration === 'bigint' && maxDuration < Number.MAX_SAFE_INTEGER) {
54356
            maxDuration = Number(maxDuration);
54357
        }
54358
        return maxDuration;
54359
    };
54360
    const segmentTooLong = ({
54361
                                segmentDuration,
54362
                                maxDuration
54363
                            }) => {
54364
        // 0 duration segments are most likely due to metadata only segments or a lack of
54365
        // information.
54366
        if (!segmentDuration) {
54367
            return false;
54368
        } // For HLS:
54369
        //
54370
        // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.3.1
54371
        // The EXTINF duration of each Media Segment in the Playlist
54372
        // file, when rounded to the nearest integer, MUST be less than or equal
54373
        // to the target duration; longer segments can trigger playback stalls
54374
        // or other errors.
54375
        //
54376
        // For DASH, the mpd-parser uses the largest reported segment duration as the target
54377
        // duration. Although that reported duration is occasionally approximate (i.e., not
54378
        // exact), a strict check may report that a segment is too long more often in DASH.
54379
 
54380
        return Math.round(segmentDuration) > maxDuration + TIME_FUDGE_FACTOR;
54381
    };
54382
    const getTroublesomeSegmentDurationMessage = (segmentInfo, sourceType) => {
54383
        // Right now we aren't following DASH's timing model exactly, so only perform
54384
        // this check for HLS content.
54385
        if (sourceType !== 'hls') {
54386
            return null;
54387
        }
54388
        const segmentDuration = mediaDuration({
54389
            audioTimingInfo: segmentInfo.audioTimingInfo,
54390
            videoTimingInfo: segmentInfo.videoTimingInfo
54391
        }); // Don't report if we lack information.
54392
        //
54393
        // If the segment has a duration of 0 it is either a lack of information or a
54394
        // metadata only segment and shouldn't be reported here.
54395
 
54396
        if (!segmentDuration) {
54397
            return null;
54398
        }
54399
        const targetDuration = segmentInfo.playlist.targetDuration;
54400
        const isSegmentWayTooLong = segmentTooLong({
54401
            segmentDuration,
54402
            maxDuration: targetDuration * 2
54403
        });
54404
        const isSegmentSlightlyTooLong = segmentTooLong({
54405
            segmentDuration,
54406
            maxDuration: targetDuration
54407
        });
54408
        const segmentTooLongMessage = `Segment with index ${segmentInfo.mediaIndex} ` + `from playlist ${segmentInfo.playlist.id} ` + `has a duration of ${segmentDuration} ` + `when the reported duration is ${segmentInfo.duration} ` + `and the target duration is ${targetDuration}. ` + 'For HLS content, a duration in excess of the target duration may result in ' + 'playback issues. See the HLS specification section on EXT-X-TARGETDURATION for ' + 'more details: ' + 'https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.3.1';
54409
        if (isSegmentWayTooLong || isSegmentSlightlyTooLong) {
54410
            return {
54411
                severity: isSegmentWayTooLong ? 'warn' : 'info',
54412
                message: segmentTooLongMessage
54413
            };
54414
        }
54415
        return null;
54416
    };
54417
    /**
54418
     * An object that manages segment loading and appending.
54419
     *
54420
     * @class SegmentLoader
54421
     * @param {Object} options required and optional options
54422
     * @extends videojs.EventTarget
54423
     */
54424
 
54425
    class SegmentLoader extends videojs.EventTarget {
54426
        constructor(settings, options = {}) {
54427
            super(); // check pre-conditions
54428
 
54429
            if (!settings) {
54430
                throw new TypeError('Initialization settings are required');
54431
            }
54432
            if (typeof settings.currentTime !== 'function') {
54433
                throw new TypeError('No currentTime getter specified');
54434
            }
54435
            if (!settings.mediaSource) {
54436
                throw new TypeError('No MediaSource specified');
54437
            } // public properties
54438
 
54439
            this.bandwidth = settings.bandwidth;
54440
            this.throughput = {
54441
                rate: 0,
54442
                count: 0
54443
            };
54444
            this.roundTrip = NaN;
54445
            this.resetStats_();
54446
            this.mediaIndex = null;
54447
            this.partIndex = null; // private settings
54448
 
54449
            this.hasPlayed_ = settings.hasPlayed;
54450
            this.currentTime_ = settings.currentTime;
54451
            this.seekable_ = settings.seekable;
54452
            this.seeking_ = settings.seeking;
54453
            this.duration_ = settings.duration;
54454
            this.mediaSource_ = settings.mediaSource;
54455
            this.vhs_ = settings.vhs;
54456
            this.loaderType_ = settings.loaderType;
54457
            this.currentMediaInfo_ = void 0;
54458
            this.startingMediaInfo_ = void 0;
54459
            this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
54460
            this.goalBufferLength_ = settings.goalBufferLength;
54461
            this.sourceType_ = settings.sourceType;
54462
            this.sourceUpdater_ = settings.sourceUpdater;
54463
            this.inbandTextTracks_ = settings.inbandTextTracks;
54464
            this.state_ = 'INIT';
54465
            this.timelineChangeController_ = settings.timelineChangeController;
54466
            this.shouldSaveSegmentTimingInfo_ = true;
54467
            this.parse708captions_ = settings.parse708captions;
54468
            this.useDtsForTimestampOffset_ = settings.useDtsForTimestampOffset;
54469
            this.captionServices_ = settings.captionServices;
54470
            this.exactManifestTimings = settings.exactManifestTimings;
54471
            this.addMetadataToTextTrack = settings.addMetadataToTextTrack; // private instance variables
54472
 
54473
            this.checkBufferTimeout_ = null;
54474
            this.error_ = void 0;
54475
            this.currentTimeline_ = -1;
54476
            this.shouldForceTimestampOffsetAfterResync_ = false;
54477
            this.pendingSegment_ = null;
54478
            this.xhrOptions_ = null;
54479
            this.pendingSegments_ = [];
54480
            this.audioDisabled_ = false;
54481
            this.isPendingTimestampOffset_ = false; // TODO possibly move gopBuffer and timeMapping info to a separate controller
54482
 
54483
            this.gopBuffer_ = [];
54484
            this.timeMapping_ = 0;
54485
            this.safeAppend_ = false;
54486
            this.appendInitSegment_ = {
54487
                audio: true,
54488
                video: true
54489
            };
54490
            this.playlistOfLastInitSegment_ = {
54491
                audio: null,
54492
                video: null
54493
            };
54494
            this.callQueue_ = []; // If the segment loader prepares to load a segment, but does not have enough
54495
            // information yet to start the loading process (e.g., if the audio loader wants to
54496
            // load a segment from the next timeline but the main loader hasn't yet crossed that
54497
            // timeline), then the load call will be added to the queue until it is ready to be
54498
            // processed.
54499
 
54500
            this.loadQueue_ = [];
54501
            this.metadataQueue_ = {
54502
                id3: [],
54503
                caption: []
54504
            };
54505
            this.waitingOnRemove_ = false;
54506
            this.quotaExceededErrorRetryTimeout_ = null; // Fragmented mp4 playback
54507
 
54508
            this.activeInitSegmentId_ = null;
54509
            this.initSegments_ = {}; // HLSe playback
54510
 
54511
            this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys;
54512
            this.keyCache_ = {};
54513
            this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings
54514
            // between a time in the display time and a segment index within
54515
            // a playlist
54516
 
54517
            this.syncController_ = settings.syncController;
54518
            this.syncPoint_ = {
54519
                segmentIndex: 0,
54520
                time: 0
54521
            };
54522
            this.transmuxer_ = this.createTransmuxer_();
54523
            this.triggerSyncInfoUpdate_ = () => this.trigger('syncinfoupdate');
54524
            this.syncController_.on('syncinfoupdate', this.triggerSyncInfoUpdate_);
54525
            this.mediaSource_.addEventListener('sourceopen', () => {
54526
                if (!this.isEndOfStream_()) {
54527
                    this.ended_ = false;
54528
                }
54529
            }); // ...for determining the fetch location
54530
 
54531
            this.fetchAtBuffer_ = false;
54532
            this.logger_ = logger(`SegmentLoader[${this.loaderType_}]`);
54533
            Object.defineProperty(this, 'state', {
54534
                get() {
54535
                    return this.state_;
54536
                },
54537
                set(newState) {
54538
                    if (newState !== this.state_) {
54539
                        this.logger_(`${this.state_} -> ${newState}`);
54540
                        this.state_ = newState;
54541
                        this.trigger('statechange');
54542
                    }
54543
                }
54544
            });
54545
            this.sourceUpdater_.on('ready', () => {
54546
                if (this.hasEnoughInfoToAppend_()) {
54547
                    this.processCallQueue_();
54548
                }
54549
            }); // Only the main loader needs to listen for pending timeline changes, as the main
54550
            // loader should wait for audio to be ready to change its timeline so that both main
54551
            // and audio timelines change together. For more details, see the
54552
            // shouldWaitForTimelineChange function.
54553
 
54554
            if (this.loaderType_ === 'main') {
54555
                this.timelineChangeController_.on('pendingtimelinechange', () => {
54556
                    if (this.hasEnoughInfoToAppend_()) {
54557
                        this.processCallQueue_();
54558
                    }
54559
                });
54560
            } // The main loader only listens on pending timeline changes, but the audio loader,
54561
            // since its loads follow main, needs to listen on timeline changes. For more details,
54562
            // see the shouldWaitForTimelineChange function.
54563
 
54564
            if (this.loaderType_ === 'audio') {
54565
                this.timelineChangeController_.on('timelinechange', () => {
54566
                    if (this.hasEnoughInfoToLoad_()) {
54567
                        this.processLoadQueue_();
54568
                    }
54569
                    if (this.hasEnoughInfoToAppend_()) {
54570
                        this.processCallQueue_();
54571
                    }
54572
                });
54573
            }
54574
        }
54575
        createTransmuxer_() {
54576
            return segmentTransmuxer.createTransmuxer({
54577
                remux: false,
54578
                alignGopsAtEnd: this.safeAppend_,
54579
                keepOriginalTimestamps: true,
54580
                parse708captions: this.parse708captions_,
54581
                captionServices: this.captionServices_
54582
            });
54583
        }
54584
        /**
54585
         * reset all of our media stats
54586
         *
54587
         * @private
54588
         */
54589
 
54590
        resetStats_() {
54591
            this.mediaBytesTransferred = 0;
54592
            this.mediaRequests = 0;
54593
            this.mediaRequestsAborted = 0;
54594
            this.mediaRequestsTimedout = 0;
54595
            this.mediaRequestsErrored = 0;
54596
            this.mediaTransferDuration = 0;
54597
            this.mediaSecondsLoaded = 0;
54598
            this.mediaAppends = 0;
54599
        }
54600
        /**
54601
         * dispose of the SegmentLoader and reset to the default state
54602
         */
54603
 
54604
        dispose() {
54605
            this.trigger('dispose');
54606
            this.state = 'DISPOSED';
54607
            this.pause();
54608
            this.abort_();
54609
            if (this.transmuxer_) {
54610
                this.transmuxer_.terminate();
54611
            }
54612
            this.resetStats_();
54613
            if (this.checkBufferTimeout_) {
54614
                window.clearTimeout(this.checkBufferTimeout_);
54615
            }
54616
            if (this.syncController_ && this.triggerSyncInfoUpdate_) {
54617
                this.syncController_.off('syncinfoupdate', this.triggerSyncInfoUpdate_);
54618
            }
54619
            this.off();
54620
        }
54621
        setAudio(enable) {
54622
            this.audioDisabled_ = !enable;
54623
            if (enable) {
54624
                this.appendInitSegment_.audio = true;
54625
            } else {
54626
                // remove current track audio if it gets disabled
54627
                this.sourceUpdater_.removeAudio(0, this.duration_());
54628
            }
54629
        }
54630
        /**
54631
         * abort anything that is currently doing on with the SegmentLoader
54632
         * and reset to a default state
54633
         */
54634
 
54635
        abort() {
54636
            if (this.state !== 'WAITING') {
54637
                if (this.pendingSegment_) {
54638
                    this.pendingSegment_ = null;
54639
                }
54640
                return;
54641
            }
54642
            this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY
54643
            // since we are no longer "waiting" on any requests. XHR callback is not always run
54644
            // when the request is aborted. This will prevent the loader from being stuck in the
54645
            // WAITING state indefinitely.
54646
 
54647
            this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the
54648
            // next segment
54649
 
54650
            if (!this.paused()) {
54651
                this.monitorBuffer_();
54652
            }
54653
        }
54654
        /**
54655
         * abort all pending xhr requests and null any pending segements
54656
         *
54657
         * @private
54658
         */
54659
 
54660
        abort_() {
54661
            if (this.pendingSegment_ && this.pendingSegment_.abortRequests) {
54662
                this.pendingSegment_.abortRequests();
54663
            } // clear out the segment being processed
54664
 
54665
            this.pendingSegment_ = null;
54666
            this.callQueue_ = [];
54667
            this.loadQueue_ = [];
54668
            this.metadataQueue_.id3 = [];
54669
            this.metadataQueue_.caption = [];
54670
            this.timelineChangeController_.clearPendingTimelineChange(this.loaderType_);
54671
            this.waitingOnRemove_ = false;
54672
            window.clearTimeout(this.quotaExceededErrorRetryTimeout_);
54673
            this.quotaExceededErrorRetryTimeout_ = null;
54674
        }
54675
        checkForAbort_(requestId) {
54676
            // If the state is APPENDING, then aborts will not modify the state, meaning the first
54677
            // callback that happens should reset the state to READY so that loading can continue.
54678
            if (this.state === 'APPENDING' && !this.pendingSegment_) {
54679
                this.state = 'READY';
54680
                return true;
54681
            }
54682
            if (!this.pendingSegment_ || this.pendingSegment_.requestId !== requestId) {
54683
                return true;
54684
            }
54685
            return false;
54686
        }
54687
        /**
54688
         * set an error on the segment loader and null out any pending segements
54689
         *
54690
         * @param {Error} error the error to set on the SegmentLoader
54691
         * @return {Error} the error that was set or that is currently set
54692
         */
54693
 
54694
        error(error) {
54695
            if (typeof error !== 'undefined') {
54696
                this.logger_('error occurred:', error);
54697
                this.error_ = error;
54698
            }
54699
            this.pendingSegment_ = null;
54700
            return this.error_;
54701
        }
54702
        endOfStream() {
54703
            this.ended_ = true;
54704
            if (this.transmuxer_) {
54705
                // need to clear out any cached data to prepare for the new segment
54706
                segmentTransmuxer.reset(this.transmuxer_);
54707
            }
54708
            this.gopBuffer_.length = 0;
54709
            this.pause();
54710
            this.trigger('ended');
54711
        }
54712
        /**
54713
         * Indicates which time ranges are buffered
54714
         *
54715
         * @return {TimeRange}
54716
         *         TimeRange object representing the current buffered ranges
54717
         */
54718
 
54719
        buffered_() {
54720
            const trackInfo = this.getMediaInfo_();
54721
            if (!this.sourceUpdater_ || !trackInfo) {
54722
                return createTimeRanges();
54723
            }
54724
            if (this.loaderType_ === 'main') {
54725
                const {
54726
                    hasAudio,
54727
                    hasVideo,
54728
                    isMuxed
54729
                } = trackInfo;
54730
                if (hasVideo && hasAudio && !this.audioDisabled_ && !isMuxed) {
54731
                    return this.sourceUpdater_.buffered();
54732
                }
54733
                if (hasVideo) {
54734
                    return this.sourceUpdater_.videoBuffered();
54735
                }
54736
            } // One case that can be ignored for now is audio only with alt audio,
54737
            // as we don't yet have proper support for that.
54738
 
54739
            return this.sourceUpdater_.audioBuffered();
54740
        }
54741
        /**
54742
         * Gets and sets init segment for the provided map
54743
         *
54744
         * @param {Object} map
54745
         *        The map object representing the init segment to get or set
54746
         * @param {boolean=} set
54747
         *        If true, the init segment for the provided map should be saved
54748
         * @return {Object}
54749
         *         map object for desired init segment
54750
         */
54751
 
54752
        initSegmentForMap(map, set = false) {
54753
            if (!map) {
54754
                return null;
54755
            }
54756
            const id = initSegmentId(map);
54757
            let storedMap = this.initSegments_[id];
54758
            if (set && !storedMap && map.bytes) {
54759
                this.initSegments_[id] = storedMap = {
54760
                    resolvedUri: map.resolvedUri,
54761
                    byterange: map.byterange,
54762
                    bytes: map.bytes,
54763
                    tracks: map.tracks,
54764
                    timescales: map.timescales
54765
                };
54766
            }
54767
            return storedMap || map;
54768
        }
54769
        /**
54770
         * Gets and sets key for the provided key
54771
         *
54772
         * @param {Object} key
54773
         *        The key object representing the key to get or set
54774
         * @param {boolean=} set
54775
         *        If true, the key for the provided key should be saved
54776
         * @return {Object}
54777
         *         Key object for desired key
54778
         */
54779
 
54780
        segmentKey(key, set = false) {
54781
            if (!key) {
54782
                return null;
54783
            }
54784
            const id = segmentKeyId(key);
54785
            let storedKey = this.keyCache_[id]; // TODO: We should use the HTTP Expires header to invalidate our cache per
54786
            // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3
54787
 
54788
            if (this.cacheEncryptionKeys_ && set && !storedKey && key.bytes) {
54789
                this.keyCache_[id] = storedKey = {
54790
                    resolvedUri: key.resolvedUri,
54791
                    bytes: key.bytes
54792
                };
54793
            }
54794
            const result = {
54795
                resolvedUri: (storedKey || key).resolvedUri
54796
            };
54797
            if (storedKey) {
54798
                result.bytes = storedKey.bytes;
54799
            }
54800
            return result;
54801
        }
54802
        /**
54803
         * Returns true if all configuration required for loading is present, otherwise false.
54804
         *
54805
         * @return {boolean} True if the all configuration is ready for loading
54806
         * @private
54807
         */
54808
 
54809
        couldBeginLoading_() {
54810
            return this.playlist_ && !this.paused();
54811
        }
54812
        /**
54813
         * load a playlist and start to fill the buffer
54814
         */
54815
 
54816
        load() {
54817
            // un-pause
54818
            this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be
54819
            // specified
54820
 
54821
            if (!this.playlist_) {
54822
                return;
54823
            } // if all the configuration is ready, initialize and begin loading
54824
 
54825
            if (this.state === 'INIT' && this.couldBeginLoading_()) {
54826
                return this.init_();
54827
            } // if we're in the middle of processing a segment already, don't
54828
            // kick off an additional segment request
54829
 
54830
            if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
54831
                return;
54832
            }
54833
            this.state = 'READY';
54834
        }
54835
        /**
54836
         * Once all the starting parameters have been specified, begin
54837
         * operation. This method should only be invoked from the INIT
54838
         * state.
54839
         *
54840
         * @private
54841
         */
54842
 
54843
        init_() {
54844
            this.state = 'READY'; // if this is the audio segment loader, and it hasn't been inited before, then any old
54845
            // audio data from the muxed content should be removed
54846
 
54847
            this.resetEverything();
54848
            return this.monitorBuffer_();
54849
        }
54850
        /**
54851
         * set a playlist on the segment loader
54852
         *
54853
         * @param {PlaylistLoader} media the playlist to set on the segment loader
54854
         */
54855
 
54856
        playlist(newPlaylist, options = {}) {
54857
            if (!newPlaylist) {
54858
                return;
54859
            }
54860
            const oldPlaylist = this.playlist_;
54861
            const segmentInfo = this.pendingSegment_;
54862
            this.playlist_ = newPlaylist;
54863
            this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist
54864
            // is always our zero-time so force a sync update each time the playlist
54865
            // is refreshed from the server
54866
            //
54867
            // Use the INIT state to determine if playback has started, as the playlist sync info
54868
            // should be fixed once requests begin (as sync points are generated based on sync
54869
            // info), but not before then.
54870
 
54871
            if (this.state === 'INIT') {
54872
                newPlaylist.syncInfo = {
54873
                    mediaSequence: newPlaylist.mediaSequence,
54874
                    time: 0
54875
                }; // Setting the date time mapping means mapping the program date time (if available)
54876
                // to time 0 on the player's timeline. The playlist's syncInfo serves a similar
54877
                // purpose, mapping the initial mediaSequence to time zero. Since the syncInfo can
54878
                // be updated as the playlist is refreshed before the loader starts loading, the
54879
                // program date time mapping needs to be updated as well.
54880
                //
54881
                // This mapping is only done for the main loader because a program date time should
54882
                // map equivalently between playlists.
54883
 
54884
                if (this.loaderType_ === 'main') {
54885
                    this.syncController_.setDateTimeMappingForStart(newPlaylist);
54886
                }
54887
            }
54888
            let oldId = null;
54889
            if (oldPlaylist) {
54890
                if (oldPlaylist.id) {
54891
                    oldId = oldPlaylist.id;
54892
                } else if (oldPlaylist.uri) {
54893
                    oldId = oldPlaylist.uri;
54894
                }
54895
            }
54896
            this.logger_(`playlist update [${oldId} => ${newPlaylist.id || newPlaylist.uri}]`);
54897
            this.syncController_.updateMediaSequenceMap(newPlaylist, this.currentTime_(), this.loaderType_); // in VOD, this is always a rendition switch (or we updated our syncInfo above)
54898
            // in LIVE, we always want to update with new playlists (including refreshes)
54899
 
54900
            this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start
54901
            // buffering now
54902
 
54903
            if (this.state === 'INIT' && this.couldBeginLoading_()) {
54904
                return this.init_();
54905
            }
54906
            if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
54907
                if (this.mediaIndex !== null) {
54908
                    // we must reset/resync the segment loader when we switch renditions and
54909
                    // the segment loader is already synced to the previous rendition
54910
                    // We only want to reset the loader here for LLHLS playback, as resetLoader sets fetchAtBuffer_
54911
                    // to false, resulting in fetching segments at currentTime and causing repeated
54912
                    // same-segment requests on playlist change. This erroneously drives up the playback watcher
54913
                    // stalled segment count, as re-requesting segments at the currentTime or browser cached segments
54914
                    // will not change the buffer.
54915
                    // Reference for LLHLS fixes: https://github.com/videojs/http-streaming/pull/1201
54916
                    const isLLHLS = !newPlaylist.endList && typeof newPlaylist.partTargetDuration === 'number';
54917
                    if (isLLHLS) {
54918
                        this.resetLoader();
54919
                    } else {
54920
                        this.resyncLoader();
54921
                    }
54922
                }
54923
                this.currentMediaInfo_ = void 0;
54924
                this.trigger('playlistupdate'); // the rest of this function depends on `oldPlaylist` being defined
54925
 
54926
                return;
54927
            } // we reloaded the same playlist so we are in a live scenario
54928
            // and we will likely need to adjust the mediaIndex
54929
 
54930
            const mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
54931
            this.logger_(`live window shift [${mediaSequenceDiff}]`); // update the mediaIndex on the SegmentLoader
54932
            // this is important because we can abort a request and this value must be
54933
            // equal to the last appended mediaIndex
54934
 
54935
            if (this.mediaIndex !== null) {
54936
                this.mediaIndex -= mediaSequenceDiff; // this can happen if we are going to load the first segment, but get a playlist
54937
                // update during that. mediaIndex would go from 0 to -1 if mediaSequence in the
54938
                // new playlist was incremented by 1.
54939
 
54940
                if (this.mediaIndex < 0) {
54941
                    this.mediaIndex = null;
54942
                    this.partIndex = null;
54943
                } else {
54944
                    const segment = this.playlist_.segments[this.mediaIndex]; // partIndex should remain the same for the same segment
54945
                    // unless parts fell off of the playlist for this segment.
54946
                    // In that case we need to reset partIndex and resync
54947
 
54948
                    if (this.partIndex && (!segment.parts || !segment.parts.length || !segment.parts[this.partIndex])) {
54949
                        const mediaIndex = this.mediaIndex;
54950
                        this.logger_(`currently processing part (index ${this.partIndex}) no longer exists.`);
54951
                        this.resetLoader(); // We want to throw away the partIndex and the data associated with it,
54952
                        // as the part was dropped from our current playlists segment.
54953
                        // The mediaIndex will still be valid so keep that around.
54954
 
54955
                        this.mediaIndex = mediaIndex;
54956
                    }
54957
                }
54958
            } // update the mediaIndex on the SegmentInfo object
54959
            // this is important because we will update this.mediaIndex with this value
54960
            // in `handleAppendsDone_` after the segment has been successfully appended
54961
 
54962
            if (segmentInfo) {
54963
                segmentInfo.mediaIndex -= mediaSequenceDiff;
54964
                if (segmentInfo.mediaIndex < 0) {
54965
                    segmentInfo.mediaIndex = null;
54966
                    segmentInfo.partIndex = null;
54967
                } else {
54968
                    // we need to update the referenced segment so that timing information is
54969
                    // saved for the new playlist's segment, however, if the segment fell off the
54970
                    // playlist, we can leave the old reference and just lose the timing info
54971
                    if (segmentInfo.mediaIndex >= 0) {
54972
                        segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
54973
                    }
54974
                    if (segmentInfo.partIndex >= 0 && segmentInfo.segment.parts) {
54975
                        segmentInfo.part = segmentInfo.segment.parts[segmentInfo.partIndex];
54976
                    }
54977
                }
54978
            }
54979
            this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
54980
        }
54981
        /**
54982
         * Prevent the loader from fetching additional segments. If there
54983
         * is a segment request outstanding, it will finish processing
54984
         * before the loader halts. A segment loader can be unpaused by
54985
         * calling load().
54986
         */
54987
 
54988
        pause() {
54989
            if (this.checkBufferTimeout_) {
54990
                window.clearTimeout(this.checkBufferTimeout_);
54991
                this.checkBufferTimeout_ = null;
54992
            }
54993
        }
54994
        /**
54995
         * Returns whether the segment loader is fetching additional
54996
         * segments when given the opportunity. This property can be
54997
         * modified through calls to pause() and load().
54998
         */
54999
 
55000
        paused() {
55001
            return this.checkBufferTimeout_ === null;
55002
        }
55003
        /**
55004
         * Delete all the buffered data and reset the SegmentLoader
55005
         *
55006
         * @param {Function} [done] an optional callback to be executed when the remove
55007
         * operation is complete
55008
         */
55009
 
55010
        resetEverything(done) {
55011
            this.ended_ = false;
55012
            this.activeInitSegmentId_ = null;
55013
            this.appendInitSegment_ = {
55014
                audio: true,
55015
                video: true
55016
            };
55017
            this.resetLoader(); // remove from 0, the earliest point, to Infinity, to signify removal of everything.
55018
            // VTT Segment Loader doesn't need to do anything but in the regular SegmentLoader,
55019
            // we then clamp the value to duration if necessary.
55020
 
55021
            this.remove(0, Infinity, done); // clears fmp4 captions
55022
 
55023
            if (this.transmuxer_) {
55024
                this.transmuxer_.postMessage({
55025
                    action: 'clearAllMp4Captions'
55026
                }); // reset the cache in the transmuxer
55027
 
55028
                this.transmuxer_.postMessage({
55029
                    action: 'reset'
55030
                });
55031
            }
55032
        }
55033
        /**
55034
         * Force the SegmentLoader to resync and start loading around the currentTime instead
55035
         * of starting at the end of the buffer
55036
         *
55037
         * Useful for fast quality changes
55038
         */
55039
 
55040
        resetLoader() {
55041
            this.fetchAtBuffer_ = false;
55042
            this.resyncLoader();
55043
        }
55044
        /**
55045
         * Force the SegmentLoader to restart synchronization and make a conservative guess
55046
         * before returning to the simple walk-forward method
55047
         */
55048
 
55049
        resyncLoader() {
55050
            if (this.transmuxer_) {
55051
                // need to clear out any cached data to prepare for the new segment
55052
                segmentTransmuxer.reset(this.transmuxer_);
55053
            }
55054
            this.mediaIndex = null;
55055
            this.partIndex = null;
55056
            this.syncPoint_ = null;
55057
            this.isPendingTimestampOffset_ = false;
55058
            this.shouldForceTimestampOffsetAfterResync_ = true;
55059
            this.callQueue_ = [];
55060
            this.loadQueue_ = [];
55061
            this.metadataQueue_.id3 = [];
55062
            this.metadataQueue_.caption = [];
55063
            this.abort();
55064
            if (this.transmuxer_) {
55065
                this.transmuxer_.postMessage({
55066
                    action: 'clearParsedMp4Captions'
55067
                });
55068
            }
55069
        }
55070
        /**
55071
         * Remove any data in the source buffer between start and end times
55072
         *
55073
         * @param {number} start - the start time of the region to remove from the buffer
55074
         * @param {number} end - the end time of the region to remove from the buffer
55075
         * @param {Function} [done] - an optional callback to be executed when the remove
55076
         * @param {boolean} force - force all remove operations to happen
55077
         * operation is complete
55078
         */
55079
 
55080
        remove(start, end, done = () => {}, force = false) {
55081
            // clamp end to duration if we need to remove everything.
55082
            // This is due to a browser bug that causes issues if we remove to Infinity.
55083
            // videojs/videojs-contrib-hls#1225
55084
            if (end === Infinity) {
55085
                end = this.duration_();
55086
            } // skip removes that would throw an error
55087
            // commonly happens during a rendition switch at the start of a video
55088
            // from start 0 to end 0
55089
 
55090
            if (end <= start) {
55091
                this.logger_('skipping remove because end ${end} is <= start ${start}');
55092
                return;
55093
            }
55094
            if (!this.sourceUpdater_ || !this.getMediaInfo_()) {
55095
                this.logger_('skipping remove because no source updater or starting media info'); // nothing to remove if we haven't processed any media
55096
 
55097
                return;
55098
            } // set it to one to complete this function's removes
55099
 
55100
            let removesRemaining = 1;
55101
            const removeFinished = () => {
55102
                removesRemaining--;
55103
                if (removesRemaining === 0) {
55104
                    done();
55105
                }
55106
            };
55107
            if (force || !this.audioDisabled_) {
55108
                removesRemaining++;
55109
                this.sourceUpdater_.removeAudio(start, end, removeFinished);
55110
            } // While it would be better to only remove video if the main loader has video, this
55111
            // should be safe with audio only as removeVideo will call back even if there's no
55112
            // video buffer.
55113
            //
55114
            // In theory we can check to see if there's video before calling the remove, but in
55115
            // the event that we're switching between renditions and from video to audio only
55116
            // (when we add support for that), we may need to clear the video contents despite
55117
            // what the new media will contain.
55118
 
55119
            if (force || this.loaderType_ === 'main') {
55120
                this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
55121
                removesRemaining++;
55122
                this.sourceUpdater_.removeVideo(start, end, removeFinished);
55123
            } // remove any captions and ID3 tags
55124
 
55125
            for (const track in this.inbandTextTracks_) {
55126
                removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
55127
            }
55128
            removeCuesFromTrack(start, end, this.segmentMetadataTrack_); // finished this function's removes
55129
 
55130
            removeFinished();
55131
        }
55132
        /**
55133
         * (re-)schedule monitorBufferTick_ to run as soon as possible
55134
         *
55135
         * @private
55136
         */
55137
 
55138
        monitorBuffer_() {
55139
            if (this.checkBufferTimeout_) {
55140
                window.clearTimeout(this.checkBufferTimeout_);
55141
            }
55142
            this.checkBufferTimeout_ = window.setTimeout(this.monitorBufferTick_.bind(this), 1);
55143
        }
55144
        /**
55145
         * As long as the SegmentLoader is in the READY state, periodically
55146
         * invoke fillBuffer_().
55147
         *
55148
         * @private
55149
         */
55150
 
55151
        monitorBufferTick_() {
55152
            if (this.state === 'READY') {
55153
                this.fillBuffer_();
55154
            }
55155
            if (this.checkBufferTimeout_) {
55156
                window.clearTimeout(this.checkBufferTimeout_);
55157
            }
55158
            this.checkBufferTimeout_ = window.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
55159
        }
55160
        /**
55161
         * fill the buffer with segements unless the sourceBuffers are
55162
         * currently updating
55163
         *
55164
         * Note: this function should only ever be called by monitorBuffer_
55165
         * and never directly
55166
         *
55167
         * @private
55168
         */
55169
 
55170
        fillBuffer_() {
55171
            // TODO since the source buffer maintains a queue, and we shouldn't call this function
55172
            // except when we're ready for the next segment, this check can most likely be removed
55173
            if (this.sourceUpdater_.updating()) {
55174
                return;
55175
            } // see if we need to begin loading immediately
55176
 
55177
            const segmentInfo = this.chooseNextRequest_();
55178
            if (!segmentInfo) {
55179
                return;
55180
            }
55181
            if (typeof segmentInfo.timestampOffset === 'number') {
55182
                this.isPendingTimestampOffset_ = false;
55183
                this.timelineChangeController_.pendingTimelineChange({
55184
                    type: this.loaderType_,
55185
                    from: this.currentTimeline_,
55186
                    to: segmentInfo.timeline
55187
                });
55188
            }
55189
            this.loadSegment_(segmentInfo);
55190
        }
55191
        /**
55192
         * Determines if we should call endOfStream on the media source based
55193
         * on the state of the buffer or if appened segment was the final
55194
         * segment in the playlist.
55195
         *
55196
         * @param {number} [mediaIndex] the media index of segment we last appended
55197
         * @param {Object} [playlist] a media playlist object
55198
         * @return {boolean} do we need to call endOfStream on the MediaSource
55199
         */
55200
 
55201
        isEndOfStream_(mediaIndex = this.mediaIndex, playlist = this.playlist_, partIndex = this.partIndex) {
55202
            if (!playlist || !this.mediaSource_) {
55203
                return false;
55204
            }
55205
            const segment = typeof mediaIndex === 'number' && playlist.segments[mediaIndex]; // mediaIndex is zero based but length is 1 based
55206
 
55207
            const appendedLastSegment = mediaIndex + 1 === playlist.segments.length; // true if there are no parts, or this is the last part.
55208
 
55209
            const appendedLastPart = !segment || !segment.parts || partIndex + 1 === segment.parts.length; // if we've buffered to the end of the video, we need to call endOfStream
55210
            // so that MediaSources can trigger the `ended` event when it runs out of
55211
            // buffered data instead of waiting for me
55212
 
55213
            return playlist.endList && this.mediaSource_.readyState === 'open' && appendedLastSegment && appendedLastPart;
55214
        }
55215
        /**
55216
         * Determines what request should be made given current segment loader state.
55217
         *
55218
         * @return {Object} a request object that describes the segment/part to load
55219
         */
55220
 
55221
        chooseNextRequest_() {
55222
            const buffered = this.buffered_();
55223
            const bufferedEnd = lastBufferedEnd(buffered) || 0;
55224
            const bufferedTime = timeAheadOf(buffered, this.currentTime_());
55225
            const preloaded = !this.hasPlayed_() && bufferedTime >= 1;
55226
            const haveEnoughBuffer = bufferedTime >= this.goalBufferLength_();
55227
            const segments = this.playlist_.segments; // return no segment if:
55228
            // 1. we don't have segments
55229
            // 2. The video has not yet played and we already downloaded a segment
55230
            // 3. we already have enough buffered time
55231
 
55232
            if (!segments.length || preloaded || haveEnoughBuffer) {
55233
                return null;
55234
            }
55235
            this.syncPoint_ = this.syncPoint_ || this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_(), this.loaderType_);
55236
            const next = {
55237
                partIndex: null,
55238
                mediaIndex: null,
55239
                startOfSegment: null,
55240
                playlist: this.playlist_,
55241
                isSyncRequest: Boolean(!this.syncPoint_)
55242
            };
55243
            if (next.isSyncRequest) {
55244
                next.mediaIndex = getSyncSegmentCandidate(this.currentTimeline_, segments, bufferedEnd);
55245
                this.logger_(`choose next request. Can not find sync point. Fallback to media Index: ${next.mediaIndex}`);
55246
            } else if (this.mediaIndex !== null) {
55247
                const segment = segments[this.mediaIndex];
55248
                const partIndex = typeof this.partIndex === 'number' ? this.partIndex : -1;
55249
                next.startOfSegment = segment.end ? segment.end : bufferedEnd;
55250
                if (segment.parts && segment.parts[partIndex + 1]) {
55251
                    next.mediaIndex = this.mediaIndex;
55252
                    next.partIndex = partIndex + 1;
55253
                } else {
55254
                    next.mediaIndex = this.mediaIndex + 1;
55255
                }
55256
            } else {
55257
                // Find the segment containing the end of the buffer or current time.
55258
                const {
55259
                    segmentIndex,
55260
                    startTime,
55261
                    partIndex
55262
                } = Playlist.getMediaInfoForTime({
55263
                    exactManifestTimings: this.exactManifestTimings,
55264
                    playlist: this.playlist_,
55265
                    currentTime: this.fetchAtBuffer_ ? bufferedEnd : this.currentTime_(),
55266
                    startingPartIndex: this.syncPoint_.partIndex,
55267
                    startingSegmentIndex: this.syncPoint_.segmentIndex,
55268
                    startTime: this.syncPoint_.time
55269
                });
55270
                next.getMediaInfoForTime = this.fetchAtBuffer_ ? `bufferedEnd ${bufferedEnd}` : `currentTime ${this.currentTime_()}`;
55271
                next.mediaIndex = segmentIndex;
55272
                next.startOfSegment = startTime;
55273
                next.partIndex = partIndex;
55274
                this.logger_(`choose next request. Playlist switched and we have a sync point. Media Index: ${next.mediaIndex} `);
55275
            }
55276
            const nextSegment = segments[next.mediaIndex];
55277
            let nextPart = nextSegment && typeof next.partIndex === 'number' && nextSegment.parts && nextSegment.parts[next.partIndex]; // if the next segment index is invalid or
55278
            // the next partIndex is invalid do not choose a next segment.
55279
 
55280
            if (!nextSegment || typeof next.partIndex === 'number' && !nextPart) {
55281
                return null;
55282
            } // if the next segment has parts, and we don't have a partIndex.
55283
            // Set partIndex to 0
55284
 
55285
            if (typeof next.partIndex !== 'number' && nextSegment.parts) {
55286
                next.partIndex = 0;
55287
                nextPart = nextSegment.parts[0];
55288
            } // independentSegments applies to every segment in a playlist. If independentSegments appears in a main playlist,
55289
            // it applies to each segment in each media playlist.
55290
            // https://datatracker.ietf.org/doc/html/draft-pantos-http-live-streaming-23#section-4.3.5.1
55291
 
55292
            const hasIndependentSegments = this.vhs_.playlists && this.vhs_.playlists.main && this.vhs_.playlists.main.independentSegments || this.playlist_.independentSegments; // if we have no buffered data then we need to make sure
55293
            // that the next part we append is "independent" if possible.
55294
            // So we check if the previous part is independent, and request
55295
            // it if it is.
55296
 
55297
            if (!bufferedTime && nextPart && !hasIndependentSegments && !nextPart.independent) {
55298
                if (next.partIndex === 0) {
55299
                    const lastSegment = segments[next.mediaIndex - 1];
55300
                    const lastSegmentLastPart = lastSegment.parts && lastSegment.parts.length && lastSegment.parts[lastSegment.parts.length - 1];
55301
                    if (lastSegmentLastPart && lastSegmentLastPart.independent) {
55302
                        next.mediaIndex -= 1;
55303
                        next.partIndex = lastSegment.parts.length - 1;
55304
                        next.independent = 'previous segment';
55305
                    }
55306
                } else if (nextSegment.parts[next.partIndex - 1].independent) {
55307
                    next.partIndex -= 1;
55308
                    next.independent = 'previous part';
55309
                }
55310
            }
55311
            const ended = this.mediaSource_ && this.mediaSource_.readyState === 'ended'; // do not choose a next segment if all of the following:
55312
            // 1. this is the last segment in the playlist
55313
            // 2. end of stream has been called on the media source already
55314
            // 3. the player is not seeking
55315
 
55316
            if (next.mediaIndex >= segments.length - 1 && ended && !this.seeking_()) {
55317
                return null;
55318
            }
55319
            if (this.shouldForceTimestampOffsetAfterResync_) {
55320
                this.shouldForceTimestampOffsetAfterResync_ = false;
55321
                next.forceTimestampOffset = true;
55322
                this.logger_('choose next request. Force timestamp offset after loader resync');
55323
            }
55324
            return this.generateSegmentInfo_(next);
55325
        }
55326
        generateSegmentInfo_(options) {
55327
            const {
55328
                independent,
55329
                playlist,
55330
                mediaIndex,
55331
                startOfSegment,
55332
                isSyncRequest,
55333
                partIndex,
55334
                forceTimestampOffset,
55335
                getMediaInfoForTime
55336
            } = options;
55337
            const segment = playlist.segments[mediaIndex];
55338
            const part = typeof partIndex === 'number' && segment.parts[partIndex];
55339
            const segmentInfo = {
55340
                requestId: 'segment-loader-' + Math.random(),
55341
                // resolve the segment URL relative to the playlist
55342
                uri: part && part.resolvedUri || segment.resolvedUri,
55343
                // the segment's mediaIndex at the time it was requested
55344
                mediaIndex,
55345
                partIndex: part ? partIndex : null,
55346
                // whether or not to update the SegmentLoader's state with this
55347
                // segment's mediaIndex
55348
                isSyncRequest,
55349
                startOfSegment,
55350
                // the segment's playlist
55351
                playlist,
55352
                // unencrypted bytes of the segment
55353
                bytes: null,
55354
                // when a key is defined for this segment, the encrypted bytes
55355
                encryptedBytes: null,
55356
                // The target timestampOffset for this segment when we append it
55357
                // to the source buffer
55358
                timestampOffset: null,
55359
                // The timeline that the segment is in
55360
                timeline: segment.timeline,
55361
                // The expected duration of the segment in seconds
55362
                duration: part && part.duration || segment.duration,
55363
                // retain the segment in case the playlist updates while doing an async process
55364
                segment,
55365
                part,
55366
                byteLength: 0,
55367
                transmuxer: this.transmuxer_,
55368
                // type of getMediaInfoForTime that was used to get this segment
55369
                getMediaInfoForTime,
55370
                independent
55371
            };
55372
            const overrideCheck = typeof forceTimestampOffset !== 'undefined' ? forceTimestampOffset : this.isPendingTimestampOffset_;
55373
            segmentInfo.timestampOffset = this.timestampOffsetForSegment_({
55374
                segmentTimeline: segment.timeline,
55375
                currentTimeline: this.currentTimeline_,
55376
                startOfSegment,
55377
                buffered: this.buffered_(),
55378
                overrideCheck
55379
            });
55380
            const audioBufferedEnd = lastBufferedEnd(this.sourceUpdater_.audioBuffered());
55381
            if (typeof audioBufferedEnd === 'number') {
55382
                // since the transmuxer is using the actual timing values, but the buffer is
55383
                // adjusted by the timestamp offset, we must adjust the value here
55384
                segmentInfo.audioAppendStart = audioBufferedEnd - this.sourceUpdater_.audioTimestampOffset();
55385
            }
55386
            if (this.sourceUpdater_.videoBuffered().length) {
55387
                segmentInfo.gopsToAlignWith = gopsSafeToAlignWith(this.gopBuffer_,
55388
                    // since the transmuxer is using the actual timing values, but the time is
55389
                    // adjusted by the timestmap offset, we must adjust the value here
55390
                    this.currentTime_() - this.sourceUpdater_.videoTimestampOffset(), this.timeMapping_);
55391
            }
55392
            return segmentInfo;
55393
        } // get the timestampoffset for a segment,
55394
        // added so that vtt segment loader can override and prevent
55395
        // adding timestamp offsets.
55396
 
55397
        timestampOffsetForSegment_(options) {
55398
            return timestampOffsetForSegment(options);
55399
        }
55400
        /**
55401
         * Determines if the network has enough bandwidth to complete the current segment
55402
         * request in a timely manner. If not, the request will be aborted early and bandwidth
55403
         * updated to trigger a playlist switch.
55404
         *
55405
         * @param {Object} stats
55406
         *        Object containing stats about the request timing and size
55407
         * @private
55408
         */
55409
 
55410
        earlyAbortWhenNeeded_(stats) {
55411
            if (this.vhs_.tech_.paused() ||
55412
                // Don't abort if the current playlist is on the lowestEnabledRendition
55413
                // TODO: Replace using timeout with a boolean indicating whether this playlist is
55414
                //       the lowestEnabledRendition.
55415
                !this.xhrOptions_.timeout ||
55416
                // Don't abort if we have no bandwidth information to estimate segment sizes
55417
                !this.playlist_.attributes.BANDWIDTH) {
55418
                return;
55419
            } // Wait at least 1 second since the first byte of data has been received before
55420
            // using the calculated bandwidth from the progress event to allow the bitrate
55421
            // to stabilize
55422
 
55423
            if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
55424
                return;
55425
            }
55426
            const currentTime = this.currentTime_();
55427
            const measuredBandwidth = stats.bandwidth;
55428
            const segmentDuration = this.pendingSegment_.duration;
55429
            const requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
55430
            // if we are only left with less than 1 second when the request completes.
55431
            // A negative timeUntilRebuffering indicates we are already rebuffering
55432
 
55433
            const timeUntilRebuffer$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.vhs_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download
55434
            // is larger than the estimated time until the player runs out of forward buffer
55435
 
55436
            if (requestTimeRemaining <= timeUntilRebuffer$1) {
55437
                return;
55438
            }
55439
            const switchCandidate = minRebufferMaxBandwidthSelector({
55440
                main: this.vhs_.playlists.main,
55441
                currentTime,
55442
                bandwidth: measuredBandwidth,
55443
                duration: this.duration_(),
55444
                segmentDuration,
55445
                timeUntilRebuffer: timeUntilRebuffer$1,
55446
                currentTimeline: this.currentTimeline_,
55447
                syncController: this.syncController_
55448
            });
55449
            if (!switchCandidate) {
55450
                return;
55451
            }
55452
            const rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$1;
55453
            const timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
55454
            let minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the
55455
            // potential round trip time of the new request so that we are not too aggressive
55456
            // with switching to a playlist that might save us a fraction of a second.
55457
 
55458
            if (timeUntilRebuffer$1 <= TIME_FUDGE_FACTOR) {
55459
                minimumTimeSaving = 1;
55460
            }
55461
            if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
55462
                return;
55463
            } // set the bandwidth to that of the desired playlist being sure to scale by
55464
            // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
55465
            // don't trigger a bandwidthupdate as the bandwidth is artifial
55466
 
55467
            this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
55468
            this.trigger('earlyabort');
55469
        }
55470
        handleAbort_(segmentInfo) {
55471
            this.logger_(`Aborting ${segmentInfoString(segmentInfo)}`);
55472
            this.mediaRequestsAborted += 1;
55473
        }
55474
        /**
55475
         * XHR `progress` event handler
55476
         *
55477
         * @param {Event}
55478
         *        The XHR `progress` event
55479
         * @param {Object} simpleSegment
55480
         *        A simplified segment object copy
55481
         * @private
55482
         */
55483
 
55484
        handleProgress_(event, simpleSegment) {
55485
            this.earlyAbortWhenNeeded_(simpleSegment.stats);
55486
            if (this.checkForAbort_(simpleSegment.requestId)) {
55487
                return;
55488
            }
55489
            this.trigger('progress');
55490
        }
55491
        handleTrackInfo_(simpleSegment, trackInfo) {
55492
            this.earlyAbortWhenNeeded_(simpleSegment.stats);
55493
            if (this.checkForAbort_(simpleSegment.requestId)) {
55494
                return;
55495
            }
55496
            if (this.checkForIllegalMediaSwitch(trackInfo)) {
55497
                return;
55498
            }
55499
            trackInfo = trackInfo || {}; // When we have track info, determine what media types this loader is dealing with.
55500
            // Guard against cases where we're not getting track info at all until we are
55501
            // certain that all streams will provide it.
55502
 
55503
            if (!shallowEqual(this.currentMediaInfo_, trackInfo)) {
55504
                this.appendInitSegment_ = {
55505
                    audio: true,
55506
                    video: true
55507
                };
55508
                this.startingMediaInfo_ = trackInfo;
55509
                this.currentMediaInfo_ = trackInfo;
55510
                this.logger_('trackinfo update', trackInfo);
55511
                this.trigger('trackinfo');
55512
            } // trackinfo may cause an abort if the trackinfo
55513
            // causes a codec change to an unsupported codec.
55514
 
55515
            if (this.checkForAbort_(simpleSegment.requestId)) {
55516
                return;
55517
            } // set trackinfo on the pending segment so that
55518
            // it can append.
55519
 
55520
            this.pendingSegment_.trackInfo = trackInfo; // check if any calls were waiting on the track info
55521
 
55522
            if (this.hasEnoughInfoToAppend_()) {
55523
                this.processCallQueue_();
55524
            }
55525
        }
55526
        handleTimingInfo_(simpleSegment, mediaType, timeType, time) {
55527
            this.earlyAbortWhenNeeded_(simpleSegment.stats);
55528
            if (this.checkForAbort_(simpleSegment.requestId)) {
55529
                return;
55530
            }
55531
            const segmentInfo = this.pendingSegment_;
55532
            const timingInfoProperty = timingInfoPropertyForMedia(mediaType);
55533
            segmentInfo[timingInfoProperty] = segmentInfo[timingInfoProperty] || {};
55534
            segmentInfo[timingInfoProperty][timeType] = time;
55535
            this.logger_(`timinginfo: ${mediaType} - ${timeType} - ${time}`); // check if any calls were waiting on the timing info
55536
 
55537
            if (this.hasEnoughInfoToAppend_()) {
55538
                this.processCallQueue_();
55539
            }
55540
        }
55541
        handleCaptions_(simpleSegment, captionData) {
55542
            this.earlyAbortWhenNeeded_(simpleSegment.stats);
55543
            if (this.checkForAbort_(simpleSegment.requestId)) {
55544
                return;
55545
            } // This could only happen with fmp4 segments, but
55546
            // should still not happen in general
55547
 
55548
            if (captionData.length === 0) {
55549
                this.logger_('SegmentLoader received no captions from a caption event');
55550
                return;
55551
            }
55552
            const segmentInfo = this.pendingSegment_; // Wait until we have some video data so that caption timing
55553
            // can be adjusted by the timestamp offset
55554
 
55555
            if (!segmentInfo.hasAppendedData_) {
55556
                this.metadataQueue_.caption.push(this.handleCaptions_.bind(this, simpleSegment, captionData));
55557
                return;
55558
            }
55559
            const timestampOffset = this.sourceUpdater_.videoTimestampOffset() === null ? this.sourceUpdater_.audioTimestampOffset() : this.sourceUpdater_.videoTimestampOffset();
55560
            const captionTracks = {}; // get total start/end and captions for each track/stream
55561
 
55562
            captionData.forEach(caption => {
55563
                // caption.stream is actually a track name...
55564
                // set to the existing values in tracks or default values
55565
                captionTracks[caption.stream] = captionTracks[caption.stream] || {
55566
                    // Infinity, as any other value will be less than this
55567
                    startTime: Infinity,
55568
                    captions: [],
55569
                    // 0 as an other value will be more than this
55570
                    endTime: 0
55571
                };
55572
                const captionTrack = captionTracks[caption.stream];
55573
                captionTrack.startTime = Math.min(captionTrack.startTime, caption.startTime + timestampOffset);
55574
                captionTrack.endTime = Math.max(captionTrack.endTime, caption.endTime + timestampOffset);
55575
                captionTrack.captions.push(caption);
55576
            });
55577
            Object.keys(captionTracks).forEach(trackName => {
55578
                const {
55579
                    startTime,
55580
                    endTime,
55581
                    captions
55582
                } = captionTracks[trackName];
55583
                const inbandTextTracks = this.inbandTextTracks_;
55584
                this.logger_(`adding cues from ${startTime} -> ${endTime} for ${trackName}`);
55585
                createCaptionsTrackIfNotExists(inbandTextTracks, this.vhs_.tech_, trackName); // clear out any cues that start and end at the same time period for the same track.
55586
                // We do this because a rendition change that also changes the timescale for captions
55587
                // will result in captions being re-parsed for certain segments. If we add them again
55588
                // without clearing we will have two of the same captions visible.
55589
 
55590
                removeCuesFromTrack(startTime, endTime, inbandTextTracks[trackName]);
55591
                addCaptionData({
55592
                    captionArray: captions,
55593
                    inbandTextTracks,
55594
                    timestampOffset
55595
                });
55596
            }); // Reset stored captions since we added parsed
55597
            // captions to a text track at this point
55598
 
55599
            if (this.transmuxer_) {
55600
                this.transmuxer_.postMessage({
55601
                    action: 'clearParsedMp4Captions'
55602
                });
55603
            }
55604
        }
55605
        handleId3_(simpleSegment, id3Frames, dispatchType) {
55606
            this.earlyAbortWhenNeeded_(simpleSegment.stats);
55607
            if (this.checkForAbort_(simpleSegment.requestId)) {
55608
                return;
55609
            }
55610
            const segmentInfo = this.pendingSegment_; // we need to have appended data in order for the timestamp offset to be set
55611
 
55612
            if (!segmentInfo.hasAppendedData_) {
55613
                this.metadataQueue_.id3.push(this.handleId3_.bind(this, simpleSegment, id3Frames, dispatchType));
55614
                return;
55615
            }
55616
            this.addMetadataToTextTrack(dispatchType, id3Frames, this.duration_());
55617
        }
55618
        processMetadataQueue_() {
55619
            this.metadataQueue_.id3.forEach(fn => fn());
55620
            this.metadataQueue_.caption.forEach(fn => fn());
55621
            this.metadataQueue_.id3 = [];
55622
            this.metadataQueue_.caption = [];
55623
        }
55624
        processCallQueue_() {
55625
            const callQueue = this.callQueue_; // Clear out the queue before the queued functions are run, since some of the
55626
            // functions may check the length of the load queue and default to pushing themselves
55627
            // back onto the queue.
55628
 
55629
            this.callQueue_ = [];
55630
            callQueue.forEach(fun => fun());
55631
        }
55632
        processLoadQueue_() {
55633
            const loadQueue = this.loadQueue_; // Clear out the queue before the queued functions are run, since some of the
55634
            // functions may check the length of the load queue and default to pushing themselves
55635
            // back onto the queue.
55636
 
55637
            this.loadQueue_ = [];
55638
            loadQueue.forEach(fun => fun());
55639
        }
55640
        /**
55641
         * Determines whether the loader has enough info to load the next segment.
55642
         *
55643
         * @return {boolean}
55644
         *         Whether or not the loader has enough info to load the next segment
55645
         */
55646
 
55647
        hasEnoughInfoToLoad_() {
55648
            // Since primary timing goes by video, only the audio loader potentially needs to wait
55649
            // to load.
55650
            if (this.loaderType_ !== 'audio') {
55651
                return true;
55652
            }
55653
            const segmentInfo = this.pendingSegment_; // A fill buffer must have already run to establish a pending segment before there's
55654
            // enough info to load.
55655
 
55656
            if (!segmentInfo) {
55657
                return false;
55658
            } // The first segment can and should be loaded immediately so that source buffers are
55659
            // created together (before appending). Source buffer creation uses the presence of
55660
            // audio and video data to determine whether to create audio/video source buffers, and
55661
            // uses processed (transmuxed or parsed) media to determine the types required.
55662
 
55663
            if (!this.getCurrentMediaInfo_()) {
55664
                return true;
55665
            }
55666
            if (
55667
                // Technically, instead of waiting to load a segment on timeline changes, a segment
55668
                // can be requested and downloaded and only wait before it is transmuxed or parsed.
55669
                // But in practice, there are a few reasons why it is better to wait until a loader
55670
                // is ready to append that segment before requesting and downloading:
55671
                //
55672
                // 1. Because audio and main loaders cross discontinuities together, if this loader
55673
                //    is waiting for the other to catch up, then instead of requesting another
55674
                //    segment and using up more bandwidth, by not yet loading, more bandwidth is
55675
                //    allotted to the loader currently behind.
55676
                // 2. media-segment-request doesn't have to have logic to consider whether a segment
55677
                // is ready to be processed or not, isolating the queueing behavior to the loader.
55678
                // 3. The audio loader bases some of its segment properties on timing information
55679
                //    provided by the main loader, meaning that, if the logic for waiting on
55680
                //    processing was in media-segment-request, then it would also need to know how
55681
                //    to re-generate the segment information after the main loader caught up.
55682
                shouldWaitForTimelineChange({
55683
                    timelineChangeController: this.timelineChangeController_,
55684
                    currentTimeline: this.currentTimeline_,
55685
                    segmentTimeline: segmentInfo.timeline,
55686
                    loaderType: this.loaderType_,
55687
                    audioDisabled: this.audioDisabled_
55688
                })) {
55689
                return false;
55690
            }
55691
            return true;
55692
        }
55693
        getCurrentMediaInfo_(segmentInfo = this.pendingSegment_) {
55694
            return segmentInfo && segmentInfo.trackInfo || this.currentMediaInfo_;
55695
        }
55696
        getMediaInfo_(segmentInfo = this.pendingSegment_) {
55697
            return this.getCurrentMediaInfo_(segmentInfo) || this.startingMediaInfo_;
55698
        }
55699
        getPendingSegmentPlaylist() {
55700
            return this.pendingSegment_ ? this.pendingSegment_.playlist : null;
55701
        }
55702
        hasEnoughInfoToAppend_() {
55703
            if (!this.sourceUpdater_.ready()) {
55704
                return false;
55705
            } // If content needs to be removed or the loader is waiting on an append reattempt,
55706
            // then no additional content should be appended until the prior append is resolved.
55707
 
55708
            if (this.waitingOnRemove_ || this.quotaExceededErrorRetryTimeout_) {
55709
                return false;
55710
            }
55711
            const segmentInfo = this.pendingSegment_;
55712
            const trackInfo = this.getCurrentMediaInfo_(); // no segment to append any data for or
55713
            // we do not have information on this specific
55714
            // segment yet
55715
 
55716
            if (!segmentInfo || !trackInfo) {
55717
                return false;
55718
            }
55719
            const {
55720
                hasAudio,
55721
                hasVideo,
55722
                isMuxed
55723
            } = trackInfo;
55724
            if (hasVideo && !segmentInfo.videoTimingInfo) {
55725
                return false;
55726
            } // muxed content only relies on video timing information for now.
55727
 
55728
            if (hasAudio && !this.audioDisabled_ && !isMuxed && !segmentInfo.audioTimingInfo) {
55729
                return false;
55730
            }
55731
            if (shouldWaitForTimelineChange({
55732
                timelineChangeController: this.timelineChangeController_,
55733
                currentTimeline: this.currentTimeline_,
55734
                segmentTimeline: segmentInfo.timeline,
55735
                loaderType: this.loaderType_,
55736
                audioDisabled: this.audioDisabled_
55737
            })) {
55738
                return false;
55739
            }
55740
            return true;
55741
        }
55742
        handleData_(simpleSegment, result) {
55743
            this.earlyAbortWhenNeeded_(simpleSegment.stats);
55744
            if (this.checkForAbort_(simpleSegment.requestId)) {
55745
                return;
55746
            } // If there's anything in the call queue, then this data came later and should be
55747
            // executed after the calls currently queued.
55748
 
55749
            if (this.callQueue_.length || !this.hasEnoughInfoToAppend_()) {
55750
                this.callQueue_.push(this.handleData_.bind(this, simpleSegment, result));
55751
                return;
55752
            }
55753
            const segmentInfo = this.pendingSegment_; // update the time mapping so we can translate from display time to media time
55754
 
55755
            this.setTimeMapping_(segmentInfo.timeline); // for tracking overall stats
55756
 
55757
            this.updateMediaSecondsLoaded_(segmentInfo.part || segmentInfo.segment); // Note that the state isn't changed from loading to appending. This is because abort
55758
            // logic may change behavior depending on the state, and changing state too early may
55759
            // inflate our estimates of bandwidth. In the future this should be re-examined to
55760
            // note more granular states.
55761
            // don't process and append data if the mediaSource is closed
55762
 
55763
            if (this.mediaSource_.readyState === 'closed') {
55764
                return;
55765
            } // if this request included an initialization segment, save that data
55766
            // to the initSegment cache
55767
 
55768
            if (simpleSegment.map) {
55769
                simpleSegment.map = this.initSegmentForMap(simpleSegment.map, true); // move over init segment properties to media request
55770
 
55771
                segmentInfo.segment.map = simpleSegment.map;
55772
            } // if this request included a segment key, save that data in the cache
55773
 
55774
            if (simpleSegment.key) {
55775
                this.segmentKey(simpleSegment.key, true);
55776
            }
55777
            segmentInfo.isFmp4 = simpleSegment.isFmp4;
55778
            segmentInfo.timingInfo = segmentInfo.timingInfo || {};
55779
            if (segmentInfo.isFmp4) {
55780
                this.trigger('fmp4');
55781
                segmentInfo.timingInfo.start = segmentInfo[timingInfoPropertyForMedia(result.type)].start;
55782
            } else {
55783
                const trackInfo = this.getCurrentMediaInfo_();
55784
                const useVideoTimingInfo = this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo;
55785
                let firstVideoFrameTimeForData;
55786
                if (useVideoTimingInfo) {
55787
                    firstVideoFrameTimeForData = segmentInfo.videoTimingInfo.start;
55788
                } // Segment loader knows more about segment timing than the transmuxer (in certain
55789
                // aspects), so make any changes required for a more accurate start time.
55790
                // Don't set the end time yet, as the segment may not be finished processing.
55791
 
55792
                segmentInfo.timingInfo.start = this.trueSegmentStart_({
55793
                    currentStart: segmentInfo.timingInfo.start,
55794
                    playlist: segmentInfo.playlist,
55795
                    mediaIndex: segmentInfo.mediaIndex,
55796
                    currentVideoTimestampOffset: this.sourceUpdater_.videoTimestampOffset(),
55797
                    useVideoTimingInfo,
55798
                    firstVideoFrameTimeForData,
55799
                    videoTimingInfo: segmentInfo.videoTimingInfo,
55800
                    audioTimingInfo: segmentInfo.audioTimingInfo
55801
                });
55802
            } // Init segments for audio and video only need to be appended in certain cases. Now
55803
            // that data is about to be appended, we can check the final cases to determine
55804
            // whether we should append an init segment.
55805
 
55806
            this.updateAppendInitSegmentStatus(segmentInfo, result.type); // Timestamp offset should be updated once we get new data and have its timing info,
55807
            // as we use the start of the segment to offset the best guess (playlist provided)
55808
            // timestamp offset.
55809
 
55810
            this.updateSourceBufferTimestampOffset_(segmentInfo); // if this is a sync request we need to determine whether it should
55811
            // be appended or not.
55812
 
55813
            if (segmentInfo.isSyncRequest) {
55814
                // first save/update our timing info for this segment.
55815
                // this is what allows us to choose an accurate segment
55816
                // and the main reason we make a sync request.
55817
                this.updateTimingInfoEnd_(segmentInfo);
55818
                this.syncController_.saveSegmentTimingInfo({
55819
                    segmentInfo,
55820
                    shouldSaveTimelineMapping: this.loaderType_ === 'main'
55821
                });
55822
                const next = this.chooseNextRequest_(); // If the sync request isn't the segment that would be requested next
55823
                // after taking into account its timing info, do not append it.
55824
 
55825
                if (next.mediaIndex !== segmentInfo.mediaIndex || next.partIndex !== segmentInfo.partIndex) {
55826
                    this.logger_('sync segment was incorrect, not appending');
55827
                    return;
55828
                } // otherwise append it like any other segment as our guess was correct.
55829
 
55830
                this.logger_('sync segment was correct, appending');
55831
            } // Save some state so that in the future anything waiting on first append (and/or
55832
            // timestamp offset(s)) can process immediately. While the extra state isn't optimal,
55833
            // we need some notion of whether the timestamp offset or other relevant information
55834
            // has had a chance to be set.
55835
 
55836
            segmentInfo.hasAppendedData_ = true; // Now that the timestamp offset should be set, we can append any waiting ID3 tags.
55837
 
55838
            this.processMetadataQueue_();
55839
            this.appendData_(segmentInfo, result);
55840
        }
55841
        updateAppendInitSegmentStatus(segmentInfo, type) {
55842
            // alt audio doesn't manage timestamp offset
55843
            if (this.loaderType_ === 'main' && typeof segmentInfo.timestampOffset === 'number' &&
55844
                // in the case that we're handling partial data, we don't want to append an init
55845
                // segment for each chunk
55846
                !segmentInfo.changedTimestampOffset) {
55847
                // if the timestamp offset changed, the timeline may have changed, so we have to re-
55848
                // append init segments
55849
                this.appendInitSegment_ = {
55850
                    audio: true,
55851
                    video: true
55852
                };
55853
            }
55854
            if (this.playlistOfLastInitSegment_[type] !== segmentInfo.playlist) {
55855
                // make sure we append init segment on playlist changes, in case the media config
55856
                // changed
55857
                this.appendInitSegment_[type] = true;
55858
            }
55859
        }
55860
        getInitSegmentAndUpdateState_({
55861
                                          type,
55862
                                          initSegment,
55863
                                          map,
55864
                                          playlist
55865
                                      }) {
55866
            // "The EXT-X-MAP tag specifies how to obtain the Media Initialization Section
55867
            // (Section 3) required to parse the applicable Media Segments.  It applies to every
55868
            // Media Segment that appears after it in the Playlist until the next EXT-X-MAP tag
55869
            // or until the end of the playlist."
55870
            // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.5
55871
            if (map) {
55872
                const id = initSegmentId(map);
55873
                if (this.activeInitSegmentId_ === id) {
55874
                    // don't need to re-append the init segment if the ID matches
55875
                    return null;
55876
                } // a map-specified init segment takes priority over any transmuxed (or otherwise
55877
                // obtained) init segment
55878
                //
55879
                // this also caches the init segment for later use
55880
 
55881
                initSegment = this.initSegmentForMap(map, true).bytes;
55882
                this.activeInitSegmentId_ = id;
55883
            } // We used to always prepend init segments for video, however, that shouldn't be
55884
            // necessary. Instead, we should only append on changes, similar to what we've always
55885
            // done for audio. This is more important (though may not be that important) for
55886
            // frame-by-frame appending for LHLS, simply because of the increased quantity of
55887
            // appends.
55888
 
55889
            if (initSegment && this.appendInitSegment_[type]) {
55890
                // Make sure we track the playlist that we last used for the init segment, so that
55891
                // we can re-append the init segment in the event that we get data from a new
55892
                // playlist. Discontinuities and track changes are handled in other sections.
55893
                this.playlistOfLastInitSegment_[type] = playlist; // Disable future init segment appends for this type. Until a change is necessary.
55894
 
55895
                this.appendInitSegment_[type] = false; // we need to clear out the fmp4 active init segment id, since
55896
                // we are appending the muxer init segment
55897
 
55898
                this.activeInitSegmentId_ = null;
55899
                return initSegment;
55900
            }
55901
            return null;
55902
        }
55903
        handleQuotaExceededError_({
55904
                                      segmentInfo,
55905
                                      type,
55906
                                      bytes
55907
                                  }, error) {
55908
            const audioBuffered = this.sourceUpdater_.audioBuffered();
55909
            const videoBuffered = this.sourceUpdater_.videoBuffered(); // For now we're ignoring any notion of gaps in the buffer, but they, in theory,
55910
            // should be cleared out during the buffer removals. However, log in case it helps
55911
            // debug.
55912
 
55913
            if (audioBuffered.length > 1) {
55914
                this.logger_('On QUOTA_EXCEEDED_ERR, found gaps in the audio buffer: ' + timeRangesToArray(audioBuffered).join(', '));
55915
            }
55916
            if (videoBuffered.length > 1) {
55917
                this.logger_('On QUOTA_EXCEEDED_ERR, found gaps in the video buffer: ' + timeRangesToArray(videoBuffered).join(', '));
55918
            }
55919
            const audioBufferStart = audioBuffered.length ? audioBuffered.start(0) : 0;
55920
            const audioBufferEnd = audioBuffered.length ? audioBuffered.end(audioBuffered.length - 1) : 0;
55921
            const videoBufferStart = videoBuffered.length ? videoBuffered.start(0) : 0;
55922
            const videoBufferEnd = videoBuffered.length ? videoBuffered.end(videoBuffered.length - 1) : 0;
55923
            if (audioBufferEnd - audioBufferStart <= MIN_BACK_BUFFER && videoBufferEnd - videoBufferStart <= MIN_BACK_BUFFER) {
55924
                // Can't remove enough buffer to make room for new segment (or the browser doesn't
55925
                // allow for appends of segments this size). In the future, it may be possible to
55926
                // split up the segment and append in pieces, but for now, error out this playlist
55927
                // in an attempt to switch to a more manageable rendition.
55928
                this.logger_('On QUOTA_EXCEEDED_ERR, single segment too large to append to ' + 'buffer, triggering an error. ' + `Appended byte length: ${bytes.byteLength}, ` + `audio buffer: ${timeRangesToArray(audioBuffered).join(', ')}, ` + `video buffer: ${timeRangesToArray(videoBuffered).join(', ')}, `);
55929
                this.error({
55930
                    message: 'Quota exceeded error with append of a single segment of content',
55931
                    excludeUntil: Infinity
55932
                });
55933
                this.trigger('error');
55934
                return;
55935
            } // To try to resolve the quota exceeded error, clear back buffer and retry. This means
55936
            // that the segment-loader should block on future events until this one is handled, so
55937
            // that it doesn't keep moving onto further segments. Adding the call to the call
55938
            // queue will prevent further appends until waitingOnRemove_ and
55939
            // quotaExceededErrorRetryTimeout_ are cleared.
55940
            //
55941
            // Note that this will only block the current loader. In the case of demuxed content,
55942
            // the other load may keep filling as fast as possible. In practice, this should be
55943
            // OK, as it is a rare case when either audio has a high enough bitrate to fill up a
55944
            // source buffer, or video fills without enough room for audio to append (and without
55945
            // the availability of clearing out seconds of back buffer to make room for audio).
55946
            // But it might still be good to handle this case in the future as a TODO.
55947
 
55948
            this.waitingOnRemove_ = true;
55949
            this.callQueue_.push(this.appendToSourceBuffer_.bind(this, {
55950
                segmentInfo,
55951
                type,
55952
                bytes
55953
            }));
55954
            const currentTime = this.currentTime_(); // Try to remove as much audio and video as possible to make room for new content
55955
            // before retrying.
55956
 
55957
            const timeToRemoveUntil = currentTime - MIN_BACK_BUFFER;
55958
            this.logger_(`On QUOTA_EXCEEDED_ERR, removing audio/video from 0 to ${timeToRemoveUntil}`);
55959
            this.remove(0, timeToRemoveUntil, () => {
55960
                this.logger_(`On QUOTA_EXCEEDED_ERR, retrying append in ${MIN_BACK_BUFFER}s`);
55961
                this.waitingOnRemove_ = false; // wait the length of time alotted in the back buffer to prevent wasted
55962
                // attempts (since we can't clear less than the minimum)
55963
 
55964
                this.quotaExceededErrorRetryTimeout_ = window.setTimeout(() => {
55965
                    this.logger_('On QUOTA_EXCEEDED_ERR, re-processing call queue');
55966
                    this.quotaExceededErrorRetryTimeout_ = null;
55967
                    this.processCallQueue_();
55968
                }, MIN_BACK_BUFFER * 1000);
55969
            }, true);
55970
        }
55971
        handleAppendError_({
55972
                               segmentInfo,
55973
                               type,
55974
                               bytes
55975
                           }, error) {
55976
            // if there's no error, nothing to do
55977
            if (!error) {
55978
                return;
55979
            }
55980
            if (error.code === QUOTA_EXCEEDED_ERR) {
55981
                this.handleQuotaExceededError_({
55982
                    segmentInfo,
55983
                    type,
55984
                    bytes
55985
                }); // A quota exceeded error should be recoverable with a future re-append, so no need
55986
                // to trigger an append error.
55987
 
55988
                return;
55989
            }
55990
            this.logger_('Received non QUOTA_EXCEEDED_ERR on append', error);
55991
            this.error(`${type} append of ${bytes.length}b failed for segment ` + `#${segmentInfo.mediaIndex} in playlist ${segmentInfo.playlist.id}`); // If an append errors, we often can't recover.
55992
            // (see https://w3c.github.io/media-source/#sourcebuffer-append-error).
55993
            //
55994
            // Trigger a special error so that it can be handled separately from normal,
55995
            // recoverable errors.
55996
 
55997
            this.trigger('appenderror');
55998
        }
55999
        appendToSourceBuffer_({
56000
                                  segmentInfo,
56001
                                  type,
56002
                                  initSegment,
56003
                                  data,
56004
                                  bytes
56005
                              }) {
56006
            // If this is a re-append, bytes were already created and don't need to be recreated
56007
            if (!bytes) {
56008
                const segments = [data];
56009
                let byteLength = data.byteLength;
56010
                if (initSegment) {
56011
                    // if the media initialization segment is changing, append it before the content
56012
                    // segment
56013
                    segments.unshift(initSegment);
56014
                    byteLength += initSegment.byteLength;
56015
                } // Technically we should be OK appending the init segment separately, however, we
56016
                // haven't yet tested that, and prepending is how we have always done things.
56017
 
56018
                bytes = concatSegments({
56019
                    bytes: byteLength,
56020
                    segments
56021
                });
56022
            }
56023
            this.sourceUpdater_.appendBuffer({
56024
                segmentInfo,
56025
                type,
56026
                bytes
56027
            }, this.handleAppendError_.bind(this, {
56028
                segmentInfo,
56029
                type,
56030
                bytes
56031
            }));
56032
        }
56033
        handleSegmentTimingInfo_(type, requestId, segmentTimingInfo) {
56034
            if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) {
56035
                return;
56036
            }
56037
            const segment = this.pendingSegment_.segment;
56038
            const timingInfoProperty = `${type}TimingInfo`;
56039
            if (!segment[timingInfoProperty]) {
56040
                segment[timingInfoProperty] = {};
56041
            }
56042
            segment[timingInfoProperty].transmuxerPrependedSeconds = segmentTimingInfo.prependedContentDuration || 0;
56043
            segment[timingInfoProperty].transmuxedPresentationStart = segmentTimingInfo.start.presentation;
56044
            segment[timingInfoProperty].transmuxedDecodeStart = segmentTimingInfo.start.decode;
56045
            segment[timingInfoProperty].transmuxedPresentationEnd = segmentTimingInfo.end.presentation;
56046
            segment[timingInfoProperty].transmuxedDecodeEnd = segmentTimingInfo.end.decode; // mainly used as a reference for debugging
56047
 
56048
            segment[timingInfoProperty].baseMediaDecodeTime = segmentTimingInfo.baseMediaDecodeTime;
56049
        }
56050
        appendData_(segmentInfo, result) {
56051
            const {
56052
                type,
56053
                data
56054
            } = result;
56055
            if (!data || !data.byteLength) {
56056
                return;
56057
            }
56058
            if (type === 'audio' && this.audioDisabled_) {
56059
                return;
56060
            }
56061
            const initSegment = this.getInitSegmentAndUpdateState_({
56062
                type,
56063
                initSegment: result.initSegment,
56064
                playlist: segmentInfo.playlist,
56065
                map: segmentInfo.isFmp4 ? segmentInfo.segment.map : null
56066
            });
56067
            this.appendToSourceBuffer_({
56068
                segmentInfo,
56069
                type,
56070
                initSegment,
56071
                data
56072
            });
56073
        }
56074
        /**
56075
         * load a specific segment from a request into the buffer
56076
         *
56077
         * @private
56078
         */
56079
 
56080
        loadSegment_(segmentInfo) {
56081
            this.state = 'WAITING';
56082
            this.pendingSegment_ = segmentInfo;
56083
            this.trimBackBuffer_(segmentInfo);
56084
            if (typeof segmentInfo.timestampOffset === 'number') {
56085
                if (this.transmuxer_) {
56086
                    this.transmuxer_.postMessage({
56087
                        action: 'clearAllMp4Captions'
56088
                    });
56089
                }
56090
            }
56091
            if (!this.hasEnoughInfoToLoad_()) {
56092
                this.loadQueue_.push(() => {
56093
                    // regenerate the audioAppendStart, timestampOffset, etc as they
56094
                    // may have changed since this function was added to the queue.
56095
                    const options = _extends$1({}, segmentInfo, {
56096
                        forceTimestampOffset: true
56097
                    });
56098
                    _extends$1(segmentInfo, this.generateSegmentInfo_(options));
56099
                    this.isPendingTimestampOffset_ = false;
56100
                    this.updateTransmuxerAndRequestSegment_(segmentInfo);
56101
                });
56102
                return;
56103
            }
56104
            this.updateTransmuxerAndRequestSegment_(segmentInfo);
56105
        }
56106
        updateTransmuxerAndRequestSegment_(segmentInfo) {
56107
            // We'll update the source buffer's timestamp offset once we have transmuxed data, but
56108
            // the transmuxer still needs to be updated before then.
56109
            //
56110
            // Even though keepOriginalTimestamps is set to true for the transmuxer, timestamp
56111
            // offset must be passed to the transmuxer for stream correcting adjustments.
56112
            if (this.shouldUpdateTransmuxerTimestampOffset_(segmentInfo.timestampOffset)) {
56113
                this.gopBuffer_.length = 0; // gopsToAlignWith was set before the GOP buffer was cleared
56114
 
56115
                segmentInfo.gopsToAlignWith = [];
56116
                this.timeMapping_ = 0; // reset values in the transmuxer since a discontinuity should start fresh
56117
 
56118
                this.transmuxer_.postMessage({
56119
                    action: 'reset'
56120
                });
56121
                this.transmuxer_.postMessage({
56122
                    action: 'setTimestampOffset',
56123
                    timestampOffset: segmentInfo.timestampOffset
56124
                });
56125
            }
56126
            const simpleSegment = this.createSimplifiedSegmentObj_(segmentInfo);
56127
            const isEndOfStream = this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist, segmentInfo.partIndex);
56128
            const isWalkingForward = this.mediaIndex !== null;
56129
            const isDiscontinuity = segmentInfo.timeline !== this.currentTimeline_ &&
56130
                // currentTimeline starts at -1, so we shouldn't end the timeline switching to 0,
56131
                // the first timeline
56132
                segmentInfo.timeline > 0;
56133
            const isEndOfTimeline = isEndOfStream || isWalkingForward && isDiscontinuity;
56134
            this.logger_(`Requesting ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated with this segment, but it is not cached (identified by a lack of bytes),
56135
            // then this init segment has never been seen before and should be appended.
56136
            //
56137
            // At this point the content type (audio/video or both) is not yet known, but it should be safe to set
56138
            // both to true and leave the decision of whether to append the init segment to append time.
56139
 
56140
            if (simpleSegment.map && !simpleSegment.map.bytes) {
56141
                this.logger_('going to request init segment.');
56142
                this.appendInitSegment_ = {
56143
                    video: true,
56144
                    audio: true
56145
                };
56146
            }
56147
            segmentInfo.abortRequests = mediaSegmentRequest({
56148
                xhr: this.vhs_.xhr,
56149
                xhrOptions: this.xhrOptions_,
56150
                decryptionWorker: this.decrypter_,
56151
                segment: simpleSegment,
56152
                abortFn: this.handleAbort_.bind(this, segmentInfo),
56153
                progressFn: this.handleProgress_.bind(this),
56154
                trackInfoFn: this.handleTrackInfo_.bind(this),
56155
                timingInfoFn: this.handleTimingInfo_.bind(this),
56156
                videoSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(this, 'video', segmentInfo.requestId),
56157
                audioSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(this, 'audio', segmentInfo.requestId),
56158
                captionsFn: this.handleCaptions_.bind(this),
56159
                isEndOfTimeline,
56160
                endedTimelineFn: () => {
56161
                    this.logger_('received endedtimeline callback');
56162
                },
56163
                id3Fn: this.handleId3_.bind(this),
56164
                dataFn: this.handleData_.bind(this),
56165
                doneFn: this.segmentRequestFinished_.bind(this),
56166
                onTransmuxerLog: ({
56167
                                      message,
56168
                                      level,
56169
                                      stream
56170
                                  }) => {
56171
                    this.logger_(`${segmentInfoString(segmentInfo)} logged from transmuxer stream ${stream} as a ${level}: ${message}`);
56172
                }
56173
            });
56174
        }
56175
        /**
56176
         * trim the back buffer so that we don't have too much data
56177
         * in the source buffer
56178
         *
56179
         * @private
56180
         *
56181
         * @param {Object} segmentInfo - the current segment
56182
         */
56183
 
56184
        trimBackBuffer_(segmentInfo) {
56185
            const removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of
56186
            // buffer and a very conservative "garbage collector"
56187
            // We manually clear out the old buffer to ensure
56188
            // we don't trigger the QuotaExceeded error
56189
            // on the source buffer during subsequent appends
56190
 
56191
            if (removeToTime > 0) {
56192
                this.remove(0, removeToTime);
56193
            }
56194
        }
56195
        /**
56196
         * created a simplified copy of the segment object with just the
56197
         * information necessary to perform the XHR and decryption
56198
         *
56199
         * @private
56200
         *
56201
         * @param {Object} segmentInfo - the current segment
56202
         * @return {Object} a simplified segment object copy
56203
         */
56204
 
56205
        createSimplifiedSegmentObj_(segmentInfo) {
56206
            const segment = segmentInfo.segment;
56207
            const part = segmentInfo.part;
56208
            const simpleSegment = {
56209
                resolvedUri: part ? part.resolvedUri : segment.resolvedUri,
56210
                byterange: part ? part.byterange : segment.byterange,
56211
                requestId: segmentInfo.requestId,
56212
                transmuxer: segmentInfo.transmuxer,
56213
                audioAppendStart: segmentInfo.audioAppendStart,
56214
                gopsToAlignWith: segmentInfo.gopsToAlignWith,
56215
                part: segmentInfo.part
56216
            };
56217
            const previousSegment = segmentInfo.playlist.segments[segmentInfo.mediaIndex - 1];
56218
            if (previousSegment && previousSegment.timeline === segment.timeline) {
56219
                // The baseStartTime of a segment is used to handle rollover when probing the TS
56220
                // segment to retrieve timing information. Since the probe only looks at the media's
56221
                // times (e.g., PTS and DTS values of the segment), and doesn't consider the
56222
                // player's time (e.g., player.currentTime()), baseStartTime should reflect the
56223
                // media time as well. transmuxedDecodeEnd represents the end time of a segment, in
56224
                // seconds of media time, so should be used here. The previous segment is used since
56225
                // the end of the previous segment should represent the beginning of the current
56226
                // segment, so long as they are on the same timeline.
56227
                if (previousSegment.videoTimingInfo) {
56228
                    simpleSegment.baseStartTime = previousSegment.videoTimingInfo.transmuxedDecodeEnd;
56229
                } else if (previousSegment.audioTimingInfo) {
56230
                    simpleSegment.baseStartTime = previousSegment.audioTimingInfo.transmuxedDecodeEnd;
56231
                }
56232
            }
56233
            if (segment.key) {
56234
                // if the media sequence is greater than 2^32, the IV will be incorrect
56235
                // assuming 10s segments, that would be about 1300 years
56236
                const iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
56237
                simpleSegment.key = this.segmentKey(segment.key);
56238
                simpleSegment.key.iv = iv;
56239
            }
56240
            if (segment.map) {
56241
                simpleSegment.map = this.initSegmentForMap(segment.map);
56242
            }
56243
            return simpleSegment;
56244
        }
56245
        saveTransferStats_(stats) {
56246
            // every request counts as a media request even if it has been aborted
56247
            // or canceled due to a timeout
56248
            this.mediaRequests += 1;
56249
            if (stats) {
56250
                this.mediaBytesTransferred += stats.bytesReceived;
56251
                this.mediaTransferDuration += stats.roundTripTime;
56252
            }
56253
        }
56254
        saveBandwidthRelatedStats_(duration, stats) {
56255
            // byteLength will be used for throughput, and should be based on bytes receieved,
56256
            // which we only know at the end of the request and should reflect total bytes
56257
            // downloaded rather than just bytes processed from components of the segment
56258
            this.pendingSegment_.byteLength = stats.bytesReceived;
56259
            if (duration < MIN_SEGMENT_DURATION_TO_SAVE_STATS) {
56260
                this.logger_(`Ignoring segment's bandwidth because its duration of ${duration}` + ` is less than the min to record ${MIN_SEGMENT_DURATION_TO_SAVE_STATS}`);
56261
                return;
56262
            }
56263
            this.bandwidth = stats.bandwidth;
56264
            this.roundTrip = stats.roundTripTime;
56265
        }
56266
        handleTimeout_() {
56267
            // although the VTT segment loader bandwidth isn't really used, it's good to
56268
            // maintain functinality between segment loaders
56269
            this.mediaRequestsTimedout += 1;
56270
            this.bandwidth = 1;
56271
            this.roundTrip = NaN;
56272
            this.trigger('bandwidthupdate');
56273
            this.trigger('timeout');
56274
        }
56275
        /**
56276
         * Handle the callback from the segmentRequest function and set the
56277
         * associated SegmentLoader state and errors if necessary
56278
         *
56279
         * @private
56280
         */
56281
 
56282
        segmentRequestFinished_(error, simpleSegment, result) {
56283
            // TODO handle special cases, e.g., muxed audio/video but only audio in the segment
56284
            // check the call queue directly since this function doesn't need to deal with any
56285
            // data, and can continue even if the source buffers are not set up and we didn't get
56286
            // any data from the segment
56287
            if (this.callQueue_.length) {
56288
                this.callQueue_.push(this.segmentRequestFinished_.bind(this, error, simpleSegment, result));
56289
                return;
56290
            }
56291
            this.saveTransferStats_(simpleSegment.stats); // The request was aborted and the SegmentLoader has already been reset
56292
 
56293
            if (!this.pendingSegment_) {
56294
                return;
56295
            } // the request was aborted and the SegmentLoader has already started
56296
            // another request. this can happen when the timeout for an aborted
56297
            // request triggers due to a limitation in the XHR library
56298
            // do not count this as any sort of request or we risk double-counting
56299
 
56300
            if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
56301
                return;
56302
            } // an error occurred from the active pendingSegment_ so reset everything
56303
 
56304
            if (error) {
56305
                this.pendingSegment_ = null;
56306
                this.state = 'READY'; // aborts are not a true error condition and nothing corrective needs to be done
56307
 
56308
                if (error.code === REQUEST_ERRORS.ABORTED) {
56309
                    return;
56310
                }
56311
                this.pause(); // the error is really just that at least one of the requests timed-out
56312
                // set the bandwidth to a very low value and trigger an ABR switch to
56313
                // take emergency action
56314
 
56315
                if (error.code === REQUEST_ERRORS.TIMEOUT) {
56316
                    this.handleTimeout_();
56317
                    return;
56318
                } // if control-flow has arrived here, then the error is real
56319
                // emit an error event to exclude the current playlist
56320
 
56321
                this.mediaRequestsErrored += 1;
56322
                this.error(error);
56323
                this.trigger('error');
56324
                return;
56325
            }
56326
            const segmentInfo = this.pendingSegment_; // the response was a success so set any bandwidth stats the request
56327
            // generated for ABR purposes
56328
 
56329
            this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats);
56330
            segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests;
56331
            if (result.gopInfo) {
56332
                this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, result.gopInfo, this.safeAppend_);
56333
            } // Although we may have already started appending on progress, we shouldn't switch the
56334
            // state away from loading until we are officially done loading the segment data.
56335
 
56336
            this.state = 'APPENDING'; // used for testing
56337
 
56338
            this.trigger('appending');
56339
            this.waitForAppendsToComplete_(segmentInfo);
56340
        }
56341
        setTimeMapping_(timeline) {
56342
            const timelineMapping = this.syncController_.mappingForTimeline(timeline);
56343
            if (timelineMapping !== null) {
56344
                this.timeMapping_ = timelineMapping;
56345
            }
56346
        }
56347
        updateMediaSecondsLoaded_(segment) {
56348
            if (typeof segment.start === 'number' && typeof segment.end === 'number') {
56349
                this.mediaSecondsLoaded += segment.end - segment.start;
56350
            } else {
56351
                this.mediaSecondsLoaded += segment.duration;
56352
            }
56353
        }
56354
        shouldUpdateTransmuxerTimestampOffset_(timestampOffset) {
56355
            if (timestampOffset === null) {
56356
                return false;
56357
            } // note that we're potentially using the same timestamp offset for both video and
56358
            // audio
56359
 
56360
            if (this.loaderType_ === 'main' && timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) {
56361
                return true;
56362
            }
56363
            if (!this.audioDisabled_ && timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) {
56364
                return true;
56365
            }
56366
            return false;
56367
        }
56368
        trueSegmentStart_({
56369
                              currentStart,
56370
                              playlist,
56371
                              mediaIndex,
56372
                              firstVideoFrameTimeForData,
56373
                              currentVideoTimestampOffset,
56374
                              useVideoTimingInfo,
56375
                              videoTimingInfo,
56376
                              audioTimingInfo
56377
                          }) {
56378
            if (typeof currentStart !== 'undefined') {
56379
                // if start was set once, keep using it
56380
                return currentStart;
56381
            }
56382
            if (!useVideoTimingInfo) {
56383
                return audioTimingInfo.start;
56384
            }
56385
            const previousSegment = playlist.segments[mediaIndex - 1]; // The start of a segment should be the start of the first full frame contained
56386
            // within that segment. Since the transmuxer maintains a cache of incomplete data
56387
            // from and/or the last frame seen, the start time may reflect a frame that starts
56388
            // in the previous segment. Check for that case and ensure the start time is
56389
            // accurate for the segment.
56390
 
56391
            if (mediaIndex === 0 || !previousSegment || typeof previousSegment.start === 'undefined' || previousSegment.end !== firstVideoFrameTimeForData + currentVideoTimestampOffset) {
56392
                return firstVideoFrameTimeForData;
56393
            }
56394
            return videoTimingInfo.start;
56395
        }
56396
        waitForAppendsToComplete_(segmentInfo) {
56397
            const trackInfo = this.getCurrentMediaInfo_(segmentInfo);
56398
            if (!trackInfo) {
56399
                this.error({
56400
                    message: 'No starting media returned, likely due to an unsupported media format.',
56401
                    playlistExclusionDuration: Infinity
56402
                });
56403
                this.trigger('error');
56404
                return;
56405
            } // Although transmuxing is done, appends may not yet be finished. Throw a marker
56406
            // on each queue this loader is responsible for to ensure that the appends are
56407
            // complete.
56408
 
56409
            const {
56410
                hasAudio,
56411
                hasVideo,
56412
                isMuxed
56413
            } = trackInfo;
56414
            const waitForVideo = this.loaderType_ === 'main' && hasVideo;
56415
            const waitForAudio = !this.audioDisabled_ && hasAudio && !isMuxed;
56416
            segmentInfo.waitingOnAppends = 0; // segments with no data
56417
 
56418
            if (!segmentInfo.hasAppendedData_) {
56419
                if (!segmentInfo.timingInfo && typeof segmentInfo.timestampOffset === 'number') {
56420
                    // When there's no audio or video data in the segment, there's no audio or video
56421
                    // timing information.
56422
                    //
56423
                    // If there's no audio or video timing information, then the timestamp offset
56424
                    // can't be adjusted to the appropriate value for the transmuxer and source
56425
                    // buffers.
56426
                    //
56427
                    // Therefore, the next segment should be used to set the timestamp offset.
56428
                    this.isPendingTimestampOffset_ = true;
56429
                } // override settings for metadata only segments
56430
 
56431
                segmentInfo.timingInfo = {
56432
                    start: 0
56433
                };
56434
                segmentInfo.waitingOnAppends++;
56435
                if (!this.isPendingTimestampOffset_) {
56436
                    // update the timestampoffset
56437
                    this.updateSourceBufferTimestampOffset_(segmentInfo); // make sure the metadata queue is processed even though we have
56438
                    // no video/audio data.
56439
 
56440
                    this.processMetadataQueue_();
56441
                } // append is "done" instantly with no data.
56442
 
56443
                this.checkAppendsDone_(segmentInfo);
56444
                return;
56445
            } // Since source updater could call back synchronously, do the increments first.
56446
 
56447
            if (waitForVideo) {
56448
                segmentInfo.waitingOnAppends++;
56449
            }
56450
            if (waitForAudio) {
56451
                segmentInfo.waitingOnAppends++;
56452
            }
56453
            if (waitForVideo) {
56454
                this.sourceUpdater_.videoQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo));
56455
            }
56456
            if (waitForAudio) {
56457
                this.sourceUpdater_.audioQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo));
56458
            }
56459
        }
56460
        checkAppendsDone_(segmentInfo) {
56461
            if (this.checkForAbort_(segmentInfo.requestId)) {
56462
                return;
56463
            }
56464
            segmentInfo.waitingOnAppends--;
56465
            if (segmentInfo.waitingOnAppends === 0) {
56466
                this.handleAppendsDone_();
56467
            }
56468
        }
56469
        checkForIllegalMediaSwitch(trackInfo) {
56470
            const illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.getCurrentMediaInfo_(), trackInfo);
56471
            if (illegalMediaSwitchError) {
56472
                this.error({
56473
                    message: illegalMediaSwitchError,
56474
                    playlistExclusionDuration: Infinity
56475
                });
56476
                this.trigger('error');
56477
                return true;
56478
            }
56479
            return false;
56480
        }
56481
        updateSourceBufferTimestampOffset_(segmentInfo) {
56482
            if (segmentInfo.timestampOffset === null ||
56483
                // we don't yet have the start for whatever media type (video or audio) has
56484
                // priority, timing-wise, so we must wait
56485
                typeof segmentInfo.timingInfo.start !== 'number' ||
56486
                // already updated the timestamp offset for this segment
56487
                segmentInfo.changedTimestampOffset ||
56488
                // the alt audio loader should not be responsible for setting the timestamp offset
56489
                this.loaderType_ !== 'main') {
56490
                return;
56491
            }
56492
            let didChange = false; // Primary timing goes by video, and audio is trimmed in the transmuxer, meaning that
56493
            // the timing info here comes from video. In the event that the audio is longer than
56494
            // the video, this will trim the start of the audio.
56495
            // This also trims any offset from 0 at the beginning of the media
56496
 
56497
            segmentInfo.timestampOffset -= this.getSegmentStartTimeForTimestampOffsetCalculation_({
56498
                videoTimingInfo: segmentInfo.segment.videoTimingInfo,
56499
                audioTimingInfo: segmentInfo.segment.audioTimingInfo,
56500
                timingInfo: segmentInfo.timingInfo
56501
            }); // In the event that there are part segment downloads, each will try to update the
56502
            // timestamp offset. Retaining this bit of state prevents us from updating in the
56503
            // future (within the same segment), however, there may be a better way to handle it.
56504
 
56505
            segmentInfo.changedTimestampOffset = true;
56506
            if (segmentInfo.timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) {
56507
                this.sourceUpdater_.videoTimestampOffset(segmentInfo.timestampOffset);
56508
                didChange = true;
56509
            }
56510
            if (segmentInfo.timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) {
56511
                this.sourceUpdater_.audioTimestampOffset(segmentInfo.timestampOffset);
56512
                didChange = true;
56513
            }
56514
            if (didChange) {
56515
                this.trigger('timestampoffset');
56516
            }
56517
        }
56518
        getSegmentStartTimeForTimestampOffsetCalculation_({
56519
                                                              videoTimingInfo,
56520
                                                              audioTimingInfo,
56521
                                                              timingInfo
56522
                                                          }) {
56523
            if (!this.useDtsForTimestampOffset_) {
56524
                return timingInfo.start;
56525
            }
56526
            if (videoTimingInfo && typeof videoTimingInfo.transmuxedDecodeStart === 'number') {
56527
                return videoTimingInfo.transmuxedDecodeStart;
56528
            } // handle audio only
56529
 
56530
            if (audioTimingInfo && typeof audioTimingInfo.transmuxedDecodeStart === 'number') {
56531
                return audioTimingInfo.transmuxedDecodeStart;
56532
            } // handle content not transmuxed (e.g., MP4)
56533
 
56534
            return timingInfo.start;
56535
        }
56536
        updateTimingInfoEnd_(segmentInfo) {
56537
            segmentInfo.timingInfo = segmentInfo.timingInfo || {};
56538
            const trackInfo = this.getMediaInfo_();
56539
            const useVideoTimingInfo = this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo;
56540
            const prioritizedTimingInfo = useVideoTimingInfo && segmentInfo.videoTimingInfo ? segmentInfo.videoTimingInfo : segmentInfo.audioTimingInfo;
56541
            if (!prioritizedTimingInfo) {
56542
                return;
56543
            }
56544
            segmentInfo.timingInfo.end = typeof prioritizedTimingInfo.end === 'number' ?
56545
                // End time may not exist in a case where we aren't parsing the full segment (one
56546
                // current example is the case of fmp4), so use the rough duration to calculate an
56547
                // end time.
56548
                prioritizedTimingInfo.end : prioritizedTimingInfo.start + segmentInfo.duration;
56549
        }
56550
        /**
56551
         * callback to run when appendBuffer is finished. detects if we are
56552
         * in a good state to do things with the data we got, or if we need
56553
         * to wait for more
56554
         *
56555
         * @private
56556
         */
56557
 
56558
        handleAppendsDone_() {
56559
            // appendsdone can cause an abort
56560
            if (this.pendingSegment_) {
56561
                this.trigger('appendsdone');
56562
            }
56563
            if (!this.pendingSegment_) {
56564
                this.state = 'READY'; // TODO should this move into this.checkForAbort to speed up requests post abort in
56565
                // all appending cases?
56566
 
56567
                if (!this.paused()) {
56568
                    this.monitorBuffer_();
56569
                }
56570
                return;
56571
            }
56572
            const segmentInfo = this.pendingSegment_; // Now that the end of the segment has been reached, we can set the end time. It's
56573
            // best to wait until all appends are done so we're sure that the primary media is
56574
            // finished (and we have its end time).
56575
 
56576
            this.updateTimingInfoEnd_(segmentInfo);
56577
            if (this.shouldSaveSegmentTimingInfo_) {
56578
                // Timeline mappings should only be saved for the main loader. This is for multiple
56579
                // reasons:
56580
                //
56581
                // 1) Only one mapping is saved per timeline, meaning that if both the audio loader
56582
                //    and the main loader try to save the timeline mapping, whichever comes later
56583
                //    will overwrite the first. In theory this is OK, as the mappings should be the
56584
                //    same, however, it breaks for (2)
56585
                // 2) In the event of a live stream, the initial live point will make for a somewhat
56586
                //    arbitrary mapping. If audio and video streams are not perfectly in-sync, then
56587
                //    the mapping will be off for one of the streams, dependent on which one was
56588
                //    first saved (see (1)).
56589
                // 3) Primary timing goes by video in VHS, so the mapping should be video.
56590
                //
56591
                // Since the audio loader will wait for the main loader to load the first segment,
56592
                // the main loader will save the first timeline mapping, and ensure that there won't
56593
                // be a case where audio loads two segments without saving a mapping (thus leading
56594
                // to missing segment timing info).
56595
                this.syncController_.saveSegmentTimingInfo({
56596
                    segmentInfo,
56597
                    shouldSaveTimelineMapping: this.loaderType_ === 'main'
56598
                });
56599
            }
56600
            const segmentDurationMessage = getTroublesomeSegmentDurationMessage(segmentInfo, this.sourceType_);
56601
            if (segmentDurationMessage) {
56602
                if (segmentDurationMessage.severity === 'warn') {
56603
                    videojs.log.warn(segmentDurationMessage.message);
56604
                } else {
56605
                    this.logger_(segmentDurationMessage.message);
56606
                }
56607
            }
56608
            this.recordThroughput_(segmentInfo);
56609
            this.pendingSegment_ = null;
56610
            this.state = 'READY';
56611
            if (segmentInfo.isSyncRequest) {
56612
                this.trigger('syncinfoupdate'); // if the sync request was not appended
56613
                // then it was not the correct segment.
56614
                // throw it away and use the data it gave us
56615
                // to get the correct one.
56616
 
56617
                if (!segmentInfo.hasAppendedData_) {
56618
                    this.logger_(`Throwing away un-appended sync request ${segmentInfoString(segmentInfo)}`);
56619
                    return;
56620
                }
56621
            }
56622
            this.logger_(`Appended ${segmentInfoString(segmentInfo)}`);
56623
            this.addSegmentMetadataCue_(segmentInfo);
56624
            this.fetchAtBuffer_ = true;
56625
            if (this.currentTimeline_ !== segmentInfo.timeline) {
56626
                this.timelineChangeController_.lastTimelineChange({
56627
                    type: this.loaderType_,
56628
                    from: this.currentTimeline_,
56629
                    to: segmentInfo.timeline
56630
                }); // If audio is not disabled, the main segment loader is responsible for updating
56631
                // the audio timeline as well. If the content is video only, this won't have any
56632
                // impact.
56633
 
56634
                if (this.loaderType_ === 'main' && !this.audioDisabled_) {
56635
                    this.timelineChangeController_.lastTimelineChange({
56636
                        type: 'audio',
56637
                        from: this.currentTimeline_,
56638
                        to: segmentInfo.timeline
56639
                    });
56640
                }
56641
            }
56642
            this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before
56643
            // the following conditional otherwise it may consider this a bad "guess"
56644
            // and attempt to resync when the post-update seekable window and live
56645
            // point would mean that this was the perfect segment to fetch
56646
 
56647
            this.trigger('syncinfoupdate');
56648
            const segment = segmentInfo.segment;
56649
            const part = segmentInfo.part;
56650
            const badSegmentGuess = segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3;
56651
            const badPartGuess = part && part.end && this.currentTime_() - part.end > segmentInfo.playlist.partTargetDuration * 3; // If we previously appended a segment/part that ends more than 3 part/targetDurations before
56652
            // the currentTime_ that means that our conservative guess was too conservative.
56653
            // In that case, reset the loader state so that we try to use any information gained
56654
            // from the previous request to create a new, more accurate, sync-point.
56655
 
56656
            if (badSegmentGuess || badPartGuess) {
56657
                this.logger_(`bad ${badSegmentGuess ? 'segment' : 'part'} ${segmentInfoString(segmentInfo)}`);
56658
                this.resetEverything();
56659
                return;
56660
            }
56661
            const isWalkingForward = this.mediaIndex !== null; // Don't do a rendition switch unless we have enough time to get a sync segment
56662
            // and conservatively guess
56663
 
56664
            if (isWalkingForward) {
56665
                this.trigger('bandwidthupdate');
56666
            }
56667
            this.trigger('progress');
56668
            this.mediaIndex = segmentInfo.mediaIndex;
56669
            this.partIndex = segmentInfo.partIndex; // any time an update finishes and the last segment is in the
56670
            // buffer, end the stream. this ensures the "ended" event will
56671
            // fire if playback reaches that point.
56672
 
56673
            if (this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist, segmentInfo.partIndex)) {
56674
                this.endOfStream();
56675
            } // used for testing
56676
 
56677
            this.trigger('appended');
56678
            if (segmentInfo.hasAppendedData_) {
56679
                this.mediaAppends++;
56680
            }
56681
            if (!this.paused()) {
56682
                this.monitorBuffer_();
56683
            }
56684
        }
56685
        /**
56686
         * Records the current throughput of the decrypt, transmux, and append
56687
         * portion of the semgment pipeline. `throughput.rate` is a the cumulative
56688
         * moving average of the throughput. `throughput.count` is the number of
56689
         * data points in the average.
56690
         *
56691
         * @private
56692
         * @param {Object} segmentInfo the object returned by loadSegment
56693
         */
56694
 
56695
        recordThroughput_(segmentInfo) {
56696
            if (segmentInfo.duration < MIN_SEGMENT_DURATION_TO_SAVE_STATS) {
56697
                this.logger_(`Ignoring segment's throughput because its duration of ${segmentInfo.duration}` + ` is less than the min to record ${MIN_SEGMENT_DURATION_TO_SAVE_STATS}`);
56698
                return;
56699
            }
56700
            const rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide
56701
            // by zero in the case where the throughput is ridiculously high
56702
 
56703
            const segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second
56704
 
56705
            const segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation:
56706
            //   newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
56707
 
56708
            this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
56709
        }
56710
        /**
56711
         * Adds a cue to the segment-metadata track with some metadata information about the
56712
         * segment
56713
         *
56714
         * @private
56715
         * @param {Object} segmentInfo
56716
         *        the object returned by loadSegment
56717
         * @method addSegmentMetadataCue_
56718
         */
56719
 
56720
        addSegmentMetadataCue_(segmentInfo) {
56721
            if (!this.segmentMetadataTrack_) {
56722
                return;
56723
            }
56724
            const segment = segmentInfo.segment;
56725
            const start = segment.start;
56726
            const end = segment.end; // Do not try adding the cue if the start and end times are invalid.
56727
 
56728
            if (!finite(start) || !finite(end)) {
56729
                return;
56730
            }
56731
            removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
56732
            const Cue = window.WebKitDataCue || window.VTTCue;
56733
            const value = {
56734
                custom: segment.custom,
56735
                dateTimeObject: segment.dateTimeObject,
56736
                dateTimeString: segment.dateTimeString,
56737
                programDateTime: segment.programDateTime,
56738
                bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
56739
                resolution: segmentInfo.playlist.attributes.RESOLUTION,
56740
                codecs: segmentInfo.playlist.attributes.CODECS,
56741
                byteLength: segmentInfo.byteLength,
56742
                uri: segmentInfo.uri,
56743
                timeline: segmentInfo.timeline,
56744
                playlist: segmentInfo.playlist.id,
56745
                start,
56746
                end
56747
            };
56748
            const data = JSON.stringify(value);
56749
            const cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between
56750
            // the differences of WebKitDataCue in safari and VTTCue in other browsers
56751
 
56752
            cue.value = value;
56753
            this.segmentMetadataTrack_.addCue(cue);
56754
        }
56755
    }
56756
    function noop() {}
56757
    const toTitleCase = function (string) {
56758
        if (typeof string !== 'string') {
56759
            return string;
56760
        }
56761
        return string.replace(/./, w => w.toUpperCase());
56762
    };
56763
 
56764
    /**
56765
     * @file source-updater.js
56766
     */
56767
    const bufferTypes = ['video', 'audio'];
56768
    const updating = (type, sourceUpdater) => {
56769
        const sourceBuffer = sourceUpdater[`${type}Buffer`];
56770
        return sourceBuffer && sourceBuffer.updating || sourceUpdater.queuePending[type];
56771
    };
56772
    const nextQueueIndexOfType = (type, queue) => {
56773
        for (let i = 0; i < queue.length; i++) {
56774
            const queueEntry = queue[i];
56775
            if (queueEntry.type === 'mediaSource') {
56776
                // If the next entry is a media source entry (uses multiple source buffers), block
56777
                // processing to allow it to go through first.
56778
                return null;
56779
            }
56780
            if (queueEntry.type === type) {
56781
                return i;
56782
            }
56783
        }
56784
        return null;
56785
    };
56786
    const shiftQueue = (type, sourceUpdater) => {
56787
        if (sourceUpdater.queue.length === 0) {
56788
            return;
56789
        }
56790
        let queueIndex = 0;
56791
        let queueEntry = sourceUpdater.queue[queueIndex];
56792
        if (queueEntry.type === 'mediaSource') {
56793
            if (!sourceUpdater.updating() && sourceUpdater.mediaSource.readyState !== 'closed') {
56794
                sourceUpdater.queue.shift();
56795
                queueEntry.action(sourceUpdater);
56796
                if (queueEntry.doneFn) {
56797
                    queueEntry.doneFn();
56798
                } // Only specific source buffer actions must wait for async updateend events. Media
56799
                // Source actions process synchronously. Therefore, both audio and video source
56800
                // buffers are now clear to process the next queue entries.
56801
 
56802
                shiftQueue('audio', sourceUpdater);
56803
                shiftQueue('video', sourceUpdater);
56804
            } // Media Source actions require both source buffers, so if the media source action
56805
            // couldn't process yet (because one or both source buffers are busy), block other
56806
            // queue actions until both are available and the media source action can process.
56807
 
56808
            return;
56809
        }
56810
        if (type === 'mediaSource') {
56811
            // If the queue was shifted by a media source action (this happens when pushing a
56812
            // media source action onto the queue), then it wasn't from an updateend event from an
56813
            // audio or video source buffer, so there's no change from previous state, and no
56814
            // processing should be done.
56815
            return;
56816
        } // Media source queue entries don't need to consider whether the source updater is
56817
        // started (i.e., source buffers are created) as they don't need the source buffers, but
56818
        // source buffer queue entries do.
56819
 
56820
        if (!sourceUpdater.ready() || sourceUpdater.mediaSource.readyState === 'closed' || updating(type, sourceUpdater)) {
56821
            return;
56822
        }
56823
        if (queueEntry.type !== type) {
56824
            queueIndex = nextQueueIndexOfType(type, sourceUpdater.queue);
56825
            if (queueIndex === null) {
56826
                // Either there's no queue entry that uses this source buffer type in the queue, or
56827
                // there's a media source queue entry before the next entry of this type, in which
56828
                // case wait for that action to process first.
56829
                return;
56830
            }
56831
            queueEntry = sourceUpdater.queue[queueIndex];
56832
        }
56833
        sourceUpdater.queue.splice(queueIndex, 1); // Keep a record that this source buffer type is in use.
56834
        //
56835
        // The queue pending operation must be set before the action is performed in the event
56836
        // that the action results in a synchronous event that is acted upon. For instance, if
56837
        // an exception is thrown that can be handled, it's possible that new actions will be
56838
        // appended to an empty queue and immediately executed, but would not have the correct
56839
        // pending information if this property was set after the action was performed.
56840
 
56841
        sourceUpdater.queuePending[type] = queueEntry;
56842
        queueEntry.action(type, sourceUpdater);
56843
        if (!queueEntry.doneFn) {
56844
            // synchronous operation, process next entry
56845
            sourceUpdater.queuePending[type] = null;
56846
            shiftQueue(type, sourceUpdater);
56847
            return;
56848
        }
56849
    };
56850
    const cleanupBuffer = (type, sourceUpdater) => {
56851
        const buffer = sourceUpdater[`${type}Buffer`];
56852
        const titleType = toTitleCase(type);
56853
        if (!buffer) {
56854
            return;
56855
        }
56856
        buffer.removeEventListener('updateend', sourceUpdater[`on${titleType}UpdateEnd_`]);
56857
        buffer.removeEventListener('error', sourceUpdater[`on${titleType}Error_`]);
56858
        sourceUpdater.codecs[type] = null;
56859
        sourceUpdater[`${type}Buffer`] = null;
56860
    };
56861
    const inSourceBuffers = (mediaSource, sourceBuffer) => mediaSource && sourceBuffer && Array.prototype.indexOf.call(mediaSource.sourceBuffers, sourceBuffer) !== -1;
56862
    const actions = {
56863
        appendBuffer: (bytes, segmentInfo, onError) => (type, sourceUpdater) => {
56864
            const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
56865
            // or the media source does not contain this source buffer.
56866
 
56867
            if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
56868
                return;
56869
            }
56870
            sourceUpdater.logger_(`Appending segment ${segmentInfo.mediaIndex}'s ${bytes.length} bytes to ${type}Buffer`);
56871
            try {
56872
                sourceBuffer.appendBuffer(bytes);
56873
            } catch (e) {
56874
                sourceUpdater.logger_(`Error with code ${e.code} ` + (e.code === QUOTA_EXCEEDED_ERR ? '(QUOTA_EXCEEDED_ERR) ' : '') + `when appending segment ${segmentInfo.mediaIndex} to ${type}Buffer`);
56875
                sourceUpdater.queuePending[type] = null;
56876
                onError(e);
56877
            }
56878
        },
56879
        remove: (start, end) => (type, sourceUpdater) => {
56880
            const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
56881
            // or the media source does not contain this source buffer.
56882
 
56883
            if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
56884
                return;
56885
            }
56886
            sourceUpdater.logger_(`Removing ${start} to ${end} from ${type}Buffer`);
56887
            try {
56888
                sourceBuffer.remove(start, end);
56889
            } catch (e) {
56890
                sourceUpdater.logger_(`Remove ${start} to ${end} from ${type}Buffer failed`);
56891
            }
56892
        },
56893
        timestampOffset: offset => (type, sourceUpdater) => {
56894
            const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
56895
            // or the media source does not contain this source buffer.
56896
 
56897
            if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
56898
                return;
56899
            }
56900
            sourceUpdater.logger_(`Setting ${type}timestampOffset to ${offset}`);
56901
            sourceBuffer.timestampOffset = offset;
56902
        },
56903
        callback: callback => (type, sourceUpdater) => {
56904
            callback();
56905
        },
56906
        endOfStream: error => sourceUpdater => {
56907
            if (sourceUpdater.mediaSource.readyState !== 'open') {
56908
                return;
56909
            }
56910
            sourceUpdater.logger_(`Calling mediaSource endOfStream(${error || ''})`);
56911
            try {
56912
                sourceUpdater.mediaSource.endOfStream(error);
56913
            } catch (e) {
56914
                videojs.log.warn('Failed to call media source endOfStream', e);
56915
            }
56916
        },
56917
        duration: duration => sourceUpdater => {
56918
            sourceUpdater.logger_(`Setting mediaSource duration to ${duration}`);
56919
            try {
56920
                sourceUpdater.mediaSource.duration = duration;
56921
            } catch (e) {
56922
                videojs.log.warn('Failed to set media source duration', e);
56923
            }
56924
        },
56925
        abort: () => (type, sourceUpdater) => {
56926
            if (sourceUpdater.mediaSource.readyState !== 'open') {
56927
                return;
56928
            }
56929
            const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
56930
            // or the media source does not contain this source buffer.
56931
 
56932
            if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
56933
                return;
56934
            }
56935
            sourceUpdater.logger_(`calling abort on ${type}Buffer`);
56936
            try {
56937
                sourceBuffer.abort();
56938
            } catch (e) {
56939
                videojs.log.warn(`Failed to abort on ${type}Buffer`, e);
56940
            }
56941
        },
56942
        addSourceBuffer: (type, codec) => sourceUpdater => {
56943
            const titleType = toTitleCase(type);
56944
            const mime = getMimeForCodec(codec);
56945
            sourceUpdater.logger_(`Adding ${type}Buffer with codec ${codec} to mediaSource`);
56946
            const sourceBuffer = sourceUpdater.mediaSource.addSourceBuffer(mime);
56947
            sourceBuffer.addEventListener('updateend', sourceUpdater[`on${titleType}UpdateEnd_`]);
56948
            sourceBuffer.addEventListener('error', sourceUpdater[`on${titleType}Error_`]);
56949
            sourceUpdater.codecs[type] = codec;
56950
            sourceUpdater[`${type}Buffer`] = sourceBuffer;
56951
        },
56952
        removeSourceBuffer: type => sourceUpdater => {
56953
            const sourceBuffer = sourceUpdater[`${type}Buffer`];
56954
            cleanupBuffer(type, sourceUpdater); // can't do anything if the media source / source buffer is null
56955
            // or the media source does not contain this source buffer.
56956
 
56957
            if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
56958
                return;
56959
            }
56960
            sourceUpdater.logger_(`Removing ${type}Buffer with codec ${sourceUpdater.codecs[type]} from mediaSource`);
56961
            try {
56962
                sourceUpdater.mediaSource.removeSourceBuffer(sourceBuffer);
56963
            } catch (e) {
56964
                videojs.log.warn(`Failed to removeSourceBuffer ${type}Buffer`, e);
56965
            }
56966
        },
56967
        changeType: codec => (type, sourceUpdater) => {
56968
            const sourceBuffer = sourceUpdater[`${type}Buffer`];
56969
            const mime = getMimeForCodec(codec); // can't do anything if the media source / source buffer is null
56970
            // or the media source does not contain this source buffer.
56971
 
56972
            if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
56973
                return;
56974
            } // do not update codec if we don't need to.
56975
 
56976
            if (sourceUpdater.codecs[type] === codec) {
56977
                return;
56978
            }
56979
            sourceUpdater.logger_(`changing ${type}Buffer codec from ${sourceUpdater.codecs[type]} to ${codec}`); // check if change to the provided type is supported
56980
 
56981
            try {
56982
                sourceBuffer.changeType(mime);
56983
                sourceUpdater.codecs[type] = codec;
56984
            } catch (e) {
56985
                videojs.log.warn(`Failed to changeType on ${type}Buffer`, e);
56986
            }
56987
        }
56988
    };
56989
    const pushQueue = ({
56990
                           type,
56991
                           sourceUpdater,
56992
                           action,
56993
                           doneFn,
56994
                           name
56995
                       }) => {
56996
        sourceUpdater.queue.push({
56997
            type,
56998
            action,
56999
            doneFn,
57000
            name
57001
        });
57002
        shiftQueue(type, sourceUpdater);
57003
    };
57004
    const onUpdateend = (type, sourceUpdater) => e => {
57005
        // Although there should, in theory, be a pending action for any updateend receieved,
57006
        // there are some actions that may trigger updateend events without set definitions in
57007
        // the w3c spec. For instance, setting the duration on the media source may trigger
57008
        // updateend events on source buffers. This does not appear to be in the spec. As such,
57009
        // if we encounter an updateend without a corresponding pending action from our queue
57010
        // for that source buffer type, process the next action.
57011
        if (sourceUpdater.queuePending[type]) {
57012
            const doneFn = sourceUpdater.queuePending[type].doneFn;
57013
            sourceUpdater.queuePending[type] = null;
57014
            if (doneFn) {
57015
                // if there's an error, report it
57016
                doneFn(sourceUpdater[`${type}Error_`]);
57017
            }
57018
        }
57019
        shiftQueue(type, sourceUpdater);
57020
    };
57021
    /**
57022
     * A queue of callbacks to be serialized and applied when a
57023
     * MediaSource and its associated SourceBuffers are not in the
57024
     * updating state. It is used by the segment loader to update the
57025
     * underlying SourceBuffers when new data is loaded, for instance.
57026
     *
57027
     * @class SourceUpdater
57028
     * @param {MediaSource} mediaSource the MediaSource to create the SourceBuffer from
57029
     * @param {string} mimeType the desired MIME type of the underlying SourceBuffer
57030
     */
57031
 
57032
    class SourceUpdater extends videojs.EventTarget {
57033
        constructor(mediaSource) {
57034
            super();
57035
            this.mediaSource = mediaSource;
57036
            this.sourceopenListener_ = () => shiftQueue('mediaSource', this);
57037
            this.mediaSource.addEventListener('sourceopen', this.sourceopenListener_);
57038
            this.logger_ = logger('SourceUpdater'); // initial timestamp offset is 0
57039
 
57040
            this.audioTimestampOffset_ = 0;
57041
            this.videoTimestampOffset_ = 0;
57042
            this.queue = [];
57043
            this.queuePending = {
57044
                audio: null,
57045
                video: null
57046
            };
57047
            this.delayedAudioAppendQueue_ = [];
57048
            this.videoAppendQueued_ = false;
57049
            this.codecs = {};
57050
            this.onVideoUpdateEnd_ = onUpdateend('video', this);
57051
            this.onAudioUpdateEnd_ = onUpdateend('audio', this);
57052
            this.onVideoError_ = e => {
57053
                // used for debugging
57054
                this.videoError_ = e;
57055
            };
57056
            this.onAudioError_ = e => {
57057
                // used for debugging
57058
                this.audioError_ = e;
57059
            };
57060
            this.createdSourceBuffers_ = false;
57061
            this.initializedEme_ = false;
57062
            this.triggeredReady_ = false;
57063
        }
57064
        initializedEme() {
57065
            this.initializedEme_ = true;
57066
            this.triggerReady();
57067
        }
57068
        hasCreatedSourceBuffers() {
57069
            // if false, likely waiting on one of the segment loaders to get enough data to create
57070
            // source buffers
57071
            return this.createdSourceBuffers_;
57072
        }
57073
        hasInitializedAnyEme() {
57074
            return this.initializedEme_;
57075
        }
57076
        ready() {
57077
            return this.hasCreatedSourceBuffers() && this.hasInitializedAnyEme();
57078
        }
57079
        createSourceBuffers(codecs) {
57080
            if (this.hasCreatedSourceBuffers()) {
57081
                // already created them before
57082
                return;
57083
            } // the intial addOrChangeSourceBuffers will always be
57084
            // two add buffers.
57085
 
57086
            this.addOrChangeSourceBuffers(codecs);
57087
            this.createdSourceBuffers_ = true;
57088
            this.trigger('createdsourcebuffers');
57089
            this.triggerReady();
57090
        }
57091
        triggerReady() {
57092
            // only allow ready to be triggered once, this prevents the case
57093
            // where:
57094
            // 1. we trigger createdsourcebuffers
57095
            // 2. ie 11 synchronously initializates eme
57096
            // 3. the synchronous initialization causes us to trigger ready
57097
            // 4. We go back to the ready check in createSourceBuffers and ready is triggered again.
57098
            if (this.ready() && !this.triggeredReady_) {
57099
                this.triggeredReady_ = true;
57100
                this.trigger('ready');
57101
            }
57102
        }
57103
        /**
57104
         * Add a type of source buffer to the media source.
57105
         *
57106
         * @param {string} type
57107
         *        The type of source buffer to add.
57108
         *
57109
         * @param {string} codec
57110
         *        The codec to add the source buffer with.
57111
         */
57112
 
57113
        addSourceBuffer(type, codec) {
57114
            pushQueue({
57115
                type: 'mediaSource',
57116
                sourceUpdater: this,
57117
                action: actions.addSourceBuffer(type, codec),
57118
                name: 'addSourceBuffer'
57119
            });
57120
        }
57121
        /**
57122
         * call abort on a source buffer.
57123
         *
57124
         * @param {string} type
57125
         *        The type of source buffer to call abort on.
57126
         */
57127
 
57128
        abort(type) {
57129
            pushQueue({
57130
                type,
57131
                sourceUpdater: this,
57132
                action: actions.abort(type),
57133
                name: 'abort'
57134
            });
57135
        }
57136
        /**
57137
         * Call removeSourceBuffer and remove a specific type
57138
         * of source buffer on the mediaSource.
57139
         *
57140
         * @param {string} type
57141
         *        The type of source buffer to remove.
57142
         */
57143
 
57144
        removeSourceBuffer(type) {
57145
            if (!this.canRemoveSourceBuffer()) {
57146
                videojs.log.error('removeSourceBuffer is not supported!');
57147
                return;
57148
            }
57149
            pushQueue({
57150
                type: 'mediaSource',
57151
                sourceUpdater: this,
57152
                action: actions.removeSourceBuffer(type),
57153
                name: 'removeSourceBuffer'
57154
            });
57155
        }
57156
        /**
57157
         * Whether or not the removeSourceBuffer function is supported
57158
         * on the mediaSource.
57159
         *
57160
         * @return {boolean}
57161
         *          if removeSourceBuffer can be called.
57162
         */
57163
 
57164
        canRemoveSourceBuffer() {
57165
            // As of Firefox 83 removeSourceBuffer
57166
            // throws errors, so we report that it does not support this.
57167
            return !videojs.browser.IS_FIREFOX && window.MediaSource && window.MediaSource.prototype && typeof window.MediaSource.prototype.removeSourceBuffer === 'function';
57168
        }
57169
        /**
57170
         * Whether or not the changeType function is supported
57171
         * on our SourceBuffers.
57172
         *
57173
         * @return {boolean}
57174
         *         if changeType can be called.
57175
         */
57176
 
57177
        static canChangeType() {
57178
            return window.SourceBuffer && window.SourceBuffer.prototype && typeof window.SourceBuffer.prototype.changeType === 'function';
57179
        }
57180
        /**
57181
         * Whether or not the changeType function is supported
57182
         * on our SourceBuffers.
57183
         *
57184
         * @return {boolean}
57185
         *         if changeType can be called.
57186
         */
57187
 
57188
        canChangeType() {
57189
            return this.constructor.canChangeType();
57190
        }
57191
        /**
57192
         * Call the changeType function on a source buffer, given the code and type.
57193
         *
57194
         * @param {string} type
57195
         *        The type of source buffer to call changeType on.
57196
         *
57197
         * @param {string} codec
57198
         *        The codec string to change type with on the source buffer.
57199
         */
57200
 
57201
        changeType(type, codec) {
57202
            if (!this.canChangeType()) {
57203
                videojs.log.error('changeType is not supported!');
57204
                return;
57205
            }
57206
            pushQueue({
57207
                type,
57208
                sourceUpdater: this,
57209
                action: actions.changeType(codec),
57210
                name: 'changeType'
57211
            });
57212
        }
57213
        /**
57214
         * Add source buffers with a codec or, if they are already created,
57215
         * call changeType on source buffers using changeType.
57216
         *
57217
         * @param {Object} codecs
57218
         *        Codecs to switch to
57219
         */
57220
 
57221
        addOrChangeSourceBuffers(codecs) {
57222
            if (!codecs || typeof codecs !== 'object' || Object.keys(codecs).length === 0) {
57223
                throw new Error('Cannot addOrChangeSourceBuffers to undefined codecs');
57224
            }
57225
            Object.keys(codecs).forEach(type => {
57226
                const codec = codecs[type];
57227
                if (!this.hasCreatedSourceBuffers()) {
57228
                    return this.addSourceBuffer(type, codec);
57229
                }
57230
                if (this.canChangeType()) {
57231
                    this.changeType(type, codec);
57232
                }
57233
            });
57234
        }
57235
        /**
57236
         * Queue an update to append an ArrayBuffer.
57237
         *
57238
         * @param {MediaObject} object containing audioBytes and/or videoBytes
57239
         * @param {Function} done the function to call when done
57240
         * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
57241
         */
57242
 
57243
        appendBuffer(options, doneFn) {
57244
            const {
57245
                segmentInfo,
57246
                type,
57247
                bytes
57248
            } = options;
57249
            this.processedAppend_ = true;
57250
            if (type === 'audio' && this.videoBuffer && !this.videoAppendQueued_) {
57251
                this.delayedAudioAppendQueue_.push([options, doneFn]);
57252
                this.logger_(`delayed audio append of ${bytes.length} until video append`);
57253
                return;
57254
            } // In the case of certain errors, for instance, QUOTA_EXCEEDED_ERR, updateend will
57255
            // not be fired. This means that the queue will be blocked until the next action
57256
            // taken by the segment-loader. Provide a mechanism for segment-loader to handle
57257
            // these errors by calling the doneFn with the specific error.
57258
 
57259
            const onError = doneFn;
57260
            pushQueue({
57261
                type,
57262
                sourceUpdater: this,
57263
                action: actions.appendBuffer(bytes, segmentInfo || {
57264
                    mediaIndex: -1
57265
                }, onError),
57266
                doneFn,
57267
                name: 'appendBuffer'
57268
            });
57269
            if (type === 'video') {
57270
                this.videoAppendQueued_ = true;
57271
                if (!this.delayedAudioAppendQueue_.length) {
57272
                    return;
57273
                }
57274
                const queue = this.delayedAudioAppendQueue_.slice();
57275
                this.logger_(`queuing delayed audio ${queue.length} appendBuffers`);
57276
                this.delayedAudioAppendQueue_.length = 0;
57277
                queue.forEach(que => {
57278
                    this.appendBuffer.apply(this, que);
57279
                });
57280
            }
57281
        }
57282
        /**
57283
         * Get the audio buffer's buffered timerange.
57284
         *
57285
         * @return {TimeRange}
57286
         *         The audio buffer's buffered time range
57287
         */
57288
 
57289
        audioBuffered() {
57290
            // no media source/source buffer or it isn't in the media sources
57291
            // source buffer list
57292
            if (!inSourceBuffers(this.mediaSource, this.audioBuffer)) {
57293
                return createTimeRanges();
57294
            }
57295
            return this.audioBuffer.buffered ? this.audioBuffer.buffered : createTimeRanges();
57296
        }
57297
        /**
57298
         * Get the video buffer's buffered timerange.
57299
         *
57300
         * @return {TimeRange}
57301
         *         The video buffer's buffered time range
57302
         */
57303
 
57304
        videoBuffered() {
57305
            // no media source/source buffer or it isn't in the media sources
57306
            // source buffer list
57307
            if (!inSourceBuffers(this.mediaSource, this.videoBuffer)) {
57308
                return createTimeRanges();
57309
            }
57310
            return this.videoBuffer.buffered ? this.videoBuffer.buffered : createTimeRanges();
57311
        }
57312
        /**
57313
         * Get a combined video/audio buffer's buffered timerange.
57314
         *
57315
         * @return {TimeRange}
57316
         *         the combined time range
57317
         */
57318
 
57319
        buffered() {
57320
            const video = inSourceBuffers(this.mediaSource, this.videoBuffer) ? this.videoBuffer : null;
57321
            const audio = inSourceBuffers(this.mediaSource, this.audioBuffer) ? this.audioBuffer : null;
57322
            if (audio && !video) {
57323
                return this.audioBuffered();
57324
            }
57325
            if (video && !audio) {
57326
                return this.videoBuffered();
57327
            }
57328
            return bufferIntersection(this.audioBuffered(), this.videoBuffered());
57329
        }
57330
        /**
57331
         * Add a callback to the queue that will set duration on the mediaSource.
57332
         *
57333
         * @param {number} duration
57334
         *        The duration to set
57335
         *
57336
         * @param {Function} [doneFn]
57337
         *        function to run after duration has been set.
57338
         */
57339
 
57340
        setDuration(duration, doneFn = noop) {
57341
            // In order to set the duration on the media source, it's necessary to wait for all
57342
            // source buffers to no longer be updating. "If the updating attribute equals true on
57343
            // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and
57344
            // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes).
57345
            pushQueue({
57346
                type: 'mediaSource',
57347
                sourceUpdater: this,
57348
                action: actions.duration(duration),
57349
                name: 'duration',
57350
                doneFn
57351
            });
57352
        }
57353
        /**
57354
         * Add a mediaSource endOfStream call to the queue
57355
         *
57356
         * @param {Error} [error]
57357
         *        Call endOfStream with an error
57358
         *
57359
         * @param {Function} [doneFn]
57360
         *        A function that should be called when the
57361
         *        endOfStream call has finished.
57362
         */
57363
 
57364
        endOfStream(error = null, doneFn = noop) {
57365
            if (typeof error !== 'string') {
57366
                error = undefined;
57367
            } // In order to set the duration on the media source, it's necessary to wait for all
57368
            // source buffers to no longer be updating. "If the updating attribute equals true on
57369
            // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and
57370
            // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes).
57371
 
57372
            pushQueue({
57373
                type: 'mediaSource',
57374
                sourceUpdater: this,
57375
                action: actions.endOfStream(error),
57376
                name: 'endOfStream',
57377
                doneFn
57378
            });
57379
        }
57380
        /**
57381
         * Queue an update to remove a time range from the buffer.
57382
         *
57383
         * @param {number} start where to start the removal
57384
         * @param {number} end where to end the removal
57385
         * @param {Function} [done=noop] optional callback to be executed when the remove
57386
         * operation is complete
57387
         * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
57388
         */
57389
 
57390
        removeAudio(start, end, done = noop) {
57391
            if (!this.audioBuffered().length || this.audioBuffered().end(0) === 0) {
57392
                done();
57393
                return;
57394
            }
57395
            pushQueue({
57396
                type: 'audio',
57397
                sourceUpdater: this,
57398
                action: actions.remove(start, end),
57399
                doneFn: done,
57400
                name: 'remove'
57401
            });
57402
        }
57403
        /**
57404
         * Queue an update to remove a time range from the buffer.
57405
         *
57406
         * @param {number} start where to start the removal
57407
         * @param {number} end where to end the removal
57408
         * @param {Function} [done=noop] optional callback to be executed when the remove
57409
         * operation is complete
57410
         * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
57411
         */
57412
 
57413
        removeVideo(start, end, done = noop) {
57414
            if (!this.videoBuffered().length || this.videoBuffered().end(0) === 0) {
57415
                done();
57416
                return;
57417
            }
57418
            pushQueue({
57419
                type: 'video',
57420
                sourceUpdater: this,
57421
                action: actions.remove(start, end),
57422
                doneFn: done,
57423
                name: 'remove'
57424
            });
57425
        }
57426
        /**
57427
         * Whether the underlying sourceBuffer is updating or not
57428
         *
57429
         * @return {boolean} the updating status of the SourceBuffer
57430
         */
57431
 
57432
        updating() {
57433
            // the audio/video source buffer is updating
57434
            if (updating('audio', this) || updating('video', this)) {
57435
                return true;
57436
            }
57437
            return false;
57438
        }
57439
        /**
57440
         * Set/get the timestampoffset on the audio SourceBuffer
57441
         *
57442
         * @return {number} the timestamp offset
57443
         */
57444
 
57445
        audioTimestampOffset(offset) {
57446
            if (typeof offset !== 'undefined' && this.audioBuffer &&
57447
                // no point in updating if it's the same
57448
                this.audioTimestampOffset_ !== offset) {
57449
                pushQueue({
57450
                    type: 'audio',
57451
                    sourceUpdater: this,
57452
                    action: actions.timestampOffset(offset),
57453
                    name: 'timestampOffset'
57454
                });
57455
                this.audioTimestampOffset_ = offset;
57456
            }
57457
            return this.audioTimestampOffset_;
57458
        }
57459
        /**
57460
         * Set/get the timestampoffset on the video SourceBuffer
57461
         *
57462
         * @return {number} the timestamp offset
57463
         */
57464
 
57465
        videoTimestampOffset(offset) {
57466
            if (typeof offset !== 'undefined' && this.videoBuffer &&
57467
                // no point in updating if it's the same
57468
                this.videoTimestampOffset !== offset) {
57469
                pushQueue({
57470
                    type: 'video',
57471
                    sourceUpdater: this,
57472
                    action: actions.timestampOffset(offset),
57473
                    name: 'timestampOffset'
57474
                });
57475
                this.videoTimestampOffset_ = offset;
57476
            }
57477
            return this.videoTimestampOffset_;
57478
        }
57479
        /**
57480
         * Add a function to the queue that will be called
57481
         * when it is its turn to run in the audio queue.
57482
         *
57483
         * @param {Function} callback
57484
         *        The callback to queue.
57485
         */
57486
 
57487
        audioQueueCallback(callback) {
57488
            if (!this.audioBuffer) {
57489
                return;
57490
            }
57491
            pushQueue({
57492
                type: 'audio',
57493
                sourceUpdater: this,
57494
                action: actions.callback(callback),
57495
                name: 'callback'
57496
            });
57497
        }
57498
        /**
57499
         * Add a function to the queue that will be called
57500
         * when it is its turn to run in the video queue.
57501
         *
57502
         * @param {Function} callback
57503
         *        The callback to queue.
57504
         */
57505
 
57506
        videoQueueCallback(callback) {
57507
            if (!this.videoBuffer) {
57508
                return;
57509
            }
57510
            pushQueue({
57511
                type: 'video',
57512
                sourceUpdater: this,
57513
                action: actions.callback(callback),
57514
                name: 'callback'
57515
            });
57516
        }
57517
        /**
57518
         * dispose of the source updater and the underlying sourceBuffer
57519
         */
57520
 
57521
        dispose() {
57522
            this.trigger('dispose');
57523
            bufferTypes.forEach(type => {
57524
                this.abort(type);
57525
                if (this.canRemoveSourceBuffer()) {
57526
                    this.removeSourceBuffer(type);
57527
                } else {
57528
                    this[`${type}QueueCallback`](() => cleanupBuffer(type, this));
57529
                }
57530
            });
57531
            this.videoAppendQueued_ = false;
57532
            this.delayedAudioAppendQueue_.length = 0;
57533
            if (this.sourceopenListener_) {
57534
                this.mediaSource.removeEventListener('sourceopen', this.sourceopenListener_);
57535
            }
57536
            this.off();
57537
        }
57538
    }
57539
    const uint8ToUtf8 = uintArray => decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray)));
57540
    const bufferToHexString = buffer => {
57541
        const uInt8Buffer = new Uint8Array(buffer);
57542
        return Array.from(uInt8Buffer).map(byte => byte.toString(16).padStart(2, '0')).join('');
57543
    };
57544
 
57545
    /**
57546
     * @file vtt-segment-loader.js
57547
     */
57548
    const VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(char => char.charCodeAt(0)));
57549
    class NoVttJsError extends Error {
57550
        constructor() {
57551
            super('Trying to parse received VTT cues, but there is no WebVTT. Make sure vtt.js is loaded.');
57552
        }
57553
    }
57554
    /**
57555
     * An object that manages segment loading and appending.
57556
     *
57557
     * @class VTTSegmentLoader
57558
     * @param {Object} options required and optional options
57559
     * @extends videojs.EventTarget
57560
     */
57561
 
57562
    class VTTSegmentLoader extends SegmentLoader {
57563
        constructor(settings, options = {}) {
57564
            super(settings, options); // SegmentLoader requires a MediaSource be specified or it will throw an error;
57565
            // however, VTTSegmentLoader has no need of a media source, so delete the reference
57566
 
57567
            this.mediaSource_ = null;
57568
            this.subtitlesTrack_ = null;
57569
            this.loaderType_ = 'subtitle';
57570
            this.featuresNativeTextTracks_ = settings.featuresNativeTextTracks;
57571
            this.loadVttJs = settings.loadVttJs; // The VTT segment will have its own time mappings. Saving VTT segment timing info in
57572
            // the sync controller leads to improper behavior.
57573
 
57574
            this.shouldSaveSegmentTimingInfo_ = false;
57575
        }
57576
        createTransmuxer_() {
57577
            // don't need to transmux any subtitles
57578
            return null;
57579
        }
57580
        /**
57581
         * Indicates which time ranges are buffered
57582
         *
57583
         * @return {TimeRange}
57584
         *         TimeRange object representing the current buffered ranges
57585
         */
57586
 
57587
        buffered_() {
57588
            if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues || !this.subtitlesTrack_.cues.length) {
57589
                return createTimeRanges();
57590
            }
57591
            const cues = this.subtitlesTrack_.cues;
57592
            const start = cues[0].startTime;
57593
            const end = cues[cues.length - 1].startTime;
57594
            return createTimeRanges([[start, end]]);
57595
        }
57596
        /**
57597
         * Gets and sets init segment for the provided map
57598
         *
57599
         * @param {Object} map
57600
         *        The map object representing the init segment to get or set
57601
         * @param {boolean=} set
57602
         *        If true, the init segment for the provided map should be saved
57603
         * @return {Object}
57604
         *         map object for desired init segment
57605
         */
57606
 
57607
        initSegmentForMap(map, set = false) {
57608
            if (!map) {
57609
                return null;
57610
            }
57611
            const id = initSegmentId(map);
57612
            let storedMap = this.initSegments_[id];
57613
            if (set && !storedMap && map.bytes) {
57614
                // append WebVTT line terminators to the media initialization segment if it exists
57615
                // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
57616
                // requires two or more WebVTT line terminators between the WebVTT header and the
57617
                // rest of the file
57618
                const combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
57619
                const combinedSegment = new Uint8Array(combinedByteLength);
57620
                combinedSegment.set(map.bytes);
57621
                combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
57622
                this.initSegments_[id] = storedMap = {
57623
                    resolvedUri: map.resolvedUri,
57624
                    byterange: map.byterange,
57625
                    bytes: combinedSegment
57626
                };
57627
            }
57628
            return storedMap || map;
57629
        }
57630
        /**
57631
         * Returns true if all configuration required for loading is present, otherwise false.
57632
         *
57633
         * @return {boolean} True if the all configuration is ready for loading
57634
         * @private
57635
         */
57636
 
57637
        couldBeginLoading_() {
57638
            return this.playlist_ && this.subtitlesTrack_ && !this.paused();
57639
        }
57640
        /**
57641
         * Once all the starting parameters have been specified, begin
57642
         * operation. This method should only be invoked from the INIT
57643
         * state.
57644
         *
57645
         * @private
57646
         */
57647
 
57648
        init_() {
57649
            this.state = 'READY';
57650
            this.resetEverything();
57651
            return this.monitorBuffer_();
57652
        }
57653
        /**
57654
         * Set a subtitle track on the segment loader to add subtitles to
57655
         *
57656
         * @param {TextTrack=} track
57657
         *        The text track to add loaded subtitles to
57658
         * @return {TextTrack}
57659
         *        Returns the subtitles track
57660
         */
57661
 
57662
        track(track) {
57663
            if (typeof track === 'undefined') {
57664
                return this.subtitlesTrack_;
57665
            }
57666
            this.subtitlesTrack_ = track; // if we were unpaused but waiting for a sourceUpdater, start
57667
            // buffering now
57668
 
57669
            if (this.state === 'INIT' && this.couldBeginLoading_()) {
57670
                this.init_();
57671
            }
57672
            return this.subtitlesTrack_;
57673
        }
57674
        /**
57675
         * Remove any data in the source buffer between start and end times
57676
         *
57677
         * @param {number} start - the start time of the region to remove from the buffer
57678
         * @param {number} end - the end time of the region to remove from the buffer
57679
         */
57680
 
57681
        remove(start, end) {
57682
            removeCuesFromTrack(start, end, this.subtitlesTrack_);
57683
        }
57684
        /**
57685
         * fill the buffer with segements unless the sourceBuffers are
57686
         * currently updating
57687
         *
57688
         * Note: this function should only ever be called by monitorBuffer_
57689
         * and never directly
57690
         *
57691
         * @private
57692
         */
57693
 
57694
        fillBuffer_() {
57695
            // see if we need to begin loading immediately
57696
            const segmentInfo = this.chooseNextRequest_();
57697
            if (!segmentInfo) {
57698
                return;
57699
            }
57700
            if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
57701
                // We don't have the timestamp offset that we need to sync subtitles.
57702
                // Rerun on a timestamp offset or user interaction.
57703
                const checkTimestampOffset = () => {
57704
                    this.state = 'READY';
57705
                    if (!this.paused()) {
57706
                        // if not paused, queue a buffer check as soon as possible
57707
                        this.monitorBuffer_();
57708
                    }
57709
                };
57710
                this.syncController_.one('timestampoffset', checkTimestampOffset);
57711
                this.state = 'WAITING_ON_TIMELINE';
57712
                return;
57713
            }
57714
            this.loadSegment_(segmentInfo);
57715
        } // never set a timestamp offset for vtt segments.
57716
 
57717
        timestampOffsetForSegment_() {
57718
            return null;
57719
        }
57720
        chooseNextRequest_() {
57721
            return this.skipEmptySegments_(super.chooseNextRequest_());
57722
        }
57723
        /**
57724
         * Prevents the segment loader from requesting segments we know contain no subtitles
57725
         * by walking forward until we find the next segment that we don't know whether it is
57726
         * empty or not.
57727
         *
57728
         * @param {Object} segmentInfo
57729
         *        a segment info object that describes the current segment
57730
         * @return {Object}
57731
         *         a segment info object that describes the current segment
57732
         */
57733
 
57734
        skipEmptySegments_(segmentInfo) {
57735
            while (segmentInfo && segmentInfo.segment.empty) {
57736
                // stop at the last possible segmentInfo
57737
                if (segmentInfo.mediaIndex + 1 >= segmentInfo.playlist.segments.length) {
57738
                    segmentInfo = null;
57739
                    break;
57740
                }
57741
                segmentInfo = this.generateSegmentInfo_({
57742
                    playlist: segmentInfo.playlist,
57743
                    mediaIndex: segmentInfo.mediaIndex + 1,
57744
                    startOfSegment: segmentInfo.startOfSegment + segmentInfo.duration,
57745
                    isSyncRequest: segmentInfo.isSyncRequest
57746
                });
57747
            }
57748
            return segmentInfo;
57749
        }
57750
        stopForError(error) {
57751
            this.error(error);
57752
            this.state = 'READY';
57753
            this.pause();
57754
            this.trigger('error');
57755
        }
57756
        /**
57757
         * append a decrypted segement to the SourceBuffer through a SourceUpdater
57758
         *
57759
         * @private
57760
         */
57761
 
57762
        segmentRequestFinished_(error, simpleSegment, result) {
57763
            if (!this.subtitlesTrack_) {
57764
                this.state = 'READY';
57765
                return;
57766
            }
57767
            this.saveTransferStats_(simpleSegment.stats); // the request was aborted
57768
 
57769
            if (!this.pendingSegment_) {
57770
                this.state = 'READY';
57771
                this.mediaRequestsAborted += 1;
57772
                return;
57773
            }
57774
            if (error) {
57775
                if (error.code === REQUEST_ERRORS.TIMEOUT) {
57776
                    this.handleTimeout_();
57777
                }
57778
                if (error.code === REQUEST_ERRORS.ABORTED) {
57779
                    this.mediaRequestsAborted += 1;
57780
                } else {
57781
                    this.mediaRequestsErrored += 1;
57782
                }
57783
                this.stopForError(error);
57784
                return;
57785
            }
57786
            const segmentInfo = this.pendingSegment_; // although the VTT segment loader bandwidth isn't really used, it's good to
57787
            // maintain functionality between segment loaders
57788
 
57789
            this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats); // if this request included a segment key, save that data in the cache
57790
 
57791
            if (simpleSegment.key) {
57792
                this.segmentKey(simpleSegment.key, true);
57793
            }
57794
            this.state = 'APPENDING'; // used for tests
57795
 
57796
            this.trigger('appending');
57797
            const segment = segmentInfo.segment;
57798
            if (segment.map) {
57799
                segment.map.bytes = simpleSegment.map.bytes;
57800
            }
57801
            segmentInfo.bytes = simpleSegment.bytes; // Make sure that vttjs has loaded, otherwise, load it and wait till it finished loading
57802
 
57803
            if (typeof window.WebVTT !== 'function' && typeof this.loadVttJs === 'function') {
57804
                this.state = 'WAITING_ON_VTTJS'; // should be fine to call multiple times
57805
                // script will be loaded once but multiple listeners will be added to the queue, which is expected.
57806
 
57807
                this.loadVttJs().then(() => this.segmentRequestFinished_(error, simpleSegment, result), () => this.stopForError({
57808
                    message: 'Error loading vtt.js'
57809
                }));
57810
                return;
57811
            }
57812
            segment.requested = true;
57813
            try {
57814
                this.parseVTTCues_(segmentInfo);
57815
            } catch (e) {
57816
                this.stopForError({
57817
                    message: e.message
57818
                });
57819
                return;
57820
            }
57821
            this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_);
57822
            if (segmentInfo.cues.length) {
57823
                segmentInfo.timingInfo = {
57824
                    start: segmentInfo.cues[0].startTime,
57825
                    end: segmentInfo.cues[segmentInfo.cues.length - 1].endTime
57826
                };
57827
            } else {
57828
                segmentInfo.timingInfo = {
57829
                    start: segmentInfo.startOfSegment,
57830
                    end: segmentInfo.startOfSegment + segmentInfo.duration
57831
                };
57832
            }
57833
            if (segmentInfo.isSyncRequest) {
57834
                this.trigger('syncinfoupdate');
57835
                this.pendingSegment_ = null;
57836
                this.state = 'READY';
57837
                return;
57838
            }
57839
            segmentInfo.byteLength = segmentInfo.bytes.byteLength;
57840
            this.mediaSecondsLoaded += segment.duration; // Create VTTCue instances for each cue in the new segment and add them to
57841
            // the subtitle track
57842
 
57843
            segmentInfo.cues.forEach(cue => {
57844
                this.subtitlesTrack_.addCue(this.featuresNativeTextTracks_ ? new window.VTTCue(cue.startTime, cue.endTime, cue.text) : cue);
57845
            }); // Remove any duplicate cues from the subtitle track. The WebVTT spec allows
57846
            // cues to have identical time-intervals, but if the text is also identical
57847
            // we can safely assume it is a duplicate that can be removed (ex. when a cue
57848
            // "overlaps" VTT segments)
57849
 
57850
            removeDuplicateCuesFromTrack(this.subtitlesTrack_);
57851
            this.handleAppendsDone_();
57852
        }
57853
        handleData_() {// noop as we shouldn't be getting video/audio data captions
57854
            // that we do not support here.
57855
        }
57856
        updateTimingInfoEnd_() {// noop
57857
        }
57858
        /**
57859
         * Uses the WebVTT parser to parse the segment response
57860
         *
57861
         * @throws NoVttJsError
57862
         *
57863
         * @param {Object} segmentInfo
57864
         *        a segment info object that describes the current segment
57865
         * @private
57866
         */
57867
 
57868
        parseVTTCues_(segmentInfo) {
57869
            let decoder;
57870
            let decodeBytesToString = false;
57871
            if (typeof window.WebVTT !== 'function') {
57872
                // caller is responsible for exception handling.
57873
                throw new NoVttJsError();
57874
            }
57875
            if (typeof window.TextDecoder === 'function') {
57876
                decoder = new window.TextDecoder('utf8');
57877
            } else {
57878
                decoder = window.WebVTT.StringDecoder();
57879
                decodeBytesToString = true;
57880
            }
57881
            const parser = new window.WebVTT.Parser(window, window.vttjs, decoder);
57882
            segmentInfo.cues = [];
57883
            segmentInfo.timestampmap = {
57884
                MPEGTS: 0,
57885
                LOCAL: 0
57886
            };
57887
            parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
57888
            parser.ontimestampmap = map => {
57889
                segmentInfo.timestampmap = map;
57890
            };
57891
            parser.onparsingerror = error => {
57892
                videojs.log.warn('Error encountered when parsing cues: ' + error.message);
57893
            };
57894
            if (segmentInfo.segment.map) {
57895
                let mapData = segmentInfo.segment.map.bytes;
57896
                if (decodeBytesToString) {
57897
                    mapData = uint8ToUtf8(mapData);
57898
                }
57899
                parser.parse(mapData);
57900
            }
57901
            let segmentData = segmentInfo.bytes;
57902
            if (decodeBytesToString) {
57903
                segmentData = uint8ToUtf8(segmentData);
57904
            }
57905
            parser.parse(segmentData);
57906
            parser.flush();
57907
        }
57908
        /**
57909
         * Updates the start and end times of any cues parsed by the WebVTT parser using
57910
         * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
57911
         * from the SyncController
57912
         *
57913
         * @param {Object} segmentInfo
57914
         *        a segment info object that describes the current segment
57915
         * @param {Object} mappingObj
57916
         *        object containing a mapping from TS to media time
57917
         * @param {Object} playlist
57918
         *        the playlist object containing the segment
57919
         * @private
57920
         */
57921
 
57922
        updateTimeMapping_(segmentInfo, mappingObj, playlist) {
57923
            const segment = segmentInfo.segment;
57924
            if (!mappingObj) {
57925
                // If the sync controller does not have a mapping of TS to Media Time for the
57926
                // timeline, then we don't have enough information to update the cue
57927
                // start/end times
57928
                return;
57929
            }
57930
            if (!segmentInfo.cues.length) {
57931
                // If there are no cues, we also do not have enough information to figure out
57932
                // segment timing. Mark that the segment contains no cues so we don't re-request
57933
                // an empty segment.
57934
                segment.empty = true;
57935
                return;
57936
            }
57937
            const {
57938
                MPEGTS,
57939
                LOCAL
57940
            } = segmentInfo.timestampmap;
57941
            /**
57942
             * From the spec:
57943
             * The MPEGTS media timestamp MUST use a 90KHz timescale,
57944
             * even when non-WebVTT Media Segments use a different timescale.
57945
             */
57946
 
57947
            const mpegTsInSeconds = MPEGTS / clock_1;
57948
            const diff = mpegTsInSeconds - LOCAL + mappingObj.mapping;
57949
            segmentInfo.cues.forEach(cue => {
57950
                const duration = cue.endTime - cue.startTime;
57951
                const startTime = MPEGTS === 0 ? cue.startTime + diff : this.handleRollover_(cue.startTime + diff, mappingObj.time);
57952
                cue.startTime = Math.max(startTime, 0);
57953
                cue.endTime = Math.max(startTime + duration, 0);
57954
            });
57955
            if (!playlist.syncInfo) {
57956
                const firstStart = segmentInfo.cues[0].startTime;
57957
                const lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
57958
                playlist.syncInfo = {
57959
                    mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
57960
                    time: Math.min(firstStart, lastStart - segment.duration)
57961
                };
57962
            }
57963
        }
57964
        /**
57965
         * MPEG-TS PES timestamps are limited to 2^33.
57966
         * Once they reach 2^33, they roll over to 0.
57967
         * mux.js handles PES timestamp rollover for the following scenarios:
57968
         * [forward rollover(right)] ->
57969
         *    PES timestamps monotonically increase, and once they reach 2^33, they roll over to 0
57970
         * [backward rollover(left)] -->
57971
         *    we seek back to position before rollover.
57972
         *
57973
         * According to the HLS SPEC:
57974
         * When synchronizing WebVTT with PES timestamps, clients SHOULD account
57975
         * for cases where the 33-bit PES timestamps have wrapped and the WebVTT
57976
         * cue times have not.  When the PES timestamp wraps, the WebVTT Segment
57977
         * SHOULD have a X-TIMESTAMP-MAP header that maps the current WebVTT
57978
         * time to the new (low valued) PES timestamp.
57979
         *
57980
         * So we want to handle rollover here and align VTT Cue start/end time to the player's time.
57981
         */
57982
 
57983
        handleRollover_(value, reference) {
57984
            if (reference === null) {
57985
                return value;
57986
            }
57987
            let valueIn90khz = value * clock_1;
57988
            const referenceIn90khz = reference * clock_1;
57989
            let offset;
57990
            if (referenceIn90khz < valueIn90khz) {
57991
                // - 2^33
57992
                offset = -8589934592;
57993
            } else {
57994
                // + 2^33
57995
                offset = 8589934592;
57996
            } // distance(value - reference) > 2^32
57997
 
57998
            while (Math.abs(valueIn90khz - referenceIn90khz) > 4294967296) {
57999
                valueIn90khz += offset;
58000
            }
58001
            return valueIn90khz / clock_1;
58002
        }
58003
    }
58004
 
58005
    /**
58006
     * @file ad-cue-tags.js
58007
     */
58008
    /**
58009
     * Searches for an ad cue that overlaps with the given mediaTime
58010
     *
58011
     * @param {Object} track
58012
     *        the track to find the cue for
58013
     *
58014
     * @param {number} mediaTime
58015
     *        the time to find the cue at
58016
     *
58017
     * @return {Object|null}
58018
     *         the found cue or null
58019
     */
58020
 
58021
    const findAdCue = function (track, mediaTime) {
58022
        const cues = track.cues;
58023
        for (let i = 0; i < cues.length; i++) {
58024
            const cue = cues[i];
58025
            if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
58026
                return cue;
58027
            }
58028
        }
58029
        return null;
58030
    };
58031
    const updateAdCues = function (media, track, offset = 0) {
58032
        if (!media.segments) {
58033
            return;
58034
        }
58035
        let mediaTime = offset;
58036
        let cue;
58037
        for (let i = 0; i < media.segments.length; i++) {
58038
            const segment = media.segments[i];
58039
            if (!cue) {
58040
                // Since the cues will span for at least the segment duration, adding a fudge
58041
                // factor of half segment duration will prevent duplicate cues from being
58042
                // created when timing info is not exact (e.g. cue start time initialized
58043
                // at 10.006677, but next call mediaTime is 10.003332 )
58044
                cue = findAdCue(track, mediaTime + segment.duration / 2);
58045
            }
58046
            if (cue) {
58047
                if ('cueIn' in segment) {
58048
                    // Found a CUE-IN so end the cue
58049
                    cue.endTime = mediaTime;
58050
                    cue.adEndTime = mediaTime;
58051
                    mediaTime += segment.duration;
58052
                    cue = null;
58053
                    continue;
58054
                }
58055
                if (mediaTime < cue.endTime) {
58056
                    // Already processed this mediaTime for this cue
58057
                    mediaTime += segment.duration;
58058
                    continue;
58059
                } // otherwise extend cue until a CUE-IN is found
58060
 
58061
                cue.endTime += segment.duration;
58062
            } else {
58063
                if ('cueOut' in segment) {
58064
                    cue = new window.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut);
58065
                    cue.adStartTime = mediaTime; // Assumes tag format to be
58066
                    // #EXT-X-CUE-OUT:30
58067
 
58068
                    cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
58069
                    track.addCue(cue);
58070
                }
58071
                if ('cueOutCont' in segment) {
58072
                    // Entered into the middle of an ad cue
58073
                    // Assumes tag formate to be
58074
                    // #EXT-X-CUE-OUT-CONT:10/30
58075
                    const [adOffset, adTotal] = segment.cueOutCont.split('/').map(parseFloat);
58076
                    cue = new window.VTTCue(mediaTime, mediaTime + segment.duration, '');
58077
                    cue.adStartTime = mediaTime - adOffset;
58078
                    cue.adEndTime = cue.adStartTime + adTotal;
58079
                    track.addCue(cue);
58080
                }
58081
            }
58082
            mediaTime += segment.duration;
58083
        }
58084
    };
58085
 
58086
    /**
58087
     * @file sync-controller.js
58088
     */
58089
        // synchronize expired playlist segments.
58090
        // the max media sequence diff is 48 hours of live stream
58091
        // content with two second segments. Anything larger than that
58092
        // will likely be invalid.
58093
 
58094
    const MAX_MEDIA_SEQUENCE_DIFF_FOR_SYNC = 86400;
58095
    const syncPointStrategies = [
58096
        // Stategy "VOD": Handle the VOD-case where the sync-point is *always*
58097
        //                the equivalence display-time 0 === segment-index 0
58098
        {
58099
            name: 'VOD',
58100
            run: (syncController, playlist, duration, currentTimeline, currentTime) => {
58101
                if (duration !== Infinity) {
58102
                    const syncPoint = {
58103
                        time: 0,
58104
                        segmentIndex: 0,
58105
                        partIndex: null
58106
                    };
58107
                    return syncPoint;
58108
                }
58109
                return null;
58110
            }
58111
        }, {
58112
            name: 'MediaSequence',
58113
            /**
58114
             * run media sequence strategy
58115
             *
58116
             * @param {SyncController} syncController
58117
             * @param {Object} playlist
58118
             * @param {number} duration
58119
             * @param {number} currentTimeline
58120
             * @param {number} currentTime
58121
             * @param {string} type
58122
             */
58123
            run: (syncController, playlist, duration, currentTimeline, currentTime, type) => {
58124
                if (!type) {
58125
                    return null;
58126
                }
58127
                const mediaSequenceMap = syncController.getMediaSequenceMap(type);
58128
                if (!mediaSequenceMap || mediaSequenceMap.size === 0) {
58129
                    return null;
58130
                }
58131
                if (playlist.mediaSequence === undefined || !Array.isArray(playlist.segments) || !playlist.segments.length) {
58132
                    return null;
58133
                }
58134
                let currentMediaSequence = playlist.mediaSequence;
58135
                let segmentIndex = 0;
58136
                for (const segment of playlist.segments) {
58137
                    const range = mediaSequenceMap.get(currentMediaSequence);
58138
                    if (!range) {
58139
                        // unexpected case
58140
                        // we expect this playlist to be the same playlist in the map
58141
                        // just break from the loop and move forward to the next strategy
58142
                        break;
58143
                    }
58144
                    if (currentTime >= range.start && currentTime < range.end) {
58145
                        // we found segment
58146
                        if (Array.isArray(segment.parts) && segment.parts.length) {
58147
                            let currentPartStart = range.start;
58148
                            let partIndex = 0;
58149
                            for (const part of segment.parts) {
58150
                                const start = currentPartStart;
58151
                                const end = start + part.duration;
58152
                                if (currentTime >= start && currentTime < end) {
58153
                                    return {
58154
                                        time: range.start,
58155
                                        segmentIndex,
58156
                                        partIndex
58157
                                    };
58158
                                }
58159
                                partIndex++;
58160
                                currentPartStart = end;
58161
                            }
58162
                        } // no parts found, return sync point for segment
58163
 
58164
                        return {
58165
                            time: range.start,
58166
                            segmentIndex,
58167
                            partIndex: null
58168
                        };
58169
                    }
58170
                    segmentIndex++;
58171
                    currentMediaSequence++;
58172
                } // we didn't find any segments for provided current time
58173
 
58174
                return null;
58175
            }
58176
        },
58177
        // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
58178
        {
58179
            name: 'ProgramDateTime',
58180
            run: (syncController, playlist, duration, currentTimeline, currentTime) => {
58181
                if (!Object.keys(syncController.timelineToDatetimeMappings).length) {
58182
                    return null;
58183
                }
58184
                let syncPoint = null;
58185
                let lastDistance = null;
58186
                const partsAndSegments = getPartsAndSegments(playlist);
58187
                currentTime = currentTime || 0;
58188
                for (let i = 0; i < partsAndSegments.length; i++) {
58189
                    // start from the end and loop backwards for live
58190
                    // or start from the front and loop forwards for non-live
58191
                    const index = playlist.endList || currentTime === 0 ? i : partsAndSegments.length - (i + 1);
58192
                    const partAndSegment = partsAndSegments[index];
58193
                    const segment = partAndSegment.segment;
58194
                    const datetimeMapping = syncController.timelineToDatetimeMappings[segment.timeline];
58195
                    if (!datetimeMapping || !segment.dateTimeObject) {
58196
                        continue;
58197
                    }
58198
                    const segmentTime = segment.dateTimeObject.getTime() / 1000;
58199
                    let start = segmentTime + datetimeMapping; // take part duration into account.
58200
 
58201
                    if (segment.parts && typeof partAndSegment.partIndex === 'number') {
58202
                        for (let z = 0; z < partAndSegment.partIndex; z++) {
58203
                            start += segment.parts[z].duration;
58204
                        }
58205
                    }
58206
                    const distance = Math.abs(currentTime - start); // Once the distance begins to increase, or if distance is 0, we have passed
58207
                    // currentTime and can stop looking for better candidates
58208
 
58209
                    if (lastDistance !== null && (distance === 0 || lastDistance < distance)) {
58210
                        break;
58211
                    }
58212
                    lastDistance = distance;
58213
                    syncPoint = {
58214
                        time: start,
58215
                        segmentIndex: partAndSegment.segmentIndex,
58216
                        partIndex: partAndSegment.partIndex
58217
                    };
58218
                }
58219
                return syncPoint;
58220
            }
58221
        },
58222
        // Stategy "Segment": We have a known time mapping for a timeline and a
58223
        //                    segment in the current timeline with timing data
58224
        {
58225
            name: 'Segment',
58226
            run: (syncController, playlist, duration, currentTimeline, currentTime) => {
58227
                let syncPoint = null;
58228
                let lastDistance = null;
58229
                currentTime = currentTime || 0;
58230
                const partsAndSegments = getPartsAndSegments(playlist);
58231
                for (let i = 0; i < partsAndSegments.length; i++) {
58232
                    // start from the end and loop backwards for live
58233
                    // or start from the front and loop forwards for non-live
58234
                    const index = playlist.endList || currentTime === 0 ? i : partsAndSegments.length - (i + 1);
58235
                    const partAndSegment = partsAndSegments[index];
58236
                    const segment = partAndSegment.segment;
58237
                    const start = partAndSegment.part && partAndSegment.part.start || segment && segment.start;
58238
                    if (segment.timeline === currentTimeline && typeof start !== 'undefined') {
58239
                        const distance = Math.abs(currentTime - start); // Once the distance begins to increase, we have passed
58240
                        // currentTime and can stop looking for better candidates
58241
 
58242
                        if (lastDistance !== null && lastDistance < distance) {
58243
                            break;
58244
                        }
58245
                        if (!syncPoint || lastDistance === null || lastDistance >= distance) {
58246
                            lastDistance = distance;
58247
                            syncPoint = {
58248
                                time: start,
58249
                                segmentIndex: partAndSegment.segmentIndex,
58250
                                partIndex: partAndSegment.partIndex
58251
                            };
58252
                        }
58253
                    }
58254
                }
58255
                return syncPoint;
58256
            }
58257
        },
58258
        // Stategy "Discontinuity": We have a discontinuity with a known
58259
        //                          display-time
58260
        {
58261
            name: 'Discontinuity',
58262
            run: (syncController, playlist, duration, currentTimeline, currentTime) => {
58263
                let syncPoint = null;
58264
                currentTime = currentTime || 0;
58265
                if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
58266
                    let lastDistance = null;
58267
                    for (let i = 0; i < playlist.discontinuityStarts.length; i++) {
58268
                        const segmentIndex = playlist.discontinuityStarts[i];
58269
                        const discontinuity = playlist.discontinuitySequence + i + 1;
58270
                        const discontinuitySync = syncController.discontinuities[discontinuity];
58271
                        if (discontinuitySync) {
58272
                            const distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed
58273
                            // currentTime and can stop looking for better candidates
58274
 
58275
                            if (lastDistance !== null && lastDistance < distance) {
58276
                                break;
58277
                            }
58278
                            if (!syncPoint || lastDistance === null || lastDistance >= distance) {
58279
                                lastDistance = distance;
58280
                                syncPoint = {
58281
                                    time: discontinuitySync.time,
58282
                                    segmentIndex,
58283
                                    partIndex: null
58284
                                };
58285
                            }
58286
                        }
58287
                    }
58288
                }
58289
                return syncPoint;
58290
            }
58291
        },
58292
        // Stategy "Playlist": We have a playlist with a known mapping of
58293
        //                     segment index to display time
58294
        {
58295
            name: 'Playlist',
58296
            run: (syncController, playlist, duration, currentTimeline, currentTime) => {
58297
                if (playlist.syncInfo) {
58298
                    const syncPoint = {
58299
                        time: playlist.syncInfo.time,
58300
                        segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence,
58301
                        partIndex: null
58302
                    };
58303
                    return syncPoint;
58304
                }
58305
                return null;
58306
            }
58307
        }];
58308
    class SyncController extends videojs.EventTarget {
58309
        constructor(options = {}) {
58310
            super(); // ...for synching across variants
58311
 
58312
            this.timelines = [];
58313
            this.discontinuities = [];
58314
            this.timelineToDatetimeMappings = {};
58315
            /**
58316
             * @type {Map<string, Map<number, { start: number, end: number }>>}
58317
             * @private
58318
             */
58319
 
58320
            this.mediaSequenceStorage_ = new Map();
58321
            this.logger_ = logger('SyncController');
58322
        }
58323
        /**
58324
         * Get media sequence map by type
58325
         *
58326
         * @param {string} type - segment loader type
58327
         * @return {Map<number, { start: number, end: number }> | undefined}
58328
         */
58329
 
58330
        getMediaSequenceMap(type) {
58331
            return this.mediaSequenceStorage_.get(type);
58332
        }
58333
        /**
58334
         * Update Media Sequence Map -> <MediaSequence, Range>
58335
         *
58336
         * @param {Object} playlist - parsed playlist
58337
         * @param {number} currentTime - current player's time
58338
         * @param {string} type - segment loader type
58339
         * @return {void}
58340
         */
58341
 
58342
        updateMediaSequenceMap(playlist, currentTime, type) {
58343
            // we should not process this playlist if it does not have mediaSequence or segments
58344
            if (playlist.mediaSequence === undefined || !Array.isArray(playlist.segments) || !playlist.segments.length) {
58345
                return;
58346
            }
58347
            const currentMap = this.getMediaSequenceMap(type);
58348
            const result = new Map();
58349
            let currentMediaSequence = playlist.mediaSequence;
58350
            let currentBaseTime;
58351
            if (!currentMap) {
58352
                // first playlist setup:
58353
                currentBaseTime = 0;
58354
            } else if (currentMap.has(playlist.mediaSequence)) {
58355
                // further playlists setup:
58356
                currentBaseTime = currentMap.get(playlist.mediaSequence).start;
58357
            } else {
58358
                // it seems like we have a gap between playlists, use current time as a fallback:
58359
                this.logger_(`MediaSequence sync for ${type} segment loader - received a gap between playlists.
58360
Fallback base time to: ${currentTime}.
58361
Received media sequence: ${currentMediaSequence}.
58362
Current map: `, currentMap);
58363
                currentBaseTime = currentTime;
58364
            }
58365
            this.logger_(`MediaSequence sync for ${type} segment loader.
58366
Received media sequence: ${currentMediaSequence}.
58367
base time is ${currentBaseTime}
58368
Current map: `, currentMap);
58369
            playlist.segments.forEach(segment => {
58370
                const start = currentBaseTime;
58371
                const end = start + segment.duration;
58372
                const range = {
58373
                    start,
58374
                    end
58375
                };
58376
                result.set(currentMediaSequence, range);
58377
                currentMediaSequence++;
58378
                currentBaseTime = end;
58379
            });
58380
            this.mediaSequenceStorage_.set(type, result);
58381
        }
58382
        /**
58383
         * Find a sync-point for the playlist specified
58384
         *
58385
         * A sync-point is defined as a known mapping from display-time to
58386
         * a segment-index in the current playlist.
58387
         *
58388
         * @param {Playlist} playlist
58389
         *        The playlist that needs a sync-point
58390
         * @param {number} duration
58391
         *        Duration of the MediaSource (Infinite if playing a live source)
58392
         * @param {number} currentTimeline
58393
         *        The last timeline from which a segment was loaded
58394
         * @param {number} currentTime
58395
         *        Current player's time
58396
         * @param {string} type
58397
         *        Segment loader type
58398
         * @return {Object}
58399
         *          A sync-point object
58400
         */
58401
 
58402
        getSyncPoint(playlist, duration, currentTimeline, currentTime, type) {
58403
            // Always use VOD sync point for VOD
58404
            if (duration !== Infinity) {
58405
                const vodSyncPointStrategy = syncPointStrategies.find(({
58406
                                                                           name
58407
                                                                       }) => name === 'VOD');
58408
                return vodSyncPointStrategy.run(this, playlist, duration);
58409
            }
58410
            const syncPoints = this.runStrategies_(playlist, duration, currentTimeline, currentTime, type);
58411
            if (!syncPoints.length) {
58412
                // Signal that we need to attempt to get a sync-point manually
58413
                // by fetching a segment in the playlist and constructing
58414
                // a sync-point from that information
58415
                return null;
58416
            } // If we have exact match just return it instead of finding the nearest distance
58417
 
58418
            for (const syncPointInfo of syncPoints) {
58419
                const {
58420
                    syncPoint,
58421
                    strategy
58422
                } = syncPointInfo;
58423
                const {
58424
                    segmentIndex,
58425
                    time
58426
                } = syncPoint;
58427
                if (segmentIndex < 0) {
58428
                    continue;
58429
                }
58430
                const selectedSegment = playlist.segments[segmentIndex];
58431
                const start = time;
58432
                const end = start + selectedSegment.duration;
58433
                this.logger_(`Strategy: ${strategy}. Current time: ${currentTime}. selected segment: ${segmentIndex}. Time: [${start} -> ${end}]}`);
58434
                if (currentTime >= start && currentTime < end) {
58435
                    this.logger_('Found sync point with exact match: ', syncPoint);
58436
                    return syncPoint;
58437
                }
58438
            } // Now find the sync-point that is closest to the currentTime because
58439
            // that should result in the most accurate guess about which segment
58440
            // to fetch
58441
 
58442
            return this.selectSyncPoint_(syncPoints, {
58443
                key: 'time',
58444
                value: currentTime
58445
            });
58446
        }
58447
        /**
58448
         * Calculate the amount of time that has expired off the playlist during playback
58449
         *
58450
         * @param {Playlist} playlist
58451
         *        Playlist object to calculate expired from
58452
         * @param {number} duration
58453
         *        Duration of the MediaSource (Infinity if playling a live source)
58454
         * @return {number|null}
58455
         *          The amount of time that has expired off the playlist during playback. Null
58456
         *          if no sync-points for the playlist can be found.
58457
         */
58458
 
58459
        getExpiredTime(playlist, duration) {
58460
            if (!playlist || !playlist.segments) {
58461
                return null;
58462
            }
58463
            const syncPoints = this.runStrategies_(playlist, duration, playlist.discontinuitySequence, 0, 'main'); // Without sync-points, there is not enough information to determine the expired time
58464
 
58465
            if (!syncPoints.length) {
58466
                return null;
58467
            }
58468
            const syncPoint = this.selectSyncPoint_(syncPoints, {
58469
                key: 'segmentIndex',
58470
                value: 0
58471
            }); // If the sync-point is beyond the start of the playlist, we want to subtract the
58472
            // duration from index 0 to syncPoint.segmentIndex instead of adding.
58473
 
58474
            if (syncPoint.segmentIndex > 0) {
58475
                syncPoint.time *= -1;
58476
            }
58477
            return Math.abs(syncPoint.time + sumDurations({
58478
                defaultDuration: playlist.targetDuration,
58479
                durationList: playlist.segments,
58480
                startIndex: syncPoint.segmentIndex,
58481
                endIndex: 0
58482
            }));
58483
        }
58484
        /**
58485
         * Runs each sync-point strategy and returns a list of sync-points returned by the
58486
         * strategies
58487
         *
58488
         * @private
58489
         * @param {Playlist} playlist
58490
         *        The playlist that needs a sync-point
58491
         * @param {number} duration
58492
         *        Duration of the MediaSource (Infinity if playing a live source)
58493
         * @param {number} currentTimeline
58494
         *        The last timeline from which a segment was loaded
58495
         * @param {number} currentTime
58496
         *        Current player's time
58497
         * @param {string} type
58498
         *        Segment loader type
58499
         * @return {Array}
58500
         *          A list of sync-point objects
58501
         */
58502
 
58503
        runStrategies_(playlist, duration, currentTimeline, currentTime, type) {
58504
            const syncPoints = []; // Try to find a sync-point in by utilizing various strategies...
58505
 
58506
            for (let i = 0; i < syncPointStrategies.length; i++) {
58507
                const strategy = syncPointStrategies[i];
58508
                const syncPoint = strategy.run(this, playlist, duration, currentTimeline, currentTime, type);
58509
                if (syncPoint) {
58510
                    syncPoint.strategy = strategy.name;
58511
                    syncPoints.push({
58512
                        strategy: strategy.name,
58513
                        syncPoint
58514
                    });
58515
                }
58516
            }
58517
            return syncPoints;
58518
        }
58519
        /**
58520
         * Selects the sync-point nearest the specified target
58521
         *
58522
         * @private
58523
         * @param {Array} syncPoints
58524
         *        List of sync-points to select from
58525
         * @param {Object} target
58526
         *        Object specifying the property and value we are targeting
58527
         * @param {string} target.key
58528
         *        Specifies the property to target. Must be either 'time' or 'segmentIndex'
58529
         * @param {number} target.value
58530
         *        The value to target for the specified key.
58531
         * @return {Object}
58532
         *          The sync-point nearest the target
58533
         */
58534
 
58535
        selectSyncPoint_(syncPoints, target) {
58536
            let bestSyncPoint = syncPoints[0].syncPoint;
58537
            let bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
58538
            let bestStrategy = syncPoints[0].strategy;
58539
            for (let i = 1; i < syncPoints.length; i++) {
58540
                const newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
58541
                if (newDistance < bestDistance) {
58542
                    bestDistance = newDistance;
58543
                    bestSyncPoint = syncPoints[i].syncPoint;
58544
                    bestStrategy = syncPoints[i].strategy;
58545
                }
58546
            }
58547
            this.logger_(`syncPoint for [${target.key}: ${target.value}] chosen with strategy` + ` [${bestStrategy}]: [time:${bestSyncPoint.time},` + ` segmentIndex:${bestSyncPoint.segmentIndex}` + (typeof bestSyncPoint.partIndex === 'number' ? `,partIndex:${bestSyncPoint.partIndex}` : '') + ']');
58548
            return bestSyncPoint;
58549
        }
58550
        /**
58551
         * Save any meta-data present on the segments when segments leave
58552
         * the live window to the playlist to allow for synchronization at the
58553
         * playlist level later.
58554
         *
58555
         * @param {Playlist} oldPlaylist - The previous active playlist
58556
         * @param {Playlist} newPlaylist - The updated and most current playlist
58557
         */
58558
 
58559
        saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
58560
            const mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // Ignore large media sequence gaps
58561
 
58562
            if (mediaSequenceDiff > MAX_MEDIA_SEQUENCE_DIFF_FOR_SYNC) {
58563
                videojs.log.warn(`Not saving expired segment info. Media sequence gap ${mediaSequenceDiff} is too large.`);
58564
                return;
58565
            } // When a segment expires from the playlist and it has a start time
58566
            // save that information as a possible sync-point reference in future
58567
 
58568
            for (let i = mediaSequenceDiff - 1; i >= 0; i--) {
58569
                const lastRemovedSegment = oldPlaylist.segments[i];
58570
                if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
58571
                    newPlaylist.syncInfo = {
58572
                        mediaSequence: oldPlaylist.mediaSequence + i,
58573
                        time: lastRemovedSegment.start
58574
                    };
58575
                    this.logger_(`playlist refresh sync: [time:${newPlaylist.syncInfo.time},` + ` mediaSequence: ${newPlaylist.syncInfo.mediaSequence}]`);
58576
                    this.trigger('syncinfoupdate');
58577
                    break;
58578
                }
58579
            }
58580
        }
58581
        /**
58582
         * Save the mapping from playlist's ProgramDateTime to display. This should only happen
58583
         * before segments start to load.
58584
         *
58585
         * @param {Playlist} playlist - The currently active playlist
58586
         */
58587
 
58588
        setDateTimeMappingForStart(playlist) {
58589
            // It's possible for the playlist to be updated before playback starts, meaning time
58590
            // zero is not yet set. If, during these playlist refreshes, a discontinuity is
58591
            // crossed, then the old time zero mapping (for the prior timeline) would be retained
58592
            // unless the mappings are cleared.
58593
            this.timelineToDatetimeMappings = {};
58594
            if (playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
58595
                const firstSegment = playlist.segments[0];
58596
                const playlistTimestamp = firstSegment.dateTimeObject.getTime() / 1000;
58597
                this.timelineToDatetimeMappings[firstSegment.timeline] = -playlistTimestamp;
58598
            }
58599
        }
58600
        /**
58601
         * Calculates and saves timeline mappings, playlist sync info, and segment timing values
58602
         * based on the latest timing information.
58603
         *
58604
         * @param {Object} options
58605
         *        Options object
58606
         * @param {SegmentInfo} options.segmentInfo
58607
         *        The current active request information
58608
         * @param {boolean} options.shouldSaveTimelineMapping
58609
         *        If there's a timeline change, determines if the timeline mapping should be
58610
         *        saved for timeline mapping and program date time mappings.
58611
         */
58612
 
58613
        saveSegmentTimingInfo({
58614
                                  segmentInfo,
58615
                                  shouldSaveTimelineMapping
58616
                              }) {
58617
            const didCalculateSegmentTimeMapping = this.calculateSegmentTimeMapping_(segmentInfo, segmentInfo.timingInfo, shouldSaveTimelineMapping);
58618
            const segment = segmentInfo.segment;
58619
            if (didCalculateSegmentTimeMapping) {
58620
                this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information
58621
                // now with segment timing information
58622
 
58623
                if (!segmentInfo.playlist.syncInfo) {
58624
                    segmentInfo.playlist.syncInfo = {
58625
                        mediaSequence: segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex,
58626
                        time: segment.start
58627
                    };
58628
                }
58629
            }
58630
            const dateTime = segment.dateTimeObject;
58631
            if (segment.discontinuity && shouldSaveTimelineMapping && dateTime) {
58632
                this.timelineToDatetimeMappings[segment.timeline] = -(dateTime.getTime() / 1000);
58633
            }
58634
        }
58635
        timestampOffsetForTimeline(timeline) {
58636
            if (typeof this.timelines[timeline] === 'undefined') {
58637
                return null;
58638
            }
58639
            return this.timelines[timeline].time;
58640
        }
58641
        mappingForTimeline(timeline) {
58642
            if (typeof this.timelines[timeline] === 'undefined') {
58643
                return null;
58644
            }
58645
            return this.timelines[timeline].mapping;
58646
        }
58647
        /**
58648
         * Use the "media time" for a segment to generate a mapping to "display time" and
58649
         * save that display time to the segment.
58650
         *
58651
         * @private
58652
         * @param {SegmentInfo} segmentInfo
58653
         *        The current active request information
58654
         * @param {Object} timingInfo
58655
         *        The start and end time of the current segment in "media time"
58656
         * @param {boolean} shouldSaveTimelineMapping
58657
         *        If there's a timeline change, determines if the timeline mapping should be
58658
         *        saved in timelines.
58659
         * @return {boolean}
58660
         *          Returns false if segment time mapping could not be calculated
58661
         */
58662
 
58663
        calculateSegmentTimeMapping_(segmentInfo, timingInfo, shouldSaveTimelineMapping) {
58664
            // TODO: remove side effects
58665
            const segment = segmentInfo.segment;
58666
            const part = segmentInfo.part;
58667
            let mappingObj = this.timelines[segmentInfo.timeline];
58668
            let start;
58669
            let end;
58670
            if (typeof segmentInfo.timestampOffset === 'number') {
58671
                mappingObj = {
58672
                    time: segmentInfo.startOfSegment,
58673
                    mapping: segmentInfo.startOfSegment - timingInfo.start
58674
                };
58675
                if (shouldSaveTimelineMapping) {
58676
                    this.timelines[segmentInfo.timeline] = mappingObj;
58677
                    this.trigger('timestampoffset');
58678
                    this.logger_(`time mapping for timeline ${segmentInfo.timeline}: ` + `[time: ${mappingObj.time}] [mapping: ${mappingObj.mapping}]`);
58679
                }
58680
                start = segmentInfo.startOfSegment;
58681
                end = timingInfo.end + mappingObj.mapping;
58682
            } else if (mappingObj) {
58683
                start = timingInfo.start + mappingObj.mapping;
58684
                end = timingInfo.end + mappingObj.mapping;
58685
            } else {
58686
                return false;
58687
            }
58688
            if (part) {
58689
                part.start = start;
58690
                part.end = end;
58691
            } // If we don't have a segment start yet or the start value we got
58692
            // is less than our current segment.start value, save a new start value.
58693
            // We have to do this because parts will have segment timing info saved
58694
            // multiple times and we want segment start to be the earliest part start
58695
            // value for that segment.
58696
 
58697
            if (!segment.start || start < segment.start) {
58698
                segment.start = start;
58699
            }
58700
            segment.end = end;
58701
            return true;
58702
        }
58703
        /**
58704
         * Each time we have discontinuity in the playlist, attempt to calculate the location
58705
         * in display of the start of the discontinuity and save that. We also save an accuracy
58706
         * value so that we save values with the most accuracy (closest to 0.)
58707
         *
58708
         * @private
58709
         * @param {SegmentInfo} segmentInfo - The current active request information
58710
         */
58711
 
58712
        saveDiscontinuitySyncInfo_(segmentInfo) {
58713
            const playlist = segmentInfo.playlist;
58714
            const segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where
58715
            // the start of the range and it's accuracy is 0 (greater accuracy values
58716
            // mean more approximation)
58717
 
58718
            if (segment.discontinuity) {
58719
                this.discontinuities[segment.timeline] = {
58720
                    time: segment.start,
58721
                    accuracy: 0
58722
                };
58723
            } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
58724
                // Search for future discontinuities that we can provide better timing
58725
                // information for and save that information for sync purposes
58726
                for (let i = 0; i < playlist.discontinuityStarts.length; i++) {
58727
                    const segmentIndex = playlist.discontinuityStarts[i];
58728
                    const discontinuity = playlist.discontinuitySequence + i + 1;
58729
                    const mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
58730
                    const accuracy = Math.abs(mediaIndexDiff);
58731
                    if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
58732
                        let time;
58733
                        if (mediaIndexDiff < 0) {
58734
                            time = segment.start - sumDurations({
58735
                                defaultDuration: playlist.targetDuration,
58736
                                durationList: playlist.segments,
58737
                                startIndex: segmentInfo.mediaIndex,
58738
                                endIndex: segmentIndex
58739
                            });
58740
                        } else {
58741
                            time = segment.end + sumDurations({
58742
                                defaultDuration: playlist.targetDuration,
58743
                                durationList: playlist.segments,
58744
                                startIndex: segmentInfo.mediaIndex + 1,
58745
                                endIndex: segmentIndex
58746
                            });
58747
                        }
58748
                        this.discontinuities[discontinuity] = {
58749
                            time,
58750
                            accuracy
58751
                        };
58752
                    }
58753
                }
58754
            }
58755
        }
58756
        dispose() {
58757
            this.trigger('dispose');
58758
            this.off();
58759
        }
58760
    }
58761
 
58762
    /**
58763
     * The TimelineChangeController acts as a source for segment loaders to listen for and
58764
     * keep track of latest and pending timeline changes. This is useful to ensure proper
58765
     * sync, as each loader may need to make a consideration for what timeline the other
58766
     * loader is on before making changes which could impact the other loader's media.
58767
     *
58768
     * @class TimelineChangeController
58769
     * @extends videojs.EventTarget
58770
     */
58771
 
58772
    class TimelineChangeController extends videojs.EventTarget {
58773
        constructor() {
58774
            super();
58775
            this.pendingTimelineChanges_ = {};
58776
            this.lastTimelineChanges_ = {};
58777
        }
58778
        clearPendingTimelineChange(type) {
58779
            this.pendingTimelineChanges_[type] = null;
58780
            this.trigger('pendingtimelinechange');
58781
        }
58782
        pendingTimelineChange({
58783
                                  type,
58784
                                  from,
58785
                                  to
58786
                              }) {
58787
            if (typeof from === 'number' && typeof to === 'number') {
58788
                this.pendingTimelineChanges_[type] = {
58789
                    type,
58790
                    from,
58791
                    to
58792
                };
58793
                this.trigger('pendingtimelinechange');
58794
            }
58795
            return this.pendingTimelineChanges_[type];
58796
        }
58797
        lastTimelineChange({
58798
                               type,
58799
                               from,
58800
                               to
58801
                           }) {
58802
            if (typeof from === 'number' && typeof to === 'number') {
58803
                this.lastTimelineChanges_[type] = {
58804
                    type,
58805
                    from,
58806
                    to
58807
                };
58808
                delete this.pendingTimelineChanges_[type];
58809
                this.trigger('timelinechange');
58810
            }
58811
            return this.lastTimelineChanges_[type];
58812
        }
58813
        dispose() {
58814
            this.trigger('dispose');
58815
            this.pendingTimelineChanges_ = {};
58816
            this.lastTimelineChanges_ = {};
58817
            this.off();
58818
        }
58819
    }
58820
 
58821
    /* rollup-plugin-worker-factory start for worker!/home/runner/work/http-streaming/http-streaming/src/decrypter-worker.js */
58822
    const workerCode = transform(getWorkerString(function () {
58823
        /**
58824
         * @file stream.js
58825
         */
58826
 
58827
        /**
58828
         * A lightweight readable stream implemention that handles event dispatching.
58829
         *
58830
         * @class Stream
58831
         */
58832
 
58833
        var Stream = /*#__PURE__*/function () {
58834
            function Stream() {
58835
                this.listeners = {};
58836
            }
58837
            /**
58838
             * Add a listener for a specified event type.
58839
             *
58840
             * @param {string} type the event name
58841
             * @param {Function} listener the callback to be invoked when an event of
58842
             * the specified type occurs
58843
             */
58844
 
58845
            var _proto = Stream.prototype;
58846
            _proto.on = function on(type, listener) {
58847
                if (!this.listeners[type]) {
58848
                    this.listeners[type] = [];
58849
                }
58850
                this.listeners[type].push(listener);
58851
            }
58852
            /**
58853
             * Remove a listener for a specified event type.
58854
             *
58855
             * @param {string} type the event name
58856
             * @param {Function} listener  a function previously registered for this
58857
             * type of event through `on`
58858
             * @return {boolean} if we could turn it off or not
58859
             */;
58860
 
58861
            _proto.off = function off(type, listener) {
58862
                if (!this.listeners[type]) {
58863
                    return false;
58864
                }
58865
                var index = this.listeners[type].indexOf(listener); // TODO: which is better?
58866
                // In Video.js we slice listener functions
58867
                // on trigger so that it does not mess up the order
58868
                // while we loop through.
58869
                //
58870
                // Here we slice on off so that the loop in trigger
58871
                // can continue using it's old reference to loop without
58872
                // messing up the order.
58873
 
58874
                this.listeners[type] = this.listeners[type].slice(0);
58875
                this.listeners[type].splice(index, 1);
58876
                return index > -1;
58877
            }
58878
            /**
58879
             * Trigger an event of the specified type on this stream. Any additional
58880
             * arguments to this function are passed as parameters to event listeners.
58881
             *
58882
             * @param {string} type the event name
58883
             */;
58884
 
58885
            _proto.trigger = function trigger(type) {
58886
                var callbacks = this.listeners[type];
58887
                if (!callbacks) {
58888
                    return;
58889
                } // Slicing the arguments on every invocation of this method
58890
                // can add a significant amount of overhead. Avoid the
58891
                // intermediate object creation for the common case of a
58892
                // single callback argument
58893
 
58894
                if (arguments.length === 2) {
58895
                    var length = callbacks.length;
58896
                    for (var i = 0; i < length; ++i) {
58897
                        callbacks[i].call(this, arguments[1]);
58898
                    }
58899
                } else {
58900
                    var args = Array.prototype.slice.call(arguments, 1);
58901
                    var _length = callbacks.length;
58902
                    for (var _i = 0; _i < _length; ++_i) {
58903
                        callbacks[_i].apply(this, args);
58904
                    }
58905
                }
58906
            }
58907
            /**
58908
             * Destroys the stream and cleans up.
58909
             */;
58910
 
58911
            _proto.dispose = function dispose() {
58912
                this.listeners = {};
58913
            }
58914
            /**
58915
             * Forwards all `data` events on this stream to the destination stream. The
58916
             * destination stream should provide a method `push` to receive the data
58917
             * events as they arrive.
58918
             *
58919
             * @param {Stream} destination the stream that will receive all `data` events
58920
             * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
58921
             */;
58922
 
58923
            _proto.pipe = function pipe(destination) {
58924
                this.on('data', function (data) {
58925
                    destination.push(data);
58926
                });
58927
            };
58928
            return Stream;
58929
        }();
58930
        /*! @name pkcs7 @version 1.0.4 @license Apache-2.0 */
58931
 
58932
        /**
58933
         * Returns the subarray of a Uint8Array without PKCS#7 padding.
58934
         *
58935
         * @param padded {Uint8Array} unencrypted bytes that have been padded
58936
         * @return {Uint8Array} the unpadded bytes
58937
         * @see http://tools.ietf.org/html/rfc5652
58938
         */
58939
 
58940
        function unpad(padded) {
58941
            return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
58942
        }
58943
        /*! @name aes-decrypter @version 4.0.1 @license Apache-2.0 */
58944
 
58945
        /**
58946
         * @file aes.js
58947
         *
58948
         * This file contains an adaptation of the AES decryption algorithm
58949
         * from the Standford Javascript Cryptography Library. That work is
58950
         * covered by the following copyright and permissions notice:
58951
         *
58952
         * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
58953
         * All rights reserved.
58954
         *
58955
         * Redistribution and use in source and binary forms, with or without
58956
         * modification, are permitted provided that the following conditions are
58957
         * met:
58958
         *
58959
         * 1. Redistributions of source code must retain the above copyright
58960
         *    notice, this list of conditions and the following disclaimer.
58961
         *
58962
         * 2. Redistributions in binary form must reproduce the above
58963
         *    copyright notice, this list of conditions and the following
58964
         *    disclaimer in the documentation and/or other materials provided
58965
         *    with the distribution.
58966
         *
58967
         * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
58968
         * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
58969
         * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
58970
         * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
58971
         * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
58972
         * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
58973
         * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
58974
         * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
58975
         * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
58976
         * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
58977
         * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
58978
         *
58979
         * The views and conclusions contained in the software and documentation
58980
         * are those of the authors and should not be interpreted as representing
58981
         * official policies, either expressed or implied, of the authors.
58982
         */
58983
 
58984
        /**
58985
         * Expand the S-box tables.
58986
         *
58987
         * @private
58988
         */
58989
 
58990
        const precompute = function () {
58991
            const tables = [[[], [], [], [], []], [[], [], [], [], []]];
58992
            const encTable = tables[0];
58993
            const decTable = tables[1];
58994
            const sbox = encTable[4];
58995
            const sboxInv = decTable[4];
58996
            let i;
58997
            let x;
58998
            let xInv;
58999
            const d = [];
59000
            const th = [];
59001
            let x2;
59002
            let x4;
59003
            let x8;
59004
            let s;
59005
            let tEnc;
59006
            let tDec; // Compute double and third tables
59007
 
59008
            for (i = 0; i < 256; i++) {
59009
                th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
59010
            }
59011
            for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
59012
                // Compute sbox
59013
                s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
59014
                s = s >> 8 ^ s & 255 ^ 99;
59015
                sbox[x] = s;
59016
                sboxInv[s] = x; // Compute MixColumns
59017
 
59018
                x8 = d[x4 = d[x2 = d[x]]];
59019
                tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
59020
                tEnc = d[s] * 0x101 ^ s * 0x1010100;
59021
                for (i = 0; i < 4; i++) {
59022
                    encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
59023
                    decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
59024
                }
59025
            } // Compactify. Considerable speedup on Firefox.
59026
 
59027
            for (i = 0; i < 5; i++) {
59028
                encTable[i] = encTable[i].slice(0);
59029
                decTable[i] = decTable[i].slice(0);
59030
            }
59031
            return tables;
59032
        };
59033
        let aesTables = null;
59034
        /**
59035
         * Schedule out an AES key for both encryption and decryption. This
59036
         * is a low-level class. Use a cipher mode to do bulk encryption.
59037
         *
59038
         * @class AES
59039
         * @param key {Array} The key as an array of 4, 6 or 8 words.
59040
         */
59041
 
59042
        class AES {
59043
            constructor(key) {
59044
                /**
59045
                 * The expanded S-box and inverse S-box tables. These will be computed
59046
                 * on the client so that we don't have to send them down the wire.
59047
                 *
59048
                 * There are two tables, _tables[0] is for encryption and
59049
                 * _tables[1] is for decryption.
59050
                 *
59051
                 * The first 4 sub-tables are the expanded S-box with MixColumns. The
59052
                 * last (_tables[01][4]) is the S-box itself.
59053
                 *
59054
                 * @private
59055
                 */
59056
                // if we have yet to precompute the S-box tables
59057
                // do so now
59058
                if (!aesTables) {
59059
                    aesTables = precompute();
59060
                } // then make a copy of that object for use
59061
 
59062
                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()]];
59063
                let i;
59064
                let j;
59065
                let tmp;
59066
                const sbox = this._tables[0][4];
59067
                const decTable = this._tables[1];
59068
                const keyLen = key.length;
59069
                let rcon = 1;
59070
                if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
59071
                    throw new Error('Invalid aes key size');
59072
                }
59073
                const encKey = key.slice(0);
59074
                const decKey = [];
59075
                this._key = [encKey, decKey]; // schedule encryption keys
59076
 
59077
                for (i = keyLen; i < 4 * keyLen + 28; i++) {
59078
                    tmp = encKey[i - 1]; // apply sbox
59079
 
59080
                    if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
59081
                        tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
59082
 
59083
                        if (i % keyLen === 0) {
59084
                            tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
59085
                            rcon = rcon << 1 ^ (rcon >> 7) * 283;
59086
                        }
59087
                    }
59088
                    encKey[i] = encKey[i - keyLen] ^ tmp;
59089
                } // schedule decryption keys
59090
 
59091
                for (j = 0; i; j++, i--) {
59092
                    tmp = encKey[j & 3 ? i : i - 4];
59093
                    if (i <= 4 || j < 4) {
59094
                        decKey[j] = tmp;
59095
                    } else {
59096
                        decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
59097
                    }
59098
                }
59099
            }
59100
            /**
59101
             * Decrypt 16 bytes, specified as four 32-bit words.
59102
             *
59103
             * @param {number} encrypted0 the first word to decrypt
59104
             * @param {number} encrypted1 the second word to decrypt
59105
             * @param {number} encrypted2 the third word to decrypt
59106
             * @param {number} encrypted3 the fourth word to decrypt
59107
             * @param {Int32Array} out the array to write the decrypted words
59108
             * into
59109
             * @param {number} offset the offset into the output array to start
59110
             * writing results
59111
             * @return {Array} The plaintext.
59112
             */
59113
 
59114
            decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
59115
                const key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
59116
 
59117
                let a = encrypted0 ^ key[0];
59118
                let b = encrypted3 ^ key[1];
59119
                let c = encrypted2 ^ key[2];
59120
                let d = encrypted1 ^ key[3];
59121
                let a2;
59122
                let b2;
59123
                let c2; // key.length === 2 ?
59124
 
59125
                const nInnerRounds = key.length / 4 - 2;
59126
                let i;
59127
                let kIndex = 4;
59128
                const table = this._tables[1]; // load up the tables
59129
 
59130
                const table0 = table[0];
59131
                const table1 = table[1];
59132
                const table2 = table[2];
59133
                const table3 = table[3];
59134
                const sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
59135
 
59136
                for (i = 0; i < nInnerRounds; i++) {
59137
                    a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
59138
                    b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
59139
                    c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
59140
                    d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
59141
                    kIndex += 4;
59142
                    a = a2;
59143
                    b = b2;
59144
                    c = c2;
59145
                } // Last round.
59146
 
59147
                for (i = 0; i < 4; i++) {
59148
                    out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
59149
                    a2 = a;
59150
                    a = b;
59151
                    b = c;
59152
                    c = d;
59153
                    d = a2;
59154
                }
59155
            }
59156
        }
59157
        /**
59158
         * @file async-stream.js
59159
         */
59160
 
59161
        /**
59162
         * A wrapper around the Stream class to use setTimeout
59163
         * and run stream "jobs" Asynchronously
59164
         *
59165
         * @class AsyncStream
59166
         * @extends Stream
59167
         */
59168
 
59169
        class AsyncStream extends Stream {
59170
            constructor() {
59171
                super(Stream);
59172
                this.jobs = [];
59173
                this.delay = 1;
59174
                this.timeout_ = null;
59175
            }
59176
            /**
59177
             * process an async job
59178
             *
59179
             * @private
59180
             */
59181
 
59182
            processJob_() {
59183
                this.jobs.shift()();
59184
                if (this.jobs.length) {
59185
                    this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
59186
                } else {
59187
                    this.timeout_ = null;
59188
                }
59189
            }
59190
            /**
59191
             * push a job into the stream
59192
             *
59193
             * @param {Function} job the job to push into the stream
59194
             */
59195
 
59196
            push(job) {
59197
                this.jobs.push(job);
59198
                if (!this.timeout_) {
59199
                    this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
59200
                }
59201
            }
59202
        }
59203
        /**
59204
         * @file decrypter.js
59205
         *
59206
         * An asynchronous implementation of AES-128 CBC decryption with
59207
         * PKCS#7 padding.
59208
         */
59209
 
59210
        /**
59211
         * Convert network-order (big-endian) bytes into their little-endian
59212
         * representation.
59213
         */
59214
 
59215
        const ntoh = function (word) {
59216
            return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
59217
        };
59218
        /**
59219
         * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
59220
         *
59221
         * @param {Uint8Array} encrypted the encrypted bytes
59222
         * @param {Uint32Array} key the bytes of the decryption key
59223
         * @param {Uint32Array} initVector the initialization vector (IV) to
59224
         * use for the first round of CBC.
59225
         * @return {Uint8Array} the decrypted bytes
59226
         *
59227
         * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
59228
         * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
59229
         * @see https://tools.ietf.org/html/rfc2315
59230
         */
59231
 
59232
        const decrypt = function (encrypted, key, initVector) {
59233
            // word-level access to the encrypted bytes
59234
            const encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
59235
            const decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
59236
 
59237
            const decrypted = new Uint8Array(encrypted.byteLength);
59238
            const decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
59239
            // decrypted data
59240
 
59241
            let init0;
59242
            let init1;
59243
            let init2;
59244
            let init3;
59245
            let encrypted0;
59246
            let encrypted1;
59247
            let encrypted2;
59248
            let encrypted3; // iteration variable
59249
 
59250
            let wordIx; // pull out the words of the IV to ensure we don't modify the
59251
            // passed-in reference and easier access
59252
 
59253
            init0 = initVector[0];
59254
            init1 = initVector[1];
59255
            init2 = initVector[2];
59256
            init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
59257
            // to each decrypted block
59258
 
59259
            for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
59260
                // convert big-endian (network order) words into little-endian
59261
                // (javascript order)
59262
                encrypted0 = ntoh(encrypted32[wordIx]);
59263
                encrypted1 = ntoh(encrypted32[wordIx + 1]);
59264
                encrypted2 = ntoh(encrypted32[wordIx + 2]);
59265
                encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
59266
 
59267
                decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
59268
                // plaintext
59269
 
59270
                decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
59271
                decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
59272
                decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
59273
                decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
59274
 
59275
                init0 = encrypted0;
59276
                init1 = encrypted1;
59277
                init2 = encrypted2;
59278
                init3 = encrypted3;
59279
            }
59280
            return decrypted;
59281
        };
59282
        /**
59283
         * The `Decrypter` class that manages decryption of AES
59284
         * data through `AsyncStream` objects and the `decrypt`
59285
         * function
59286
         *
59287
         * @param {Uint8Array} encrypted the encrypted bytes
59288
         * @param {Uint32Array} key the bytes of the decryption key
59289
         * @param {Uint32Array} initVector the initialization vector (IV) to
59290
         * @param {Function} done the function to run when done
59291
         * @class Decrypter
59292
         */
59293
 
59294
        class Decrypter {
59295
            constructor(encrypted, key, initVector, done) {
59296
                const step = Decrypter.STEP;
59297
                const encrypted32 = new Int32Array(encrypted.buffer);
59298
                const decrypted = new Uint8Array(encrypted.byteLength);
59299
                let i = 0;
59300
                this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously
59301
 
59302
                this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
59303
                for (i = step; i < encrypted32.length; i += step) {
59304
                    initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
59305
                    this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
59306
                } // invoke the done() callback when everything is finished
59307
 
59308
                this.asyncStream_.push(function () {
59309
                    // remove pkcs#7 padding from the decrypted bytes
59310
                    done(null, unpad(decrypted));
59311
                });
59312
            }
59313
            /**
59314
             * a getter for step the maximum number of bytes to process at one time
59315
             *
59316
             * @return {number} the value of step 32000
59317
             */
59318
 
59319
            static get STEP() {
59320
                // 4 * 8000;
59321
                return 32000;
59322
            }
59323
            /**
59324
             * @private
59325
             */
59326
 
59327
            decryptChunk_(encrypted, key, initVector, decrypted) {
59328
                return function () {
59329
                    const bytes = decrypt(encrypted, key, initVector);
59330
                    decrypted.set(bytes, encrypted.byteOffset);
59331
                };
59332
            }
59333
        }
59334
        var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
59335
        var win;
59336
        if (typeof window !== "undefined") {
59337
            win = window;
59338
        } else if (typeof commonjsGlobal !== "undefined") {
59339
            win = commonjsGlobal;
59340
        } else if (typeof self !== "undefined") {
59341
            win = self;
59342
        } else {
59343
            win = {};
59344
        }
59345
        var window_1 = win;
59346
        var isArrayBufferView = function isArrayBufferView(obj) {
59347
            if (ArrayBuffer.isView === 'function') {
59348
                return ArrayBuffer.isView(obj);
59349
            }
59350
            return obj && obj.buffer instanceof ArrayBuffer;
59351
        };
59352
        var BigInt = window_1.BigInt || Number;
59353
        [BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')];
59354
        (function () {
59355
            var a = new Uint16Array([0xFFCC]);
59356
            var b = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
59357
            if (b[0] === 0xFF) {
59358
                return 'big';
59359
            }
59360
            if (b[0] === 0xCC) {
59361
                return 'little';
59362
            }
59363
            return 'unknown';
59364
        })();
59365
        /**
59366
         * Creates an object for sending to a web worker modifying properties that are TypedArrays
59367
         * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
59368
         *
59369
         * @param {Object} message
59370
         *        Object of properties and values to send to the web worker
59371
         * @return {Object}
59372
         *         Modified message with TypedArray values expanded
59373
         * @function createTransferableMessage
59374
         */
59375
 
59376
        const createTransferableMessage = function (message) {
59377
            const transferable = {};
59378
            Object.keys(message).forEach(key => {
59379
                const value = message[key];
59380
                if (isArrayBufferView(value)) {
59381
                    transferable[key] = {
59382
                        bytes: value.buffer,
59383
                        byteOffset: value.byteOffset,
59384
                        byteLength: value.byteLength
59385
                    };
59386
                } else {
59387
                    transferable[key] = value;
59388
                }
59389
            });
59390
            return transferable;
59391
        };
59392
        /* global self */
59393
 
59394
        /**
59395
         * Our web worker interface so that things can talk to aes-decrypter
59396
         * that will be running in a web worker. the scope is passed to this by
59397
         * webworkify.
59398
         */
59399
 
59400
        self.onmessage = function (event) {
59401
            const data = event.data;
59402
            const encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength);
59403
            const key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
59404
            const iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
59405
            /* eslint-disable no-new, handle-callback-err */
59406
 
59407
            new Decrypter(encrypted, key, iv, function (err, bytes) {
59408
                self.postMessage(createTransferableMessage({
59409
                    source: data.source,
59410
                    decrypted: bytes
59411
                }), [bytes.buffer]);
59412
            });
59413
            /* eslint-enable */
59414
        };
59415
    }));
59416
 
59417
    var Decrypter = factory(workerCode);
59418
    /* rollup-plugin-worker-factory end for worker!/home/runner/work/http-streaming/http-streaming/src/decrypter-worker.js */
59419
 
59420
    /**
59421
     * Convert the properties of an HLS track into an audioTrackKind.
59422
     *
59423
     * @private
59424
     */
59425
 
59426
    const audioTrackKind_ = properties => {
59427
        let kind = properties.default ? 'main' : 'alternative';
59428
        if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
59429
            kind = 'main-desc';
59430
        }
59431
        return kind;
59432
    };
59433
    /**
59434
     * Pause provided segment loader and playlist loader if active
59435
     *
59436
     * @param {SegmentLoader} segmentLoader
59437
     *        SegmentLoader to pause
59438
     * @param {Object} mediaType
59439
     *        Active media type
59440
     * @function stopLoaders
59441
     */
59442
 
59443
    const stopLoaders = (segmentLoader, mediaType) => {
59444
        segmentLoader.abort();
59445
        segmentLoader.pause();
59446
        if (mediaType && mediaType.activePlaylistLoader) {
59447
            mediaType.activePlaylistLoader.pause();
59448
            mediaType.activePlaylistLoader = null;
59449
        }
59450
    };
59451
    /**
59452
     * Start loading provided segment loader and playlist loader
59453
     *
59454
     * @param {PlaylistLoader} playlistLoader
59455
     *        PlaylistLoader to start loading
59456
     * @param {Object} mediaType
59457
     *        Active media type
59458
     * @function startLoaders
59459
     */
59460
 
59461
    const startLoaders = (playlistLoader, mediaType) => {
59462
        // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
59463
        // playlist loader
59464
        mediaType.activePlaylistLoader = playlistLoader;
59465
        playlistLoader.load();
59466
    };
59467
    /**
59468
     * Returns a function to be called when the media group changes. It performs a
59469
     * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
59470
     * change of group is merely a rendition switch of the same content at another encoding,
59471
     * rather than a change of content, such as switching audio from English to Spanish.
59472
     *
59473
     * @param {string} type
59474
     *        MediaGroup type
59475
     * @param {Object} settings
59476
     *        Object containing required information for media groups
59477
     * @return {Function}
59478
     *         Handler for a non-destructive resync of SegmentLoader when the active media
59479
     *         group changes.
59480
     * @function onGroupChanged
59481
     */
59482
 
59483
    const onGroupChanged = (type, settings) => () => {
59484
        const {
59485
            segmentLoaders: {
59486
                [type]: segmentLoader,
59487
                main: mainSegmentLoader
59488
            },
59489
            mediaTypes: {
59490
                [type]: mediaType
59491
            }
59492
        } = settings;
59493
        const activeTrack = mediaType.activeTrack();
59494
        const activeGroup = mediaType.getActiveGroup();
59495
        const previousActiveLoader = mediaType.activePlaylistLoader;
59496
        const lastGroup = mediaType.lastGroup_; // the group did not change do nothing
59497
 
59498
        if (activeGroup && lastGroup && activeGroup.id === lastGroup.id) {
59499
            return;
59500
        }
59501
        mediaType.lastGroup_ = activeGroup;
59502
        mediaType.lastTrack_ = activeTrack;
59503
        stopLoaders(segmentLoader, mediaType);
59504
        if (!activeGroup || activeGroup.isMainPlaylist) {
59505
            // there is no group active or active group is a main playlist and won't change
59506
            return;
59507
        }
59508
        if (!activeGroup.playlistLoader) {
59509
            if (previousActiveLoader) {
59510
                // The previous group had a playlist loader but the new active group does not
59511
                // this means we are switching from demuxed to muxed audio. In this case we want to
59512
                // do a destructive reset of the main segment loader and not restart the audio
59513
                // loaders.
59514
                mainSegmentLoader.resetEverything();
59515
            }
59516
            return;
59517
        } // Non-destructive resync
59518
 
59519
        segmentLoader.resyncLoader();
59520
        startLoaders(activeGroup.playlistLoader, mediaType);
59521
    };
59522
    const onGroupChanging = (type, settings) => () => {
59523
        const {
59524
            segmentLoaders: {
59525
                [type]: segmentLoader
59526
            },
59527
            mediaTypes: {
59528
                [type]: mediaType
59529
            }
59530
        } = settings;
59531
        mediaType.lastGroup_ = null;
59532
        segmentLoader.abort();
59533
        segmentLoader.pause();
59534
    };
59535
    /**
59536
     * Returns a function to be called when the media track changes. It performs a
59537
     * destructive reset of the SegmentLoader to ensure we start loading as close to
59538
     * currentTime as possible.
59539
     *
59540
     * @param {string} type
59541
     *        MediaGroup type
59542
     * @param {Object} settings
59543
     *        Object containing required information for media groups
59544
     * @return {Function}
59545
     *         Handler for a destructive reset of SegmentLoader when the active media
59546
     *         track changes.
59547
     * @function onTrackChanged
59548
     */
59549
 
59550
    const onTrackChanged = (type, settings) => () => {
59551
        const {
59552
            mainPlaylistLoader,
59553
            segmentLoaders: {
59554
                [type]: segmentLoader,
59555
                main: mainSegmentLoader
59556
            },
59557
            mediaTypes: {
59558
                [type]: mediaType
59559
            }
59560
        } = settings;
59561
        const activeTrack = mediaType.activeTrack();
59562
        const activeGroup = mediaType.getActiveGroup();
59563
        const previousActiveLoader = mediaType.activePlaylistLoader;
59564
        const lastTrack = mediaType.lastTrack_; // track did not change, do nothing
59565
 
59566
        if (lastTrack && activeTrack && lastTrack.id === activeTrack.id) {
59567
            return;
59568
        }
59569
        mediaType.lastGroup_ = activeGroup;
59570
        mediaType.lastTrack_ = activeTrack;
59571
        stopLoaders(segmentLoader, mediaType);
59572
        if (!activeGroup) {
59573
            // there is no group active so we do not want to restart loaders
59574
            return;
59575
        }
59576
        if (activeGroup.isMainPlaylist) {
59577
            // track did not change, do nothing
59578
            if (!activeTrack || !lastTrack || activeTrack.id === lastTrack.id) {
59579
                return;
59580
            }
59581
            const pc = settings.vhs.playlistController_;
59582
            const newPlaylist = pc.selectPlaylist(); // media will not change do nothing
59583
 
59584
            if (pc.media() === newPlaylist) {
59585
                return;
59586
            }
59587
            mediaType.logger_(`track change. Switching main audio from ${lastTrack.id} to ${activeTrack.id}`);
59588
            mainPlaylistLoader.pause();
59589
            mainSegmentLoader.resetEverything();
59590
            pc.fastQualityChange_(newPlaylist);
59591
            return;
59592
        }
59593
        if (type === 'AUDIO') {
59594
            if (!activeGroup.playlistLoader) {
59595
                // when switching from demuxed audio/video to muxed audio/video (noted by no
59596
                // playlist loader for the audio group), we want to do a destructive reset of the
59597
                // main segment loader and not restart the audio loaders
59598
                mainSegmentLoader.setAudio(true); // don't have to worry about disabling the audio of the audio segment loader since
59599
                // it should be stopped
59600
 
59601
                mainSegmentLoader.resetEverything();
59602
                return;
59603
            } // although the segment loader is an audio segment loader, call the setAudio
59604
            // function to ensure it is prepared to re-append the init segment (or handle other
59605
            // config changes)
59606
 
59607
            segmentLoader.setAudio(true);
59608
            mainSegmentLoader.setAudio(false);
59609
        }
59610
        if (previousActiveLoader === activeGroup.playlistLoader) {
59611
            // Nothing has actually changed. This can happen because track change events can fire
59612
            // multiple times for a "single" change. One for enabling the new active track, and
59613
            // one for disabling the track that was active
59614
            startLoaders(activeGroup.playlistLoader, mediaType);
59615
            return;
59616
        }
59617
        if (segmentLoader.track) {
59618
            // For WebVTT, set the new text track in the segmentloader
59619
            segmentLoader.track(activeTrack);
59620
        } // destructive reset
59621
 
59622
        segmentLoader.resetEverything();
59623
        startLoaders(activeGroup.playlistLoader, mediaType);
59624
    };
59625
    const onError = {
59626
        /**
59627
         * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
59628
         * an error.
59629
         *
59630
         * @param {string} type
59631
         *        MediaGroup type
59632
         * @param {Object} settings
59633
         *        Object containing required information for media groups
59634
         * @return {Function}
59635
         *         Error handler. Logs warning (or error if the playlist is excluded) to
59636
         *         console and switches back to default audio track.
59637
         * @function onError.AUDIO
59638
         */
59639
        AUDIO: (type, settings) => () => {
59640
            const {
59641
                mediaTypes: {
59642
                    [type]: mediaType
59643
                },
59644
                excludePlaylist
59645
            } = settings; // switch back to default audio track
59646
 
59647
            const activeTrack = mediaType.activeTrack();
59648
            const activeGroup = mediaType.activeGroup();
59649
            const id = (activeGroup.filter(group => group.default)[0] || activeGroup[0]).id;
59650
            const defaultTrack = mediaType.tracks[id];
59651
            if (activeTrack === defaultTrack) {
59652
                // Default track encountered an error. All we can do now is exclude the current
59653
                // rendition and hope another will switch audio groups
59654
                excludePlaylist({
59655
                    error: {
59656
                        message: 'Problem encountered loading the default audio track.'
59657
                    }
59658
                });
59659
                return;
59660
            }
59661
            videojs.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
59662
            for (const trackId in mediaType.tracks) {
59663
                mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
59664
            }
59665
            mediaType.onTrackChanged();
59666
        },
59667
        /**
59668
         * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
59669
         * an error.
59670
         *
59671
         * @param {string} type
59672
         *        MediaGroup type
59673
         * @param {Object} settings
59674
         *        Object containing required information for media groups
59675
         * @return {Function}
59676
         *         Error handler. Logs warning to console and disables the active subtitle track
59677
         * @function onError.SUBTITLES
59678
         */
59679
        SUBTITLES: (type, settings) => () => {
59680
            const {
59681
                mediaTypes: {
59682
                    [type]: mediaType
59683
                }
59684
            } = settings;
59685
            videojs.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
59686
            const track = mediaType.activeTrack();
59687
            if (track) {
59688
                track.mode = 'disabled';
59689
            }
59690
            mediaType.onTrackChanged();
59691
        }
59692
    };
59693
    const setupListeners = {
59694
        /**
59695
         * Setup event listeners for audio playlist loader
59696
         *
59697
         * @param {string} type
59698
         *        MediaGroup type
59699
         * @param {PlaylistLoader|null} playlistLoader
59700
         *        PlaylistLoader to register listeners on
59701
         * @param {Object} settings
59702
         *        Object containing required information for media groups
59703
         * @function setupListeners.AUDIO
59704
         */
59705
        AUDIO: (type, playlistLoader, settings) => {
59706
            if (!playlistLoader) {
59707
                // no playlist loader means audio will be muxed with the video
59708
                return;
59709
            }
59710
            const {
59711
                tech,
59712
                requestOptions,
59713
                segmentLoaders: {
59714
                    [type]: segmentLoader
59715
                }
59716
            } = settings;
59717
            playlistLoader.on('loadedmetadata', () => {
59718
                const media = playlistLoader.media();
59719
                segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload
59720
                // permits, start downloading segments
59721
 
59722
                if (!tech.paused() || media.endList && tech.preload() !== 'none') {
59723
                    segmentLoader.load();
59724
                }
59725
            });
59726
            playlistLoader.on('loadedplaylist', () => {
59727
                segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
59728
 
59729
                if (!tech.paused()) {
59730
                    segmentLoader.load();
59731
                }
59732
            });
59733
            playlistLoader.on('error', onError[type](type, settings));
59734
        },
59735
        /**
59736
         * Setup event listeners for subtitle playlist loader
59737
         *
59738
         * @param {string} type
59739
         *        MediaGroup type
59740
         * @param {PlaylistLoader|null} playlistLoader
59741
         *        PlaylistLoader to register listeners on
59742
         * @param {Object} settings
59743
         *        Object containing required information for media groups
59744
         * @function setupListeners.SUBTITLES
59745
         */
59746
        SUBTITLES: (type, playlistLoader, settings) => {
59747
            const {
59748
                tech,
59749
                requestOptions,
59750
                segmentLoaders: {
59751
                    [type]: segmentLoader
59752
                },
59753
                mediaTypes: {
59754
                    [type]: mediaType
59755
                }
59756
            } = settings;
59757
            playlistLoader.on('loadedmetadata', () => {
59758
                const media = playlistLoader.media();
59759
                segmentLoader.playlist(media, requestOptions);
59760
                segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload
59761
                // permits, start downloading segments
59762
 
59763
                if (!tech.paused() || media.endList && tech.preload() !== 'none') {
59764
                    segmentLoader.load();
59765
                }
59766
            });
59767
            playlistLoader.on('loadedplaylist', () => {
59768
                segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
59769
 
59770
                if (!tech.paused()) {
59771
                    segmentLoader.load();
59772
                }
59773
            });
59774
            playlistLoader.on('error', onError[type](type, settings));
59775
        }
59776
    };
59777
    const initialize = {
59778
        /**
59779
         * Setup PlaylistLoaders and AudioTracks for the audio groups
59780
         *
59781
         * @param {string} type
59782
         *        MediaGroup type
59783
         * @param {Object} settings
59784
         *        Object containing required information for media groups
59785
         * @function initialize.AUDIO
59786
         */
59787
        'AUDIO': (type, settings) => {
59788
            const {
59789
                vhs,
59790
                sourceType,
59791
                segmentLoaders: {
59792
                    [type]: segmentLoader
59793
                },
59794
                requestOptions,
59795
                main: {
59796
                    mediaGroups
59797
                },
59798
                mediaTypes: {
59799
                    [type]: {
59800
                        groups,
59801
                        tracks,
59802
                        logger_
59803
                    }
59804
                },
59805
                mainPlaylistLoader
59806
            } = settings;
59807
            const audioOnlyMain = isAudioOnly(mainPlaylistLoader.main); // force a default if we have none
59808
 
59809
            if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
59810
                mediaGroups[type] = {
59811
                    main: {
59812
                        default: {
59813
                            default: true
59814
                        }
59815
                    }
59816
                };
59817
                if (audioOnlyMain) {
59818
                    mediaGroups[type].main.default.playlists = mainPlaylistLoader.main.playlists;
59819
                }
59820
            }
59821
            for (const groupId in mediaGroups[type]) {
59822
                if (!groups[groupId]) {
59823
                    groups[groupId] = [];
59824
                }
59825
                for (const variantLabel in mediaGroups[type][groupId]) {
59826
                    let properties = mediaGroups[type][groupId][variantLabel];
59827
                    let playlistLoader;
59828
                    if (audioOnlyMain) {
59829
                        logger_(`AUDIO group '${groupId}' label '${variantLabel}' is a main playlist`);
59830
                        properties.isMainPlaylist = true;
59831
                        playlistLoader = null; // if vhs-json was provided as the source, and the media playlist was resolved,
59832
                        // use the resolved media playlist object
59833
                    } else if (sourceType === 'vhs-json' && properties.playlists) {
59834
                        playlistLoader = new PlaylistLoader(properties.playlists[0], vhs, requestOptions);
59835
                    } else if (properties.resolvedUri) {
59836
                        playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions); // TODO: dash isn't the only type with properties.playlists
59837
                        // should we even have properties.playlists in this check.
59838
                    } else if (properties.playlists && sourceType === 'dash') {
59839
                        playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, mainPlaylistLoader);
59840
                    } else {
59841
                        // no resolvedUri means the audio is muxed with the video when using this
59842
                        // audio track
59843
                        playlistLoader = null;
59844
                    }
59845
                    properties = merge({
59846
                        id: variantLabel,
59847
                        playlistLoader
59848
                    }, properties);
59849
                    setupListeners[type](type, properties.playlistLoader, settings);
59850
                    groups[groupId].push(properties);
59851
                    if (typeof tracks[variantLabel] === 'undefined') {
59852
                        const track = new videojs.AudioTrack({
59853
                            id: variantLabel,
59854
                            kind: audioTrackKind_(properties),
59855
                            enabled: false,
59856
                            language: properties.language,
59857
                            default: properties.default,
59858
                            label: variantLabel
59859
                        });
59860
                        tracks[variantLabel] = track;
59861
                    }
59862
                }
59863
            } // setup single error event handler for the segment loader
59864
 
59865
            segmentLoader.on('error', onError[type](type, settings));
59866
        },
59867
        /**
59868
         * Setup PlaylistLoaders and TextTracks for the subtitle groups
59869
         *
59870
         * @param {string} type
59871
         *        MediaGroup type
59872
         * @param {Object} settings
59873
         *        Object containing required information for media groups
59874
         * @function initialize.SUBTITLES
59875
         */
59876
        'SUBTITLES': (type, settings) => {
59877
            const {
59878
                tech,
59879
                vhs,
59880
                sourceType,
59881
                segmentLoaders: {
59882
                    [type]: segmentLoader
59883
                },
59884
                requestOptions,
59885
                main: {
59886
                    mediaGroups
59887
                },
59888
                mediaTypes: {
59889
                    [type]: {
59890
                        groups,
59891
                        tracks
59892
                    }
59893
                },
59894
                mainPlaylistLoader
59895
            } = settings;
59896
            for (const groupId in mediaGroups[type]) {
59897
                if (!groups[groupId]) {
59898
                    groups[groupId] = [];
59899
                }
59900
                for (const variantLabel in mediaGroups[type][groupId]) {
59901
                    if (!vhs.options_.useForcedSubtitles && mediaGroups[type][groupId][variantLabel].forced) {
59902
                        // Subtitle playlists with the forced attribute are not selectable in Safari.
59903
                        // According to Apple's HLS Authoring Specification:
59904
                        //   If content has forced subtitles and regular subtitles in a given language,
59905
                        //   the regular subtitles track in that language MUST contain both the forced
59906
                        //   subtitles and the regular subtitles for that language.
59907
                        // Because of this requirement and that Safari does not add forced subtitles,
59908
                        // forced subtitles are skipped here to maintain consistent experience across
59909
                        // all platforms
59910
                        continue;
59911
                    }
59912
                    let properties = mediaGroups[type][groupId][variantLabel];
59913
                    let playlistLoader;
59914
                    if (sourceType === 'hls') {
59915
                        playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions);
59916
                    } else if (sourceType === 'dash') {
59917
                        const playlists = properties.playlists.filter(p => p.excludeUntil !== Infinity);
59918
                        if (!playlists.length) {
59919
                            return;
59920
                        }
59921
                        playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, mainPlaylistLoader);
59922
                    } else if (sourceType === 'vhs-json') {
59923
                        playlistLoader = new PlaylistLoader(
59924
                            // if the vhs-json object included the media playlist, use the media playlist
59925
                            // as provided, otherwise use the resolved URI to load the playlist
59926
                            properties.playlists ? properties.playlists[0] : properties.resolvedUri, vhs, requestOptions);
59927
                    }
59928
                    properties = merge({
59929
                        id: variantLabel,
59930
                        playlistLoader
59931
                    }, properties);
59932
                    setupListeners[type](type, properties.playlistLoader, settings);
59933
                    groups[groupId].push(properties);
59934
                    if (typeof tracks[variantLabel] === 'undefined') {
59935
                        const track = tech.addRemoteTextTrack({
59936
                            id: variantLabel,
59937
                            kind: 'subtitles',
59938
                            default: properties.default && properties.autoselect,
59939
                            language: properties.language,
59940
                            label: variantLabel
59941
                        }, false).track;
59942
                        tracks[variantLabel] = track;
59943
                    }
59944
                }
59945
            } // setup single error event handler for the segment loader
59946
 
59947
            segmentLoader.on('error', onError[type](type, settings));
59948
        },
59949
        /**
59950
         * Setup TextTracks for the closed-caption groups
59951
         *
59952
         * @param {String} type
59953
         *        MediaGroup type
59954
         * @param {Object} settings
59955
         *        Object containing required information for media groups
59956
         * @function initialize['CLOSED-CAPTIONS']
59957
         */
59958
        'CLOSED-CAPTIONS': (type, settings) => {
59959
            const {
59960
                tech,
59961
                main: {
59962
                    mediaGroups
59963
                },
59964
                mediaTypes: {
59965
                    [type]: {
59966
                        groups,
59967
                        tracks
59968
                    }
59969
                }
59970
            } = settings;
59971
            for (const groupId in mediaGroups[type]) {
59972
                if (!groups[groupId]) {
59973
                    groups[groupId] = [];
59974
                }
59975
                for (const variantLabel in mediaGroups[type][groupId]) {
59976
                    const properties = mediaGroups[type][groupId][variantLabel]; // Look for either 608 (CCn) or 708 (SERVICEn) caption services
59977
 
59978
                    if (!/^(?:CC|SERVICE)/.test(properties.instreamId)) {
59979
                        continue;
59980
                    }
59981
                    const captionServices = tech.options_.vhs && tech.options_.vhs.captionServices || {};
59982
                    let newProps = {
59983
                        label: variantLabel,
59984
                        language: properties.language,
59985
                        instreamId: properties.instreamId,
59986
                        default: properties.default && properties.autoselect
59987
                    };
59988
                    if (captionServices[newProps.instreamId]) {
59989
                        newProps = merge(newProps, captionServices[newProps.instreamId]);
59990
                    }
59991
                    if (newProps.default === undefined) {
59992
                        delete newProps.default;
59993
                    } // No PlaylistLoader is required for Closed-Captions because the captions are
59994
                    // embedded within the video stream
59995
 
59996
                    groups[groupId].push(merge({
59997
                        id: variantLabel
59998
                    }, properties));
59999
                    if (typeof tracks[variantLabel] === 'undefined') {
60000
                        const track = tech.addRemoteTextTrack({
60001
                            id: newProps.instreamId,
60002
                            kind: 'captions',
60003
                            default: newProps.default,
60004
                            language: newProps.language,
60005
                            label: newProps.label
60006
                        }, false).track;
60007
                        tracks[variantLabel] = track;
60008
                    }
60009
                }
60010
            }
60011
        }
60012
    };
60013
    const groupMatch = (list, media) => {
60014
        for (let i = 0; i < list.length; i++) {
60015
            if (playlistMatch(media, list[i])) {
60016
                return true;
60017
            }
60018
            if (list[i].playlists && groupMatch(list[i].playlists, media)) {
60019
                return true;
60020
            }
60021
        }
60022
        return false;
60023
    };
60024
    /**
60025
     * Returns a function used to get the active group of the provided type
60026
     *
60027
     * @param {string} type
60028
     *        MediaGroup type
60029
     * @param {Object} settings
60030
     *        Object containing required information for media groups
60031
     * @return {Function}
60032
     *         Function that returns the active media group for the provided type. Takes an
60033
     *         optional parameter {TextTrack} track. If no track is provided, a list of all
60034
     *         variants in the group, otherwise the variant corresponding to the provided
60035
     *         track is returned.
60036
     * @function activeGroup
60037
     */
60038
 
60039
    const activeGroup = (type, settings) => track => {
60040
        const {
60041
            mainPlaylistLoader,
60042
            mediaTypes: {
60043
                [type]: {
60044
                    groups
60045
                }
60046
            }
60047
        } = settings;
60048
        const media = mainPlaylistLoader.media();
60049
        if (!media) {
60050
            return null;
60051
        }
60052
        let variants = null; // set to variants to main media active group
60053
 
60054
        if (media.attributes[type]) {
60055
            variants = groups[media.attributes[type]];
60056
        }
60057
        const groupKeys = Object.keys(groups);
60058
        if (!variants) {
60059
            // find the mainPlaylistLoader media
60060
            // that is in a media group if we are dealing
60061
            // with audio only
60062
            if (type === 'AUDIO' && groupKeys.length > 1 && isAudioOnly(settings.main)) {
60063
                for (let i = 0; i < groupKeys.length; i++) {
60064
                    const groupPropertyList = groups[groupKeys[i]];
60065
                    if (groupMatch(groupPropertyList, media)) {
60066
                        variants = groupPropertyList;
60067
                        break;
60068
                    }
60069
                } // use the main group if it exists
60070
            } else if (groups.main) {
60071
                variants = groups.main; // only one group, use that one
60072
            } else if (groupKeys.length === 1) {
60073
                variants = groups[groupKeys[0]];
60074
            }
60075
        }
60076
        if (typeof track === 'undefined') {
60077
            return variants;
60078
        }
60079
        if (track === null || !variants) {
60080
            // An active track was specified so a corresponding group is expected. track === null
60081
            // means no track is currently active so there is no corresponding group
60082
            return null;
60083
        }
60084
        return variants.filter(props => props.id === track.id)[0] || null;
60085
    };
60086
    const activeTrack = {
60087
        /**
60088
         * Returns a function used to get the active track of type provided
60089
         *
60090
         * @param {string} type
60091
         *        MediaGroup type
60092
         * @param {Object} settings
60093
         *        Object containing required information for media groups
60094
         * @return {Function}
60095
         *         Function that returns the active media track for the provided type. Returns
60096
         *         null if no track is active
60097
         * @function activeTrack.AUDIO
60098
         */
60099
        AUDIO: (type, settings) => () => {
60100
            const {
60101
                mediaTypes: {
60102
                    [type]: {
60103
                        tracks
60104
                    }
60105
                }
60106
            } = settings;
60107
            for (const id in tracks) {
60108
                if (tracks[id].enabled) {
60109
                    return tracks[id];
60110
                }
60111
            }
60112
            return null;
60113
        },
60114
        /**
60115
         * Returns a function used to get the active track of type provided
60116
         *
60117
         * @param {string} type
60118
         *        MediaGroup type
60119
         * @param {Object} settings
60120
         *        Object containing required information for media groups
60121
         * @return {Function}
60122
         *         Function that returns the active media track for the provided type. Returns
60123
         *         null if no track is active
60124
         * @function activeTrack.SUBTITLES
60125
         */
60126
        SUBTITLES: (type, settings) => () => {
60127
            const {
60128
                mediaTypes: {
60129
                    [type]: {
60130
                        tracks
60131
                    }
60132
                }
60133
            } = settings;
60134
            for (const id in tracks) {
60135
                if (tracks[id].mode === 'showing' || tracks[id].mode === 'hidden') {
60136
                    return tracks[id];
60137
                }
60138
            }
60139
            return null;
60140
        }
60141
    };
60142
    const getActiveGroup = (type, {
60143
        mediaTypes
60144
    }) => () => {
60145
        const activeTrack_ = mediaTypes[type].activeTrack();
60146
        if (!activeTrack_) {
60147
            return null;
60148
        }
60149
        return mediaTypes[type].activeGroup(activeTrack_);
60150
    };
60151
    /**
60152
     * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
60153
     * Closed-Captions) specified in the main manifest.
60154
     *
60155
     * @param {Object} settings
60156
     *        Object containing required information for setting up the media groups
60157
     * @param {Tech} settings.tech
60158
     *        The tech of the player
60159
     * @param {Object} settings.requestOptions
60160
     *        XHR request options used by the segment loaders
60161
     * @param {PlaylistLoader} settings.mainPlaylistLoader
60162
     *        PlaylistLoader for the main source
60163
     * @param {VhsHandler} settings.vhs
60164
     *        VHS SourceHandler
60165
     * @param {Object} settings.main
60166
     *        The parsed main manifest
60167
     * @param {Object} settings.mediaTypes
60168
     *        Object to store the loaders, tracks, and utility methods for each media type
60169
     * @param {Function} settings.excludePlaylist
60170
     *        Excludes the current rendition and forces a rendition switch.
60171
     * @function setupMediaGroups
60172
     */
60173
 
60174
    const setupMediaGroups = settings => {
60175
        ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(type => {
60176
            initialize[type](type, settings);
60177
        });
60178
        const {
60179
            mediaTypes,
60180
            mainPlaylistLoader,
60181
            tech,
60182
            vhs,
60183
            segmentLoaders: {
60184
                ['AUDIO']: audioSegmentLoader,
60185
                main: mainSegmentLoader
60186
            }
60187
        } = settings; // setup active group and track getters and change event handlers
60188
 
60189
        ['AUDIO', 'SUBTITLES'].forEach(type => {
60190
            mediaTypes[type].activeGroup = activeGroup(type, settings);
60191
            mediaTypes[type].activeTrack = activeTrack[type](type, settings);
60192
            mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
60193
            mediaTypes[type].onGroupChanging = onGroupChanging(type, settings);
60194
            mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
60195
            mediaTypes[type].getActiveGroup = getActiveGroup(type, settings);
60196
        }); // DO NOT enable the default subtitle or caption track.
60197
        // DO enable the default audio track
60198
 
60199
        const audioGroup = mediaTypes.AUDIO.activeGroup();
60200
        if (audioGroup) {
60201
            const groupId = (audioGroup.filter(group => group.default)[0] || audioGroup[0]).id;
60202
            mediaTypes.AUDIO.tracks[groupId].enabled = true;
60203
            mediaTypes.AUDIO.onGroupChanged();
60204
            mediaTypes.AUDIO.onTrackChanged();
60205
            const activeAudioGroup = mediaTypes.AUDIO.getActiveGroup(); // a similar check for handling setAudio on each loader is run again each time the
60206
            // track is changed, but needs to be handled here since the track may not be considered
60207
            // changed on the first call to onTrackChanged
60208
 
60209
            if (!activeAudioGroup.playlistLoader) {
60210
                // either audio is muxed with video or the stream is audio only
60211
                mainSegmentLoader.setAudio(true);
60212
            } else {
60213
                // audio is demuxed
60214
                mainSegmentLoader.setAudio(false);
60215
                audioSegmentLoader.setAudio(true);
60216
            }
60217
        }
60218
        mainPlaylistLoader.on('mediachange', () => {
60219
            ['AUDIO', 'SUBTITLES'].forEach(type => mediaTypes[type].onGroupChanged());
60220
        });
60221
        mainPlaylistLoader.on('mediachanging', () => {
60222
            ['AUDIO', 'SUBTITLES'].forEach(type => mediaTypes[type].onGroupChanging());
60223
        }); // custom audio track change event handler for usage event
60224
 
60225
        const onAudioTrackChanged = () => {
60226
            mediaTypes.AUDIO.onTrackChanged();
60227
            tech.trigger({
60228
                type: 'usage',
60229
                name: 'vhs-audio-change'
60230
            });
60231
        };
60232
        tech.audioTracks().addEventListener('change', onAudioTrackChanged);
60233
        tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
60234
        vhs.on('dispose', () => {
60235
            tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
60236
            tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
60237
        }); // clear existing audio tracks and add the ones we just created
60238
 
60239
        tech.clearTracks('audio');
60240
        for (const id in mediaTypes.AUDIO.tracks) {
60241
            tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
60242
        }
60243
    };
60244
    /**
60245
     * Creates skeleton object used to store the loaders, tracks, and utility methods for each
60246
     * media type
60247
     *
60248
     * @return {Object}
60249
     *         Object to store the loaders, tracks, and utility methods for each media type
60250
     * @function createMediaTypes
60251
     */
60252
 
60253
    const createMediaTypes = () => {
60254
        const mediaTypes = {};
60255
        ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(type => {
60256
            mediaTypes[type] = {
60257
                groups: {},
60258
                tracks: {},
60259
                activePlaylistLoader: null,
60260
                activeGroup: noop,
60261
                activeTrack: noop,
60262
                getActiveGroup: noop,
60263
                onGroupChanged: noop,
60264
                onTrackChanged: noop,
60265
                lastTrack_: null,
60266
                logger_: logger(`MediaGroups[${type}]`)
60267
            };
60268
        });
60269
        return mediaTypes;
60270
    };
60271
 
60272
    /**
60273
     * A utility class for setting properties and maintaining the state of the content steering manifest.
60274
     *
60275
     * Content Steering manifest format:
60276
     * VERSION: number (required) currently only version 1 is supported.
60277
     * TTL: number in seconds (optional) until the next content steering manifest reload.
60278
     * RELOAD-URI: string (optional) uri to fetch the next content steering manifest.
60279
     * SERVICE-LOCATION-PRIORITY or PATHWAY-PRIORITY a non empty array of unique string values.
60280
     * PATHWAY-CLONES: array (optional) (HLS only) pathway clone objects to copy from other playlists.
60281
     */
60282
 
60283
    class SteeringManifest {
60284
        constructor() {
60285
            this.priority_ = [];
60286
            this.pathwayClones_ = new Map();
60287
        }
60288
        set version(number) {
60289
            // Only version 1 is currently supported for both DASH and HLS.
60290
            if (number === 1) {
60291
                this.version_ = number;
60292
            }
60293
        }
60294
        set ttl(seconds) {
60295
            // TTL = time-to-live, default = 300 seconds.
60296
            this.ttl_ = seconds || 300;
60297
        }
60298
        set reloadUri(uri) {
60299
            if (uri) {
60300
                // reload URI can be relative to the previous reloadUri.
60301
                this.reloadUri_ = resolveUrl(this.reloadUri_, uri);
60302
            }
60303
        }
60304
        set priority(array) {
60305
            // priority must be non-empty and unique values.
60306
            if (array && array.length) {
60307
                this.priority_ = array;
60308
            }
60309
        }
60310
        set pathwayClones(array) {
60311
            // pathwayClones must be non-empty.
60312
            if (array && array.length) {
60313
                this.pathwayClones_ = new Map(array.map(clone => [clone.ID, clone]));
60314
            }
60315
        }
60316
        get version() {
60317
            return this.version_;
60318
        }
60319
        get ttl() {
60320
            return this.ttl_;
60321
        }
60322
        get reloadUri() {
60323
            return this.reloadUri_;
60324
        }
60325
        get priority() {
60326
            return this.priority_;
60327
        }
60328
        get pathwayClones() {
60329
            return this.pathwayClones_;
60330
        }
60331
    }
60332
    /**
60333
     * This class represents a content steering manifest and associated state. See both HLS and DASH specifications.
60334
     * HLS: https://developer.apple.com/streaming/HLSContentSteeringSpecification.pdf and
60335
     * https://datatracker.ietf.org/doc/draft-pantos-hls-rfc8216bis/ section 4.4.6.6.
60336
     * DASH: https://dashif.org/docs/DASH-IF-CTS-00XX-Content-Steering-Community-Review.pdf
60337
     *
60338
     * @param {function} xhr for making a network request from the browser.
60339
     * @param {function} bandwidth for fetching the current bandwidth from the main segment loader.
60340
     */
60341
 
60342
    class ContentSteeringController extends videojs.EventTarget {
60343
        constructor(xhr, bandwidth) {
60344
            super();
60345
            this.currentPathway = null;
60346
            this.defaultPathway = null;
60347
            this.queryBeforeStart = false;
60348
            this.availablePathways_ = new Set();
60349
            this.steeringManifest = new SteeringManifest();
60350
            this.proxyServerUrl_ = null;
60351
            this.manifestType_ = null;
60352
            this.ttlTimeout_ = null;
60353
            this.request_ = null;
60354
            this.currentPathwayClones = new Map();
60355
            this.nextPathwayClones = new Map();
60356
            this.excludedSteeringManifestURLs = new Set();
60357
            this.logger_ = logger('Content Steering');
60358
            this.xhr_ = xhr;
60359
            this.getBandwidth_ = bandwidth;
60360
        }
60361
        /**
60362
         * Assigns the content steering tag properties to the steering controller
60363
         *
60364
         * @param {string} baseUrl the baseURL from the main manifest for resolving the steering manifest url
60365
         * @param {Object} steeringTag the content steering tag from the main manifest
60366
         */
60367
 
60368
        assignTagProperties(baseUrl, steeringTag) {
60369
            this.manifestType_ = steeringTag.serverUri ? 'HLS' : 'DASH'; // serverUri is HLS serverURL is DASH
60370
 
60371
            const steeringUri = steeringTag.serverUri || steeringTag.serverURL;
60372
            if (!steeringUri) {
60373
                this.logger_(`steering manifest URL is ${steeringUri}, cannot request steering manifest.`);
60374
                this.trigger('error');
60375
                return;
60376
            } // Content steering manifests can be encoded as a data URI. We can decode, parse and return early if that's the case.
60377
 
60378
            if (steeringUri.startsWith('data:')) {
60379
                this.decodeDataUriManifest_(steeringUri.substring(steeringUri.indexOf(',') + 1));
60380
                return;
60381
            } // reloadUri is the resolution of the main manifest URL and steering URL.
60382
 
60383
            this.steeringManifest.reloadUri = resolveUrl(baseUrl, steeringUri); // pathwayId is HLS defaultServiceLocation is DASH
60384
 
60385
            this.defaultPathway = steeringTag.pathwayId || steeringTag.defaultServiceLocation; // currently only DASH supports the following properties on <ContentSteering> tags.
60386
 
60387
            this.queryBeforeStart = steeringTag.queryBeforeStart;
60388
            this.proxyServerUrl_ = steeringTag.proxyServerURL; // trigger a steering event if we have a pathway from the content steering tag.
60389
            // this tells VHS which segment pathway to start with.
60390
            // If queryBeforeStart is true we need to wait for the steering manifest response.
60391
 
60392
            if (this.defaultPathway && !this.queryBeforeStart) {
60393
                this.trigger('content-steering');
60394
            }
60395
        }
60396
        /**
60397
         * Requests the content steering manifest and parse the response. This should only be called after
60398
         * assignTagProperties was called with a content steering tag.
60399
         *
60400
         * @param {string} initialUri The optional uri to make the request with.
60401
         *    If set, the request should be made with exactly what is passed in this variable.
60402
         *    This scenario should only happen once on initalization.
60403
         */
60404
 
60405
        requestSteeringManifest(initial) {
60406
            const reloadUri = this.steeringManifest.reloadUri;
60407
            if (!reloadUri) {
60408
                return;
60409
            } // We currently don't support passing MPD query parameters directly to the content steering URL as this requires
60410
            // ExtUrlQueryInfo tag support. See the DASH content steering spec section 8.1.
60411
            // This request URI accounts for manifest URIs that have been excluded.
60412
 
60413
            const uri = initial ? reloadUri : this.getRequestURI(reloadUri); // If there are no valid manifest URIs, we should stop content steering.
60414
 
60415
            if (!uri) {
60416
                this.logger_('No valid content steering manifest URIs. Stopping content steering.');
60417
                this.trigger('error');
60418
                this.dispose();
60419
                return;
60420
            }
60421
            this.request_ = this.xhr_({
60422
                uri
60423
            }, (error, errorInfo) => {
60424
                if (error) {
60425
                    // If the client receives HTTP 410 Gone in response to a manifest request,
60426
                    // it MUST NOT issue another request for that URI for the remainder of the
60427
                    // playback session. It MAY continue to use the most-recently obtained set
60428
                    // of Pathways.
60429
                    if (errorInfo.status === 410) {
60430
                        this.logger_(`manifest request 410 ${error}.`);
60431
                        this.logger_(`There will be no more content steering requests to ${uri} this session.`);
60432
                        this.excludedSteeringManifestURLs.add(uri);
60433
                        return;
60434
                    } // If the client receives HTTP 429 Too Many Requests with a Retry-After
60435
                    // header in response to a manifest request, it SHOULD wait until the time
60436
                    // specified by the Retry-After header to reissue the request.
60437
 
60438
                    if (errorInfo.status === 429) {
60439
                        const retrySeconds = errorInfo.responseHeaders['retry-after'];
60440
                        this.logger_(`manifest request 429 ${error}.`);
60441
                        this.logger_(`content steering will retry in ${retrySeconds} seconds.`);
60442
                        this.startTTLTimeout_(parseInt(retrySeconds, 10));
60443
                        return;
60444
                    } // If the Steering Manifest cannot be loaded and parsed correctly, the
60445
                    // client SHOULD continue to use the previous values and attempt to reload
60446
                    // it after waiting for the previously-specified TTL (or 5 minutes if
60447
                    // none).
60448
 
60449
                    this.logger_(`manifest failed to load ${error}.`);
60450
                    this.startTTLTimeout_();
60451
                    return;
60452
                }
60453
                const steeringManifestJson = JSON.parse(this.request_.responseText);
60454
                this.assignSteeringProperties_(steeringManifestJson);
60455
                this.startTTLTimeout_();
60456
            });
60457
        }
60458
        /**
60459
         * Set the proxy server URL and add the steering manifest url as a URI encoded parameter.
60460
         *
60461
         * @param {string} steeringUrl the steering manifest url
60462
         * @return the steering manifest url to a proxy server with all parameters set
60463
         */
60464
 
60465
        setProxyServerUrl_(steeringUrl) {
60466
            const steeringUrlObject = new window.URL(steeringUrl);
60467
            const proxyServerUrlObject = new window.URL(this.proxyServerUrl_);
60468
            proxyServerUrlObject.searchParams.set('url', encodeURI(steeringUrlObject.toString()));
60469
            return this.setSteeringParams_(proxyServerUrlObject.toString());
60470
        }
60471
        /**
60472
         * Decodes and parses the data uri encoded steering manifest
60473
         *
60474
         * @param {string} dataUri the data uri to be decoded and parsed.
60475
         */
60476
 
60477
        decodeDataUriManifest_(dataUri) {
60478
            const steeringManifestJson = JSON.parse(window.atob(dataUri));
60479
            this.assignSteeringProperties_(steeringManifestJson);
60480
        }
60481
        /**
60482
         * Set the HLS or DASH content steering manifest request query parameters. For example:
60483
         * _HLS_pathway="<CURRENT-PATHWAY-ID>" and _HLS_throughput=<THROUGHPUT>
60484
         * _DASH_pathway and _DASH_throughput
60485
         *
60486
         * @param {string} uri to add content steering server parameters to.
60487
         * @return a new uri as a string with the added steering query parameters.
60488
         */
60489
 
60490
        setSteeringParams_(url) {
60491
            const urlObject = new window.URL(url);
60492
            const path = this.getPathway();
60493
            const networkThroughput = this.getBandwidth_();
60494
            if (path) {
60495
                const pathwayKey = `_${this.manifestType_}_pathway`;
60496
                urlObject.searchParams.set(pathwayKey, path);
60497
            }
60498
            if (networkThroughput) {
60499
                const throughputKey = `_${this.manifestType_}_throughput`;
60500
                urlObject.searchParams.set(throughputKey, networkThroughput);
60501
            }
60502
            return urlObject.toString();
60503
        }
60504
        /**
60505
         * Assigns the current steering manifest properties and to the SteeringManifest object
60506
         *
60507
         * @param {Object} steeringJson the raw JSON steering manifest
60508
         */
60509
 
60510
        assignSteeringProperties_(steeringJson) {
60511
            this.steeringManifest.version = steeringJson.VERSION;
60512
            if (!this.steeringManifest.version) {
60513
                this.logger_(`manifest version is ${steeringJson.VERSION}, which is not supported.`);
60514
                this.trigger('error');
60515
                return;
60516
            }
60517
            this.steeringManifest.ttl = steeringJson.TTL;
60518
            this.steeringManifest.reloadUri = steeringJson['RELOAD-URI']; // HLS = PATHWAY-PRIORITY required. DASH = SERVICE-LOCATION-PRIORITY optional
60519
 
60520
            this.steeringManifest.priority = steeringJson['PATHWAY-PRIORITY'] || steeringJson['SERVICE-LOCATION-PRIORITY']; // Pathway clones to be created/updated in HLS.
60521
            // See section 7.2 https://datatracker.ietf.org/doc/draft-pantos-hls-rfc8216bis/
60522
 
60523
            this.steeringManifest.pathwayClones = steeringJson['PATHWAY-CLONES'];
60524
            this.nextPathwayClones = this.steeringManifest.pathwayClones; // 1. apply first pathway from the array.
60525
            // 2. if first pathway doesn't exist in manifest, try next pathway.
60526
            //    a. if all pathways are exhausted, ignore the steering manifest priority.
60527
            // 3. if segments fail from an established pathway, try all variants/renditions, then exclude the failed pathway.
60528
            //    a. exclude a pathway for a minimum of the last TTL duration. Meaning, from the next steering response,
60529
            //       the excluded pathway will be ignored.
60530
            //       See excludePathway usage in excludePlaylist().
60531
            // If there are no available pathways, we need to stop content steering.
60532
 
60533
            if (!this.availablePathways_.size) {
60534
                this.logger_('There are no available pathways for content steering. Ending content steering.');
60535
                this.trigger('error');
60536
                this.dispose();
60537
            }
60538
            const chooseNextPathway = pathwaysByPriority => {
60539
                for (const path of pathwaysByPriority) {
60540
                    if (this.availablePathways_.has(path)) {
60541
                        return path;
60542
                    }
60543
                } // If no pathway matches, ignore the manifest and choose the first available.
60544
 
60545
                return [...this.availablePathways_][0];
60546
            };
60547
            const nextPathway = chooseNextPathway(this.steeringManifest.priority);
60548
            if (this.currentPathway !== nextPathway) {
60549
                this.currentPathway = nextPathway;
60550
                this.trigger('content-steering');
60551
            }
60552
        }
60553
        /**
60554
         * Returns the pathway to use for steering decisions
60555
         *
60556
         * @return {string} returns the current pathway or the default
60557
         */
60558
 
60559
        getPathway() {
60560
            return this.currentPathway || this.defaultPathway;
60561
        }
60562
        /**
60563
         * Chooses the manifest request URI based on proxy URIs and server URLs.
60564
         * Also accounts for exclusion on certain manifest URIs.
60565
         *
60566
         * @param {string} reloadUri the base uri before parameters
60567
         *
60568
         * @return {string} the final URI for the request to the manifest server.
60569
         */
60570
 
60571
        getRequestURI(reloadUri) {
60572
            if (!reloadUri) {
60573
                return null;
60574
            }
60575
            const isExcluded = uri => this.excludedSteeringManifestURLs.has(uri);
60576
            if (this.proxyServerUrl_) {
60577
                const proxyURI = this.setProxyServerUrl_(reloadUri);
60578
                if (!isExcluded(proxyURI)) {
60579
                    return proxyURI;
60580
                }
60581
            }
60582
            const steeringURI = this.setSteeringParams_(reloadUri);
60583
            if (!isExcluded(steeringURI)) {
60584
                return steeringURI;
60585
            } // Return nothing if all valid manifest URIs are excluded.
60586
 
60587
            return null;
60588
        }
60589
        /**
60590
         * Start the timeout for re-requesting the steering manifest at the TTL interval.
60591
         *
60592
         * @param {number} ttl time in seconds of the timeout. Defaults to the
60593
         *        ttl interval in the steering manifest
60594
         */
60595
 
60596
        startTTLTimeout_(ttl = this.steeringManifest.ttl) {
60597
            // 300 (5 minutes) is the default value.
60598
            const ttlMS = ttl * 1000;
60599
            this.ttlTimeout_ = window.setTimeout(() => {
60600
                this.requestSteeringManifest();
60601
            }, ttlMS);
60602
        }
60603
        /**
60604
         * Clear the TTL timeout if necessary.
60605
         */
60606
 
60607
        clearTTLTimeout_() {
60608
            window.clearTimeout(this.ttlTimeout_);
60609
            this.ttlTimeout_ = null;
60610
        }
60611
        /**
60612
         * aborts any current steering xhr and sets the current request object to null
60613
         */
60614
 
60615
        abort() {
60616
            if (this.request_) {
60617
                this.request_.abort();
60618
            }
60619
            this.request_ = null;
60620
        }
60621
        /**
60622
         * aborts steering requests clears the ttl timeout and resets all properties.
60623
         */
60624
 
60625
        dispose() {
60626
            this.off('content-steering');
60627
            this.off('error');
60628
            this.abort();
60629
            this.clearTTLTimeout_();
60630
            this.currentPathway = null;
60631
            this.defaultPathway = null;
60632
            this.queryBeforeStart = null;
60633
            this.proxyServerUrl_ = null;
60634
            this.manifestType_ = null;
60635
            this.ttlTimeout_ = null;
60636
            this.request_ = null;
60637
            this.excludedSteeringManifestURLs = new Set();
60638
            this.availablePathways_ = new Set();
60639
            this.steeringManifest = new SteeringManifest();
60640
        }
60641
        /**
60642
         * adds a pathway to the available pathways set
60643
         *
60644
         * @param {string} pathway the pathway string to add
60645
         */
60646
 
60647
        addAvailablePathway(pathway) {
60648
            if (pathway) {
60649
                this.availablePathways_.add(pathway);
60650
            }
60651
        }
60652
        /**
60653
         * Clears all pathways from the available pathways set
60654
         */
60655
 
60656
        clearAvailablePathways() {
60657
            this.availablePathways_.clear();
60658
        }
60659
        /**
60660
         * Removes a pathway from the available pathways set.
60661
         */
60662
 
60663
        excludePathway(pathway) {
60664
            return this.availablePathways_.delete(pathway);
60665
        }
60666
        /**
60667
         * Checks the refreshed DASH manifest content steering tag for changes.
60668
         *
60669
         * @param {string} baseURL new steering tag on DASH manifest refresh
60670
         * @param {Object} newTag the new tag to check for changes
60671
         * @return a true or false whether the new tag has different values
60672
         */
60673
 
60674
        didDASHTagChange(baseURL, newTag) {
60675
            return !newTag && this.steeringManifest.reloadUri || newTag && (resolveUrl(baseURL, newTag.serverURL) !== this.steeringManifest.reloadUri || newTag.defaultServiceLocation !== this.defaultPathway || newTag.queryBeforeStart !== this.queryBeforeStart || newTag.proxyServerURL !== this.proxyServerUrl_);
60676
        }
60677
        getAvailablePathways() {
60678
            return this.availablePathways_;
60679
        }
60680
    }
60681
 
60682
    /**
60683
     * @file playlist-controller.js
60684
     */
60685
    const ABORT_EARLY_EXCLUSION_SECONDS = 10;
60686
    let Vhs$1; // SegmentLoader stats that need to have each loader's
60687
    // values summed to calculate the final value
60688
 
60689
    const loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred', 'mediaAppends'];
60690
    const sumLoaderStat = function (stat) {
60691
        return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
60692
    };
60693
    const shouldSwitchToMedia = function ({
60694
                                              currentPlaylist,
60695
                                              buffered,
60696
                                              currentTime,
60697
                                              nextPlaylist,
60698
                                              bufferLowWaterLine,
60699
                                              bufferHighWaterLine,
60700
                                              duration,
60701
                                              bufferBasedABR,
60702
                                              log
60703
                                          }) {
60704
        // we have no other playlist to switch to
60705
        if (!nextPlaylist) {
60706
            videojs.log.warn('We received no playlist to switch to. Please check your stream.');
60707
            return false;
60708
        }
60709
        const sharedLogLine = `allowing switch ${currentPlaylist && currentPlaylist.id || 'null'} -> ${nextPlaylist.id}`;
60710
        if (!currentPlaylist) {
60711
            log(`${sharedLogLine} as current playlist is not set`);
60712
            return true;
60713
        } // no need to switch if playlist is the same
60714
 
60715
        if (nextPlaylist.id === currentPlaylist.id) {
60716
            return false;
60717
        } // determine if current time is in a buffered range.
60718
 
60719
        const isBuffered = Boolean(findRange(buffered, currentTime).length); // If the playlist is live, then we want to not take low water line into account.
60720
        // This is because in LIVE, the player plays 3 segments from the end of the
60721
        // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
60722
        // in those segments, a viewer will never experience a rendition upswitch.
60723
 
60724
        if (!currentPlaylist.endList) {
60725
            // For LLHLS live streams, don't switch renditions before playback has started, as it almost
60726
            // doubles the time to first playback.
60727
            if (!isBuffered && typeof currentPlaylist.partTargetDuration === 'number') {
60728
                log(`not ${sharedLogLine} as current playlist is live llhls, but currentTime isn't in buffered.`);
60729
                return false;
60730
            }
60731
            log(`${sharedLogLine} as current playlist is live`);
60732
            return true;
60733
        }
60734
        const forwardBuffer = timeAheadOf(buffered, currentTime);
60735
        const maxBufferLowWaterLine = bufferBasedABR ? Config.EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE : Config.MAX_BUFFER_LOW_WATER_LINE; // For the same reason as LIVE, we ignore the low water line when the VOD
60736
        // duration is below the max potential low water line
60737
 
60738
        if (duration < maxBufferLowWaterLine) {
60739
            log(`${sharedLogLine} as duration < max low water line (${duration} < ${maxBufferLowWaterLine})`);
60740
            return true;
60741
        }
60742
        const nextBandwidth = nextPlaylist.attributes.BANDWIDTH;
60743
        const currBandwidth = currentPlaylist.attributes.BANDWIDTH; // when switching down, if our buffer is lower than the high water line,
60744
        // we can switch down
60745
 
60746
        if (nextBandwidth < currBandwidth && (!bufferBasedABR || forwardBuffer < bufferHighWaterLine)) {
60747
            let logLine = `${sharedLogLine} as next bandwidth < current bandwidth (${nextBandwidth} < ${currBandwidth})`;
60748
            if (bufferBasedABR) {
60749
                logLine += ` and forwardBuffer < bufferHighWaterLine (${forwardBuffer} < ${bufferHighWaterLine})`;
60750
            }
60751
            log(logLine);
60752
            return true;
60753
        } // and if our buffer is higher than the low water line,
60754
        // we can switch up
60755
 
60756
        if ((!bufferBasedABR || nextBandwidth > currBandwidth) && forwardBuffer >= bufferLowWaterLine) {
60757
            let logLine = `${sharedLogLine} as forwardBuffer >= bufferLowWaterLine (${forwardBuffer} >= ${bufferLowWaterLine})`;
60758
            if (bufferBasedABR) {
60759
                logLine += ` and next bandwidth > current bandwidth (${nextBandwidth} > ${currBandwidth})`;
60760
            }
60761
            log(logLine);
60762
            return true;
60763
        }
60764
        log(`not ${sharedLogLine} as no switching criteria met`);
60765
        return false;
60766
    };
60767
    /**
60768
     * the main playlist controller controller all interactons
60769
     * between playlists and segmentloaders. At this time this mainly
60770
     * involves a main playlist and a series of audio playlists
60771
     * if they are available
60772
     *
60773
     * @class PlaylistController
60774
     * @extends videojs.EventTarget
60775
     */
60776
 
60777
    class PlaylistController extends videojs.EventTarget {
60778
        constructor(options) {
60779
            super();
60780
            const {
60781
                src,
60782
                withCredentials,
60783
                tech,
60784
                bandwidth,
60785
                externVhs,
60786
                useCueTags,
60787
                playlistExclusionDuration,
60788
                enableLowInitialPlaylist,
60789
                sourceType,
60790
                cacheEncryptionKeys,
60791
                bufferBasedABR,
60792
                leastPixelDiffSelector,
60793
                captionServices
60794
            } = options;
60795
            if (!src) {
60796
                throw new Error('A non-empty playlist URL or JSON manifest string is required');
60797
            }
60798
            let {
60799
                maxPlaylistRetries
60800
            } = options;
60801
            if (maxPlaylistRetries === null || typeof maxPlaylistRetries === 'undefined') {
60802
                maxPlaylistRetries = Infinity;
60803
            }
60804
            Vhs$1 = externVhs;
60805
            this.bufferBasedABR = Boolean(bufferBasedABR);
60806
            this.leastPixelDiffSelector = Boolean(leastPixelDiffSelector);
60807
            this.withCredentials = withCredentials;
60808
            this.tech_ = tech;
60809
            this.vhs_ = tech.vhs;
60810
            this.sourceType_ = sourceType;
60811
            this.useCueTags_ = useCueTags;
60812
            this.playlistExclusionDuration = playlistExclusionDuration;
60813
            this.maxPlaylistRetries = maxPlaylistRetries;
60814
            this.enableLowInitialPlaylist = enableLowInitialPlaylist;
60815
            if (this.useCueTags_) {
60816
                this.cueTagsTrack_ = this.tech_.addTextTrack('metadata', 'ad-cues');
60817
                this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
60818
            }
60819
            this.requestOptions_ = {
60820
                withCredentials,
60821
                maxPlaylistRetries,
60822
                timeout: null
60823
            };
60824
            this.on('error', this.pauseLoading);
60825
            this.mediaTypes_ = createMediaTypes();
60826
            this.mediaSource = new window.MediaSource();
60827
            this.handleDurationChange_ = this.handleDurationChange_.bind(this);
60828
            this.handleSourceOpen_ = this.handleSourceOpen_.bind(this);
60829
            this.handleSourceEnded_ = this.handleSourceEnded_.bind(this);
60830
            this.mediaSource.addEventListener('durationchange', this.handleDurationChange_); // load the media source into the player
60831
 
60832
            this.mediaSource.addEventListener('sourceopen', this.handleSourceOpen_);
60833
            this.mediaSource.addEventListener('sourceended', this.handleSourceEnded_); // we don't have to handle sourceclose since dispose will handle termination of
60834
            // everything, and the MediaSource should not be detached without a proper disposal
60835
 
60836
            this.seekable_ = createTimeRanges();
60837
            this.hasPlayed_ = false;
60838
            this.syncController_ = new SyncController(options);
60839
            this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
60840
                kind: 'metadata',
60841
                label: 'segment-metadata'
60842
            }, false).track;
60843
            this.decrypter_ = new Decrypter();
60844
            this.sourceUpdater_ = new SourceUpdater(this.mediaSource);
60845
            this.inbandTextTracks_ = {};
60846
            this.timelineChangeController_ = new TimelineChangeController();
60847
            this.keyStatusMap_ = new Map();
60848
            const segmentLoaderSettings = {
60849
                vhs: this.vhs_,
60850
                parse708captions: options.parse708captions,
60851
                useDtsForTimestampOffset: options.useDtsForTimestampOffset,
60852
                captionServices,
60853
                mediaSource: this.mediaSource,
60854
                currentTime: this.tech_.currentTime.bind(this.tech_),
60855
                seekable: () => this.seekable(),
60856
                seeking: () => this.tech_.seeking(),
60857
                duration: () => this.duration(),
60858
                hasPlayed: () => this.hasPlayed_,
60859
                goalBufferLength: () => this.goalBufferLength(),
60860
                bandwidth,
60861
                syncController: this.syncController_,
60862
                decrypter: this.decrypter_,
60863
                sourceType: this.sourceType_,
60864
                inbandTextTracks: this.inbandTextTracks_,
60865
                cacheEncryptionKeys,
60866
                sourceUpdater: this.sourceUpdater_,
60867
                timelineChangeController: this.timelineChangeController_,
60868
                exactManifestTimings: options.exactManifestTimings,
60869
                addMetadataToTextTrack: this.addMetadataToTextTrack.bind(this)
60870
            }; // The source type check not only determines whether a special DASH playlist loader
60871
            // should be used, but also covers the case where the provided src is a vhs-json
60872
            // manifest object (instead of a URL). In the case of vhs-json, the default
60873
            // PlaylistLoader should be used.
60874
 
60875
            this.mainPlaylistLoader_ = this.sourceType_ === 'dash' ? new DashPlaylistLoader(src, this.vhs_, merge(this.requestOptions_, {
60876
                addMetadataToTextTrack: this.addMetadataToTextTrack.bind(this)
60877
            })) : new PlaylistLoader(src, this.vhs_, merge(this.requestOptions_, {
60878
                addDateRangesToTextTrack: this.addDateRangesToTextTrack_.bind(this)
60879
            }));
60880
            this.setupMainPlaylistLoaderListeners_(); // setup segment loaders
60881
            // combined audio/video or just video when alternate audio track is selected
60882
 
60883
            this.mainSegmentLoader_ = new SegmentLoader(merge(segmentLoaderSettings, {
60884
                segmentMetadataTrack: this.segmentMetadataTrack_,
60885
                loaderType: 'main'
60886
            }), options); // alternate audio track
60887
 
60888
            this.audioSegmentLoader_ = new SegmentLoader(merge(segmentLoaderSettings, {
60889
                loaderType: 'audio'
60890
            }), options);
60891
            this.subtitleSegmentLoader_ = new VTTSegmentLoader(merge(segmentLoaderSettings, {
60892
                loaderType: 'vtt',
60893
                featuresNativeTextTracks: this.tech_.featuresNativeTextTracks,
60894
                loadVttJs: () => new Promise((resolve, reject) => {
60895
                    function onLoad() {
60896
                        tech.off('vttjserror', onError);
60897
                        resolve();
60898
                    }
60899
                    function onError() {
60900
                        tech.off('vttjsloaded', onLoad);
60901
                        reject();
60902
                    }
60903
                    tech.one('vttjsloaded', onLoad);
60904
                    tech.one('vttjserror', onError); // safe to call multiple times, script will be loaded only once:
60905
 
60906
                    tech.addWebVttScript_();
60907
                })
60908
            }), options);
60909
            const getBandwidth = () => {
60910
                return this.mainSegmentLoader_.bandwidth;
60911
            };
60912
            this.contentSteeringController_ = new ContentSteeringController(this.vhs_.xhr, getBandwidth);
60913
            this.setupSegmentLoaderListeners_();
60914
            if (this.bufferBasedABR) {
60915
                this.mainPlaylistLoader_.one('loadedplaylist', () => this.startABRTimer_());
60916
                this.tech_.on('pause', () => this.stopABRTimer_());
60917
                this.tech_.on('play', () => this.startABRTimer_());
60918
            } // Create SegmentLoader stat-getters
60919
            // mediaRequests_
60920
            // mediaRequestsAborted_
60921
            // mediaRequestsTimedout_
60922
            // mediaRequestsErrored_
60923
            // mediaTransferDuration_
60924
            // mediaBytesTransferred_
60925
            // mediaAppends_
60926
 
60927
            loaderStats.forEach(stat => {
60928
                this[stat + '_'] = sumLoaderStat.bind(this, stat);
60929
            });
60930
            this.logger_ = logger('pc');
60931
            this.triggeredFmp4Usage = false;
60932
            if (this.tech_.preload() === 'none') {
60933
                this.loadOnPlay_ = () => {
60934
                    this.loadOnPlay_ = null;
60935
                    this.mainPlaylistLoader_.load();
60936
                };
60937
                this.tech_.one('play', this.loadOnPlay_);
60938
            } else {
60939
                this.mainPlaylistLoader_.load();
60940
            }
60941
            this.timeToLoadedData__ = -1;
60942
            this.mainAppendsToLoadedData__ = -1;
60943
            this.audioAppendsToLoadedData__ = -1;
60944
            const event = this.tech_.preload() === 'none' ? 'play' : 'loadstart'; // start the first frame timer on loadstart or play (for preload none)
60945
 
60946
            this.tech_.one(event, () => {
60947
                const timeToLoadedDataStart = Date.now();
60948
                this.tech_.one('loadeddata', () => {
60949
                    this.timeToLoadedData__ = Date.now() - timeToLoadedDataStart;
60950
                    this.mainAppendsToLoadedData__ = this.mainSegmentLoader_.mediaAppends;
60951
                    this.audioAppendsToLoadedData__ = this.audioSegmentLoader_.mediaAppends;
60952
                });
60953
            });
60954
        }
60955
        mainAppendsToLoadedData_() {
60956
            return this.mainAppendsToLoadedData__;
60957
        }
60958
        audioAppendsToLoadedData_() {
60959
            return this.audioAppendsToLoadedData__;
60960
        }
60961
        appendsToLoadedData_() {
60962
            const main = this.mainAppendsToLoadedData_();
60963
            const audio = this.audioAppendsToLoadedData_();
60964
            if (main === -1 || audio === -1) {
60965
                return -1;
60966
            }
60967
            return main + audio;
60968
        }
60969
        timeToLoadedData_() {
60970
            return this.timeToLoadedData__;
60971
        }
60972
        /**
60973
         * Run selectPlaylist and switch to the new playlist if we should
60974
         *
60975
         * @param {string} [reason=abr] a reason for why the ABR check is made
60976
         * @private
60977
         */
60978
 
60979
        checkABR_(reason = 'abr') {
60980
            const nextPlaylist = this.selectPlaylist();
60981
            if (nextPlaylist && this.shouldSwitchToMedia_(nextPlaylist)) {
60982
                this.switchMedia_(nextPlaylist, reason);
60983
            }
60984
        }
60985
        switchMedia_(playlist, cause, delay) {
60986
            const oldMedia = this.media();
60987
            const oldId = oldMedia && (oldMedia.id || oldMedia.uri);
60988
            const newId = playlist && (playlist.id || playlist.uri);
60989
            if (oldId && oldId !== newId) {
60990
                this.logger_(`switch media ${oldId} -> ${newId} from ${cause}`);
60991
                this.tech_.trigger({
60992
                    type: 'usage',
60993
                    name: `vhs-rendition-change-${cause}`
60994
                });
60995
            }
60996
            this.mainPlaylistLoader_.media(playlist, delay);
60997
        }
60998
        /**
60999
         * A function that ensures we switch our playlists inside of `mediaTypes`
61000
         * to match the current `serviceLocation` provided by the contentSteering controller.
61001
         * We want to check media types of `AUDIO`, `SUBTITLES`, and `CLOSED-CAPTIONS`.
61002
         *
61003
         * This should only be called on a DASH playback scenario while using content steering.
61004
         * This is necessary due to differences in how media in HLS manifests are generally tied to
61005
         * a video playlist, where in DASH that is not always the case.
61006
         */
61007
 
61008
        switchMediaForDASHContentSteering_() {
61009
            ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(type => {
61010
                const mediaType = this.mediaTypes_[type];
61011
                const activeGroup = mediaType ? mediaType.activeGroup() : null;
61012
                const pathway = this.contentSteeringController_.getPathway();
61013
                if (activeGroup && pathway) {
61014
                    // activeGroup can be an array or a single group
61015
                    const mediaPlaylists = activeGroup.length ? activeGroup[0].playlists : activeGroup.playlists;
61016
                    const dashMediaPlaylists = mediaPlaylists.filter(p => p.attributes.serviceLocation === pathway); // Switch the current active playlist to the correct CDN
61017
 
61018
                    if (dashMediaPlaylists.length) {
61019
                        this.mediaTypes_[type].activePlaylistLoader.media(dashMediaPlaylists[0]);
61020
                    }
61021
                }
61022
            });
61023
        }
61024
        /**
61025
         * Start a timer that periodically calls checkABR_
61026
         *
61027
         * @private
61028
         */
61029
 
61030
        startABRTimer_() {
61031
            this.stopABRTimer_();
61032
            this.abrTimer_ = window.setInterval(() => this.checkABR_(), 250);
61033
        }
61034
        /**
61035
         * Stop the timer that periodically calls checkABR_
61036
         *
61037
         * @private
61038
         */
61039
 
61040
        stopABRTimer_() {
61041
            // if we're scrubbing, we don't need to pause.
61042
            // This getter will be added to Video.js in version 7.11.
61043
            if (this.tech_.scrubbing && this.tech_.scrubbing()) {
61044
                return;
61045
            }
61046
            window.clearInterval(this.abrTimer_);
61047
            this.abrTimer_ = null;
61048
        }
61049
        /**
61050
         * Get a list of playlists for the currently selected audio playlist
61051
         *
61052
         * @return {Array} the array of audio playlists
61053
         */
61054
 
61055
        getAudioTrackPlaylists_() {
61056
            const main = this.main();
61057
            const defaultPlaylists = main && main.playlists || []; // if we don't have any audio groups then we can only
61058
            // assume that the audio tracks are contained in main
61059
            // playlist array, use that or an empty array.
61060
 
61061
            if (!main || !main.mediaGroups || !main.mediaGroups.AUDIO) {
61062
                return defaultPlaylists;
61063
            }
61064
            const AUDIO = main.mediaGroups.AUDIO;
61065
            const groupKeys = Object.keys(AUDIO);
61066
            let track; // get the current active track
61067
 
61068
            if (Object.keys(this.mediaTypes_.AUDIO.groups).length) {
61069
                track = this.mediaTypes_.AUDIO.activeTrack(); // or get the default track from main if mediaTypes_ isn't setup yet
61070
            } else {
61071
                // default group is `main` or just the first group.
61072
                const defaultGroup = AUDIO.main || groupKeys.length && AUDIO[groupKeys[0]];
61073
                for (const label in defaultGroup) {
61074
                    if (defaultGroup[label].default) {
61075
                        track = {
61076
                            label
61077
                        };
61078
                        break;
61079
                    }
61080
                }
61081
            } // no active track no playlists.
61082
 
61083
            if (!track) {
61084
                return defaultPlaylists;
61085
            }
61086
            const playlists = []; // get all of the playlists that are possible for the
61087
            // active track.
61088
 
61089
            for (const group in AUDIO) {
61090
                if (AUDIO[group][track.label]) {
61091
                    const properties = AUDIO[group][track.label];
61092
                    if (properties.playlists && properties.playlists.length) {
61093
                        playlists.push.apply(playlists, properties.playlists);
61094
                    } else if (properties.uri) {
61095
                        playlists.push(properties);
61096
                    } else if (main.playlists.length) {
61097
                        // if an audio group does not have a uri
61098
                        // see if we have main playlists that use it as a group.
61099
                        // if we do then add those to the playlists list.
61100
                        for (let i = 0; i < main.playlists.length; i++) {
61101
                            const playlist = main.playlists[i];
61102
                            if (playlist.attributes && playlist.attributes.AUDIO && playlist.attributes.AUDIO === group) {
61103
                                playlists.push(playlist);
61104
                            }
61105
                        }
61106
                    }
61107
                }
61108
            }
61109
            if (!playlists.length) {
61110
                return defaultPlaylists;
61111
            }
61112
            return playlists;
61113
        }
61114
        /**
61115
         * Register event handlers on the main playlist loader. A helper
61116
         * function for construction time.
61117
         *
61118
         * @private
61119
         */
61120
 
61121
        setupMainPlaylistLoaderListeners_() {
61122
            this.mainPlaylistLoader_.on('loadedmetadata', () => {
61123
                const media = this.mainPlaylistLoader_.media();
61124
                const requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
61125
                // timeout the request.
61126
 
61127
                if (isLowestEnabledRendition(this.mainPlaylistLoader_.main, this.mainPlaylistLoader_.media())) {
61128
                    this.requestOptions_.timeout = 0;
61129
                } else {
61130
                    this.requestOptions_.timeout = requestTimeout;
61131
                } // if this isn't a live video and preload permits, start
61132
                // downloading segments
61133
 
61134
                if (media.endList && this.tech_.preload() !== 'none') {
61135
                    this.mainSegmentLoader_.playlist(media, this.requestOptions_);
61136
                    this.mainSegmentLoader_.load();
61137
                }
61138
                setupMediaGroups({
61139
                    sourceType: this.sourceType_,
61140
                    segmentLoaders: {
61141
                        AUDIO: this.audioSegmentLoader_,
61142
                        SUBTITLES: this.subtitleSegmentLoader_,
61143
                        main: this.mainSegmentLoader_
61144
                    },
61145
                    tech: this.tech_,
61146
                    requestOptions: this.requestOptions_,
61147
                    mainPlaylistLoader: this.mainPlaylistLoader_,
61148
                    vhs: this.vhs_,
61149
                    main: this.main(),
61150
                    mediaTypes: this.mediaTypes_,
61151
                    excludePlaylist: this.excludePlaylist.bind(this)
61152
                });
61153
                this.triggerPresenceUsage_(this.main(), media);
61154
                this.setupFirstPlay();
61155
                if (!this.mediaTypes_.AUDIO.activePlaylistLoader || this.mediaTypes_.AUDIO.activePlaylistLoader.media()) {
61156
                    this.trigger('selectedinitialmedia');
61157
                } else {
61158
                    // We must wait for the active audio playlist loader to
61159
                    // finish setting up before triggering this event so the
61160
                    // representations API and EME setup is correct
61161
                    this.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', () => {
61162
                        this.trigger('selectedinitialmedia');
61163
                    });
61164
                }
61165
            });
61166
            this.mainPlaylistLoader_.on('loadedplaylist', () => {
61167
                if (this.loadOnPlay_) {
61168
                    this.tech_.off('play', this.loadOnPlay_);
61169
                }
61170
                let updatedPlaylist = this.mainPlaylistLoader_.media();
61171
                if (!updatedPlaylist) {
61172
                    // Add content steering listeners on first load and init.
61173
                    this.attachContentSteeringListeners_();
61174
                    this.initContentSteeringController_(); // exclude any variants that are not supported by the browser before selecting
61175
                    // an initial media as the playlist selectors do not consider browser support
61176
 
61177
                    this.excludeUnsupportedVariants_();
61178
                    let selectedMedia;
61179
                    if (this.enableLowInitialPlaylist) {
61180
                        selectedMedia = this.selectInitialPlaylist();
61181
                    }
61182
                    if (!selectedMedia) {
61183
                        selectedMedia = this.selectPlaylist();
61184
                    }
61185
                    if (!selectedMedia || !this.shouldSwitchToMedia_(selectedMedia)) {
61186
                        return;
61187
                    }
61188
                    this.initialMedia_ = selectedMedia;
61189
                    this.switchMedia_(this.initialMedia_, 'initial'); // Under the standard case where a source URL is provided, loadedplaylist will
61190
                    // fire again since the playlist will be requested. In the case of vhs-json
61191
                    // (where the manifest object is provided as the source), when the media
61192
                    // playlist's `segments` list is already available, a media playlist won't be
61193
                    // requested, and loadedplaylist won't fire again, so the playlist handler must be
61194
                    // called on its own here.
61195
 
61196
                    const haveJsonSource = this.sourceType_ === 'vhs-json' && this.initialMedia_.segments;
61197
                    if (!haveJsonSource) {
61198
                        return;
61199
                    }
61200
                    updatedPlaylist = this.initialMedia_;
61201
                }
61202
                this.handleUpdatedMediaPlaylist(updatedPlaylist);
61203
            });
61204
            this.mainPlaylistLoader_.on('error', () => {
61205
                const error = this.mainPlaylistLoader_.error;
61206
                this.excludePlaylist({
61207
                    playlistToExclude: error.playlist,
61208
                    error
61209
                });
61210
            });
61211
            this.mainPlaylistLoader_.on('mediachanging', () => {
61212
                this.mainSegmentLoader_.abort();
61213
                this.mainSegmentLoader_.pause();
61214
            });
61215
            this.mainPlaylistLoader_.on('mediachange', () => {
61216
                const media = this.mainPlaylistLoader_.media();
61217
                const requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
61218
                // timeout the request.
61219
 
61220
                if (isLowestEnabledRendition(this.mainPlaylistLoader_.main, this.mainPlaylistLoader_.media())) {
61221
                    this.requestOptions_.timeout = 0;
61222
                } else {
61223
                    this.requestOptions_.timeout = requestTimeout;
61224
                }
61225
                if (this.sourceType_ === 'dash') {
61226
                    // we don't want to re-request the same hls playlist right after it was changed
61227
                    this.mainPlaylistLoader_.load();
61228
                } // TODO: Create a new event on the PlaylistLoader that signals
61229
                // that the segments have changed in some way and use that to
61230
                // update the SegmentLoader instead of doing it twice here and
61231
                // on `loadedplaylist`
61232
 
61233
                this.mainSegmentLoader_.pause();
61234
                this.mainSegmentLoader_.playlist(media, this.requestOptions_);
61235
                if (this.waitingForFastQualityPlaylistReceived_) {
61236
                    this.runFastQualitySwitch_();
61237
                } else {
61238
                    this.mainSegmentLoader_.load();
61239
                }
61240
                this.tech_.trigger({
61241
                    type: 'mediachange',
61242
                    bubbles: true
61243
                });
61244
            });
61245
            this.mainPlaylistLoader_.on('playlistunchanged', () => {
61246
                const updatedPlaylist = this.mainPlaylistLoader_.media(); // ignore unchanged playlists that have already been
61247
                // excluded for not-changing. We likely just have a really slowly updating
61248
                // playlist.
61249
 
61250
                if (updatedPlaylist.lastExcludeReason_ === 'playlist-unchanged') {
61251
                    return;
61252
                }
61253
                const playlistOutdated = this.stuckAtPlaylistEnd_(updatedPlaylist);
61254
                if (playlistOutdated) {
61255
                    // Playlist has stopped updating and we're stuck at its end. Try to
61256
                    // exclude it and switch to another playlist in the hope that that
61257
                    // one is updating (and give the player a chance to re-adjust to the
61258
                    // safe live point).
61259
                    this.excludePlaylist({
61260
                        error: {
61261
                            message: 'Playlist no longer updating.',
61262
                            reason: 'playlist-unchanged'
61263
                        }
61264
                    }); // useful for monitoring QoS
61265
 
61266
                    this.tech_.trigger('playliststuck');
61267
                }
61268
            });
61269
            this.mainPlaylistLoader_.on('renditiondisabled', () => {
61270
                this.tech_.trigger({
61271
                    type: 'usage',
61272
                    name: 'vhs-rendition-disabled'
61273
                });
61274
            });
61275
            this.mainPlaylistLoader_.on('renditionenabled', () => {
61276
                this.tech_.trigger({
61277
                    type: 'usage',
61278
                    name: 'vhs-rendition-enabled'
61279
                });
61280
            });
61281
        }
61282
        /**
61283
         * Given an updated media playlist (whether it was loaded for the first time, or
61284
         * refreshed for live playlists), update any relevant properties and state to reflect
61285
         * changes in the media that should be accounted for (e.g., cues and duration).
61286
         *
61287
         * @param {Object} updatedPlaylist the updated media playlist object
61288
         *
61289
         * @private
61290
         */
61291
 
61292
        handleUpdatedMediaPlaylist(updatedPlaylist) {
61293
            if (this.useCueTags_) {
61294
                this.updateAdCues_(updatedPlaylist);
61295
            } // TODO: Create a new event on the PlaylistLoader that signals
61296
            // that the segments have changed in some way and use that to
61297
            // update the SegmentLoader instead of doing it twice here and
61298
            // on `mediachange`
61299
 
61300
            this.mainSegmentLoader_.pause();
61301
            this.mainSegmentLoader_.playlist(updatedPlaylist, this.requestOptions_);
61302
            if (this.waitingForFastQualityPlaylistReceived_) {
61303
                this.runFastQualitySwitch_();
61304
            }
61305
            this.updateDuration(!updatedPlaylist.endList); // If the player isn't paused, ensure that the segment loader is running,
61306
            // as it is possible that it was temporarily stopped while waiting for
61307
            // a playlist (e.g., in case the playlist errored and we re-requested it).
61308
 
61309
            if (!this.tech_.paused()) {
61310
                this.mainSegmentLoader_.load();
61311
                if (this.audioSegmentLoader_) {
61312
                    this.audioSegmentLoader_.load();
61313
                }
61314
            }
61315
        }
61316
        /**
61317
         * A helper function for triggerring presence usage events once per source
61318
         *
61319
         * @private
61320
         */
61321
 
61322
        triggerPresenceUsage_(main, media) {
61323
            const mediaGroups = main.mediaGroups || {};
61324
            let defaultDemuxed = true;
61325
            const audioGroupKeys = Object.keys(mediaGroups.AUDIO);
61326
            for (const mediaGroup in mediaGroups.AUDIO) {
61327
                for (const label in mediaGroups.AUDIO[mediaGroup]) {
61328
                    const properties = mediaGroups.AUDIO[mediaGroup][label];
61329
                    if (!properties.uri) {
61330
                        defaultDemuxed = false;
61331
                    }
61332
                }
61333
            }
61334
            if (defaultDemuxed) {
61335
                this.tech_.trigger({
61336
                    type: 'usage',
61337
                    name: 'vhs-demuxed'
61338
                });
61339
            }
61340
            if (Object.keys(mediaGroups.SUBTITLES).length) {
61341
                this.tech_.trigger({
61342
                    type: 'usage',
61343
                    name: 'vhs-webvtt'
61344
                });
61345
            }
61346
            if (Vhs$1.Playlist.isAes(media)) {
61347
                this.tech_.trigger({
61348
                    type: 'usage',
61349
                    name: 'vhs-aes'
61350
                });
61351
            }
61352
            if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
61353
                this.tech_.trigger({
61354
                    type: 'usage',
61355
                    name: 'vhs-alternate-audio'
61356
                });
61357
            }
61358
            if (this.useCueTags_) {
61359
                this.tech_.trigger({
61360
                    type: 'usage',
61361
                    name: 'vhs-playlist-cue-tags'
61362
                });
61363
            }
61364
        }
61365
        shouldSwitchToMedia_(nextPlaylist) {
61366
            const currentPlaylist = this.mainPlaylistLoader_.media() || this.mainPlaylistLoader_.pendingMedia_;
61367
            const currentTime = this.tech_.currentTime();
61368
            const bufferLowWaterLine = this.bufferLowWaterLine();
61369
            const bufferHighWaterLine = this.bufferHighWaterLine();
61370
            const buffered = this.tech_.buffered();
61371
            return shouldSwitchToMedia({
61372
                buffered,
61373
                currentTime,
61374
                currentPlaylist,
61375
                nextPlaylist,
61376
                bufferLowWaterLine,
61377
                bufferHighWaterLine,
61378
                duration: this.duration(),
61379
                bufferBasedABR: this.bufferBasedABR,
61380
                log: this.logger_
61381
            });
61382
        }
61383
        /**
61384
         * Register event handlers on the segment loaders. A helper function
61385
         * for construction time.
61386
         *
61387
         * @private
61388
         */
61389
 
61390
        setupSegmentLoaderListeners_() {
61391
            this.mainSegmentLoader_.on('bandwidthupdate', () => {
61392
                // Whether or not buffer based ABR or another ABR is used, on a bandwidth change it's
61393
                // useful to check to see if a rendition switch should be made.
61394
                this.checkABR_('bandwidthupdate');
61395
                this.tech_.trigger('bandwidthupdate');
61396
            });
61397
            this.mainSegmentLoader_.on('timeout', () => {
61398
                if (this.bufferBasedABR) {
61399
                    // If a rendition change is needed, then it would've be done on `bandwidthupdate`.
61400
                    // Here the only consideration is that for buffer based ABR there's no guarantee
61401
                    // of an immediate switch (since the bandwidth is averaged with a timeout
61402
                    // bandwidth value of 1), so force a load on the segment loader to keep it going.
61403
                    this.mainSegmentLoader_.load();
61404
                }
61405
            }); // `progress` events are not reliable enough of a bandwidth measure to trigger buffer
61406
            // based ABR.
61407
 
61408
            if (!this.bufferBasedABR) {
61409
                this.mainSegmentLoader_.on('progress', () => {
61410
                    this.trigger('progress');
61411
                });
61412
            }
61413
            this.mainSegmentLoader_.on('error', () => {
61414
                const error = this.mainSegmentLoader_.error();
61415
                this.excludePlaylist({
61416
                    playlistToExclude: error.playlist,
61417
                    error
61418
                });
61419
            });
61420
            this.mainSegmentLoader_.on('appenderror', () => {
61421
                this.error = this.mainSegmentLoader_.error_;
61422
                this.trigger('error');
61423
            });
61424
            this.mainSegmentLoader_.on('syncinfoupdate', () => {
61425
                this.onSyncInfoUpdate_();
61426
            });
61427
            this.mainSegmentLoader_.on('timestampoffset', () => {
61428
                this.tech_.trigger({
61429
                    type: 'usage',
61430
                    name: 'vhs-timestamp-offset'
61431
                });
61432
            });
61433
            this.audioSegmentLoader_.on('syncinfoupdate', () => {
61434
                this.onSyncInfoUpdate_();
61435
            });
61436
            this.audioSegmentLoader_.on('appenderror', () => {
61437
                this.error = this.audioSegmentLoader_.error_;
61438
                this.trigger('error');
61439
            });
61440
            this.mainSegmentLoader_.on('ended', () => {
61441
                this.logger_('main segment loader ended');
61442
                this.onEndOfStream();
61443
            });
61444
            this.mainSegmentLoader_.on('earlyabort', event => {
61445
                // never try to early abort with the new ABR algorithm
61446
                if (this.bufferBasedABR) {
61447
                    return;
61448
                }
61449
                this.delegateLoaders_('all', ['abort']);
61450
                this.excludePlaylist({
61451
                    error: {
61452
                        message: 'Aborted early because there isn\'t enough bandwidth to complete ' + 'the request without rebuffering.'
61453
                    },
61454
                    playlistExclusionDuration: ABORT_EARLY_EXCLUSION_SECONDS
61455
                });
61456
            });
61457
            const updateCodecs = () => {
61458
                if (!this.sourceUpdater_.hasCreatedSourceBuffers()) {
61459
                    return this.tryToCreateSourceBuffers_();
61460
                }
61461
                const codecs = this.getCodecsOrExclude_(); // no codecs means that the playlist was excluded
61462
 
61463
                if (!codecs) {
61464
                    return;
61465
                }
61466
                this.sourceUpdater_.addOrChangeSourceBuffers(codecs);
61467
            };
61468
            this.mainSegmentLoader_.on('trackinfo', updateCodecs);
61469
            this.audioSegmentLoader_.on('trackinfo', updateCodecs);
61470
            this.mainSegmentLoader_.on('fmp4', () => {
61471
                if (!this.triggeredFmp4Usage) {
61472
                    this.tech_.trigger({
61473
                        type: 'usage',
61474
                        name: 'vhs-fmp4'
61475
                    });
61476
                    this.triggeredFmp4Usage = true;
61477
                }
61478
            });
61479
            this.audioSegmentLoader_.on('fmp4', () => {
61480
                if (!this.triggeredFmp4Usage) {
61481
                    this.tech_.trigger({
61482
                        type: 'usage',
61483
                        name: 'vhs-fmp4'
61484
                    });
61485
                    this.triggeredFmp4Usage = true;
61486
                }
61487
            });
61488
            this.audioSegmentLoader_.on('ended', () => {
61489
                this.logger_('audioSegmentLoader ended');
61490
                this.onEndOfStream();
61491
            });
61492
        }
61493
        mediaSecondsLoaded_() {
61494
            return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
61495
        }
61496
        /**
61497
         * Call load on our SegmentLoaders
61498
         */
61499
 
61500
        load() {
61501
            this.mainSegmentLoader_.load();
61502
            if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
61503
                this.audioSegmentLoader_.load();
61504
            }
61505
            if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
61506
                this.subtitleSegmentLoader_.load();
61507
            }
61508
        }
61509
        /**
61510
         * Re-tune playback quality level for the current player
61511
         * conditions. This method will perform destructive actions like removing
61512
         * already buffered content in order to readjust the currently active
61513
         * playlist quickly. This is good for manual quality changes
61514
         *
61515
         * @private
61516
         */
61517
 
61518
        fastQualityChange_(media = this.selectPlaylist()) {
61519
            if (media && media === this.mainPlaylistLoader_.media()) {
61520
                this.logger_('skipping fastQualityChange because new media is same as old');
61521
                return;
61522
            }
61523
            this.switchMedia_(media, 'fast-quality'); // we would like to avoid race condition when we call fastQuality,
61524
            // reset everything and start loading segments from prev segments instead of new because new playlist is not received yet
61525
 
61526
            this.waitingForFastQualityPlaylistReceived_ = true;
61527
        }
61528
        runFastQualitySwitch_() {
61529
            this.waitingForFastQualityPlaylistReceived_ = false; // Delete all buffered data to allow an immediate quality switch, then seek to give
61530
            // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds
61531
            // ahead was roughly the minimum that will accomplish this across a variety of content
61532
            // in IE and Edge, but seeking in place is sufficient on all other browsers)
61533
            // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/
61534
            // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904
61535
 
61536
            this.mainSegmentLoader_.pause();
61537
            this.mainSegmentLoader_.resetEverything(() => {
61538
                this.tech_.setCurrentTime(this.tech_.currentTime());
61539
            }); // don't need to reset audio as it is reset when media changes
61540
        }
61541
        /**
61542
         * Begin playback.
61543
         */
61544
 
61545
        play() {
61546
            if (this.setupFirstPlay()) {
61547
                return;
61548
            }
61549
            if (this.tech_.ended()) {
61550
                this.tech_.setCurrentTime(0);
61551
            }
61552
            if (this.hasPlayed_) {
61553
                this.load();
61554
            }
61555
            const seekable = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window,
61556
            // seek forward to the live point
61557
 
61558
            if (this.tech_.duration() === Infinity) {
61559
                if (this.tech_.currentTime() < seekable.start(0)) {
61560
                    return this.tech_.setCurrentTime(seekable.end(seekable.length - 1));
61561
                }
61562
            }
61563
        }
61564
        /**
61565
         * Seek to the latest media position if this is a live video and the
61566
         * player and video are loaded and initialized.
61567
         */
61568
 
61569
        setupFirstPlay() {
61570
            const media = this.mainPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play
61571
            //  If 1) there is no active media
61572
            //     2) the player is paused
61573
            //     3) the first play has already been setup
61574
            // then exit early
61575
 
61576
            if (!media || this.tech_.paused() || this.hasPlayed_) {
61577
                return false;
61578
            } // when the video is a live stream and/or has a start time
61579
 
61580
            if (!media.endList || media.start) {
61581
                const seekable = this.seekable();
61582
                if (!seekable.length) {
61583
                    // without a seekable range, the player cannot seek to begin buffering at the
61584
                    // live or start point
61585
                    return false;
61586
                }
61587
                const seekableEnd = seekable.end(0);
61588
                let startPoint = seekableEnd;
61589
                if (media.start) {
61590
                    const offset = media.start.timeOffset;
61591
                    if (offset < 0) {
61592
                        startPoint = Math.max(seekableEnd + offset, seekable.start(0));
61593
                    } else {
61594
                        startPoint = Math.min(seekableEnd, offset);
61595
                    }
61596
                } // trigger firstplay to inform the source handler to ignore the next seek event
61597
 
61598
                this.trigger('firstplay'); // seek to the live point
61599
 
61600
                this.tech_.setCurrentTime(startPoint);
61601
            }
61602
            this.hasPlayed_ = true; // we can begin loading now that everything is ready
61603
 
61604
            this.load();
61605
            return true;
61606
        }
61607
        /**
61608
         * handle the sourceopen event on the MediaSource
61609
         *
61610
         * @private
61611
         */
61612
 
61613
        handleSourceOpen_() {
61614
            // Only attempt to create the source buffer if none already exist.
61615
            // handleSourceOpen is also called when we are "re-opening" a source buffer
61616
            // after `endOfStream` has been called (in response to a seek for instance)
61617
            this.tryToCreateSourceBuffers_(); // if autoplay is enabled, begin playback. This is duplicative of
61618
            // code in video.js but is required because play() must be invoked
61619
            // *after* the media source has opened.
61620
 
61621
            if (this.tech_.autoplay()) {
61622
                const playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request
61623
                // on browsers which return a promise
61624
 
61625
                if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
61626
                    playPromise.then(null, e => {});
61627
                }
61628
            }
61629
            this.trigger('sourceopen');
61630
        }
61631
        /**
61632
         * handle the sourceended event on the MediaSource
61633
         *
61634
         * @private
61635
         */
61636
 
61637
        handleSourceEnded_() {
61638
            if (!this.inbandTextTracks_.metadataTrack_) {
61639
                return;
61640
            }
61641
            const cues = this.inbandTextTracks_.metadataTrack_.cues;
61642
            if (!cues || !cues.length) {
61643
                return;
61644
            }
61645
            const duration = this.duration();
61646
            cues[cues.length - 1].endTime = isNaN(duration) || Math.abs(duration) === Infinity ? Number.MAX_VALUE : duration;
61647
        }
61648
        /**
61649
         * handle the durationchange event on the MediaSource
61650
         *
61651
         * @private
61652
         */
61653
 
61654
        handleDurationChange_() {
61655
            this.tech_.trigger('durationchange');
61656
        }
61657
        /**
61658
         * Calls endOfStream on the media source when all active stream types have called
61659
         * endOfStream
61660
         *
61661
         * @param {string} streamType
61662
         *        Stream type of the segment loader that called endOfStream
61663
         * @private
61664
         */
61665
 
61666
        onEndOfStream() {
61667
            let isEndOfStream = this.mainSegmentLoader_.ended_;
61668
            if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
61669
                const mainMediaInfo = this.mainSegmentLoader_.getCurrentMediaInfo_(); // if the audio playlist loader exists, then alternate audio is active
61670
 
61671
                if (!mainMediaInfo || mainMediaInfo.hasVideo) {
61672
                    // if we do not know if the main segment loader contains video yet or if we
61673
                    // definitively know the main segment loader contains video, then we need to wait
61674
                    // for both main and audio segment loaders to call endOfStream
61675
                    isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
61676
                } else {
61677
                    // otherwise just rely on the audio loader
61678
                    isEndOfStream = this.audioSegmentLoader_.ended_;
61679
                }
61680
            }
61681
            if (!isEndOfStream) {
61682
                return;
61683
            }
61684
            this.stopABRTimer_();
61685
            this.sourceUpdater_.endOfStream();
61686
        }
61687
        /**
61688
         * Check if a playlist has stopped being updated
61689
         *
61690
         * @param {Object} playlist the media playlist object
61691
         * @return {boolean} whether the playlist has stopped being updated or not
61692
         */
61693
 
61694
        stuckAtPlaylistEnd_(playlist) {
61695
            const seekable = this.seekable();
61696
            if (!seekable.length) {
61697
                // playlist doesn't have enough information to determine whether we are stuck
61698
                return false;
61699
            }
61700
            const expired = this.syncController_.getExpiredTime(playlist, this.duration());
61701
            if (expired === null) {
61702
                return false;
61703
            } // does not use the safe live end to calculate playlist end, since we
61704
            // don't want to say we are stuck while there is still content
61705
 
61706
            const absolutePlaylistEnd = Vhs$1.Playlist.playlistEnd(playlist, expired);
61707
            const currentTime = this.tech_.currentTime();
61708
            const buffered = this.tech_.buffered();
61709
            if (!buffered.length) {
61710
                // return true if the playhead reached the absolute end of the playlist
61711
                return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
61712
            }
61713
            const bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute
61714
            // end of playlist
61715
 
61716
            return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA;
61717
        }
61718
        /**
61719
         * Exclude a playlist for a set amount of time, making it unavailable for selection by
61720
         * the rendition selection algorithm, then force a new playlist (rendition) selection.
61721
         *
61722
         * @param {Object=} playlistToExclude
61723
         *                  the playlist to exclude, defaults to the currently selected playlist
61724
         * @param {Object=} error
61725
         *                  an optional error
61726
         * @param {number=} playlistExclusionDuration
61727
         *                  an optional number of seconds to exclude the playlist
61728
         */
61729
 
61730
        excludePlaylist({
61731
                            playlistToExclude = this.mainPlaylistLoader_.media(),
61732
                            error = {},
61733
                            playlistExclusionDuration
61734
                        }) {
61735
            // If the `error` was generated by the playlist loader, it will contain
61736
            // the playlist we were trying to load (but failed) and that should be
61737
            // excluded instead of the currently selected playlist which is likely
61738
            // out-of-date in this scenario
61739
            playlistToExclude = playlistToExclude || this.mainPlaylistLoader_.media();
61740
            playlistExclusionDuration = playlistExclusionDuration || error.playlistExclusionDuration || this.playlistExclusionDuration; // If there is no current playlist, then an error occurred while we were
61741
            // trying to load the main OR while we were disposing of the tech
61742
 
61743
            if (!playlistToExclude) {
61744
                this.error = error;
61745
                if (this.mediaSource.readyState !== 'open') {
61746
                    this.trigger('error');
61747
                } else {
61748
                    this.sourceUpdater_.endOfStream('network');
61749
                }
61750
                return;
61751
            }
61752
            playlistToExclude.playlistErrors_++;
61753
            const playlists = this.mainPlaylistLoader_.main.playlists;
61754
            const enabledPlaylists = playlists.filter(isEnabled);
61755
            const isFinalRendition = enabledPlaylists.length === 1 && enabledPlaylists[0] === playlistToExclude; // Don't exclude the only playlist unless it was excluded
61756
            // forever
61757
 
61758
            if (playlists.length === 1 && playlistExclusionDuration !== Infinity) {
61759
                videojs.log.warn(`Problem encountered with playlist ${playlistToExclude.id}. ` + 'Trying again since it is the only playlist.');
61760
                this.tech_.trigger('retryplaylist'); // if this is a final rendition, we should delay
61761
 
61762
                return this.mainPlaylistLoader_.load(isFinalRendition);
61763
            }
61764
            if (isFinalRendition) {
61765
                // If we're content steering, try other pathways.
61766
                if (this.main().contentSteering) {
61767
                    const pathway = this.pathwayAttribute_(playlistToExclude); // Ignore at least 1 steering manifest refresh.
61768
 
61769
                    const reIncludeDelay = this.contentSteeringController_.steeringManifest.ttl * 1000;
61770
                    this.contentSteeringController_.excludePathway(pathway);
61771
                    this.excludeThenChangePathway_();
61772
                    setTimeout(() => {
61773
                        this.contentSteeringController_.addAvailablePathway(pathway);
61774
                    }, reIncludeDelay);
61775
                    return;
61776
                } // Since we're on the final non-excluded playlist, and we're about to exclude
61777
                // it, instead of erring the player or retrying this playlist, clear out the current
61778
                // exclusion list. This allows other playlists to be attempted in case any have been
61779
                // fixed.
61780
 
61781
                let reincluded = false;
61782
                playlists.forEach(playlist => {
61783
                    // skip current playlist which is about to be excluded
61784
                    if (playlist === playlistToExclude) {
61785
                        return;
61786
                    }
61787
                    const excludeUntil = playlist.excludeUntil; // a playlist cannot be reincluded if it wasn't excluded to begin with.
61788
 
61789
                    if (typeof excludeUntil !== 'undefined' && excludeUntil !== Infinity) {
61790
                        reincluded = true;
61791
                        delete playlist.excludeUntil;
61792
                    }
61793
                });
61794
                if (reincluded) {
61795
                    videojs.log.warn('Removing other playlists from the exclusion list because the last ' + 'rendition is about to be excluded.'); // Technically we are retrying a playlist, in that we are simply retrying a previous
61796
                    // playlist. This is needed for users relying on the retryplaylist event to catch a
61797
                    // case where the player might be stuck and looping through "dead" playlists.
61798
 
61799
                    this.tech_.trigger('retryplaylist');
61800
                }
61801
            } // Exclude this playlist
61802
 
61803
            let excludeUntil;
61804
            if (playlistToExclude.playlistErrors_ > this.maxPlaylistRetries) {
61805
                excludeUntil = Infinity;
61806
            } else {
61807
                excludeUntil = Date.now() + playlistExclusionDuration * 1000;
61808
            }
61809
            playlistToExclude.excludeUntil = excludeUntil;
61810
            if (error.reason) {
61811
                playlistToExclude.lastExcludeReason_ = error.reason;
61812
            }
61813
            this.tech_.trigger('excludeplaylist');
61814
            this.tech_.trigger({
61815
                type: 'usage',
61816
                name: 'vhs-rendition-excluded'
61817
            }); // TODO: only load a new playlist if we're excluding the current playlist
61818
            // If this function was called with a playlist that's not the current active playlist
61819
            // (e.g., media().id !== playlistToExclude.id),
61820
            // then a new playlist should not be selected and loaded, as there's nothing wrong with the current playlist.
61821
 
61822
            const nextPlaylist = this.selectPlaylist();
61823
            if (!nextPlaylist) {
61824
                this.error = 'Playback cannot continue. No available working or supported playlists.';
61825
                this.trigger('error');
61826
                return;
61827
            }
61828
            const logFn = error.internal ? this.logger_ : videojs.log.warn;
61829
            const errorMessage = error.message ? ' ' + error.message : '';
61830
            logFn(`${error.internal ? 'Internal problem' : 'Problem'} encountered with playlist ${playlistToExclude.id}.` + `${errorMessage} Switching to playlist ${nextPlaylist.id}.`); // if audio group changed reset audio loaders
61831
 
61832
            if (nextPlaylist.attributes.AUDIO !== playlistToExclude.attributes.AUDIO) {
61833
                this.delegateLoaders_('audio', ['abort', 'pause']);
61834
            } // if subtitle group changed reset subtitle loaders
61835
 
61836
            if (nextPlaylist.attributes.SUBTITLES !== playlistToExclude.attributes.SUBTITLES) {
61837
                this.delegateLoaders_('subtitle', ['abort', 'pause']);
61838
            }
61839
            this.delegateLoaders_('main', ['abort', 'pause']);
61840
            const delayDuration = nextPlaylist.targetDuration / 2 * 1000 || 5 * 1000;
61841
            const shouldDelay = typeof nextPlaylist.lastRequest === 'number' && Date.now() - nextPlaylist.lastRequest <= delayDuration; // delay if it's a final rendition or if the last refresh is sooner than half targetDuration
61842
 
61843
            return this.switchMedia_(nextPlaylist, 'exclude', isFinalRendition || shouldDelay);
61844
        }
61845
        /**
61846
         * Pause all segment/playlist loaders
61847
         */
61848
 
61849
        pauseLoading() {
61850
            this.delegateLoaders_('all', ['abort', 'pause']);
61851
            this.stopABRTimer_();
61852
        }
61853
        /**
61854
         * Call a set of functions in order on playlist loaders, segment loaders,
61855
         * or both types of loaders.
61856
         *
61857
         * @param {string} filter
61858
         *        Filter loaders that should call fnNames using a string. Can be:
61859
         *        * all - run on all loaders
61860
         *        * audio - run on all audio loaders
61861
         *        * subtitle - run on all subtitle loaders
61862
         *        * main - run on the main loaders
61863
         *
61864
         * @param {Array|string} fnNames
61865
         *        A string or array of function names to call.
61866
         */
61867
 
61868
        delegateLoaders_(filter, fnNames) {
61869
            const loaders = [];
61870
            const dontFilterPlaylist = filter === 'all';
61871
            if (dontFilterPlaylist || filter === 'main') {
61872
                loaders.push(this.mainPlaylistLoader_);
61873
            }
61874
            const mediaTypes = [];
61875
            if (dontFilterPlaylist || filter === 'audio') {
61876
                mediaTypes.push('AUDIO');
61877
            }
61878
            if (dontFilterPlaylist || filter === 'subtitle') {
61879
                mediaTypes.push('CLOSED-CAPTIONS');
61880
                mediaTypes.push('SUBTITLES');
61881
            }
61882
            mediaTypes.forEach(mediaType => {
61883
                const loader = this.mediaTypes_[mediaType] && this.mediaTypes_[mediaType].activePlaylistLoader;
61884
                if (loader) {
61885
                    loaders.push(loader);
61886
                }
61887
            });
61888
            ['main', 'audio', 'subtitle'].forEach(name => {
61889
                const loader = this[`${name}SegmentLoader_`];
61890
                if (loader && (filter === name || filter === 'all')) {
61891
                    loaders.push(loader);
61892
                }
61893
            });
61894
            loaders.forEach(loader => fnNames.forEach(fnName => {
61895
                if (typeof loader[fnName] === 'function') {
61896
                    loader[fnName]();
61897
                }
61898
            }));
61899
        }
61900
        /**
61901
         * set the current time on all segment loaders
61902
         *
61903
         * @param {TimeRange} currentTime the current time to set
61904
         * @return {TimeRange} the current time
61905
         */
61906
 
61907
        setCurrentTime(currentTime) {
61908
            const buffered = findRange(this.tech_.buffered(), currentTime);
61909
            if (!(this.mainPlaylistLoader_ && this.mainPlaylistLoader_.media())) {
61910
                // return immediately if the metadata is not ready yet
61911
                return 0;
61912
            } // it's clearly an edge-case but don't thrown an error if asked to
61913
            // seek within an empty playlist
61914
 
61915
            if (!this.mainPlaylistLoader_.media().segments) {
61916
                return 0;
61917
            } // if the seek location is already buffered, continue buffering as usual
61918
 
61919
            if (buffered && buffered.length) {
61920
                return currentTime;
61921
            } // cancel outstanding requests so we begin buffering at the new
61922
            // location
61923
 
61924
            this.mainSegmentLoader_.pause();
61925
            this.mainSegmentLoader_.resetEverything();
61926
            if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
61927
                this.audioSegmentLoader_.pause();
61928
                this.audioSegmentLoader_.resetEverything();
61929
            }
61930
            if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
61931
                this.subtitleSegmentLoader_.pause();
61932
                this.subtitleSegmentLoader_.resetEverything();
61933
            } // start segment loader loading in case they are paused
61934
 
61935
            this.load();
61936
        }
61937
        /**
61938
         * get the current duration
61939
         *
61940
         * @return {TimeRange} the duration
61941
         */
61942
 
61943
        duration() {
61944
            if (!this.mainPlaylistLoader_) {
61945
                return 0;
61946
            }
61947
            const media = this.mainPlaylistLoader_.media();
61948
            if (!media) {
61949
                // no playlists loaded yet, so can't determine a duration
61950
                return 0;
61951
            } // Don't rely on the media source for duration in the case of a live playlist since
61952
            // setting the native MediaSource's duration to infinity ends up with consequences to
61953
            // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details.
61954
            //
61955
            // This is resolved in the spec by https://github.com/w3c/media-source/pull/92,
61956
            // however, few browsers have support for setLiveSeekableRange()
61957
            // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange
61958
            //
61959
            // Until a time when the duration of the media source can be set to infinity, and a
61960
            // seekable range specified across browsers, just return Infinity.
61961
 
61962
            if (!media.endList) {
61963
                return Infinity;
61964
            } // Since this is a VOD video, it is safe to rely on the media source's duration (if
61965
            // available). If it's not available, fall back to a playlist-calculated estimate.
61966
 
61967
            if (this.mediaSource) {
61968
                return this.mediaSource.duration;
61969
            }
61970
            return Vhs$1.Playlist.duration(media);
61971
        }
61972
        /**
61973
         * check the seekable range
61974
         *
61975
         * @return {TimeRange} the seekable range
61976
         */
61977
 
61978
        seekable() {
61979
            return this.seekable_;
61980
        }
61981
        onSyncInfoUpdate_() {
61982
            let audioSeekable; // TODO check for creation of both source buffers before updating seekable
61983
            //
61984
            // A fix was made to this function where a check for
61985
            // this.sourceUpdater_.hasCreatedSourceBuffers
61986
            // was added to ensure that both source buffers were created before seekable was
61987
            // updated. However, it originally had a bug where it was checking for a true and
61988
            // returning early instead of checking for false. Setting it to check for false to
61989
            // return early though created other issues. A call to play() would check for seekable
61990
            // end without verifying that a seekable range was present. In addition, even checking
61991
            // for that didn't solve some issues, as handleFirstPlay is sometimes worked around
61992
            // due to a media update calling load on the segment loaders, skipping a seek to live,
61993
            // thereby starting live streams at the beginning of the stream rather than at the end.
61994
            //
61995
            // This conditional should be fixed to wait for the creation of two source buffers at
61996
            // the same time as the other sections of code are fixed to properly seek to live and
61997
            // not throw an error due to checking for a seekable end when no seekable range exists.
61998
            //
61999
            // For now, fall back to the older behavior, with the understanding that the seekable
62000
            // range may not be completely correct, leading to a suboptimal initial live point.
62001
 
62002
            if (!this.mainPlaylistLoader_) {
62003
                return;
62004
            }
62005
            let media = this.mainPlaylistLoader_.media();
62006
            if (!media) {
62007
                return;
62008
            }
62009
            let expired = this.syncController_.getExpiredTime(media, this.duration());
62010
            if (expired === null) {
62011
                // not enough information to update seekable
62012
                return;
62013
            }
62014
            const main = this.mainPlaylistLoader_.main;
62015
            const mainSeekable = Vhs$1.Playlist.seekable(media, expired, Vhs$1.Playlist.liveEdgeDelay(main, media));
62016
            if (mainSeekable.length === 0) {
62017
                return;
62018
            }
62019
            if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
62020
                media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
62021
                expired = this.syncController_.getExpiredTime(media, this.duration());
62022
                if (expired === null) {
62023
                    return;
62024
                }
62025
                audioSeekable = Vhs$1.Playlist.seekable(media, expired, Vhs$1.Playlist.liveEdgeDelay(main, media));
62026
                if (audioSeekable.length === 0) {
62027
                    return;
62028
                }
62029
            }
62030
            let oldEnd;
62031
            let oldStart;
62032
            if (this.seekable_ && this.seekable_.length) {
62033
                oldEnd = this.seekable_.end(0);
62034
                oldStart = this.seekable_.start(0);
62035
            }
62036
            if (!audioSeekable) {
62037
                // seekable has been calculated based on buffering video data so it
62038
                // can be returned directly
62039
                this.seekable_ = mainSeekable;
62040
            } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) {
62041
                // seekables are pretty far off, rely on main
62042
                this.seekable_ = mainSeekable;
62043
            } else {
62044
                this.seekable_ = createTimeRanges([[audioSeekable.start(0) > mainSeekable.start(0) ? audioSeekable.start(0) : mainSeekable.start(0), audioSeekable.end(0) < mainSeekable.end(0) ? audioSeekable.end(0) : mainSeekable.end(0)]]);
62045
            } // seekable is the same as last time
62046
 
62047
            if (this.seekable_ && this.seekable_.length) {
62048
                if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) {
62049
                    return;
62050
                }
62051
            }
62052
            this.logger_(`seekable updated [${printableRange(this.seekable_)}]`);
62053
            this.tech_.trigger('seekablechanged');
62054
        }
62055
        /**
62056
         * Update the player duration
62057
         */
62058
 
62059
        updateDuration(isLive) {
62060
            if (this.updateDuration_) {
62061
                this.mediaSource.removeEventListener('sourceopen', this.updateDuration_);
62062
                this.updateDuration_ = null;
62063
            }
62064
            if (this.mediaSource.readyState !== 'open') {
62065
                this.updateDuration_ = this.updateDuration.bind(this, isLive);
62066
                this.mediaSource.addEventListener('sourceopen', this.updateDuration_);
62067
                return;
62068
            }
62069
            if (isLive) {
62070
                const seekable = this.seekable();
62071
                if (!seekable.length) {
62072
                    return;
62073
                } // Even in the case of a live playlist, the native MediaSource's duration should not
62074
                // be set to Infinity (even though this would be expected for a live playlist), since
62075
                // setting the native MediaSource's duration to infinity ends up with consequences to
62076
                // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details.
62077
                //
62078
                // This is resolved in the spec by https://github.com/w3c/media-source/pull/92,
62079
                // however, few browsers have support for setLiveSeekableRange()
62080
                // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange
62081
                //
62082
                // Until a time when the duration of the media source can be set to infinity, and a
62083
                // seekable range specified across browsers, the duration should be greater than or
62084
                // equal to the last possible seekable value.
62085
                // MediaSource duration starts as NaN
62086
                // It is possible (and probable) that this case will never be reached for many
62087
                // sources, since the MediaSource reports duration as the highest value without
62088
                // accounting for timestamp offset. For example, if the timestamp offset is -100 and
62089
                // we buffered times 0 to 100 with real times of 100 to 200, even though current
62090
                // time will be between 0 and 100, the native media source may report the duration
62091
                // as 200. However, since we report duration separate from the media source (as
62092
                // Infinity), and as long as the native media source duration value is greater than
62093
                // our reported seekable range, seeks will work as expected. The large number as
62094
                // duration for live is actually a strategy used by some players to work around the
62095
                // issue of live seekable ranges cited above.
62096
 
62097
                if (isNaN(this.mediaSource.duration) || this.mediaSource.duration < seekable.end(seekable.length - 1)) {
62098
                    this.sourceUpdater_.setDuration(seekable.end(seekable.length - 1));
62099
                }
62100
                return;
62101
            }
62102
            const buffered = this.tech_.buffered();
62103
            let duration = Vhs$1.Playlist.duration(this.mainPlaylistLoader_.media());
62104
            if (buffered.length > 0) {
62105
                duration = Math.max(duration, buffered.end(buffered.length - 1));
62106
            }
62107
            if (this.mediaSource.duration !== duration) {
62108
                this.sourceUpdater_.setDuration(duration);
62109
            }
62110
        }
62111
        /**
62112
         * dispose of the PlaylistController and everything
62113
         * that it controls
62114
         */
62115
 
62116
        dispose() {
62117
            this.trigger('dispose');
62118
            this.decrypter_.terminate();
62119
            this.mainPlaylistLoader_.dispose();
62120
            this.mainSegmentLoader_.dispose();
62121
            this.contentSteeringController_.dispose();
62122
            this.keyStatusMap_.clear();
62123
            if (this.loadOnPlay_) {
62124
                this.tech_.off('play', this.loadOnPlay_);
62125
            }
62126
            ['AUDIO', 'SUBTITLES'].forEach(type => {
62127
                const groups = this.mediaTypes_[type].groups;
62128
                for (const id in groups) {
62129
                    groups[id].forEach(group => {
62130
                        if (group.playlistLoader) {
62131
                            group.playlistLoader.dispose();
62132
                        }
62133
                    });
62134
                }
62135
            });
62136
            this.audioSegmentLoader_.dispose();
62137
            this.subtitleSegmentLoader_.dispose();
62138
            this.sourceUpdater_.dispose();
62139
            this.timelineChangeController_.dispose();
62140
            this.stopABRTimer_();
62141
            if (this.updateDuration_) {
62142
                this.mediaSource.removeEventListener('sourceopen', this.updateDuration_);
62143
            }
62144
            this.mediaSource.removeEventListener('durationchange', this.handleDurationChange_); // load the media source into the player
62145
 
62146
            this.mediaSource.removeEventListener('sourceopen', this.handleSourceOpen_);
62147
            this.mediaSource.removeEventListener('sourceended', this.handleSourceEnded_);
62148
            this.off();
62149
        }
62150
        /**
62151
         * return the main playlist object if we have one
62152
         *
62153
         * @return {Object} the main playlist object that we parsed
62154
         */
62155
 
62156
        main() {
62157
            return this.mainPlaylistLoader_.main;
62158
        }
62159
        /**
62160
         * return the currently selected playlist
62161
         *
62162
         * @return {Object} the currently selected playlist object that we parsed
62163
         */
62164
 
62165
        media() {
62166
            // playlist loader will not return media if it has not been fully loaded
62167
            return this.mainPlaylistLoader_.media() || this.initialMedia_;
62168
        }
62169
        areMediaTypesKnown_() {
62170
            const usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader;
62171
            const hasMainMediaInfo = !!this.mainSegmentLoader_.getCurrentMediaInfo_(); // if we are not using an audio loader, then we have audio media info
62172
            // otherwise check on the segment loader.
62173
 
62174
            const hasAudioMediaInfo = !usingAudioLoader ? true : !!this.audioSegmentLoader_.getCurrentMediaInfo_(); // one or both loaders has not loaded sufficently to get codecs
62175
 
62176
            if (!hasMainMediaInfo || !hasAudioMediaInfo) {
62177
                return false;
62178
            }
62179
            return true;
62180
        }
62181
        getCodecsOrExclude_() {
62182
            const media = {
62183
                main: this.mainSegmentLoader_.getCurrentMediaInfo_() || {},
62184
                audio: this.audioSegmentLoader_.getCurrentMediaInfo_() || {}
62185
            };
62186
            const playlist = this.mainSegmentLoader_.getPendingSegmentPlaylist() || this.media(); // set "main" media equal to video
62187
 
62188
            media.video = media.main;
62189
            const playlistCodecs = codecsForPlaylist(this.main(), playlist);
62190
            const codecs = {};
62191
            const usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader;
62192
            if (media.main.hasVideo) {
62193
                codecs.video = playlistCodecs.video || media.main.videoCodec || DEFAULT_VIDEO_CODEC;
62194
            }
62195
            if (media.main.isMuxed) {
62196
                codecs.video += `,${playlistCodecs.audio || media.main.audioCodec || DEFAULT_AUDIO_CODEC}`;
62197
            }
62198
            if (media.main.hasAudio && !media.main.isMuxed || media.audio.hasAudio || usingAudioLoader) {
62199
                codecs.audio = playlistCodecs.audio || media.main.audioCodec || media.audio.audioCodec || DEFAULT_AUDIO_CODEC; // set audio isFmp4 so we use the correct "supports" function below
62200
 
62201
                media.audio.isFmp4 = media.main.hasAudio && !media.main.isMuxed ? media.main.isFmp4 : media.audio.isFmp4;
62202
            } // no codecs, no playback.
62203
 
62204
            if (!codecs.audio && !codecs.video) {
62205
                this.excludePlaylist({
62206
                    playlistToExclude: playlist,
62207
                    error: {
62208
                        message: 'Could not determine codecs for playlist.'
62209
                    },
62210
                    playlistExclusionDuration: Infinity
62211
                });
62212
                return;
62213
            } // fmp4 relies on browser support, while ts relies on muxer support
62214
 
62215
            const supportFunction = (isFmp4, codec) => isFmp4 ? browserSupportsCodec(codec) : muxerSupportsCodec(codec);
62216
            const unsupportedCodecs = {};
62217
            let unsupportedAudio;
62218
            ['video', 'audio'].forEach(function (type) {
62219
                if (codecs.hasOwnProperty(type) && !supportFunction(media[type].isFmp4, codecs[type])) {
62220
                    const supporter = media[type].isFmp4 ? 'browser' : 'muxer';
62221
                    unsupportedCodecs[supporter] = unsupportedCodecs[supporter] || [];
62222
                    unsupportedCodecs[supporter].push(codecs[type]);
62223
                    if (type === 'audio') {
62224
                        unsupportedAudio = supporter;
62225
                    }
62226
                }
62227
            });
62228
            if (usingAudioLoader && unsupportedAudio && playlist.attributes.AUDIO) {
62229
                const audioGroup = playlist.attributes.AUDIO;
62230
                this.main().playlists.forEach(variant => {
62231
                    const variantAudioGroup = variant.attributes && variant.attributes.AUDIO;
62232
                    if (variantAudioGroup === audioGroup && variant !== playlist) {
62233
                        variant.excludeUntil = Infinity;
62234
                    }
62235
                });
62236
                this.logger_(`excluding audio group ${audioGroup} as ${unsupportedAudio} does not support codec(s): "${codecs.audio}"`);
62237
            } // if we have any unsupported codecs exclude this playlist.
62238
 
62239
            if (Object.keys(unsupportedCodecs).length) {
62240
                const message = Object.keys(unsupportedCodecs).reduce((acc, supporter) => {
62241
                    if (acc) {
62242
                        acc += ', ';
62243
                    }
62244
                    acc += `${supporter} does not support codec(s): "${unsupportedCodecs[supporter].join(',')}"`;
62245
                    return acc;
62246
                }, '') + '.';
62247
                this.excludePlaylist({
62248
                    playlistToExclude: playlist,
62249
                    error: {
62250
                        internal: true,
62251
                        message
62252
                    },
62253
                    playlistExclusionDuration: Infinity
62254
                });
62255
                return;
62256
            } // check if codec switching is happening
62257
 
62258
            if (this.sourceUpdater_.hasCreatedSourceBuffers() && !this.sourceUpdater_.canChangeType()) {
62259
                const switchMessages = [];
62260
                ['video', 'audio'].forEach(type => {
62261
                    const newCodec = (parseCodecs(this.sourceUpdater_.codecs[type] || '')[0] || {}).type;
62262
                    const oldCodec = (parseCodecs(codecs[type] || '')[0] || {}).type;
62263
                    if (newCodec && oldCodec && newCodec.toLowerCase() !== oldCodec.toLowerCase()) {
62264
                        switchMessages.push(`"${this.sourceUpdater_.codecs[type]}" -> "${codecs[type]}"`);
62265
                    }
62266
                });
62267
                if (switchMessages.length) {
62268
                    this.excludePlaylist({
62269
                        playlistToExclude: playlist,
62270
                        error: {
62271
                            message: `Codec switching not supported: ${switchMessages.join(', ')}.`,
62272
                            internal: true
62273
                        },
62274
                        playlistExclusionDuration: Infinity
62275
                    });
62276
                    return;
62277
                }
62278
            } // TODO: when using the muxer shouldn't we just return
62279
            // the codecs that the muxer outputs?
62280
 
62281
            return codecs;
62282
        }
62283
        /**
62284
         * Create source buffers and exlude any incompatible renditions.
62285
         *
62286
         * @private
62287
         */
62288
 
62289
        tryToCreateSourceBuffers_() {
62290
            // media source is not ready yet or sourceBuffers are already
62291
            // created.
62292
            if (this.mediaSource.readyState !== 'open' || this.sourceUpdater_.hasCreatedSourceBuffers()) {
62293
                return;
62294
            }
62295
            if (!this.areMediaTypesKnown_()) {
62296
                return;
62297
            }
62298
            const codecs = this.getCodecsOrExclude_(); // no codecs means that the playlist was excluded
62299
 
62300
            if (!codecs) {
62301
                return;
62302
            }
62303
            this.sourceUpdater_.createSourceBuffers(codecs);
62304
            const codecString = [codecs.video, codecs.audio].filter(Boolean).join(',');
62305
            this.excludeIncompatibleVariants_(codecString);
62306
        }
62307
        /**
62308
         * Excludes playlists with codecs that are unsupported by the muxer and browser.
62309
         */
62310
 
62311
        excludeUnsupportedVariants_() {
62312
            const playlists = this.main().playlists;
62313
            const ids = []; // TODO: why don't we have a property to loop through all
62314
            // playlist? Why did we ever mix indexes and keys?
62315
 
62316
            Object.keys(playlists).forEach(key => {
62317
                const variant = playlists[key]; // check if we already processed this playlist.
62318
 
62319
                if (ids.indexOf(variant.id) !== -1) {
62320
                    return;
62321
                }
62322
                ids.push(variant.id);
62323
                const codecs = codecsForPlaylist(this.main, variant);
62324
                const unsupported = [];
62325
                if (codecs.audio && !muxerSupportsCodec(codecs.audio) && !browserSupportsCodec(codecs.audio)) {
62326
                    unsupported.push(`audio codec ${codecs.audio}`);
62327
                }
62328
                if (codecs.video && !muxerSupportsCodec(codecs.video) && !browserSupportsCodec(codecs.video)) {
62329
                    unsupported.push(`video codec ${codecs.video}`);
62330
                }
62331
                if (codecs.text && codecs.text === 'stpp.ttml.im1t') {
62332
                    unsupported.push(`text codec ${codecs.text}`);
62333
                }
62334
                if (unsupported.length) {
62335
                    variant.excludeUntil = Infinity;
62336
                    this.logger_(`excluding ${variant.id} for unsupported: ${unsupported.join(', ')}`);
62337
                }
62338
            });
62339
        }
62340
        /**
62341
         * Exclude playlists that are known to be codec or
62342
         * stream-incompatible with the SourceBuffer configuration. For
62343
         * instance, Media Source Extensions would cause the video element to
62344
         * stall waiting for video data if you switched from a variant with
62345
         * video and audio to an audio-only one.
62346
         *
62347
         * @param {Object} media a media playlist compatible with the current
62348
         * set of SourceBuffers. Variants in the current main playlist that
62349
         * do not appear to have compatible codec or stream configurations
62350
         * will be excluded from the default playlist selection algorithm
62351
         * indefinitely.
62352
         * @private
62353
         */
62354
 
62355
        excludeIncompatibleVariants_(codecString) {
62356
            const ids = [];
62357
            const playlists = this.main().playlists;
62358
            const codecs = unwrapCodecList(parseCodecs(codecString));
62359
            const codecCount_ = codecCount(codecs);
62360
            const videoDetails = codecs.video && parseCodecs(codecs.video)[0] || null;
62361
            const audioDetails = codecs.audio && parseCodecs(codecs.audio)[0] || null;
62362
            Object.keys(playlists).forEach(key => {
62363
                const variant = playlists[key]; // check if we already processed this playlist.
62364
                // or it if it is already excluded forever.
62365
 
62366
                if (ids.indexOf(variant.id) !== -1 || variant.excludeUntil === Infinity) {
62367
                    return;
62368
                }
62369
                ids.push(variant.id);
62370
                const exclusionReasons = []; // get codecs from the playlist for this variant
62371
 
62372
                const variantCodecs = codecsForPlaylist(this.mainPlaylistLoader_.main, variant);
62373
                const variantCodecCount = codecCount(variantCodecs); // if no codecs are listed, we cannot determine that this
62374
                // variant is incompatible. Wait for mux.js to probe
62375
 
62376
                if (!variantCodecs.audio && !variantCodecs.video) {
62377
                    return;
62378
                } // TODO: we can support this by removing the
62379
                // old media source and creating a new one, but it will take some work.
62380
                // The number of streams cannot change
62381
 
62382
                if (variantCodecCount !== codecCount_) {
62383
                    exclusionReasons.push(`codec count "${variantCodecCount}" !== "${codecCount_}"`);
62384
                } // only exclude playlists by codec change, if codecs cannot switch
62385
                // during playback.
62386
 
62387
                if (!this.sourceUpdater_.canChangeType()) {
62388
                    const variantVideoDetails = variantCodecs.video && parseCodecs(variantCodecs.video)[0] || null;
62389
                    const variantAudioDetails = variantCodecs.audio && parseCodecs(variantCodecs.audio)[0] || null; // the video codec cannot change
62390
 
62391
                    if (variantVideoDetails && videoDetails && variantVideoDetails.type.toLowerCase() !== videoDetails.type.toLowerCase()) {
62392
                        exclusionReasons.push(`video codec "${variantVideoDetails.type}" !== "${videoDetails.type}"`);
62393
                    } // the audio codec cannot change
62394
 
62395
                    if (variantAudioDetails && audioDetails && variantAudioDetails.type.toLowerCase() !== audioDetails.type.toLowerCase()) {
62396
                        exclusionReasons.push(`audio codec "${variantAudioDetails.type}" !== "${audioDetails.type}"`);
62397
                    }
62398
                }
62399
                if (exclusionReasons.length) {
62400
                    variant.excludeUntil = Infinity;
62401
                    this.logger_(`excluding ${variant.id}: ${exclusionReasons.join(' && ')}`);
62402
                }
62403
            });
62404
        }
62405
        updateAdCues_(media) {
62406
            let offset = 0;
62407
            const seekable = this.seekable();
62408
            if (seekable.length) {
62409
                offset = seekable.start(0);
62410
            }
62411
            updateAdCues(media, this.cueTagsTrack_, offset);
62412
        }
62413
        /**
62414
         * Calculates the desired forward buffer length based on current time
62415
         *
62416
         * @return {number} Desired forward buffer length in seconds
62417
         */
62418
 
62419
        goalBufferLength() {
62420
            const currentTime = this.tech_.currentTime();
62421
            const initial = Config.GOAL_BUFFER_LENGTH;
62422
            const rate = Config.GOAL_BUFFER_LENGTH_RATE;
62423
            const max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
62424
            return Math.min(initial + currentTime * rate, max);
62425
        }
62426
        /**
62427
         * Calculates the desired buffer low water line based on current time
62428
         *
62429
         * @return {number} Desired buffer low water line in seconds
62430
         */
62431
 
62432
        bufferLowWaterLine() {
62433
            const currentTime = this.tech_.currentTime();
62434
            const initial = Config.BUFFER_LOW_WATER_LINE;
62435
            const rate = Config.BUFFER_LOW_WATER_LINE_RATE;
62436
            const max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
62437
            const newMax = Math.max(initial, Config.EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE);
62438
            return Math.min(initial + currentTime * rate, this.bufferBasedABR ? newMax : max);
62439
        }
62440
        bufferHighWaterLine() {
62441
            return Config.BUFFER_HIGH_WATER_LINE;
62442
        }
62443
        addDateRangesToTextTrack_(dateRanges) {
62444
            createMetadataTrackIfNotExists(this.inbandTextTracks_, 'com.apple.streaming', this.tech_);
62445
            addDateRangeMetadata({
62446
                inbandTextTracks: this.inbandTextTracks_,
62447
                dateRanges
62448
            });
62449
        }
62450
        addMetadataToTextTrack(dispatchType, metadataArray, videoDuration) {
62451
            const timestampOffset = this.sourceUpdater_.videoBuffer ? this.sourceUpdater_.videoTimestampOffset() : this.sourceUpdater_.audioTimestampOffset(); // There's potentially an issue where we could double add metadata if there's a muxed
62452
            // audio/video source with a metadata track, and an alt audio with a metadata track.
62453
            // However, this probably won't happen, and if it does it can be handled then.
62454
 
62455
            createMetadataTrackIfNotExists(this.inbandTextTracks_, dispatchType, this.tech_);
62456
            addMetadata({
62457
                inbandTextTracks: this.inbandTextTracks_,
62458
                metadataArray,
62459
                timestampOffset,
62460
                videoDuration
62461
            });
62462
        }
62463
        /**
62464
         * Utility for getting the pathway or service location from an HLS or DASH playlist.
62465
         *
62466
         * @param {Object} playlist for getting pathway from.
62467
         * @return the pathway attribute of a playlist
62468
         */
62469
 
62470
        pathwayAttribute_(playlist) {
62471
            return playlist.attributes['PATHWAY-ID'] || playlist.attributes.serviceLocation;
62472
        }
62473
        /**
62474
         * Initialize available pathways and apply the tag properties.
62475
         */
62476
 
62477
        initContentSteeringController_() {
62478
            const main = this.main();
62479
            if (!main.contentSteering) {
62480
                return;
62481
            }
62482
            for (const playlist of main.playlists) {
62483
                this.contentSteeringController_.addAvailablePathway(this.pathwayAttribute_(playlist));
62484
            }
62485
            this.contentSteeringController_.assignTagProperties(main.uri, main.contentSteering); // request the steering manifest immediately if queryBeforeStart is set.
62486
 
62487
            if (this.contentSteeringController_.queryBeforeStart) {
62488
                // When queryBeforeStart is true, initial request should omit steering parameters.
62489
                this.contentSteeringController_.requestSteeringManifest(true);
62490
                return;
62491
            } // otherwise start content steering after playback starts
62492
 
62493
            this.tech_.one('canplay', () => {
62494
                this.contentSteeringController_.requestSteeringManifest();
62495
            });
62496
        }
62497
        /**
62498
         * Reset the content steering controller and re-init.
62499
         */
62500
 
62501
        resetContentSteeringController_() {
62502
            this.contentSteeringController_.clearAvailablePathways();
62503
            this.contentSteeringController_.dispose();
62504
            this.initContentSteeringController_();
62505
        }
62506
        /**
62507
         * Attaches the listeners for content steering.
62508
         */
62509
 
62510
        attachContentSteeringListeners_() {
62511
            this.contentSteeringController_.on('content-steering', this.excludeThenChangePathway_.bind(this));
62512
            if (this.sourceType_ === 'dash') {
62513
                this.mainPlaylistLoader_.on('loadedplaylist', () => {
62514
                    const main = this.main(); // check if steering tag or pathways changed.
62515
 
62516
                    const didDashTagChange = this.contentSteeringController_.didDASHTagChange(main.uri, main.contentSteering);
62517
                    const didPathwaysChange = () => {
62518
                        const availablePathways = this.contentSteeringController_.getAvailablePathways();
62519
                        const newPathways = [];
62520
                        for (const playlist of main.playlists) {
62521
                            const serviceLocation = playlist.attributes.serviceLocation;
62522
                            if (serviceLocation) {
62523
                                newPathways.push(serviceLocation);
62524
                                if (!availablePathways.has(serviceLocation)) {
62525
                                    return true;
62526
                                }
62527
                            }
62528
                        } // If we have no new serviceLocations and previously had availablePathways
62529
 
62530
                        if (!newPathways.length && availablePathways.size) {
62531
                            return true;
62532
                        }
62533
                        return false;
62534
                    };
62535
                    if (didDashTagChange || didPathwaysChange()) {
62536
                        this.resetContentSteeringController_();
62537
                    }
62538
                });
62539
            }
62540
        }
62541
        /**
62542
         * Simple exclude and change playlist logic for content steering.
62543
         */
62544
 
62545
        excludeThenChangePathway_() {
62546
            const currentPathway = this.contentSteeringController_.getPathway();
62547
            if (!currentPathway) {
62548
                return;
62549
            }
62550
            this.handlePathwayClones_();
62551
            const main = this.main();
62552
            const playlists = main.playlists;
62553
            const ids = new Set();
62554
            let didEnablePlaylists = false;
62555
            Object.keys(playlists).forEach(key => {
62556
                const variant = playlists[key];
62557
                const pathwayId = this.pathwayAttribute_(variant);
62558
                const differentPathwayId = pathwayId && currentPathway !== pathwayId;
62559
                const steeringExclusion = variant.excludeUntil === Infinity && variant.lastExcludeReason_ === 'content-steering';
62560
                if (steeringExclusion && !differentPathwayId) {
62561
                    delete variant.excludeUntil;
62562
                    delete variant.lastExcludeReason_;
62563
                    didEnablePlaylists = true;
62564
                }
62565
                const noExcludeUntil = !variant.excludeUntil && variant.excludeUntil !== Infinity;
62566
                const shouldExclude = !ids.has(variant.id) && differentPathwayId && noExcludeUntil;
62567
                if (!shouldExclude) {
62568
                    return;
62569
                }
62570
                ids.add(variant.id);
62571
                variant.excludeUntil = Infinity;
62572
                variant.lastExcludeReason_ = 'content-steering'; // TODO: kind of spammy, maybe move this.
62573
 
62574
                this.logger_(`excluding ${variant.id} for ${variant.lastExcludeReason_}`);
62575
            });
62576
            if (this.contentSteeringController_.manifestType_ === 'DASH') {
62577
                Object.keys(this.mediaTypes_).forEach(key => {
62578
                    const type = this.mediaTypes_[key];
62579
                    if (type.activePlaylistLoader) {
62580
                        const currentPlaylist = type.activePlaylistLoader.media_; // Check if the current media playlist matches the current CDN
62581
 
62582
                        if (currentPlaylist && currentPlaylist.attributes.serviceLocation !== currentPathway) {
62583
                            didEnablePlaylists = true;
62584
                        }
62585
                    }
62586
                });
62587
            }
62588
            if (didEnablePlaylists) {
62589
                this.changeSegmentPathway_();
62590
            }
62591
        }
62592
        /**
62593
         * Add, update, or delete playlists and media groups for
62594
         * the pathway clones for HLS Content Steering.
62595
         *
62596
         * See https://datatracker.ietf.org/doc/draft-pantos-hls-rfc8216bis/
62597
         *
62598
         * NOTE: Pathway cloning does not currently support the `PER_VARIANT_URIS` and
62599
         * `PER_RENDITION_URIS` as we do not handle `STABLE-VARIANT-ID` or
62600
         * `STABLE-RENDITION-ID` values.
62601
         */
62602
 
62603
        handlePathwayClones_() {
62604
            const main = this.main();
62605
            const playlists = main.playlists;
62606
            const currentPathwayClones = this.contentSteeringController_.currentPathwayClones;
62607
            const nextPathwayClones = this.contentSteeringController_.nextPathwayClones;
62608
            const hasClones = currentPathwayClones && currentPathwayClones.size || nextPathwayClones && nextPathwayClones.size;
62609
            if (!hasClones) {
62610
                return;
62611
            }
62612
            for (const [id, clone] of currentPathwayClones.entries()) {
62613
                const newClone = nextPathwayClones.get(id); // Delete the old pathway clone.
62614
 
62615
                if (!newClone) {
62616
                    this.mainPlaylistLoader_.updateOrDeleteClone(clone);
62617
                    this.contentSteeringController_.excludePathway(id);
62618
                }
62619
            }
62620
            for (const [id, clone] of nextPathwayClones.entries()) {
62621
                const oldClone = currentPathwayClones.get(id); // Create a new pathway if it is a new pathway clone object.
62622
 
62623
                if (!oldClone) {
62624
                    const playlistsToClone = playlists.filter(p => {
62625
                        return p.attributes['PATHWAY-ID'] === clone['BASE-ID'];
62626
                    });
62627
                    playlistsToClone.forEach(p => {
62628
                        this.mainPlaylistLoader_.addClonePathway(clone, p);
62629
                    });
62630
                    this.contentSteeringController_.addAvailablePathway(id);
62631
                    continue;
62632
                } // There have not been changes to the pathway clone object, so skip.
62633
 
62634
                if (this.equalPathwayClones_(oldClone, clone)) {
62635
                    continue;
62636
                } // Update a preexisting cloned pathway.
62637
                // True is set for the update flag.
62638
 
62639
                this.mainPlaylistLoader_.updateOrDeleteClone(clone, true);
62640
                this.contentSteeringController_.addAvailablePathway(id);
62641
            } // Deep copy contents of next to current pathways.
62642
 
62643
            this.contentSteeringController_.currentPathwayClones = new Map(JSON.parse(JSON.stringify([...nextPathwayClones])));
62644
        }
62645
        /**
62646
         * Determines whether two pathway clone objects are equivalent.
62647
         *
62648
         * @param {Object} a The first pathway clone object.
62649
         * @param {Object} b The second pathway clone object.
62650
         * @return {boolean} True if the pathway clone objects are equal, false otherwise.
62651
         */
62652
 
62653
        equalPathwayClones_(a, b) {
62654
            if (a['BASE-ID'] !== b['BASE-ID'] || a.ID !== b.ID || a['URI-REPLACEMENT'].HOST !== b['URI-REPLACEMENT'].HOST) {
62655
                return false;
62656
            }
62657
            const aParams = a['URI-REPLACEMENT'].PARAMS;
62658
            const bParams = b['URI-REPLACEMENT'].PARAMS; // We need to iterate through both lists of params because one could be
62659
            // missing a parameter that the other has.
62660
 
62661
            for (const p in aParams) {
62662
                if (aParams[p] !== bParams[p]) {
62663
                    return false;
62664
                }
62665
            }
62666
            for (const p in bParams) {
62667
                if (aParams[p] !== bParams[p]) {
62668
                    return false;
62669
                }
62670
            }
62671
            return true;
62672
        }
62673
        /**
62674
         * Changes the current playlists for audio, video and subtitles after a new pathway
62675
         * is chosen from content steering.
62676
         */
62677
 
62678
        changeSegmentPathway_() {
62679
            const nextPlaylist = this.selectPlaylist();
62680
            this.pauseLoading(); // Switch audio and text track playlists if necessary in DASH
62681
 
62682
            if (this.contentSteeringController_.manifestType_ === 'DASH') {
62683
                this.switchMediaForDASHContentSteering_();
62684
            }
62685
            this.switchMedia_(nextPlaylist, 'content-steering');
62686
        }
62687
        /**
62688
         * Iterates through playlists and check their keyId set and compare with the
62689
         * keyStatusMap, only enable playlists that have a usable key. If the playlist
62690
         * has no keyId leave it enabled by default.
62691
         */
62692
 
62693
        excludeNonUsablePlaylistsByKeyId_() {
62694
            if (!this.mainPlaylistLoader_ || !this.mainPlaylistLoader_.main) {
62695
                return;
62696
            }
62697
            let nonUsableKeyStatusCount = 0;
62698
            const NON_USABLE = 'non-usable';
62699
            this.mainPlaylistLoader_.main.playlists.forEach(playlist => {
62700
                const keyIdSet = this.mainPlaylistLoader_.getKeyIdSet(playlist); // If the playlist doesn't have keyIDs lets not exclude it.
62701
 
62702
                if (!keyIdSet || !keyIdSet.size) {
62703
                    return;
62704
                }
62705
                keyIdSet.forEach(key => {
62706
                    const USABLE = 'usable';
62707
                    const hasUsableKeyStatus = this.keyStatusMap_.has(key) && this.keyStatusMap_.get(key) === USABLE;
62708
                    const nonUsableExclusion = playlist.lastExcludeReason_ === NON_USABLE && playlist.excludeUntil === Infinity;
62709
                    if (!hasUsableKeyStatus) {
62710
                        // Only exclude playlists that haven't already been excluded as non-usable.
62711
                        if (playlist.excludeUntil !== Infinity && playlist.lastExcludeReason_ !== NON_USABLE) {
62712
                            playlist.excludeUntil = Infinity;
62713
                            playlist.lastExcludeReason_ = NON_USABLE;
62714
                            this.logger_(`excluding playlist ${playlist.id} because the key ID ${key} doesn't exist in the keyStatusMap or is not ${USABLE}`);
62715
                        } // count all nonUsableKeyStatus
62716
 
62717
                        nonUsableKeyStatusCount++;
62718
                    } else if (hasUsableKeyStatus && nonUsableExclusion) {
62719
                        delete playlist.excludeUntil;
62720
                        delete playlist.lastExcludeReason_;
62721
                        this.logger_(`enabling playlist ${playlist.id} because key ID ${key} is ${USABLE}`);
62722
                    }
62723
                });
62724
            }); // If for whatever reason every playlist has a non usable key status. Lets try re-including the SD renditions as a failsafe.
62725
 
62726
            if (nonUsableKeyStatusCount >= this.mainPlaylistLoader_.main.playlists.length) {
62727
                this.mainPlaylistLoader_.main.playlists.forEach(playlist => {
62728
                    const isNonHD = playlist && playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height < 720;
62729
                    const excludedForNonUsableKey = playlist.excludeUntil === Infinity && playlist.lastExcludeReason_ === NON_USABLE;
62730
                    if (isNonHD && excludedForNonUsableKey) {
62731
                        // Only delete the excludeUntil so we don't try and re-exclude these playlists.
62732
                        delete playlist.excludeUntil;
62733
                        videojs.log.warn(`enabling non-HD playlist ${playlist.id} because all playlists were excluded due to ${NON_USABLE} key IDs`);
62734
                    }
62735
                });
62736
            }
62737
        }
62738
        /**
62739
         * Adds a keystatus to the keystatus map, tries to convert to string if necessary.
62740
         *
62741
         * @param {any} keyId the keyId to add a status for
62742
         * @param {string} status the status of the keyId
62743
         */
62744
 
62745
        addKeyStatus_(keyId, status) {
62746
            const isString = typeof keyId === 'string';
62747
            const keyIdHexString = isString ? keyId : bufferToHexString(keyId);
62748
            const formattedKeyIdString = keyIdHexString.slice(0, 32).toLowerCase();
62749
            this.logger_(`KeyStatus '${status}' with key ID ${formattedKeyIdString} added to the keyStatusMap`);
62750
            this.keyStatusMap_.set(formattedKeyIdString, status);
62751
        }
62752
        /**
62753
         * Utility function for adding key status to the keyStatusMap and filtering usable encrypted playlists.
62754
         *
62755
         * @param {any} keyId the keyId from the keystatuschange event
62756
         * @param {string} status the key status string
62757
         */
62758
 
62759
        updatePlaylistByKeyStatus(keyId, status) {
62760
            this.addKeyStatus_(keyId, status);
62761
            if (!this.waitingForFastQualityPlaylistReceived_) {
62762
                this.excludeNonUsableThenChangePlaylist_();
62763
            } // Listen to loadedplaylist with a single listener and check for new contentProtection elements when a playlist is updated.
62764
 
62765
            this.mainPlaylistLoader_.off('loadedplaylist', this.excludeNonUsableThenChangePlaylist_.bind(this));
62766
            this.mainPlaylistLoader_.on('loadedplaylist', this.excludeNonUsableThenChangePlaylist_.bind(this));
62767
        }
62768
        excludeNonUsableThenChangePlaylist_() {
62769
            this.excludeNonUsablePlaylistsByKeyId_();
62770
            this.fastQualityChange_();
62771
        }
62772
    }
62773
 
62774
    /**
62775
     * Returns a function that acts as the Enable/disable playlist function.
62776
     *
62777
     * @param {PlaylistLoader} loader - The main playlist loader
62778
     * @param {string} playlistID - id of the playlist
62779
     * @param {Function} changePlaylistFn - A function to be called after a
62780
     * playlist's enabled-state has been changed. Will NOT be called if a
62781
     * playlist's enabled-state is unchanged
62782
     * @param {boolean=} enable - Value to set the playlist enabled-state to
62783
     * or if undefined returns the current enabled-state for the playlist
62784
     * @return {Function} Function for setting/getting enabled
62785
     */
62786
 
62787
    const enableFunction = (loader, playlistID, changePlaylistFn) => enable => {
62788
        const playlist = loader.main.playlists[playlistID];
62789
        const incompatible = isIncompatible(playlist);
62790
        const currentlyEnabled = isEnabled(playlist);
62791
        if (typeof enable === 'undefined') {
62792
            return currentlyEnabled;
62793
        }
62794
        if (enable) {
62795
            delete playlist.disabled;
62796
        } else {
62797
            playlist.disabled = true;
62798
        }
62799
        if (enable !== currentlyEnabled && !incompatible) {
62800
            // Ensure the outside world knows about our changes
62801
            changePlaylistFn();
62802
            if (enable) {
62803
                loader.trigger('renditionenabled');
62804
            } else {
62805
                loader.trigger('renditiondisabled');
62806
            }
62807
        }
62808
        return enable;
62809
    };
62810
    /**
62811
     * The representation object encapsulates the publicly visible information
62812
     * in a media playlist along with a setter/getter-type function (enabled)
62813
     * for changing the enabled-state of a particular playlist entry
62814
     *
62815
     * @class Representation
62816
     */
62817
 
62818
    class Representation {
62819
        constructor(vhsHandler, playlist, id) {
62820
            const {
62821
                playlistController_: pc
62822
            } = vhsHandler;
62823
            const qualityChangeFunction = pc.fastQualityChange_.bind(pc); // some playlist attributes are optional
62824
 
62825
            if (playlist.attributes) {
62826
                const resolution = playlist.attributes.RESOLUTION;
62827
                this.width = resolution && resolution.width;
62828
                this.height = resolution && resolution.height;
62829
                this.bandwidth = playlist.attributes.BANDWIDTH;
62830
                this.frameRate = playlist.attributes['FRAME-RATE'];
62831
            }
62832
            this.codecs = codecsForPlaylist(pc.main(), playlist);
62833
            this.playlist = playlist; // The id is simply the ordinality of the media playlist
62834
            // within the main playlist
62835
 
62836
            this.id = id; // Partially-apply the enableFunction to create a playlist-
62837
            // specific variant
62838
 
62839
            this.enabled = enableFunction(vhsHandler.playlists, playlist.id, qualityChangeFunction);
62840
        }
62841
    }
62842
    /**
62843
     * A mixin function that adds the `representations` api to an instance
62844
     * of the VhsHandler class
62845
     *
62846
     * @param {VhsHandler} vhsHandler - An instance of VhsHandler to add the
62847
     * representation API into
62848
     */
62849
 
62850
    const renditionSelectionMixin = function (vhsHandler) {
62851
        // Add a single API-specific function to the VhsHandler instance
62852
        vhsHandler.representations = () => {
62853
            const main = vhsHandler.playlistController_.main();
62854
            const playlists = isAudioOnly(main) ? vhsHandler.playlistController_.getAudioTrackPlaylists_() : main.playlists;
62855
            if (!playlists) {
62856
                return [];
62857
            }
62858
            return playlists.filter(media => !isIncompatible(media)).map((e, i) => new Representation(vhsHandler, e, e.id));
62859
        };
62860
    };
62861
 
62862
    /**
62863
     * @file playback-watcher.js
62864
     *
62865
     * Playback starts, and now my watch begins. It shall not end until my death. I shall
62866
     * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
62867
     * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
62868
     * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
62869
     * my life and honor to the Playback Watch, for this Player and all the Players to come.
62870
     */
62871
 
62872
    const timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
62873
    /**
62874
     * @class PlaybackWatcher
62875
     */
62876
 
62877
    class PlaybackWatcher {
62878
        /**
62879
         * Represents an PlaybackWatcher object.
62880
         *
62881
         * @class
62882
         * @param {Object} options an object that includes the tech and settings
62883
         */
62884
        constructor(options) {
62885
            this.playlistController_ = options.playlistController;
62886
            this.tech_ = options.tech;
62887
            this.seekable = options.seekable;
62888
            this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow;
62889
            this.liveRangeSafeTimeDelta = options.liveRangeSafeTimeDelta;
62890
            this.media = options.media;
62891
            this.consecutiveUpdates = 0;
62892
            this.lastRecordedTime = null;
62893
            this.checkCurrentTimeTimeout_ = null;
62894
            this.logger_ = logger('PlaybackWatcher');
62895
            this.logger_('initialize');
62896
            const playHandler = () => this.monitorCurrentTime_();
62897
            const canPlayHandler = () => this.monitorCurrentTime_();
62898
            const waitingHandler = () => this.techWaiting_();
62899
            const cancelTimerHandler = () => this.resetTimeUpdate_();
62900
            const pc = this.playlistController_;
62901
            const loaderTypes = ['main', 'subtitle', 'audio'];
62902
            const loaderChecks = {};
62903
            loaderTypes.forEach(type => {
62904
                loaderChecks[type] = {
62905
                    reset: () => this.resetSegmentDownloads_(type),
62906
                    updateend: () => this.checkSegmentDownloads_(type)
62907
                };
62908
                pc[`${type}SegmentLoader_`].on('appendsdone', loaderChecks[type].updateend); // If a rendition switch happens during a playback stall where the buffer
62909
                // isn't changing we want to reset. We cannot assume that the new rendition
62910
                // will also be stalled, until after new appends.
62911
 
62912
                pc[`${type}SegmentLoader_`].on('playlistupdate', loaderChecks[type].reset); // Playback stalls should not be detected right after seeking.
62913
                // This prevents one segment playlists (single vtt or single segment content)
62914
                // from being detected as stalling. As the buffer will not change in those cases, since
62915
                // the buffer is the entire video duration.
62916
 
62917
                this.tech_.on(['seeked', 'seeking'], loaderChecks[type].reset);
62918
            });
62919
            /**
62920
             * We check if a seek was into a gap through the following steps:
62921
             * 1. We get a seeking event and we do not get a seeked event. This means that
62922
             *    a seek was attempted but not completed.
62923
             * 2. We run `fixesBadSeeks_` on segment loader appends. This means that we already
62924
             *    removed everything from our buffer and appended a segment, and should be ready
62925
             *    to check for gaps.
62926
             */
62927
 
62928
            const setSeekingHandlers = fn => {
62929
                ['main', 'audio'].forEach(type => {
62930
                    pc[`${type}SegmentLoader_`][fn]('appended', this.seekingAppendCheck_);
62931
                });
62932
            };
62933
            this.seekingAppendCheck_ = () => {
62934
                if (this.fixesBadSeeks_()) {
62935
                    this.consecutiveUpdates = 0;
62936
                    this.lastRecordedTime = this.tech_.currentTime();
62937
                    setSeekingHandlers('off');
62938
                }
62939
            };
62940
            this.clearSeekingAppendCheck_ = () => setSeekingHandlers('off');
62941
            this.watchForBadSeeking_ = () => {
62942
                this.clearSeekingAppendCheck_();
62943
                setSeekingHandlers('on');
62944
            };
62945
            this.tech_.on('seeked', this.clearSeekingAppendCheck_);
62946
            this.tech_.on('seeking', this.watchForBadSeeking_);
62947
            this.tech_.on('waiting', waitingHandler);
62948
            this.tech_.on(timerCancelEvents, cancelTimerHandler);
62949
            this.tech_.on('canplay', canPlayHandler);
62950
            /*
62951
        An edge case exists that results in gaps not being skipped when they exist at the beginning of a stream. This case
62952
        is surfaced in one of two ways:
62953
         1)  The `waiting` event is fired before the player has buffered content, making it impossible
62954
            to find or skip the gap. The `waiting` event is followed by a `play` event. On first play
62955
            we can check if playback is stalled due to a gap, and skip the gap if necessary.
62956
        2)  A source with a gap at the beginning of the stream is loaded programatically while the player
62957
            is in a playing state. To catch this case, it's important that our one-time play listener is setup
62958
            even if the player is in a playing state
62959
      */
62960
 
62961
            this.tech_.one('play', playHandler); // Define the dispose function to clean up our events
62962
 
62963
            this.dispose = () => {
62964
                this.clearSeekingAppendCheck_();
62965
                this.logger_('dispose');
62966
                this.tech_.off('waiting', waitingHandler);
62967
                this.tech_.off(timerCancelEvents, cancelTimerHandler);
62968
                this.tech_.off('canplay', canPlayHandler);
62969
                this.tech_.off('play', playHandler);
62970
                this.tech_.off('seeking', this.watchForBadSeeking_);
62971
                this.tech_.off('seeked', this.clearSeekingAppendCheck_);
62972
                loaderTypes.forEach(type => {
62973
                    pc[`${type}SegmentLoader_`].off('appendsdone', loaderChecks[type].updateend);
62974
                    pc[`${type}SegmentLoader_`].off('playlistupdate', loaderChecks[type].reset);
62975
                    this.tech_.off(['seeked', 'seeking'], loaderChecks[type].reset);
62976
                });
62977
                if (this.checkCurrentTimeTimeout_) {
62978
                    window.clearTimeout(this.checkCurrentTimeTimeout_);
62979
                }
62980
                this.resetTimeUpdate_();
62981
            };
62982
        }
62983
        /**
62984
         * Periodically check current time to see if playback stopped
62985
         *
62986
         * @private
62987
         */
62988
 
62989
        monitorCurrentTime_() {
62990
            this.checkCurrentTime_();
62991
            if (this.checkCurrentTimeTimeout_) {
62992
                window.clearTimeout(this.checkCurrentTimeTimeout_);
62993
            } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
62994
 
62995
            this.checkCurrentTimeTimeout_ = window.setTimeout(this.monitorCurrentTime_.bind(this), 250);
62996
        }
62997
        /**
62998
         * Reset stalled download stats for a specific type of loader
62999
         *
63000
         * @param {string} type
63001
         *        The segment loader type to check.
63002
         *
63003
         * @listens SegmentLoader#playlistupdate
63004
         * @listens Tech#seeking
63005
         * @listens Tech#seeked
63006
         */
63007
 
63008
        resetSegmentDownloads_(type) {
63009
            const loader = this.playlistController_[`${type}SegmentLoader_`];
63010
            if (this[`${type}StalledDownloads_`] > 0) {
63011
                this.logger_(`resetting possible stalled download count for ${type} loader`);
63012
            }
63013
            this[`${type}StalledDownloads_`] = 0;
63014
            this[`${type}Buffered_`] = loader.buffered_();
63015
        }
63016
        /**
63017
         * Checks on every segment `appendsdone` to see
63018
         * if segment appends are making progress. If they are not
63019
         * and we are still downloading bytes. We exclude the playlist.
63020
         *
63021
         * @param {string} type
63022
         *        The segment loader type to check.
63023
         *
63024
         * @listens SegmentLoader#appendsdone
63025
         */
63026
 
63027
        checkSegmentDownloads_(type) {
63028
            const pc = this.playlistController_;
63029
            const loader = pc[`${type}SegmentLoader_`];
63030
            const buffered = loader.buffered_();
63031
            const isBufferedDifferent = isRangeDifferent(this[`${type}Buffered_`], buffered);
63032
            this[`${type}Buffered_`] = buffered; // if another watcher is going to fix the issue or
63033
            // the buffered value for this loader changed
63034
            // appends are working
63035
 
63036
            if (isBufferedDifferent) {
63037
                this.resetSegmentDownloads_(type);
63038
                return;
63039
            }
63040
            this[`${type}StalledDownloads_`]++;
63041
            this.logger_(`found #${this[`${type}StalledDownloads_`]} ${type} appends that did not increase buffer (possible stalled download)`, {
63042
                playlistId: loader.playlist_ && loader.playlist_.id,
63043
                buffered: timeRangesToArray(buffered)
63044
            }); // after 10 possibly stalled appends with no reset, exclude
63045
 
63046
            if (this[`${type}StalledDownloads_`] < 10) {
63047
                return;
63048
            }
63049
            this.logger_(`${type} loader stalled download exclusion`);
63050
            this.resetSegmentDownloads_(type);
63051
            this.tech_.trigger({
63052
                type: 'usage',
63053
                name: `vhs-${type}-download-exclusion`
63054
            });
63055
            if (type === 'subtitle') {
63056
                return;
63057
            } // TODO: should we exclude audio tracks rather than main tracks
63058
            // when type is audio?
63059
 
63060
            pc.excludePlaylist({
63061
                error: {
63062
                    message: `Excessive ${type} segment downloading detected.`
63063
                },
63064
                playlistExclusionDuration: Infinity
63065
            });
63066
        }
63067
        /**
63068
         * The purpose of this function is to emulate the "waiting" event on
63069
         * browsers that do not emit it when they are waiting for more
63070
         * data to continue playback
63071
         *
63072
         * @private
63073
         */
63074
 
63075
        checkCurrentTime_() {
63076
            if (this.tech_.paused() || this.tech_.seeking()) {
63077
                return;
63078
            }
63079
            const currentTime = this.tech_.currentTime();
63080
            const buffered = this.tech_.buffered();
63081
            if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) {
63082
                // If current time is at the end of the final buffered region, then any playback
63083
                // stall is most likely caused by buffering in a low bandwidth environment. The tech
63084
                // should fire a `waiting` event in this scenario, but due to browser and tech
63085
                // inconsistencies. Calling `techWaiting_` here allows us to simulate
63086
                // responding to a native `waiting` event when the tech fails to emit one.
63087
                return this.techWaiting_();
63088
            }
63089
            if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
63090
                this.consecutiveUpdates++;
63091
                this.waiting_();
63092
            } else if (currentTime === this.lastRecordedTime) {
63093
                this.consecutiveUpdates++;
63094
            } else {
63095
                this.consecutiveUpdates = 0;
63096
                this.lastRecordedTime = currentTime;
63097
            }
63098
        }
63099
        /**
63100
         * Resets the 'timeupdate' mechanism designed to detect that we are stalled
63101
         *
63102
         * @private
63103
         */
63104
 
63105
        resetTimeUpdate_() {
63106
            this.consecutiveUpdates = 0;
63107
        }
63108
        /**
63109
         * Fixes situations where there's a bad seek
63110
         *
63111
         * @return {boolean} whether an action was taken to fix the seek
63112
         * @private
63113
         */
63114
 
63115
        fixesBadSeeks_() {
63116
            const seeking = this.tech_.seeking();
63117
            if (!seeking) {
63118
                return false;
63119
            } // TODO: It's possible that these seekable checks should be moved out of this function
63120
            // and into a function that runs on seekablechange. It's also possible that we only need
63121
            // afterSeekableWindow as the buffered check at the bottom is good enough to handle before
63122
            // seekable range.
63123
 
63124
            const seekable = this.seekable();
63125
            const currentTime = this.tech_.currentTime();
63126
            const isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow);
63127
            let seekTo;
63128
            if (isAfterSeekableRange) {
63129
                const seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
63130
 
63131
                seekTo = seekableEnd;
63132
            }
63133
            if (this.beforeSeekableWindow_(seekable, currentTime)) {
63134
                const seekableStart = seekable.start(0); // sync to the beginning of the live window
63135
                // provide a buffer of .1 seconds to handle rounding/imprecise numbers
63136
 
63137
                seekTo = seekableStart + (
63138
                    // if the playlist is too short and the seekable range is an exact time (can
63139
                    // happen in live with a 3 segment playlist), then don't use a time delta
63140
                    seekableStart === seekable.end(0) ? 0 : SAFE_TIME_DELTA);
63141
            }
63142
            if (typeof seekTo !== 'undefined') {
63143
                this.logger_(`Trying to seek outside of seekable at time ${currentTime} with ` + `seekable range ${printableRange(seekable)}. Seeking to ` + `${seekTo}.`);
63144
                this.tech_.setCurrentTime(seekTo);
63145
                return true;
63146
            }
63147
            const sourceUpdater = this.playlistController_.sourceUpdater_;
63148
            const buffered = this.tech_.buffered();
63149
            const audioBuffered = sourceUpdater.audioBuffer ? sourceUpdater.audioBuffered() : null;
63150
            const videoBuffered = sourceUpdater.videoBuffer ? sourceUpdater.videoBuffered() : null;
63151
            const media = this.media(); // verify that at least two segment durations or one part duration have been
63152
            // appended before checking for a gap.
63153
 
63154
            const minAppendedDuration = media.partTargetDuration ? media.partTargetDuration : (media.targetDuration - TIME_FUDGE_FACTOR) * 2; // verify that at least two segment durations have been
63155
            // appended before checking for a gap.
63156
 
63157
            const bufferedToCheck = [audioBuffered, videoBuffered];
63158
            for (let i = 0; i < bufferedToCheck.length; i++) {
63159
                // skip null buffered
63160
                if (!bufferedToCheck[i]) {
63161
                    continue;
63162
                }
63163
                const timeAhead = timeAheadOf(bufferedToCheck[i], currentTime); // if we are less than two video/audio segment durations or one part
63164
                // duration behind we haven't appended enough to call this a bad seek.
63165
 
63166
                if (timeAhead < minAppendedDuration) {
63167
                    return false;
63168
                }
63169
            }
63170
            const nextRange = findNextRange(buffered, currentTime); // we have appended enough content, but we don't have anything buffered
63171
            // to seek over the gap
63172
 
63173
            if (nextRange.length === 0) {
63174
                return false;
63175
            }
63176
            seekTo = nextRange.start(0) + SAFE_TIME_DELTA;
63177
            this.logger_(`Buffered region starts (${nextRange.start(0)}) ` + ` just beyond seek point (${currentTime}). Seeking to ${seekTo}.`);
63178
            this.tech_.setCurrentTime(seekTo);
63179
            return true;
63180
        }
63181
        /**
63182
         * Handler for situations when we determine the player is waiting.
63183
         *
63184
         * @private
63185
         */
63186
 
63187
        waiting_() {
63188
            if (this.techWaiting_()) {
63189
                return;
63190
            } // All tech waiting checks failed. Use last resort correction
63191
 
63192
            const currentTime = this.tech_.currentTime();
63193
            const buffered = this.tech_.buffered();
63194
            const currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered
63195
            // region with no indication that anything is amiss (seen in Firefox). Seeking to
63196
            // currentTime is usually enough to kickstart the player. This checks that the player
63197
            // is currently within a buffered region before attempting a corrective seek.
63198
            // Chrome does not appear to continue `timeupdate` events after a `waiting` event
63199
            // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
63200
            // make sure there is ~3 seconds of forward buffer before taking any corrective action
63201
            // to avoid triggering an `unknownwaiting` event when the network is slow.
63202
 
63203
            if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
63204
                this.resetTimeUpdate_();
63205
                this.tech_.setCurrentTime(currentTime);
63206
                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
63207
 
63208
                this.tech_.trigger({
63209
                    type: 'usage',
63210
                    name: 'vhs-unknown-waiting'
63211
                });
63212
                return;
63213
            }
63214
        }
63215
        /**
63216
         * Handler for situations when the tech fires a `waiting` event
63217
         *
63218
         * @return {boolean}
63219
         *         True if an action (or none) was needed to correct the waiting. False if no
63220
         *         checks passed
63221
         * @private
63222
         */
63223
 
63224
        techWaiting_() {
63225
            const seekable = this.seekable();
63226
            const currentTime = this.tech_.currentTime();
63227
            if (this.tech_.seeking()) {
63228
                // Tech is seeking or already waiting on another action, no action needed
63229
                return true;
63230
            }
63231
            if (this.beforeSeekableWindow_(seekable, currentTime)) {
63232
                const livePoint = seekable.end(seekable.length - 1);
63233
                this.logger_(`Fell out of live window at time ${currentTime}. Seeking to ` + `live point (seekable end) ${livePoint}`);
63234
                this.resetTimeUpdate_();
63235
                this.tech_.setCurrentTime(livePoint); // live window resyncs may be useful for monitoring QoS
63236
 
63237
                this.tech_.trigger({
63238
                    type: 'usage',
63239
                    name: 'vhs-live-resync'
63240
                });
63241
                return true;
63242
            }
63243
            const sourceUpdater = this.tech_.vhs.playlistController_.sourceUpdater_;
63244
            const buffered = this.tech_.buffered();
63245
            const videoUnderflow = this.videoUnderflow_({
63246
                audioBuffered: sourceUpdater.audioBuffered(),
63247
                videoBuffered: sourceUpdater.videoBuffered(),
63248
                currentTime
63249
            });
63250
            if (videoUnderflow) {
63251
                // Even though the video underflowed and was stuck in a gap, the audio overplayed
63252
                // the gap, leading currentTime into a buffered range. Seeking to currentTime
63253
                // allows the video to catch up to the audio position without losing any audio
63254
                // (only suffering ~3 seconds of frozen video and a pause in audio playback).
63255
                this.resetTimeUpdate_();
63256
                this.tech_.setCurrentTime(currentTime); // video underflow may be useful for monitoring QoS
63257
 
63258
                this.tech_.trigger({
63259
                    type: 'usage',
63260
                    name: 'vhs-video-underflow'
63261
                });
63262
                return true;
63263
            }
63264
            const nextRange = findNextRange(buffered, currentTime); // check for gap
63265
 
63266
            if (nextRange.length > 0) {
63267
                this.logger_(`Stopped at ${currentTime} and seeking to ${nextRange.start(0)}`);
63268
                this.resetTimeUpdate_();
63269
                this.skipTheGap_(currentTime);
63270
                return true;
63271
            } // All checks failed. Returning false to indicate failure to correct waiting
63272
 
63273
            return false;
63274
        }
63275
        afterSeekableWindow_(seekable, currentTime, playlist, allowSeeksWithinUnsafeLiveWindow = false) {
63276
            if (!seekable.length) {
63277
                // we can't make a solid case if there's no seekable, default to false
63278
                return false;
63279
            }
63280
            let allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA;
63281
            const isLive = !playlist.endList;
63282
            const isLLHLS = typeof playlist.partTargetDuration === 'number';
63283
            if (isLive && (isLLHLS || allowSeeksWithinUnsafeLiveWindow)) {
63284
                allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3;
63285
            }
63286
            if (currentTime > allowedEnd) {
63287
                return true;
63288
            }
63289
            return false;
63290
        }
63291
        beforeSeekableWindow_(seekable, currentTime) {
63292
            if (seekable.length &&
63293
                // can't fall before 0 and 0 seekable start identifies VOD stream
63294
                seekable.start(0) > 0 && currentTime < seekable.start(0) - this.liveRangeSafeTimeDelta) {
63295
                return true;
63296
            }
63297
            return false;
63298
        }
63299
        videoUnderflow_({
63300
                            videoBuffered,
63301
                            audioBuffered,
63302
                            currentTime
63303
                        }) {
63304
            // audio only content will not have video underflow :)
63305
            if (!videoBuffered) {
63306
                return;
63307
            }
63308
            let gap; // find a gap in demuxed content.
63309
 
63310
            if (videoBuffered.length && audioBuffered.length) {
63311
                // in Chrome audio will continue to play for ~3s when we run out of video
63312
                // so we have to check that the video buffer did have some buffer in the
63313
                // past.
63314
                const lastVideoRange = findRange(videoBuffered, currentTime - 3);
63315
                const videoRange = findRange(videoBuffered, currentTime);
63316
                const audioRange = findRange(audioBuffered, currentTime);
63317
                if (audioRange.length && !videoRange.length && lastVideoRange.length) {
63318
                    gap = {
63319
                        start: lastVideoRange.end(0),
63320
                        end: audioRange.end(0)
63321
                    };
63322
                } // find a gap in muxed content.
63323
            } else {
63324
                const nextRange = findNextRange(videoBuffered, currentTime); // Even if there is no available next range, there is still a possibility we are
63325
                // stuck in a gap due to video underflow.
63326
 
63327
                if (!nextRange.length) {
63328
                    gap = this.gapFromVideoUnderflow_(videoBuffered, currentTime);
63329
                }
63330
            }
63331
            if (gap) {
63332
                this.logger_(`Encountered a gap in video from ${gap.start} to ${gap.end}. ` + `Seeking to current time ${currentTime}`);
63333
                return true;
63334
            }
63335
            return false;
63336
        }
63337
        /**
63338
         * Timer callback. If playback still has not proceeded, then we seek
63339
         * to the start of the next buffered region.
63340
         *
63341
         * @private
63342
         */
63343
 
63344
        skipTheGap_(scheduledCurrentTime) {
63345
            const buffered = this.tech_.buffered();
63346
            const currentTime = this.tech_.currentTime();
63347
            const nextRange = findNextRange(buffered, currentTime);
63348
            this.resetTimeUpdate_();
63349
            if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
63350
                return;
63351
            }
63352
            this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played
63353
 
63354
            this.tech_.setCurrentTime(nextRange.start(0) + TIME_FUDGE_FACTOR);
63355
            this.tech_.trigger({
63356
                type: 'usage',
63357
                name: 'vhs-gap-skip'
63358
            });
63359
        }
63360
        gapFromVideoUnderflow_(buffered, currentTime) {
63361
            // At least in Chrome, if there is a gap in the video buffer, the audio will continue
63362
            // playing for ~3 seconds after the video gap starts. This is done to account for
63363
            // video buffer underflow/underrun (note that this is not done when there is audio
63364
            // buffer underflow/underrun -- in that case the video will stop as soon as it
63365
            // encounters the gap, as audio stalls are more noticeable/jarring to a user than
63366
            // video stalls). The player's time will reflect the playthrough of audio, so the
63367
            // time will appear as if we are in a buffered region, even if we are stuck in a
63368
            // "gap."
63369
            //
63370
            // Example:
63371
            // video buffer:   0 => 10.1, 10.2 => 20
63372
            // audio buffer:   0 => 20
63373
            // overall buffer: 0 => 10.1, 10.2 => 20
63374
            // current time: 13
63375
            //
63376
            // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
63377
            // however, the audio continued playing until it reached ~3 seconds past the gap
63378
            // (13 seconds), at which point it stops as well. Since current time is past the
63379
            // gap, findNextRange will return no ranges.
63380
            //
63381
            // To check for this issue, we see if there is a gap that starts somewhere within
63382
            // a 3 second range (3 seconds +/- 1 second) back from our current time.
63383
            const gaps = findGaps(buffered);
63384
            for (let i = 0; i < gaps.length; i++) {
63385
                const start = gaps.start(i);
63386
                const end = gaps.end(i); // gap is starts no more than 4 seconds back
63387
 
63388
                if (currentTime - start < 4 && currentTime - start > 2) {
63389
                    return {
63390
                        start,
63391
                        end
63392
                    };
63393
                }
63394
            }
63395
            return null;
63396
        }
63397
    }
63398
    const defaultOptions = {
63399
        errorInterval: 30,
63400
        getSource(next) {
63401
            const tech = this.tech({
63402
                IWillNotUseThisInPlugins: true
63403
            });
63404
            const sourceObj = tech.currentSource_ || this.currentSource();
63405
            return next(sourceObj);
63406
        }
63407
    };
63408
    /**
63409
     * Main entry point for the plugin
63410
     *
63411
     * @param {Player} player a reference to a videojs Player instance
63412
     * @param {Object} [options] an object with plugin options
63413
     * @private
63414
     */
63415
 
63416
    const initPlugin = function (player, options) {
63417
        let lastCalled = 0;
63418
        let seekTo = 0;
63419
        const localOptions = merge(defaultOptions, options);
63420
        player.ready(() => {
63421
            player.trigger({
63422
                type: 'usage',
63423
                name: 'vhs-error-reload-initialized'
63424
            });
63425
        });
63426
        /**
63427
         * Player modifications to perform that must wait until `loadedmetadata`
63428
         * has been triggered
63429
         *
63430
         * @private
63431
         */
63432
 
63433
        const loadedMetadataHandler = function () {
63434
            if (seekTo) {
63435
                player.currentTime(seekTo);
63436
            }
63437
        };
63438
        /**
63439
         * Set the source on the player element, play, and seek if necessary
63440
         *
63441
         * @param {Object} sourceObj An object specifying the source url and mime-type to play
63442
         * @private
63443
         */
63444
 
63445
        const setSource = function (sourceObj) {
63446
            if (sourceObj === null || sourceObj === undefined) {
63447
                return;
63448
            }
63449
            seekTo = player.duration() !== Infinity && player.currentTime() || 0;
63450
            player.one('loadedmetadata', loadedMetadataHandler);
63451
            player.src(sourceObj);
63452
            player.trigger({
63453
                type: 'usage',
63454
                name: 'vhs-error-reload'
63455
            });
63456
            player.play();
63457
        };
63458
        /**
63459
         * Attempt to get a source from either the built-in getSource function
63460
         * or a custom function provided via the options
63461
         *
63462
         * @private
63463
         */
63464
 
63465
        const errorHandler = function () {
63466
            // Do not attempt to reload the source if a source-reload occurred before
63467
            // 'errorInterval' time has elapsed since the last source-reload
63468
            if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
63469
                player.trigger({
63470
                    type: 'usage',
63471
                    name: 'vhs-error-reload-canceled'
63472
                });
63473
                return;
63474
            }
63475
            if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
63476
                videojs.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
63477
                return;
63478
            }
63479
            lastCalled = Date.now();
63480
            return localOptions.getSource.call(player, setSource);
63481
        };
63482
        /**
63483
         * Unbind any event handlers that were bound by the plugin
63484
         *
63485
         * @private
63486
         */
63487
 
63488
        const cleanupEvents = function () {
63489
            player.off('loadedmetadata', loadedMetadataHandler);
63490
            player.off('error', errorHandler);
63491
            player.off('dispose', cleanupEvents);
63492
        };
63493
        /**
63494
         * Cleanup before re-initializing the plugin
63495
         *
63496
         * @param {Object} [newOptions] an object with plugin options
63497
         * @private
63498
         */
63499
 
63500
        const reinitPlugin = function (newOptions) {
63501
            cleanupEvents();
63502
            initPlugin(player, newOptions);
63503
        };
63504
        player.on('error', errorHandler);
63505
        player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before
63506
        // initializing the plugin
63507
 
63508
        player.reloadSourceOnError = reinitPlugin;
63509
    };
63510
    /**
63511
     * Reload the source when an error is detected as long as there
63512
     * wasn't an error previously within the last 30 seconds
63513
     *
63514
     * @param {Object} [options] an object with plugin options
63515
     */
63516
 
63517
    const reloadSourceOnError = function (options) {
63518
        initPlugin(this, options);
63519
    };
63520
    var version$4 = "3.10.0";
63521
    var version$3 = "7.0.2";
63522
    var version$2 = "1.3.0";
63523
    var version$1 = "7.1.0";
63524
    var version = "4.0.1";
63525
 
63526
    /**
63527
     * @file videojs-http-streaming.js
63528
     *
63529
     * The main file for the VHS project.
63530
     * License: https://github.com/videojs/videojs-http-streaming/blob/main/LICENSE
63531
     */
63532
    const Vhs = {
63533
        PlaylistLoader,
63534
        Playlist,
63535
        utils,
63536
        STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
63537
        INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
63538
        lastBandwidthSelector,
63539
        movingAverageBandwidthSelector,
63540
        comparePlaylistBandwidth,
63541
        comparePlaylistResolution,
63542
        xhr: xhrFactory()
63543
    }; // Define getter/setters for config properties
63544
 
63545
    Object.keys(Config).forEach(prop => {
63546
        Object.defineProperty(Vhs, prop, {
63547
            get() {
63548
                videojs.log.warn(`using Vhs.${prop} is UNSAFE be sure you know what you are doing`);
63549
                return Config[prop];
63550
            },
63551
            set(value) {
63552
                videojs.log.warn(`using Vhs.${prop} is UNSAFE be sure you know what you are doing`);
63553
                if (typeof value !== 'number' || value < 0) {
63554
                    videojs.log.warn(`value of Vhs.${prop} must be greater than or equal to 0`);
63555
                    return;
63556
                }
63557
                Config[prop] = value;
63558
            }
63559
        });
63560
    });
63561
    const LOCAL_STORAGE_KEY = 'videojs-vhs';
63562
    /**
63563
     * Updates the selectedIndex of the QualityLevelList when a mediachange happens in vhs.
63564
     *
63565
     * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
63566
     * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
63567
     * @function handleVhsMediaChange
63568
     */
63569
 
63570
    const handleVhsMediaChange = function (qualityLevels, playlistLoader) {
63571
        const newPlaylist = playlistLoader.media();
63572
        let selectedIndex = -1;
63573
        for (let i = 0; i < qualityLevels.length; i++) {
63574
            if (qualityLevels[i].id === newPlaylist.id) {
63575
                selectedIndex = i;
63576
                break;
63577
            }
63578
        }
63579
        qualityLevels.selectedIndex_ = selectedIndex;
63580
        qualityLevels.trigger({
63581
            selectedIndex,
63582
            type: 'change'
63583
        });
63584
    };
63585
    /**
63586
     * Adds quality levels to list once playlist metadata is available
63587
     *
63588
     * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
63589
     * @param {Object} vhs Vhs object to listen to for media events.
63590
     * @function handleVhsLoadedMetadata
63591
     */
63592
 
63593
    const handleVhsLoadedMetadata = function (qualityLevels, vhs) {
63594
        vhs.representations().forEach(rep => {
63595
            qualityLevels.addQualityLevel(rep);
63596
        });
63597
        handleVhsMediaChange(qualityLevels, vhs.playlists);
63598
    }; // VHS is a source handler, not a tech. Make sure attempts to use it
63599
    // as one do not cause exceptions.
63600
 
63601
    Vhs.canPlaySource = function () {
63602
        return videojs.log.warn('VHS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
63603
    };
63604
    const emeKeySystems = (keySystemOptions, mainPlaylist, audioPlaylist) => {
63605
        if (!keySystemOptions) {
63606
            return keySystemOptions;
63607
        }
63608
        let codecs = {};
63609
        if (mainPlaylist && mainPlaylist.attributes && mainPlaylist.attributes.CODECS) {
63610
            codecs = unwrapCodecList(parseCodecs(mainPlaylist.attributes.CODECS));
63611
        }
63612
        if (audioPlaylist && audioPlaylist.attributes && audioPlaylist.attributes.CODECS) {
63613
            codecs.audio = audioPlaylist.attributes.CODECS;
63614
        }
63615
        const videoContentType = getMimeForCodec(codecs.video);
63616
        const audioContentType = getMimeForCodec(codecs.audio); // upsert the content types based on the selected playlist
63617
 
63618
        const keySystemContentTypes = {};
63619
        for (const keySystem in keySystemOptions) {
63620
            keySystemContentTypes[keySystem] = {};
63621
            if (audioContentType) {
63622
                keySystemContentTypes[keySystem].audioContentType = audioContentType;
63623
            }
63624
            if (videoContentType) {
63625
                keySystemContentTypes[keySystem].videoContentType = videoContentType;
63626
            } // Default to using the video playlist's PSSH even though they may be different, as
63627
            // videojs-contrib-eme will only accept one in the options.
63628
            //
63629
            // This shouldn't be an issue for most cases as early intialization will handle all
63630
            // unique PSSH values, and if they aren't, then encrypted events should have the
63631
            // specific information needed for the unique license.
63632
 
63633
            if (mainPlaylist.contentProtection && mainPlaylist.contentProtection[keySystem] && mainPlaylist.contentProtection[keySystem].pssh) {
63634
                keySystemContentTypes[keySystem].pssh = mainPlaylist.contentProtection[keySystem].pssh;
63635
            } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
63636
            // so we need to prevent overwriting the URL entirely
63637
 
63638
            if (typeof keySystemOptions[keySystem] === 'string') {
63639
                keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
63640
            }
63641
        }
63642
        return merge(keySystemOptions, keySystemContentTypes);
63643
    };
63644
    /**
63645
     * @typedef {Object} KeySystems
63646
     *
63647
     * keySystems configuration for https://github.com/videojs/videojs-contrib-eme
63648
     * Note: not all options are listed here.
63649
     *
63650
     * @property {Uint8Array} [pssh]
63651
     *           Protection System Specific Header
63652
     */
63653
 
63654
    /**
63655
     * Goes through all the playlists and collects an array of KeySystems options objects
63656
     * containing each playlist's keySystems and their pssh values, if available.
63657
     *
63658
     * @param {Object[]} playlists
63659
     *        The playlists to look through
63660
     * @param {string[]} keySystems
63661
     *        The keySystems to collect pssh values for
63662
     *
63663
     * @return {KeySystems[]}
63664
     *         An array of KeySystems objects containing available key systems and their
63665
     *         pssh values
63666
     */
63667
 
63668
    const getAllPsshKeySystemsOptions = (playlists, keySystems) => {
63669
        return playlists.reduce((keySystemsArr, playlist) => {
63670
            if (!playlist.contentProtection) {
63671
                return keySystemsArr;
63672
            }
63673
            const keySystemsOptions = keySystems.reduce((keySystemsObj, keySystem) => {
63674
                const keySystemOptions = playlist.contentProtection[keySystem];
63675
                if (keySystemOptions && keySystemOptions.pssh) {
63676
                    keySystemsObj[keySystem] = {
63677
                        pssh: keySystemOptions.pssh
63678
                    };
63679
                }
63680
                return keySystemsObj;
63681
            }, {});
63682
            if (Object.keys(keySystemsOptions).length) {
63683
                keySystemsArr.push(keySystemsOptions);
63684
            }
63685
            return keySystemsArr;
63686
        }, []);
63687
    };
63688
    /**
63689
     * Returns a promise that waits for the
63690
     * [eme plugin](https://github.com/videojs/videojs-contrib-eme) to create a key session.
63691
     *
63692
     * Works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449 in non-IE11
63693
     * browsers.
63694
     *
63695
     * As per the above ticket, this is particularly important for Chrome, where, if
63696
     * unencrypted content is appended before encrypted content and the key session has not
63697
     * been created, a MEDIA_ERR_DECODE will be thrown once the encrypted content is reached
63698
     * during playback.
63699
     *
63700
     * @param {Object} player
63701
     *        The player instance
63702
     * @param {Object[]} sourceKeySystems
63703
     *        The key systems options from the player source
63704
     * @param {Object} [audioMedia]
63705
     *        The active audio media playlist (optional)
63706
     * @param {Object[]} mainPlaylists
63707
     *        The playlists found on the main playlist object
63708
     *
63709
     * @return {Object}
63710
     *         Promise that resolves when the key session has been created
63711
     */
63712
 
63713
    const waitForKeySessionCreation = ({
63714
                                           player,
63715
                                           sourceKeySystems,
63716
                                           audioMedia,
63717
                                           mainPlaylists
63718
                                       }) => {
63719
        if (!player.eme.initializeMediaKeys) {
63720
            return Promise.resolve();
63721
        } // TODO should all audio PSSH values be initialized for DRM?
63722
        //
63723
        // All unique video rendition pssh values are initialized for DRM, but here only
63724
        // the initial audio playlist license is initialized. In theory, an encrypted
63725
        // event should be fired if the user switches to an alternative audio playlist
63726
        // where a license is required, but this case hasn't yet been tested. In addition, there
63727
        // may be many alternate audio playlists unlikely to be used (e.g., multiple different
63728
        // languages).
63729
 
63730
        const playlists = audioMedia ? mainPlaylists.concat([audioMedia]) : mainPlaylists;
63731
        const keySystemsOptionsArr = getAllPsshKeySystemsOptions(playlists, Object.keys(sourceKeySystems));
63732
        const initializationFinishedPromises = [];
63733
        const keySessionCreatedPromises = []; // Since PSSH values are interpreted as initData, EME will dedupe any duplicates. The
63734
        // only place where it should not be deduped is for ms-prefixed APIs, but
63735
        // the existence of modern EME APIs in addition to
63736
        // ms-prefixed APIs on Edge should prevent this from being a concern.
63737
        // initializeMediaKeys also won't use the webkit-prefixed APIs.
63738
 
63739
        keySystemsOptionsArr.forEach(keySystemsOptions => {
63740
            keySessionCreatedPromises.push(new Promise((resolve, reject) => {
63741
                player.tech_.one('keysessioncreated', resolve);
63742
            }));
63743
            initializationFinishedPromises.push(new Promise((resolve, reject) => {
63744
                player.eme.initializeMediaKeys({
63745
                    keySystems: keySystemsOptions
63746
                }, err => {
63747
                    if (err) {
63748
                        reject(err);
63749
                        return;
63750
                    }
63751
                    resolve();
63752
                });
63753
            }));
63754
        }); // The reasons Promise.race is chosen over Promise.any:
63755
        //
63756
        // * Promise.any is only available in Safari 14+.
63757
        // * None of these promises are expected to reject. If they do reject, it might be
63758
        //   better here for the race to surface the rejection, rather than mask it by using
63759
        //   Promise.any.
63760
 
63761
        return Promise.race([
63762
            // If a session was previously created, these will all finish resolving without
63763
            // creating a new session, otherwise it will take until the end of all license
63764
            // requests, which is why the key session check is used (to make setup much faster).
63765
            Promise.all(initializationFinishedPromises),
63766
            // Once a single session is created, the browser knows DRM will be used.
63767
            Promise.race(keySessionCreatedPromises)]);
63768
    };
63769
    /**
63770
     * If the [eme](https://github.com/videojs/videojs-contrib-eme) plugin is available, and
63771
     * there are keySystems on the source, sets up source options to prepare the source for
63772
     * eme.
63773
     *
63774
     * @param {Object} player
63775
     *        The player instance
63776
     * @param {Object[]} sourceKeySystems
63777
     *        The key systems options from the player source
63778
     * @param {Object} media
63779
     *        The active media playlist
63780
     * @param {Object} [audioMedia]
63781
     *        The active audio media playlist (optional)
63782
     *
63783
     * @return {boolean}
63784
     *         Whether or not options were configured and EME is available
63785
     */
63786
 
63787
    const setupEmeOptions = ({
63788
                                 player,
63789
                                 sourceKeySystems,
63790
                                 media,
63791
                                 audioMedia
63792
                             }) => {
63793
        const sourceOptions = emeKeySystems(sourceKeySystems, media, audioMedia);
63794
        if (!sourceOptions) {
63795
            return false;
63796
        }
63797
        player.currentSource().keySystems = sourceOptions; // eme handles the rest of the setup, so if it is missing
63798
        // do nothing.
63799
 
63800
        if (sourceOptions && !player.eme) {
63801
            videojs.log.warn('DRM encrypted source cannot be decrypted without a DRM plugin');
63802
            return false;
63803
        }
63804
        return true;
63805
    };
63806
    const getVhsLocalStorage = () => {
63807
        if (!window.localStorage) {
63808
            return null;
63809
        }
63810
        const storedObject = window.localStorage.getItem(LOCAL_STORAGE_KEY);
63811
        if (!storedObject) {
63812
            return null;
63813
        }
63814
        try {
63815
            return JSON.parse(storedObject);
63816
        } catch (e) {
63817
            // someone may have tampered with the value
63818
            return null;
63819
        }
63820
    };
63821
    const updateVhsLocalStorage = options => {
63822
        if (!window.localStorage) {
63823
            return false;
63824
        }
63825
        let objectToStore = getVhsLocalStorage();
63826
        objectToStore = objectToStore ? merge(objectToStore, options) : options;
63827
        try {
63828
            window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(objectToStore));
63829
        } catch (e) {
63830
            // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where
63831
            // storage is set to 0).
63832
            // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions
63833
            // No need to perform any operation.
63834
            return false;
63835
        }
63836
        return objectToStore;
63837
    };
63838
    /**
63839
     * Parses VHS-supported media types from data URIs. See
63840
     * https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
63841
     * for information on data URIs.
63842
     *
63843
     * @param {string} dataUri
63844
     *        The data URI
63845
     *
63846
     * @return {string|Object}
63847
     *         The parsed object/string, or the original string if no supported media type
63848
     *         was found
63849
     */
63850
 
63851
    const expandDataUri = dataUri => {
63852
        if (dataUri.toLowerCase().indexOf('data:application/vnd.videojs.vhs+json,') === 0) {
63853
            return JSON.parse(dataUri.substring(dataUri.indexOf(',') + 1));
63854
        } // no known case for this data URI, return the string as-is
63855
 
63856
        return dataUri;
63857
    };
63858
    /**
63859
     * Adds a request hook to an xhr object
63860
     *
63861
     * @param {Object} xhr object to add the onRequest hook to
63862
     * @param {function} callback hook function for an xhr request
63863
     */
63864
 
63865
    const addOnRequestHook = (xhr, callback) => {
63866
        if (!xhr._requestCallbackSet) {
63867
            xhr._requestCallbackSet = new Set();
63868
        }
63869
        xhr._requestCallbackSet.add(callback);
63870
    };
63871
    /**
63872
     * Adds a response hook to an xhr object
63873
     *
63874
     * @param {Object} xhr object to add the onResponse hook to
63875
     * @param {function} callback hook function for an xhr response
63876
     */
63877
 
63878
    const addOnResponseHook = (xhr, callback) => {
63879
        if (!xhr._responseCallbackSet) {
63880
            xhr._responseCallbackSet = new Set();
63881
        }
63882
        xhr._responseCallbackSet.add(callback);
63883
    };
63884
    /**
63885
     * Removes a request hook on an xhr object, deletes the onRequest set if empty.
63886
     *
63887
     * @param {Object} xhr object to remove the onRequest hook from
63888
     * @param {function} callback hook function to remove
63889
     */
63890
 
63891
    const removeOnRequestHook = (xhr, callback) => {
63892
        if (!xhr._requestCallbackSet) {
63893
            return;
63894
        }
63895
        xhr._requestCallbackSet.delete(callback);
63896
        if (!xhr._requestCallbackSet.size) {
63897
            delete xhr._requestCallbackSet;
63898
        }
63899
    };
63900
    /**
63901
     * Removes a response hook on an xhr object, deletes the onResponse set if empty.
63902
     *
63903
     * @param {Object} xhr object to remove the onResponse hook from
63904
     * @param {function} callback hook function to remove
63905
     */
63906
 
63907
    const removeOnResponseHook = (xhr, callback) => {
63908
        if (!xhr._responseCallbackSet) {
63909
            return;
63910
        }
63911
        xhr._responseCallbackSet.delete(callback);
63912
        if (!xhr._responseCallbackSet.size) {
63913
            delete xhr._responseCallbackSet;
63914
        }
63915
    };
63916
    /**
63917
     * Whether the browser has built-in HLS support.
63918
     */
63919
 
63920
    Vhs.supportsNativeHls = function () {
63921
        if (!document || !document.createElement) {
63922
            return false;
63923
        }
63924
        const video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't
63925
 
63926
        if (!videojs.getTech('Html5').isSupported()) {
63927
            return false;
63928
        } // HLS manifests can go by many mime-types
63929
 
63930
        const canPlay = [
63931
            // Apple santioned
63932
            'application/vnd.apple.mpegurl',
63933
            // Apple sanctioned for backwards compatibility
63934
            'audio/mpegurl',
63935
            // Very common
63936
            'audio/x-mpegurl',
63937
            // Very common
63938
            'application/x-mpegurl',
63939
            // Included for completeness
63940
            'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
63941
        return canPlay.some(function (canItPlay) {
63942
            return /maybe|probably/i.test(video.canPlayType(canItPlay));
63943
        });
63944
    }();
63945
    Vhs.supportsNativeDash = function () {
63946
        if (!document || !document.createElement || !videojs.getTech('Html5').isSupported()) {
63947
            return false;
63948
        }
63949
        return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml'));
63950
    }();
63951
    Vhs.supportsTypeNatively = type => {
63952
        if (type === 'hls') {
63953
            return Vhs.supportsNativeHls;
63954
        }
63955
        if (type === 'dash') {
63956
            return Vhs.supportsNativeDash;
63957
        }
63958
        return false;
63959
    };
63960
    /**
63961
     * VHS is a source handler, not a tech. Make sure attempts to use it
63962
     * as one do not cause exceptions.
63963
     */
63964
 
63965
    Vhs.isSupported = function () {
63966
        return videojs.log.warn('VHS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
63967
    };
63968
    /**
63969
     * A global function for setting an onRequest hook
63970
     *
63971
     * @param {function} callback for request modifiction
63972
     */
63973
 
63974
    Vhs.xhr.onRequest = function (callback) {
63975
        addOnRequestHook(Vhs.xhr, callback);
63976
    };
63977
    /**
63978
     * A global function for setting an onResponse hook
63979
     *
63980
     * @param {callback} callback for response data retrieval
63981
     */
63982
 
63983
    Vhs.xhr.onResponse = function (callback) {
63984
        addOnResponseHook(Vhs.xhr, callback);
63985
    };
63986
    /**
63987
     * Deletes a global onRequest callback if it exists
63988
     *
63989
     * @param {function} callback to delete from the global set
63990
     */
63991
 
63992
    Vhs.xhr.offRequest = function (callback) {
63993
        removeOnRequestHook(Vhs.xhr, callback);
63994
    };
63995
    /**
63996
     * Deletes a global onResponse callback if it exists
63997
     *
63998
     * @param {function} callback to delete from the global set
63999
     */
64000
 
64001
    Vhs.xhr.offResponse = function (callback) {
64002
        removeOnResponseHook(Vhs.xhr, callback);
64003
    };
64004
    const Component = videojs.getComponent('Component');
64005
    /**
64006
     * The Vhs Handler object, where we orchestrate all of the parts
64007
     * of VHS to interact with video.js
64008
     *
64009
     * @class VhsHandler
64010
     * @extends videojs.Component
64011
     * @param {Object} source the soruce object
64012
     * @param {Tech} tech the parent tech object
64013
     * @param {Object} options optional and required options
64014
     */
64015
 
64016
    class VhsHandler extends Component {
64017
        constructor(source, tech, options) {
64018
            super(tech, options.vhs); // if a tech level `initialBandwidth` option was passed
64019
            // use that over the VHS level `bandwidth` option
64020
 
64021
            if (typeof options.initialBandwidth === 'number') {
64022
                this.options_.bandwidth = options.initialBandwidth;
64023
            }
64024
            this.logger_ = logger('VhsHandler'); // we need access to the player in some cases,
64025
            // so, get it from Video.js via the `playerId`
64026
 
64027
            if (tech.options_ && tech.options_.playerId) {
64028
                const _player = videojs.getPlayer(tech.options_.playerId);
64029
                this.player_ = _player;
64030
            }
64031
            this.tech_ = tech;
64032
            this.source_ = source;
64033
            this.stats = {};
64034
            this.ignoreNextSeekingEvent_ = false;
64035
            this.setOptions_();
64036
            if (this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) {
64037
                tech.overrideNativeAudioTracks(true);
64038
                tech.overrideNativeVideoTracks(true);
64039
            } else if (this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
64040
                // overriding native VHS only works if audio tracks have been emulated
64041
                // error early if we're misconfigured
64042
                throw new Error('Overriding native VHS requires emulated tracks. ' + 'See https://git.io/vMpjB');
64043
            } // listen for fullscreenchange events for this player so that we
64044
            // can adjust our quality selection quickly
64045
 
64046
            this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], event => {
64047
                const fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
64048
                if (fullscreenElement && fullscreenElement.contains(this.tech_.el())) {
64049
                    this.playlistController_.fastQualityChange_();
64050
                } else {
64051
                    // When leaving fullscreen, since the in page pixel dimensions should be smaller
64052
                    // than full screen, see if there should be a rendition switch down to preserve
64053
                    // bandwidth.
64054
                    this.playlistController_.checkABR_();
64055
                }
64056
            });
64057
            this.on(this.tech_, 'seeking', function () {
64058
                if (this.ignoreNextSeekingEvent_) {
64059
                    this.ignoreNextSeekingEvent_ = false;
64060
                    return;
64061
                }
64062
                this.setCurrentTime(this.tech_.currentTime());
64063
            });
64064
            this.on(this.tech_, 'error', function () {
64065
                // verify that the error was real and we are loaded
64066
                // enough to have pc loaded.
64067
                if (this.tech_.error() && this.playlistController_) {
64068
                    this.playlistController_.pauseLoading();
64069
                }
64070
            });
64071
            this.on(this.tech_, 'play', this.play);
64072
        }
64073
        /**
64074
         * Set VHS options based on options from configuration, as well as partial
64075
         * options to be passed at a later time.
64076
         *
64077
         * @param {Object} options A partial chunk of config options
64078
         */
64079
 
64080
        setOptions_(options = {}) {
64081
            this.options_ = merge(this.options_, options); // defaults
64082
 
64083
            this.options_.withCredentials = this.options_.withCredentials || false;
64084
            this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
64085
            this.options_.useDevicePixelRatio = this.options_.useDevicePixelRatio || false;
64086
            this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false;
64087
            this.options_.useForcedSubtitles = this.options_.useForcedSubtitles || false;
64088
            this.options_.useNetworkInformationApi = this.options_.useNetworkInformationApi || false;
64089
            this.options_.useDtsForTimestampOffset = this.options_.useDtsForTimestampOffset || false;
64090
            this.options_.customTagParsers = this.options_.customTagParsers || [];
64091
            this.options_.customTagMappers = this.options_.customTagMappers || [];
64092
            this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;
64093
            this.options_.llhls = this.options_.llhls === false ? false : true;
64094
            this.options_.bufferBasedABR = this.options_.bufferBasedABR || false;
64095
            if (typeof this.options_.playlistExclusionDuration !== 'number') {
64096
                this.options_.playlistExclusionDuration = 60;
64097
            }
64098
            if (typeof this.options_.bandwidth !== 'number') {
64099
                if (this.options_.useBandwidthFromLocalStorage) {
64100
                    const storedObject = getVhsLocalStorage();
64101
                    if (storedObject && storedObject.bandwidth) {
64102
                        this.options_.bandwidth = storedObject.bandwidth;
64103
                        this.tech_.trigger({
64104
                            type: 'usage',
64105
                            name: 'vhs-bandwidth-from-local-storage'
64106
                        });
64107
                    }
64108
                    if (storedObject && storedObject.throughput) {
64109
                        this.options_.throughput = storedObject.throughput;
64110
                        this.tech_.trigger({
64111
                            type: 'usage',
64112
                            name: 'vhs-throughput-from-local-storage'
64113
                        });
64114
                    }
64115
                }
64116
            } // if bandwidth was not set by options or pulled from local storage, start playlist
64117
            // selection at a reasonable bandwidth
64118
 
64119
            if (typeof this.options_.bandwidth !== 'number') {
64120
                this.options_.bandwidth = Config.INITIAL_BANDWIDTH;
64121
            } // If the bandwidth number is unchanged from the initial setting
64122
            // then this takes precedence over the enableLowInitialPlaylist option
64123
 
64124
            this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src
64125
 
64126
            ['withCredentials', 'useDevicePixelRatio', 'limitRenditionByPlayerDimensions', 'bandwidth', 'customTagParsers', 'customTagMappers', 'cacheEncryptionKeys', 'playlistSelector', 'initialPlaylistSelector', 'bufferBasedABR', 'liveRangeSafeTimeDelta', 'llhls', 'useForcedSubtitles', 'useNetworkInformationApi', 'useDtsForTimestampOffset', 'exactManifestTimings', 'leastPixelDiffSelector'].forEach(option => {
64127
                if (typeof this.source_[option] !== 'undefined') {
64128
                    this.options_[option] = this.source_[option];
64129
                }
64130
            });
64131
            this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
64132
            this.useDevicePixelRatio = this.options_.useDevicePixelRatio;
64133
        } // alias for public method to set options
64134
 
64135
        setOptions(options = {}) {
64136
            this.setOptions_(options);
64137
        }
64138
        /**
64139
         * called when player.src gets called, handle a new source
64140
         *
64141
         * @param {Object} src the source object to handle
64142
         */
64143
 
64144
        src(src, type) {
64145
            // do nothing if the src is falsey
64146
            if (!src) {
64147
                return;
64148
            }
64149
            this.setOptions_(); // add main playlist controller options
64150
 
64151
            this.options_.src = expandDataUri(this.source_.src);
64152
            this.options_.tech = this.tech_;
64153
            this.options_.externVhs = Vhs;
64154
            this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update the tech
64155
 
64156
            this.options_.seekTo = time => {
64157
                this.tech_.setCurrentTime(time);
64158
            };
64159
            this.playlistController_ = new PlaylistController(this.options_);
64160
            const playbackWatcherOptions = merge({
64161
                liveRangeSafeTimeDelta: SAFE_TIME_DELTA
64162
            }, this.options_, {
64163
                seekable: () => this.seekable(),
64164
                media: () => this.playlistController_.media(),
64165
                playlistController: this.playlistController_
64166
            });
64167
            this.playbackWatcher_ = new PlaybackWatcher(playbackWatcherOptions);
64168
            this.playlistController_.on('error', () => {
64169
                const player = videojs.players[this.tech_.options_.playerId];
64170
                let error = this.playlistController_.error;
64171
                if (typeof error === 'object' && !error.code) {
64172
                    error.code = 3;
64173
                } else if (typeof error === 'string') {
64174
                    error = {
64175
                        message: error,
64176
                        code: 3
64177
                    };
64178
                }
64179
                player.error(error);
64180
            });
64181
            const defaultSelector = this.options_.bufferBasedABR ? Vhs.movingAverageBandwidthSelector(0.55) : Vhs.STANDARD_PLAYLIST_SELECTOR; // `this` in selectPlaylist should be the VhsHandler for backwards
64182
            // compatibility with < v2
64183
 
64184
            this.playlistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : defaultSelector.bind(this);
64185
            this.playlistController_.selectInitialPlaylist = Vhs.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2
64186
 
64187
            this.playlists = this.playlistController_.mainPlaylistLoader_;
64188
            this.mediaSource = this.playlistController_.mediaSource; // Proxy assignment of some properties to the main playlist
64189
            // controller. Using a custom property for backwards compatibility
64190
            // with < v2
64191
 
64192
            Object.defineProperties(this, {
64193
                selectPlaylist: {
64194
                    get() {
64195
                        return this.playlistController_.selectPlaylist;
64196
                    },
64197
                    set(selectPlaylist) {
64198
                        this.playlistController_.selectPlaylist = selectPlaylist.bind(this);
64199
                    }
64200
                },
64201
                throughput: {
64202
                    get() {
64203
                        return this.playlistController_.mainSegmentLoader_.throughput.rate;
64204
                    },
64205
                    set(throughput) {
64206
                        this.playlistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value
64207
                        // for the cumulative average
64208
 
64209
                        this.playlistController_.mainSegmentLoader_.throughput.count = 1;
64210
                    }
64211
                },
64212
                bandwidth: {
64213
                    get() {
64214
                        let playerBandwidthEst = this.playlistController_.mainSegmentLoader_.bandwidth;
64215
                        const networkInformation = window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection;
64216
                        const tenMbpsAsBitsPerSecond = 10e6;
64217
                        if (this.options_.useNetworkInformationApi && networkInformation) {
64218
                            // downlink returns Mbps
64219
                            // https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/downlink
64220
                            const networkInfoBandwidthEstBitsPerSec = networkInformation.downlink * 1000 * 1000; // downlink maxes out at 10 Mbps. In the event that both networkInformationApi and the player
64221
                            // estimate a bandwidth greater than 10 Mbps, use the larger of the two estimates to ensure that
64222
                            // high quality streams are not filtered out.
64223
 
64224
                            if (networkInfoBandwidthEstBitsPerSec >= tenMbpsAsBitsPerSecond && playerBandwidthEst >= tenMbpsAsBitsPerSecond) {
64225
                                playerBandwidthEst = Math.max(playerBandwidthEst, networkInfoBandwidthEstBitsPerSec);
64226
                            } else {
64227
                                playerBandwidthEst = networkInfoBandwidthEstBitsPerSec;
64228
                            }
64229
                        }
64230
                        return playerBandwidthEst;
64231
                    },
64232
                    set(bandwidth) {
64233
                        this.playlistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter
64234
                        // `count` is set to zero that current value of `rate` isn't included
64235
                        // in the cumulative average
64236
 
64237
                        this.playlistController_.mainSegmentLoader_.throughput = {
64238
                            rate: 0,
64239
                            count: 0
64240
                        };
64241
                    }
64242
                },
64243
                /**
64244
                 * `systemBandwidth` is a combination of two serial processes bit-rates. The first
64245
                 * is the network bitrate provided by `bandwidth` and the second is the bitrate of
64246
                 * the entire process after that - decryption, transmuxing, and appending - provided
64247
                 * by `throughput`.
64248
                 *
64249
                 * Since the two process are serial, the overall system bandwidth is given by:
64250
                 *   sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
64251
                 */
64252
                systemBandwidth: {
64253
                    get() {
64254
                        const invBandwidth = 1 / (this.bandwidth || 1);
64255
                        let invThroughput;
64256
                        if (this.throughput > 0) {
64257
                            invThroughput = 1 / this.throughput;
64258
                        } else {
64259
                            invThroughput = 0;
64260
                        }
64261
                        const systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
64262
                        return systemBitrate;
64263
                    },
64264
                    set() {
64265
                        videojs.log.error('The "systemBandwidth" property is read-only');
64266
                    }
64267
                }
64268
            });
64269
            if (this.options_.bandwidth) {
64270
                this.bandwidth = this.options_.bandwidth;
64271
            }
64272
            if (this.options_.throughput) {
64273
                this.throughput = this.options_.throughput;
64274
            }
64275
            Object.defineProperties(this.stats, {
64276
                bandwidth: {
64277
                    get: () => this.bandwidth || 0,
64278
                    enumerable: true
64279
                },
64280
                mediaRequests: {
64281
                    get: () => this.playlistController_.mediaRequests_() || 0,
64282
                    enumerable: true
64283
                },
64284
                mediaRequestsAborted: {
64285
                    get: () => this.playlistController_.mediaRequestsAborted_() || 0,
64286
                    enumerable: true
64287
                },
64288
                mediaRequestsTimedout: {
64289
                    get: () => this.playlistController_.mediaRequestsTimedout_() || 0,
64290
                    enumerable: true
64291
                },
64292
                mediaRequestsErrored: {
64293
                    get: () => this.playlistController_.mediaRequestsErrored_() || 0,
64294
                    enumerable: true
64295
                },
64296
                mediaTransferDuration: {
64297
                    get: () => this.playlistController_.mediaTransferDuration_() || 0,
64298
                    enumerable: true
64299
                },
64300
                mediaBytesTransferred: {
64301
                    get: () => this.playlistController_.mediaBytesTransferred_() || 0,
64302
                    enumerable: true
64303
                },
64304
                mediaSecondsLoaded: {
64305
                    get: () => this.playlistController_.mediaSecondsLoaded_() || 0,
64306
                    enumerable: true
64307
                },
64308
                mediaAppends: {
64309
                    get: () => this.playlistController_.mediaAppends_() || 0,
64310
                    enumerable: true
64311
                },
64312
                mainAppendsToLoadedData: {
64313
                    get: () => this.playlistController_.mainAppendsToLoadedData_() || 0,
64314
                    enumerable: true
64315
                },
64316
                audioAppendsToLoadedData: {
64317
                    get: () => this.playlistController_.audioAppendsToLoadedData_() || 0,
64318
                    enumerable: true
64319
                },
64320
                appendsToLoadedData: {
64321
                    get: () => this.playlistController_.appendsToLoadedData_() || 0,
64322
                    enumerable: true
64323
                },
64324
                timeToLoadedData: {
64325
                    get: () => this.playlistController_.timeToLoadedData_() || 0,
64326
                    enumerable: true
64327
                },
64328
                buffered: {
64329
                    get: () => timeRangesToArray(this.tech_.buffered()),
64330
                    enumerable: true
64331
                },
64332
                currentTime: {
64333
                    get: () => this.tech_.currentTime(),
64334
                    enumerable: true
64335
                },
64336
                currentSource: {
64337
                    get: () => this.tech_.currentSource_,
64338
                    enumerable: true
64339
                },
64340
                currentTech: {
64341
                    get: () => this.tech_.name_,
64342
                    enumerable: true
64343
                },
64344
                duration: {
64345
                    get: () => this.tech_.duration(),
64346
                    enumerable: true
64347
                },
64348
                main: {
64349
                    get: () => this.playlists.main,
64350
                    enumerable: true
64351
                },
64352
                playerDimensions: {
64353
                    get: () => this.tech_.currentDimensions(),
64354
                    enumerable: true
64355
                },
64356
                seekable: {
64357
                    get: () => timeRangesToArray(this.tech_.seekable()),
64358
                    enumerable: true
64359
                },
64360
                timestamp: {
64361
                    get: () => Date.now(),
64362
                    enumerable: true
64363
                },
64364
                videoPlaybackQuality: {
64365
                    get: () => this.tech_.getVideoPlaybackQuality(),
64366
                    enumerable: true
64367
                }
64368
            });
64369
            this.tech_.one('canplay', this.playlistController_.setupFirstPlay.bind(this.playlistController_));
64370
            this.tech_.on('bandwidthupdate', () => {
64371
                if (this.options_.useBandwidthFromLocalStorage) {
64372
                    updateVhsLocalStorage({
64373
                        bandwidth: this.bandwidth,
64374
                        throughput: Math.round(this.throughput)
64375
                    });
64376
                }
64377
            });
64378
            this.playlistController_.on('selectedinitialmedia', () => {
64379
                // Add the manual rendition mix-in to VhsHandler
64380
                renditionSelectionMixin(this);
64381
            });
64382
            this.playlistController_.sourceUpdater_.on('createdsourcebuffers', () => {
64383
                this.setupEme_();
64384
            }); // the bandwidth of the primary segment loader is our best
64385
            // estimate of overall bandwidth
64386
 
64387
            this.on(this.playlistController_, 'progress', function () {
64388
                this.tech_.trigger('progress');
64389
            }); // In the live case, we need to ignore the very first `seeking` event since
64390
            // that will be the result of the seek-to-live behavior
64391
 
64392
            this.on(this.playlistController_, 'firstplay', function () {
64393
                this.ignoreNextSeekingEvent_ = true;
64394
            });
64395
            this.setupQualityLevels_(); // do nothing if the tech has been disposed already
64396
            // this can occur if someone sets the src in player.ready(), for instance
64397
 
64398
            if (!this.tech_.el()) {
64399
                return;
64400
            }
64401
            this.mediaSourceUrl_ = window.URL.createObjectURL(this.playlistController_.mediaSource);
64402
            this.tech_.src(this.mediaSourceUrl_);
64403
        }
64404
        createKeySessions_() {
64405
            const audioPlaylistLoader = this.playlistController_.mediaTypes_.AUDIO.activePlaylistLoader;
64406
            this.logger_('waiting for EME key session creation');
64407
            waitForKeySessionCreation({
64408
                player: this.player_,
64409
                sourceKeySystems: this.source_.keySystems,
64410
                audioMedia: audioPlaylistLoader && audioPlaylistLoader.media(),
64411
                mainPlaylists: this.playlists.main.playlists
64412
            }).then(() => {
64413
                this.logger_('created EME key session');
64414
                this.playlistController_.sourceUpdater_.initializedEme();
64415
            }).catch(err => {
64416
                this.logger_('error while creating EME key session', err);
64417
                this.player_.error({
64418
                    message: 'Failed to initialize media keys for EME',
64419
                    code: 3
64420
                });
64421
            });
64422
        }
64423
        handleWaitingForKey_() {
64424
            // If waitingforkey is fired, it's possible that the data that's necessary to retrieve
64425
            // the key is in the manifest. While this should've happened on initial source load, it
64426
            // may happen again in live streams where the keys change, and the manifest info
64427
            // reflects the update.
64428
            //
64429
            // Because videojs-contrib-eme compares the PSSH data we send to that of PSSH data it's
64430
            // already requested keys for, we don't have to worry about this generating extraneous
64431
            // requests.
64432
            this.logger_('waitingforkey fired, attempting to create any new key sessions');
64433
            this.createKeySessions_();
64434
        }
64435
        /**
64436
         * If necessary and EME is available, sets up EME options and waits for key session
64437
         * creation.
64438
         *
64439
         * This function also updates the source updater so taht it can be used, as for some
64440
         * browsers, EME must be configured before content is appended (if appending unencrypted
64441
         * content before encrypted content).
64442
         */
64443
 
64444
        setupEme_() {
64445
            const audioPlaylistLoader = this.playlistController_.mediaTypes_.AUDIO.activePlaylistLoader;
64446
            const didSetupEmeOptions = setupEmeOptions({
64447
                player: this.player_,
64448
                sourceKeySystems: this.source_.keySystems,
64449
                media: this.playlists.media(),
64450
                audioMedia: audioPlaylistLoader && audioPlaylistLoader.media()
64451
            });
64452
            this.player_.tech_.on('keystatuschange', e => {
64453
                this.playlistController_.updatePlaylistByKeyStatus(e.keyId, e.status);
64454
            });
64455
            this.handleWaitingForKey_ = this.handleWaitingForKey_.bind(this);
64456
            this.player_.tech_.on('waitingforkey', this.handleWaitingForKey_);
64457
            if (!didSetupEmeOptions) {
64458
                // If EME options were not set up, we've done all we could to initialize EME.
64459
                this.playlistController_.sourceUpdater_.initializedEme();
64460
                return;
64461
            }
64462
            this.createKeySessions_();
64463
        }
64464
        /**
64465
         * Initializes the quality levels and sets listeners to update them.
64466
         *
64467
         * @method setupQualityLevels_
64468
         * @private
64469
         */
64470
 
64471
        setupQualityLevels_() {
64472
            const player = videojs.players[this.tech_.options_.playerId]; // if there isn't a player or there isn't a qualityLevels plugin
64473
            // or qualityLevels_ listeners have already been setup, do nothing.
64474
 
64475
            if (!player || !player.qualityLevels || this.qualityLevels_) {
64476
                return;
64477
            }
64478
            this.qualityLevels_ = player.qualityLevels();
64479
            this.playlistController_.on('selectedinitialmedia', () => {
64480
                handleVhsLoadedMetadata(this.qualityLevels_, this);
64481
            });
64482
            this.playlists.on('mediachange', () => {
64483
                handleVhsMediaChange(this.qualityLevels_, this.playlists);
64484
            });
64485
        }
64486
        /**
64487
         * return the version
64488
         */
64489
 
64490
        static version() {
64491
            return {
64492
                '@videojs/http-streaming': version$4,
64493
                'mux.js': version$3,
64494
                'mpd-parser': version$2,
64495
                'm3u8-parser': version$1,
64496
                'aes-decrypter': version
64497
            };
64498
        }
64499
        /**
64500
         * return the version
64501
         */
64502
 
64503
        version() {
64504
            return this.constructor.version();
64505
        }
64506
        canChangeType() {
64507
            return SourceUpdater.canChangeType();
64508
        }
64509
        /**
64510
         * Begin playing the video.
64511
         */
64512
 
64513
        play() {
64514
            this.playlistController_.play();
64515
        }
64516
        /**
64517
         * a wrapper around the function in PlaylistController
64518
         */
64519
 
64520
        setCurrentTime(currentTime) {
64521
            this.playlistController_.setCurrentTime(currentTime);
64522
        }
64523
        /**
64524
         * a wrapper around the function in PlaylistController
64525
         */
64526
 
64527
        duration() {
64528
            return this.playlistController_.duration();
64529
        }
64530
        /**
64531
         * a wrapper around the function in PlaylistController
64532
         */
64533
 
64534
        seekable() {
64535
            return this.playlistController_.seekable();
64536
        }
64537
        /**
64538
         * Abort all outstanding work and cleanup.
64539
         */
64540
 
64541
        dispose() {
64542
            if (this.playbackWatcher_) {
64543
                this.playbackWatcher_.dispose();
64544
            }
64545
            if (this.playlistController_) {
64546
                this.playlistController_.dispose();
64547
            }
64548
            if (this.qualityLevels_) {
64549
                this.qualityLevels_.dispose();
64550
            }
64551
            if (this.tech_ && this.tech_.vhs) {
64552
                delete this.tech_.vhs;
64553
            }
64554
            if (this.mediaSourceUrl_ && window.URL.revokeObjectURL) {
64555
                window.URL.revokeObjectURL(this.mediaSourceUrl_);
64556
                this.mediaSourceUrl_ = null;
64557
            }
64558
            if (this.tech_) {
64559
                this.tech_.off('waitingforkey', this.handleWaitingForKey_);
64560
            }
64561
            super.dispose();
64562
        }
64563
        convertToProgramTime(time, callback) {
64564
            return getProgramTime({
64565
                playlist: this.playlistController_.media(),
64566
                time,
64567
                callback
64568
            });
64569
        } // the player must be playing before calling this
64570
 
64571
        seekToProgramTime(programTime, callback, pauseAfterSeek = true, retryCount = 2) {
64572
            return seekToProgramTime({
64573
                programTime,
64574
                playlist: this.playlistController_.media(),
64575
                retryCount,
64576
                pauseAfterSeek,
64577
                seekTo: this.options_.seekTo,
64578
                tech: this.options_.tech,
64579
                callback
64580
            });
64581
        }
64582
        /**
64583
         * Adds the onRequest, onResponse, offRequest and offResponse functions
64584
         * to the VhsHandler xhr Object.
64585
         */
64586
 
64587
        setupXhrHooks_() {
64588
            /**
64589
             * A player function for setting an onRequest hook
64590
             *
64591
             * @param {function} callback for request modifiction
64592
             */
64593
            this.xhr.onRequest = callback => {
64594
                addOnRequestHook(this.xhr, callback);
64595
            };
64596
            /**
64597
             * A player function for setting an onResponse hook
64598
             *
64599
             * @param {callback} callback for response data retrieval
64600
             */
64601
 
64602
            this.xhr.onResponse = callback => {
64603
                addOnResponseHook(this.xhr, callback);
64604
            };
64605
            /**
64606
             * Deletes a player onRequest callback if it exists
64607
             *
64608
             * @param {function} callback to delete from the player set
64609
             */
64610
 
64611
            this.xhr.offRequest = callback => {
64612
                removeOnRequestHook(this.xhr, callback);
64613
            };
64614
            /**
64615
             * Deletes a player onResponse callback if it exists
64616
             *
64617
             * @param {function} callback to delete from the player set
64618
             */
64619
 
64620
            this.xhr.offResponse = callback => {
64621
                removeOnResponseHook(this.xhr, callback);
64622
            }; // Trigger an event on the player to notify the user that vhs is ready to set xhr hooks.
64623
            // This allows hooks to be set before the source is set to vhs when handleSource is called.
64624
 
64625
            this.player_.trigger('xhr-hooks-ready');
64626
        }
64627
    }
64628
    /**
64629
     * The Source Handler object, which informs video.js what additional
64630
     * MIME types are supported and sets up playback. It is registered
64631
     * automatically to the appropriate tech based on the capabilities of
64632
     * the browser it is running in. It is not necessary to use or modify
64633
     * this object in normal usage.
64634
     */
64635
 
64636
    const VhsSourceHandler = {
64637
        name: 'videojs-http-streaming',
64638
        VERSION: version$4,
64639
        canHandleSource(srcObj, options = {}) {
64640
            const localOptions = merge(videojs.options, options);
64641
            return VhsSourceHandler.canPlayType(srcObj.type, localOptions);
64642
        },
64643
        handleSource(source, tech, options = {}) {
64644
            const localOptions = merge(videojs.options, options);
64645
            tech.vhs = new VhsHandler(source, tech, localOptions);
64646
            tech.vhs.xhr = xhrFactory();
64647
            tech.vhs.setupXhrHooks_();
64648
            tech.vhs.src(source.src, source.type);
64649
            return tech.vhs;
64650
        },
64651
        canPlayType(type, options) {
64652
            const simpleType = simpleTypeFromSourceType(type);
64653
            if (!simpleType) {
64654
                return '';
64655
            }
64656
            const overrideNative = VhsSourceHandler.getOverrideNative(options);
64657
            const supportsTypeNatively = Vhs.supportsTypeNatively(simpleType);
64658
            const canUseMsePlayback = !supportsTypeNatively || overrideNative;
64659
            return canUseMsePlayback ? 'maybe' : '';
64660
        },
64661
        getOverrideNative(options = {}) {
64662
            const {
64663
                vhs = {}
64664
            } = options;
64665
            const defaultOverrideNative = !(videojs.browser.IS_ANY_SAFARI || videojs.browser.IS_IOS);
64666
            const {
64667
                overrideNative = defaultOverrideNative
64668
            } = vhs;
64669
            return overrideNative;
64670
        }
64671
    };
64672
    /**
64673
     * Check to see if the native MediaSource object exists and supports
64674
     * an MP4 container with both H.264 video and AAC-LC audio.
64675
     *
64676
     * @return {boolean} if  native media sources are supported
64677
     */
64678
 
64679
    const supportsNativeMediaSources = () => {
64680
        return browserSupportsCodec('avc1.4d400d,mp4a.40.2');
64681
    }; // register source handlers with the appropriate techs
64682
 
64683
    if (supportsNativeMediaSources()) {
64684
        videojs.getTech('Html5').registerSourceHandler(VhsSourceHandler, 0);
64685
    }
64686
    videojs.VhsHandler = VhsHandler;
64687
    videojs.VhsSourceHandler = VhsSourceHandler;
64688
    videojs.Vhs = Vhs;
64689
    if (!videojs.use) {
64690
        videojs.registerComponent('Vhs', Vhs);
64691
    }
64692
    videojs.options.vhs = videojs.options.vhs || {};
64693
    if (!videojs.getPlugin || !videojs.getPlugin('reloadSourceOnError')) {
64694
        videojs.registerPlugin('reloadSourceOnError', reloadSourceOnError);
64695
    }
64696
 
64697
    return videojs;
64698
 
64699
}));