PageSourceSearch

https://www.africanews.com/js/article-503f8d49ca.js

js africanews.com collected 2026-09-24 07:48:50 UTC 3,195,769 bytes, 94,932 lines download raw bytes

1(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
2'use strict';
3
4var ResponsiveIframes = require('./common/utils/responsive-iframes');
5var Twitter = require('./common/utils/twitter');
6var VideoJsPlayer = require('./common/video/VideoJsPlayer');
7var PfpVideoJsFallback = require('./common/video/PfpVideoJsFallback');
8var AdobeVideoTracking = require('./common/third-parts/adobe/AdobeVideoTracking');
9
10(function (w) {
11  'use strict';
12
13  var responsiveIframes = new ResponsiveIframes();
14  var twitter = new Twitter();
15
16  var mediaArticle = window.document.getElementById('jsMainMediaArticle');
17  if (mediaArticle && mediaArticle.getAttribute('data-content')) {
18    var articleContent = JSON.parse(mediaArticle.getAttribute('data-content'));
19    if (articleContent.video > 0) {
20      var videoJsOptions = {
21        article: articleContent,
22        autoplay: mediaArticle.getAttribute('data-autoplay') === '1',
23        embed: false
24      };
25      var pfpElement = document.getElementById('pfp-primary-player');
26
27      if (pfpElement) {
28        new PfpVideoJsFallback(pfpElement, mediaArticle, videoJsOptions).init();
29      } else {
30        var player = new VideoJsPlayer(mediaArticle.querySelector('video'), videoJsOptions);
31        player.init().then(function (instance) {
32          w.adobeVideoTracking = new AdobeVideoTracking(instance, 'videojs');
33        }).catch(function (error) {
34          return console.error('VIDEO.JS - article player initialisation failed', error);
35        });
36      }
37    }
38  }
39})(window);
40
41},{"./common/third-parts/adobe/AdobeVideoTracking":2,"./common/utils/responsive-iframes":4,"./common/utils/twitter":5,"./common/video/PfpVideoJsFallback":6,"./common/video/VideoJsPlayer":8}],2:[function(require,module,exports){
42'use strict';
43
44var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
45
46function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
47
48/** See this docs for more informations : https://marketing.adobe.com/resources/help/en_US/sc/appmeasurement/hbvideo/js_2.0/vhl-2.0_js.pdf
49 *  TO DEBUG : add '?heartbeatDebug=1' to your url
50 */
51var AdobeVideoTracking = function () {
52    /**
53     * Initialise video adobe video tracking
54     * @param playerInstance player adapter
55     * retrun {Void}
56     */
57    function AdobeVideoTracking(playerInstance, playerName) {
58        _classCallCheck(this, AdobeVideoTracking);
59
60        if (typeof window.ADB === 'undefined') {
61            window.ADB = require('./VideoHeartbeat.min');
62        }
63
64        this.playerInstance = playerInstance;
65        this.playerName = playerName;
66        this.trackSessionStarted = false;
67        this.trackingInitialised = false;
68        this.eventListeners = [];
69        this.mediaHeartbeat = null;
70        // Configuration : TODO : TASK-1 pass all configuration in a separate file
71        this.config = {
72            accountDev: 'euronewsgroupdev',
73            accountProd: 'euronewsgroupeuronews'
74        };
75        this.heartbeatDebug = this.isHeartbeatDebugActivated();
76        // Event listenned
77        this.adobeEventStrings = {
78            videoLoad: 'video-load',
79            videoPlay: 'video-play',
80            videoPause: 'video-pause',
81            videoComplete: 'video-complete',
82            videoPlaylistSwitch: 'video-playlist-switch',
83            pfpVideoLoad: 'en-player-video-ready',
84            pfpVideoPlay: 'en-player-video-play',
85            pfpVideoPause: 'en-player-video-pause',
86            pfpVideoComplete: 'en-player-video-stop'
87        };
88        this.videoId = '';
89
90        // Test if adobe DTM is loaded ( s_gi var is created )
91        if (typeof window.s_gi !== 'undefined') {
92            this.initVideoTracking();
93        } else {
94            // Listen event from adobe DTM
95            // To set this event go to adobe dtm admin panel :
96            // https://dtm.adobe.com/companies/euronews-group/web_properties/euronews-group/site_catalyst_accounts/5a0ca30864746d0bc6009071/edit.
97            window.addEventListener('adobeAnalyticsLoaded', this.initVideoTracking.bind(this));
98        }
99    }
100    /**
101     * configure adobe librairies :
102     * 1 - AppMeasurement ( loaded with the DTM )
103     * 2 - VisitorAPI  ( loaded with the DTM )
104     * 3 - Heartbeat ( require )
105     * @return boolean
106     * TODO : TASK-1 pass all configuration in a separate file
107     */
108
109
110    _createClass(AdobeVideoTracking, [{
111        key: 'initVideoTracking',
112        value: function initVideoTracking() {
113            if (this.trackingInitialised) {
114                return true;
115            }
116            var that = this;
117            // Test if we are live
118            if (window.digitalData.video.type === 'live') {
119                this.videoId = 'live';
120            }
121            // Test : video is required
122            else if (typeof window.digitalData.video.housenumbervidid === 'undefined' || window.digitalData.video.housenumbervidid == false) {
123                    return false;
124                    // Videoid is housenumbervidid in classical cases
125                } else {
126                    this.videoId = window.digitalData.video.housenumbervidid;
127                }
128            // Instanciate AppMeasurement (global)
129            var s_account = this.config.accountProd;
130            window.s = window.s_gi(s_account);
131
132            // AppMeasurement configuration
133            // IDS setting ( see : https://marketing.adobe.com/resources/help/en_US/sc/implement/js_implementation.html )
134            window.s.account = this.config.accountProd;
135            window.s.trackingServer = 'euronewsgroup.d3.sc.omtrdc.net';
136            // additional config see : https://marketing.adobe.com/resources/help/en_US/sc/implement/appmeasure_mjs_pagecode.html )
137            window.s.trackDownloadLinks = true;
138            window.s.trackExternalLinks = true;
139            window.s.trackInlineStats = true;
140            window.s.linkDownloadFileTypes = "exe,zip,wav,mp3,mov,mpg,avi,wmv,pdf,doc,docx,xls,xlsx,ppt,pptx";
141            window.s.linkLeaveQueryString = false;
142            window.s.linkTrackVars = 'None';
143            window.s.linkTrackEvents = 'None';
144            //  PageName setting
145            window.s.pageName = window.digitalData.page.pagename;
146            // Visitor API configuration
147            window.s.visitorNamespace = 'euronewsgroup';
148            window.s.visitor = Visitor.getInstance('237E3E1059F2E95E0A495E5D@AdobeOrg');
149            // Heartbeat config
150            window.MediaHeartbeat = window.ADB.va.MediaHeartbeat;
151            window.MediaHeartbeatConfig = window.ADB.va.MediaHeartbeatConfig;
152            window.MediaHeartbeatDelegate = window.ADB.va.MediaHeartbeatDelegate;
153            // MediaHeartbeat Config
154            var mediaConfig = new window.MediaHeartbeatConfig();
155            mediaConfig.trackingServer = 'http://heartbeats.omtrdc.net';
156            mediaConfig.playerName = this.playerName;
157            mediaConfig.channel = 'africanews';
158            mediaConfig.debugLogging = this.heartbeatDebug;
159            mediaConfig.appVersion = '0.0.1';
160            mediaConfig.ssl = true;
161            mediaConfig.ovp = 'africanews';
162            // MediaHeartBeatDelegate config
163            var mediaDelegate = new window.MediaHeartbeatDelegate();
164            mediaDelegate.getCurrentPlaybackTime = function () {
165                return that.playerInstance.getPosition();
166            };
167            mediaDelegate.getQoSObject = function () {
168                var bitrate = 0; // this.playerInstance.getVisualQuality(); doesn't work
169                var startuptime = that.playerInstance.getSetupTime();
170                var fps = 60;
171                var droppedFrames = 0;
172
173                return window.MediaHeartbeat.createQoSObject(bitrate, startuptime, fps, droppedFrames);
174            };
175            // Instanciate mediaHeart beat object
176            this.mediaHeartbeat = new window.MediaHeartbeat(mediaDelegate, mediaConfig, window.s);
177            window.mediaHeartbeat = this.mediaHeartbeat;
178            this.trackingInitialised = true;
179
180            // Set mediaObject
181            this.setMediaObject();
182
183            // Initialise event listeners
184            this.initEventListeners();
185            return true;
186        }
187
188        /**
189         * Initialize event listenners : event are emitted by component/player.js
190         * @return {void} void
191         */
192
193    }, {
194        key: 'initEventListeners',
195        value: function initEventListeners() {
196            if (this.playerName === 'pfp') {
197                this.addEventListener(this.adobeEventStrings.pfpVideoLoad, this.trackLoad);
198                this.addEventListener(this.adobeEventStrings.pfpVideoPlay, this.trackPlay);
199                this.addEventListener(this.adobeEventStrings.pfpVideoPause, this.trackPause);
200                this.addEventListener(this.adobeEventStrings.pfpVideoComplete, this.trackComplete);
201                return;
202            }
203
204            this.addEventListener(this.adobeEventStrings.videoLoad, this.trackLoad);
205            this.addEventListener(this.adobeEventStrings.videoPlay, this.trackPlay);
206            this.addEventListener(this.adobeEventStrings.videoPause, this.trackPause);
207            this.addEventListener(this.adobeEventStrings.videoComplete, this.trackComplete);
208            this.addEventListener(this.adobeEventStrings.videoPlaylistSwitch, this.trackPlaylistSwitch);
209        }
210    }, {
211        key: 'addEventListener',
212        value: function addEventListener(eventName, listener) {
213            var boundListener = listener.bind(this);
214            this.eventListeners.push({ eventName: eventName, listener: boundListener });
215            window.addEventListener(eventName, boundListener);
216        }
217
218        /**
219         * set mediaObject
220         * @param videoIndex : index of the video ( it's used only in playlist mode )
221         */
222
223    }, {
224        key: 'setMediaObject',
225        value: function setMediaObject() {
226            var videoIndex = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
227
228            var videoHousenumber = this.videoId.split('|')[videoIndex];
229            var videoName = window.digitalData.video.name.split('|')[videoIndex];
230            var videoDuration = this.getVideoDuration(videoIndex);
231            var streamType = window.digitalData.video.type === 'live' ? window.MediaHeartbeat.StreamType.LIVE : window.MediaHeartbeat.StreamType.VOD;
232            // Setting heartbeat media object : ( properties are set with the dataLayer -> src/components/adobeAnalytics files )
233            window.mediaObject = window.MediaHeartbeat.createMediaObject(videoName, videoHousenumber, videoDuration, streamType);
234
235            return window.mediaObject;
236        }
237        /**
238         * Trackload call Adobe heartbeat track session start. This is heartbeat init.
239         * This must only work if trackSessionStart isn't active
240         * @return {Void} void
241         */
242
243    }, {
244        key: 'trackLoad',
245        value: function trackLoad() {
246            var contextData = window.digitalData;
247            if (!this.trackSessionStarted) {
248                this.mediaHeartbeat.trackSessionStart(window.mediaObject, contextData);
249                this.trackSessionStarted = true;
250            }
251        }
252        /**
253         * TrackPlay : playback activated
254         * @return {Void} void
255         */
256
257    }, {
258        key: 'trackPlay',
259        value: function trackPlay() {
260            // If trackSessin isn't started, we need to start a new session with trackLoad()
261            if (!this.trackSessionStarted) {
262                this.trackLoad();
263            }
264            this.mediaHeartbeat.trackPlay();
265        }
266        /**
267         * TrackPause : playback paused
268         * @return {Void} void
269         */
270
271    }, {
272        key: 'trackPause',
273        value: function trackPause() {
274            if (this.trackSessionStarted) {
275                this.mediaHeartbeat.trackPause();
276            }
277        }
278        /**
279         * Trackcomplete : video complete
280         * @return {Void} void
281         */
282
283    }, {
284        key: 'trackComplete',
285        value: function trackComplete() {
286            if (!this.trackSessionStarted) {
287                return;
288            }
289            this.mediaHeartbeat.trackComplete();
290            this.mediaHeartbeat.trackSessionEnd();
291            this.trackSessionStarted = false;
292        }
293        /**
294         * trackPlaylistSwitch : video complete
295         * @param event : custom event
296         * @return {Void} void
297         */
298
299    }, {
300        key: 'trackPlaylistSwitch',
301        value: function trackPlaylistSwitch(event) {
302            // Test if event detail is valid
303            if (typeof event.detail.index !== 'number') {
304                console.error('error adobevideotracking : wrong videoIndex passed');
305                return -1;
306            }
307            // Variables assignations
308            var videoIndex = event.detail.index;
309            // Stop session if started
310            if (this.trackSessionStarted) {
311                this.mediaHeartbeat.trackSessionEnd();
312                this.trackSessionStarted = false;
313            }
314            // Change media object
315            this.setMediaObject(videoIndex);
316            return 0;
317        }
318        /**
319         * Check if heartbeat debug is activated ( GET param heartbeatDebug = 1 )
320         * @return {Boolean} heartbeatDebugActivated
321         */
322
323    }, {
324        key: 'isHeartbeatDebugActivated',
325        value: function isHeartbeatDebugActivated() {
326            var heartbeatDebugActivated = false;
327            if (window.location.href.indexOf("heartbeatDebug=1") > -1) {
328                heartbeatDebugActivated = true;
329            }
330            return heartbeatDebugActivated;
331        }
332    }, {
333        key: 'getVideoDuration',
334        value: function getVideoDuration(videoIndex) {
335            var duration = window.digitalData.video.duration;
336
337            if (duration === '' || duration === 'none') {
338                return 0;
339            }
340
341            return parseInt(String(duration).split('|')[videoIndex]);
342        }
343    }, {
344        key: 'dispose',
345        value: function dispose() {
346            this.eventListeners.forEach(function (eventListener) {
347                window.removeEventListener(eventListener.eventName, eventListener.listener);
348            });
349            this.eventListeners = [];
350
351            if (this.trackSessionStarted && this.mediaHeartbeat) {
352                this.mediaHeartbeat.trackSessionEnd();
353                this.trackSessionStarted = false;
354            }
355        }
356    }]);
357
358    return AdobeVideoTracking;
359}();
360
361module.exports = AdobeVideoTracking;
362
363},{"./VideoHeartbeat.min":3}],3:[function(require,module,exports){
364'use strict';
365
366var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
367
368/*
369 * ************************************************************************
370 *
371 *  ADOBE CONFIDENTIAL
372 *  ___________________
373 *
374 *   (c) Copyright 2018 Adobe Systems Incorporated
375 *   All Rights Reserved.
376 *
377 *  NOTICE:  All information contained herein is, and remains
378 *  the property of Adobe Systems Incorporated and its suppliers,
379 *  if any.  The intellectual and technical concepts contained
380 *  herein are proprietary to Adobe Systems Incorporated and its
381 *  suppliers and may be covered by U.S. and Foreign Patents,
382 *  patents in process, and are protected by trade secret or copyright law.
383 *  Dissemination of this information or reproduction of this material
384 *  is strictly forbidden unless prior written permission is obtained
385 *  from Adobe Systems Incorporated.
386 * ************************************************************************
387 */
388
389/*
390 * video heartbeats - 2.1.0 - 2018-02-15
391 * Copyright (c) 2018 Adobe Systems, Inc. All Rights Reserved.
392 */
393/*
394! Copyright for external libraries used in ADBMobile library
395*/
396/*
397 * JavaScript MD5 1.0.1
398 * https://github.com/blueimp/JavaScript-MD5
399 *
400 * Copyright 2011, Sebastian Tschan
401 * https://blueimp.net
402 *
403 * Licensed under the MIT license:
404 * http://www.opensource.org/licenses/MIT
405 *
406 * Based on
407 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
408 * Digest Algorithm, as defined in RFC 1321.
409 * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
410 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
411 * Distributed under the BSD License
412 * See http://pajhome.org.uk/crypt/md5 for more info.
413 */
414/*
415* The MIT License (MIT)
416* Copyright (c) 2014 the UMD contributors.
417* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
418* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
419* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
420*/
421(function (root, factory) {
422    //https://github.com/umdjs/umd/blob/master/templates/returnExports.js
423    if (typeof define === 'function' && define.amd) {
424        define([], factory);
425    } else if ((typeof module === 'undefined' ? 'undefined' : _typeof(module)) === 'object' && module.exports) {
426        module.exports = factory();
427    } else {
428        root.ADB = factory();
429    }
430})(typeof self !== 'undefined' ? self : undefined, function () {
431    var exports = {};
432    (function () {
433
434        // Heartbeat core
435        !function (a) {
436            if (void 0 === b) var b = {};if (void 0 === c) var c = {};if (void 0 === d) var d = {};if (d.radio || (d.radio = {}), d.plugin || (d.plugin = {}), void 0 === e) var e = {};e.clock || (e.clock = {}), function (a) {
437                "use strict";
438                function b(a, b) {
439                    var c = (65535 & a) + (65535 & b);return (a >> 16) + (b >> 16) + (c >> 16) << 16 | 65535 & c;
440                }function c(a, b) {
441                    return a << b | a >>> 32 - b;
442                }function d(a, d, e, f, g, h) {
443                    return b(c(b(b(d, a), b(f, h)), g), e);
444                }function e(a, b, c, e, f, g, h) {
445                    return d(b & c | ~b & e, a, b, f, g, h);
446                }function f(a, b, c, e, f, g, h) {
447                    return d(b & e | c & ~e, a, b, f, g, h);
448                }function g(a, b, c, e, f, g, h) {
449                    return d(b ^ c ^ e, a, b, f, g, h);
450                }function h(a, b, c, e, f, g, h) {
451                    return d(c ^ (b | ~e), a, b, f, g, h);
452                }function i(a, c) {
453                    a[c >> 5] |= 128 << c % 32, a[14 + (c + 64 >>> 9 << 4)] = c;var d,
454                        i,
455                        j,
456                        k,
457                        l,
458                        m = 1732584193,
459                        n = -271733879,
460                        o = -1732584194,
461                        p = 271733878;for (d = 0; d < a.length; d += 16) {
462                        i = m, j = n, k = o, l = p, m = e(m, n, o, p, a[d], 7, -680876936), p = e(p, m, n, o, a[d + 1], 12, -389564586), o = e(o, p, m, n, a[d + 2], 17, 606105819), n = e(n, o, p, m, a[d + 3], 22, -1044525330), m = e(m, n, o, p, a[d + 4], 7, -176418897), p = e(p, m, n, o, a[d + 5], 12, 1200080426), o = e(o, p, m, n, a[d + 6], 17, -1473231341), n = e(n, o, p, m, a[d + 7], 22, -45705983), m = e(m, n, o, p, a[d + 8], 7, 1770035416), p = e(p, m, n, o, a[d + 9], 12, -1958414417), o = e(o, p, m, n, a[d + 10], 17, -42063), n = e(n, o, p, m, a[d + 11], 22, -1990404162), m = e(m, n, o, p, a[d + 12], 7, 1804603682), p = e(p, m, n, o, a[d + 13], 12, -40341101), o = e(o, p, m, n, a[d + 14], 17, -1502002290), n = e(n, o, p, m, a[d + 15], 22, 
4621236535329), m = f(m, n, o, p, a[d + 1], 5, -165796510), p = f(p, m, n, o, a[d + 6], 9, -1069501632), o = f(o, p, m, n, a[d + 11], 14, 643717713), n = f(n, o, p, m, a[d], 20, -373897302), m = f(m, n, o, p, a[d + 5], 5, -701558691), p = f(p, m, n, o, a[d + 10], 9, 38016083), o = f(o, p, m, n, a[d + 15], 14, -660478335), n = f(n, o, p, m, a[d + 4], 20, -405537848), m = f(m, n, o, p, a[d + 9], 5, 568446438), p = f(p, m, n, o, a[d + 14], 9, -1019803690), o = f(o, p, m, n, a[d + 3], 14, -187363961), n = f(n, o, p, m, a[d + 8], 20, 1163531501), m = f(m, n, o, p, a[d + 13], 5, -1444681467), p = f(p, m, n, o, a[d + 2], 9, -51403784), o = f(o, p, m, n, a[d + 7], 14, 1735328473), n = f(n, o, p, m, a[d + 12], 20, -1926607734), m = g(m, n, o, p, a[d + 5], 4, -378558), p = g(p, m, n, o, a[d + 8], 11, -2022574463), o = g(o, p, m, n, a[d + 11], 16, 1839030562), n = g(n, o, p, m, a[d + 14], 23, -35309556), m = g(m, n, o, p, a[d + 1], 4, -1530992060), p = g(p, m, n, o, a[d + 4], 11, 1272893353), o = g(o, p, m, n, a[d + 7], 16, -155497632), n = g(n, o, p, m, a[d + 10], 23, -1094730640), m = g(m, n, o, p, a[d + 13], 4, 681279174), p = g(p, m, n, o, a[d], 11, -358537222), o = g(o, p, m, n, a[d + 3], 16, -722521979), n = g(n, o, p, m, a[d + 6], 23, 76029189), m = g(m, n, o, p, a[d + 9], 4, -640364487), p = g(p, m, n, o, a[d + 12], 11, -421815835), o = g(o, p, m, n, a[d + 15], 16, 530742520), n = g(n, o, p, m, a[d + 2], 23, -995338651), m = h(m, n, o, p, a[d], 6, -198630844), p = h(p, m, n, o, a[d + 7], 10, 1126891415), o = h(o, p, m, n, a[d + 14], 15, -1416354905), n = h(n, o, p, m, a[d + 5], 21, -57434055), m = h(m, n, o, p, a[d + 12], 6, 1700485571), p = h(p, m, n, o, a[d + 3], 10, -1894986606), o = h(o, p, m, n, a[d + 10], 15, -1051523), n = h(n, o, p, m, a[d + 1], 21, -2054922799), m = h(m, n, o, p, a[d + 8], 6, 1873313359), p = h(p, m, n, o, a[d + 15], 10, -30611744), o = h(o, p, m, n, a[d + 6], 15, -1560198380), n = h(n, o, p, m, a[d + 13], 21, 1309151649), m = h(m, n, o, p, a[d + 4], 6, -145523070), p = h(p, m, n, o, a[d + 11], 10, -1120210379), o = h(o, p, m, n, a[d + 2], 15, 718787259), n = h(n, o, p, m, a[d + 9], 21, -343485551), m = b(m, i), n = b(n, j), o = b(o, k), p = b(p, l);
463                    }return [m, n, o, p];
464                }function j(a) {
465                    var b,
466                        c = "";for (b = 0; b < 32 * a.length; b += 8) {
467                        c += String.fromCharCode(a[b >> 5] >>> b % 32 & 255);
468                    }return c;
469                }function k(a) {
470                    var b,
471                        c = [];for (c[(a.length >> 2) - 1] = void 0, b = 0; b < c.length; b += 1) {
472                        c[b] = 0;
473                    }for (b = 0; b < 8 * a.length; b += 8) {
474                        c[b >> 5] |= (255 & a.charCodeAt(b / 8)) << b % 32;
475                    }return c;
476                }function l(a) {
477                    return j(i(k(a), 8 * a.length));
478                }function m(a, b) {
479                    var c,
480                        d,
481                        e = k(a),
482                        f = [],
483                        g = [];for (f[15] = g[15] = void 0, e.length > 16 && (e = i(e, 8 * a.length)), c = 0; c < 16; c += 1) {
484                        f[c] = 909522486 ^ e[c], g[c] = 1549556828 ^ e[c];
485                    }return d = i(f.concat(k(b)), 512 + 8 * b.length), j(i(g.concat(d), 640));
486                }function n(a) {
487                    var b,
488                        c,
489                        d = "0123456789abcdef",
490                        e = "";for (c = 0; c < a.length; c += 1) {
491                        b = a.charCodeAt(c), e += d.charAt(b >>> 4 & 15) + d.charAt(15 & b);
492                    }return e;
493                }function o(a) {
494                    return unescape(encodeURIComponent(a));
495                }function p(a) {
496                    return l(o(a));
497                }function q(a) {
498                    return n(p(a));
499                }function r(a, b) {
500                    return m(o(a), o(b));
501                }function s(a, b) {
502                    return n(r(a, b));
503                }function t(a, b, c) {
504                    return b ? c ? r(b, a) : s(b, a) : c ? p(a) : q(a);
505                }a.md5 = t;
506            }(b), function (a) {
507                "use strict";
508                var b = {};b.startsWith = function (a, b) {
509                    return 0 == a.indexOf(b);
510                }, a.StringUtils = b;
511            }(b), function (a) {
512                "use strict";
513                var b = {};b.clone = function (a) {
514                    var b = {};for (var c in a) {
515                        a.hasOwnProperty(c) && (b[c] = a[c]);
516                    }return b;
517                }, b.merge = function (a, c) {
518                    var d = b.clone(a);for (var e in c) {
519                        c.hasOwnProperty(e) && (d[e] = c[e]);
520                    }return d;
521                }, b.append = function (a, b) {
522                    for (var c in b) {
523                        b.hasOwnProperty(c) && (a[c] = b[c]);
524                    }
525                }, a.ObjectUtils = b;
526            }(b), function (a) {
527                "use strict";
528                function b(a, b, c) {
529                    this.fn = a, this.ctx = b, this.params = c;
530                }b.prototype.run = function () {
531                    this.params ? this.fn.apply(this.ctx, this.params) : this.fn.apply(this.ctx);
532                }, a.radio.Command = b;
533            }(d), function (a) {
534                "use strict";
535                function b(a, b) {
536                    this._queue = [], this._lastTs = 0, this._isSuspended = void 0 !== a && a, this._delay 
536= void 0 !== b ? b : 0;
537                }b.prototype.addCommand = function (a) {
538                    this._queue.push(a), this._drain();
539                }, b.prototype.cancelAllCommands = function () {
540                    this._queue = [];
541                }, b.prototype.isEmpty = function () {
542                    return 0 === this._queue.length;
543                }, b.prototype.suspend = function () {
544                    this._isSuspended = !0;
545                }, b.prototype.resume = function () {
546                    this._isSuspended = !1, this._drain();
547                }, b.prototype.flush = function () {
548                    this._isSuspended = !1;for (var a = 0; a < this._queue.length; a++) {
549                        this._queue[a].run();
550                    }this._queue = [];
551                }, b.prototype._drain = function () {
552                    if (!this._isSuspended && !this._drainInProgress) {
553                        this._drainInProgress = !0;var a = this;!function b() {
554                            var c = a._queue.shift();c ? a._runCommand(c, function () {
555                                a._isSuspended || b();
556                            }) : a._drainInProgress = !1;
557                        }();
558                    }
559                }, b.prototype._runCommand = function (a, b) {
560                    function c() {
561                        a.run(), null != b && b.call(d);
562                    }var d = this;if (0 == this._lastTs) c();else {
563                        var e = new Date().getTime(),
564                            f = e - this._lastTs;this._lastTs = e, f < this._delay ? setTimeout(c, this._delay - f) : c();
565                    }
566                }, a.radio.CommandQueue = b;
567            }(d), function (a) {
568                "use strict";
569                function b(a, b) {
570                    if (this._name = a, !b) throw new Error("Reference to the logger object cannot be NULL");this._logger = b, this._listeners = {}, this._requests = {}, this._commands = {}, this._isShutDown = !1;
571                }function c(a, c) {
572                    if (a === c) return !0;for (var d = (a || "").split(b.SEPARATOR), e = (c || "").split(b.SEPARATOR), f = !0, g = 0; g < d.length; g++) {
573                        f = f && (d[g] === b.WILDCARD || d[g] === e[g]);
574                    }return f;
575                }b.WILDCARD = "*", b.SEPARATOR = ":", b.prototype.toString = function () {
576                    return "<channel: " + this._name + ">";
577                }, b.prototype.shutdown = function () {
578                    this._isShutDown || (this._logger.debug(d, "#shutdown > Shutting down"), this.off(), this._requests = {}, this._commands = {}, this._isShutDown = !0);
579                }, b.prototype.on = function (a, b, c) {
580                    this._isShutDown || (this._listeners[a] || (this._listeners[a] = []), this._listeners[a].push({ fn: b, ctx: c }));
581                }, b.prototype.off = function (a, b, c) {
582                    if (!this._isShutDown) {
583                        if (b = "function" == typeof b ? b : null, !a && null == b && !c) return void (this._listeners = {});if (a) this._removeListener(a, b, c);else for (a in this._listeners) {
584                            this._listeners.hasOwnProperty(a) && this._removeListener(a, b, c);
585                        }
586                    }
587                }, b.prototype.trigger = function (a) {
588                    if (!this._isShutDown) for (var b in this._listeners) {
589                        if (this._listeners.hasOwnProperty(b) && c(b, a.name)) for (var d = this._listeners[b].slice(0), e = 0; e < d.length; e++) {
590                            var f = d[e];f.fn.call(f.ctx, a);
591                        }
592                    }
593                }, b.prototype.comply = function (a, b, c) {
594                    this._isShutDown || (this._commands[a] = { cmd: b, ctx: c });
595                }, b.prototype.command = function (a, b) {
596                    if (!this._isShutDown) {
597                        var c = this._commands[a];if (!c) return void this._logger.warn(d, "#command > No command handler for: " + a);c.cmd.call(c.ctx, b);
598                    }
599                }, b.prototype.reply = function (a, b, c) {
600                    this._isShutDown || (this._requests[a] = { fn: b, ctx: c });
601                }, b.prototype.request = function (a) {
602                    if (!this._isShutDown) {
603                        var b = this._requests[a];return b ? b.fn.call(b.ctx) : (this._logger.warn(d, "#request > No request handler for: " + a), null);
604                    }
605                }, b.prototype._removeListener = function (a, b, c) {
606                    b = "function" == typeof b ? b : null;var d = this._listeners[a];if (d) {
607                        if (!d.length || null == b && !c) return void delete this._listeners[a];for (var e = 0; e < d.length; e++) {
608                            var f = d[e];null !== b && b !== f.fn || c && c !== f.ctx || this._listeners[a].splice(e, 1);
609                        }
610                    }
611                };var d = "radio::Channel";a.radio.Channel = b;
612            }(d), function (a) {
613                "use strict";
614                function b(a) {
615                    if (!a) throw new Error("Reference to the logger object cannot be NULL");this._logger = a, this._channels = {};
616                }var c = a.radio.Channel;b.prototype.channel = function (a) {
617                    return this._channels[a] || (this._channels[a] = new c(a, this._logger)), this._channels[a];
618                }, b.prototype.shutdown = function () {
619                    for (var a in this._channels) {
620                        this._channels.hasOwnProperty(a) && this._channels[a].shutdown();
621                    }
622                }, a.radio.Radio = b;
623            }(d), function (a) {
624                "use strict";
625                function b(a, b) {
626                    function c() {
627                        this.constructor = a;
628                    }for (var d in b) {
629                        b.hasOwnProperty(d) && (a[d] = b[d]);
630                    }return c.prototype = b.prototype, a.prototype = new c(), a.__super__ = b.prototype, a;
631                }a.extend = b;
632            }(d), function (a) {
633                "use strict";
634                function b() {}b.prototype.write = function (a) {
635                    throw new Error("Implementation error: Method must be overridden.");
636                }, a.ILogWriter = b;
637            }(d), function (a) {
638                "use strict";
639                function b() {}b.prototype.write = function (a) {
640                    window.console && window.console.log && window.console.log(a);
641                }, a.LogWriter = b;
642            }(d), function (a) {
643                "use strict";
644                function b() {}b.prototype.setLogWriter = function (a) {
645                    throw new Error("Implementation error: Method must be overridden.");
646                }, b.prototype.getLogWriter = function () {
647                    throw new Error("Implementation error: Method must be overridden.");
648                }, b.prototype.getEnabled = function () {
649                    throw new Error("Implementation error: Method must be overridden.");
650                }, b.prototype.enable = function () {
651                    throw new Error("Implementation error: Method must be overridden.");
652                }, b.prototype.disable = function () {
653                    throw new Error("Implementation error: Method must be overridden.");
654                }, b.prototype.debug = function (a, b) {
655                    throw new Error("Implementation error: Method must be overridden.");
656                }, b.prototype.info = function (a, b) {
657                    throw new Error("Implementation error: Method must be overridden.");
658                }, b.prototype.warn = function (a, b) {
659                    throw new Error("Implementation error: Method must be overridden.");
660                }, b.prototype.error = function (a, b) {
661                    throw new Error("Implementation error: Method must be overridden.");
662                }, a.ILogger = b;
663            }(d), function (a) {
664                "use strict";
665                function b() {
666                    this._logWriter = new d();
667                }function c(a) {
668                    return a < 10 ? "00" + a : a < 100 ? "0" + a : "" + a;
669                }var d = a.LogWriter;b.prototype.setLogWriter = function (a) {
670                    if (!a) throw new Error("Reference to the ILogWriter object cannot be NULL");this._logWriter = a, this._enabled = !1;
671                }, b.prototype.getLogWriter = function () {
672                    return this._logWriter;
673                }, b.prototype.getEnabled = function () {
674                    return this._enabled;
675                }, b.prototype.enable = function () {
676                    this._enabled = !0;
677                }, b.prototype.disable = function () {
678                    this._enabled = !1;
679                }, b.prototype.debug = function (a, b) {
680                    this._log(a, f, b);
681                }, b.prototype.info = function (a, b) {
682                    this._log(a, e, b);
683                }, b.prototype.warn = function (a, b) {
684                    this._log(a, g, b);
685                }, b.prototype.error = function (a, b) {
686                    this._log(a, h, b);
687                }, b.prototype._log = function (a, b, d) {
688                    if (b == h || this._enabled) {
689                        var e = "",
690                            f = new Date();
690e += "[" + f.toTimeString() + "." + c(f.getMilliseconds()) + "] [" + b + "] ", e += "[" + a + "] " + d, this._logWriter.write(e);
691                    }
692                };var e = "INFO",
693                    f = "DEBUG",
694                    g = "WARN",
695                    h = "ERROR";a.Logger = b;
696            }(d), function (a) {
697                "use strict";
698                function b(a, b) {
699                    this._pluginName = a, this._eventName = b;
700                }var c = a.radio.Channel;b.prototype.getPluginName = function () {
701                    return this._pluginName;
702                }, b.prototype.getEventName = function () {
703                    return this._eventName;
704                }, b.prototype.getName = function () {
705                    return this._pluginName + c.SEPARATOR + this._eventName;
706                }, a.Trigger = b;
707            }(d), function (a) {
708                "use strict";
709                function b(a, b) {
710                    this.name = a, this.data = b;
711                }b.SUCCESS = "success", b.ERROR = "error", b.createFromTrigger = function (a) {
712                    return new b(a.getName());
713                }, a.Event = b;
714            }(d), function (a) {
715                "use strict";
716                function b() {
717                    this._events = {};
718                }b.prototype.addEventListener = function (a, b, c) {
719                    a && b && (c = c || window, this._events[a] = this._events[a] || [], this._events[a].push({ cb: b, ctx: c }));
720                }, b.prototype.removeEventListener = function (a, b, c) {
721                    if (a && b) {
722                        c = c || window;var d,
723                            e,
724                            f = !1;for (e in this._events) {
725                            if (a === e) {
726                                f = !0;break;
727                            }
728                        }if (f) {
729                            for (d = this._events[e].length - 1; d >= 0; d--) {
730                                var g = this._events[e][d];b === g.cb && c === g.ctx && this._events[e].splice(d, 1);
731                            }this._events[e].length || delete this._events[e];
732                        }
733                    }
734                }, b.prototype.dispatchEvent = function (a) {
735                    if (a.name) {
736                        var b, c;for (b in this._events) {
737                            if (this._events.hasOwnProperty(b) && a.name === b) {
738                                var d = this._events[b],
739                                    e = d.slice(0),
740                                    f = e.length;for (c = 0; c < f; c++) {
741                                    e[c].cb.call(e[c].ctx, a);
742                                }break;
743                            }
744                        }
745                    }
746                }, b.prototype.removeAllListeners = function (a) {
747                    if (a) {
748                        var b, c;for (c in this._events) {
749                            if (this._events.hasOwnProperty(c)) {
750                                for (b = this._events[c].length - 1; b >= 0; b--) {
751                                    var d = this._events[c][b];d.ctx === a && this._events[c].splice(b, 1);
752                                }this._events[c].length || delete this._events[c];
753                            }
754                        }
755                    } else this._events = {};
756                }, a.EventDispatcher = b;
757            }(d), function (a) {
758                "use strict";
759                function b() {}function c(a, b) {
760                    this.url = a || null, this.method = b, this._xmlhttp = null;
761                }function d() {
762                    d.__super__.constructor.call(this), this._connection = null;
763                }var e = a.Event,
764                    f = a.EventDispatcher;b.GET = "GET", d.RESPONSE = "response", d.INSTANCE = "instance", a.extend(d, f), d.prototype.close = function () {
765                    this.removeAllListeners(null);
766                }, d.prototype.load = function (a) {
767                    a && a.method && a.url && (a._xmlhttp = this._createCORSRequest(a), a._xmlhttp ? a._xmlhttp.send() : this._loadImage(a));
768                }, d.prototype._createCORSRequest = function (a) {
769                    var b = null;if (void 0 !== window.XMLHttpRequest) {
770                        var c = new window.XMLHttpRequest();"withCredentials" in c && (b = c, b.open(a.method, a.url, !0));
771                    }if (null == b && void 0 !== window.XDomainRequest && (b = new window.XDomainRequest(), b.open(a.method, a.url)), b) {
772                        var f = {};f[d.INSTANCE] = this;var g = this;b.onload = function () {
773                            if (b.status && parseInt(b.status, 10) >
773= 400) return this.onerror();f[d.RESPONSE] = b.responseText, g.dispatchEvent(new e(e.SUCCESS, f));
774                        }, b.onerror = function () {
775                            g.dispatchEvent(new e(e.ERROR, f));
776                        };
777                    }return b;
778                }, d.prototype._loadImage = function (a) {
779                    this._connection || (this._connection = new Image(), this._connection.alt = ""), this._connection.src = a.url;var b = {};b[d.RESPONSE] = "", b[d.INSTANCE] = this, this.dispatchEvent(new e(e.SUCCESS, b));
780                }, a.URLRequestMethod = b, a.URLRequest = c, a.URLLoader = d;
781            }(d), function (a) {
782                "use strict";
783                var b = "2.1.0.161",
784                    c = "43bccc",
785                    d = {};d.getVersion = function () {
786                    return "js-" + b + "-" + c;
787                }, d.getMajor = function () {
788                    return d.getNumberAtPosition(0);
789                }, d.getMinor = function () {
790                    return d.getNumberAtPosition(1);
791                }, d.getMicro = function () {
792                    return d.getNumberAtPosition(2);
793                }, d.getPatch = function () {
794                    return d.getNumberAtPosition(3);
795                }, d.getBuild = function () {
796                    return c;
797                }, d.getApiLevel = function () {
798                    return 4;
799                }, d.getNumberAtPosition = function (a) {
800                    return b.split(".")[a];
801                }, a.Version = d;
802            }(c), function (a) {
803                "use strict";
804                function b(a, b) {
805                    this._message = a, this._details = b;
806                }b.prototype.getMessage = function () {
807                    return this._message;
808                }, b.prototype.getDetails = function () {
809                    return this._details;
810                }, a.ErrorInfo = b;
811            }(c), function (a) {
812                "use strict";
813                function b() {
814                    this.debugLogging = !1;
815                }a.HeartbeatConfig = b;
816            }(c), function (a) {
817                "use strict";
818                function b() {}b.prototype.onError = function (a) {}, a.HeartbeatDelegate = b;
819            }(c), function (a) {
820                "use strict";
821                function b() {}b.prototype.configure = function (a) {
822                    throw new Error("Implementation error: Method must be overridden.");
823                }, b.prototype.bootstrap = function (a) {
824                    throw new Error("Implementation error: Method must be overridden.");
825                }, b.prototype.setup = function () {
826                    throw new Error("Implementation error: Method must be overridden.");
827                }, b.prototype.destroy = function () {
828                    throw new Error("Implementation error: Method must be overridden.");
829                }, b.prototype.enable = function () {
830                    throw new Error("Implementation error: Method must be overridden.");
831                }, b.prototype.disable = function () {
832                    throw new Error("Implementation error: Method must be overridden.");
833                }, b.prototype.getName = function () {
834                    throw new Error("Implementation error: Method must be overridden.");
835                }, b.prototype.isInitialized = function () {
836                    throw new Error("Implementation error: Method must be overridden.");
837                }, b.prototype.resolveData = function (a) {
838                    throw new Error("Implementation error: Method must be overridden.");
839                }, a.plugin.IPlugin = b;
840            }(d), function (a) {
841                "use strict";
842                function b(a, b, c, d) {
843                    this.trigger = a, this.action = c, this.plugin = b, this._paramMappings = {}
843, this.mergeParams(d);
844                }var c = a.plugin.ParamMapping;b.prototype.mergeParams = function (a) {
845                    if (a) for (var b = 0; b < a.length; b++) {
846                        var c = a[b];this._paramMappings[c.getKeyName()] = c;
847                    }
848                }, b.prototype.getParams = function () {
849                    var a = [];for (var b in this._paramMappings) {
850                        this._paramMappings.hasOwnProperty(b) && a.push(this._paramMappings[b]);
851                    }return a;
852                }, b.prototype.addParam = function (a) {
853                    this._paramMappings[a.getKeyName()] = a;
854                }, b.prototype.removeParam = function (a, b) {
855                    var d = new c(a, b);this._paramMappings.hasOwnProperty(d.getKeyName()) && delete this._paramMappings[d.getKeyName()];
856                }, a.plugin.Behaviour = b;
857            }(d), function (a) {
858                "use strict";
859                function b(a, b, d) {
860                    this._pluginName = a, this._key = b, this._paramName = d || a + c.SEPARATOR + b;
861                }var c = a.radio.Channel;b.prototype.getPluginName = function () {
862                    return this._pluginName;
863                }, b.prototype.getKey = function () {
864                    return this._key;
865                }, b.prototype.getKeyName = function () {
866                    return this._pluginName + c.SEPARATOR + this._key;
867                }, b.prototype.getParamName = function () {
868                    return this._paramName;
869                }, a.plugin.ParamMapping = b;
870            }(d), function (a) {
871                "use strict";
872                function b(a) {
873                    if (!a) throw new Error("Reference to the logger object cannot be NULL");this._logger = a, this._plugins = {}, this._behaviours = {}, this._radio = new d(this._logger), this._dataChannel = this._radio.channel(g), this._ctrlChannel = this._radio.channel(h);
874                }var c = a.Event,
875                    d = a.radio.Radio,
876                    e = a.radio.Channel,
877                    f = a.plugin.Behaviour;b.ERROR = "error", b.prototype.addPlugin = function (a) {
878                    var b = a.getName();this._plugins[b] && this._logger.warn(i, "#addPlugin > Replacing plugin: " + b), this._plugins[b] = a, a.bootstrap(this);
879                }, b.prototype.setupPlugins = function () {
880                    for (var a in this._plugins) {
881                        this._plugins.hasOwnProperty(a) && this._plugins[a].setup();
882                    }
883                }, b.prototype.pluginExists = function (a) {
884                    return !!this._plugins[a];
885                }, b.prototype.isPluginInitialized = function (a) {
886                    return this._plugins[a] && this._plugins[a].isInitialized();
887                }, b.prototype.on = function (a, b, c, d) {
888                    this._dataChannel.on(a + e.SEPARATOR + b, c, d);
889                }, b.prototype.off = function (a, b, c, d) {
890                    var f = a && b ? a + e.SEPARATOR + b : null;this._dataChannel.off(f, c, d);
891                }, b.prototype.trigger = function (a) {
892                    var b = a.name,
893                        c = this._behaviours[b];if (c) {
894                        var d,
895                            e,
896                            f,
897                            g,
898                            h,
899                            i = {},
900                            j = {};for (d = 0; d < c.length; d++) {
901                            if (f = c[d], g = f.getParams()) for (e = 0; e < g.length; e++) {
902                                h = g[e], i[h.getPluginName()] = i[h.getPluginName()] || [], h.key in i[h.getPluginName()] || i[h.getPluginName()].push(h.getKey());
903                            }
904                        }for (var k in i) {
905                            i.hasOwnProperty(k) && (j[k] = this.request(k, i[k]));
906                        }for (d = 0; d < c.length; d++) {
907                            f = c[d];var l = { _behaviour: f, _eventData: a.data || {} };if (g = f.getParams()) {
908                                for (e = 0; e < g.length; e++) {
909                                    h = g[e], l[h.getParamName()] = j[h.getPluginName()] ? j[h.getPluginName()][h.getKey()] : null;
910                                }this.command(f.plugin.getName(), f.action, l);
911                            }
912                        }
913                    }this._dataChannel.trigger(a);
914                }, b.prototype.request = function (a, b) {
915                    var c = this._plugins[a];return c && b && 0 != b.length ? c.resolveData(b) : null;
916                }, b.prototype.raise = function (a) {
917                    this._errorInfo = a;var d = new c(b.ERROR, a);this._ctrlChannel.trigger(d);
918                }, b.prototype.getErrorInfo = function () {
919                    return this._errorInfo;
920                }, b.prototype.destroy = function () {
921                    this._radio.shutdown();for (var a in this._plugins) {
922                        this._plugins.hasOwnProperty(a) && this._plugins[a].destroy();
923                    }
924                }, b.prototype.comply = function (a, b, c) {
925                    this._dataChannel.comply(a.getName() + e.SEPARATOR + b, c, a);
926                }, b.prototype.command = function (a, b, c) {
927                    this._dataChannel.command(a + e.SEPARATOR + b, c);
928                }, b.prototype.registerBehaviour = function (a, b, c, d) {
929                    var e = a.getName(),
930                        g = new f(a, b, c, d);this._behaviours[e] = this._behaviours[e] || [], this._behaviours[e].push(g);
931                };var g = "data_channel",
932                    h = "ctrl_channel",
933                    i = "plugin::PluginManager";a.plugin.PluginManager = b;
934            }(d), function (a, b) {
935                "use strict";
936                function c(a) {
937                    this._name = a, this._isInitialized = !1, this._isDestroyed = !1, this._isEnabled = !0, this._dataResolver = {}
937, this._logTag = "plugin::" + this.getName(), this._logger = new d();
938                }var d = a.Logger,
939                    e = a.Trigger,
940                    f = a.Event,
941                    g = b.ErrorInfo;c.INITIALIZED = "initialized", c.prototype.configure = function (a) {}, c.prototype.bootstrap = function (a) {
942                    this._pluginManager = a, this._isDestroyed && this._pluginManager.raise(new g("Invalid state.", "Plugin already destroyed."));
943                }, c.prototype.setup = function () {
944                    this._trigger(c.INITIALIZED), this._isInitialized = !0;
945                }, c.prototype.destroy = function () {
946                    this._isDestroyed || (this._isDestroyed = !0, this._teardown());
947                }, c.prototype.enable = function () {
948                    this._isEnabled = !0, this._enabled();
949                }, c.prototype.disable = function () {
950                    this._isEnabled = !1, this._disabled();
951                }, c.prototype.getName = function () {
952                    return this._name;
953                }, c.prototype.getLogger = function () {
954                    return this._logger;
955                }, c.prototype.isInitialized = function () {
956                    return this._isInitialized;
957                }, c.prototype.resolveData = function (a) {
958                    if (!this._isEnabled || !this._isInitialized) return this._logger.warn(this._logTag, "Unable to retrieve plugin data. Plugin: " + this._name + ". Enabled: " + this._isEnabled + ". Initialized: " + this._isInitialized + "."), null;if ("function" == typeof this._dataResolver) return this._dataResolver.call(this, a);var b = null;if (a) for (var c = 0; c < a.length; c++) {
959                        var d = a[c];this._dataResolver.hasOwnProperty(d) && (b = b || {}, "function" == typeof this._dataResolver[d] ? b[d] = this._dataResolver[d].call(this) : b[d] = this._dataResolver[d]);
960                    }return b;
961                }, c.prototype.toString = function () {
962                    return "<plugin: " + this._name + ">";
963                }, c.prototype._enabled = function () {}, c.prototype._disabled = function () {}, c.prototype._teardown = function () {}, c.prototype._canProcess = function () {
964                    return this._isEnabled ? !this._isDestroyed || (this._logger.error(this._logTag, "Plugin destroyed."), !1) : (this._logger.error(this._logTag, "Plugin disabled."), !1);
965                }, c.prototype._trigger = function (a, b) {
966                    var c = f.createFromTrigger(new e(this.getName(), a));c.data = b, this._pluginManager.trigger(c);
967                }, a.plugin.BasePlugin = c;
968            }(d, c), function (a) {
969                "use strict";
970                function b(a, b, c) {
971                    this.name = a, this.interval = b, this.isActive = !1, this.repeatCount = void 0 !== c ? c : e, this._nextTickTimestamp = 0, this.reset();
972                }function c(a, b) {
973                    if (!a) throw new Error("Reference to the ClockService object cannot be NULL");if (this._service = a, !b) throw new Error("Reference to the logger object cannot be NULL");this._logger = b, this._isDestroyed = !1, this._timers = {};var c = this;this._clock = window.setInterval(function () {
974                        c._onTick();
975                    }, 1e3 * f);
976                }b.prototype.reset = function () {
977                    this.tick = 0, this._createdTimestamp = new Date().getTime(), this._updateNextTickTimestamp();
978                }, b.prototype.shouldTick = function () {
979                    return new Date().getTime() > this._nextTickTimestamp - g / 2 && (this.tick++, this._updateNextTickTimestamp(), !0);
980                }, b.prototype._updateNextTickTimestamp = function () {
981                    var a = new Date().getTime();this._nextTickTimestamp = a + 1e3 * this.interval - 1;
982                }, c.prototype.createTimer = function (a, c, d) {
983                    this._timers[a] = new b(a, c, d);
984                }, c.prototype.destroyTimer = function (a) {
985                    delete this._timers[a];
986                }, c.prototype.resumeTimer = function (a, b) {
987                    b = void 0 !== b && b, this._logger.debug(d, "#resumeTimer(name=" + a + ", reset=" + b + ")");var c = this._timers[a];c && (c.isActive = !0, b && c.reset());
988                }, c.prototype.pauseTimer = function (a, b) {
989                    b = void 0 !== b && b, this._logger.debug(d, "#pauseTimer(name=" + a + ", reset=" + b + ")");var c = this._timers[a];c && (c.isActive = !1, b && c.reset());
990                }, c.prototype.isTimerPaused = function (a) {
991                    var b = this._timers[a];return !!b && !b.isActive;
992                }, c.prototype.destroy = function () {
993                    this._isDestroyed || (this._isDestroyed = !0, this._timers = {}, window.clearInterval(this._clock));
994                }
994, c.prototype._onTick = function () {
995                    for (var a in this._timers) {
996                        if (this._timers.hasOwnProperty(a)) {
997                            var b = this._timers[a];b.isActive && b.shouldTick() && (b.interval > 1 && this._logger.debug(d, "#_onTick() > " + b.name + "(" + b.tick + " | " + b.repeatCount + ")"), 0 != b.repeatCount ? (this._service.onTick(b.name, b.interval, b.tick), b.repeatCount != e && b.repeatCount--) : this.destroyTimer(b.name));
998                        }
999                    }
1000                };var d = "service.clock::TimerManager",
1001                    e = -1,
1002                    f = .25,
1003                    g = 1e3 * f;a.clock.TimerDescriptor = b, a.clock.TimerManager = c;
1004            }(e), function (a, b, c) {
1005                "use strict";
1006                function d(a) {
1007                    if (d.__super__.constructor.call(this, h), !a) throw new Error("Reference to the logger object cannot be NULL");this._logger = a, this._timerManager = new e(this, this._logger), this._setupDataResolver();
1008                }var e = c.clock.TimerManager,
1009                    f = b.StringUtils,
1010                    g = a.plugin.BasePlugin;a.extend(d, g), d.prototype.bootstrap = function (a) {
1011                    d.__super__.bootstrap.call(this, a), this._pluginManager.comply(this, i, this._cmdCreate), this._pluginManager.comply(this, k, this._cmdResume), this._pluginManager.comply(this, j, this._cmdPause), this._pluginManager.comply(this, l, this._cmdDestroy);
1012                }, d.prototype._teardown = function () {
1013                    this._timerManager.destroy();
1014                }, d.prototype._cmdCreate = function (a) {
1015                    var b = a[o] || s;this._timerManager.createTimer(a[m], a[n], b);
1016                }, d.prototype._cmdPause = function (a) {
1017                    this._timerManager.pauseTimer(a[m], !!a[q]);
1018                }, d.prototype._cmdResume = function (a) {
1019                    this._timerManager.resumeTimer(a[m], !!a[q]);
1020                }, d.prototype._cmdDestroy = function (a) {
1021                    this._timerManager.destroyTimer(a[m]);
1022                }, d.prototype.onTick = function (a, b, c) {
1023                    a += ".tick";var d = {};d[m] = a, d[n] = b, d[p] = c, this._trigger(a, d);
1024                }, d.prototype._setupDataResolver = function () {
1025                    var a = {},
1026                        b = this._timerManager;a[r] = function (a) {
1027                        return b.isTimerPaused(a);
1028                    }, this._dataResolver = function (b) {
1029                        if (!b || 0 == b.length) return null;for (var c = null, d = 0; d < b.length; d++) {
1030                            var e = b[d];if (c = c || {}, f.startsWith(e, r)) {
1031                                var g = e.split(r + ".");g.length > 0 && (c[e] = a[r].call(this, g[1]));
1032                            }
1033                        }return c;
1034                    };
1035                };var h = "service.clock",
1036                    i = "create",
1037                    j = "pause",
1038                    k = "resume",
1039                    l = "destroy",
1040                    m = "name",
1041                    n = "interval",
1042                    o = "repeat_count",
1043                    p = "tick",
1044                    q = "reset",
1045                    r = "is_paused",
1046                    s = -1;c.clock.ClockService = d;
1047            }(d, b, e), function (a, b, c) {
1048                "use strict";
1049                function d(a, b) {
1050                    if (this._logger = new e(), this._pluginManager = new f(this._logger), this._pluginManager.addPlugin(new g(this._logger)), b) for (var c = 0; c < b.length; c++) {
1051                        this._pluginManager.addPlugin(b[c]);
1052                    }this._pluginManager.setupPlugins(), this._isDestroyed = !1;
1053                }var e = a.Logger,
1054                    f = a.plugin.PluginManager,
1055                    g = b.clock.ClockService;d.prototype.configure = function (a) {
1056                    if (!a) throw new Error("Configuration object cannot be NULL.");a.debugLogging ? this._logger.enable() : this._logger.disable(), this._isDestroyed && this._logger.error(h, "Instance is destroyed.");
1057                }, d.prototype.destroy = function () {
1058                    this._isDestroyed || (this._pluginManager.destroy(), this._isDestroyed = !0);
1059                };var h = "Heartbeat";c.Heartbeat = d;
1060            }(d, e, c), a.ADB || (a.ADB = {}), a.ADB.core || (a.ADB.core = d), a.ADB.va || (a.ADB.va = c), a.ADB.va.utils || (a.ADB.va.utils = b), a.ADB.va.plugins || (a.ADB.va.plugins = {});
1061        }(this);
1062
1063        // VideoPlayerPlugin
1064        !function (a) {
1065            if (void 0 === b) var b = {};!function (a) {
1066                "use strict";
1067                var b = {};b.ASSET_TYPE_VOD = "vod", b.ASSET_TYPE_LIVE = "live", b.ASSET_TYPE_LINEAR = "linear", a.AssetType = b;
1068            }(b), function (a) {
1069                "use strict";
1070                function b() {
1071                    this.playerName = null, this.name = null, this.position = null, this.startTime = null;
1072                }b.prototype.toString = function () {
1073                    return "playerName=" + this.playerName + ", name=" + this.name + ", position=" + this.position + ", startTime=" + this.startTime;
1074                }, a.AdBreakInfo = b;
1075            }(b), function (a) {
1076                "use strict";
1077                function b() {
1078                    this.id = null, this.name = null, this.length = null, this.position = null, this.granularTracking = !0;
1079                }b.prototype.toString = function () {
1080                    return "id=" + this.id + ", name=" + this.name + ", length=" + this.length + ", position=" + this.position + ", granularTracking=" + this.granularTracking;
1081                }, a.AdInfo = b;
1082            }(b), function (a) {
1083                "use strict";
1084                function b() {
1085                    this.name = null, this.length = null, this.position = null, this.startTime = null;
1086                }b.prototype.toString = function () {
1087                    return "name=" + this.name + ", length=" + this.length + ", position=" + this.position + ", startTime=" + this.startTime;
1088                }, a.ChapterInfo = b;
1089            }(b), function (a) {
1090                "use strict";
1091                function b() {
1092                    this.bitrate = null, this.fps = null, this.droppedFrames = null, this.startupTime = null;
1093                }b.prototype.toString = function () {
1094                    return "bitrate=" + this.bitrate + ", fps=" + this.fps + ", droppedFrames=" + this.droppedFrames + ", startupTime=" + this.startupTime;
1095                }, a.QoSInfo = b;
1096            }(b), function (a) {
1097                "use strict";
1098                function b() {
1099                    this.playerName = null, this.id = null, this.name = null, this.length = null, this.playhead = null, this.streamType = null, this.resumed = !1;
1100                }b.prototype.toString = function () {
1101                    return "playerName=" + this.playerName + ", id=" + this.id + ", name=" + this.name + ", length=" + this.length + ", playhead=" + this.playhead + ", streamType=" + this.streamType + ", resumed=" + this.resumed;
1102                }, a.VideoInfo = b;
1103            }(b), function (a) {
1104                "use strict";
1105                function b() {
1106                    this.debugLogging = !1;
1107                }a.VideoPlayerPluginConfig = b;
1108            }(b), function (a) {
1109                "use strict";
1110                function b() {}b.prototype.getVideoInfo = function () {
1111                    throw new Error("Implementation error: Method must be overridden.");
1112                }, b.prototype.getAdBreakInfo = function () {
1113                    return null;
1114                }, b.prototype.getAdInfo = function () {
1115                    return null;
1116                }, b.prototype.getChapterInfo = function () {
1117                    return null;
1118                }, b.prototype.getQoSInfo = function () {
1119                    return null;
1120                }, a.VideoPlayerPluginDelegate = b;
1121            }(b), function (a, b) {
1122                "use strict";
1123                function c(a) {
1124                    if (c.__super__.constructor.call(this, h), !a) throw new Error("PlayerPlugin delegate cannot be NULL.");this._delegate = a, this._isTrackingSessionActive = !1, this._isTrackingSessionStarted = !1, this._setupDataResolver();
1125                }var d = a.plugin.ParamMapping,
1126                    e = a.Trigger,
1127                    f = a.plugin.BasePlugin,
1128                    g = b.VideoPlayerPluginConfig;a.extend(c, f), c.prototype.configure = function (a) {
1129                    if (!a) throw new Error("Reference to the configuration data cannot be NULL.");if (!(a instanceof g)) throw new Error("Expected config data to be instance of VideoPlayerPluginConfig.");a.debugLogging ? this._logger.enable() : this._logger.disable(), this._logger.debug(this._logTag, "#configure(debugLogging=" + a.debugLogging + ")");
1130                }, c.prototype.bootstrap = function (a) {
1131                    c.__super__.bootstrap.call(this, a), this._registerCommands(), this._registerBehaviours();
1132                }, c.prototype._cmdVideoIdleStart = function (a) {
1133                    this._logger.info(this._logTag, "#_cmdVideoIdleStart()"), this._videoIdle = !0;
1134                }, c.prototype._cmdVideoIdleResume = function (a) {
1135                    this._logger.info(this._logTag, "#_cmdVideoIdleResume()"), this._videoIdle && (this._trigger(p), this._trigger(q), a.isInChapter && this._trigger(E), a.isInAd && (this._trigger(x), this._isTrackingAd = !0)), this._videoIdle = !1;
1136                }, c.prototype.trackSessionStart = function () {
1137                    if (this._logger.info(this._logTag, "#trackSessionStart()"), this._canProcess()) {
1138                        if (!this._isTrackingSessionActive) return void this._logger.warn(this._logTag, "#trackSessionStart() > No active tracking session.");if (this._isTrackingSessionStarted) return void this._logger.info(this._logTag, "#trackSessionStart() > Tracking session already started.");this._trigger(p), this._isTrackingSessionStarted = !0;var a = this._dataResolver(["video.resumed"]);a.hasOwnProperty("video.resumed") && a["video.resumed"] && this._trigger(q);
1139                    }
1140                }, c.prototype.trackVideoLoad = function () {
1141                    this._logger.info(this._logTag, "#trackVideoLoad()"), this._canProcess() && (this._isTrackingAd = !1, this._contentStarted = !1, this._isPaused = !0, this._isBuffering = !1, this._isSeeking = !1, this._playheadTimer = null, this._previousPlayhead = -1, this._stalledPlayheadCount = 0, this._playheadStalled = !1, this._videoIdle = !1, this._trigger(m), this._isTrackingSessionActive = !0, this._isTrackingSessionStarted = !1);
1142                }, c.prototype.trackVideoUnload = function () {
1143                    if (this._logger.info(this._logTag, "#trackVideoUnload()"), this._canProcess()) {
1144                        if (!this._isTrackingSessionActive) return void this._logger.warn(this._logTag, "#trackVideoUnload() > No active tracking session.");this._stopPlayheadTimer(), this._trigger(n), this._isTrackingSessionActive = !1, this._isTrackingSessionStarted = !1, this._contentStarted = !1;
1145                    }
1146                }, c.prototype.trackPlay = function () {
1147                    this._logger.info(this._logTag, "#trackPlay()"), this._canProcess() && this._startSessionIfNeeded("trackPlay") && (this._isPaused = !1, this._trigger(u), this._startPlayheadTimer());
1148                }, c.prototype.trackPause = function () {
1149                    if (this._logger.info(this._logTag, "#trackPause()"), this._canProcess() && this._startSessionIfNeeded("trackPause")) {
1150                        this._stopPlayheadTimer();var a = {};a[M] = !1, this._isPaused = !0, this._trigger(v, a);
1151                    }
1152                }, c.prototype.trackBufferStart = function () {
1153                    this._logger.info(this._logTag, "#trackBufferStart()"), this._canProcess() && this._startSessionIfNeeded("trackBufferStart") && (this._stopPlayheadTimer(), this._isBuffering = !0, this._trigger(A));
1154                }, c.prototype.trackBufferComplete = function () {
1155                    this._logger.info(this._logTag, "#trackBufferComplete()"), this._canProcess() && this._startSessionIfNeeded("trackBufferComplete") && (this._isBuffering = !1, this._trigger(B), this._startPlayheadTimer());
1156                }, c.prototype.trackSeekStart = function () {
1157                    this._logger.info(this._logTag, "#trackSeekStart()"), this._canProcess() && this._startSessionIfNeeded("trackSeekStart") && (this._stopPlayheadTimer(), this._isSeeking = !0, this._trigger(C));
1158                }, c.prototype.trackSeekComplete = function () {
1159                    if (this._logger.info(this._logTag, "#trackSeekComplete()"), this._canProcess() && this._startSessionIfNeeded("trackSeekComplete")) {
1160                        this._isSeeking = !1, this._trigger(D), this._startPlayheadTimer();var a = this._dataResolver(["ad.isInAd"]);this._isTrackingAd = a.hasOwnProperty("ad.isInAd") && a["ad.isInAd"];
1161                    }
1162                }, c.prototype.trackComplete = function (a, b) {
1163                    if (this._logger.info(this._logTag, "#trackComplete()"), this._canProcess() && this._startSessionIfNeeded("trackComplete")) {
1164                        this._stopPlayheadTimer();var c = {};c[L] = a, b = void 0 === b || !!b, b ? this._trigger(r, c) : (this._trigger(s), this._trigger(o, c));
1165                    }
1166                }, c.prototype.trackTimedMetadata = function (a) {
1167                    this._logger.info(this._logTag, "#trackComplete()"), this._canProcess() && this._startSessionIfNeeded("trackTimedMetadata") && this._trigger(t, a);
1168                }, c.prototype.trackChapterStart = function () {
1169                    this._logger.info(this._logTag, "#trackChapterStart()"), this._canProcess() && this._startSessionIfNeeded("#trackChapterStart") && this._trigger(E);
1170                }, c.prototype.trackChapterComplete = function () {
1171                    this._logger.info(this._logTag, "#trackChapterComplete()"), this._canProcess() && this._startSessionIfNeeded("trackChapterComplete") && this._trigger(F);
1172                }, c.prototype.trackChapterSkip = function () {
1173                    this._logger.info(this._logTag, "#trackChapterSkip()"), this._canProcess() && this._startSessionIfNeeded("trackChapterSkip") && this._trigger(G);
1174                }, c.prototype.trackAdStart = function () {
1175                    this._logger.info(this._logTag, "#trackAdStart()"), this._canProcess() && this._startSessionIfNeeded("trackAdStart") && (this._trigger(x), this._isTrackingAd = !0);
1176                }, c.prototype.trackAdComplete = function () {
1177                    this._logger.info(this._logTag, "#trackAdComplete()"), this._canProcess() && this._startSessionIfNeeded("trackAdComplete") && (this._trigger(y), this._isTrackingAd = !1);
1178                }, c.prototype.trackAdSkip = function () {
1179                    this._logger.info(this._logTag, "#trackAdSkip()"), this._canProcess() && this._startSessionIfNeeded("trackAdSkip") && (this._trigger(z), this._isTrackingAd = !1);
1180                }, c.prototype.trackBitrateChange = function () {
1181                    this._logger.info(this._logTag, "#trackBitrateChange()"), this._canProcess() && this._startSessionIfNeeded("trackBitrateChange") && this._trigger(H);
1182                }, c.prototype.trackVideoPlayerError = function (a) {
1183                    if (this._logger.info(this._logTag, "#trackVideoPlayerError(errorId=" + a + ")"), this._startSessionIfNeeded("trackVideoPlayerError")) {
1184                        var b = {};b[N] = l, b[O] = a, this._trigger(I, b);
1185                    }
1186                }, c.prototype.trackApplicationError = function (a) {
1187                    if (this._logger.info(this._logTag, "#trackApplicationError(errorId=" + a + ")"), this._startSessionIfNeeded("trackApplicationError")) {
1188                        var b = {};b[N] = k, b[O] = a, this._trigger(I, b);
1189                    }
1190                }, c.prototype._registerCommands = function () {
1191                    this._pluginManager.comply(this, "handleVideoIdleStart", this._cmdVideoIdleStart)
1191, this._pluginManager.comply(this, "handleVideoIdleResume", this._cmdVideoIdleResume);
1192                }, c.prototype._registerBehaviours = function () {
1193                    this._pluginManager.registerBehaviour(new e(j, J), this, "handleVideoIdleStart"), this._pluginManager.registerBehaviour(new e(j, K), this, "handleVideoIdleResume", [new d(i, "ad.isInAd", "isInAd"), new d(i, "chapter.isInChapter", "isInChapter")]);
1194                }, c.prototype._setupDataResolver = function () {
1195                    function a() {
1196                        return g.video ? g.video : (g.video = h._delegate.getVideoInfo(), h._logger.info(h._logTag, "Data from delegate > VideoInfo: " + g.video), g.video);
1197                    }function b() {
1198                        return g.ad ? g.ad : (g.ad = h._delegate.getAdInfo(), h._logger.info(h._logTag, "Data from delegate > AdInfo: " + g.ad), g.ad);
1199                    }function c() {
1200                        return g.pod ? g.pod : (g.pod = h._delegate.getAdBreakInfo(), h._logger.info(h._logTag, "Data from delegate > AdBreakInfo: " + g.pod), g.pod);
1201                    }function d() {
1202                        return g.chapter ? g.chapter : (g.chapter = h._delegate.getChapterInfo(), h._logger.info(h._logTag, "Data from delegate > ChapterInfo: " + g.chapter), g.chapter);
1203                    }function e() {
1204                        return g.qos ? g.qos : (g.qos = h._delegate.getQoSInfo(), h._logger.info(h._logTag, "Data from delegate > QoSInfo: " + g.qos), g.qos);
1205                    }var f = {},
1206                        g = {},
1207                        h = this;f["video.id"] = function () {
1208                        var b = a(),
1209                            c = b ? b.id : null;return h._logger.debug(h._logTag, "Resolving video.id: " + c), c;
1210                    }, f["video.name"] = function () {
1211                        var b = a(),
1212                            c = b ? b.name : null;return h._logger.debug(h._logTag, "Resolving video.name: " + c), c;
1213                    }, f["video.length"] = function () {
1214                        var b = a(),
1215                            c = b ? b.length : NaN;return h._logger.debug(h._logTag, "Resolving video.length: " + c), c;
1216                    }, f["video.playerName"] = function () {
1217                        var b = a(),
1218                            c = b ? b.playerName : null;return h._logger.debug(h._logTag, "Resolving video.playerName: " + c), c;
1219                    }, f["video.streamType"] = function () {
1220                        var b = a(),
1221                            c = b ? b.streamType : null;return h._logger.debug(h._logTag, "Resolving video.streamType: " + c), c;
1222                    }, f["video.playhead"] = function () {
1223                        var b = a(),
1224                            c = b ? b.playhead : NaN;return h._logger.debug(h._logTag, "Resolving video.playhead: " + c), c;
1225                    }, f["video.resumed"] = function () {
1226                        var b = a(),
1227                            c = !!b && b.resumed;return h._logger.debug(h._logTag, "Resolving video.resumed: " + c), c;
1228                    }, f["video.playheadStalled"] = function () {
1229                        return this._playheadStalled;
1230                    }, f["pod.name"] = function () {
1231                        var a = c(),
1232                            b = a ? a.name : null;return h._logger.debug(h._logTag, "Resolving pod.name: " + b), b;
1233                    }, f["pod.playerName"] = function () {
1234                        var a = c(),
1235                            b = a ? a.playerName : null;return h._logger.debug(h._logTag, "Resolving pod.playerName: " + b), b;
1236                    }, f["pod.position"] = function () {
1237                        var a = c(),
1238                            b = a ? a.position : NaN;return h._logger.debug(h._logTag, "Resolving pod.position: " + b), b;
1239                    }, f["pod.startTime"] = function () {
1240                        var a = c(),
1241                            b = a ? a.startTime : NaN;return h._logger.debug(h._logTag, "Resolving pod.startTime: " + b), b;
1242                    }, f["ad.isInAd"] = function () {
1243                        var a = b(),
1244                            c = null != a;return h._logger.debug(h._logTag, "Resolving ad.isInAd: " + c), c;
1245                    }, f["ad.isInAdBreak"] = function () {
1246                        var a = c(),
1247                            b = null != a;return h._logger.debug(h._logTag, "Resolving ad.isInAdBreak: " + b), b;
1248                    }, f["ad.id"] = function () {
1249                        var a = b(),
1250                            c = a ? a.id : null;return h._logger.debug(h._logTag, "Resolving ad.id: " + c), c;
1251                    }, f["ad.name"] = function () {
1252                        var a = b(),
1253                            c = a ? a.name : null;return h._logger.debug(h._logTag, "Resolving ad.name: " + c), c;
1254                    }, f["ad.length"] = function () {
1255                        var a = b(),
1256                            c = a ? a.length : NaN;return h._logger.debug(h._logTag, "Resolving ad.length: " + c), c;
1257                    }, f["ad.position"] = function () {
1258                        var a = b(),
1259                            c = a ? a.position : NaN;return h._logger.debug(h._logTag, "Resolving ad.position: " + c), c;
1260                    }, f["ad.granularTracking"] = function () {
1261                        var a = b(),
1262                            c = !!a && a.granularTracking;return h._logger.debug(h._logTag, "Resolving ad.granularTracking: " + c), c;
1263                    }, f["ad.trackingInterval"] = function () {
1264                        var a = Q;return h._logger.debug(h._logTag, "Resolving ad.trackingInterval: " + a), a;
1265                    }, f["chapter.isInChapter"] = function () {
1266                        var a = d(),
1267                            b = null != a;return h._logger.debug(h._logTag, "Resolving chapter.isInChapter: " + b), b;
1268                    }, f["chapter.name"] = function () {
1269                        var a = d(),
1270                            b = a ? a.name : null;return h._logger.debug(h._logTag, "Resolving chapter.name: " + b), b;
1271                    }, f["chapter.length"] = function () {
1272                        var a = d(),
1273                            b = a ? a.length : NaN;return h._logger.debug(h._logTag, "Resolving chapter.length: " + b), b;
1274                    }, f["chapter.position"] = function () {
1275                        var a = d(),
1276                            b = a ? a.position : NaN;return h._logger.debug(h._logTag, "Resolving chapter.position: " + b), b;
1277                    }, f["chapter.startTime"] = function () {
1278                        var a = d(),
1279                            b = a ? a.startTime : NaN;return h._logger.debug(h._logTag, "Resolving chapter.startTime: " + b), b;
1280                    }, f["qos.bitrate"] = function () {
1281                        var a = e(),
1282                            b = a ? a.bitrate : NaN;return h._logger.debug(h._logTag, "Resolving qos.bitrate: " + b), b;
1283                    }, f["qos.fps"] = function () {
1284                        var a = e(),
1285                            b = a ? a.fps : NaN;return h._logger.debug(h._logTag, "Resolving qos.fps: " + b), b;
1286                    }, f["qos.droppedFrames"] = function () {
1287                        var a = e(),
1288                            b = a ? a.droppedFrames : NaN;return h._logger.debug(h._logTag, "Resolving qos.droppedFrames: " + b), b;
1289                    }, f["qos.startupTime"] = function () {
1290                        var a = e(),
1291                            b = a ? 1e3 * a.startupTime : NaN;return h._logger.debug(h._logTag, "Resolving qos.startupTime: " + b), b;
1292                    }, this._dataResolver = function (a) {
1293                        if (!a || 0 == a.length) return null;g = {};for (var b = null, c = 0; c < a.length; c++) {
1294                            var d = a[c];b = b || {}
1294, b[d] = f.hasOwnProperty(d) ? f[d].call(this) : null;
1295                        }return b;
1296                    };
1297                }, c.prototype._trackPlayheadStall = function () {
1298                    this._canProcess() && (this._playheadStalled || (this._logger.info(this._logTag, "#_trackPlayheadStall()"), this._stalledPlayheadCount = 0, this._playheadStalled = !0, this._trigger(v)));
1299                }, c.prototype._trackExitStall = function () {
1300                    this._canProcess() && (this._stalledPlayheadCount = 0, !this._playheadStalled || this._isPaused || this._isSeeking || this._isBuffering || (this._logger.info(this._logTag, "#_trackExitStall()"), this._playheadStalled = !1, this._trigger(u)));
1301                }, c.prototype._startPlayheadTimer = function () {
1302                    var a = this;this._playheadTimer || this._isPaused || this._isSeeking || this._isBuffering || (this._playheadTimer = setInterval(function () {
1303                        if (a._canProcess()) {
1304                            var b = a._dataResolver(["ad.isInAd", "video.playhead"]);if (a._isTrackingAd) a._playheadStalled && a._trackExitStall(), b.hasOwnProperty("ad.isInAd") && !b["ad.isInAd"] && (a._isTrackingAd = !1);else {
1305                                var c = b["video.playhead"];c != a._previousPlayhead ? a._trackExitStall() : a._previousPlayhead >= 0 && c == a._previousPlayhead && ++a._stalledPlayheadCount == R && a._trackPlayheadStall(), c != a._previousPlayhead && c > 0 && !a._contentStarted && (a._isPaused || a._isBuffering || a._isSeeking || (a._logger.info(a._logTag, "#_playheadTimer playhead progress to: " + c), a._trigger(w), a._contentStarted = !0)), a._previousPlayhead = c;
1306                            }
1307                        }
1308                    }, P));
1309                }, c.prototype._stopPlayheadTimer = function () {
1310                    this._playheadTimer && (clearInterval(this._playheadTimer), this._playheadTimer = null), this._trackExitStall();
1311                }, c.prototype._startSessionIfNeeded = function (a) {
1312                    return this._isTrackingSessionActive ? (this._isTrackingSessionStarted || (this._logger.info(this._logTag, "#" + a + "() > Tracking session auto-start."), this.trackSessionStart()), !0) : (this._logger.warn(this._logTag, "#" + a + "() > No active tracking session."), !1);
1313                };var h = "player",
1314                    i = h,
1315                    j = "adobe-heartbeat",
1316                    k = "sourceErrorExternal",
1317                    l = "sourceErrorSDK",
1318                    m = "video_load",
1319                    n = "video_unload",
1320                    o = "video_session_end",
1321                    p = "video_start",
1322                    q = "video_resume",
1323                    r = "video_complete",
1324                    s = "video_skip",
1325                    t = "timed_metadata",
1326                    u = "play",
1327                    v = "pause",
1328                    w = "content_start",
1329                    x = "ad_start",
1330                    y = "ad_complete",
1331                    z = "ad_skip",
1332                    A = "buffer_start",
1333                    B = "buffer_complete",
1334                    C = "seek_start",
1335                    D = "seek_complete",
1336                    E = "chapter_start",
1337                    F = "chapter_complete",
1338                    G = "chapter_skip",
1339                    H = "bitrate_change",
1340                    I = "track_error",
1341                    J = "video_idle_start",
1342                    K = "video_idle_resume",
1343                    L = "callback",
1344                    M = "filter_report",
1345                    N = "source",
1346                    O = "error_id",
1347                    P = 1001,
1348                    Q = 1,
1349                    R = 2;b.VideoPlayerPlugin = c;
1350            }(a.ADB.core, b), a.ADB.va.plugins.videoplayer || (a.ADB.va.plugins.videoplayer = b);
1351        }(this);
1352
1353        // AdobeHeartbeatPlugin
1354        !function (a) {
1355            if (void 0 === b) var b = {};b.clock || (b.clock = {}), b.context || (b.context = {}), b.filter || (b.filter = {}), b.model || (b.model = {}), b.network || (b.network = {}), function (a, b) {
1356                "use strict";
1357                function c(a, b, c, d, e) {
1358                    if (!b) throw new Error("Reference to the channel object cannot be NULL");if (this._channel = b, !a) throw new Error("Reference to the pluginManager object cannot be NULL");if (this._pluginManager = a, !e) throw new Error("Reference to the logger object cannot be NULL");this._logTag = "ah::Timer." + c, this._logger = e, this._isDestroyed = !1, this._createTimer(c, d), this._installHandlers();
1359                }var d = a.Event;c.KEY_NAME = "name", c.KEY_INTERVAL = "interval", c.KEY_RESET = "reset", c.prototype.resume = function (a) {
1360                    this._logger.debug(this._logTag, "Starting timer: " + this._name);var b = {};b[c.KEY_NAME] = e + "." + this._name, b[c.KEY_RESET] = a, this._pluginManager.command(f, i, b);
1361                }, c.prototype.pause = function (a) {
1362                    this._logger.debug(this._logTag, "Stopping timer: " + this._name);var b = {};b[c.KEY_NAME] = e + "." + this._name, b[c.KEY_RESET] = a, this._pluginManager.command(f, h, b);
1363                }, c.prototype.destroy = function () {
1364                    if (!this._isDestroyed) {
1365                        this._isDestroyed = !0, this._uninstallHandlers();var a = {};a[c.KEY_NAME] = e + "." + this._name, this._pluginManager.command(f, j, a);
1366                    }
1367                }, c.prototype.setInterval = function (a) {
1368                    var b = k + "." + e + "." + this._name,
1369                        c = this._pluginManager.request(f, [b])[b];this.pause(!0), this._createTimer(this._name, a), c || this.resume(!0);
1370                }, c.prototype._cmdResume = function (a) {
1371                    var b = !1;null != a && a.hasOwnProperty(c.KEY_RESET) && (b = a[c.KEY_RESET]), this.resume(b);
1372                }, c.prototype._cmdPause = function (a) {
1373                    var b = !1;null != a && a.hasOwnProperty(c.KEY_RESET) && (b = a[c.KEY_RESET]), this.pause(b);
1374                }
1374, c.prototype._onTick = function (a, b) {
1375                    this._channel.trigger(new d("clock:" + this._name + ".tick", b));
1376                }, c.prototype._installHandlers = function () {
1377                    this._channel.comply("clock:" + this._name + ".resume", this._cmdResume, this), this._channel.comply("clock:" + this._name + ".pause", this._cmdPause, this), this._pluginManager.on(f, e + "." + this._name + ".tick", this._onTick, this);
1378                }, c.prototype._uninstallHandlers = function () {
1379                    this._channel.off(null, null, this), this._pluginManager.off(null, null, null, this);
1380                }, c.prototype._createTimer = function (a, b) {
1381                    this._name = a, this._interval = b;var d = {};d[c.KEY_NAME] = e + "." + this._name, d[c.KEY_INTERVAL] = this._interval, this._pluginManager.command(f, g, d);
1382                };var e = "heartbeat",
1383                    f = "service.clock",
1384                    g = "create",
1385                    h = "pause",
1386                    i = "resume",
1387                    j = "destroy",
1388                    k = "is_paused";b.clock.Timer = c;
1389            }(a.ADB.core, b), function (a, b) {
1390                "use strict";
1391                function c(a, b, d) {
1392                    c.__super__.constructor.call(this, a, b, f, h, d);
1393                }var d = a.Event,
1394                    e = b.clock.Timer;a.extend(c, e), c.prototype._onCheckStatusComplete = function (a) {
1395                    var b = a.data[l];if (this._logger.debug(this._logTag, "#_onCheckStatusComplete(interval=" + b + ")"), b) {
1396                        if (b == this._interval) return void this._logger.debug(this._logTag, "#_onCheckStatusComplete() > Interval value not changed.");b > g ? (this._logger.warn(this._logTag, "#_onCheckStatusComplete() > Interval value too large: " + b), this.setInterval(g)) : (this._logger.debug(this._logTag, "#_onCheckStatusComplete() > Interval changed to: " + b), this.setInterval(b));
1397                    } else this._logger.warn(this._logTag, "#_onCheckStatusComplete() > Invalid interval value."), this.setInterval(h);
1398                }, c.prototype._getSettings = function (a) {
1399                    this._logger.debug(this._logTag, "#_getSettings()"), this._channel.trigger(new d(i));
1400                }, c.prototype._installHandlers = function () {
1401                    c.__super__._installHandlers.call(this), this._channel.on(j, this._getSettings, this), this._channel.on(k, this._onCheckStatusComplete, this), this._channel.reply(l, function () {
1402                        return this._interval;
1403                    }, this);
1404                };var f = "check_status",
1405                    g = 600,
1406                    h = 60,
1407                    i = "clock:check_status.tick",
1408                    j = "clock:check_status.get_settings",
1409                    k = "net:check_status_complete",
1410                    l = "check_status_interval";b.clock.CheckStatusTimer = c;
1411            }(a.ADB.core, b), function (a, b) {
1412                "use strict";
1413                function c(a, b, d) {
1414                    c.__super__.constructor.call(this, a, b, e, f, d), this._doNotOverrideInterval = !1;
1415                }var d = b.clock.Timer;a.extend(c, d), c.prototype._onCheckStatusComplete = function (a) {
1416                    var b = a.data[g];if (this._logger.debug(this._logTag, "#_onCheckStatusComplete(interval=" + b + ")"), this._doNotOverrideInterval) this._logger.debug(this._logTag, "#_onCheckStatusComplete() > Interval value not changed. (doNotOverrideInterval = true)");else if (b) {
1417                        if (b == this._interval) return void this._logger.debug(this._logTag, "#_onCheckStatusComplete() > Interval value not changed.");this._logger.debug(this._logTag, "#_onCheckStatusComplete() > Interval changed to: " + b), this.setInterval(b);
1418                    } else this._logger.warn(this._logTag, "#_onCheckStatusComplete() > Invalid interval value."), this.setInterval(f);
1419                }, c.prototype._onUpdateReportingInterval = function (a) {
1420                    var b = a.data[g];if (this._doNotOverrideInterval = !!a.data[h], this._logger.debug(this._logTag, "#_onUpdateReportingInterval(interval=" + b + ", doNotOverrideInterval=" + this._doNotOverrideInterval + ")"), b) {
1421                        if (b == this._interval) return void this._logger.debug(this._logTag, "#_onUpdateReportingInterval() > Interval value not changed.");this._logger.debug(this._logTag, "#_onUpdateReportingInterval() > Interval changed to: " + b), this.setInterval(b);
1422                    } else this._logger.warn(this._logTag, "#_onUpdateReportingInterval() > Invalid interval value."), this.setInterval(f);
1423                }, c.prototype._installHandlers = function () {
1424                    c.__super__._installHandlers.call(this), this._channel.on(j, this._onCheckStatusComplete, this), this._channel.on(i, this._onUpdateReportingInterval, this), this._channel.reply(g, function () {
1425                        return this._interval;
1426                    }, this);
1427                };var e = "reporting",
1428                    f = 10,
1429                    g = "reporting_interval",
1430                    h = "do_not_override_interval",
1431                    i = "reporting:update_interval",
1432                    j = "net:check_status_complete";b.clock.ReportingTimer = c;
1433            }(a.ADB.core, b), function (a, b) {
1434                "use strict";
1435                function c(a, b, d) {
1436                    c.__super__.constructor.call(this, a, b, e, f, d);
1437                }var d = b.clock.Timer;a.extend(c, d);var e = "idle",
1438                    f = 1800;b.clock.IdleTimer = c;
1439            }(a.ADB.core, b), function (a, b) {
1440                "use strict";
1441                function c(a, b, d) {
1442                    c.__super__.constructor.call(this, a, b, e, f, d);
1443                }var d = b.clock.Timer;a.extend(c, d);var e = "flush_filter",
1444                    f = .25;b.clock.FlushFilterTimer = c;
1445            }(a.ADB.core, b), function (a, b) {
1446                "use strict";
1447                function c(a, b, c) {
1448                    if (!a) throw new Error("Reference to the pluginManager object cannot be NULL");if (!b) throw new Error("Reference to the channel object cannot be NULL");if (!c) throw new Error("Reference to the logger object cannot be NULL");this._isDestroyed = !1, this._reportingTimer = new f(a, b, c), this._checkStatusTimer = new d(a, b, c), this._flushFilterTimer = new e(a, b, c), this._idleTimer = new g(a, b, c);
1449                }var d = b.clock.CheckStatusTimer,
1450                    e = b.clock.FlushFilterTimer,
1451                    f = b.clock.ReportingTimer,
1452                    g = b.clock.IdleTimer;c.prototype.destroy = function () {
1453                    this._isDestroyed || (this._isDestroyed = !0, this._reportingTimer.destroy(), this._checkStatusTimer.destroy(), this._flushFilterTimer.destroy(), this._idleTimer.destroy());
1454                }, b.clock.Clock = c;
1455            }(a.ADB.core, b), function (a, b) {
1456                "use strict";
1457                function c(a, b) {
1458                    this.value = a, this.hint = b;
1459                }function d(a) {
1460                    this.realm = a, this.data = {};
1461                }c.HINT_SHORT = "short", d.prototype.setField = function (a, b, d) {
1462                    this.data[a] = new c(b, d);
1463                }, d.prototype._createAccessor = function (a, b, c) {
1464                    var d = this;return function () {
1465                        return arguments.length && (d[a] = arguments[0], d.setField(b, arguments[0], c)), d[a];
1466                    };
1467                }, b.model.Dao = d, b.model.DaoField = c;
1468            }(a.ADB.core, b), function (a, b) {
1469                "use strict";
1470                function c() {
1471                    if (c.__super__.constructor.call(this, "asset"), this.adId = this._createAccessor("_adI
1471d", "ad_id", null), this.sid = this._createAccessor("_sid", "ad_sid", null), this.resolver = this._createAccessor("_resolver", "resolver", null), this.podId = this._createAccessor("_podId", "pod_id", null), this.podPosition = this._createAccessor("_podPosition", "pod_position", null), this.podOffset = this._createAccessor("_podOffset", "pod_offset", null), this.podName = this._createAccessor("_podName", "pod_name", null), this.adLength = this._createAccessor("_adLength", "ad_length", null), this.adName = this._createAccessor("_adName", "ad_name", null), arguments.length && arguments[0] instanceof c) {
1472                        var a = arguments[0];this.adId(a.adId()), this.sid(a.sid()), this.resolver(a.resolver()), this.podId(a.podId()), this.podPosition(a.podPosition()), this.podOffset(a.podOffset()), this.podName(a.podName()), this.adLength(a.adLength()), this.adName(a.adName());
1473                    } else this.adId(""), this.sid(""), this.resolver(""), this.podId(""), this.podPosition(""), this.podOffset(0), this.podName(""), this.adLength(0), this.adName("");
1474                }var d = b.model.Dao;a.extend(c, d), b.model.AdDao = c;
1475            }(a.ADB.core, b), function (a, b) {
1476                "use strict";
1477                function c() {
1478                    if (c.__super__.constructor.call(this, "sc"), this.reportSuiteId = this._createAccessor("_reportSuiteId", "rsid", null), this.trackingServer = this._createAccessor("_trackingServer", "tracking_server", null), this.ssl = this._createAccessor("_ssl", "ssl", e.HINT_SHORT), arguments.length && arguments[0] instanceof c) {
1479                        var a = arguments[0];this.reportSuiteId(a.reportSuiteId()), this.trackingServer(a.trackingServer()), this.ssl(a.ssl());
1480                    } else this.reportSuiteId(""), this.trackingServer(""), this.ssl(0);
1481                }var d = b.model.Dao,
1482                    e = b.model.DaoField;a.extend(c, d), b.model.AdobeAnalyticsDao = c;
1483            }(a.ADB.core, b), function (a, b) {
1484                "use strict";
1485                function c() {
1486                    if (c.__super__.constructor.call(this, "stream"), this.id = this._createAccessor("_id", "chapter_id", null), this.sid = this._createAccessor("_sid", "chapter_sid", null), this.name = this._createAccessor("_name", "chapter_name", null), this.position = this._createAccessor("_position", "chapter_pos", null), this.length = this._createAccessor("_length", "chapter_length", null), this.offset = this._createAccessor("_offset", "chapter_offset", null), arguments.length && arguments[0] instanceof c) {
1487                        var a = arguments[0];this.id(a.id()), this.sid(a.sid()), this.name(a.name()), this.position(a.position()), this.length(a.length()), this.offset(a.offset());
1488                    } else this.id(""), this.sid(""), this.name(""), this.position(0), this.length(0), this.offset(0);
1489                }var d = b.model.Dao;a.extend(c, d), b.model.ChapterDao = c;
1490            }(a.ADB.core, b), function (a, b) {
1491                "use strict";
1492                function c() {
1493                    if (c.__super__.constructor.call(this, "asset"), this.type = this._createAccessor("_type", "type", null), this.videoId = this._createAccessor("_videoId", "video_id", null), this.publisher = this._createAccessor("_publisher", "publisher", null), this.adData = this._createAccessor("_adData", "ad_data", null), this.chapterData = this._createAccessor("_chapterData", "chapter_data", null), this.length = this._createAccessor("_length", "length", null), this.name = this._createAccessor("_name", "name", null), arguments.length && arguments[0] instanceof c) {
1494                        var a = arguments[0];this.type(a.type()), this.name(a.name()), this.videoId(a.videoId()), this.publisher(a.publisher()), this.length(a.length());var b = a.adData() ? new e(a.adData()) : null;this.adData(b);var d = a.chapterData() ? new f(a.chapterData()) : null;this.chapterData(d);
1495                    } else this.type(""), this.name(""), this.videoId(""), this.publisher(""), this.length(0), this.adData(null), this.chapterData(null);
1496                }var d = b.model.Dao,
1497                    e = b.model.AdDao,
1498                    f = b.model.ChapterDao;a.extend(c, d), c.TYPE_AD = "ad", c.TYPE_MAIN_CONTENT = "main", b.model.AssetDao = c;
1499            }(a.ADB.core, b), function (a, b) {
1500                "use strict";
1501                function c() {
1502                    if (c.__super__.constructor.call(this, "event"), this.type = this._createAccessor("_type", "type", null), this.duration = this._createAccessor("_duration", "duration", null), this.playhead = this._createAccessor("_playhead", "playhead", null), this.id = this._createAccessor("_id", "id", null), this.source = this._createAccessor("_source", "source", null), this.ts = this._createAccessor("_ts", "ts", null), this.prevTs = this._createAccessor("_prevTs", "prev_ts", null), arguments.length && arguments[0] instanceof c) {
1503                        var a = arguments[0];this.type(a.type()), this.duration(a.duration()), this.playhead(a.playhead()), this.id(a.id()), this.source(a.source()), this.ts(a.ts()), this.prevTs(a.prevTs());
1504                    } else this.type(""), this.duration(0), this.playhead(0), this.id(""), this.source(""), this.ts(0), this.prevTs(-1);
1505                }var d = b.model.Dao;a.extend(c, d), c.EVENT_TYPE_AA_START = "aa_start", c.EVENT_TYPE_AA_AD_START = "aa_ad_start", c.EVENT_TYPE_START = "start", c.EVENT_TYPE_RESUME = "resume", c.EVENT_TYPE_CHAPTER_START = "chapter_start", c.EVENT_TYPE_CHAPTER_COMPLETE = "chapter_complete", c.EVENT_TYPE_CHAPTER_SKIP = "chapter_skip", c.EVENT_TYPE_PLAY = "play", c.EVENT_TYPE_PAUSE = "pause", c.EVENT_TYPE_STALL = "stall", c.EVENT_TYPE_BUFFER = "buffer", c.EVENT_TYPE_BITRATE_CHANGE = "bitrate_change", c.EVENT_TYPE_ERROR = "error", c.EVENT_TYPE_COMPLETE = "complete", c.EVENT_TYPE_SKIP = "skip", c.EVENT_TYPE_END = "end", b.model.EventDao = c;
1506            }(a.ADB.core, b), function (a, b) {
1507                "use strict";
1508                function c() {
1509                    if (c.__super__.constructor.call(this, "stream"), this.bitrate = this._createAccessor("_bitrate", "bitrate", null), this.fps = this._createAccessor("_fps", "fps", null), this.droppedFrames = this._createAccessor("_droppedFrames", "dropped_frames", null), this.startupTime = this._createAccessor("_startup_time", "startup_time", null), arguments.length && arguments[0] instanceof c) {
1510                        var a = arguments[0];this.bitrate(a.bitrate()), this.fps(a.fps()), this.droppedFrames(a.droppedFrames()), this.startupTime(a.startupTime()), this.isStartupTimeOverridden = a.isStartupTimeOverridden;
1511                    } else this.bitrate(0), this.fps(0), this.droppedFrames(0), this.startupTime(0), this.isStartupTimeOverridden = !1;
1512                }var d = b.model.Dao;a.extend(c, d), b.model.QoSDao = c;
1513            }(a.ADB.core, b), function (a, b) {
1514                "use strict";
1515                function c() {
1516                    if (c.__super__.constructor.call(this, "sp"), this.ovp = this._createAccessor("_ovp", "ovp", null), this.sdk = this._createAccessor("_sdk", "sdk", null), this.channel = this._createAccessor("_channel", "channel", null), this.playerName = this._createAccessor("_playerName", "player_name", null), this.libVersion = this._createAccessor("_libVersion", "hb_version", null), this.apiLevel = this._createAccessor("_apiLevel", "hb_api_lvl", null), arguments.length && arguments[0] instanceof c) {
1517                        var a = arguments[0];this.ovp(a.ovp()), this.sdk(a.sdk()), this.channel(a.channel()), this.playerName(a.playerName()), this.libVersion(a.libVersion()), this.apiLevel(a.apiLevel());
1518                    } else this.ovp(e), this.sdk(e), this.channel(e), this.playerName(""), this.libVersion(""), this.apiLevel(0);
1519                }var d = b.model.Dao;a.extend(c, d);var e = "unknown";b.model.ServiceProviderDao = c;
1520            }(a.ADB.core, b), function (a, b) {
1521                "use strict";
1522                function c() {
1523                    if (c.__super__.constructor.call(this, "event"), this.sessionId = this._createAccessor("_sessionId", "sid", null), arguments.length && arguments[0] instanceof c) {
1524                        var a = arguments[0];this.sessionId(a.sessionId());
1525                    } else this.sessionId(null);
1526                }var d = b.model.Dao;a.extend(c, d), b.model.SessionDao = c;
1527            }(a.ADB.core, b), function (a, b) {
1528                "use strict";
1529                function c() {
1530                    if (c.__super__.constructor.call(this, "stream"), this.type = this._createAccessor("_type", "type", null), arguments.length && arguments[0] instanceof c) {
1531                        var a = arguments[0];this.type(a.type());
1532                    } else this.type(null);
1533                }var d = b.model.Dao;a.extend(c, d), b.model.StreamDao = c;
1534            }(a.ADB.core, b), function (a, b) {
1535                "use strict";
1536                function c() {
1537                    if (c.__super__.constructor.call(this, "user"), this.analyticsVisitorId = this._createAccessor("_analyticsVisitorId", "aid", null), this.marketingCloudVisitorId = this._createAccessor("_marketingCloudVisitorId", "mid", null), this.visitorId = this._createAccessor("_visitorId", "id", null), arguments.length && arguments[0] instanceof c) {
1538                        var a = arguments[0];this.analyticsVisitorId(a.analyticsVisitorId()), this.marketingCloudVisitorId(a.marketingCloudVisitorId()), this.visitorId(a.visitorId());
1539                    } else this.analyticsVisitorId(null), this.marketingCloudVisitorId(null), this.visitorId(null);
1540                }var d = b.model.Dao;a.extend(c, d), b.model.UserDao = c;
1541            }(a.ADB.core, b), function (a, b) {
1542                "use strict";
1543                function c() {
1544                    if (c.__super__.constructor.call(this, "aam"), this.audienceManagerBlob = this._createAccessor("_audienceManagerBlob", "blob", null), this.audienceManagerLocationHint = this._createAccessor("_audienceManagerLocationHint", "loc_hint", null), arguments.length && arguments[0] instanceof c) {
1545                        var a = arguments[0];this.audienceManagerBlob(a.audienceManagerBlob()), this.audienceManagerLocationHint(a.audienceManagerLocationHint());
1546                    } else this.audienceManagerBlob(null), this.audienceManagerLocationHint(null);
1547                }var d = b.model.Dao;a.extend(c, d), b.model.AudienceManagerDao = c;
1548            }(a.ADB.core, b), function (a, b) {
1549                "use strict";
1550                function c(a, b, c, i, j) {
1551                    this.eventData = new e(), this.eventData.type(b), this.eventData.duration(0), this.eventData.ts(new Date().getTime()), this.eventData.playhead(c), this.assetData = new f(a._assetData), this.streamData = new g(a._streamData), this.qosData = new h(a._qosData), this.cuserData = d.clone(a._cuserData), this.meta = i, this.callback = j, this.filterReport = !0;
1552                }var d = a.ObjectUtils,
1553                    e = b.model.EventDao,
1554                    f = b.model.AssetDao,
1555                    g = b.model.StreamDao,
1556                    h = b.model.QoSDao;b.model.TrackItem = c;
1557            }(a.ADB.va.utils, b), function (a, b) {
1558                "use strict";
1559                function c(a, b, c, i, j, k) {
1560                    this.adobeAnalyticsData = a, this.userData = b, this.aamData = c, this.serviceProviderData = i, this.sessionData = j, this.eventData = new e(k.eventData), this.assetData = new f(k.assetData), this.streamData = new g(k.streamData), this.qosData = new h(k.qosData), this.cuserData = d.clone(k.cuserData), this.meta = d.clone(k.meta), this.callback = k.callback, this.filterReport = k.filterReport;
1561                }var d = a.ObjectUtils,
1562                    e = b.model.EventDao,
1563                    f = b.model.AssetDao,
1564                    g = b.model.StreamDao,
1565                    h = b.model.QoSDao;b.model.CUserDao;b.model.Report = c;
1566            }(a.ADB.va.utils, b), function (a) {
1567                "use strict";
1568                function b() {}b.prototype.serializeReport = function (a) {}, b.prototype.serializeDao = function (a) {}, b.prototype.serializeMap = function (a) {}, b.prototype.serializeNumber = function (a, b, c, d) {}, b.prototype.serializeString = function (a, b, c, d) {}, a.model.ISerializer = b;
1569            }(b), function (a, b) {
1570                "use strict";
1571                function c(a) {
1572                    if (!a) throw new Error("Reference to the logger object cannot be NULL");this._logger = a;
1573                }var d = b.model.Dao,
1574                    e = b.model.DaoField,
1575                    f = b.model.ISerializer;a.extend(c, f), c.prototype.serializeReport = function (a) {
1576                    var b = [];return b.push(this.serializeDao(a.adobeAnalyticsData)), b.push(this.serializeDao(a.userData)), b.push(this.serializeDao(a.aamData)), b.push(this.serializeMap(a.cuserData, "cuser")), b.push(this.serializeDao(a.serviceProviderData)), b.push(this.serializeDao(a.sessionData)), b.push(this.serializeDao(a.eventData)), b.push(this.serializeDao(a.assetData)), b.push(this.serializeDao(a.streamData)), b.push(this.serializeDao(a.qosData)), b.push(this.serializeMap(a.meta, "meta")), { serializedOutput: b.f
1576ilter(function (a) {
1577                            return !!a;
1578                        }).join("&"), callback: a.callback };
1579                }, c.prototype.serializeDao = function (a) {
1580                    return this._processDao(a).filter(function (a) {
1581                        return !!a;
1582                    }).join("&");
1583                }, c.prototype.serializeMap = function (a, b) {
1584                    var c = [],
1585                        d = b || "meta";for (var e in a) {
1586                        a.hasOwnProperty(e) && a[e] && c.push("s:" + d + ":" + e + "=" + window.encodeURIComponent(a[e]));
1587                    }return c.join("&");
1588                }, c.prototype.serializeNumber = function (a, b, c, d) {
1589                    var f = h;return null == b || isNaN(b) ? null : (d === e.HINT_SHORT && (f = i), f + ":" + c + ":" + a + "=" + Math.floor(b));
1590                }, c.prototype.serializeString = function (a, b, c, d) {
1591                    return b ? j + ":" + c + ":" + a + "=" + window.encodeURIComponent(b) : null;
1592                }, c.prototype._processDao = function (a) {
1593                    var b = [];for (var c in a.data) {
1594                        if (a.data.hasOwnProperty(c)) {
1595                            var e = a.data[c],
1596                                f = e.value,
1597                                h = e.hint,
1598                                i = null,
1599                                j = a.realm;if (null == f) continue;"number" == typeof f ? i = this.serializeNumber(c, f, j, h) : "string" == typeof f ? i = this.serializeString(c, f, j, h) : f instanceof d ? i = this.serializeDao(f) : this._logger.warn(g, "#_processDao() > Unable to serialize DAO. Field: " + c + ". Value: " + f + "."), i && b.push(i);
1600                        }
1601                    }return b;
1602                };var g = "ah::QuerystringSerializer",
1603                    h = "l",
1604                    i = "h",
1605                    j = "s";b.model.QuerystringSerializer = c;
1606            }(a.ADB.core, b), function (a, b) {
1607                "use strict";
1608                function c(a, b) {
1609                    if (!a) throw new Error("Reference to the data object cannot be NULL");if (this._data = a, !b) throw new Error("Reference to the logger object cannot be NULL");this._logger = b;
1610                }c.prototype.parse = function () {
1611                    var a, b, c, i, j;if (window.DOMParser) {
1612                        j = new window.DOMParser().parseFromString(this._data, "text/xml");
1613                    } else j = new window.ActiveXObject("Microsoft.XMLDOM"), j.async = !1, j.loadXML(this._data);var k;k = parseInt(j.getElementsByTagName("trackingInterval")[0].childNodes[0].nodeValue, 10), k && (a = k), k = parseInt(j.getElementsByTagName("setupCheckInterval")[0].childNodes[0].nodeValue, 10), k && (b = k), k = parseInt(j.getElementsByTagName("trackExternalErrors")[0].childNodes[0].nodeValue, 10), k && (c = 1 == k), j.getElementsByTagName("nielsenEnabled")[0] ? (k = parseInt(j.getElementsByTagName("nielsenEnabled")[0].childNodes[0].nodeValue, 10), i = 1 == k) : i = !0;var l = {};return l[e] = a, l[f] = b, l[g] = c, l[h] = i, this._logger.debug(d, "#parse() > Obtained configuration settings."), l;
1614                };var d = "ah::SettingsParser",
1615                    e = "reporting_interval",
1616                    f = "check_status_interval",
1617                    g = "track_external_errors",
1618                    h = "nielsen_enabled";b.network.SettingsParser = c;
1619            }(a.ADB.core, b), function (a, b) {
1620                "use strict";
1621                function c(a, b) {
1622                    if (this._trackingServer = null, this._checkStatusServer = null, this._publisher = null, this._isConfigured = !1, this._isDestroyed = !1, this._quietMode = !1, this._visitorOptedOut = !1, this._prevReportSent = null, !a) throw new Error("Reference to the channel object cannot be NULL");if (this._channel = a, !b) throw new Error("Reference to the logger object cannot be NULL");this._logger = b, this._serializer = new i(b), this._installEventListeners();
1623                }var d = a.Event,
1624                    e = a.URLRequestMethod,
1625                    f = a.URLRequest,
1626                    g = a.URLLoader,
1627                    h = b.network.SettingsParser,
1628                    i = b.model.QuerystringSerializer;c.prototype.destroy = function () {
1629                    this._isDestroyed || (this._isDestroyed = !0, this._logger.debug(j, "#destroy()"), this._uninstallEventListeners());
1630                }, c.prototype._onApiConfig = function (a) {
1631                    var b = a.data;this._logger.debug(j, "#_onApiConfig(sb_server=" + b[k] + ", check_status_server=" + b[l] + ", publisher=" + b[m] + ", quiet_mode=" + b[n] + ", ssl=" + b[o] + ")"), this._trackingServer = this._updateRequestProtocol(b[k], b[o]), this._checkStatusServer = this._updateRequestProtocol(b[l], b[o]), this._publisher = b[m], this._quietMode = b[n], this._isConfigured = !0;
1632                }, c.prototype._onUpdateVisitorOptOut = function (a) {
1633                    var b = a.data;this._logger.debug(j, "#_onUpdateVisitorOptOut(visitor_opt_out=" + b[q] + ")"), this._visitorOptedOut = b[q];
1634                }, c.prototype._onFilterReportAvailable = function (a) {
1635                    var b = a.data;if (!this._isConfigured) return void this._logger.warn(j, "#_onFilterReportAvailable() > Unable to send request: not configured.");var c = b[p],
1636                        h = this._serializer.serializeReport(c),
1637                        i = this._trackingServer + "/?" + h.serializedOutput;if (this._prevReportSent && this._prevReportSent.eventData && c.eventData && this._prevReportSent.eventData.playhead == c.eventData.playhead && this._prevReportSent.eventData.ts == c.eventData.ts && this._prevReportSent.eventData.prevTs == c.eventData.prevTs && this._prevReportSent.eventData.type == c.eventData.type) return void this._logger.debug(j, "#_onFilterReportAvailable() > Duplicate heartbeat report not sent for URL:\n" + i);this._prevReportSent = c;var k = new f(i, e.GET);this._logger.debug(j, "#_onFilterReportAvailable() > " + k.url);var l = this,
1638                        m = function m(a) {
1639                        o.close(), h.callback && h.callback.call(null);
1640                    },
1641                        n = function n(a) {
1642                        o.close(), l._logger.warn(j, "#_onFilterReportAvailable() > Failed to send heartbeat report."), h.callback && h.callback.call(null);
1643                    };if (!this._quietMode && !this._visitorOptedOut) {
1644                        var o = new g();o.addEventListener(d.SUCCESS, m, this), o.addEventListener(d.ERROR, n, this), o.load(k);
1645                    }
1646                }, c.prototype._onClockCheckStatusTick = function (a) {
1647                    function b(a) {
1648                        if (a.data) {
1649                            var b = new h(a.data.response, i._logger),
1650                                c = b.parse();c ? i._channel.trigger(new d(v, c)) : i._logger.warn(j, "#_onClockCheckStatusTick() > Failed to parse the config. settings.");
1651                        }n.close();
1652                    }function c(a) {
1653                        i._logger.warn(j, "#_onClockCheckStatusTick() > Failed to obtain the config. settings."), n.close();
1654                    }if (!this._isConfigured) return void this._logger.warn(j, "#_onClockCheckStatusTick() > Unable to send request: not configured.");if (!this._publisher) return void this._logger.warn(j, "#_onClockCheckStatusTick() > Publisher is NULL.");var i = this,
1655                        k = this._publisher.replace(/[^a-zA-Z0-9]+/, "-").toLocaleLowerCase(),
1656                        l = this._checkStatusServer + k + ".xml?r=" + new Date().getTime(),
1657                        m = new f(l, e.GET),
1658                        n = new g();n.addEventListener(d.SUCCESS, b, this), n.addEventListener(d.ERROR, c, this), this._logger.debug(j, "#_onClockCheckStatusTick() > Get new settings from: " + l), n.load(m);
1659                }, c.prototype._updateRequestProtocol = function (a, b) {
1660                    var c = a;return 0 === c.indexOf("http://") ? c = c.slice(7) : 0 === c.indexOf("https://") && (c = c.slice(8)), b ? "https://" + c : "http://" + c;
1661                }, c.prototype._installEventListeners = function () {
1662                    this._channel.on(r, this._onApiConfig, this), this._channel.on(s, this._onUpdateVisitorOptOut, this), this._channel.on(t, this._onFilterReportAvailable, this), this._channel.on(u, this._onClockCheckStatusTick, this);
1663                }, c.prototype._uninstallEventListeners = function () {
1664                    this._channel.off(null, null, this);
1665                };var j = "ah::Network",
1666                    k = "tracking_server",
1667                    l = "check_status_server",
1668                    m = "publisher",
1669                    n = "quiet_mode",
1670                    o = "ssl",
1671                    p = "report",
1672                    q = "visitor_opt_out",
1673                    r = "api:config",
1674                    s = "api:update_visitor_optout",
1675                    t = "filter:data_available",
1676                    u = "clock:check_status.tick",
1677                    v = "net:check_status_complete";b.network.Network = c;
1678            }(a.ADB.core, b), function (a, b) {
1679                "use strict";
1680                function c(a, b) {
1681                    if (!a) throw new Error("Reference to the channel object cannot be NULL");if (this._channel = a, !b) throw new Error("Reference to the logger object cannot be NULL");this._logger = b, this._isDestroyed = !1, this._isBufferingInProgress = !1, this._reportBuffer = {}, this._tsHistory = {}, this._workQueue = new i(), this._installEventListeners();
1682                }function d(a) {
1683                    var b = [];return a && a.forEach(function (a) {
1684                        a.eventData.type() == k.EVENT_TYPE_PAUSE || a.eventData.type() == k.EVENT_TYPE_STALL ? (!a.filterReport || a.eventData.duration() > u) && b.push(a) : b.push(a);
1685                    }), b;
1686                }function e(a) {
1687                    var b = -1,
1688                        c = -1,
1689                        d = [];return a.forEach(function (a) {
1690                        a.eventData.type() == k.EVENT_TYPE_START ? a.assetData.type() == l.TYPE_MAIN_CONTENT ? -1 == b ? b = d.push(a) - 1 : (a.eventData.prevTs(-1), d[b] = a) : -1 == c ? c = d.push(a) - 1 : (a.eventData.prevTs(-1), d[c] = a) : d.push(a);
1691                    }), d;
1692                }function f(a) {
1693                    var b = [];return a.forEach(function (c) {
1694                        if (c.eventData.type() == k.EVENT_TYPE_PLAY) {
1695                            if (c.eventData.duration() > t) b.push(c);else if (0 == c.eventData.duration() && c.assetData.type() == l.TYPE_MAIN_CONTENT) {
1696                                var d = g(a);d.indexOf(c) == d.length - 1 && b.push(c);
1697                            }
1698                        } else b.push(c);
1699                    }), b;
1700                }
1700function g(a) {
1701                    var b = [];return a.forEach(function (a) {
1702                        a.eventData.type() != k.EVENT_TYPE_PLAY && a.eventData.type() != k.EVENT_TYPE_BUFFER && a.eventData.type() != k.EVENT_TYPE_START || b.push(a);
1703                    }), b;
1704                }var h = a.radio.Command,
1705                    i = a.radio.CommandQueue,
1706                    j = a.Event,
1707                    k = b.model.EventDao,
1708                    l = b.model.AssetDao;c.prototype.destroy = function () {
1709                    this._isDestroyed || (this._isDestroyed = !0, this._logger.debug(w, "#destroy()"), this._uninstallEventListeners(), this.clear());
1710                }, c.prototype.clear = function () {
1711                    this._logger.debug(w, "#clear()"), this._workQueue.cancelAllCommands(), this._reportBuffer = {}, this._tsHistory = {}, this._isBufferingInProgress = !1;
1712                }, c.prototype.flush = function () {
1713                    this._workQueue.addCommand(new h(this._flushBufferReport, this));
1714                }, c.prototype._bufferReport = function (a) {
1715                    if (!this._isDestroyed) {
1716                        var b = a[q];if (b) {
1717                            var c = b.sessionData.sessionId();this._reportBuffer[c] = this._reportBuffer[c] || [], this._reportBuffer[c].push(b);
1718                        }if (!this._isBufferingInProgress) {
1719                            this._isBufferingInProgress = !0;var d = {};d[p] = !0, d[r] = 1, this._channel.command(s, d);
1720                        }
1721                    }
1722                }, c.prototype._flushBufferReport = function () {
1723                    function a(a) {
1724                        if (a) for (var c = 0; c < a.length; c++) {
1725                            var d = a[c],
1726                                e = d.sessionData.sessionId();b._tsHistory[e] = b._tsHistory[e] || {};var f = d.eventData.type() + "." + (d.assetData.type() == l.TYPE_AD ? d.assetData.adData().adId() : d.assetData.videoId());b._tsHistory[e].hasOwnProperty(f) && d.eventData.prevTs(b._tsHistory[e][f]), b._tsHistory[e][f] = d.eventData.ts();
1727                        }
1728                    }if (!this._isDestroyed) {
1729                        var b = this;for (var c in this._reportBuffer) {
1730                            if (this._reportBuffer.hasOwnProperty(c)) {
1731                                var g = f(e(d(this._reportBuffer[c])));a(g);for (var h = 0; h < g.length; h++) {
1732                                    var i = g[h],
1733                                        k = {};k[q] = i, this._channel.trigger(new j(n, k));
1734                                }
1735                            }
1736                        }this._reportBuffer = {};var m = this._channel.request(v),
1737                            o = this._tsHistory[m] || {};this._tsHistory = {}, this._tsHistory[m] = o, this._isBufferingInProgress = !1;
1738                    }
1739                }, c.prototype._onContextReportAvailable = function (a) {
1740                    var b = a.data;this._workQueue.addCommand(new h(this._bufferReport, this, [b]));
1741                }, c.prototype._onClockFlushFilterTick = function (a) {
1742                    this.flush();
1743                }, c.prototype._installEventListeners = function () {
1744                    this._channel.on(m, this._onContextReportAvailable, this), this._channel.on(o, this._onClockFlushFilterTick, this);
1745                }, c.prototype._uninstallEventListeners = function () {
1746                    this._channel.off(null, null, this);
1747                };var m = "context:report_available",
1748                    n = "filter:data_available",
1749                    o = "clock:flush_filter.tick",
1750                    p = "reset",
1751                    q = "report",
1752                    r = "repeat_count",
1753                    s = "clock:flush_filter.resume",
1754                    t = 250,
1755                    u = 250,
1756                    v = "session_id",
1757                    w = "ah::ReportFilter";b.filter.ReportFilter = c;
1758            }(a.ADB.core, b), function (a, b) {
1759                "use strict";
1760                function c(a, b) {
1761                    this._onFail = { fn: a, ctx: b };
1762                }var d = a.ErrorInfo;c.prototype.validateFields = function (a, b) {
1763                    if (!a) return this._fail("Data cannot be null");if (b) for (var c = 0; c < b.length; c++) {
1764                        var d = b[c];switch (d) {case "videoId":
1765                                if (!a.hasOwnProperty("videoId")) return this._fail("The ID for the main video must be specified.");if ("string" != typeof a.videoId) return this._fail("The ID for the main video must be a String.");if ("" === a.videoId) return this._fail("The ID for the main video cannot be an empty string.");break;case "streamType":
1766                                if (!a.hasOwnProperty("streamType")) return this._fail("The stream type for the main video must be specified.");if ("string" != typeof a.streamType) return this._fail("The stream type for the main video must be a String.");if ("" === a.streamType) return this._fail("The stream type for the main video cannot be an empty string.");break;
1766case "videoLength":
1767                                if (!a.hasOwnProperty("videoLength")) return this._fail("The length of the main video must be specified.");if ("number" != typeof a.videoLength) return this._fail("The length of the main video must be a Number.");if (isNaN(a.videoLength)) return this._fail("The length of the main video cannot be NaN.");break;case "playhead":
1768                                if (!a.hasOwnProperty("playhead")) return this._fail("The playhead for the main video must be specified.");if ("number" != typeof a.playhead) return this._fail("The playhead for the main video must be a Number.");if (isNaN(a.playhead)) return this._fail("The playhead for the main video cannot be NaN.");break;case "playerName":
1769                                if (!a.hasOwnProperty("playerName")) return this._fail("The player name for the main video must be specified.");if ("string" != typeof a.playerName) return this._fail("The player name for the main video must be a String.");if ("" === a.playerName) return this._fail("The player name for the main video cannot be an empty string.");break;case "rsid":
1770                                if (!a.hasOwnProperty("rsid")) return this._fail("account (rsid) is required and has to be set in the AppMeasurement instance.");if ("string" != typeof a.rsid) return this._fail("account (rsid) of the AppMeasurement instance must be a String.");if ("" === a.rsid) return this._fail("account (rsid) of the AppMeasurement instance  cannot be an empty string.");break;case "trackingServer":
1771                                if (!a.hasOwnProperty("trackingServer")) return this._fail("trackingServer is required and has to be set in the AppMeasurement instance.");if ("string" != typeof a.trackingServer) return this._fail("trackingServer of the AppMeasurement instance must be a String.");if ("" === a.trackingServer) return this._fail("trackingServer of the AppMeasurement instance cannot be an empty string.");break;case "podPlayerName":
1772                                if (!a.hasOwnProperty("podPlayerName")) return this._fail("The player name for the ad-break must be specified.");if ("string" != typeof a.podPlayerName) return this._fail("The player name for the ad-break must be a String.");if ("" === a.podPlayerName) return this._fail("The player name for the ad-break cannot be an empty string.");break;case "podPosition":
1773                                if (!a.hasOwnProperty("podPosition")) return this._fail("Position (index) of the ad-break must be specified.");if ("number" != typeof a.podPosition) return this._fail("Position (index) of the ad-break must be a Number.");if (isNaN(a.podPosition)) return this._fail("Position (index) of the ad-break cannot be NaN.");break;case "adId":
1774                                if (!a.hasOwnProperty("adId")) return this._fail("The ad ID must be specified.");if ("string" != typeof a.adId) return this._fail("The ad ID must be a String.");if ("" === a.adId) return this._fail("The ad ID cannot be an empty string.");break;case "adPosition":
1775                                if (!a.hasOwnProperty("adPosition")) return this._fail("Position (index) of the ad must be specified.");if ("number" != typeof a.adPosition) return this._fail("Position (index) of the ad must be a Number.");if (isNaN(a.adPosition)) return this._fail("Position (index) of the ad cannot be NaN.");break;case "chapterPosition":
1776                                if (!a.hasOwnProperty("chapterPosition")) return this._fail("Position (index) of the chapter must be specified.");if ("number" != typeof a.chapterPosition) return this._fail("Position (index) of the chapter must be a Number.");if (isNaN(a.chapterPosition)) return this._fail("Position (index) of the chapter cannot be NaN.");break;case "chapterOffset":
1777                                if (!a.hasOwnProperty("chapterOffset")) return this._fail("Chapter start-time (offset) must be specified.");if ("number" != typeof a.chapterOffset) return this._fail("Chapter start-time (offset) must be a Number.");if (isNaN(a.chapterOffset)) return this._fail("Chapter start-time (offset) cannot be NaN.");break;case "chapterLength":
1778                                if (!a.hasOwnProperty("chapterLength")) return this._fail("The length of the chapter must be specified.");if ("number" != typeof a.chapterLength) return this._fail("The length of the chapter must be a Number.");if (isNaN(a.chapterLength)) return this._fail("The length of the chapter cannot be NaN.");break;default:
1779                                return this._fail("Unable to validate unknown parameter: " + d);}
1780                    }return !0;
1781                }, c.prototype._fail = function (a) {
1782                    var b = new d("Invalid input data", a);return this._onFail.fn && this._onFail.fn.call(this._onFail.ctx, b), !1;
1783                }, b.context.InputDataValidator = c;
1784            }(a.ADB.va, b), function (a, b) {
1785                "use strict";
1786                function c(a, b) {
1787                    if (!b) throw new Error("Reference to the logger object cannot be NULL");if (this._logger = b, !a) throw new Error("Reference to the context object cannot be NULL");this._context = a;
1788                }var d = b.model.Report;c.prototype.createReportForItem = function (a) {
1789                    return this._logger.debug(e, "Creating report for item: " + a.eventData.type()), new d(this._context._adobeAnalyticsData, this._context._userData, this._context._aamData, this._context._serviceProviderData, this._context._sessionData, a);
1790                };var e = "ah::ReportFactory";b.context.ReportFactory = c;
1791            }(a.ADB.core, b), function (a, b, c, d) {
1792                "use strict";
1793                function e(a, b) {
1794                    if (!a) throw new Error("Reference to the channel object cannot be NULL");if (this._channel = a, !b) throw new Error("Reference to the logger object cannot be NULL");this._logger = b, this._lastInBandItem = null, this._autoComputedStartupTime = 0, this._reportingInterval = ma, this._assetData = null, this._streamData = null, this._qosData = null, this._sessionData = null, this._cuserData = null, this._adobeAnalyticsData = new j(), this._serviceProviderData = new k(), this._userData = new l(), this._aamData = new m(), this._isTrackingSessionActive = !1, this._isVideoComplete = !1, this._isDestroyed = !1, this._doNotOverrideEventDuration = !1, this._reportFactory = new u(this, this._logger), this._inputDataValidator = new v(function (a) {
1795                        this._logger.error(w, a.getMessage() + " | " + a.getDetails()), this._channel.trigger(new h(y, a));
1796                    }, this), this._trackExternalErrors = !0, this._installEventListeners();
1797                }var f = c.md5,
1798                    g = c.ObjectUtils,
1799                    h = a.Event,
1800                    i = d.model.SessionDao,
1801                    j = d.model.AdobeAnalyticsDao,
1802                    k = d.model.ServiceProviderDao,
1803                    l = d.model.UserDao,
1804                    m = d.model.AudienceManagerDao,
1805                    n = d.model.EventDao,
1806                    o = d.model.AssetDao,
1807                    p = d.model.StreamDao,
1808                    q = d.model.QoSDao,
1809                    r = d.model.AdDao,
1810                    s = d.model.ChapterDao,
1811                    t = d.model.TrackItem,
1812                    u = d.context.ReportFactory,
1813                    v = d.context.InputDataValidator;e.prototype.destroy = function () {
1814                    this._isDestroyed || (this._isDestroyed = !0, this._logger.debug(w, "#destroy()"), this._uninstallEventListeners());
1815                }, e.prototype._onApiAnalyticsStart = function (a) {
1816                    this._logger.debug(w, "#_onApiAnalyticsStart()");var b = a.data;if (this._checkCall("_onApiAnalyticsStart") && this._inputDataValidator.validateFields(b, ["playhead"])) {
1817                        this._userData.visitorId(b.vid), this._userData.analyticsVisitorId(b.aid), this._userData.marketingCloudVisitorId(b.mid), this._aamData.audienceManagerBlob(b.blob), this._aamData.audienceManagerLocationHint(b.loc_hint), b.customerIDs && (this._cuserData = b.customerIDs), this._updateQoSInfo(b);var c = new t(this, n.EVENT_TYPE_AA_START, b.playhead, null, b._eventData[E]);c.assetData.adData(null), c.assetData.type(o.TYPE_MAIN_CONTENT), this._cuserData = null, this._sendHit(c);
1818                    }
1819                }, e.prototype._onApiAnalyticsAdStart = function (a) {
1820                    this._logger.debug(w, "#_onApiAnalyticsAdStart()");var b = a.data;if (this._checkCall("_onApiAnalyticsAdStart") && this._inputDataValidator.validateFields(b, ["playhead"])) {
1821                        this._updateQoSInfo(b);var c = new t(this, n.EVENT_TYPE_AA_AD_START, b.playhead, null, b._eventData[E]);this._sendHit(c);
1822                    }
1823                }, e.prototype._onApiVideoLoad = function (a) {
1824                    var b = a.data;this._logger.debug(w, "#_onApiVideoLoad(rsid=" + b.rsid + ", aa_trackingServer=" + b.trackingServer + ")"), this._resetInternalState(), this._inputDataValidator.validateFields(b, ["rsid", "trackingServer"]) && (this._sessionData.sessionId(this._generateSessionId()), this._isTrackingSessionActive = !0);
1825                }, e.prototype._onApiVideoUnload = function (a) {
1826                    if (this._logger.debug(w, "#_onApiVideoUnload()"), !this._isTrackingSessionActive) return void this._logger.debug(w, "#_onApiVideoUnload() > No active tracking session.");this._isTrackingSessionActive = !1;
1827                }, e.prototype._onApiVideoStart = function (a) {
1828                    var b = a.data;if (this._logger.debug(w, "#_onApiVideoStart(id=" + b.videoId + ", name=" + b.videoName + ", length=" + b.videoLe
1828ngth + ", type=" + b.streamType + ", playerName=" + b.playerName + ")"), this._checkCall("_onApiVideoStart") && this._inputDataValidator.validateFields(b, ["videoId", "streamType", "videoLength", "playhead", "playerName"])) {
1829                        this._lastInBandItem = null, this._adobeAnalyticsData.reportSuiteId(b.rsid), this._adobeAnalyticsData.trackingServer(b.trackingServer), this._adobeAnalyticsData.ssl(Number(b.useSsl)), this._serviceProviderData.ovp(b.ovp), this._serviceProviderData.sdk(b.sdk), this._serviceProviderData.channel(b.channel), this._serviceProviderData.libVersion(b.version), this._serviceProviderData.apiLevel(b.apiLvl), this._serviceProviderData.playerName(b.playerName), this._assetData.adData(null), this._assetData.chapterData(null), this._assetData.videoId(b.videoId), this._assetData.length(b.videoLength), this._assetData.type(o.TYPE_MAIN_CONTENT), this._assetData.publisher(b.publisher), this._assetData.name(b.videoName), this._streamData.type(b.streamType), this._updateQoSInfo(b);var c = b.metaNielsen ? g.merge(b.metaVideo, b.metaNielsen) : b.metaVideo,
1830                            d = new t(this, n.EVENT_TYPE_START, b.playhead, c, b._eventData[E]);this._sendHit(d);
1831                    }
1832                }, e.prototype._onApiVideoResume = function (a) {
1833                    var b = a.data;if (this._logger.debug(w, "#_onApiVideoResume(id=" + b.videoId + ", name=" + b.videoName + ", length=" + b.videoLength + ", type=" + b.streamType + ", playerName=" + b.playerName + ")"), this._checkCall("_onApiVideoResume") && this._inputDataValidator.validateFields(b, ["videoId", "streamType", "videoLength", "playhead", "playerName"])) {
1834                        this._assetData.videoId(b.videoId), this._assetData.length(b.videoLength), this._assetData.type(o.TYPE_MAIN_CONTENT), this._assetData.name(b.videoName), this._streamData.type(b.streamType);var c = new t(this, n.EVENT_TYPE_RESUME, b.playhead, null, b._eventData[E]);this._sendHit(c);
1835                    }
1836                }, e.prototype._onApiVideoSessionEnd = function (a) {
1837                    this._logger.debug(w, "#_onApiVideoSessionEnd()");var b = a.data;if (this._checkCall("_onApiVideoSessionEnd") && this._inputDataValidator.validateFields(b, ["playhead"])) {
1838                        var c = new t(this, n.EVENT_TYPE_END, b.playhead, null, b._eventData[E]);c.assetData.adData(null), c.assetData.type(o.TYPE_MAIN_CONTENT), this._sendHit(c);
1839                    }
1840                }, e.prototype._onApiVideoComplete = function (a) {
1841                    this._logger.debug(w, "#_onApiVideoComplete()");var b = a.data;if (this._checkCall("_onApiVideoComplete")) {
1842                        var c = new t(this, n.EVENT_TYPE_COMPLETE, this._assetData.length(), null, b._eventData[E]);this._sendHit(c), this._isVideoComplete = !0;
1843                    }
1844                }, e.prototype._onApiVideoSkip = function (a) {
1845                    this._logger.debug(w, "#_onApiVideoSkip()");var b = a.data;if (this._checkCall("_onApiVideoSkip")) {
1846                        var c = new t(this, n.EVENT_TYPE_SKIP, b.playhead, null, b._eventData[E]);this._sendHit(c), this._isVideoComplete = !0;
1847                    }
1848                }, e.prototype._onApiPlay = function (a) {
1849                    this._logger.debug(w, "#_onApiPlay()");var b = a.data;if (this._checkCall("_onApiPlay") && this._inputDataValidator.validateFields(b, ["playhead"])) {
1850                        this._updateQoSInfo(b);var c = new t(this, n.EVENT_TYPE_PLAY, b.playhead, null, b._eventData[E]);this._sendHit(c);
1851                    }
1852                }, e.prototype._onApiPause = function (a) {
1853                    this._logger.debug(w, "#_onApiPause()");var b = a.data;if (this._checkCall("_onApiPause") && this._inputDataValidator.validateFields(b, ["playhead"])) {
1854                        this._updateQoSInfo(b);var c = b.playheadStalled ? n.EVENT_TYPE_STALL : n.EVENT_TYPE_PAUSE,
1855                            d = new t(this, c, b.playhead, null, b._eventData[E]);b._eventData.hasOwnProperty(F) && (d.filterReport = b._eventData[F]), this._sendHit(d);
1856                    }
1857                }, e.prototype._onApiBufferStart = function (a) {
1858                    this._logger.debug(w, "#_onApiBufferStart()");var b = a.data;if (this._checkCall("_onApiBufferStart") && this._inputDataValidator.validateFields(b, ["playhead"])) {
1859                        this._updateQoSInfo(b);var c = new t(this, n.EVENT_TYPE_BUFFER, b.playhead, null, b._eventData[E]);this._sendHit(c);
1860                    }
1861                }, e.prototype._onApiSeekStart = function (a) {
1862                    this._logger.debug(w, "#_onApiSeekStart()");
1863                }, e.prototype._onApiSeekComplete = function (a) {
1864                    this._logger.debug(w, "#_onApiSeekComplete()");var b = a.data;if (this._checkCall("_onApiSeekComplete") && this._inputDataValidator.validateFields(b, ["playhead"])) {
1865                        var c = this._assetData.adData(),
1866                            d = this._assetData.chapterData();if (b.isInAd) {
1867                            if (!this._inputDataValidator.validateFields(b, ["adId", "adPosition", "podPosition", "podPlayerName"])) return;var e = f(this._assetData.videoId()) + "_" + b.podPosition;if (!c || c.podId() != e || parseInt(c.podPosition(), 10) != b.adPosition) {
1868                                var g = new r();g.adId(b.adId), g.adName(b.adName), g.adLength(b.adLength), g.podId(e), g.resolver(b.podPlayerName), g.podPosition(b.adPosition + ""), g.podName(b.podName), g.podOffset(b.podSecond), g.sid(this._generateSessionId()), this._assetData.adData(g);
1869                            }this._assetData.type(o.TYPE_AD);
1870                        } else this._assetData.adData(null), this._assetData.type(o.TYPE_MAIN_CONTENT);if (b.isInChapter) {
1871                            if (!this._inputDataValidator.validateFields(b, ["chapterPosition", "chapterLength", "chapterOffset"])) return;if (!d || b.chapterPosition != d.position()) {
1872                                var h = new s();h.id(f(this._assetData.videoId()) + "_" + b.chapterPosition), h.name(b.chapterName), h.length(b.chapterLength), h.position(b.chapterPosition), h.offset(b.chapterOffset), h.sid(this._generateSessionId()), this._assetData.chapterData(h);
1873                            }
1874                        } else this._assetData.chapterData(null);
1875                    }
1876                }, e.prototype._onApiAdStart = function (a) {
1877                    var b = a.data;if (this._logger.debug(w, "#_onApiAdStart(id=" + b.adId + ", player_name=" + b.podPlayerName + ", parent_name=" + this._assetData.videoId() + ", pod_pos=" + b.adPosition + ")"), this._checkCall("_onApiAdStart") && this._inputDataValidator.validateFields(b, ["playhead", "podPosition", "podPlayerName", "adId", "adPosition"])) {
1878                        var c = new r();c.adId(b.adId), c.adName(b.adName), c.adLength(b.adLength), c.resolver(b.podPlayerName), c.podId(f(this._assetData.videoId()) + "_" + b.podPosition), c.podPosition(b.adPosition + ""), c.podName(b.podName), c.podOffset(b.podSecond), c.sid(this._generateSessionId()), this._assetData.adData(c), this._assetData.type(o.TYPE_AD), this._updateQoSInfo(b);var d = g.merge(b.metaVideo, b.metaAd);d = b.metaNielsen ? g.merge(d, b.metaNielsen) : d;var e = new t(this, n.EVENT_TYPE_START, b.playhead, d, b._eventData[E]);this._sendHit(e);if (!!b.adGranularTracking) {
1879                            var h = b.adTrackingInterval ? b.adTrackingInterval : this._reportingInterval;this._updateReportingInterval(h, !0);
1880                        }
1881                    }
1882                }, e.prototype._onApiAdComplete = function (a) {
1883                    this._logger.debug(w, "#_onApiAdComplete()");var b = a.data;if (this._checkCall("_onApiAdComplete") && this._inputDataValidator.validateFields(b, ["playhead"])) {
1884                        if (this._assetData.type() != o.TYPE_AD) return void this._logger.warn(w, "#_onApiAdComplete() > Ignoring the ad complete event, because we are no longer in an ad.");this._updateQoSInfo(b);var c = new t(this, n.EVENT_TYPE_COMPLETE, b.playhead, null, b._eventData[E]);this._sendHit(c), this._assetData.adData(null), this._assetData.type(o.TYPE_MAIN_CONTENT), this._updateReportingInterval(this._reportingInterval, !1);
1885                    }
1886                }, e.prototype._onApiAdSkip = function (a) {
1887                    this._logger.debug(w, "#_onApiAdSkip()");var b = a.data;if (this._checkCall("_onApiAdSkip") && this._inputDataValidator.validateFields(b, ["playhead"])) {
1888                        if (this._assetData.type() != o.TYPE_AD) return void this._logger.warn(w, "#_onApiAdSkip() >
1888 Ignoring the ad skip event, because we are no longer in an ad.");this._updateQoSInfo(b);var c = new t(this, n.EVENT_TYPE_SKIP, b.playhead, null, b._eventData[E]);this._sendHit(c), this._assetData.adData(null), this._assetData.type(o.TYPE_MAIN_CONTENT), this._updateReportingInterval(this._reportingInterval, !1);
1889                    }
1890                }, e.prototype._onApiChapterStart = function (a) {
1891                    var b = a.data;if (this._logger.debug(w, "#_onApiChapterStart(name=" + b.chapterName + ", length=" + b.chapterLength + ", position=" + b.chapterPosition + ", chapter_offset=" + b.chapterOffset + ")"), this._checkCall("_onApiChapterStart") && this._inputDataValidator.validateFields(b, ["playhead", "chapterPosition", "chapterOffset", "chapterLength"])) {
1892                        var c = new s();c.id(f(this._assetData.videoId()) + "_" + b.chapterPosition), c.name(b.chapterName), c.length(b.chapterLength), c.position(b.chapterPosition), c.offset(b.chapterOffset), c.sid(this._generateSessionId()), this._assetData.chapterData(c), this._updateQoSInfo(b);var d = g.merge(b.metaVideo, b.metaChapter),
1893                            e = new t(this, n.EVENT_TYPE_CHAPTER_START, b.playhead, d, b._eventData[E]);e.assetData.adData(null), e.assetData.type(o.TYPE_MAIN_CONTENT), this._sendHit(e);
1894                    }
1895                }, e.prototype._onApiChapterComplete = function (a) {
1896                    this._logger.debug(w, "#_onApiChapterComplete()");var b = a.data;if (this._checkCall("_onApiChapterComplete") && this._inputDataValidator.validateFields(b, ["playhead"])) {
1897                        if (!this._assetData.chapterData()) return void this._logger.warn(w, "#_onApiChapterComplete() > Ignoring the chapter complete event, because we are no longer in a chapter.");this._updateQoSInfo(b);var c = new t(this, n.EVENT_TYPE_CHAPTER_COMPLETE, b.playhead, null, b._eventData[E]);c.assetData.adData(null), c.assetData.type(o.TYPE_MAIN_CONTENT), this._sendHit(c), this._assetData.chapterData(null);
1898                    }
1899                }, e.prototype._onApiChapterSkip = function (a) {
1900                    this._logger.debug(w, "#_onApiChapterSkip()");var b = a.data;if (this._checkCall("_onApiChapterSkip") && this._inputDataValidator.validateFields(b, ["playhead"])) {
1901                        if (!this._assetData.chapterData()) return void this._logger.warn(w, "#_onApiChapterSkip() > Ignoring the chapter skip event, because we are no longer in a chapter.");this._updateQoSInfo(b);var c = new t(this, n.EVENT_TYPE_CHAPTER_SKIP, b.playhead, null, b._eventData[E]);c.assetData.adData(null), c.assetData.type(o.TYPE_MAIN_CONTENT), this._sendHit(c), this._assetData.chapterData(null);
1902                    }
1903                }, e.prototype._onApiBitrateChange = function (a) {
1904                    this._logger.debug(w, "#_onApiBitrateChange()");var b = a.data;if (this._checkCall("_onApiBitrateChange") && this._inputDataValidator.validateFields(b, ["playhead"])) {
1905                        this._updateQoSInfo(b);var c = new t(this, n.EVENT_TYPE_BITRATE_CHANGE, b.playhead, null, b._eventData[E]);this._sendHit(c);
1906                    }
1907                }, e.prototype._onApiTrackError = function (a) {
1908                    var b = a.data;if (this._logger.debug(w, "#_onApiTrackError(source=" + b._eventData.source + ", err_id=" + b._eventData.error_id + ")"), !this._isTrackingSessionActive) return void this._logger.warn(w, "#_onApiTrackError() > No active tracking session.");if (this._trackExternalErrors || b._eventData.source === x) {
1909                        this._updateQoSInfo(b);var c = new t(this, n.EVENT_TYPE_ERROR, 0, null, b._eventData[E]);c.eventData.id(b._eventData.error_id), c.eventData.source(b._eventData.source), this._sendHit(c);
1910                    }
1911                }, e.prototype._onApiTrackInternalError = function (a) {
1912                    var b = a.data;this._logger.debug(w, "#_onApiTrackInternalError(source=" + b.source + ", err_id=" + b.error_id + ")"), this._updateQoSInfo(b);var c = new t(this, n.EVENT_TYPE_ERROR, 0);c.eventData.id(b.error_id), c.eventData.source(b.source), this._sendHit(c);
1913                }, e.prototype._onApiQuantumEnd = function (a) {
1914                    this._logger.debug(w, "#_onApiQuantumEnd(interval=" + this._channel.request(B) + ")");var b = a.data;if (this._checkCall("_onApiQuantumEnd") && this._inputDataValidator.validateFields(b, ["playhead"])) {
1915                        var c = this._lastInBandItem;if (c) {
1916                            this._updateQoSInfo(b);var d = new t(this, c.eventData.type(), b.playhead, c.meta, c.callback);d.filterReport = c.filterReport, this._sendHit(d, !0);
1917                        }
1918                    }
1919                }, e.prototype._onNetworkCheckStatusComplete = function (a) {
1920                    var b = a.data;this._trackExternalErrors = b[I], this._reportingInterval = b[J], this._reportingInterval || (this._reportingInterval = ma), this._logger.debug(w, "#_onNetworkCheckStatusComplete(track_ext_err=" + this._trackExternalErrors + ")");
1921                }, e.prototype._onResetSessionId = function (a) {
1922                    var b = this._generateSessionId();this._sessionData = new i(), this._sessionData.sessionId(b), this._logger.debug(w, "#_resetSessionId(new sessionId=" + b + ")");
1923                }, e.prototype._installEventListeners = function () {
1924                    this._channel.on(L, this._onApiAnalyticsStart, this), this._channel.on(M, this._onApiAnalyticsAdStart, this), this._channel.on(N, this._onApiVideoLoad, this), this._channel.on(O, this._onApiVideoUnload, this), this._channel.on(P, this._onApiVideoStart, this), this._channel.on(Q, this._onApiVideoComplete, this), this._channel.on(R, this._onApiVideoSkip, this), this._channel.on(S, this._onApiVideoResume, this), this._channel.on(T, this._onApiVideoSessionEnd, this), this._channel.on(U, this._onApiAdStart, this), this._channel.on(V, this._onApiAdComplete, this), this._channel.on(W, this._onApiAdSkip, this), this._channel.on(X, this._onApiPlay, this), this._channel.on(Y, this._onApiPause, this), this._channel.on(Z, this._onApiBufferStart, this), this._channel.on($, this._onApiSeekStart, this), this._channel.on(_, this._onApiSeekComplete, this), this._channel.on(aa, this._onApiChapterStart, this), this._channel.on(ba, this._onApiChapterComplete, this), this._channel.on(ca, this._onApiChapterSkip, this), this._channel.on(fa, this._onApiBitrateChange, this), this._channel.on(da, this._onApiTrackError, this), this._channel.on(ea, this._onApiTrackInternalError, this), this._channel.on(ga, this._onApiQuantumEnd, this), this._channel.on(ia, this._onNetworkCheckStatusComplete, this), this._channel.on(D, this._onResetSessionId, this), this._channel.reply(C, function () {
1925                        return this._sessionData && this._sessionData.sessionId() ? this._sessionData.sessionId() : null;
1926                    }, this);
1927                }, e.prototype._uninstallEventListeners = function () {
1928                    this._channel.off(null, null, this);
1929                }, e.prototype._resetInternalState = function () {
1930                    this._logger.debug(w, "#_resetInternalState()"), this._isTrackingSessionActive = !1, this._isVideoComplete = !1, this._autoComputedStartupTime = 0, this._lastInBandItem = null, this._streamData = new p(), this._qosData = new q(), this._sessionData = new i(), this._assetData = new o(), this._cuserData = null;
1931                }, e.prototype._generateSessionId = function () {
1932                    return "" + new Date().getTime() + Math.floor(1e9 * Math.random());
1933                }, e.prototype._updateQoSInfo = function (a) {
1934                    this._qosData.bitrate(a.bitrate || 0), this._qosData.fps(a.fps || 0), this._qosData.droppedFrames(a.droppedFrames || 0), null == a.startupTime || isNaN(a.startupTime) ? (this._qosData.startupTime(this._autoComputedStartupTime), this._qosData.isStartupTimeOverridden = !1) : (this._qosData.startupTime(a.startupTime), this._qosData.isStartupTimeOverridden = !0);
1935                }, e.prototype._checkCall = function (a) {
1936                    return this._isTrackingSessionActive ? !this._isVideoComplete || "_onApiVideoSessionEnd" === a || (this._logger.warn(w, "#" + a + "() > The video content already completed."), !1) : (this._logger.warn(w, "#" + a + "() > No active tracking session."), !1);
1937                }, e.prototype._updateReportingInterval = function (a, b) {
1938                    var c = {};
1938c[K] = !!b, c[J] = a, this._channel.trigger(new h(ja, c));
1939                }, e.prototype._updateLastInBandItem = function (a) {
1940                    var b = this._lastInBandItem,
1941                        c = new Date().getTime(),
1942                        d = b.assetData.type() === o.TYPE_AD || a.assetData.type() === o.TYPE_AD,
1943                        e = 1e3 * Math.abs(a.eventData.playhead() - b.eventData.playhead()),
1944                        f = Math.abs(c - b.eventData.ts()),
1945                        g = Math.abs(e - f),
1946                        h = f;h > ka ? (this._logger.warn(w, " Resetting duration in lastInBandItem[" + b.assetData.type() + ":" + b.eventData.type() + "] call to 0 as calculated duration (" + h + ")exceeds 10mins"), h = 0) : b.eventData.type() == n.EVENT_TYPE_PLAY && !d && !this._doNotOverrideEventDuration && g > la && (h = Math.min(e, f), this._logger.warn(w, " Resetting duration in lastInBandItem[" + b.assetData.type() + ":" + b.eventData.type() + "] call to " + h + " as calculated error delta (" + g + ")exceeds 2sec")), this._doNotOverrideEventDuration = !1, b.eventData.duration(h), b.eventData.ts(c), b.eventData.playhead(a.eventData.playhead()), b.qosData.startupTime(a.qosData.startupTime()), b.qosData.isStartupTimeOverridden = a.qosData.isStartupTimeOverridden;
1947                }, e.prototype._createAndSendReport = function (a) {
1948                    var b = this._reportFactory.createReportForItem(a);b.qosData.isStartupTimeOverridden || b.qosData.startupTime(this._autoComputedStartupTime);var c = {};if (c[G] = b, this._channel.trigger(new h(ha, c)), b.eventData.type() == n.EVENT_TYPE_START || b.eventData.type() == n.EVENT_TYPE_PLAY || b.eventData.type() == n.EVENT_TYPE_PAUSE || b.eventData.type() == n.EVENT_TYPE_STALL || b.eventData.type() == n.EVENT_TYPE_BUFFER) {
1949                        var d = {};d[H] = !0, this._channel.command(z, d);
1950                    }
1951                }, e.prototype._sendHit = function (a, b) {
1952                    switch (a.eventData.type()) {case n.EVENT_TYPE_START:case n.EVENT_TYPE_PLAY:case n.EVENT_TYPE_PAUSE:case n.EVENT_TYPE_STALL:case n.EVENT_TYPE_BUFFER:
1953                            this._lastInBandItem ? (this._updateLastInBandItem(a), this._lastInBandItem.eventData.type() == n.EVENT_TYPE_START && this._lastInBandItem.assetData.type() == o.TYPE_MAIN_CONTENT && (this._autoComputedStartupTime += this._lastInBandItem.eventData.duration()), this._createAndSendReport(this._lastInBandItem), b && this._lastInBandItem.eventData.type() == a.eventData.type() || this._createAndSendReport(a)) : this._createAndSendReport(a), this._lastInBandItem = a;break;case n.EVENT_TYPE_COMPLETE:case n.EVENT_TYPE_SKIP:
1954                            if (this._lastInBandItem && (this._updateLastInBandItem(a), this._createAndSendReport(this._lastInBandItem)), a.eventData.type() !== n.EVENT_TYPE_SKIP && this._createAndSendReport(a), a.assetData.type() == o.TYPE_MAIN_CONTENT) {
1955                                this._lastInBandItem = null;var c = {};c[H] = !0, this._channel.command(A, c);
1956                            } else a.assetData.type() == o.TYPE_AD && (this._lastInBandItem.assetData.adData(null), this._lastInBandItem.assetData.type(o.TYPE_MAIN_CONTENT), this._doNotOverrideEventDuration = !0);break;case n.EVENT_TYPE_CHAPTER_START:case n.EVENT_TYPE_CHAPTER_COMPLETE:case n.EVENT_TYPE_CHAPTER_SKIP:
1957                            this._lastInBandItem && (this._updateLastInBandItem(a), this._createAndSendReport(this._lastInBandItem)), a.eventData.type() !== n.EVENT_TYPE_CHAPTER_SKIP && this._createAndSendReport(a), this._lastInBandItem && (this._lastInBandItem.assetData.chapterData(a.eventData.type() == n.EVENT_TYPE_CHAPTER_START ? new s(a.assetData.chapterData()) : null), this._lastInBandItem.eventData.duration(0), this._createAndSendReport(this._lastInBandItem));break;default:
1958                            this._createAndSendReport(a);}
1959                };var w = "ah::Context",
1960                    x = "sourceErrorSDK",
1961                    y = "error",
1962                    z = "clock:reporting.resume",
1963                    A = "clock:reporting.pause",
1964                    B = "reporting_interval",
1965                    C = "session_id",
1966                    D = "reset_session_id",
1967                    E = "callback",
1968                    F = "filter_report",
1969                    G = "report",
1970                    H = "reset",
1971                    I = "track_external_errors",
1972                    J = "reporting_interval",
1973                    K = "do_not_override_interval",
1974                    L = "api:aa_start",
1975                    M = "api:aa_ad_start",
1976                    N = "api:video_load",
1977                    O = "api:video_unload",
1978                    P = "api:video_start",
1979                    Q = "api:video_complete",
1980                    R = "api:video_skip",
1981                    S = "api:video_resume",
1982                    T = "api:video_session_end",
1983                    U = "api:ad_start",
1984                    V = "api:ad_complete",
1985                    W = "api:ad_skip",
1986                    X = "api:play",
1987                    Y = "api:pause",
1988                    Z = "api:buffer_start",
1989                    $ = "api:seek_start",
1990                    _ = "api:seek_complete",
1991                    aa = "api:chapter_start",
1992                    ba = "api:chapter_complete",
1993                    ca = "api:chapter_skip",
1994                    da = "api:track_error",
1995                    ea = "api:track_internal_error",
1996                    fa = "api:bitrate_change",
1997                    ga = "api:quantum_end",
1998                    ha = "context:report_available",
1999                    ia = "net:check_status_complete",
2000                    ja = "reporting:update_interval",
2001                    ka = 6e5,
2002                    la = 2e3,
2003                    ma = 10;d.context.Context = e;
2004            }(a.ADB.core, a.ADB.va, a.ADB.va.utils, b), function (a) {
2005                "use strict";
2006                function b(a, b) {
2007                    this.trackingServer = a, this.publisher = b, this.ssl = !1, this.ovp = c, this.sdk = c, this.quietMode = !1, this.debugLogging = !1, this.__isPrimetime = !1, this.__psdkVersion = null;
2008                }var c = "unknown";a.AdobeHeartbeatPluginConfig = b;
2009            }(b), function (a) {
2010                "use strict";
2011                function b() {}b.prototype.onError = function (a) {}, a.AdobeHeartbeatPluginDelegate = b;
2012            }(b), function (a, b, c) {
2013                "use strict";
2014                function d(a) {
2015                    d.__super__.constructor.call(this, q), this._radio = new i(this._logger), this._channel = this._radio.channel(y), this._delegate = a, this._context = new l(this._channel, this._logger), this._filter = new m(this._channel, this._logger), this._network = new n(this._channel, this._logger), this._setupDataResolver();
2016                }var e = a.Event,
2017                    f = a.Trigger,
2018                    g = a.plugin.BasePlugin,
2019                    h = a.plugin.ParamMapping,
2020                    i = a.radio.Radio,
2021                    j = b.ErrorInfo,
2022                    k = b.Version,
2023                    l = c.context.Context,
2024                    m = c.filter.ReportFilter,
2025                    n = c.network.Network,
2026                    o = c.clock.Clock,
2027                    p = c.AdobeHeartbeatPluginConfig;a.extend(d, g), d.prototype.configure = function (a) {
2028                    if (!a) throw new Error("Reference to the configuration data cannot be NULL.");if (!(a instanceof p)) throw new Error("Expected config data to be instance of AdobeHeartbeatPluginConfig.");this._config = a, this._config.debugLogging ? this._logger.enable() : this._logger.disable(), this._logger.debug(this._logTag, "#configure({trackingServer=" + this._config.trackingServer + ", publisher=" + this._config.publisher + ", quietMode=" + this._config.quietMode + ", ssl=" + this._config.ssl + "})");var b = this._config.trackingServer + "/settings/",
2029                        c = {};c[ga] = this._config.trackingServer, c[ha] = b, c[ia] = this._config.publisher, c[ja] = this._config.quietMode, c[ka] = this._config.ssl, this._channel.trigger(new e(oa, c)), this._isConfigured = !0;
2030                }, d.prototype.bootstrap = function (a) {
2031                    d.__super__.bootstrap.call(this, a), this._channel.on(z, this._onError, this), this._clock = new o(this._pluginManager, this._channel, this._logger), this._channel.command(Na), this._channel.trigger(new e(Ua)), this._registerCommands(), this._registerBehaviours();
2032                }, d.prototype._teardown = function () {
2033                    this._logger.debug(this._logTag, "#_teardown()"), this._radio.shutdown(), this._context.destroy(), this._clock.destroy(), this._filter.destroy(), this._network.destroy();
2034                }, d.prototype._canProcess = function () {
2035                    return this._isConfigured ? this._errorInfo ? (this._logger.error(this._logTag, "_canProcess() > Plugin in ERROR state."), !1) : d.__super__._canProcess.call(this) : (this._logger.error(this._logTag, "_canProcess() > Plugin not configured."), !1);
2036                }, d.prototype._cmdAnalyticsError = function (a) {
2037                    this._errorInfo || (this._errorInfo = new j("Internal error", "AdobeAnalyticsPlugin is in ERROR state."), this._trigger(z, this._errorInfo), this._delegate && this._delegate.onError(this._errorInfo));
2038                }, d.prototype._cmdAnalyticsStart = function (a) {
2039                    if (this._canProcess()) {
2040                        var b = {};b[la] = a.visitorOptedOut, this._channel.trigger(new e(pa, b)), this._channel.trigger(new e(ma, a));
2041                    }
2042                }, d.prototype._cmdAnalyticsAdStart = function (a) {
2043                    this._canProcess() && this._channel.trigger(new e(na, a));
2044                }, d.prototype._cmdVideoLoad = function (a) {
2045                    this._errorInfo = null, this._canProcess() && (this._isTrackingSessionActive && this._channel.trigger(new e(ra, a)), this._isTrackingSessionActive = !1, this._isPaused = !0, this._isSeeking = !1, this._isBuffering = !1, this._isVideoIdle = !1, this._filter.clear(), this._channel.trigger(new e(qa, a)), this._isTrackingSessionActive = !0);
2046                }, d.prototype._cmdVideoUnload = function (a) {
2047                    this._errorInfo = null, this._canProcess() && (this._channel.trigger(new e(ra, a)), this._filter.flush(), this._runReportingTimer(!1), this._runFlushFilterTimer(!1), this._runIdleTimer(!1), this._isTrackingSessionActive = !1);
2048                }, d.prototype._cmdVideoSessionEnd = function (a) {
2049                    this._canProcess() && this._channel.trigger(new e(wa, a));
2050                }, d.prototype._cmdVideoStart = function (a) {
2051                    this._canProcess() && (this._channel.trigger(new e(sa, a)), this._filter.flush());
2052                }, d.prototype._cmdVideoComplete = function (a) {
2053                    this._canProcess() && this._channel.trigger(new e(ta, a));
2054                }, d.prototype._cmdVideoSkip = function (a) {
2055                    this._canProcess() && this._channel.trigger(new e(ua, a));
2056                }, d.prototype._cmdVideoResume = function (a) {
2057                    this._canProcess() && this._channel.trigger(new e(va, a));
2058                }, d.prototype._cmdPlay = function (a) {
2059                    this._canProcess() && (this._isPaused = !1, this._resumePlaybackIfPossible(a));
2060                }, d.prototype._cmdPause = function (a) {
2061                    this._canProcess() && (this._channel.trigger(new e(Ba, a)), this._isPaused = !0, this._runIdleTimer(!0));
2062                }, d.prototype._cmdAdStart = function (a) {
2063                    this._canProcess() && (this._channel.trigger(new e(xa, a)), this._resumePlaybackIfPossible(a));
2064                }, d.prototype._cmdAdComplete = function (a) {
2065                    this._canProcess() && (this._channel.trigger(new e(ya, a)), a.isInAdBreak || this._resumePlaybackIfPossible(a));
2066                }, d.prototype._cmdAdSkip = function (a) {
2067                    this._canProcess() && (this._channel.trigger(new e(za, a)), a.isInAdBreak || this._resumePlaybackIfPossible(a));
2068                }, d.prototype._cmdBufferStart = function (a) {
2069                    this._canProcess() && (this._channel.trigger(new e(Ca, a)), this._isBuffering = !0, this._runIdleTimer(!0));
2070                }, d.prototype._cmdBufferComplete = function (a) {
2071                    this._canProcess() && (this._isBuffering = !1, this._isPaused ? this._channel.trigger(new e(Ba, a)) : this._resumePlaybackIfPossible(a));
2072                }, d.prototype._cmdSeekStart = function (a) {
2073                    this._canProcess() && (this._channel.trigger(new e(Da, a)), this._channel.trigger(new e(Ba, a)), this._isSeeking = !0, this._runIdleTimer(!0));
2074                }, d.prototype._cmdSeekComplete = function (a) {
2075                    this._canProcess() && (this._channel.trigger(new e(Ea, a)), this._isSeeking = !1, this._resumePlaybackIfPossible(a));
2076                }, d.prototype._cmdChapterStart = function (a) {
2077                    this._canProcess() && this._channel.trigger(new e(Fa, a));
2078                }, d.prototype._cmdChapterComplete = function (a) {
2079                    this._canProcess() && this._channel.trigger(new e(Ga, a));
2080                }, d.prototype._cmdChapterSkip = function (a) {
2081                    this._canProcess() && this._channel.trigger(new e(Ha, a));
2082                }, d.prototype._cmdBitrateChange = function (a) {
2083                    this._canProcess() && this._channel.trigger(new e(Ka, a));
2084                }, d.prototype._cmdTrackError = function (a) {
2085                    this._canProcess() && this._channel.trigger(new e(Ia, a));
2086                }, d.prototype._cmdClockReportingTick = function (a) {
2087                    this._canProcess() && this._channel.trigger(new e(La, a));
2088                }, d.prototype._cmdIdleTick = function (a) {
2089                    this._canProcess() && (this._isVideoIdle = !0, this._trigger($), this._channel.trigger(new e(wa, a)), this._filter.flush(), this._runReportingTimer(!1), this._runFlushFilterTimer(!1), this._runIdleTimer(!1), this._trigger(Y));
2090                }, d.prototype._onError = function (a) {
2091                    this._errorInfo = a.data;var b = {};b[ea] = Ma, b[fa] = this._errorInfo.getMessage() + "|" + this._errorInfo.getDetails(), this._channel.trigger(new e(Ja, b)), this._runReportingTimer(!1), this._trigger(z, this._errorInfo), this._delegate && this._delegate.onError(this._errorInfo);
2092                }, d.prototype._runIdleTimer = function (a) {
2093                    var b = {};b[da] = !0, a ? this._channel.command(Sa, b) : this._channel.command(Ta, b);
2094                }, d.prototype._runFlushFilterTimer = function (a) {
2095                    var b = {};b[da] = !0, a ? this._channel.command(Qa, b) : this._channel.command(Ra, b);
2096                }, d.prototype._runReportingTimer = function (a) {
2097                    var b = {};b[da] = !0, a ? this._channel.command(Oa, b) : this._channel.command(Pa, b);
2098                }, d.prototype._registerCommands = function () {
2099                    this._pluginManager.comply(this, "handleAnalyticsError", this._cmdAnalyticsError), this._pluginManager.comply(this, "handleAnalyticsStart", this._cmdAnalyticsStart), this._pluginManager.comply(this, "handleAnalyticsAdStart", this._cmdAnalyticsAdStart), this._pluginManager.comply(this, "handleVideoLoad", this._cmdVideoLoad), this._pluginManager.comply(this, "handleVideoUnload", this._cmdVideoUnload), this._pluginManager.comply(this, "handleVideoSessionEnd", this._cmdVideoSessionEnd), this._pluginManager.comply(this, "handleVideoStart", this._cmdVideoStart), this._pluginManager.comply(this, "handleVideoComplete", this._cmdVideoComplete), this._pluginManager.comply(this, "handleVideoSkip", this._cmdVideoSkip), this._pluginManager.comply(this, "handleVideoResume", this._cmdVideoResume), this._pluginManager.comply(this, "handlePlay", this._cmdPlay), this._pluginManager.comply(this, "handlePause", this._cmdPause), this._pluginManager.comply(this, "handleAdStart", this._cmdAdStart), this._pluginManager.comply(this, "handleAdComplete", this._cmdAdComplete), this._pluginManager.comply(this, "handleAdSkip", this._cmdAdSkip), this._pluginManager.comply(this, "handleBufferStart", this._cmdBufferStart), this._pluginManager.comply(this, "handleBufferComplete", this._cmdBufferComplete), this._pluginManager.comply(this, "handleSeekStart", this._cmdSeekStart), this._pluginManager.comply(this, "handleSeekComplete", this._cmdSeekComplete), this._pluginManager.comply(this, "handleChapterStart", this._cmdChapterStart), this._pluginManager.comply(this, "handleChapterComplete", this._cmdChapterComplete), this._pluginManager.comply(this, "handleChapterSkip", this._cmdChapterSkip), this._pluginManager.comply(this, "handleBitrateChange", this._cmdBitrateChange), this._pluginManager.comply(this, "handleTrackError", this._cmdTrackError), this._pluginManager.comply(this, "handleClockReportingTick", this._cmdClockReportingTick), this._pluginManager.comply(this, "handleIdleTick", this._cmdIdleTick);
2100                }, d.prototype._registerBehaviours = function () {
2101                    this._pluginManager.registerBehaviour(new f(t, C), this, "handleVideoLoad", [new h(s, "rsid", "rsid"), new h(s, "tracking_server", "trackingServer")]), this._pluginManager.registerBehaviour(new f(t, D), this, "handleVideoUnload"), this._pluginManager.registerBehaviour(new f(t, E), this, "handleVideoSessionEnd", [new h(t, "video.playhead", "playhead")]), this._pluginManager.registerBehaviour(new f(t, F), this, "handleVideoStart", [new h(t, "video.id", "videoId"), new h(t, "video.name", "videoName"), new h(t, "video.length", "videoLe
2101ngth"), new h(t, "video.playhead", "playhead"), new h(t, "video.playerName", "playerName"), new h(t, "video.streamType", "streamType"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime"), new h(s, "rsid", "rsid"), new h(s, "tracking_server", "trackingServer"), new h(s, "channel", "channel"), new h(s, "meta.video.*", "metaVideo"), new h(s, "ssl", "useSsl"), new h(u, "meta", "metaNielsen"), new h(r, "publisher", "publisher"), new h(r, "sdk", "sdk"), new h(r, "ovp", "ovp"), new h(r, "version", "version"), new h(r, "api_level", "apiLvl")]), this._pluginManager.registerBehaviour(new f(t, H), this, "handleVideoComplete", [new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]), this._pluginManager.registerBehaviour(new f(t, I), this, "handleVideoSkip", [new h(t, "video.playhead", "playhead"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]), this._pluginManager.registerBehaviour(new f(t, G), this, "handleVideoResume", [new h(t, "video.id", "videoId"), new h(t, "video.name", "videoName"), new h(t, "video.length", "videoLength"), new h(t, "video.playhead", "playhead"), new h(t, "video.playerName", "playerName"), new h(t, "video.streamType", "streamType")]), this._pluginManager.registerBehaviour(new f(t, J), this, "handlePlay", [new h(t, "video.playhead", "playhead"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]), this._pluginManager.registerBehaviour(new f(t, K), this, "handlePause", [new h(t, "video.playhead", "playhead"), new h(t, "video.playheadStalled", "playheadStalled"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]), this._pluginManager.registerBehaviour(new f(t, M), this, "handleAdStart", [new h(t, "video.playhead", "playhead"), new h(t, "ad.id", "adId"), new h(t, "ad.name", "adName"), new h(t, "ad.length", "adLength"), new h(t, "ad.position", "adPosition"), new h(t, "ad.granularTracking", "adGranularTracking"), new h(t, "ad.trackingInterval", "adTrackingInterval"), new h(t, "pod.name", "podName"), new h(t, "pod.playerName", "podPlayerName"), new h(t, "pod.position", "podPosition"), new h(t, "pod.startTime", "podSecond"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime"), new h(s, "meta.video.*", "metaVideo"), new h(s, "meta.ad.*", "metaAd"), new h(u, "meta", "metaNielsen")]), this._pluginManager.registerBehaviour(new f(t, N), this, "handleAdComplete", [new h(t, "video.playhead", "playhead"), new h(t, "ad.isInAdBreak", "isInAdBreak"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]), this._pluginManager.registerBehaviour(new f(t, O), this, "handleAdSkip", [new h(t, "video.playhead", "playhead"), new h(t, "ad.isInAdBreak", "isInAdBreak"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]), this._pluginManager.registerBehaviour(new f(t, P), this, "handleBufferStart", [new h(t, "video.playhead", "playhead"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]), this._pluginManager.registerBehaviour(new f(t, Q), this, "handleBufferComplete", [new h(t, "video.playhead", "playhead"), new h(t, "video.playheadStalled", "playheadStalled"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]), this._pluginManager.registerBehaviour(new f(t, R), this, "handleSeekStart", [new h(t, "video.playhead", "playhead")]), this._pluginManager.registerBehaviour(new f(t, S), this, "handleSeekComplete", [new h(t, "video.playhead", "playhead"), new h(t, "ad.isInAd", "isInAd"), new h(t, "ad.id", "adId"), new h(t, "ad.position", "adPosition"), new h(t, "pod.playerName", "podPlayerName"), new h(t, "pod.position", "podPosition"), new h(t, "chapter.isInChapter", "isInChapter"), new h(t, "chapter.position", "chapterPosition"), new h(t, "chapter.name", "chapterName"), new h(t, "chapter.length", "chapterLength"), new h(t, "chapter.startTime", "chapterOffset"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]), this._pluginManager.registerBehaviour(new f(t, T), this, "handleChapterStart", [new h(t, "video.playhead", "playhead"), new h(t, "chapter.position", "chapterPosition"), new h(t, "chapter.name", "chapterName"), new h(t, "chapter.length", "chapterLength"), new h(t, "chapter.startTime", "chapterOffset"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime"), new h(s, "meta.video.*", "metaVideo"), new h(s, "meta.chapter.*", "metaChapter"), new h(u, "meta", "metaNielsen")]), this._pluginManager.registerBehaviour(new f(t, U), this, "handleChapterComplete", [new h(t, "video.playhead", "playhead"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]), this._pluginManager.registerBehaviour(new f(t, V), this, "handleChapterSkip", [new h(t, "video.playhead", "playhead"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]), this._pluginManager.registerBehaviour(new f(t, W), this, "handleBitrateChange", [new h(t, "video.playhead", "playhead"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]), this._pluginManager.registerBehaviour(new f(t, X), this, "handleTrackError"), this._pluginManager.registerBehaviour(new f(v, aa), this, "handleClockReportingTick", [new h(t, "video.playhead", "playhead"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]), this._pluginManager.registerBehaviour(new f(t, L), this, "handleClockReportingTick", [new h(t, "video.playhead", "playhead"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]), this._pluginManager.registerBehaviour(new f(v, ca), this, "handleIdleTick", [new h(t, "video.playhead", "playhead")]), this._pluginManager.registerBehaviour(new f(r, $), this, "handleClockReportingTick", [new h(t, "video.playhead", "playhead"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]), this._pluginManager.registerBehaviour(new f(s, z), this, "handleAnalyticsError"), this._pluginManager.registerBehaviour(new f(s, A), this, "handleAnalyticsStart", [new h(s, "vid", "vid"), new h(s, "aid", "aid"), new h(s, "mid", "mid"), new h(s, "customerIDs", "customerIDs"), new h(s, "visitorOptedOut", "visitorOptedOut"), new h(s, "blob", "blob"), new h(s, "loc_hint", "loc_hint"), new h(t, "video.playhead", "playhead"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]), this._pluginManager.registerBehaviour(new f(s, B), this, "handleAnalyticsAdStart", [new h(t, "video.playhead", "playhead"), new h(t, "qos.fps", "fps"), new h(t, "qos.droppedFrames", "droppedFrames"), new h(t, "qos.bitrate", "bitrate"), new h(t, "qos.startupTime", "startupTime")]);
2102                }, d.prototype._setupDataResolver = function () {
2103                    var a = {},
2104                        b = this;a.version = function () {
2105                        return k.getVersion();
2106                    }, a.api_level = function () {
2107                        return k.getApiLevel();
2108                    }, a.tracking_server = function () {
2109                        return b._config ? b._config.trackingServer : null;
2110                    }, a.publisher = function () {
2111                        return b._config ? b._config.publisher : null;
2112                    }, a.quiet_mode = function () {
2113                        return !!b._config && b._config.quietMode;
2114                    }, a.ovp = function () {
2115                        return b._config ? b._config.ovp : null;
2116                    }, a.sdk = function () {
2117                        return b._config ? b._config.sdk : null;
2118                    }, a.is_primetime = function () {
2119                        return !!b._config && b._config.__isPrimetime;
2120                    }, a.psdk_version = function () {
2121                        return b._config ? b._config.__psdkVersion : null;
2122                    }, a.session_id = function () {
2123                        return b._channel.request(w);
2124                    }, this._dataResolver = function (b) {
2125                        if (!b || 0 == b.length) return null;for (var c = null, d = 0; d < b.length; d++) {
2126                            var e = b[d];c = c || {}, c[e] = a.hasOwnProperty(e) ? a[e].call(this) : null;
2127                        }return c;
2128                    };
2129                }, d.prototype._resumePlaybackIfPossible = function (a) {
2130                    this._errorInfo || this._isPaused || this._isSeeking || this._isBuffering || (this._isVideoIdle && (this._isVideoIdle = !1, this._resumePlaybackFromIdle()), this._runIdleTimer(!1), this._channel.trigger(new e(Aa, a)));
2131                }, d.prototype._resumePlaybackFromIdle = function () {
2132                    this._trigger($), this._filter.clear(), this._channel.trigger(new e(x)), this._trigger(Z), this._runReportingTimer(!0), this._runFlushFilterTimer(!0);
2133                };var q = "adobe-heartbeat",
2134                    r = q,
2135                    s = "adobe-analytics",
2136                    t = "player",
2137                    u = "nielsen",
2138                    v = "service.clock",
2139                    w = "session_id",
2140                    x = "reset_session_id",
2141                    y = "heartbeat-channel",
2142                    z = "error",
2143                    A = "aa_start",
2144                    B = "sc_ad_start",
2145                    C = "video_load",
2146                    D = "video_unload",
2147                    E = "video_session_end",
2148                    F = "video_start",
2149                    G = "video_resume",
2150                    H = "video_complete",
2151                    I = "video_skip",
2152                    J = "play",
2153                    K = "pause",
2154                    L = "content_start",
2155                    M = "ad_start",
2156                    N = "ad_complete",
2157                    O = "ad_skip",
2158                    P = "buffer_start",
2159                    Q = "buffer_complete",
2160                    R = "seek_start",
2161                    S = "seek_complete",
2162                    T = "chapter_start",
2163                    U = "chapter_complete",
2164                    V = "chapter_skip",
2165                    W = "bitrate_change",
2166                    X = "track_error",
2167                    Y = "video_idle_start",
2168                    Z = "video_idle_resume",
2169                    $ = "quantum_close",
2170                    _ = "heartbeat.reporting",
2171                    aa = _ + ".tick",
2172                    ba = "heartbeat.idle",
2173                    ca = ba + ".tick",
2174                    da = "reset",
2175                    ea = "source",
2176                    fa = "error_id",
2177                    ga = "tracking_server",
2178                    ha = "check_status_server",
2179                    ia = "publisher",
2180                    ja = "quiet_mode",
2181                    ka = "ssl",
2182                    la = "visitor_opt_out",
2183                    ma = "api:aa_start",
2184                    na = "api:aa_ad_start",
2185                    oa = "api:config",
2186                    pa = "api:update_visitor_optout",
2187                    qa = "api:video_load",
2188                    ra = "api:video_unload",
2189                    sa = "api:video_start",
2190                    ta = "api:video_complete",
2191                    ua = "api:video_skip",
2192                    va = "api:video_resume",
2193                    wa = "api:video_session_end",
2194                    xa = "api:ad_start",
2195                    ya = "api:ad_complete",
2196                    za = "api:ad_skip",
2197                    Aa = "api:play",
2198                    Ba = "api:pause",
2199                    Ca = "api:buffer_start",
2200                    Da = "api:seek_start",
2201                    Ea = "api:seek_complete",
2202                    Fa = "api:chapter_start",
2203                    Ga = "api:chapter_complete",
2204                    Ha = "api:chapter_skip",
2205                    Ia = "api:track_error",
2206                    Ja = "api:track_internal_error",
2207                    Ka = "api:bitrate_change",
2208                    La = "api:quantum_end",
2209                    Ma = "sourceErrorHeartbeat",
2210                    Na = "clock:check_status.resume",
2211                    Oa = "clock:reporting.resume",
2212                    Pa = "clock:reporting.pause",
2213                    Qa = "clock:flush_filter.resume",
2214                    Ra = "clock:flush_filter.pause",
2215                    Sa = "clock:idle.resume",
2216                    Ta = "clock:idle.pause",
2217                    Ua = "clock:check_status.get_settings";c.AdobeHeartbeatPlugin = d;
2218            }(a.ADB.core, a.ADB.va, b), a.ADB.va.plugins.ah || (a.ADB.va.plugins.ah = b);
2219        }(this);
2220
2221        // AdobeAnalyticsPlugin
2222        !function (a) {
2223            if (void 0 === b) var b = {};!function (a, b) {
2224                "use strict";
2225                function c(a, b) {
2226                    this._onFail = { fn: a, ctx: b };
2227                }var d = a.ErrorInfo;c.prototype.validateFields = function (a, b) {
2228                    if (!a) return this._fail("Data cannot be null");if (b) for (var c = 0; c < b.length; c++) {
2229                        var d = b[c];switch (d) {case "videoId":
2230                                if (!a.hasOwnProperty("videoId")) return this._fail("The ID for the main video must be specified.");if ("string" != typeof a.videoId) return this._fail("The ID for the main video must be a String.");if ("" === a.videoId) return this._fail("The ID for the main video cannot be an empty string.");break;case "streamType":
2231                                if (!a.hasOwnProperty("streamType")) return this._fail("The stream type for the main video must be specified.");if ("string" != typeof a.streamType) return this._fail("The stream type for the main video must be a String.");if ("" === a.streamType) return this._fail("The stream type for the main video cannot be an empty string.");break;case "playerName":
2232                                if (!a.hasOwnProperty("playerName")) return this._fail("The player name for the main video must be specified.");if ("string" != typeof a.playerName) return this._fail("The player name for the main video must be a String.");if ("" === a.playerName) return this._fail("The player name for the main video cannot be an empty string.");break;case "videoLength":
2233                                if (!a.hasOwnProperty("videoLength")) return this._fail("The length of the main video must be specified.");if ("number" != typeof a.videoLength) return this._fail("The length of the main video must be a Number.");if (isNaN(a.videoLength)) return this._fail("The length of the main video cannot be NaN.");break;case "podPlayerName":
2234                                if (!a.hasOwnProperty("podPlayerName")) return this._fail("The player name for the ad-break must be specified.");if ("string" != typeof a.podPlayerName) return this._fail("The player name for the ad-break must be a String.");if ("" === a.podPlayerName) return this._fail("The player name for the ad-break cannot be an empty string.");break;case "podPosition":
2235                                if (!a.hasOwnProperty("podPosition")) return this._fail("Position (index) of the ad-break must be specified.");if ("number" != typeof a.podPosition) return this._fail("Position (index) of the ad-break must be a Number.");if (isNaN(a.podPosition)) return this._fail("Position (index) of the ad-break cannot be NaN.");break;case "adId":
2236                                if (!a.hasOwnProperty("adId")) return this._fail("The ad ID must be specified.");if ("string" != typeof a.adId) return this._fail("The ad ID must be a String.");if ("" === a.adId) return this._fail("The ad ID cannot be an empty string.");break;case "adPosition":
2237                                if (!a.hasOwnProperty("adPosition")) return this._fail("Position (index) of the ad must be specified.");if ("number" != typeof a.adPosition) return this._fail("Position (index) of the ad must be a Number.");if (isNaN(a.adPosition)) return this._fail("Position (index) of the ad cannot be NaN.");break;case "adLength":
2238                                if (!a.hasOwnProperty("adLength")) return this._fail("The length of the ad must be specified.");if ("number" != typeof a.adLength) return this._fail("The length of the ad must be a Number.");if (isNaN(a.adLength)) return this._fail("The length of the ad cannot be NaN.");break;default:
2239                                return this._fail("Unable to validate unknown parameter: " + d);}
2240                    }return !0;
2241                }, c.prototype._fail = function (a) {
2242                    var b = new d("Invalid input data", a);return this._onFail.fn && this._onFail.fn.call(this._onFail.ctx, b), !1;
2243                }, b.InputDataValidator = c;
2244            }(a.ADB.va, b), function (a) {
2245                "use strict";
2246                function b() {
2247                    this.channel = c, this.debugLogging = !1;
2248                }var c = "";a.AdobeAnalyticsPluginConfig = b;
2249            }(b), function (a) {
2250                "use strict";
2251                function b() {}b.prototype.onError = function (a) {}, a.AdobeAnalyticsPluginDelegate = b;
2252            }(b), function (a, b, c, d) {
2253                "use strict";
2254                function e(a, b) {
2255                    if (e.__super__.constructor.call(this, r), !a) throw new Error("The reference to the AppMeasurement object cannot be NULL.");if (a.unsupportedBrowser) throw new Error("AppMeasurement is not supported in current browser.");this._appMeasurement = a, this._delegate = b, this._videoMetadata = {}, this._adMetadata = {}, this._chapterMetadata = {}, this._errorInfo = null, this._workQueue = new k(!0, w), this._inputDataValidator = new q(function (a) {
2256                        this._errorInfo = a, this._logger.error(this._logTag, a.getMessage() + " | " + a.getDetails());var b = this;setTimeout(function () {
2257                            b._trigger(x, a), b._delegate && b._delegate.onError(b._errorInfo);
2258                        }, 0);
2259                    }, this), this._appMeasurement.isReadyToTrack(), this._setupDataResolver();
2260                }var f = a.Trigger,
2261                    g = a.plugin.BasePlugin,
2262                    h = a.plugin.ParamMapping,
2263                    i = a.radio.Channel,
2264                    j = a.radio.Command,
2265                    k = a.radio.CommandQueue,
2266                    l = b.ErrorInfo,
2267                    m = c.md5,
2268                    n = c.StringUtils,
2269                    o = c.ObjectUtils,
2270                    p = d.AdobeAnalyticsPluginConfig,
2271                    q = d.InputDataValidator;a.extend(e, g), e.prototype.configure = function (a) {
2272                    if (!a) throw new Error("Reference to the configuration data cannot be NULL.");if (!(a instanceof p)) throw new Error("Expected config data to be instance of AdobeAnalyticsPluginConfig.");this._config = a, this._config.debugLogging ? this._logger.enable() : this._logger.disable(), this._logger.debug(this._logTag, "#configure({trackingServer=" + this._config.debugLogging + ", 
2272channel=" + this._config.channel + ", ssl=" + this._appMeasurement.ssl + "})");
2273                }, e.prototype.bootstrap = function (a) {
2274                    e.__super__.bootstrap.call(this, a), this._registerCommands(), this._registerBehaviours();
2275                }, e.prototype.setup = function () {
2276                    this._appMeasurement.isReadyToTrack() ? this._onAppMeasurementReady() : this._appMeasurement.callbackWhenReadyToTrack(this, this._onAppMeasurementReady, []), e.__super__.setup.call(this);
2277                }, e.prototype.setVideoMetadata = function (a) {
2278                    this._videoMetadata = o.clone(a);
2279                }, e.prototype.setAdMetadata = function (a) {
2280                    this._adMetadata = o.clone(a);
2281                }, e.prototype.setChapterMetadata = function (a) {
2282                    this._chapterMetadata = o.clone(a);
2283                }, e.prototype._canProcess = function () {
2284                    return this._errorInfo ? (this._logger.error(this._logTag, "#_canProcess() > In ERROR state."), !1) : e.__super__._canProcess.call(this);
2285                }, e.prototype._cmdVideoLoad = function (a) {
2286                    this._errorInfo = null;
2287                }, e.prototype._cmdVideoStart = function (a) {
2288                    this._logger.debug(this._logTag, "#_cmdVideoStart()"), this._canProcess() && this._workQueue.addCommand(new j(this._executeOpen, this, [a]));
2289                }, e.prototype._cmdAdStart = function (a) {
2290                    this._logger.debug(this._logTag, "#_cmdAdStart()"), this._canProcess() && this._workQueue.addCommand(new j(this._executeOpenAd, this, [a]));
2291                }, e.prototype._cmdHeartbeatPluginError = function (a) {
2292                    this._errorInfo || (this._errorInfo = new l("Internal error", "HeartbeatPlugin is in ERROR state."), this._trigger(x, this._errorInfo), this._delegate && this._delegate.onError(this._errorInfo));
2293                }, e.prototype._track = function (a) {
2294                    if (!this._appMeasurement.visitorOptedOut) {
2295                        var b = this._appMeasurement.linkTrackVars;this._appMeasurement.linkTrackVars = "", this._appMeasurement.track(a), this._appMeasurement.linkTrackVars = b;
2296                    }
2297                }, e.prototype._executeOpen = function (a) {
2298                    if (this._logger.debug(this._logTag, "#_executeOpen(id=" + a.videoId + ", videoName=" + a.videoName + ", streamType=" + a.streamType + ", length=" + a.videoLength + ", playerName=" + a.playerName + ", channel=" + a.channel + ", isPrimetime=" + a.isPrimetime + ", sessionId=" + a.sessionId + ")"), this._canProcess() && this._inputDataValidator.validateFields(a, ["videoId", "streamType", "videoLength", "playerName"])) {
2299                        var b = {};for (var c in a.metaVideo) {
2300                            a.metaVideo.hasOwnProperty(c) && (b[c] = a.metaVideo[c]);
2301                        }if (a.metaNielsen) for (var c in a.metaNielsen) {
2302                            a.metaNielsen.hasOwnProperty(c) && (b[c] = a.metaNielsen[c]);
2303                        }b["a.contentType"] = a.streamType, b["a.media.name"] = a.videoId, b["a.media.friendlyName"] = a.videoName || "", b["a.media.length"] = Math.floor(a.videoLength) || "0.0", b["a.media.playerName"] = a.playerName, b["a.media.channel"] = a.channel || "", b["a.media.view"] = !0, b["a.media.vsid"] = a.sessionId;var d = {};d.contextData = b, d.pev3 = y, d.pe = a.isPrimetime ? B : A, this._track(d);var e = this;setTimeout(function () {
2304                            e._trigger(E, a);
2305                        }, 0);
2306                    }
2307                }, e.prototype._executeOpenAd = function (a) {
2308                    var b = m(a.videoId) + "_" + a.podPosition;if (this._logger.debug(this._logTag, "#_executeOpenAd(id=" + a.adId + ", streamType=" + a.streamType + ", length=" + a.adLength + ", podPlayerName=" + a.podPlayerName + ", parentId=" + a.videoId + ", podId=" + b + ", parentPodPosition=" + a.adPosition + ", podSecond=" + a.podSecond + ")"), this._canProcess() && this._inputDataValidator.validateFields(a, ["videoId", "streamType", "playerName", "adId", "adLength", "podPlayerName", "adPosition"])) {
2309                        a.podSecond = null == a.podSecond || isNaN(a.podSecond) ? a.playhead : a.podSecond;var c,
2310                            d = {};for (c in a.metaVideo) {
2311                            a.metaVideo.hasOwnProperty(c) && (d[c] = a.metaVideo[c]);
2312                        }for (c in a.metaAd) {
2313                            a.metaAd.hasOwnProperty(c) && (d[c] = a.metaAd[c]);
2314                        }if (a.metaNielsen) for (var c in a.metaNielsen) {
2315                            a.metaNielsen.hasOwnProperty(c) && (d[c] = a.metaNielsen[c]);
2316                        }d["a.contentType"] = a.streamType, d["a.media.name"] = a.videoId, d["a.media.playerName"] = a.playerName, d["a.media.channel"] = a.channel || "", d["a.media.vsid"] = a.sessionId, d["a.media.friendlyName"] = a.videoName || "", d["a.media.length"] = Math.floor(a.videoLength) || "0.0", d["a.media.ad.name"] = a.adId, d["a.media.ad.friendlyName"] = a.adName || "", d["a.media.ad.podFriendlyName"] = a.podName || "", d["a.media.ad.length"] = Math.floor(a.adLength) || "0.0", d["a.media.ad.playerName"] = a.podPlayerName, d["a.media.ad.pod"] = b, d["a.media.ad.podPosition"] = Math.floor(a.adPosition) || "0.0", d["a.media.ad.podSecond"] = Math.floor(a.podSecond) || "0.0", d["a.media.ad.view"] = !0;var e = {};e.contextData = d, e.pev3 = z, e.pe = a.isPrimetime ? D : C, this._track(e);var f = this;setTimeout(function () {
2317                            f._trigger(F, a);
2318                        }, 0);
2319                    }
2320                }, e.prototype._setupDataResolver = function () {
2321                    var a = {},
2322                        b = this;a.rsid = function () {
2323                        return b._appMeasurement.account;
2324                    }, a.tracking_server = function () {
2325                        return b._appMeasurement.ssl && b._appMeasurement.trackingServerSecure ? b._appMeasurement.trackingServerSecure : b._appMeasurement.trackingServer;
2326                    }, a.ssl = function () {
2327                        return b._appMeasurement.ssl;
2328                    }, a.vid = function () {
2329                        return b._appMeasurement.visitorID;
2330                    }, a.aid = function () {
2331                        return b._appMeasurement.analyticsVisitorID;
2332                    }, a.mid = function () {
2333                        return b._appMeasurement.marketingCloudVisitorID;
2334                    }, a.blob = function () {
2335                        return b._appMeasurement.audienceManagerBlob;
2336                    }, a.loc_hint = function () {
2337                        return b._appMeasurement.audienceManagerLocationHint ? parseInt(b._appMeasurement.audienceManagerLocationHint) : "";
2338                    }, a.customerIDs = function () {
2339                        var a = {},
2340                            c = b._appMeasurement.visitor.getCustomerIDs();for (var d in c) {
2341                            if (c.hasOwnProperty(d)) {
2342                                var e = c[d];if ("object" == (typeof e === 'undefined' ? 'undefined' : _typeof(e))) {
2343                                    for (var f in e) {
2344                                        e.hasOwnProperty(f) && ("authState" == f ? a[d + ".as"] = e[f] : a[d + "." + f] = e[f]);
2345                                    }a[d + ".as"] || (a[d + ".as"] = "0");
2346                                }
2347                            }
2348                        }return a;
2349                    }, a.visitorOptedOut = function () {
2350                        return !!b._appMeasurement.visitorOptedOut;
2351                    }, a.channel = function () {
2352                        return b._config ? b._config.channel : null;
2353                    }, a.meta = function (a) {
2354                        var c = a.split(".");if (c.length < 2) return null;var d = c.shift();switch (a = c.join("."), d) {case "video":
2355                                return a == i.WILDCARD ? b._videoMetadata : b._videoMetadata[a];case "ad":
2356                                return a == i.WILDCARD ? b._adMetadata : b._adMetadata[a];case "chapter":
2357                                return a == i.WILDCARD ? b._chapterMetadata : b._chapterMetadata[a];default:
2358                                return null;}
2359                    }, this._dataResolver = function (b) {
2360                        if (!b || 0 == b.length) return null;for (var c = null, d = 0; d < b.length; d++) {
2361                            var e = b[d];c = c || {}, n.startsWith(e, "meta.") ? c[e] = a.meta(e.split("meta.")[1]) : c[e] = a.hasOwnProperty(e) ? a[e].call(this) : null;
2362                        }return c;
2363                    };
2364                }, e.prototype._registerCommands = function () {
2365                    this._pluginManager.comply(this, "handleVideoLoad", this._cmdVideoLoad), this._pluginManager.comply(this, "handleVideoStart", this._cmdVideoStart), this._pluginManager.comply(this, "handleAdStart", this._cmdAdStart), this._pluginManager.comply(this, "handleHeartbeatPluginError", this._cmdHeartbeatPluginError);
2366                }, e.prototype._registerBehaviours = function () {
2367                    this._pluginManager.registerBehaviour(new f(u, G), this, "handleVideoLoad"), this._pluginManager.registerBehaviour(new f(u, H), this, "handleVideoStart", [new h(u, "video.id", "videoId"), new h(u, "video.streamType", "streamType"), new h(u, "video.name", "videoName"), new h(u, "video.length", "videoLength"), new h(u, "video.playerName", "playerName"), new h(u, "video.streamType", "streamType"), new h(v, "is_primetime", "isPrimetime"), new h(v, "session_id", "sessionId"), new h(s, "channel", "channel"), new h(s, "meta.video.*", "metaVideo"), new h(t, "meta", "metaNielsen")]), this._pluginManager.registerBehaviour(new f(u, I), this, "handleAdStart", [new h(u, "video.id", "videoId"), new h(u, "video.streamType", "streamType"), new h(u, "video.playhead", "playhead"), new h(u, "video.playerName", "playerName"), new h(u, "video.name", "videoName"), new h(u, "video.length", "videoLength"), new h(u, "ad.id", "adId"), new h(u, "ad.length", "adLength"), new h(u, "ad.position", "adPosition"), new h(u, "ad.name", "adName"), new h(u, "pod.name", "podName"), new h(u, "pod.position", "podPosition"), new h(u, "pod.playerName", "podPlayerName"), new h(u, "pod.startTime", "podSecond"), new h(v, "is_primetime", "isPrimetime"), new h(v, "session_id", "sessionId"), new h(s, "channel", "channel"), new h(s, "meta.video.*", "metaVideo"), new h(s, "meta.ad.*", "metaAd"), new h(t, "meta", "metaNielsen")]), this._pluginManager.registerBehaviour(new f(v, x), this, "handleHeartbeatPluginError");
2368                }, e.prototype._onAppMeasurementReady = function () {
2369                    this._logger.debug(this._logTag, "[Init] #_onAppMeasurementReady"), this._workQueue.resume();
2370                };var r = "adobe-analytics",
2371                    s = r,
2372                    t = "nielsen",
2373                    u = "player",
2374                    v = "adobe-heartbeat",
2375                    w = 2e3,
2376                    x = "error",
2377                    y = "video",
2378                    z = "videoAd",
2379                    A = "ms_s",
2380                    B = "msp_s",
2381                    C = "msa_s",
2382                    D = "mspa_s",
2383                    E = "aa_start",
2384                    F = "sc_ad_start",
2385                    G = "video_load",
2386                    H = "video_start",
2387                    I = "ad_start";d.AdobeAnalyticsPlugin = e;
2388            }(a.ADB.core, a.ADB.va, a.ADB.va.utils, b), function (a) {
2389                "use strict";
2390                var b = { SHOW: "a.media.show", SEASON: "a.media.season", EPISODE: "a.media.episode", ASSET_ID: "a.media.asset", GENRE: "a.media.genre", FIRST_AIR_DATE: "a.media.airDate", FIRST_DIGITAL_DATE: "a.media.digitalDate", RATING: "a.media.rating", ORIGINATOR: "a.media.originator", NETWORK: "a.media.network", SHOW_TYPE: "a.media.type", AD_LOAD: "a.media.adLoad", MVPD: "a.media.pass.mvpd", AUTHORIZED: "a.media.pass.auth", DAY_PART: "a.media.dayPart", FEED: "a.media.feed", STREAM_FORMAT: "a.media.format" },
2391                    c = { ADVERTISER: "a.media.ad.advertiser", CAMPAIGN_ID: "a.media.ad.campaign", CREATIVE_ID: "a.media.ad.creative", PLACEMENT_ID: "a.media.ad.placement", SITE_ID: "a.media.ad.site", CREATIVE_URL: "a.media.ad.creativeURL" };a.VideoMetadataKeys = b, a.AdMetadataKeys = c;
2392            }(b), a.ADB.va.plugins.aa || (a.ADB.va.plugins.aa = b);
2393        }(this);
2394
2395        // MediaHeartbeat
2396        !function (a) {
2397            !function (a, b) {
2398                "use strict";
2399                function c() {
2400                    this._processAction = !0, this._store = {};
2401                }function d(a) {
2402                    if (!a) throw new Error("Reference to the logger object cannot be NULL");this._logger = a, this._rules = [];
2403                }c.prototype.setRuleName = function (a) {
2404                    this._ruleName = a;
2405                }, c.prototype.getRuleName = function (a, b) {
2406                    return this._ruleName;
2407                }, c.prototype.setData = function (a, b) {
2408                    this._store[a] = b;
2409                }, c.prototype.getData = function (a) {
2410                    return this._store[a];
2411                }, c.prototype.shouldProcessAction = function () {
2412                    return this._processAction;
2413                }, c.prototype.stopProcessingAction = function () {
2414                    this._processAction = !1;
2415                }, c.prototype.startProcessingAction = function () {
2416                    this._processAction = !0;
2417                }, d.createContext = function () {
2418                    return new c();
2419                }, d.createPredicate = function (a, b, c) {
2420                    return { fn: a, expectedValue: b, msg: c };
2421                }, d.prototype.registerRule = function (a, b, c, d, e) {
2422                    this._rules.push({ name: a, desc: b, preconditions: c, actions: d, scope: e });
2423                }, d.prototype.registerEnterExitAction = function (a, b) {
2424                    this._enterAction = a, this._exitAction = b;
2425                }, d.prototype._handleFailure = function (a, b) {
2426                    this._logger.error(e, a.desc + " -  " + b.msg);
2427                }, d.prototype._getRule = function (a) {
2428                    for (var b = 0; b < this._rules.length; ++b) {
2429                        if (this._rules[b].name === a) return this._rules[b];
2430                    }return null;
2431                }, d.prototype.processRule = function (a, b) {
2432                    var c = !0,
2433                        f = this._getRule(a);if (f) {
2434                        var g = f.scope;b || (b = d.createContext()), b.setRuleName(a);for (var h = !1, i = 0; i < f.preconditions.length; ++i) {
2435                            var j = f.preconditions[i];if (h = !!j.fn.call(g, b) !== j.expectedValue) {
2436                                this._handleFailure(f, j);break;
2437                            }
2438                        }if (h) c = !1;else {
2439                            b.startProcessingAction(), this._enterAction && this._enterAction.call(g, b);for (var i = 0; i < f.actions.length; ++i) {
2440                                var k = f.actions[i];if (!b.shouldProcessAction()) {
2441                                    this._logger.info(e, "Stopping actions for " + f.desc);break;
2442                                }k.call(g, b);
2443                            }this._exitAction && b.shouldProcessAction() && this._exitAction.call(g, b);
2444                        }
2445                    } else this._logger.warn(e, "No registered event found for ruleName " + a), c = !1;return c;
2446                };var e = "RuleEngine";b._RuleEngine = d;
2447            }(a.ADB.core, a.ADB.va), function (a, b) {
2448                "use strict";
2449                function c(a, b, c) {
2450                    this.taskFn = a, this.scope = b, this.interval = c, this.remainingInterval = c;
2451                }function d(a) {
2452                    if (!a) throw new Error("Reference to the logger object cannot be NULL");this._logger = a, this._tasks = [], this._pausedTasks = [];
2453                }c.prototype.elapsedTime = function (a) {
2454                    this.remainingInterval -= a;
2455                }, c.prototype.shouldExecute = function () {
2456                    return this.remainingInterval <= 0;
2457                }, c.prototype.execute = function () {
2458                    this.taskFn.call(this.scope);
2459                }, d.prototype._getCurrentTimeInMS = function () {
2460                    return new Date().getTime();
2461                }, d.prototype._runTasksForTime = function (a) {
2462                    var b = [],
2463                        c = a - this._lastTickTime;this._lastTickTime = a;for (var d = 0; d < this._tasks.length;) {
2464                        var e = this._tasks[d];e.elapsedTime(c), e.shouldExecute() ? (b.push(e), this._tasks.splice(d, 1)) : ++d;
2465                    }this._checkStopTimer();for (var d = 0; d < b.length; ++d) {
2466                        b[d].execute();
2467                    }
2468                }
2468, d.prototype._onTick = function () {
2469                    var a = this._getCurrentTimeInMS();this._runTasksForTime(a);
2470                }, d.prototype._startTimer = function () {
2471                    var a = this;this._timer || (this._logger.info(e, "#startTimer()"), a._lastTickTime = this._getCurrentTimeInMS(), this._timer = window.setInterval(function () {
2472                        a._onTick();
2473                    }, f));
2474                }, d.prototype._stopTimer = function () {
2475                    this._timer && (this._logger.info(e, "#stopTimer()"), window.clearInterval(this._timer), this._timer = null);
2476                }, d.prototype._checkStartTimer = function () {
2477                    this._tasks.length > 0 && this._startTimer();
2478                }, d.prototype._checkStopTimer = function () {
2479                    0 === this._tasks.length && this._stopTimer();
2480                }, d.prototype._removeTask = function (a, b) {
2481                    for (var c = 0; c < a.length; ++c) {
2482                        if (a[c] === b) return a.splice(c, 1), !0;
2483                    }return !1;
2484                }, d.prototype.scheduleTask = function (a, b, d) {
2485                    if (this._logger.info(e, "#scheduleTask()"), !a) throw new Error("Reference to the taskFn cannot be NULL");var f = new c(a, b, d);return this._tasks.push(f), this._checkStartTimer(), f;
2486                }, d.prototype.cancelTask = function (a) {
2487                    this._logger.info(e, "#cancelTask()"), this._removeTask(this._tasks, a), this._checkStopTimer();
2488                }, d.prototype.pauseTask = function (a) {
2489                    this._logger.info(e, "#pauseTask()"), this._removeTask(this._tasks, a) && this._pausedTasks.push(a), this._checkStopTimer();
2490                }, d.prototype.resumeTask = function (a) {
2491                    this._logger.info(e, "#resumeTask()"), this._removeTask(this._pausedTasks, a) && this._tasks.push(a), this._checkStartTimer();
2492                }, d.prototype.clearTasks = function () {
2493                    this._stopTimer(), this._tasks = [], this._pausedTasks = [];
2494                };var e = "TaskScheduler",
2495                    f = 250;b._TaskScheduler = d;
2496            }(a.ADB.core, a.ADB.va), function (a) {
2497                "use strict";
2498                function b() {
2499                    this.trackingServer = void 0, this.channel = void 0, this.ovp = void 0, this.appVersion = void 0, this.playerName = void 0, this.ssl = !1, this.debugLogging = !1;
2500                }a.MediaHeartbeatConfig = b, a.MediaHeartbeatConfig.sharedInstance = new b();
2501            }(a.ADB.va), function (a) {
2502                "use strict";
2503                function b() {
2504                    this.data = {};
2505                }var c = a.plugins.videoplayer.VideoInfo,
2506                    d = a.plugins.videoplayer.AdBreakInfo,
2507                    e = a.plugins.videoplayer.AdInfo,
2508                    f = a.plugins.videoplayer.ChapterInfo,
2509                    g = a.plugins.videoplayer.QoSInfo;b.MEDIAINFO_KEY_NAME = "a.name", b.MEDIAINFO_KEY_VIDEOID = "a.videoId", b.MEDIAINFO_KEY_ADID = "a.adId", b.MEDIAINFO_KEY_LENGTH = "a.length", b.MEDIAINFO_KEY_PLAYHEAD = "a.playhead", b.MEDIAINFO_KEY_STREAMTYPE = "a.streamType", b.MEDIAINFO_KEY_POSITION = "a.position", b.MEDIAINFO_KEY_STARTTIME = "a.startTime", b.MEDIAINFO_KEY_BITRATE = "a.bitrate", b.MEDIAINFO_KEY_FPS = "a.fps", b.MEDIAINFO_KEY_DROPPEDFRAMES = "a.droppedFrames", b.MEDIAINFO_KEY_STARTUPTIME = "a.startupTime", b.MEDIAINFO_KEY_TIMEDMETADATA = "a.timedMetadata", b.prototype.setValue = function (a, b) {
2510                    this.data[a] = b;
2511                }, b.prototype.getValue = function (a) {
2512                    return this.data.hasOwnProperty(a) ? this.data[a] : null;
2513                }, b.prototype.createVideoInfo = function () {
2514                    var a = new c();return a.id = null != this.getValue(b.MEDIAINFO_KEY_VIDEOID) ? this.getValue(b.MEDIAINFO_KEY_VIDEOID) : "", a.name = null != this.getValue(b.MEDIAINFO_KEY_NAME) ? this.getValue(b.MEDIAINFO_KEY_NAME) : "", a.length = null != this.getValue(b.MEDIAINFO_KEY_LENGTH) ? this.getValue(b.MEDIAINFO_KEY_LENGTH) : 0, a.playhead = null != this.getValue(b.MEDIAINFO_KEY_PLAYHEAD) ? this.getValue(b.MEDIAINFO_KEY_PLAYHEAD) : 0, a.streamType = null != this.getValue(b.MEDIAINFO_KEY_STREAMTYPE) ? this.getValue(b.MEDIAINFO_KEY_STREAMTYPE) : "", a;
2515                }, b.prototype.createAdBreakInfo = function () {
2516                    var a = new d();return a.name = null != this.getValue(b.MEDIAINFO_KEY_NAME) ? this.getValue(b.MEDIAINFO_KEY_NAME) : "", a.position = null != this.getValue(b.MEDIAINFO_KEY_POSITION) ? this.getValue(b.MEDIAINFO_KEY_POSITION) : 0, a.startTime = null != this.getValue(b.MEDIAINFO_KEY_STARTTIME) ? this.getValue(b.MEDIAINFO_KEY_STARTTIME) : 0, a;
2517                }, b.prototype.createAdInfo = function () {
2518                    var a = new e();return a.id = null != this.getValue(b.MEDIAINFO_KEY_ADID) ? this.getValue(b.MEDIAINFO_KEY_ADID) : "", a.name = null != this.getValue(b.MEDIAINFO_KEY_NAME) ? this.getValue(b.MEDIAINFO_KEY_NAME) : "", a.length = null != this.getValue(b.MEDIAINFO_KEY_LENGTH) ? this.getValue(b.MEDIAINFO_KEY_LENGTH) : 0, a.position = null != this.getValue(b.MEDIAINFO_KEY_POSITION) ? this.getValue(b.MEDIAINFO_KEY_POSITION) : 0, a;
2519                }, b.prototype.createChapterInfo = function () {
2520                    var a = new f();return a.name = null != this.getValue(b.MEDIAINFO_KEY_NAME) ? this.getValue(b.MEDIAINFO_KEY_NAME) : "", a.length = null != this.getValue(b.MEDIAINFO_KEY_LENGTH) ? this.getValue(b.MEDIAINFO_KEY_LENGTH) : 0, a.startTime = null != this.getValue(b.MEDIAINFO_KEY_STARTTIME) ? this.getValue(b.MEDIAINFO_KEY_STARTTIME) : 0, a.position = null != this.getValue(b.MEDIAINFO_KEY_POSITION) ? this.getValue(b.MEDIAINFO_KEY_POSITION) : 0, a;
2521                }, b.prototype.createQoSInfo = function () {
2522                    var a = new g();return a.bitrate = null != this.getValue(b.MEDIAINFO_KEY_BITRATE) ? this.getValue(b.MEDIAINFO_KEY_BITRATE) : 0, a.fps = null != this.getValue(b.MEDIAINFO_KEY_FPS) ? this.getValue(b.MEDIAINFO_KEY_FPS) : 0, a.droppedFrames = null != this.getValue(b.MEDIAINFO_KEY_DROPPEDFRAMES) ? this.getValue(b.MEDIAINFO_KEY_DROPPEDFRAMES) : 0, a.startupTime = null != this.getValue(b.MEDIAINFO_KEY_STARTUPTIME) ? this.getValue(b.MEDIAINFO_KEY_STARTUPTIME) : 0, a;
2523                }, b.prototype.isEqual = function (a) {
2524                    if (this === a) return !0;if (!a || "object" != (typeof a === 'undefined' ? 'undefined' : _typeof(a)) || "function" != typeof a.getValue) return !1;for (var c = [b.MEDIAINFO_KEY_NAME, b.MEDIAINFO_KEY_VIDEOID, b.MEDIAINFO_KEY_ADID, b.MEDIAINFO_KEY_LENGTH, b.MEDIAINFO_KEY_PLAYHEAD, b.MEDIAINFO_KEY_STREAMTYPE, b.MEDIAINFO_KEY_POSITION, b.MEDIAINFO_KEY_STARTTIME, b.MEDIAINFO_KEY_BITRATE, b.MEDIAINFO_KEY_FPS, b.MEDIAINFO_KEY_DROPPEDFRAMES, b.MEDIAINFO_KEY_STARTUPTIME, b.MEDIAINFO_KEY_TIMEDMETADATA], d = 0; d < c.length; ++d) {
2525                        var e = c[d];if (this.getValue(e) !== a.getValue(e)) return !1;
2526                    }return !0;
2527                }, a.MediaObject = b;
2528            }(a.ADB.va), function (a, b) {
2529                "use strict";
2530                function c(a) {
2531                    c.__super__.constructor.call(this), this._heartbeat = a;
2532                }function d(a) {
2533                    d.__super__.constructor.call(this), this._heartbeat = a;
2534                }function e(a) {
2535                    e.__super__.constructor.call(this), this._heartbeat = a;
2536                }function f(a) {
2537                    f.__super__.constructor.call(this), this._heartbeat = a;
2538                }a.extend(c, b.plugins.aa.AdobeAnalyticsPluginDelegate), c.prototype.onError = function (a) {
2539                    this._heartbeat && this._heartbeat._onDelegateError(a);
2540                }, a.extend(d, b.plugins.ah.AdobeHeartbeatPluginDelegate), d.prototype.onError = function (a) {
2541                    this._heartbeat && this._heartbeat._onDelegateError(a);
2542                }, a.extend(e, b.HeartbeatDelegate), e.prototype.onError = function (a) {
2543                    this._heartbeat && this._heartbeat._onDelegateError(a);
2544                }, a.extend(f, b.plugins.videoplayer.VideoPlayerPluginDelegate), f.prototype.getVideoInfo = function () {
2545                    return this._heartbeat && this._heartbeat._videoInfo ? (this._heartbeat._delegate && (this._heartbeat._videoInfo.playhead = this._heartbeat._delegate.getCurrentPlaybackTime()), this._heartbeat._videoInfo) : null;
2546                }, f.prototype.getAdBreakInfo = function () {
2547                    return this._heartbeat && this._heartbeat._adBreakInfo ? this._heartbeat._adBreakInfo : null;
2548                }, f.prototype.getAdInfo = function () {
2549                    return this._heartbeat && this._heartbeat._adInfo ? this._heartbeat._adInfo : null;
2550                }, f.prototype.getChapterInfo = function () {
2551                    return this._heartbeat && this._heartbeat._chapterInfo ? this._heartbeat._chapterInfo : null;
2552                }, f.prototype.getQoSInfo = function () {
2553                    if (this._heartbeat && this._heartbeat._delegate && this._heartbeat._delegate.getQoSObject()) {
2554                        var a = this._heartbeat._delegate.getQoSObject();if (a && "object" == (typeof a === 'undefined' ? 'undefined' : _typeof(a)) && a.setValue) return a.createQoSInfo();
2555                    }return null;
2556                }, b._MediaAnalyticsPluginDelegate = c, b._MediaHeartbeatPluginDelegate = d, b._ADBMediaHeartbeatDelegate = e, b._MediaHeartbeatVideoPlayerPluginDelegate = f;
2557            }(a.ADB.core, a.ADB.va), function (a, b) {
2558                "use strict";
2559                function c() {}function d(a) {
2560                    d.__super__.constructor.call(this), this._heartbeat = a;
2561                }var e = b.MediaHeartbeatConfig;b.plugins.nielsen && (c.MEDIAHEARTBEAT_NIELSEN_CONTENT_METADATA = "media_nielsen_content_metadata", c.MEDIAHEARTBEAT_NIELSEN_AD_METADATA = "media_nielsen_ad_metadata", c.MEDIAHEARTBEAT_NIELSEN_CHANNEL_METADATA = "media_nielsen_channel_metadata", e.prototype.nielsenConfigKey = void 0, e.prototype.nielsenAppInfo = void 0, a.extend(d, b.plugins.nielsen.NielsenPluginDelegate), d.prototype.getMetadataInfo = function () {
2562                    if (this._heartbeat && this._heartbeat._currentMediaObject) {
2563                        var a = this._heartbeat._currentMediaObject.getValue(c.MEDIAHEARTBEAT_NIELSEN_CONTENT_METADATA);if ("object" == (typeof a === 'undefined' ? 'undefined' : _typeof(a))) return a;
2564                    }return null;
2565                }, d.prototype.getAdMetadataInfo = function () {
2566                    if (this._heartbeat && this._heartbeat._currentAdObject) {
2567                        var a = this._heartbeat._currentAdObject.getValue(c.MEDIAHEARTBEAT_NIELSEN_AD_METADATA);if ("object" == (typeof a === 'undefined' ? 'undefined' : _typeof(a))) return a;
2568                    }return null;
2569                }, d.prototype.getChannelInfo = function () {
2570                    if (this._heartbeat && this._heartbeat._currentMediaObject) {
2571                        var a = this._heartbeat._currentMediaObject.getValue(c.MEDIAHEARTBEAT_NIELSEN_CHANNEL_METADATA);if ("object" == (typeof a === 'undefined' ? 'undefined' : _typeof(a))) return a;
2572                    }return null;
2573                }, d.prototype.onError = function (a) {
2574                    this._heartbeat && this._heartbeat._onDelegateError(a);
2575                }, b._MediaHeartbeatNielsen = c, b._MediaHeartbeatNielsenPluginDelegate = d);
2576            }(a.ADB.core, a.ADB.va), function (b, c) {
2577                "use strict";
2578                function d() {}function e(b, d, e) {
2579                    if (this._appMeasurement = e || a.s, !this._appMeasurement) throw new Error("MediaHeartbeat needs a valid AppMeasurement instance.");if (!this._appMeasurement.visitor || !this._appMeasurement.visitor.marketingCloudOrgID) throw new Error("MediaHeartbeat needs a valid visitor instance with marketingCloudOrgId set.");if (!b) throw new Error("MediaHeartbeat needs a valid delegate object.");if (!d || "object" != (typeof d === 'undefined' ? 'undefined' : _typeof(d)) || !d.trackingServer) throw new Error("MediaHeartbeat needs a valid config object with trackingServer set.");this._config = d, this._delegate = b, this._debugLogging = c.MediaHeartbeat._debugLogging || this._config.debugLogging, this._logger = new f(), this._debugLogging ? this._logger.enable() : this._logger.disable(), this._ruleEngine = new u(this._logger), this._taskScheduler = new v(this._logger), this._resetState(), this._setupRules();
2580                }var f = b.Logger,
2581                    g = c.plugins.videoplayer.AssetType,
2582                    h = c.MediaObject,
2583                    i = c.Heartbeat,
2584                    j = c.HeartbeatConfig,
2585                    k = c._ADBMediaHeartbeatDelegate,
2586                    l = c.plugins.videoplayer.VideoPlayerPlugin,
2587                    m = c.plugins.videoplayer.VideoPlayerPluginConfig,
2588                    n = c._MediaHeartbeatVideoPlayerPluginDelegate,
2589                    o = c.plugins.aa.AdobeAnalyticsPlugin,
2590                    p = c.plugins.aa.AdobeAnalyticsPluginConfig,
2591                    q = c._MediaAnalyticsPluginDelegate,
2592                    r = c.plugins.ah.AdobeHeartbeatPlugin,
2593                    s = c.plugins.ah.AdobeHeartbeatPluginConfig,
2594                    t = c._MediaHeartbeatPluginDelegate,
2595                    u = c._RuleEngine,
2596                    v = c._TaskScheduler,
2597                    w = c.utils.ObjectUtils;if (d.prototype.getCurrentPlaybackTime = function () {
2598                    return null;
2599                }, d.prototype.getQoSObject = function () {
2600                    return null;
2601                }, e.Event = { AdBreakStart: "adBreakStart", AdBreakComplete: "adBreakComplete", AdStart: "adStart", AdComplete: "adComplete", AdSkip: "adSkip", ChapterStart: "chapterStart", ChapterComplete: "chapterComplete", ChapterSkip: "chapterSkip", SeekStart: "seekStart", SeekComplete: "seekComplete", BufferStart: "bufferStart", BufferComplete: "bufferComplete", BitrateChange: "bitrateChange", TimedMetadataUpdate: "timedMetadataUpdate" }, e.StreamType = { VOD: "vod", LIVE: "live", LINEAR: "linear" }, e.MediaObjectKey = { StandardVideoMetadata: "media_standard_content_metadata", StandardAdMetadata: "media_standard_ad_metadata", 
2601VideoResumed: "resumed", PrerollTrackingWaitingTime: "preroll_tracking_waiting_time" }, e.VideoMetadataKeys = c.plugins.aa.VideoMetadataKeys, e.AdMetadataKeys = c.plugins.aa.AdMetadataKeys, e.createMediaObject = function (a, b, c, d) {
2602                    var e = new h();e.setValue(h.MEDIAINFO_KEY_VIDEOID, b), e.setValue(h.MEDIAINFO_KEY_NAME, a), e.setValue(h.MEDIAINFO_KEY_LENGTH, c), e.setValue(h.MEDIAINFO_KEY_PLAYHEAD, 0);var f = d || g.ASSET_TYPE_VOD;return e.setValue(h.MEDIAINFO_KEY_STREAMTYPE, f), e;
2603                }, e.createAdBreakObject = function (a, b, c) {
2604                    var d = new h();return d.setValue(h.MEDIAINFO_KEY_NAME, a), d.setValue(h.MEDIAINFO_KEY_POSITION, b), d.setValue(h.MEDIAINFO_KEY_STARTTIME, c), d;
2605                }, e.createAdObject = function (a, b, c, d) {
2606                    var e = new h();return e.setValue(h.MEDIAINFO_KEY_NAME, a), e.setValue(h.MEDIAINFO_KEY_ADID, b), e.setValue(h.MEDIAINFO_KEY_POSITION, c), e.setValue(h.MEDIAINFO_KEY_LENGTH, d), e;
2607                }, e.createChapterObject = function (a, b, c, d) {
2608                    var e = new h();return e.setValue(h.MEDIAINFO_KEY_NAME, a), e.setValue(h.MEDIAINFO_KEY_POSITION, b), e.setValue(h.MEDIAINFO_KEY_LENGTH, c), e.setValue(h.MEDIAINFO_KEY_STARTTIME, d), e;
2609                }, e.createQoSObject = function (a, b, c, d) {
2610                    var e = new h();return e.setValue(h.MEDIAINFO_KEY_BITRATE, a), e.setValue(h.MEDIAINFO_KEY_FPS, c), e.setValue(h.MEDIAINFO_KEY_DROPPEDFRAMES, d), e.setValue(h.MEDIAINFO_KEY_STARTUPTIME, b), e;
2611                }, e.createTimedMetadataObject = function (a) {
2612                    var b = new h();return b.setValue(h.MEDIAINFO_KEY_TIMEDMETADATA, a), b;
2613                }, e.version = function () {
2614                    return c.Version.getVersion();
2615                }, e.prototype.trackSessionStart = function (a, b) {
2616                    this._logger.info(B, "#::trackSessionStart()");var c = u.createContext();c.setData(C, a), c.setData(H, this._cleanContextData(b)), this._processRule(z.SessionStart, c);
2617                }, e.prototype.trackPlay = function () {
2618                    this._logger.info(B, "#::trackPlay()"), this._processRule(z.Play);
2619                }, e.prototype.trackPause = function () {
2620                    this._logger.info(B, "#::trackPause()"), this._processRule(z.Pause);
2621                }, e.prototype.trackComplete = function () {
2622                    this._logger.info(B, "#::trackComplete()"), this._processRule(z.VideoComplete);
2623                }, e.prototype.trackSessionEnd = function () {
2624                    this._logger.info(B, "#::trackSessionEnd()"), this._processRule(z.SessionEnd);
2625                }, e.prototype.trackError = function (a) {
2626                    this._logger.info(B, "#::trackError()");var b = u.createContext();b.setData(I, a), this._processRule(z.Error, b);
2627                }, e.prototype.trackEvent = function (a, b, c) {
2628                    this._logger.info(B, "#::trackEvent() - " + a);var d,
2629                        f = u.createContext();switch (a) {case e.Event.AdBreakStart:
2630                            f.setData(D, b), f.setData(H, this._cleanContextData(c)), d = z.AdBreakStart;break;case e.Event.AdBreakComplete:
2631                            d = z.AdBreakComplete;break;case e.Event.AdStart:
2632                            f.setData(E, b), f.setData(H, this._cleanContextData(c)), d = z.AdStart;break;case e.Event.AdComplete:
2633                            d = z.AdComplete;break;case e.Event.AdSkip:
2634                            d = z.AdSkip;break;case e.Event.SeekStart:
2635                            d = z.SeekStart;break;case e.Event.SeekComplete:
2636                            d = z.SeekComplete;break;case e.Event.ChapterStart:
2637                            f.setData(F, b), f.setData(H, this._cleanContextData(c)), d = z.ChapterStart;break;case e.Event.ChapterComplete:
2638                            d = z.ChapterComplete;break;case e.Event.ChapterSkip:
2639                            d = z.ChapterSkip;break;case e.Event.BufferStart:
2640                            d = z.BufferStart;break;case e.Event.BufferComplete:
2641                            d = z.BufferComplete;break;case e.Event.BitrateChange:
2642                            d = z.BitrateChange;break;case e.Event.TimedMetadataUpdate:
2643                            d = z.TimedMetadataUpdate, f.setData(G, b);break;default:
2644                            return void this._logger.error(B, "Incorrect event name.");}this._processRule(d, f);
2645                }, c.plugins.nielsen) {
2646                    var x = c._MediaHeartbeatNielsen;e.MediaObjectKey.NielsenContentMetadata = x.MEDIAHEARTBEAT_NIELSEN_CONTENT_METADATA, e.MediaObjectKey.NielsenAdMetadata = x.MEDIAHEARTBEAT_NIELSEN_AD_METADATA, e.MediaObjectKey.NielsenChannelMetadata = x.MEDIAHEARTBEAT_NIELSEN_CHANNEL_METADATA, e.prototype.nielsenLoadMetadata = function (a) {
2647                        this._nielsenPlugin && this._nielsenPlugin.loadMetadata(a);
2648                    };
2649                }e.prototype._setState = function (a, b) {
2650                    this._mediaState[a] = b;
2651                }, e.prototype._isInState = function (a) {
2652                    return this._mediaState[a];
2653                }, e.prototype._isInSession = function (a) {
2654                    return this._isInState(y.Session);
2655                }, e.prototype._isInMedia = function (a) {
2656                    return this._isInState(y.Media);
2657                }, e.prototype._isInAd = function (a) {
2658                    return this._isInState(y.Ad);
2659                }, e.prototype._isInAdBreak = function (a) {
2660                    return this._isInState(y.AdBreak);
2661                }, e.prototype._isInChapter = function (a) {
2662                    return this._isInState(y.Chapter);
2663                }, e.prototype._isInPlay = function (a) {
2664                    return this._isInState(y.PlayPause);
2665                }, e.prototype._isInPause = function (a) {
2666                    return !this._isInState(y.PlayPause);
2667                }, e.prototype._isInBuffer = function (a) {
2668                    return this._isInState(y.Buffer);
2669                }, e.prototype._isInSeek = function (a) {
2670                    return this._isInState(y.Seek);
2671                }, e.prototype._isPlatformTrackingSupported = function (a) {
2672                    return !this._appMeasurement.unsupportedBrowser;
2673                }, e.prototype._isValidMediaObject = function (a) {
2674                    var b = a.getData(C);if (b && b instanceof h) {
2675                        var c = b.getValue(e.MediaObjectKey.VideoResumed);null != c && "boolean" != typeof c && this._logger.warn(B, "Ignoring value set for MediaHeartbeat.MediaObjectKey.VideoResumed in MediaObject as we expect a boolean value");var d = b.getValue(e.MediaObjectKey.PrerollTrackingWaitingTime);if (null != d) {
2676                            ("string" == typeof d || "number" == typeof d) && !isNaN(d) || this._logger.warn(B, "Ignoring value set for MediaHeartbeat.MediaObjectKey.PrerollTrackingWaitingTime in MediaObject as we expect a valid duration as number in milliseconds.");
2677                        }var f = b.getValue(e.MediaObjectKey.StandardVideoMetadata);return null != f && "object" != (typeof f === 'undefined' ? 'undefined' : _typeof(f)) && this._logger.warn(B, "Ignoring value set for MediaHeartbeat.MediaObjectKey.StandardVideoMetadata in MediaObject as we expect a valid object with kv pairs."), !0;
2678                    }return !1;
2679                }, e.prototype._isValidAdBreakObject = function (a) {
2680                    var b = a.getData(D);return b && b instanceof h;
2681                }, e.prototype._isDifferentAdBreakObject = function (a) {
2682                    var b = a.getData(D);return !(this._currentAdBreakObject && this._currentAdBreakObject.isEqual(b));
2683                }, e.prototype._isValidAdObject = function (a) {
2684                    var b = a.getData(E);if (b && b instanceof h) {
2685                        var c = b.getValue(K);null != c && "boolean" != typeof c && this._logger.warn(B, "Ignoring value set for MediaHeartbeat.MediaObjectKey.GranularAdTracking in AdObject as we expect a boolean value.");var d = b.getValue(e.MediaObjectKey.StandardAdMetadata);return null != d && "object" != (typeof d === 'undefined' ? 'undefined' : _typeof(d)) && this._logger.warn(B, "Ignoring value set for MediaHeartbeat.MediaObjectKey.StandardAdMetadata in AdObject as we expect a valid object with kv pairs."), !0;
2686                    }return !1;
2687                }, e.prototype._isDifferentAdObject = function (a) {
2688                    var b = a.getData(E);return !(this._currentAdObject && this._currentAdObject.isEqual(b));
2689                }, e.prototype._isValidChapterObject = function (a) {
2690                    var b = a.getData(F);return b && b instanceof h;
2691                }, e.prototype._isDifferentChapterObject = function (a) {
2692                    var b = a.getData(F);return !(this._currentChapterObject && this._currentChapterObject.isEqual(b));
2693                }, e.prototype._isValidTimedMetadataObject = function (a) {
2694                    var b = a.getData(G);
2694if (b && b instanceof h) {
2695                        var c = b.getValue(h.MEDIAINFO_KEY_TIMEDMETADATA);return c && "string" == typeof c;
2696                    }return !1;
2697                }, e.prototype._deferredTrackPlay = function () {
2698                    this._prerollWaitEnabled && (this._logger.info(B, "Executing deferred API:trackPlay."), this._prerollWaitEnabled = !1, this._playTaskHandle = null, this._processRule(z.Play));
2699                }, e.prototype._cmdEnterAction = function (a) {
2700                    var b = a.getRuleName();if (this._prerollWaitEnabled) if (this._playReceived) switch (b) {case z.SeekStart:case z.BufferStart:
2701                            this._logger.info(B, "Cancelling scheduled API:trackPlay because of SeekStart/BufferStart event"), this._taskScheduler.cancelTask(this._playTaskHandle), this._playTaskHandle = null;break;case z.SeekComplete:case z.BufferComplete:
2702                            this._logger.info(B, "Rescheduled API:trackPlay after SeekComplete/BufferComplete event"), this._playTaskHandle = this._taskScheduler.scheduleTask(this._deferredTrackPlay, this, this._prerollWaitTime);break;case z.Play:
2703                            this._logger.info(B, "Dropping API:trackPlay as we already have a API:trackPlay scheduled."), a.stopProcessingAction();break;case z.Pause:
2704                            this._logger.info(B, "Cancelling scheduled API:trackPlay because of API:trackPause call."), this._taskScheduler.cancelTask(this._playTaskHandle), this._playTaskHandle = null, this._prerollWaitEnabled = !1;break;case z.AdBreakStart:
2705                            this._logger.info(B, "Received API:trackEvent(AdBreakStart) within " + this._prerollWaitTime + " ms after API:trackPlay. We will track this as preroll AdBreak."), this._taskScheduler.cancelTask(this._playTaskHandle), this._playTaskHandle = null, this._prerollWaitEnabled = !1, this._playAfterAdStart = !0;} else switch (b) {case z.Play:
2706                            this._logger.info(B, "Deferring API:trackPlay for " + this._prerollWaitTime + " ms."), this._playReceived = !0, this._playTaskHandle = this._taskScheduler.scheduleTask(this._deferredTrackPlay, this, this._prerollWaitTime), a.stopProcessingAction();break;case z.AdBreakStart:
2707                            this._logger.info(B, "Received trackEvent(AdBreakStart) before first trackPlay."), this._prerollWaitEnabled = !1;}
2708                }, e.prototype._cmdExitAction = function (a) {
2709                    var b = a.getRuleName();this._playAfterAdStart && (b === z.AdStart ? (this._cmdPlay(a), this._playAfterAdStart = !1) : b === z.AdBreakComplete && (this._playAfterAdStart = !1)), b !== z.AdStart || this._isInState(y.FPlayPause) || this._cmdPlay(a);
2710                }, e.prototype._cmdConfigure = function (a) {
2711                    this._resetState(), this._configureAdobeAnalyticsPlugin(), this._configureAdobeHearbeatPlugin(), this._configureVideoPlayerPlugin(), this._configureOtherPlugins(), this._configureHeartbeat();
2712                }, e.prototype._cmdSessionStart = function (a) {
2713                    var b = a.getData(C),
2714                        c = a.getData(H);this._currentMediaObject = b, this._videoInfo = b.createVideoInfo(), this._videoInfo.playerName = this._config.playerName ? this._config.playerName : "";var d = b.getValue(e.MediaObjectKey.StandardVideoMetadata);d && "object" == (typeof d === 'undefined' ? 'undefined' : _typeof(d)) && (c || (c = {}), w.append(c, this._cleanContextData(d)));var f = b.getValue(e.MediaObjectKey.VideoResumed);"boolean" == typeof f && (this._videoInfo.resumed = f);var g = b.getValue(e.MediaObjectKey.PrerollTrackingWaitingTime);"string" != typeof g && "number" != typeof g || isNaN(g) || (this._prerollWaitTime = Number(g), this._prerollWaitTime <= 0 && (this._prerollWaitEnabled = !1)), this._aaPlugin.setVideoMetadata(c), this._playerPlugin.trackVideoLoad(), this._playerPlugin.trackSessionStart(), this._setState(y.Session, !0), this._setState(y.Media, !0);
2715                }, e.prototype._cmdVideoEnd = function (a) {
2716                    var b = a.getRuleName() === z.VideoComplete;if (this._isInState(y.Media)) {
2717                        var c = this._heartbeat,
2718                            d = this._playerPlugin;this._playerPlugin.trackComplete(function () {
2719                            d && d.trackVideoUnload(), c && c.destroy();
2720                        }, b);
2721                    }this._setState(y.Media, !1);
2722                }, e.prototype._cmdHandleMediaComplete = function (a) {
2723                    this._isInMedia(a) || (this._logger.info(B, "API:trackComplete has already cleaned up Heartbeat instance."), this._cmdSessionEnd(a), a.stopProcessingAction());
2724                }, e.prototype._cmdSessionEnd = function (a) {
2725                    this._setState(y.Session, !1), this._resetState();
2726                }, e.prototype._cmdBufferStart = function (a) {
2727                    this._playerPlugin.trackBufferStart(), this._setState(y.Buffer, !0);
2728                }, e.prototype._cmdBufferComplete = function (a) {
2729                    this._isInState(y.Buffer) && this._playerPlugin.trackBufferComplete(), this._setState(y.Buffer, !1);
2730                }, e.prototype._cmdSeekStart = function (a) {
2731                    this._playerPlugin.trackSeekStart(), this._setState(y.Seek, !0);
2732                }, e.prototype._cmdSeekComplete = function (a) {
2733                    this._isInState(y.Seek) && this._playerPlugin.trackSeekComplete(), this._setState(y.Seek, !1);
2734                }, e.prototype._cmdPlay = function (a) {
2735                    this._playerPlugin.trackPlay(), this._setState(y.PlayPause, !0), this._setState(y.FPlayPause, !0);
2736                }, e.prototype._cmdPause = function (a) {
2737                    this._playerPlugin.trackPause(), this._setState(y.PlayPause, !1), this._setState(y.FPlayPause, !0);
2738                }, e.prototype._cmdAdBreakStart = function (a) {
2739                    var b = a.getData(D);this._currentAdBreakObject = b, this._adBreakInfo = b.createAdBreakInfo(), this._adBreakInfo.playerName = this._config.playerName ? this._config.playerName : "", this._setState(y.AdBreak, !0);
2740                }, e.prototype._cmdAdBreakComplete = function (a) {
2741                    this._currentAdBreakObject = null, this._adBreakInfo = null, this._setState(y.AdBreak, !1);
2742                }, e.prototype._cmdAdStart = function (a) {
2743                    var b = a.getData(E),
2744                        c = a.getData(H);this._currentAdObject = b, this._adInfo = b.createAdInfo();var d = b.getValue(K);"boolean" == typeof d && (this._adInfo.granularTracking = d);var f = b.getValue(e.MediaObjectKey.StandardAdMetadata);f && "object" == (typeof f === 'undefined' ? 'undefined' : _typeof(f)) && (c || (c = {}), w.append(c, this._cleanContextData(f))), this._aaPlugin.setAdMetadata(c), this._playerPlugin.trackAdStart(), this._setState(y.Ad, !0);
2745                }, e.prototype._cmdAdComplete = function (a) {
2746                    this._currentAdObject = null, this._adInfo = null, this._isInState(y.Ad) && this._playerPlugin.trackAdComplete(), this._setState(y.Ad, !1);
2747                }, e.prototype._cmdAdSkip = function (a) {
2748                    this._currentAdObject = null, this._adInfo = null, this._isInState(y.Ad) && this._playerPlugin.trackAdSkip(), this._setState(y.Ad, !1);
2749                }, e.prototype._cmdChapterStart = function (a) {
2750                    var b = a.getData(F),
2751                        c = a.getData(H);this._currentChapterObject = b, this._chapterInfo = b.createChapterInfo(), this._aaPlugin.setChapterMetadata(c), this._playerPlugin.trackChapterStart(), this._setState(y.Chapter, !0);
2752                }, e.prototype._cmdChapterComplete = function (a) {
2753                    this._currentChapterObject = null, this._chapterInfo = null, this._isInState(y.Chapter) && this._playerPlugin.trackChapterComplete(), this._setState(y.Chapter, !1);
2754                }, e.prototype._cmdChapterSkip = function (a) {
2755                    this._currentChapterObject = null, this._chapterInfo = null, this._isInState(y.Chapter) && this._playerPlugin.trackChapterSkip(), this._setState(y.Chapter, !1);
2756                }, e.prototype._cmdError = function (a) {
2757                    var b = a.getData(I);b || (b = "unknown_error_id"), this._playerPlugin.trackVideoPlayerError(b);
2758                }, e.prototype._cmdBitrate = function (a) {
2759                    this._playerPlugin.trackBitrateChange();
2760                }, e.prototype._cmdTimedMetadataUpdate = function (a) {
2761                    var b = a.getData(G),
2762                        c = b.getValue(h.MEDIAINFO_KEY_TIMEDMETADATA);this._playerPlugin.trackTimedMetadata(c);
2763                }, e.prototype._processRule = function (a, b) {
2764                    return this._ruleEngine.processRule(a, b);
2765                }, e.prototype._setupRules = function () {
2766                    this._ruleEngine.registerEnterExitAction(this._cmdEnterAction, this._cmdExitAction), this._ruleEngine.registerRule(z.SessionStart, "API:trackSessionStart", [u.createPredicate(this._isPlatformTrackingSupported, !0, A.
2766ErrUnSupportedPlatform), u.createPredicate(this._isInSession, !1, A.ErrInSession), u.createPredicate(this._isValidMediaObject, !0, A.ErrInvalidMediaObject)], [this._cmdConfigure, this._cmdSessionStart], this), this._ruleEngine.registerRule(z.SessionEnd, "API:trackSessionEnd", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession)], [this._cmdHandleMediaComplete, this._cmdAdSkip, this._cmdAdBreakComplete, this._cmdChapterSkip, this._cmdVideoEnd, this._cmdSessionEnd], this), this._ruleEngine.registerRule(z.VideoComplete, "API:trackComplete", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia)], [this._cmdAdSkip, this._cmdAdBreakComplete, this._cmdChapterSkip, this._cmdVideoEnd], this), this._ruleEngine.registerRule(z.Error, "API:trackError", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia)], [this._cmdError], this), this._ruleEngine.registerRule(z.Play, "API:trackPlay", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia)], [this._cmdSeekComplete, this._cmdBufferComplete, this._cmdPlay], this), this._ruleEngine.registerRule(z.Pause, "API:trackPause", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia), u.createPredicate(this._isInBuffer, !1, A.ErrInBuffer), u.createPredicate(this._isInSeek, !1, A.ErrInSeek)], [this._cmdPause], this), this._ruleEngine.registerRule(z.BufferStart, "API:trackEvent(BufferStart)", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia), u.createPredicate(this._isInBuffer, !1, A.ErrInBuffer), u.createPredicate(this._isInSeek, !1, A.ErrInSeek)], [this._cmdBufferStart], this), this._ruleEngine.registerRule(z.BufferComplete, "API:trackEvent(BufferComplete)", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia), u.createPredicate(this._isInBuffer, !0, A.ErrNotInBuffer)], [this._cmdBufferComplete], this), this._ruleEngine.registerRule(z.SeekStart, "API:trackEvent(SeekStart)", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia), u.createPredicate(this._isInSeek, !1, A.ErrInSeek), u.createPredicate(this._isInBuffer, !1, A.ErrInBuffer)], [this._cmdSeekStart], this), this._ruleEngine.registerRule(z.SeekComplete, "API:trackEvent(SeekComplete)", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia), u.createPredicate(this._isInSeek, !0, A.ErrNotInSeek)], [this._cmdSeekComplete], this), this._ruleEngine.registerRule(z.AdBreakStart, "API:trackEvent(AdBreakStart)", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia), u.createPredicate(this._isValidAdBreakObject, !0, A.ErrInvalidAdBreakObject), u.createPredicate(this._isDifferentAdBreakObject, !0, A.ErrDuplicateAdBreakObject)], [this._cmdAdSkip, this._cmdAdBreakComplete, this._cmdAdBreakStart], this), this._ruleEngine.registerRule(z.AdBreakComplete, "API:trackEvent(AdBreakComplete)", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia), u.createPredicate(this._isInAdBreak, !0, A.ErrNotInAdBreak)], [this._cmdAdSkip, this._cmdAdBreakComplete], this), this._ruleEngine.registerRule(z.AdStart, "API:trackEvent(AdStart)", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia), u.createPredicate(this._isInAdBreak, !0, A.ErrNotInAdBreak), u.createPredicate(this._isValidAdObject, !0, A.ErrInvalidAdObject), u.createPredicate(this._isDifferentAdObject, !0, A.ErrDuplicateAdObject)], [this._cmdAdSkip, this._cmdAdStart], this), this._ruleEngine.registerRule(z.AdComplete, "API:trackEvent(AdComplete)", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.
2766ErrNotInMedia), u.createPredicate(this._isInAdBreak, !0, A.ErrNotInAdBreak), u.createPredicate(this._isInAd, !0, A.ErrNotInAd)], [this._cmdAdComplete], this), this._ruleEngine.registerRule(z.AdSkip, "API:trackEvent(AdSkip)", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia), u.createPredicate(this._isInAdBreak, !0, A.ErrNotInAdBreak), u.createPredicate(this._isInAd, !0, A.ErrNotInAd)], [this._cmdAdSkip], this), this._ruleEngine.registerRule(z.ChapterStart, "API:trackEvent(ChapterStart)", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia), u.createPredicate(this._isValidChapterObject, !0, A.ErrInvalidChapterObject), u.createPredicate(this._isDifferentChapterObject, !0, A.ErrDuplicateChapterObject)], [this._cmdChapterSkip, this._cmdChapterStart], this), this._ruleEngine.registerRule(z.ChapterComplete, "API:trackEvent(ChapterComplete)", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia), u.createPredicate(this._isInChapter, !0, A.ErrNotInChapter)], [this._cmdChapterComplete], this), this._ruleEngine.registerRule(z.ChapterSkip, "API:trackEvent(ChapterSkip)", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia), u.createPredicate(this._isInChapter, !0, A.ErrNotInChapter)], [this._cmdChapterSkip], this), this._ruleEngine.registerRule(z.BitrateChange, "API:trackEvent(BitrateChange)", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia)], [this._cmdBitrate], this), this._ruleEngine.registerRule(z.TimedMetadataUpdate, "API:trackEvent(TimedMetadataUpdate)", [u.createPredicate(this._isInSession, !0, A.ErrNotInSession), u.createPredicate(this._isInMedia, !0, A.ErrNotInMedia), u.createPredicate(this._isValidTimedMetadataObject, !0, A.ErrInvalidTimedMetadataObject)], [this._cmdTimedMetadataUpdate], this);
2767                }, e.prototype._configureAdobeAnalyticsPlugin = function () {
2768                    this._aaPlugin = new o(this._appMeasurement, new q(this));var a = new p();a.channel = this._config.channel ? this._config.channel : "", a.debugLogging = c.MediaHeartbeat._debugLogging || this._config.debugLogging, this._aaPlugin.configure(a), this._plugins.push(this._aaPlugin);
2769                }, e.prototype._configureAdobeHearbeatPlugin = function () {
2770                    var a = this._appMeasurement.visitor ? this._appMeasurement.visitor.marketingCloudOrgID : "";this._ahPlugin = new r(new t(this));var b = new s(this._config.trackingServer, a);b.debugLogging = c.MediaHeartbeat._debugLogging || this._config.debugLogging, b.ovp = this._config.ovp ? this._config.ovp : "", b.ssl = this._config.ssl, b.sdk = this._config.appVersion ? this._config.appVersion : "";var d = this._primetimeTVSDKVersion();d && d.length > 0 && (b.__primetime = !0, b.__psdkVersion = d), this._ahPlugin.configure(b), this._plugins.push(this._ahPlugin);
2771                }, e.prototype._configureVideoPlayerPlugin = function () {
2772                    this._playerPlugin = new l(new n(this));var a = new m();a.debugLogging = c.MediaHeartbeat._debugLogging || this._config.debugLogging, this._playerPlugin.configure(a), this._plugins.push(this._playerPlugin);
2773                }, e.prototype._configureOtherPlugins = function () {
2774                    if (c.plugins.nielsen && this._config.nielsenConfigKey && this._config.nielsenAppInfo) {
2775                        this._nielsenPlugin = new c.plugins.nielsen.NielsenPlugin(new c._MediaHeartbeatNielsenPluginDelegate(this));var a = new c.plugins.nielsen.NielsenPluginConfig();a.debugLogging = c.MediaHeartbeat._debugLogging || this._config.debugLogging, a.appInfo = this._config.nielsenAppInfo, a.configKey = this._config.nielsenConfigKey, this._nielsenPlugin.configure(a), this._plugins.push(this._nielsenPlugin);
2776                    }
2777                }, e.prototype._configureHeartbeat = function () {
2778                    var a = new j();a.debugLogging = c.MediaHeartbeat._debugLogging || this._config.debugLogging, this._heartbeat = new i(new k(this), this._plugins), this._heartbeat.configure(a);
2779                }, e.prototype._resetState = function () {
2780                    this._taskScheduler.clearTasks(), this._mediaState = {}, this._plugins = [], this._playerPlugin = null, this._aaPlugin = null, this._ahPlugin = null, this._nielsenPlugin = null, this._heartbeat = null, this._currentMediaObject = null, this._currentAdBreakObject = null, this._currentAdObject = null, this._currentChapterObject = null, this._videoInfo = null, this._adBreakInfo = null, this._adInfo = null, this._chapterInfo = null, this._prerollWaitEnabled = !0, this._prerollWaitTime = L, this._playReceived = !1, this._playTaskHandle = null, this._playAfterAdStart = !1;
2781                }, e.prototype._primetimeTVSDKVersion = function () {
2782                    return this._currentMediaObject ? this._currentMediaObject.getValue(J) : null;
2783                }, e.prototype._cleanContextData = function (a) {
2784                    if (null == a || "object" != (typeof a === 'undefined' ? 'undefined' : _typeof(a))) return null;var b = {};for (var c in a) {
2785                        if (a.hasOwnProperty(c)) {
2786                            var d = a[c];"number" != typeof d && "string" != typeof d && "boolean" != typeof d || (b[c] = d);
2787                        }
2788                    }return b;
2789                }, e.prototype._onDelegateError = function (a) {
2790                    this._logger.error(B, a.getMessage() + " | " + a.getDetails());
2791                };var y = { Session: 0, Media: 1, AdBreak: 2, Ad: 3, Chapter: 4, PlayPause: 5, Buffer: 6, Seek: 7, FPlayPause: 8 },
2792                    z = { SessionStart: 0, SessionEnd: 1, VideoComplete: 2, Play: 3, Pause: 4, Error: 5, AdBreakStart: 6, AdBreakComplete: 7, AdStart: 8, AdComplete: 9, AdSkip: 10, ChapterStart: 11, ChapterComplete: 12, ChapterSkip: 13, SeekStart: 14, SeekComplete: 15, BufferStart: 16, BufferComplete: 17, BitrateChange: 18, TimedMetadataUpdate: 19 },
2793                    A = { ErrUnSupportedPlatform: "MediaHeartbeat does not support tracking due to AppMeasurement or VisitorAPI not supporting the browser.", ErrNotInSession: 'MediaHeartbeat is not in active tracking session, call "API:trackSessionStart" to begin a new tracking session.', ErrInSession: 'MediaHeartbeat is in active tracking session, call "API:trackSessionEnd" to end current tracking session.', ErrN
2793otInMedia: 'MediaHeartbeat has completed tracking session, call "API:trackSessionEnd" first to end current session and then begin a new tracking session.', ErrInBuffer: 'MediaHeartbeat is tracking buffer events, call "API:trackEvent(BufferComplete)" first to stop tracking buffer events.', ErrNotInBuffer: 'MediaHeartbeat is not tracking buffer events, call "API:trackEvent(BufferStart)" before "API:trackEvent(BufferComplete)".', ErrInSeek: 'MediaHeartbeat is tracking seek events, call "API:trackEvent(SeekComplete)" first to stop tracking seek events.', ErrNotInSeek: 'MediaHeartbeat is not tracking seek events, call "API:trackEvent(SeekStart)" before "API:trackEvent(SeekComplete)".', ErrNotInAdBreak: 'MediaHeartbeat is not tracking any AdBreak, call "API:trackEvent(AdBreakStart)" to begin tracking AdBreak', ErrNotInAd: 'MediaHeartbeat is not tracking any Ad, call "API:trackEvent(AdStart)" to begin tracking Ad', ErrNotInChapter: 'MediaHeartbeat is not tracking any Chapter, call "API:trackEvent(ChapterStart)" to begin tracking Chapter', ErrInvalidMediaObject: 'MediaInfo passed into "API:trackSessionStart" is invalid.', ErrInvalidAdBreakObject: 'AdBreakInfo passed into "API:trackEvent(AdBreakStart)" is invalid.', ErrDuplicateAdBreakObject: 'MediaHeartbeat is currently tracking the AdBreak passed into "API:trackEvent(AdBreakStart)".', ErrInvalidAdObject: 'AdInfo passed into "API:trackEvent(AdStart)" is invalid.', ErrDuplicateAdObject: 'MediaHeartbeat is currently tracking the Ad passed into "API:trackEvent(AdStart)".', ErrInvalidChapterObject: 'ChapterInfo passed into "API:trackEvent(ChapterStart)" is invalid.', ErrDuplicateChapterObject: 'MediaHeartbeat is currently tracking the Chapter passed into "API:trackEvent(ChapterStart)".', ErrInvalidTimedMetadataObject: 'TimedMetadata passed into "API:trackEvent(TimedMetadataUpdate)" is invalid.' },
2794                    B = "MediaHeartbeat",
2795                    C = "key_media_object",
2796                    D = "key_adbreak_object",
2797                    E = "key_ad_object",
2798                    F = "key_chapter_object",
2799                    G = "key_timed_metadata_object",
2800                    H = "key_custom_metadata",
2801                    I = "key_error_id",
2802                    J = "a.__pttvsdkVersion",
2803                    K = "granular_ad_tracking",
2804                    L = 250;c._MediaHeartbeatErrorMessage = A, c.MediaHeartbeatDelegate = d, c.MediaHeartbeat = e, c.MediaHeartbeat._debugLogging = !1;
2805            }(a.ADB.core, a.ADB.va), a.ADB || (a.ADB = {}), a.ADB.core || (a.ADB.core = core), a.ADB.va || (a.ADB.va = va), a.ADB.va.plugins || (a.ADB.va.plugins = {});
2806        }(this);
2807    }).call(exports);
2808    return exports.ADB;
2809});
2810
2811},{}],4:[function(require,module,exports){
2812'use strict';
2813
2814var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
2815
2816function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
2817
2818var $ = require('jquery');
2819
2820var ResponsiveIframes = function () {
2821  function ResponsiveIframes() {
2822    _classCallCheck(this, ResponsiveIframes);
2823
2824    this.$container = $('.jsArticleFirst .js-responsive-iframes-container');
2825    this.$containerIframes = this.$container.find('iframe');
2826
2827    this.filterContainers();
2828    this.setAspectRatios();
2829    this.initializeEventListenners();
2830    this.resizeResponsiveIframes();
2831  }
2832
2833  /**
2834   * @description Remove the iframe containers that do not need to be reponsive
2835   * @return {Void} void
2836   */
2837
2838
2839  _createClass(ResponsiveIframes, [{
2840    key: 'filterContainers',
2841    value: function filterContainers() {
2842      var that = this;
2843      var indexToRemove = [];
2844      var lastIndex = 0;
2845
2846      // Loop on the containers
2847      this.$containerIframes.toArray().forEach(function (element, index) {
2848        var $element = $(element);
2849
2850        // Format height attr correctly
2851        if (element.height !== 'undefined') {
2852          $element.attr('height', element.height.replace('px', ''));
2853        }
2854        // Format width attr correctly
2855        if (element.width !== 'undefined') {
2856          $element.attr('width', element.width.replace('px', ''));
2857        }
2858        // Exclude if we don't have Height and Widht attr on the tag
2859        if (element.height.replace(/[0-9]+px|[0-9]+/gi, '') !== '' || element.width.replace(/[0-9]+px|[0-9]+/gi, '') !== '') {
2860          indexToRemove.push(index);
2861        }
2862        // Exclude if it's a facebook widget
2863        if (element.src.includes('www.facebook.com')) {
2864          indexToRemove.push(index);
2865        }
2866      });
2867
2868      // Loop on containers and remove by index
2869      lastIndex = indexToRemove.length - 1;
2870      for (var i = lastIndex; i >= 0; i--) {
2871        that.$containerIframes.splice(indexToRemove[i], 1);
2872      }
2873      return that.$containerIframes;
2874    }
2875
2876    /**
2877     * @description Set aspect ratio for each responsive iframes ( html attribute : data-aspectRatio )
2878     * @return {Void} void
2879     */
2880
2881  }, {
2882    key: 'setAspectRatios',
2883    value: function setAspectRatios() {
2884      // Figure out and save aspect ratio for each iframe
2885      this.$containerIframes.each(function () {
2886        $(this).data('aspectRatio', this.height / this.width)
2887        // and remove the hard coded width/height
2888        .removeAttr('height').removeAttr('width');
2889      });
2890    }
2891
2892    /**
2893     * @description Initialize events for responsive iframes
2894     * @return {Void} void
2895     */
2896
2897  }, {
2898    key: 'initializeEventListenners',
2899    value: function initializeEventListenners() {
2900      var that = this;
2901      // Event : resize event
2902      $(window).resize(function () {
2903        that.resizeResponsiveIframes();
2904      });
2905    }
2906
2907    /**
2908     * @description Resize responsive iframes based on their aspectRatio and their with
2909     * @return {Void} void
2910     */
2911
2912  }, {
2913    key: 'resizeResponsiveIframes',
2914    value: function resizeResponsiveIframes() {
2915      var that = this;
2916      // Loop on containers
2917      this.$containerIframes.each(function () {
2918        var $element = $(this);
2919        var newWidth = that.$container.width();
2920        var newHeight = newWidth * $element.data('aspectRatio');
2921
2922        $element.width(newWidth).height(newHeight);
2923      });
2924    }
2925  }]);
2926
2927  return ResponsiveIframes;
2928}();
2929
2930module.exports = ResponsiveIframes;
2931
2932},{"jquery":43}],5:[function(require,module,exports){
2933'use strict';
2934
2935var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
2936
2937function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
2938
2939var Twitter = function () {
2940  function Twitter() {
2941    _classCallCheck(this, Twitter);
2942
2943    this.createTweet();
2944  }
2945
2946  _createClass(Twitter, [{
2947    key: 'createTweet',
2948    value: function createTweet() {
2949      var _this = this;
2950
2951      window.twttr.ready(function (twttr) {
2952        _this.createArticleTweet();
2953      });
2954    }
2955  }, {
2956    key: 'createArticleTweet',
2957    value: function createArticleTweet() {
2958      $('[data-tweet-id]').each(function () {
2959        var widget = $(this).find('twitter-widget');
2960
2961        // Do not create a tweet if there is already one displayed
2962        if (widget.length) {
2963          return;
2964        }
2965
2966        twttr.widgets.createTweet(this.getAttribute('data-tweet-id'), this, { linkColor: '#55acee' });
2967      });
2968    }
2969  }]);
2970
2971  return Twitter;
2972}();
2973
2974module.exports = Twitter;
2975
2976},{}],6:[function(require,module,exports){
2977'use strict';
2978
2979var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
2980
2981function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
2982
2983var VideoJsPlayer = require('./VideoJsPlayer');
2984var AdobeVideoTracking = require('../third-parts/adobe/AdobeVideoTracking');
2985
2986var PfpVideoJsFallback = function () {
2987  function PfpVideoJsFallback(pfpElement, fallbackElement, videoJsOptions, options) {
2988    _classCallCheck(this, PfpVideoJsFallback);
2989
2990    this.pfpElement = pfpElement;
2991    this.fallbackElement = fallbackElement;
2992    this.videoJsOptions = videoJsOptions;
2993    this.options = options || {};
2994    this.fallbackStarted = false;
2995    this.pfpTrackingStarted = false;
2996    this.pfpAttached = false;
2997    this.pfpTracking = null;
2998    this.errorTimer = null;
2999    this.startupTimer = null;
3000    this.playerPoll = null;
3001  }
3002
3003  _createClass(PfpVideoJsFallback, [{
3004    key: 'init',
3005    value: function init() {
3006      this.fallbackElement.style.display = 'none';
3007      window.addEventListener('pfp-ready', this.attachToPfp.bind(this));
3008      window.addEventListener('en-player-ready', this.onPfpReady.bind(this));
3009      window.addEventListener('en-player-video-play', this.onPfpPlay.bind(this));
3010
3011      this.playerPoll = window.setInterval(this.attachToPfp.bind(this), 250);
3012      this.startupTimer = window.setTimeout(this.startFallback.bind(this, new Error('PFP startup timeout')), this.options.startupTimeout || 15000);
3013      this.attachToPfp();
3014    }
3015  }, {
3016    key: 'attachToPfp',
3017    value: function attachToPfp() {
3018      if (this.fallbackStarted || this.pfpAttached || !window.YTPlayer || typeof window.YTPlayer.addEventListener !== 'function') {
3019        return;
3020      }
3021
3022      this.pfpAttached = true;
3023      window.clearInterval(this.playerPoll);
3024      this.playerPoll = null;
3025      window.YTPlayer.addEventListener('onError', this.onPfpError.bind(this));
3026      window.YTPlayer.addEventListener('onReady', this.onPfpReady.bind(this));
3027
3028      try {
3029        var videoData = window.YTPlayer.getVideoData();
3030        if (videoData && videoData.video_id) {
3031          this.onPfpReady();
3032        }
3033      } catch (error) {
3034        // The YouTube object exists before its iframe is ready. Its onReady event handles this case.
3035      }
3036    }
3037  }, {
3038    key: 'onPfpReady',
3039    value: function onPfpReady() {
3040      if (this.fallbackStarted) {
3041        return;
3042      }
3043
3044      window.clearTimeout(this.startupTimer);
3045      this.attachToPfp();
3046      if (!this.pfpTrackingStarted && window.YTPlayer) {
3047        this.pfpTrackingStarted = true;
3048        var adapter = {
3049          getPosition: function getPosition() {
3050            return window.YTPlayer && window.YTPlayer.getCurrentTime ? window.YTPlayer.getCurrentTime() : 0;
3051          },
3052          getSetupTime: function getSetupTime() {
3053            return 0;
3054          }
3055        };
3056        this.pfpTracking = new AdobeVideoTracking(adapter, 'pfp');
3057        window.adobeVideoTracking = this.pfpTracking;
3058      }
3059    }
3060  }, {
3061    key: 'onPfpPlay',
3062    value: function onPfpPlay() {
3063      window.clearTimeout(this.errorTimer);
3064      this.onPfpReady();
3065    }
3066  }, {
3067    key: 'onPfpError',
3068    value: function onPfpError(error) {
3069      window.clearTimeout(this.errorTimer);
3070      this.errorTimer = window.setTimeout(this.startFallback.bind(this, error || new Error('PFP playback error')), typeof this.options.errorDelay === 'number' ? this.options.errorDelay : 1500);
3071    }
3072  }, {
3073    key: 'startFallback',
3074    value: function startFallback(reason) {
3075      if (this.fallbackStarted) {
3076        return;
3077      }
3078      this.fallbackStarted = true;
3079      window.clearInterval(this.playerPoll);
3080      window.clearTimeout(this.startupTimer);
3081      window.clearTimeout(this.errorTimer);
3082
3083      console.warn('PFP unavailable; switching to Video.js', reason);
3084      this.pfpElement.style.display = 'none';
3085      this.pfpElement.innerHTML = '';
3086      this.fallbackElement.style.display = '';
3087      if (this.pfpTracking && typeof this.pfpTracking.dispose === 'function') {
3088        this.pfpTracking.dispose();
3089      }
3090      if (window.digitalData && window.digitalData.video) {
3091        window.digitalData.video.playername = 'videojs';
3092      }
3093
3094      var videoElement = this.fallbackElement.querySelector('video');
3095      var optionsPromise = typeof this.videoJsOptions === 'function' ? Promise.resolve().then(this.videoJsOptions) : Promise.resolve(this.videoJsOptions);
3096
3097      optionsPromise.then(function (videoJsOptions) {
3098        var player = new VideoJsPlayer(videoElement, videoJsOptions);
3099        return player.init();
3100      }).then(function (instance) {
3101        window.adobeVideoTracking = new AdobeVideoTracking(instance, 'videojs');
3102      }).catch(function (error) {
3103        console.error('VIDEO.JS - fallback initialisation failed', error);
3104      });
3105    }
3106  }]);
3107
3108  return PfpVideoJsFallback;
3109}();
3110
3111module.exports = PfpVideoJsFallback;
3112
3113},{"../third-parts/adobe/AdobeVideoTracking":2,"./VideoJsPlayer":8}],7:[function(require,module,exports){
3114'use strict';
3115
3116var IMA_SDK_URL = 'https://imasdk.googleapis.com/js/sdkloader/ima3.js';
3117var IMA_SDK_ID = 'google-ima-sdk';
3118
3119var imaSdkPromise = void 0;
3120
3121/**
3122 * GAM/IMA plumbing shared by both ad paths: Video.js (VideoJsPlayer) and the
3123 * YouTube PFP player (PfpImaAds).
3124 *
3125 * This module deliberately has NO dependency on video.js: the PFP bundle
3126 * (youtube-pfp.min.js, ~12 kB) loads it on every page carrying a PFP player,
3127 * and pulling the Video.js distribution in would grow it to several megabytes.
3128 */
3129
3130function loadImaSdk() {
3131  if (window.google && window.google.ima) {
3132    return Promise.resolve();
3133  }
3134  if (imaSdkPromise) {
3135    return imaSdkPromise;
3136  }
3137
3138  imaSdkPromise = new Promise(function (resolve, reject) {
3139    var existingScript = document.getElementById(IMA_SDK_ID);
3140    if (existingScript) {
3141      existingScript.addEventListener('load', resolve);
3142      existingScript.addEventListener('error', reject);
3143      return;
3144    }
3145
3146    var script = document.createElement('script');
3147    script.id = IMA_SDK_ID;
3148    script.async = true;
3149    script.src = IMA_SDK_URL;
3150    script.addEventListener('load', resolve);
3151    script.addEventListener('error', reject);
3152    document.head.appendChild(script);
3153  });
3154
3155  return imaSdkPromise;
3156}
3157
3158function getAdvertisingData() {
3159  if (!window.enAds || !window.enAds.data) {
3160    return null;
3161  }
3162
3163  return window.enAds.data;
3164}
3165
3166/**
3167 * Builds the GAM VAST URL.
3168 *
3169 * `options` lets the PFP path reuse this builder, mirroring what front_euronews
3170 * does in YoutubePfpImaAdapter.buildImaAdTagUrl():
3171 *   - playerType:        value of the `player_type` custom param (default 'videojs')
3172 *   - extraParams:       extra query params, e.g. 'vpos=preroll', 'ad_rule=0'
3173 *   - extraCustomParams: extra cust_params entries, e.g. 'yt_embed_ima=1'
3174 */
3175function buildVastUrl(article, embed, options) {
3176  var advertising = getAdvertisingData();
3177  if (!advertising || !advertising.level1 || !advertising.adUnit) {
3178    console.warn('VAST - no enAds.data.level1/adUnit; the player runs without ads');
3179    return null;
3180  }
3181
3182  var settings = options || {};
3183  var keyValues = advertising.keyValues || {};
3184  var customParams = [];
3185  Object.keys(keyValues).forEach(function (key) {
3186    // `lng` and `tags` are re-added below from the article, so skip them here to
3187    // avoid sending the key twice with two different values.
3188    if (key !== 'lng' && key !== 'tags') {
3189      customParams.push(key + '=' + keyValues[key]);
3190    }
3191  });
3192  customParams.push('lng=' + (article && article.lang || document.documentElement.lang || 'en'));
3193  customParams.push('dfpt=' + (window.location.search.indexOf('dfpt=') !== -1 ? 'true' : 'false'));
3194  customParams.push('player_type=' + (settings.playerType || 'videojs'));
3195
3196  var articleTags = article && article.keywords ? Array.isArray(article.keywords) ? article.keywords.map(function (keyword) {
3197    return keyword.urlSafeValue || keyword.id || '';
3198  }).filter(Boolean).join(',') : article.keywords : '';
3199  var tags = articleTags || keyValues.tags || '';
3200  if (tags) {
3201    customParams.push('tags=' + tags);
3202  }
3203
3204  (settings.extraCustomParams || []).forEach(function (entry) {
3205    return customParams.push(entry);
3206  });
3207
3208  var adUnit = '/6458/' + advertising.level1 + '/' + advertising.adUnit;
3209  var params = ['env=vp', 'gdfp_req=1', 'impl=s', 'unviewed_position_start=1', 'output=vast', 'iu=' + encodeURIComponent(adUnit), 'sz=480x360', 'url=' + encodeURIComponent(window.location.href), 'correlator=' + Math.floor(Math.random() * 1000000), 'description_url=' + encodeURIComponent(window.location.href), 'hl=' + encodeURIComponent(article && article.lang || document.documentElement.lang || 'en'), 'cust_params=' + encodeURIComponent(customParams.join('&')), 'embed=' + (embed ? '1' : '0')];
3210
3211  (settings.extraParams || []).forEach(function (entry) {
3212    return params.push(entry);
3213  });
3214
3215  if (article && article.program) {
3216    var program = article.program.urlSafeValue || article.program;
3217    params.push('prog=' + encodeURIComponent(program));
3218  }
3219
3220  return 'https://pubads.g.doubleclick.net/gampad/ads?' + params.join('&');
3221}
3222
3223module.exports = {
3224  buildVastUrl: buildVastUrl,
3225  getAdvertisingData: getAdvertisingData,
3226  loadImaSdk: loadImaSdk
3227};
3228
3229},{}],8:[function(require,module,exports){
3230'use strict';
3231
3232var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
3233
3234function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
3235
3236var videojs = require('video.js');
3237var french = require('video.js/dist/lang/fr.json');
3238var VastAds = require('./VastAds');
3239require('videojs-contrib-ads');
3240require('videojs-ima');
3241
3242videojs.addLanguage('fr', french);
3243
3244function dispatch(name, detail) {
3245  window.dispatchEvent(new CustomEvent(name, { detail: detail || {} }));
3246}
3247
3248function secureUrl(url) {
3249  return typeof url === 'string' ? url.replace(/^http:\/\//, 'https://') : '';
3250}
3251
3252function sourceType(url) {
3253  return url.indexOf('.m3u8') !== -1 ? 'application/x-mpegURL' : 'video/mp4';
3254}
3255
3256function articleSources(article) {
3257  var videos = article && Array.isArray(article.videos) ? article.videos : [];
3258  return videos.map(function (video) {
3259    return secureUrl(video.url || video.file || '');
3260  }).filter(Boolean).map(function (url) {
3261    return { src: url, type: sourceType(url) };
3262  });
3263}
3264
3265function articlePoster(article) {
3266  var images = article && Array.isArray(article.images) ? article.images : [];
3267  if (!images.length || !images[0].url) {
3268    return '';
3269  }
3270
3271  return secureUrl(images[0].url).replace(/{{w}}/g, '960').replace(/{{h}}/g, '540');
3272}
3273
3274function playerLanguage() {
3275  var language = (document.documentElement.lang || 'en').split('-')[0];
3276  return language === 'fr' ? 'fr' : 'en';
3277}
3278
3279var VideoJsPlayer = function () {
3280  function VideoJsPlayer(element, options) {
3281    _classCallCheck(this, VideoJsPlayer);
3282
3283    this.element = element;
3284    this.options = options || {};
3285    this.player = null;
3286    this.setupStartedAt = Date.now();
3287    this.setupTime = 0;
3288    this.contentLoaded = false;
3289    this.adDisplayContainerInitialized = false;
3290  }
3291
3292  _createClass(VideoJsPlayer, [{
3293    key: 'init',
3294    value: function init() {
3295      var _this = this;
3296
3297      var config = this.options;
3298      var language = playerLanguage();
3299      var sources = config.sources || articleSources(config.article);
3300      var vastUrl = config.ads === false ? null : VastAds.buildVastUrl(config.article, config.embed);
3301      var adsReady = vastUrl ? VastAds.loadImaSdk().catch(function (error) {
3302        console.error('VIDEO.JS - IMA SDK unavailable; playing without ads', error);
3303        return null;
3304      }) : Promise.resolve(null);
3305
3306      return adsReady.then(function () {
3307        _this.player = videojs(_this.element, {
3308          autoplay: !!config.autoplay,
3309          controls: config.controls !== false,
3310          fluid: config.fluid !== false,
3311          language: language,
3312          liveui: !!config.live,
3313          muted: !!config.muted,
3314          playsinline: true,
3315          poster: config.poster || articlePoster(config.article),
3316          preload: config.autoplay ? 'auto' : 'metadata',
3317          sources: sources
3318        });
3319
3320        if (vastUrl && window.google && window.google.ima && typeof _this.player.ima === 'function') {
3321          try {
3322            _this.player.ima({
3323              adTagUrl: vastUrl,
3324              adLabel: language === 'fr' ? 'Publicité' : 'Advert
3324isement',
3325              adLabelNofN: language === 'fr' ? 'sur' : 'of',
3326              locale: language,
3327              debug: false,
3328              contribAdsSettings: { timeout: 5000, prerollTimeout: 3000 }
3329            });
3330            _this.bindAdDisplayContainer();
3331            _this.bindAdEvents();
3332          } catch (error) {
3333            console.error('VIDEO.JS - IMA initialisation failed; playing without ads', error);
3334          }
3335        }
3336
3337        _this.bindContentEvents();
3338        return new Promise(function (resolve) {
3339          _this.player.ready(function () {
3340            _this.setupTime = Date.now() - _this.setupStartedAt;
3341            dispatch('video-ready', { player_instance: _this });
3342            resolve(_this);
3343          });
3344        });
3345      });
3346    }
3347  }, {
3348    key: 'bindAdDisplayContainer',
3349    value: function bindAdDisplayContainer() {
3350      var _this2 = this;
3351
3352      var initialise = function initialise() {
3353        if (_this2.adDisplayContainerInitialized || !_this2.player || !_this2.player.ima) {
3354          return;
3355        }
3356        _this2.adDisplayContainerInitialized = true;
3357        try {
3358          _this2.player.ima.initializeAdDisplayContainer();
3359        } catch (error) {
3360          console.error('VIDEO.JS - IMA display container initialisation failed', error);
3361        }
3362      };
3363
3364      this.element.addEventListener('pointerdown', initialise, { once: true });
3365      this.element.addEventListener('touchend', initialise, { once: true });
3366      this.element.addEventListener('click', initialise, { once: true });
3367    }
3368  }, {
3369    key: 'bindAdEvents',
3370    value: function bindAdEvents() {
3371      this.player.on('ads-ad-started', function () {
3372        return dispatch('video-ad-play', { play: true });
3373      });
3374      this.player.on('ads-ad-ended', function () {
3375        return dispatch('video-ad-complete', { complete: true });
3376      });
3377      this.player.on('adskip', function () {
3378        return dispatch('video-ad-skipped', { skipped: true });
3379      });
3380      this.player.on('adpause', function () {
3381        return dispatch('video-ad-pause', { play: false });
3382      });
3383      this.player.on('adserror', function (error) {
3384        console.error('VIDEO.JS - IMA ad error; resuming content', error);
3385      });
3386    }
3387  }, {
3388    key: 'bindContentEvents',
3389    value: function bindContentEvents() {
3390      var _this3 = this;
3391
3392      this.player.on('play', function () {
3393        var eventName = _this3.contentLoaded ? 'video-play' : 'video-load';
3394        _this3.contentLoaded = true;
3395        dispatch(eventName, { play: true, load: true });
3396      });
3397      this.player.on('pause', function () {
3398        return dispatch('video-pause', { play: false });
3399      });
3400      this.player.on('ended', function () {
3401        return dispatch('video-complete', { complete: true });
3402      });
3403      this.player.on('error', function () {
3404        var error = _this3.player.error();
3405        var currentSource = _this3.player.currentSrc();
3406        var fallbackSource = currentSource.replace('.qat.local', '').replace('.trn.local', '').replace('.int.local', '');
3407
3408        if (fallbackSource !== currentSource) {
3409          _this3.player.src({ src: fallbackSource, type: sourceType(fallbackSource) });
3410          _this3.player.play();
3411          return;
3412        }
3413        console.error('VIDEO.JS - playback error', error);
3414      });
3415    }
3416  }, {
3417    key: 'getPosition',
3418    value: function getPosition() {
3419      return this.player ? this.player.currentTime() || 0 : 0;
3420    }
3421  }, {
3422    key: 'getSetupTime',
3423    value: function getSetupTime() {
3424      return this.setupTime;
3425    }
3426  }, {
3427    key: 'qoe',
3428    value: function qoe() {
3429      return { setupTime: this.setupTime };
3430    }
3431  }, {
3432    key: 'play',
3433    value: function play() {
3434      return this.player && this.player.play();
3435    }
3436  }, {
3437    key: 'pause',
3438    value: function pause() {
3439      if (this.player) {
3440        this.player.pause();
3441      }
3442    }
3443  }, {
3444    key: 'dispose',
3445    value: function dispose() {
3446      if (this.player && !this.player.isDisposed()) {
3447        this.player.dispose();
3448      }
3449    }
3450  }]);
3451
3452  return VideoJsPlayer;
3453}();
3454
3455VideoJsPlayer.articleSources = articleSources;
3456VideoJsPlayer.articlePoster = articlePoster;
3457VideoJsPlayer.buildVastUrl = VastAds.buildVastUrl;
3458VideoJsPlayer.loadImaSdk = VastAds.loadImaSdk;
3459VideoJsPlayer.secureUrl = secureUrl;
3460VideoJsPlayer.sourceType = sourceType;
3461
3462module.exports = VideoJsPlayer;
3463
3464},{"./VastAds":7,"video.js":50,"video.js/dist/lang/fr.json":49,"videojs-contrib-ads":51,"videojs-ima":52}],9:[function(require,module,exports){
3465function _extends() {
3466  return module.exports = _extends = Object.assign ? Object.assign.bind() : function (n) {
3467    for (var e = 1; e < arguments.length; e++) {
3468      var t = arguments[e];
3469      for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
3470    }
3471    return n;
3472  }, module.exports.__esModule = true, module.exports["default"] = module.exports, _extends.apply(null, arguments);
3473}
3474module.exports = _extends, module.exports.__esModule = true, module.exports["default"] = module.exports;
3475},{}],10:[function(require,module,exports){
3476function _interopRequireDefault(e) {
3477  return e && e.__esModule ? e : {
3478    "default": e
3479  };
3480}
3481module.exports = _interopRequireDefault, module.exports.__esModule = true, module.exports["default"] = module.exports;
3482},{}],11:[function(require,module,exports){
3483"use strict";
3484
3485var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
3486
3487Object.defineProperty(exports, "__esModule", {
3488  value: true
3489});
3490exports.reverseBytes = exports.sliceBytes = exports.bytesMatch = exports.concatTypedArrays = exports.stringToBytes = exports.bytesToString = exports.numberToBytes = exports.bytesToNumber = exports.IS_LITTLE_ENDIAN = exports.IS_BIG_ENDIAN = exports.ENDIANNESS = exports.toBinaryString = exports.toHexString = exports.toUint8 = exports.isTypedArray = exports.isArrayBufferView = exports.padStart = exports.countBytes = exports.countBits = void 0;
3491
3492var _window = _interopRequireDefault(require("global/window"));
3493
3494// const log2 = Math.log2 ? Math.log2 : (x) => (Math.log(x) / Math.log(2));
3495var repeat = function repeat(str, len) {
3496  var acc = '';
3497
3498  while (len--) {
3499    acc += str;
3500  }
3501
3502  return acc;
3503}; // count the number of bits it would take to represent a number
3504// we used to do this with log2 but BigInt does not support builtin math
3505// Math.ceil(log2(x));
3506
3507
3508var countBits = function countBits(x) {
3509  return x.toString(2).length;
3510}; // count the number of whole bytes it would take to represent a number
3511
3512
3513exports.countBits = countBits;
3514
3515var countBytes = function countBytes(x) {
3516  return Math.ceil(countBits(x) / 8);
3517};
3518
3519exports.countBytes = countBytes;
3520
3521var padStart = function padStart(b, len, str) {
3522  if (str === void 0) {
3523    str = ' ';
3524  }
3525
3526  return (repeat(str, len) + b.toString()).slice(-len);
3527};
3528
3529exports.padStart = padStart;
3530
3531var isArrayBufferView = function isArrayBufferView(obj) {
3532  if (ArrayBuffer.isView === 'function') {
3533    return ArrayBuffer.isView(obj);
3534  }
3535
3536  return obj && obj.buffer instanceof ArrayBuffer;
3537};
3538
3539exports.isArrayBufferView = isArrayBufferView;
3540
3541var isTypedArray = function isTypedArray(obj) {
3542  return isArrayBufferView(obj);
3543};
3544
3545exports.isTypedArray = isTypedArray;
3546
3547var toUint8 = function toUint8(bytes) {
3548  if (bytes instanceof Uint8Array) {
3549    return bytes;
3550  }
3551
3552  if (!Array.isArray(bytes) && !isTypedArray(bytes) && !(bytes instanceof ArrayBuffer)) {
3553    // any non-number or NaN leads to empty uint8array
3554    // eslint-disable-next-line
3555    if (typeof bytes !== 'number' || typeof bytes === 'number' && bytes !== bytes) {
3556      bytes = 0;
3557    } else {
3558      bytes = [bytes];
3559    }
3560  }
3561
3562  return new Uint8Array(bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0);
3563};
3564
3565exports.toUint8 = toUint8;
3566
3567var toHexString = function toHexString(bytes) {
3568  bytes = toUint8(bytes);
3569  var str = '';
3570
3571  for (var i = 0; i < bytes.length; i++) {
3572    str += padStart(bytes[i].toString(16), 2, '0');
3573  }
3574
3575  return str;
3576};
3577
3578exports.toHexString = toHexString;
3579
3580var toBinaryString = function toBinaryString(bytes) {
3581  bytes = toUint8(bytes);
3582  var str = '';
3583
3584  for (var i = 0; i < bytes.length; i++) {
3585    str += padStart(bytes[i].toString(2), 8, '0');
3586  }
3587
3588  return str;
3589};
3590
3591exports.toBinaryString = toBinaryString;
3592var BigInt = _window.default.BigInt || Number;
3593var BYTE_TABLE = [BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')];
3594
3595var ENDIANNESS = function () {
3596  var a = new Uint16Array([0xFFCC]);
3597  var b = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
3598
3599  if (b[0] === 0xFF) {
3600    return 'big';
3601  }
3602
3603  if (b[0] === 0xCC) {
3604    return 'little';
3605  }
3606
3607  return 'unknown';
3608}();
3609
3610exports.ENDIANNESS = ENDIANNESS;
3611var IS_BIG_ENDIAN = ENDIANNESS === 'big';
3612exports.IS_BIG_ENDIAN = IS_BIG_ENDIAN;
3613var IS_LITTLE_ENDIAN = ENDIANNESS === 'little';
3614exports.IS_LITTLE_ENDIAN = IS_LITTLE_ENDIAN;
3615
3616var bytesToNumber = function bytesToNumber(bytes, _temp) {
3617  var _ref = _temp === void 0 ? {} : _temp,
3618      _ref$signed = _ref.signed,
3619      signed = _ref$signed === void 0 ? false : _ref$signed,
3620      _ref$le = _ref.le,
3621      le = _ref$le === void 0 ? false : _ref$le;
3622
3623  bytes = toUint8(bytes);
3624  var fn = le ? 'reduce' : 'reduceRight';
3625  var obj = bytes[fn] ? bytes[fn] : Array.prototype[fn];
3626  var number = obj.call(bytes, function (total, byte, i) {
3627    var exponent = le ? i : Math.abs(i + 1 - bytes.length);
3628    return total + BigInt(byte) * BYTE_TABLE[exponent];
3629  }, BigInt(0));
3630
3631  if (signed) {
3632    var max = BYTE_TABLE[bytes.length] / BigInt(2) - BigInt(1);
3633    number = BigInt(number);
3634
3635    if (number > max) {
3636      number -= max;
3637      number -= max;
3638      number -= BigInt(2);
3639    }
3640  }
3641
3642  return Number(number);
3643};
3644
3645exports.bytesToNumber = bytesToNumber;
3646
3647var numberToBytes = function numberToBytes(number, _temp2) {
3648  var _ref2 = _temp2 === void 0 ? {}
3648 : _temp2,
3649      _ref2$le = _ref2.le,
3650      le = _ref2$le === void 0 ? false : _ref2$le;
3651
3652  // eslint-disable-next-line
3653  if (typeof number !== 'bigint' && typeof number !== 'number' || typeof number === 'number' && number !== number) {
3654    number = 0;
3655  }
3656
3657  number = BigInt(number);
3658  var byteCount = countBytes(number);
3659  var bytes = new Uint8Array(new ArrayBuffer(byteCount));
3660
3661  for (var i = 0; i < byteCount; i++) {
3662    var byteIndex = le ? i : Math.abs(i + 1 - bytes.length);
3663    bytes[byteIndex] = Number(number / BYTE_TABLE[i] & BigInt(0xFF));
3664
3665    if (number < 0) {
3666      bytes[byteIndex] = Math.abs(~bytes[byteIndex]);
3667      bytes[byteIndex] -= i === 0 ? 1 : 2;
3668    }
3669  }
3670
3671  return bytes;
3672};
3673
3674exports.numberToBytes = numberToBytes;
3675
3676var bytesToString = function bytesToString(bytes) {
3677  if (!bytes) {
3678    return '';
3679  } // TODO: should toUint8 handle cases where we only have 8 bytes
3680  // but report more since this is a Uint16+ Array?
3681
3682
3683  bytes = Array.prototype.slice.call(bytes);
3684  var string = String.fromCharCode.apply(null, toUint8(bytes));
3685
3686  try {
3687    return decodeURIComponent(escape(string));
3688  } catch (e) {// if decodeURIComponent/escape fails, we are dealing with partial
3689    // or full non string data. Just return the potentially garbled string.
3690  }
3691
3692  return string;
3693};
3694
3695exports.bytesToString = bytesToString;
3696
3697var stringToBytes = function stringToBytes(string, stringIsBytes) {
3698  if (typeof string !== 'string' && string && typeof string.toString === 'function') {
3699    string = string.toString();
3700  }
3701
3702  if (typeof string !== 'string') {
3703    return new Uint8Array();
3704  } // If the string already is bytes, we don't have to do this
3705  // otherwise we do this so that we split multi length characters
3706  // into individual bytes
3707
3708
3709  if (!stringIsBytes) {
3710    string = unescape(encodeURIComponent(string));
3711  }
3712
3713  var view = new Uint8Array(string.length);
3714
3715  for (var i = 0; i < string.length; i++) {
3716    view[i] = string.charCodeAt(i);
3717  }
3718
3719  return view;
3720};
3721
3722exports.stringToBytes = stringToBytes;
3723
3724var concatTypedArrays = function concatTypedArrays() {
3725  for (var _len = arguments.length, buffers = new Array(_len), _key = 0; _key < _len; _key++) {
3726    buffers[_key] = arguments[_key];
3727  }
3728
3729  buffers = buffers.filter(function (b) {
3730    return b && (b.byteLength || b.length) && typeof b !== 'string';
3731  });
3732
3733  if (buffers.length <= 1) {
3734    // for 0 length we will return empty uint8
3735    // for 1 length we return the first uint8
3736    return toUint8(buffers[0]);
3737  }
3738
3739  var totalLen = buffers.reduce(function (total, buf, i) {
3740    return total + (buf.byteLength || buf.length);
3741  }, 0);
3742  var tempBuffer = new Uint8Array(totalLen);
3743  var offset = 0;
3744  buffers.forEach(function (buf) {
3745    buf = toUint8(buf);
3746    tempBuffer.set(buf, offset);
3747    offset += buf.byteLength;
3748  });
3749  return tempBuffer;
3750};
3751/**
3752 * Check if the bytes "b" are contained within bytes "a".
3753 *
3754 * @param {Uint8Array|Array} a
3755 *        Bytes to check in
3756 *
3757 * @param {Uint8Array|Array} b
3758 *        Bytes to check for
3759 *
3760 * @param {Object} options
3761 *        options
3762 *
3763 * @param {Array|Uint8Array} [offset=0]
3764 *        offset to use when looking at bytes in a
3765 *
3766 * @param {Array|Uint8Array} [mask=[]]
3767 *        mask to use on bytes before comparison.
3768 *
3769 * @return {boolean}
3770 *         If all bytes in b are inside of a, taking into account
3771 *         bit masks.
3772 */
3773
3774
3775exports.concatTypedArrays = concatTypedArrays;
3776
3777var bytesMatch = function bytesMatch(a, b, _temp3) {
3778  var _ref3 = _temp3 === void 0 ? {} : _temp3,
3779      _ref3$offset = _ref3.offset,
3780      offset = _ref3$offset === void 0 ? 0 : _ref3$offset,
3781      _ref3$mask = _ref3.mask,
3782      mask = _ref3$mask === void 0 ? [] : _ref3$mask;
3783
3784  a = toUint8(a);
3785  b = toUint8(b); // ie 11 does not support uint8 every
3786
3787  var fn = b.every ? b.every : Array.prototype.every;
3788  return b.length && a.length - offset >= b.length && // ie 11 doesn't support every on uin8
3789  fn.call(b, function (bByte, i) {
3790    var aByte = mask[i] ? mask[i] & a[offset + i] : a[offset + i];
3791    return bByte === aByte;
3792  });
3793};
3794
3795exports.bytesMatch = bytesMatch;
3796
3797var sliceBytes = function sliceBytes(src, start, end) {
3798  if (Uint8Array.prototype.slice) {
3799    return Uint8Array.prototype.slice.call(src, start, end);
3800  }
3801
3802  return new Uint8Array(Array.prototype.slice.call(src, start, end));
3803};
3804
3805exports.sliceBytes = sliceBytes;
3806
3807var reverseBytes = function reverseBytes(src) {
3808  if (src.reverse) {
3809    return src.reverse();
3810  }
3811
3812  return Array.prototype.reverse.call(src);
3813};
3814
3815exports.reverseBytes = reverseBytes;
3816},{"@babel/runtime/helpers/interopRequireDefault":10,"global/window":39}
3816],12:[function(require,module,exports){
3817"use strict";
3818
3819Object.defineProperty(exports, "__esModule", {
3820  value: true
3821});
3822exports.getHvcCodec = exports.getAvcCodec = exports.getAv1Codec = void 0;
3823
3824var _byteHelpers = require("./byte-helpers.js");
3825
3826// https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax
3827// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/codecs_parameter#AV1
3828var getAv1Codec = function getAv1Codec(bytes) {
3829  var codec = '';
3830  var profile = bytes[1] >>> 3;
3831  var level = bytes[1] & 0x1F;
3832  var tier = bytes[2] >>> 7;
3833  var highBitDepth = (bytes[2] & 0x40) >> 6;
3834  var twelveBit = (bytes[2] & 0x20) >> 5;
3835  var monochrome = (bytes[2] & 0x10) >> 4;
3836  var chromaSubsamplingX = (bytes[2] & 0x08) >> 3;
3837  var chromaSubsamplingY = (bytes[2] & 0x04) >> 2;
3838  var chromaSamplePosition = bytes[2] & 0x03;
3839  codec += profile + "." + (0, _byteHelpers.padStart)(level, 2, '0');
3840
3841  if (tier === 0) {
3842    codec += 'M';
3843  } else if (tier === 1) {
3844    codec += 'H';
3845  }
3846
3847  var bitDepth;
3848
3849  if (profile === 2 && highBitDepth) {
3850    bitDepth = twelveBit ? 12 : 10;
3851  } else {
3852    bitDepth = highBitDepth ? 10 : 8;
3853  }
3854
3855  codec += "." + (0, _byteHelpers.padStart)(bitDepth, 2, '0'); // TODO: can we parse color range??
3856
3857  codec += "." + monochrome;
3858  codec += "." + chromaSubsamplingX + chromaSubsamplingY + chromaSamplePosition;
3859  return codec;
3860};
3861
3862exports.getAv1Codec = getAv1Codec;
3863
3864var getAvcCodec = function getAvcCodec(bytes) {
3865  var profileId = (0, _byteHelpers.toHexString)(bytes[1]);
3866  var constraintFlags = (0, _byteHelpers.toHexString)(bytes[2] & 0xFC);
3867  var levelId = (0, _byteHelpers.toHexString)(bytes[3]);
3868  return "" + profileId + constraintFlags + levelId;
3869};
3870
3871exports.getAvcCodec = getAvcCodec;
3872
3873var getHvcCodec = function getHvcCodec(bytes) {
3874  var codec = '';
3875  var profileSpace = bytes[1] >> 6;
3876  var profileId = bytes[1] & 0x1F;
3877  var tierFlag = (bytes[1] & 0x20) >> 5;
3878  var profileCompat = bytes.subarray(2, 6);
3879  var constraintIds = bytes.subarray(6, 12);
3880  var levelId = bytes[12];
3881
3882  if (profileSpace === 1) {
3883    codec += 'A';
3884  } else if (profileSpace === 2) {
3885    codec += 'B';
3886  } else if (profileSpace === 3) {
3887    codec += 'C';
3888  }
3889
3890  codec += profileId + "."; // ffmpeg does this in big endian
3891
3892  var profileCompatVal = parseInt((0, _byteHelpers.toBinaryString)(profileCompat).split('').reverse().join(''), 2); // apple does this in little endian...
3893
3894  if (profileCompatVal > 255) {
3895    profileCompatVal = parseInt((0, _byteHelpers.toBinaryString)(profileCompat), 2);
3896  }
3897
3898  codec += profileCompatVal.toString(16) + ".";
3899
3900  if (tierFlag === 0) {
3901    codec += 'L';
3902  } else {
3903    codec += 'H';
3904  }
3905
3906  codec += levelId;
3907  var constraints = '';
3908
3909  for (var i = 0; i < constraintIds.length; i++) {
3910    var v = constraintIds[i];
3911
3912    if (v) {
3913      if (constraints) {
3914        constraints += '.';
3915      }
3916
3917      constraints += v.toString(16);
3918    }
3919  }
3920
3921  if (constraints) {
3922    codec += "." + constraints;
3923  }
3924
3925  return codec;
3926};
3927
3928exports.getHvcCodec = getHvcCodec;
3929},{"./byte-helpers.js":11}],13:[function(require,module,exports){
3930"use strict";
3931
3932var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
3933
3934Object.defineProperty(exports, "__esModule", {
3935  value: true
3936});
3937exports.DEFAULT_VIDEO_CODEC = exports.DEFAULT_AUDIO_CODEC = exports.muxerSupportsCodec = exports.browserSupportsCodec = exports.getMimeForCodec = exports.isTextCodec = exports.isAudioCodec = exports.isVideoCodec = exports.codecsFromDefault = exports.parseCodecs = exports.mapLegacyAvcCodecs = exports.translateLegacyCodecs = exports.translateLegacyCodec = void 0;
3938
3939var _window = _interopRequireDefault(require("global/window"));
3940
3941var regexs = {
3942  // to determine mime types
3943  mp4: /^(av0?1|avc0?[1234]|vp0?9|flac|opus|mp3|mp4a|mp4v|stpp.ttml.im1t)/,
3944  webm: /^(vp0?[89]|av0?1|opus|vorbis)/,
3945  ogg: /^(vp0?[89]|theora|flac|opus|vorbis)/,
3946  // to determine if a codec is audio or video
3947  video: /^(av0?1|avc0?[1234]|vp0?[89]|hvc1|hev1|theora|mp4v)/,
3948  audio: /^(mp4a|flac|vorbis|opus|ac-[34]|ec-3|alac|mp3|speex|aac)/,
3949  text: /^(stpp.ttml.im1t)/,
3950  // mux.js support regex
3951  muxerVideo: /^(avc0?1)/,
3952  muxerAudio: /^(mp4a)/,
3953  // match nothing as muxer does not support text right now.
3954  // there cannot never be a character before the start of a string
3955  // so this matches nothing.
3956  muxerText: /a^/
3957};
3958var mediaTypes = ['video', 'audio', 'text'];
3959var upperMediaTypes = ['Video', 'Audio', 'Text'];
3960/**
3961 * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
3962 * `avc1.<hhhhhh>`
3963 *
3964 * @param {string} codec
3965 *        Codec string to translate
3966 * @return {string}
3967 *         The translated codec string
3968 */
3969
3970var translateLegacyCodec = function translateLegacyCodec(codec) {
3971  if (!codec) {
3972    return codec;
3973  }
3974
3975  return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
3976    var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
3977    var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
3978    return 'avc1.' + profileHex + '00' + avcLevelHex;
3979  });
3980};
3981/**
3982 * Replace the old apple-style `avc1.<dd>.<dd>` codec strings with the standard
3983 * `avc1.<hhhhhh>`
3984 *
3985 * @param {string[]} codecs
3986 *        An array of codec strings to translate
3987 * @return {string[]}
3988 *         The translated array of codec strings
3989 */
3990
3991
3992exports.translateLegacyCodec = translateLegacyCodec;
3993
3994var translateLegacyCodecs = function translateLegacyCodecs(codecs) {
3995  return codecs.map(translateLegacyCodec);
3996};
3997/**
3998 * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
3999 * standard `avc1.<hhhhhh>`.
4000 *
4001 * @param {string} codecString
4002 *        The codec string
4003 * @return {string}
4004 *         The codec string with old apple-style codecs replaced
4005 *
4006 * @private
4007 */
4008
4009
4010exports.translateLegacyCodecs = translateLegacyCodecs;
4011
4012var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) {
4013  return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) {
4014    return translateLegacyCodecs([match])[0];
4015  });
4016};
4017/**
4018 * @typedef {Object} ParsedCodecInfo
4019 * @property {number} codecCount
4020 *           Number of codecs parsed
4021 * @property {string} [videoCodec]
4022 *           Parsed video codec (if found)
4023 * @property {string} [videoObjectTypeIndicator]
4024 *           Video object type indicator (if found)
4025 * @property {string|null} audioProfile
4026 *           Audio profile
4027 */
4028
4029/**
4030 * Parses a codec string to retrieve the number of codecs specified, the video codec and
4031 * object type indicator, and the audio profile.
4032 *
4033 * @param {string} [codecString]
4034 *        The codec string to parse
4035 * @return {ParsedCodecInfo}
4036 *         Parsed codec info
4037 */
4038
4039
4040exports.mapLegacyAvcCodecs = mapLegacyAvcCodecs;
4041
4042var parseCodecs = function parseCodecs(codecString) {
4043  if (codecString === void 0) {
4044    codecString = '';
4045  }
4046
4047  var codecs = codecString.split(',');
4048  var result = [];
4049  codecs.forEach(function (codec) {
4050    codec = codec.trim();
4051    var codecType;
vendor: 4,114 bytes, lines 4052-4212
4052    mediaTypes.forEach(function (name) {
4053      var match = regexs[name].exec(codec.toLowerCase());
4054
4055      if (!match || match.length <= 1) {
4056        return;
4057      }
4058
4059      codecType = name; // maintain codec case
4060
4061      var type = codec.substring(0, match[1].length);
4062      var details = codec.replace(type, '');
4063      result.push({
4064        type: type,
4065        details: details,
4066        mediaType: name
4067      });
4068    });
4069
4070    if (!codecType) {
4071      result.push({
4072        type: codec,
4073        details: '',
4074        mediaType: 'unknown'
4075      });
4076    }
4077  });
4078  return result;
4079};
4080/**
4081 * Returns a ParsedCodecInfo object for the default alternate audio playlist if there is
4082 * a default alternate audio playlist for the provided audio group.
4083 *
4084 * @param {Object} master
4085 *        The master playlist
4086 * @param {string} audioGroupId
4087 *        ID of the audio group for which to find the default codec info
4088 * @return {ParsedCodecInfo}
4089 *         Parsed codec info
4090 */
4091
4092
4093exports.parseCodecs = parseCodecs;
4094
4095var codecsFromDefault = function codecsFromDefault(master, audioGroupId) {
4096  if (!master.mediaGroups.AUDIO || !audioGroupId) {
4097    return null;
4098  }
4099
4100  var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
4101
4102  if (!audioGroup) {
4103    return null;
4104  }
4105
4106  for (var name in audioGroup) {
4107    var audioType = audioGroup[name];
4108
4109    if (audioType.default && audioType.playlists) {
4110      // codec should be the same for all playlists within the audio type
4111      return parseCodecs(audioType.playlists[0].attributes.CODECS);
4112    }
4113  }
4114
4115  return null;
4116};
4117
4118exports.codecsFromDefault = codecsFromDefault;
4119
4120var isVideoCodec = function isVideoCodec(codec) {
4121  if (codec === void 0) {
4122    codec = '';
4123  }
4124
4125  return regexs.video.test(codec.trim().toLowerCase());
4126};
4127
4128exports.isVideoCodec = isVideoCodec;
4129
4130var isAudioCodec = function isAudioCodec(codec) {
4131  if (codec === void 0) {
4132    codec = '';
4133  }
4134
4135  return regexs.audio.test(codec.trim().toLowerCase());
4136};
4137
4138exports.isAudioCodec = isAudioCodec;
4139
4140var isTextCodec = function isTextCodec(codec) {
4141  if (codec === void 0) {
4142    codec = '';
4143  }
4144
4145  return regexs.text.test(codec.trim().toLowerCase());
4146};
4147
4148exports.isTextCodec = isTextCodec;
4149
4150var getMimeForCodec = function getMimeForCodec(codecString) {
4151  if (!codecString || typeof codecString !== 'string') {
4152    return;
4153  }
4154
4155  var codecs = codecString.toLowerCase().split(',').map(function (c) {
4156    return translateLegacyCodec(c.trim());
4157  }); // default to video type
4158
4159  var type = 'video'; // only change to audio type if the only codec we have is
4160  // audio
4161
4162  if (codecs.length === 1 && isAudioCodec(codecs[0])) {
4163    type = 'audio';
4164  } else if (codecs.length === 1 && isTextCodec(codecs[0])) {
4165    // text uses application/<container> for now
4166    type = 'application';
4167  } // default the container to mp4
4168
4169
4170  var container = 'mp4'; // every codec must be able to go into the container
4171  // for that container to be the correct one
4172
4173  if (codecs.every(function (c) {
4174    return regexs.mp4.test(c);
4175  })) {
4176    container = 'mp4';
4177  } else if (codecs.every(function (c) {
4178    return regexs.webm.test(c);
4179  })) {
4180    container = 'webm';
4181  } else if (codecs.every(function (c) {
4182    return regexs.ogg.test(c);
4183  })) {
4184    container = 'ogg';
4185  }
4186
4187  return type + "/" + container + ";codecs=\"" + codecString + "\"";
4188};
4189/**
4190 * Tests whether the codec is supported by MediaSource. Optionally also tests ManagedMediaSource.
4191 *
4192 * @param {string} codecString
4193 *        Codec to test
4194 * @param {boolean} [withMMS]
4195 *        Whether to check if ManagedMediaSource supports it
4196 * @return {boolean}
4197 *          Codec is supported
4198 */
4199
4200
4201exports.getMimeForCodec = getMimeForCodec;
4202
4203var browserSupportsCodec = function browserSupportsCodec(codecString, withMMS) {
4204  if (codecString === void 0) {
4205    codecString = '';
4206  }
4207
4208  if (withMMS === void 0) {
4209    withMMS = false;
4210  }
4211
4212  return _window.default.MediaSource && _window.default.MediaSource.isTypeSupported && _window.default.MediaSource.isTypeSupported(getMimeForCodec(codecString)) || withMMS && _window.default.ManagedMediaSource && _window.default.ManagedMediaSource.isTypeSupp
4212orted && _window.default.ManagedMediaSource.isTypeSupported(getMimeForCodec(codecString)) || false;
4213};
4214
4215exports.browserSupportsCodec = browserSupportsCodec;
4216
4217var muxerSupportsCodec = function muxerSupportsCodec(codecString) {
4218  if (codecString === void 0) {
4219    codecString = '';
4220  }
4221
4222  return codecString.toLowerCase().split(',').every(function (codec) {
4223    codec = codec.trim(); // any match is supported.
4224
4225    for (var i = 0; i < upperMediaTypes.length; i++) {
4226      var type = upperMediaTypes[i];
4227
4228      if (regexs["muxer" + type].test(codec)) {
4229        return true;
4230      }
4231    }
4232
4233    return false;
4234  });
4235};
4236
4237exports.muxerSupportsCodec = muxerSupportsCodec;
4238var DEFAULT_AUDIO_CODEC = 'mp4a.40.2';
4239exports.DEFAULT_AUDIO_CODEC = DEFAULT_AUDIO_CODEC;
4240var DEFAULT_VIDEO_CODEC = 'avc1.4d400d';
4241exports.DEFAULT_VIDEO_CODEC = DEFAULT_VIDEO_CODEC;
4242},{"@babel/runtime/helpers/interopRequireDefault":10,"global/window":39}],14:[function(require,module,exports){
4243"use strict";
4244
4245Object.defineProperty(exports, "__esModule", {
4246  value: true
4247});
4248exports.isLikelyFmp4MediaSegment = exports.detectContainerForBytes = exports.isLikely = void 0;
4249
4250var _byteHelpers = require("./byte-helpers.js");
4251
4252var _mp4Helpers = require("./mp4-helpers.js");
4253
4254var _ebmlHelpers = require("./ebml-helpers.js");
4255
4256var _id3Helpers = require("./id3-helpers.js");
4257
4258var _nalHelpers = require("./nal-helpers.js");
4259
4260var CONSTANTS = {
4261  // "webm" string literal in hex
4262  'webm': (0, _byteHelpers.toUint8)([0x77, 0x65, 0x62, 0x6d]),
4263  // "matroska" string literal in hex
4264  'matroska': (0, _byteHelpers.toUint8)([0x6d, 0x61, 0x74, 0x72, 0x6f, 0x73, 0x6b, 0x61]),
4265  // "fLaC" string literal in hex
4266  'flac': (0, _byteHelpers.toUint8)([0x66, 0x4c, 0x61, 0x43]),
4267  // "OggS" string literal in hex
4268  'ogg': (0, _byteHelpers.toUint8)([0x4f, 0x67, 0x67, 0x53]),
4269  // ac-3 sync byte, also works for ec-3 as that is simply a codec
4270  // of ac-3
4271  'ac3': (0, _byteHelpers.toUint8)([0x0b, 0x77]),
4272  // "RIFF" string literal in hex used for wav and avi
4273  'riff': (0, _byteHelpers.toUint8)([0x52, 0x49, 0x46, 0x46]),
4274  // "AVI" string literal in hex
4275  'avi': (0, _byteHelpers.toUint8)([0x41, 0x56, 0x49]),
4276  // "WAVE" string literal in hex
4277  'wav': (0, _byteHelpers.toUint8)([0x57, 0x41, 0x56, 0x45]),
4278  // "ftyp3g" string literal in hex
4279  '3gp': (0, _byteHelpers.toUint8)([0x66, 0x74, 0x79, 0x70, 0x33, 0x67]),
4280  // "ftyp" string literal in hex
4281  'mp4': (0, _byteHelpers.toUint8)([0x66, 0x74, 0x79, 0x70]),
4282  // "styp" string literal in hex
4283  'fmp4': (0, _byteHelpers.toUint8)([0x73, 0x74, 0x79, 0x70]),
4284  // "ftypqt" string literal in hex
4285  'mov': (0, _byteHelpers.toUint8)([0x66, 0x74, 0x79, 0x70, 0x71, 0x74]),
4286  // moov string literal in hex
4287  'moov': (0, _byteHelpers.toUint8)([0x6D, 0x6F, 0x6F, 0x76]),
4288  // moof string literal in hex
4289  'moof': (0, _byteHelpers.toUint8)([0x6D, 0x6F, 0x6F, 0x66]),
4290  // "WEBVTT" string literal in hex
4291  'vtt': (0, _byteHelpers.toUint8)([0x57, 0x45, 0x42, 0x56, 0x54, 0x54])
4292};
4293var _isLikely = {
4294  aac: function aac(bytes) {
4295    var offset = (0, _id3Helpers.getId3Offset)(bytes);
4296    return (0, _byteHelpers.bytesMatch)(bytes, [0xFF, 0x10], {
4297      offset: offset,
4298      mask: [0xFF, 0x16]
4299    });
4300  },
4301  mp3: function mp3(bytes) {
4302    var offset = (0, _id3Helpers.getId3Offset)(bytes);
4303    return (0, _byteHelpers.bytesMatch)(bytes, [0xFF, 0x02], {
4304      offset: offset,
4305      mask: [0xFF, 0x06]
4306    });
4307  },
4308  webm: function webm(bytes) {
4309    var docType = (0, _ebmlHelpers.findEbml)(bytes, [_ebmlHelpers.EBML_TAGS.EBML, _ebmlHelpers.EBML_TAGS.DocType])[0]; // check if DocType EBML tag is webm
4310
4311    return (0, _byteHelpers.bytesMatch)(docType, CONSTANTS.webm);
4312  },
4313  mkv: function mkv(bytes) {
4314    var docType = (0, _ebmlHelpers.findEbml)(bytes, [_ebmlHelpers.EBML_TAGS.EBML, _ebmlHelpers.EBML_TAGS.DocType])[0]; // check if DocType EBML tag is matroska
4315
4316    return (0, _byteHelpers.bytesMatch)(docType, CONSTANTS.matroska);
4317  },
4318  mp4: function mp4(bytes) {
4319    // if this file is another base media file format, it is not mp4
4320    if (_isLikely['3gp'](bytes) || _isLikely.mov(bytes)) {
4321      return false;
4322    } // if this file starts with a ftyp or styp box its mp4
4323
4324
4325    if ((0, _byteHelpers.bytesMatch)(bytes, CONSTANTS.mp4, {
4326      offset: 4
4327    }) || (0, _byteHelpers.bytesMatch)(bytes, CONSTANTS.fmp4, {
4328      offset: 4
4329    })) {
4330      return true;
4331    } // if this file starts with a moof/moov box its mp4
4332
4333
4334    if ((0, _byteHelpers.bytesMatch)(bytes, CONSTANTS.moof, {
4335      offset: 4
4336    }) || (0, _byteHelpers.bytesMatch)(bytes, CONSTANTS.moov, {
4337      offset: 4
4338    })) {
4339      return true;
4340    }
4341  },
4342  mov: function mov(bytes) {
4343    return (0, _byteHelpers.bytesMatch)(bytes, CONSTANTS.mov, {
4344      offset: 4
4345    });
4346  },
4347  '3gp': function gp(bytes) {
4348    return (0, _byteHelpers.bytesMatch)(bytes, CONSTANTS['3gp'], {
4349      offset: 4
4350    });
4351  },
4352  ac3: function ac3(bytes) {
4353    var offset = (0, _id3Helpers.getId3Offset)(bytes);
4354    return (0, _byteHelpers.bytesMatch)(bytes, CONSTANTS.ac3, {
4355      offset: offset
4356    });
4357  },
4358  // Must be checked before ts. The sorting of isLikelyTypes ensures this.
4359  vtt: function vtt(bytes) {
4360    return (0, _byteHelpers.bytesMatch)(bytes, CONSTANTS.vtt);
4361  },
4362  ts: function ts(bytes) {
4363    if (bytes.length < 189 && bytes.length >= 1) {
4364      return bytes[0] === 0x47;
4365    }
4366
4367    var i = 0; // check the first 376 bytes for two matching sync bytes
4368
4369    while (i + 188 < bytes.length && i < 188) {
4370      if (bytes[i] === 0x47 && bytes[i + 188] === 0x47) {
4371        return true;
4372      }
4373
4374      i += 1;
4375    }
4376
4377    return false;
4378  },
4379  flac: function flac(bytes) {
4380    var offset = (0, _id3Helpers.getId3Offset)(bytes);
4381    return (0, _byteHelpers.bytesMatch)(bytes, CONSTANTS.flac, {
4382      offset: offset
4383    });
4384  },
4385  ogg: function ogg(bytes) {
4386    return (0, _byteHelpers.bytesMatch)(bytes, CONSTANTS.ogg);
4387  },
4388  avi: function avi(bytes) {
4389    return (0, _byteHelpers.bytesMatch)(bytes, CONSTANTS.riff) && (0, _byteHelpers.bytesMatch)(bytes, CONSTANTS.avi, {
4390      offset: 8
4391    });
4392  },
4393  wav: function wav(bytes) {
4394    return (0, _byteHelpers.bytesMatch)(bytes, CONSTANTS.riff) && (0, _byteHelpers.bytesMatch)(bytes, CONSTANTS.wav, {
4395      offset: 8
4396    });
4397  },
4398  'h264': function h264(bytes) {
4399    // find seq_parameter_set_rbsp
4400    return (0, _nalHelpers.findH264Nal)(bytes, 7, 3).length;
4401  },
4402  'h265': function h265(bytes) {
4403    // find video_parameter_set_rbsp or seq_parameter_set_rbsp
4404    return (0, _nalHelpers.findH265Nal)(bytes, [32, 33], 3).length;
4405  }
4406}; // get all the isLikely functions
4407// but make sure 'ts' is above h264 and h265
4408// but below everything else as it is the least specific
4409
4410var isLikelyTypes = Object.keys(_isLikely) // remove ts, h264, h265
4411.filter(function (t) {
4412  return t !== 'ts' && t !== 'h264' && t !== 'h265';
4413}) // add it back to the bottom
4414.concat(['ts', 'h264', 'h265']); // make sure we are dealing with uint8 data.
4415
4416isLikelyTypes.forEach(function (type) {
4417  var isLikelyFn = _isLikely[type];
4418
4419  _isLikely[type] = function (bytes) {
4420    return isLikelyFn((0, _byteHelpers.toUint8)(bytes));
4421  };
4422}); // export after wrapping
4423
4424var isLikely = _isLikely; // A useful list of file signatures can be found here
4425// https://en.wikipedia.org/wiki/List_of_file_signatures
4426
4427exports.isLikely = isLikely;
4428
4429var detectContainerForBytes = function detectContainerForBytes(bytes) {
4430  bytes = (0, _byteHelpers.toUint8)(bytes);
4431
4432  for (var i = 0; i < isLikelyTypes.length; i++) {
4433    var type = isLikelyTypes[i];
4434
4435    if (isLikely[type](bytes)) {
4436      return type;
4437    }
4438  }
4439
4440  return '';
4441}; // fmp4 is not a container
4442
4443
4444exports.detectContainerForBytes = detectContainerForBytes;
4445
4446var isLikelyFmp4MediaSegment = function isLikelyFmp4MediaSegment(bytes) {
4447  return (0, _mp4Helpers.findBox)(bytes, ['moof']).length > 0;
4448};
4449
4450exports.isLikelyFmp4MediaSegment = isLikelyFmp4MediaSegment;
4451},{"./byte-helpers.js":11,"./ebml-helpers.js":16,"./id3-helpers.js":17,"./mp4-helpers.js":20,"./nal-helpers.js":21}],15:[function(require,module,exports){
4452(function (Buffer){
4453"use strict";
4454
4455var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4456
4457Object.defineProperty(exports, "__esModule", {
4458  value: true
4459});
4460exports.default = decodeB64ToUint8Array;
4461
4462var _window = _interopRequireDefault(require("global/window"));
4463
4464var atob = function atob(s) {
4465  return _window.default.atob ? _window.default.atob(s) : Buffer.from(s, 'base64').toString('binary');
4466};
4467
4468function decodeB64ToUint8Array(b64Text) {
4469  var decodedString = atob(b64Text);
4470  var array = new Uint8Array(decodedString.length);
4471
4472  for (var i = 0; i < decodedString.length; i++) {
4473    array[i] = decodedString.charCodeAt(i);
4474  }
4475
4476  return array;
4477}
4478
4479module.exports = exports.default;
4480}).call(this,require("buffer").Buffer)
4481},{"@babel/runtime/helpers/interopRequireDefault":10,"buffer":37,"global/window":39}],16:[function(require,module,exports){
4482"use strict";
4483
4484Object.defineProperty(exports, "__esModule", {
4485  value: true
4486});
4487exports.parseData = exports.parseTracks = exports.decodeBlock = exports.findEbml = exports.EBML_TAGS = void 0;
4488
4489var _byteHelpers = require("./byte-helpers");
4490
4491var _codecHelpers = require("./codec-helpers.js");
4492
4493// relevant specs for this parser:
4494// https://matroska-org.github.io/libebml/specs.html
4495// https://www.matroska.org/technical/elements.html
4496// https://www.webmproject.org/docs/container/
4497var EBML_TAGS = {
4498  EBML: (0, _byteHelpers.toUint8)([0x1A, 0x45, 0xDF, 0xA3]),
4499  DocType: (0, _byteHelpers.toUint8)([0x42, 0x82]),
4500  Segment: (0, _byteHelpers.toUint8)([0x18, 0x53, 0x80, 0x67]),
4501  SegmentInfo: (0, _byteHelpers.toUint8)([0x15, 0x49, 0xA9, 0x66]),
4502  Tracks: (0, _byteHelpers.toUint8)([0x16, 0x54, 0xAE, 0x6B]),
4503  Track: (0, _byteHelpers.toUint8)([0xAE]),
4504  TrackNumber: (0, _byteHelpers.toUint8)([0xd7]),
4505  DefaultDuration: (0, _byteHelpers.toUint8)([0x23, 0xe3, 0x83]),
4506  TrackEntry: (0, _byteHelpers.toUint8)([0xAE]),
4507  TrackType: (0, _byteHelpers.toUint8)([0x83]),
4508  FlagDefault: (0, _byteHelpers.toUint8)([0x88]),
4509  CodecID: (0, _byteHelpers.toUint8)([0x86]),
4510  CodecPrivate: (0, _byteHelpers.toUint8)([0x63, 0xA2]),
4511  VideoTrack: (0, _byteHelpers.toUint8)([0xe0]),
4512  AudioTrack: (0, _byteHelpers.toUint8)([0xe1]),
4513  // Not used yet, but will be used for live webm/mkv
4514  // see https://www.matroska.org/technical/basics.html#block-structure
4515  // see https://www.matroska.org/technical/basics.html#simpleblock-structure
4516  Cluster: (0, _byteHelpers.toUint8)([0x1F, 0x43, 0xB6, 0x75]),
4517  Timestamp: (0, _byteHelpers.toUint8)([0xE7]),
4518  TimestampScale: (0, _byteHelpers.toUint8)([0x2A, 0xD7, 0xB1]),
4519  BlockGroup: (0, _byteHelpers.toUint8)([0xA0]),
4520  BlockDuration: (0, _byteHelpers.toUint8)([0x9B]),
4521  Block: (0, _byteHelpers.toUint8)([0xA1]),
4522  SimpleBlock: (0, _byteHelpers.toUint8)([0xA3])
4523};
4524/**
4525 * This is a simple table to determine the length
4526 * of things in ebml. The length is one based (starts at 1,
4527 * rather than zero) and for every zero bit before a one bit
4528 * we add one to length. We also need this table because in some
4529 * case we have to xor all the length bits from another value.
4530 */
4531
4532exports.EBML_TAGS = EBML_TAGS;
4533var LENGTH_TABLE = [128, 64, 32, 16, 8, 4, 2, 1];
4534
4535var getLength = function getLength(byte) {
4536  var len = 1;
4537
4538  for (var i = 0; i < LENGTH_TABLE.length; i++) {
4539    if (byte & LENGTH_TABLE[i]) {
4540      break;
4541    }
4542
4543    len++;
4544  }
4545
4546  return len;
4547}; // length in ebml is stored in the first 4 to 8 bits
4548// of the first byte. 4 for the id length and 8 for the
4549// data size length. Length is measured by converting the number to binary
4550// then 1 + the number of zeros before a 1 is encountered starting
4551// from the left.
4552
4553
4554var getvint = function getvint(bytes, offset, removeLength, signed) {
4555  if (removeLength === void 0) {
4556    removeLength = true;
4557  }
4558
4559  if (signed === void 0) {
4560    signed = false;
4561  }
4562
4563  var length = getLength(bytes[offset]);
4564  var valueBytes = bytes.subarray(offset, offset + length); // NOTE that we do **not** subarray here because we need to copy these bytes
4565  // as they will be modified below to remove the dataSizeLen bits and we do not
4566  // want to modify the original data. normally we could just call slice on
4567  // uint8array but ie 11 does not support that...
4568
4569  if (removeLength) {
4570    valueBytes = Array.prototype.slice.call(bytes, offset, offset + length);
4571    valueBytes[0] ^= LENGTH_TABLE[length - 1];
4572  }
4573
4574  return {
4575    length: length,
4576    value: (0, _byteHelpers.bytesToNumber)(valueBytes, {
4577      signed: signed
4578    }),
4579    bytes: valueBytes
4580  };
4581};
4582
4583var normalizePath = function normalizePath(path) {
4584  if (typeof path === 'string') {
4585    return path.match(/.{1,2}/g).map(function (p) {
4586      return normalizePath(p);
4587    });
4588  }
4589
4590  if (typeof path === 'number') {
4591    return (0, _byteHelpers.numberToBytes)(path);
4592  }
4593
4594  return path;
4595};
4596
4597var normalizePaths = function normalizePaths(paths) {
4598  if (!Array.isArray(paths)) {
4599    return [normalizePath(paths)];
4600  }
4601
4602  return paths.map(function (p) {
4603    return normalizePath(p);
4604  });
4605};
4606
4607var getInfinityDataSize = function getInfinityDataSize(id, bytes, offset) {
4608  if (offset >= bytes.length) {
4609    return bytes.length;
4610  }
4611
4612  var innerid = getvint(bytes, offset, false);
4613
4614  if ((0, _byteHelpers.bytesMatch)(id.bytes, innerid.bytes)) {
4615    return offset;
4616  }
4617
4618  var dataHeader = getvint(bytes, offset + innerid.length);
4619  return getInfinityDataSize(id, bytes, offset + dataHeader.length + dataHeader.value + innerid.length);
4620};
4621/**
4622 * Notes on the EBLM format.
4623 *
4624 * EBLM uses "vints" tags. Every vint tag contains
4625 * two parts
4626 *
4627 * 1. The length from the first byte. You get this by
4628 *    converting the byte to binary and counting the zeros
4629 *    before a 1. Then you add 1 to that. Examples
4630 *    00011111 = length 4 because there are 3 zeros before a 1.
4631 *    00100000 = length 3 because there are 2 zeros before a 1.
4632 *    00000011 = length 7 because there are 6 zeros before a 1.
4633 *
4634 * 2. The bits used for length are removed from the first byte
4635 *    Then all the bytes are merged into a value. NOTE: this
4636 *    is not the case for id ebml tags as there id includes
4637 *    length bits.
4638 *
4639 */
4640
4641
4642var findEbml = function findEbml(bytes, paths) {
4643  paths = normalizePaths(paths);
4644  bytes = (0, _byteHelpers.toUint8)(bytes);
4645  var results = [];
4646
4647  if (!paths.length) {
4648    return results;
4649  }
4650
4651  var i = 0;
4652
4653  while (i < bytes.length) {
4654    var id = getvint(bytes, i, false);
4655    var dataHeader = getvint(bytes, i + id.length);
4656    var dataStart = i + id.length + dataHeader.length; // dataSize is unknown or this is a live stream
4657
4658    if (dataHeader.value === 0x7f) {
4659      dataHeader.value = getInfinityDataSize(id, bytes, dataStart);
4660
4661      if (dataHeader.value !== bytes.length) {
4662        dataHeader.value -= dataStart;
4663      }
4664    }
4665
4666    var dataEnd = dataStart + dataHeader.value > bytes.length ? bytes.length : dataStart + dataHeader.value;
4667    var data = bytes.subarray(dataStart, dataEnd);
4668
4669    if ((0, _byteHelpers.bytesMatch)(paths[0], id.bytes)) {
4670      if (paths.length === 1) {
4671        // this is the end of the paths and we've found the tag we were
4672        // looking for
4673        results.push(data);
4674      } else {
4675        // recursively search for the next tag inside of the data
4676        // of this one
4677        results = results.concat(findEbml(data, paths.slice(1)));
4678      }
4679    }
4680
4681    var totalLength = id.length + dataHeader.length + data.length; // move past this tag entirely, we are not looking for it
4682
4683    i += totalLength;
4684  }
4685
4686  return results;
4687}; // see https://www.matroska.org/technical/basics.html#block-structure
4688
4689
4690exports.findEbml = findEbml;
4691
4692var decodeBlock = function decodeBlock(block, type, timestampScale, clusterTimestamp) {
4693  var duration;
4694
4695  if (type === 'group') {
4696    duration = findEbml(block, [EBML_TAGS.BlockDuration])[0];
4697
4698    if (duration) {
4699      duration = (0, _byteHelpers.bytesToNumber)(duration);
4700      duration = 1 / timestampScale * duration * timestampScale / 1000;
4701    }
4702
4703    block = findEbml(block, [EBML_TAGS.Block])[0];
4704    type = 'block'; // treat data as a block after this point
4705  }
4706
4707  var dv = new DataView(block.buffer, block.byteOffset, block.byteLength);
4708  var trackNumber = getvint(block, 0);
4709  var timestamp = dv.getInt16(trackNumber.length, false);
4710  var flags = block[trackNumber.length + 2];
4711  var data = block.subarray(trackNumber.length + 3); // pts/dts in seconds
4712
4713  var ptsdts = 1 / timestampScale * (clusterTimestamp + timestamp) * timestampScale / 1000; // return the frame
4714
4715  var parsed = {
4716    duration: duration,
4717    trackNumber: trackNumber.value,
4718    keyframe: type === 'simple' && flags >> 7 === 1,
4719    invisible: (flags & 0x08) >> 3 === 1,
4720    lacing: (flags & 0x06) >> 1,
4721    discardable: type === 'simple' && (flags & 0x01) === 1,
4722    frames: [],
4723    pts: ptsdts,
4724    dts: ptsdts,
4725    timestamp: timestamp
4726  };
4727
4728  if (!parsed.lacing) {
4729    parsed.frames.push(data);
4730    return parsed;
4731  }
4732
4733  var numberOfFrames = data[0] + 1;
4734  var frameSizes = [];
4735  var offset = 1; // Fixed
4736
4737  if (parsed.lacing === 2) {
4738    var sizeOfFrame = (data.length - offset) / numberOfFrames;
4739
4740    for (var i = 0; i < numberOfFrames; i++) {
4741      frameSizes.push(sizeOfFrame);
4742    }
4743  } // xiph
4744
4745
4746  if (parsed.lacing === 1) {
4747    for (var _i = 0; _i < numberOfFrames - 1; _i++) {
4748      var size = 0;
4749
4750      do {
4751        size += data[offset];
4752        offset++;
4753      } while (data[offset - 1] === 0xFF);
4754
4755      frameSizes.push(size);
4756    }
4757  } // ebml
4758
4759
4760  if (parsed.lacing === 3) {
4761    // first vint is unsinged
4762    // after that vints are singed and
4763    // based on a compounding size
4764    var _size = 0;
4765
4766    for (var _i2 = 0; _i2 < numberOfFrames - 1; _i2++) {
4767      var vint = _i2 === 0 ? getvint(data, offset) : getvint(data, offset, true, true);
4768      _size += vint.value;
4769      frameSizes.push(_size);
4770      offset += vint.length;
4771    }
4772  }
4773
4774  frameSizes.forEach(function (size) {
4775    parsed.frames.push(data.subarray(offset, offset + size));
4776    offset += size;
4777  });
4778  return parsed;
4779}; // VP9 Codec Feature Metadata (CodecPrivate)
4780// https://www.webmproject.org/docs/container/
4781
4782
4783exports.decodeBlock = decodeBlock;
4784
4785var parseVp9Private = function parseVp9Private(bytes) {
4786  var i = 0;
4787  var params = {};
4788
4789  while (i < bytes.length) {
4790    var id = bytes[i] & 0x7f;
4791    var len = bytes[i + 1];
4792    var val = void 0;
4793
4794    if (len === 1) {
4795      val = bytes[i + 2];
4796    } else {
4797      val = bytes.subarray(i + 2, i + 2 + len);
4798    }
4799
4800    if (id === 1) {
4801      params.profile = val;
4802    } else if (id === 2) {
4803      params.level = val;
4804    } else if (id === 3) {
4805      params.bitDepth = val;
4806    } else if (id === 4) {
4807      params.chromaSubsampling = val;
4808    } else {
4809      params[id] = val;
4810    }
4811
4812    i += 2 + len;
4813  }
4814
4815  return params;
4816};
4817
4818var parseTracks = function parseTracks(bytes) {
4819  bytes = (0, _byteHelpers.toUint8)(bytes);
4820  var decodedTracks = [];
4821  var tracks = findEbml(bytes, [EBML_TAGS.Segment, EBML_TAGS.Tracks, EBML_TAGS.Track]);
4822
4823  if (!tracks.length) {
4824    tracks = findEbml(bytes, [EBML_TAGS.Tracks, EBML_TAGS.Track]);
4825  }
4826
4827  if (!tracks.length) {
4828    tracks = findEbml(bytes, [EBML_TAGS.Track]);
4829  }
4830
4831  if (!tracks.length) {
4832    return decodedTracks;
4833  }
4834
4835  tracks.forEach(function (track) {
4836    var trackType = findEbml(track, EBML_TAGS.TrackType)[0];
4837
4838    if (!trackType || !trackType.length) {
4839      return;
4840    } // 1 is video, 2 is audio, 17 is subtitle
4841    // other values are unimportant in this context
4842
4843
4844    if (trackType[0] === 1) {
4845      trackType = 'video';
4846    } else if (trackType[0] === 2) {
4847      trackType = 'audio';
4848    } else if (trackType[0] === 17) {
4849      trackType = 'subtitle';
4850    } else {
4851      return;
4852    } // todo parse language
4853
4854
4855    var decodedTrack = {
4856      rawCodec: (0, _byteHelpers.bytesToString)(findEbml(track, [EBML_TAGS.CodecID])[0]),
4857      type: trackType,
4858      codecPrivate: findEbml(track, [EBML_TAGS.CodecPrivate])[0],
4859      number: (0, _byteHelpers.bytesToNumber)(findEbml(track, [EBML_TAGS.TrackNumber])[0]),
4860      defaultDuration: (0, _byteHelpers.bytesToNumber)(findEbml(track, [EBML_TAGS.DefaultDuration])[0]),
4861      default: findEbml(track, [EBML_TAGS.FlagDefault])[0],
4862      rawData: track
4863    };
4864    var codec = '';
4865
4866    if (/V_MPEG4\/ISO\/AVC/.test(decodedTrack.rawCodec)) {
4867      codec = "avc1." + (0, _codecHelpers.getAvcCodec)(decodedTrack.codecPrivate);
4868    } else if (/V_MPEGH\/ISO\/HEVC/.test(decodedTrack.rawCodec)) {
4869      codec = "hev1." + (0, _codecHelpers.getHvcCodec)(decodedTrack.codecPrivate);
4870    } else if (/V_MPEG4\/ISO\/ASP/.test(decodedTrack.rawCodec)) {
4871      if (decodedTrack.codecPrivate) {
4872        codec = 'mp4v.20.' + decodedTrack.codecPrivate[4].toString();
4873      } else {
4874        codec = 'mp4v.20.9';
4875      }
4876    } else if (/^V_THEORA/.test(decodedTrack.rawCodec)) {
4877      codec = 'theora';
4878    } else if (/^V_VP8/.test(decodedTrack.rawCodec)) {
4879      codec = 'vp8';
4880    } else if (/^V_VP9/.test(decodedTrack.rawCodec)) {
4881      if (decodedTrack.codecPrivate) {
4882        var _parseVp9Private = parseVp9Private(decodedTrack.codecPrivate),
4883            profile = _parseVp9Private.profile,
4884            level = _parseVp9Private.level,
4885            bitDepth = _parseVp9Private.bitDepth,
4886            chromaSubsampling = _parseVp9Private.chromaSubsampling;
4887
4888        codec = 'vp09.';
4889        codec += (0, _byteHelpers.padStart)(profile, 2, '0') + ".";
4890        codec += (0, _byteHelpers.padStart)(level, 2, '0') + ".";
4891        codec += (0, _byteHelpers.padStart)(bitDepth, 2, '0') + ".";
4892        codec += "" + (0, _byteHelpers.padStart)(chromaSubsampling, 2, '0'); // Video -> Colour -> Ebml name
4893
4894        var matrixCoefficients = findEbml(track, [0xE0, [0x55, 0xB0], [0x55, 0xB1]])[0] || [];
4895        var videoFullRangeFlag = findEbml(track, [0xE0, [0x55, 0xB0], [0x55, 0xB9]])[0] || [];
4896        var transferCharacteristics = findEbml(track, [0xE0, [0x55, 0xB0], [0x55, 0xBA]])[0] || [];
4897        var colourPrimaries = findEbml(track, [0xE0, [0x55, 0xB0], [0x55, 0xBB]])[0] || []; // if we find any optional codec parameter specify them all.
4898
4899        if (matrixCoefficients.length || videoFullRangeFlag.length || transferCharacteristics.length || colourPrimaries.length) {
4900          codec += "." + (0, _byteHelpers.padStart)(colourPrimaries[0], 2, '0');
4901          codec += "." + (0, _byteHelpers.padStart)(transferCharacteristics[0], 2, '0');
4902          codec += "." + (0, _byteHelpers.padStart)(matrixCoefficients[0], 2, '0');
4903          codec += "." + (0, _byteHelpers.padStart)(videoFullRangeFlag[0], 2, '0');
4904        }
4905      } else {
4906        codec = 'vp9';
4907      }
4908    } else if (/^V_AV1/.test(decodedTrack.rawCodec)) {
4909      codec = "av01." + (0, _codecHelpers.getAv1Codec)(decodedTrack.codecPrivate);
4910    } else if (/A_ALAC/.test(decodedTrack.rawCodec)) {
4911      codec = 'alac';
4912    } else if (/A_MPEG\/L2/.test(decodedTrack.rawCodec)) {
4913      codec = 'mp2';
4914    } else if (/A_MPEG\/L3/.test(decodedTrack.rawCodec)) {
4915      codec = 'mp3';
4916    } else if (/^A_AAC/.test(decodedTrack.rawCodec)) {
4917      if (decodedTrack.codecPrivate) {
4918        codec = 'mp4a.40.' + (decodedTrack.codecPrivate[0] >>> 3).toString();
4919      } else {
4920        codec = 'mp4a.40.2';
4921      }
4922    } else if (/^A_AC3/.test(decodedTrack.rawCodec)) {
4923      codec = 'ac-3';
4924    } else if (/^A_PCM/.test(decodedTrack.rawCodec)) {
4925      codec = 'pcm';
4926    } else if (/^A_MS\/ACM/.test(decodedTrack.rawCodec)) {
4927      codec = 'speex';
4928    } else if (/^A_EAC3/.test(decodedTrack.rawCodec)) {
4929      codec = 'ec-3';
4930    } else if (/^A_VORBIS/.test(decodedTrack.rawCodec)) {
4931      codec = 'vorbis';
4932    } else if (/^A_FLAC/.test(decodedTrack.rawCodec)) {
4933      codec = 'flac';
4934    } else if (/^A_OPUS/.test(decodedTrack.rawCodec)) {
4935      codec = 'opus';
4936    }
4937
4938    decodedTrack.codec = codec;
4939    decodedTracks.push(decodedTrack);
4940  });
4941  return decodedTracks.sort(function (a, b) {
4942    return a.number - b.number;
4943  });
4944};
4945
4946exports.parseTracks = parseTracks;
4947
4948var parseData = function parseData(data, tracks) {
4949  var allBlocks = [];
4950  var segment = findEbml(data, [EBML_TAGS.Segment])[0];
4951  var timestampScale = findEbml(segment, [EBML_TAGS.SegmentInfo, EBML_TAGS.TimestampScale])[0]; // in nanoseconds, defaults to 1ms
4952
4953  if (timestampScale && timestampScale.length) {
4954    timestampScale = (0, _byteHelpers.bytesToNumber)(timestampScale);
4955  } else {
4956    timestampScale = 1000000;
4957  }
4958
4959  var clusters = findEbml(segment, [EBML_TAGS.Cluster]);
4960
4961  if (!tracks) {
4962    tracks = parseTracks(segment);
4963  }
4964
4965  clusters.forEach(function (cluster, ci) {
4966    var simpleBlocks = findEbml(cluster, [EBML_TAGS.SimpleBlock]).map(function (b) {
4967      return {
4968        type: 'simple',
4969        data: b
4970      };
4971    });
4972    var blockGroups = findEbml(cluster, [EBML_TAGS.BlockGroup]).map(function (b) {
4973      return {
4974        type: 'group',
4975        data: b
4976      };
4977    });
4978    var timestamp = findEbml(cluster, [EBML_TAGS.Timestamp])[0] || 0;
4979
4980    if (timestamp && timestamp.length) {
4981      timestamp = (0, _byteHelpers.bytesToNumber)(timestamp);
4982    } // get all blocks then sort them into the correct order
4983
4984
4985    var blocks = simpleBlocks.concat(blockGroups).sort(function (a, b) {
4986      return a.data.byteOffset - b.data.byteOffset;
4987    });
4988    blocks.forEach(function (block, bi) {
4989      var decoded = decodeBlock(block.data, block.type, timestampScale, timestamp);
4990      allBlocks.push(decoded);
4991    });
4992  });
4993  return {
4994    tracks: tracks,
4995    blocks: allBlocks
4996  };
4997};
4998
4999exports.parseData = parseData;
5000},{"./byte-helpers":11,"./codec-helpers.js":12}],17:[function(require,module,exports){
5001"use strict";
5002
5003Object.defineProperty(exports, "__esModule", {
5004  value: true
5005});
5006exports.getId3Offset = exports.getId3Size = void 0;
5007
5008var _byteHelpers = require("./byte-helpers.js");
5009
5010var ID3 = (0, _byteHelpers.toUint8)([0x49, 0x44, 0x33]);
5011
5012var getId3Size = function getId3Size(bytes, offset) {
5013  if (offset === void 0) {
5014    offset = 0;
5015  }
5016
5017  bytes = (0, _byteHelpers.toUint8)(bytes);
5018  var flags = bytes[offset + 5];
5019  var returnSize = bytes[offset + 6] << 21 | bytes[offset + 7] << 14 | bytes[offset + 8] << 7 | bytes[offset + 9];
5020  var footerPresent = (flags & 16) >> 4;
5021
5022  if (footerPresent) {
5023    return returnSize + 20;
5024  }
5025
5026  return returnSize + 10;
5027};
5028
5029exports.getId3Size = getId3Size;
5030
5031var getId3Offset = function getId3Offset(bytes, offset) {
5032  if (offset === void 0) {
5033    offset = 0;
5034  }
5035
5036  bytes = (0, _byteHelpers.toUint8)(bytes);
5037
5038  if (bytes.length - offset < 10 || !(0, _byteHelpers.bytesMatch)(bytes, ID3, {
5039    offset: offset
5040  })) {
5041    return offset;
5042  }
5043
5044  offset += getId3Size(bytes, offset); // recursive check for id3 tags as some files
5045  // have multiple ID3 tag sections even though
5046  // they should not.
5047
5048  return getId3Offset(bytes, offset);
5049};
5050
5051exports.getId3Offset = getId3Offset;
5052},{"./byte-helpers.js":11}],18:[function(require,module,exports){
5053"use strict";
5054
5055Object.defineProperty(exports, "__esModule", {
5056  value: true
5057});
5058exports.forEachMediaGroup = void 0;
5059
5060/**
5061 * Loops through all supported media groups in master and calls the provided
5062 * callback for each group
5063 *
5064 * @param {Object} master
5065 *        The parsed master manifest object
5066 * @param {string[]} groups
5067 *        The media groups to call the callback for
5068 * @param {Function} callback
5069 *        Callback to call for each media group
5070 */
5071var forEachMediaGroup = function forEachMediaGroup(master, groups, callback) {
5072  groups.forEach(function (mediaType) {
5073    for (var groupKey in master.mediaGroups[mediaType]) {
5074      for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
5075        var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
5076        callback(mediaProperties, mediaType, groupKey, labelKey);
5077      }
5078    }
5079  });
5080};
5081
5082exports.forEachMediaGroup = forEachMediaGroup;
5083},{}],19:[function(require,module,exports){
5084"use strict";
5085
5086Object.defineProperty(exports, "__esModule", {
5087  value: true
5088});
5089exports.simpleTypeFromSourceType = void 0;
5090var MPEGURL_REGEX = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
5091var DASH_REGEX = /^application\/dash\+xml/i;
5092/**
5093 * Returns a string that describes the type of source based on a video source object's
5094 * media type.
5095 *
5096 * @see {@link https://dev.w3.org/html5/pf-summary/video.html#dom-source-type|Source Type}
5097 *
5098 * @param {string} type
5099 *        Video source object media type
5100 * @return {('hls'|'dash'|'vhs-json'|null)}
5101 *         VHS source type string
5102 */
5103
5104var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
5105  if (MPEGURL_REGEX.test(type)) {
5106    return 'hls';
5107  }
5108
5109  if (DASH_REGEX.test(type)) {
5110    return 'dash';
5111  } // Denotes the special case of a manifest object passed to http-streaming instead of a
5112  // source URL.
5113  //
5114  // See https://en.wikipedia.org/wiki/Media_type for details on specifying media types.
5115  //
5116  // In this case, vnd stands for vendor, video.js for the organization, VHS for this
5117  // project, and the +json suffix identifies the structure of the media type.
5118
5119
5120  if (type === 'application/vnd.videojs.vhs+json') {
5121    return 'vhs-json';
5122  }
5123
5124  return null;
5125};
5126
5127exports.simpleTypeFromSourceType = simpleTypeFromSourceType;
5128},{}],20:[function(require,module,exports){
5129"use strict";
5130
5131Object.defineProperty(exports, "__esModule", {
5132  value: true
5133});
5134exports.parseMediaInfo = exports.parseTracks = exports.addSampleDescription = exports.buildFrameTable = exports.findNamedBox = exports.findBox = exports.parseDescriptors = void 0;
5135
5136var _byteHelpers = require("./byte-helpers.js");
5137
5138var _codecHelpers = require("./codec-helpers.js");
5139
5140var _opusHelpers = require("./opus-helpers.js");
5141
5142var normalizePath = function normalizePath(path) {
5143  if (typeof path === 'string') {
5144    return (0, _byteHelpers.stringToBytes)(path);
5145  }
5146
5147  if (typeof path === 'number') {
5148    return path;
5149  }
5150
5151  return path;
5152};
5153
5154var normalizePaths = function normalizePaths(paths) {
5155  if (!Array.isArray(paths)) {
5156    return [normalizePath(paths)];
5157  }
5158
5159  return paths.map(function (p) {
5160    return normalizePath(p);
5161  });
5162};
5163
5164var DESCRIPTORS;
5165
5166var parseDescriptors = function parseDescriptors(bytes) {
5167  bytes = (0, _byteHelpers.toUint8)(bytes);
5168  var results = [];
5169  var i = 0;
5170
5171  while (bytes.length > i) {
5172    var tag = bytes[i];
5173    var size = 0;
5174    var headerSize = 0; // tag
5175
5176    headerSize++;
5177    var byte = bytes[headerSize]; // first byte
5178
5179    headerSize++;
5180
5181    while (byte & 0x80) {
5182      size = (byte & 0x7F) << 7;
5183      byte = bytes[headerSize];
5184      headerSize++;
5185    }
5186
5187    size += byte & 0x7F;
5188
5189    for (var z = 0; z < DESCRIPTORS.length; z++) {
5190      var _DESCRIPTORS$z = DESCRIPTORS[z],
5191          id = _DESCRIPTORS$z.id,
5192          parser = _DESCRIPTORS$z.parser;
5193
5194      if (tag === id) {
5195        results.push(parser(bytes.subarray(headerSize, headerSize + size)));
5196        break;
5197      }
5198    }
5199
5200    i += size + headerSize;
5201  }
5202
5203  return results;
5204};
5205
5206exports.parseDescriptors = parseDescriptors;
5207DESCRIPTORS = [{
5208  id: 0x03,
5209  parser: function parser(bytes) {
5210    var desc = {
5211      tag: 0x03,
5212      id: bytes[0] << 8 | bytes[1],
5213      flags: bytes[2],
5214      size: 3,
5215      dependsOnEsId: 0,
5216      ocrEsId: 0,
5217      descriptors: [],
5218      url: ''
5219    }; // depends on es id
5220
5221    if (desc.flags & 0x80) {
5222      desc.dependsOnEsId = bytes[desc.size] << 8 | bytes[desc.size + 1];
5223      desc.size += 2;
5224    } // url
5225
5226
5227    if (desc.flags & 0x40) {
5228      var len = bytes[desc.size];
5229      desc.url = (0, _byteHelpers.bytesToString)(bytes.subarray(desc.size + 1, desc.size + 1 + len));
5230      desc.size += len;
5231    } // ocr es id
5232
5233
5234    if (desc.flags & 0x20) {
5235      desc.ocrEsId = bytes[desc.size] << 8 | bytes[desc.size + 1];
5236      desc.size += 2;
5237    }
5238
5239    desc.descriptors = parseDescriptors(bytes.subarray(desc.size)) || [];
5240    return desc;
5241  }
5242}, {
5243  id: 0x04,
5244  parser: function parser(bytes) {
5245    // DecoderConfigDescriptor
5246    var desc = {
5247      tag: 0x04,
5248      oti: bytes[0],
5249      streamType: bytes[1],
5250      bufferSize: bytes[2] << 16 | bytes[3] << 8 | bytes[4],
5251      maxBitrate: bytes[5] << 24 | bytes[6] << 16 | bytes[7] << 8 | bytes[8],
5252      avgBitrate: bytes[9] << 24 | bytes[10] << 16 | bytes[11] << 8 | bytes[12],
5253      descriptors: parseDescriptors(bytes.subarray(13))
5254    };
5255    return desc;
5256  }
5257}, {
5258  id: 0x05,
5259  parser: function parser(bytes) {
5260    // DecoderSpecificInfo
5261    return {
5262      tag: 0x05,
5263      bytes: bytes
5264    };
5265  }
5266}, {
5267  id: 0x06,
5268  parser: function parser(bytes) {
5269    // SLConfigDescriptor
5270    return {
5271      tag: 0x06,
5272      bytes: bytes
5273    };
5274  }
5275}];
5276/**
5277 * find any number of boxes by name given a path to it in an iso bmff
5278 * such as mp4.
5279 *
5280 * @param {TypedArray} bytes
5281 *        bytes for the iso bmff to search for boxes in
5282 *
5283 * @param {Uint8Array[]|string[]|string|Uint8Array} name
5284 *        An array of paths or a single path representing the name
5285 *        of boxes to search through in bytes. Paths may be
5286 *        uint8 (character codes) or strings.
5287 *
5288 * @param {boolean} [complete=false]
5289 *        Should we search only for complete boxes on the final path.
5290 *        This is very useful when you do not want to get back partial boxes
5291 *        in the case of streaming files.
5292 *
5293 * @return {Uint8Array[]}
5294 *         An array of the end paths that we found.
5295 */
5296
5297var findBox = function findBox(bytes, paths, complete) {
5298  if (complete === void 0) {
5299    complete = false;
5300  }
5301
5302  paths = normalizePaths(paths);
5303  bytes = (0, _byteHelpers.toUint8)(bytes);
5304  var results = [];
5305
5306  if (!paths.length) {
5307    // short-circuit the search for empty paths
5308    return results;
5309  }
5310
5311  var i = 0;
5312
5313  while (i < bytes.length) {
5314    var size = (bytes[i] << 24 | bytes[i + 1] << 16 | bytes[i + 2] << 8 | bytes[i + 3]) >>> 0;
5315    var type = bytes.subarray(i + 4, i + 8); // invalid box format.
5316
5317    if (size === 0) {
5318      break;
5319    }
5320
5321    var end = i + size;
5322
5323    if (end > bytes.length) {
5324      // this box is bigger than the number of bytes we have
5325      // and complete is set, we cannot find any more boxes.
5326      if (complete) {
5327        break;
5328      }
5329
5330      end = bytes.length;
5331    }
5332
5333    var data = bytes.subarray(i + 8, end);
5334
5335    if ((0, _byteHelpers.bytesMatch)(type, paths[0])) {
5336      if (paths.length === 1) {
5337        // this is the end of the path and we've found the box we were
5338        // looking for
5339        results.push(data);
5340      } else {
5341        // recursively search for the next box along the path
5342        results.push.apply(results, findBox(data, paths.slice(1), complete));
5343      }
5344    }
5345
5346    i = end;
5347  } // we've finished searching all of bytes
5348
5349
5350  return results;
5351};
5352/**
5353 * Search for a single matching box by name in an iso bmff format like
5354 * mp4. This function is useful for finding codec boxes which
5355 * can be placed arbitrarily in sample descriptions depending
5356 * on the version of the file or file type.
5357 *
5358 * @param {TypedArray} bytes
5359 *        bytes for the iso bmff to search for boxes in
5360 *
5361 * @param {string|Uint8Array} name
5362 *        The name of the box to find.
5363 *
5364 * @return {Uint8Array[]}
5365 *         a subarray of bytes representing the name boxed we found.
5366 */
5367
5368
5369exports.findBox = findBox;
5370
5371var findNamedBox = function findNamedBox(bytes, name) {
5372  name = normalizePath(name);
5373
5374  if (!name.length) {
5375    // short-circuit the search for empty paths
5376    return bytes.subarray(bytes.length);
5377  }
5378
5379  var i = 0;
5380
5381  while (i < bytes.length) {
5382    if ((0, _byteHelpers.bytesMatch)(bytes.subarray(i, i + name.length), name)) {
5383      var size = (bytes[i - 4] << 24 | bytes[i - 3] << 16 | bytes[i - 2] << 8 | b
5383ytes[i - 1]) >>> 0;
5384      var end = size > 1 ? i + size : bytes.byteLength;
5385      return bytes.subarray(i + 4, end);
5386    }
5387
5388    i++;
5389  } // we've finished searching all of bytes
5390
5391
5392  return bytes.subarray(bytes.length);
5393};
5394
5395exports.findNamedBox = findNamedBox;
5396
5397var parseSamples = function parseSamples(data, entrySize, parseEntry) {
5398  if (entrySize === void 0) {
5399    entrySize = 4;
5400  }
5401
5402  if (parseEntry === void 0) {
5403    parseEntry = function parseEntry(d) {
5404      return (0, _byteHelpers.bytesToNumber)(d);
5405    };
5406  }
5407
5408  var entries = [];
5409
5410  if (!data || !data.length) {
5411    return entries;
5412  }
5413
5414  var entryCount = (0, _byteHelpers.bytesToNumber)(data.subarray(4, 8));
5415
5416  for (var i = 8; entryCount; i += entrySize, entryCount--) {
5417    entries.push(parseEntry(data.subarray(i, i + entrySize)));
5418  }
5419
5420  return entries;
5421};
5422
5423var buildFrameTable = function buildFrameTable(stbl, timescale) {
5424  var keySamples = parseSamples(findBox(stbl, ['stss'])[0]);
5425  var chunkOffsets = parseSamples(findBox(stbl, ['stco'])[0]);
5426  var timeToSamples = parseSamples(findBox(stbl, ['stts'])[0], 8, function (entry) {
5427    return {
5428      sampleCount: (0, _byteHelpers.bytesToNumber)(entry.subarray(0, 4)),
5429      sampleDelta: (0, _byteHelpers.bytesToNumber)(entry.subarray(4, 8))
5430    };
5431  });
5432  var samplesToChunks = parseSamples(findBox(stbl, ['stsc'])[0], 12, function (entry) {
5433    return {
5434      firstChunk: (0, _byteHelpers.bytesToNumber)(entry.subarray(0, 4)),
5435      samplesPerChunk: (0, _byteHelpers.bytesToNumber)(entry.subarray(4, 8)),
5436      sampleDescriptionIndex: (0, _byteHelpers.bytesToNumber)(entry.subarray(8, 12))
5437    };
5438  });
5439  var stsz = findBox(stbl, ['stsz'])[0]; // stsz starts with a 4 byte sampleSize which we don't need
5440
5441  var sampleSizes = parseSamples(stsz && stsz.length && stsz.subarray(4) || null);
5442  var frames = [];
5443
5444  for (var chunkIndex = 0; chunkIndex < chunkOffsets.length; chunkIndex++) {
5445    var samplesInChunk = void 0;
5446
5447    for (var i = 0; i < samplesToChunks.length; i++) {
5448      var sampleToChunk = samplesToChunks[i];
5449      var isThisOne = chunkIndex + 1 >= sampleToChunk.firstChunk && (i + 1 >= samplesToChunks.length || chunkIndex + 1 < samplesToChunks[i + 1].firstChunk);
5450
5451      if (isThisOne) {
5452        samplesInChunk = sampleToChunk.samplesPerChunk;
5453        break;
5454      }
5455    }
5456
5457    var chunkOffset = chunkOffsets[chunkIndex];
5458
5459    for (var _i = 0; _i < samplesInChunk; _i++) {
5460      var frameEnd = sampleSizes[frames.length]; // if we don't have key samples every frame is a keyframe
5461
5462      var keyframe = !keySamples.length;
5463
5464      if (keySamples.length && keySamples.indexOf(frames.length + 1) !== -1) {
5465        keyframe = true;
5466      }
5467
5468      var frame = {
5469        keyframe: keyframe,
5470        start: chunkOffset,
5471        end: chunkOffset + frameEnd
5472      };
5473
5474      for (var k = 0; k < timeToSamples.length; k++) {
5475        var _timeToSamples$k = timeToSamples[k],
5476            sampleCount = _timeToSamples$k.sampleCount,
5477            sampleDelta = _timeToSamples$k.sampleDelta;
5478
5479        if (frames.length <= sampleCount) {
5480          // ms to ns
5481          var lastTimestamp = frames.length ? frames[frames.length - 1].timestamp : 0;
5482          frame.timestamp = lastTimestamp + sampleDelta / timescale * 1000;
5483          frame.duration = sampleDelta;
5484          break;
5485        }
5486      }
5487
5488      frames.push(frame);
5489      chunkOffset += frameEnd;
5490    }
5491  }
5492
5493  return frames;
5494};
5495
5496exports.buildFrameTable = buildFrameTable;
5497
5498var addSampleDescription = function addSampleDescription(track, bytes) {
5499  var codec = (0, _byteHelpers.bytesToString)(bytes.subarray(0, 4));
5500
5501  if (track.type === 'video') {
5502    track.info = track.info || {};
5503    track.info.width = bytes[28] << 8 | bytes[29];
5504    track.info.height = bytes[30] << 8 | bytes[31];
5505  } else if (track.type === 'audio') {
5506    track.info = track.info || {};
5507    track.info.channels = bytes[20] << 8 | bytes[21];
5508    track.info.bitDepth = bytes[22] << 8 | bytes[23];
5509    track.info.sampleRate = bytes[28] << 8 | bytes[29];
5510  }
5511
5512  if (codec === 'avc1') {
5513    var avcC = findNamedBox(bytes, 'avcC'); // AVCDecoderConfigurationRecord
5514
5515    codec += "." + (0, _codecHelpers.getAvcCodec)(avcC);
5516    track.info.avcC = avcC; // TODO: do we need to parse all this?
5517
5518    /* {
5519      configurationVersion: avcC[0],
5520      profile: avcC[1],
5521      profileCompatibility: avcC[2],
5522      level: avcC[3],
5523      lengthSizeMinusOne: avcC[4] & 0x3
5524    };
5525     let spsNalUnitCount = avcC[5] & 0x1F;
5526    const spsNalUnits = track.info.avc.spsNalUnits = [];
5527     // past spsNalUnitCount
5528    let offset = 6;
5529     while (spsNalUnitCount--) {
5530      const nalLen = avcC[offset] << 8 | avcC[offset + 1];
5531       spsNalUnits.push(avcC.subarray(offset + 2, offset + 2 + nalLen));
5532       offset += nalLen + 2;
5533    }
5534    let ppsNalUnitCount = avcC[offset];
5535    const ppsNalUnits = track.info.avc.ppsNalUnits = [];
5536     // past ppsNalUnitCount
5537    offset += 1;
5538     while (ppsNalUnitCount--) {
5539      const nalLen = avcC[offset] << 8 | avcC[offset + 1];
5540       ppsNalUnits.push(avcC.subarray(offset + 2, offset + 2 + nalLen));
5541       offset += nalLen + 2;
5542    }*/
5543    // HEVCDecoderConfigurationRecord
5544  } else if (codec === 'hvc1' || codec === 'hev1') {
5545    codec += "." + (0, _codecHelpers.getHvcCodec)(findNamedBox(bytes, 'hvcC'));
5546  } else if (codec === 'mp4a' || codec === 'mp4v') {
5547    var esds = findNamedBox(bytes, 'esds');
5548    var esDescriptor = parseDescriptors(esds.subarray(4))[0];
5549    var decoderConfig = esDescriptor && esDescriptor.descriptors.filter(function (_ref) {
5550      var tag = _ref.tag;
5551      return tag === 0x04;
5552    })[0];
5553
5554    if (decoderConfig) {
5555      // most codecs do not have a further '.'
5556      // such as 0xa5 for ac-3 and 0xa6 for e-ac-3
5557      codec += '.' + (0, _byteHelpers.toHexString)(decoderConfig.oti);
5558
5559      if (decoderConfig.oti === 0x40) {
5560        codec += '.' + (decoderConfig.descriptors[0].bytes[0] >> 3).toString();
5561      } else if (decoderConfig.oti === 0x20) {
5562        codec += '.' + decoderConfig.descriptors[0].bytes[4].toString();
5563      } else if (decoderConfig.oti === 0xdd) {
5564        codec = 'vorbis';
5565      }
5566    } else if (track.type === 'audio') {
5567      codec += '.40.2';
5568    } else {
5569      codec += '.20.9';
5570    }
5571  } else if (codec === 'av01') {
5572    // AV1DecoderConfigurationRecord
5573    codec += "." + (0, _codecHelpers.getAv1Codec)(findNamedBox(bytes, 'av1C'));
5574  } else if (codec === 'vp09') {
5575    // VPCodecConfigurationRecord
5576    var vpcC = findNamedBox(bytes, 'vpcC'); // https://www.webmproject.org/vp9/mp4/
5577
5578    var profile = vpcC[0];
5579    var level = vpcC[1];
5580    var bitDepth = vpcC[2] >> 4;
5581    var chromaSubsampling = (vpcC[2] & 0x0F) >> 1;
5582    var videoFullRangeFlag = (vpcC[2] & 0x0F) >> 3;
5583    var colourPrimaries = vpcC[3];
5584    var transferCharacteristics = vpcC[4];
5585    var matrixCoefficients = vpcC[5];
5586    codec += "." + (0, _byteHelpers.padStart)(profile, 2, '0');
5587    codec += "." + (0, _byteHelpers.padStart)(level, 2, '0');
5588    codec += "." + (0, _byteHelpers.padStart)(bitDepth, 2, '0');
5589    codec += "." + (0, _byteHelpers.padStart)(chromaSubsampling, 2, '0');
5590    codec += "." + (0, _byteHelpers.padStart)(colourPrimaries, 2, '0');
5591    codec += "." + (0, _byteHelpers.padStart)(transferCharacteristics, 2, '0');
5592    codec += "." + (0, _byteHelpers.padStart)(matrixCoefficients, 2, '0');
5593    codec += "." + (0, _byteHelpers.padStart)(videoFullRangeFlag, 2, '0');
5594  } else if (codec === 'theo') {
5595    codec = 'theora';
5596  } else if (codec === 'spex') {
5597    codec = 'speex';
5598  } else if (codec === '.mp3') {
5599    codec = 'mp4a.40.34';
5600  } else if (codec === 'msVo') {
5601    codec = 'vorbis';
5602  } else if (codec === 'Opus') {
5603    codec = 'opus';
5604    var dOps = findNamedBox(bytes, 'dOps');
5605    track.info.opus = (0, _opusHelpers.parseOpusHead)(dOps); // TODO: should this go into the webm code??
5606    // Firefox requires a codecDelay for opus playback
5607    // see https://bugzilla.mozilla.org/show_bug.cgi?id=1276238
5608
5609    track.info.codecDelay = 6500000;
5610  } else {
5611    codec = codec.toLowerCase();
5612  }
5613  /* eslint-enable */
5614  // flac, ac-3, ec-3, opus
5615
5616
5617  track.codec = codec;
5618};
5619
5620exports.addSampleDescription = addSampleDescription;
5621
5622var parseTracks = function parseTracks(bytes, frameTable) {
5623  if (frameTable === void 0) {
5624    frameTable = true;
5625  }
5626
5627  bytes = (0, _byteHelpers.toUint8)(bytes);
5628  var traks = findBox(bytes, ['moov', 'trak'], true);
5629  var tracks = [];
5630  traks.forEach(function (trak) {
5631    var track = {
5632      bytes: trak
5633    };
5634    var mdia = findBox(trak, ['mdia'])[0];
5635    var hdlr = findBox(mdia, ['hdlr'])[0];
5636    var trakType = (0, _byteHelpers.bytesToString)(hdlr.subarray(8, 12));
5637
5638    if (trakType === 'soun') {
5639      track.type = 'audio';
5640    } else if (trakType === 'vide') {
5641      track.type = 'video';
5642    } else {
5643      track.type = trakType;
5644    }
5645
5646    var tkhd = findBox(trak, ['tkhd'])[0];
5647
5648    if (tkhd) {
5649      var view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
5650      var tkhdVersion = view.getUint8(0);
5651      track.number = tkhdVersion === 0 ? view.getUint32(12) : view.getUint32(20);
5652    }
5653
5654    var mdhd = findBox(mdia, ['mdhd'])[0];
5655
5656    if (mdhd) {
5657      // mdhd is a FullBox, meaning it will have its own version as the first byte
5658      var version = mdhd[0];
5659      var index = version === 0 ? 12 : 20;
5660      track.timescale = (mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]) >>> 0;
5661    }
5662
5663    var stbl = findBox(mdia, ['minf', 'stbl'])[0];
5664    var stsd = findBox(stbl, ['stsd'])[0];
5665    var descriptionCount = (0, _byteHelpers.bytesToNumber)(stsd.subarray(4, 8));
5666    var offset = 8; // add codec and codec info
5667
5668    while (descriptionCount--) {
5669      var len = (0, _byteHelpers.bytesToNumber)(stsd.subarray(offset, offset + 4));
5670      var sampleDescriptor = stsd.subarray(offset + 4, offset + 4 + len);
5671      addSampleDescription(track, sampleDescriptor);
5672      offset += 4 + len;
5673    }
5674
5675    if (frameTable) {
5676      track.frameTable = buildFrameTable(stbl, track.timescale);
5677    } // codec has no sub parameters
5678
5679
5680    tracks.push(track);
5681  });
5682  return tracks;
5683};
5684
5685exports.parseTracks = parseTracks;
5686
5687var parseMediaInfo = function parseMediaInfo(bytes) {
5688  var mvhd = findBox(bytes, ['moov', 'mvhd'], true)[0];
5689
5690  if (!mvhd || !mvhd.length) {
5691    return;
5692  }
5693
5694  var info = {}; // ms to ns
5695  // mvhd v1 has 8 byte duration and other fields too
5696
5697  if (mvhd[0] === 1) {
5698    info.timestampScale = (0, _byteHelpers.bytesToNumber)(mvhd.subarray(20, 24));
5699    info.duration = (0, _byteHelpers.bytesToNumber)(mvhd.subarray(24, 32));
5700  } else {
5701    info.timestampScale = (0, _byteHelpers.bytesToNumber)(mvhd.subarray(12, 16));
5702    info.duration = (0, _byteHelpers.bytesToNumber)(mvhd.subarray(16, 20));
5703  }
5704
5705  info.bytes = mvhd;
5706  return info;
5707};
5708
5709exports.parseMediaInfo = parseMediaInfo;
5710},{"./byte-helpers.js":11,"./codec-helpers.js":12,"./opus-helpers.js":22}],21:[function(require,module,exports){
5711"use strict";
5712
5713Object.defineProperty(exports, "__esModule", {
5714  value: true
5715});
5716exports.findH265Nal = exports.findH264Nal = exports.findNal = exports.discardEmulationPreventionBytes = exp
5716orts.EMULATION_PREVENTION = exports.NAL_TYPE_TWO = exports.NAL_TYPE_ONE = void 0;
5717
5718var _byteHelpers = require("./byte-helpers.js");
5719
5720var NAL_TYPE_ONE = (0, _byteHelpers.toUint8)([0x00, 0x00, 0x00, 0x01]);
5721exports.NAL_TYPE_ONE = NAL_TYPE_ONE;
5722var NAL_TYPE_TWO = (0, _byteHelpers.toUint8)([0x00, 0x00, 0x01]);
5723exports.NAL_TYPE_TWO = NAL_TYPE_TWO;
5724var EMULATION_PREVENTION = (0, _byteHelpers.toUint8)([0x00, 0x00, 0x03]);
5725/**
5726 * Expunge any "Emulation Prevention" bytes from a "Raw Byte
5727 * Sequence Payload"
5728 *
5729 * @param data {Uint8Array} the bytes of a RBSP from a NAL
5730 * unit
5731 * @return {Uint8Array} the RBSP without any Emulation
5732 * Prevention Bytes
5733 */
5734
5735exports.EMULATION_PREVENTION = EMULATION_PREVENTION;
5736
5737var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(bytes) {
5738  var positions = [];
5739  var i = 1; // Find all `Emulation Prevention Bytes`
5740
5741  while (i < bytes.length - 2) {
5742    if ((0, _byteHelpers.bytesMatch)(bytes.subarray(i, i + 3), EMULATION_PREVENTION)) {
5743      positions.push(i + 2);
5744      i++;
5745    }
5746
5747    i++;
5748  } // If no Emulation Prevention Bytes were found just return the original
5749  // array
5750
5751
5752  if (positions.length === 0) {
5753    return bytes;
5754  } // Create a new array to hold the NAL unit data
5755
5756
5757  var newLength = bytes.length - positions.length;
5758  var newData = new Uint8Array(newLength);
5759  var sourceIndex = 0;
5760
5761  for (i = 0; i < newLength; sourceIndex++, i++) {
5762    if (sourceIndex === positions[0]) {
5763      // Skip this byte
5764      sourceIndex++; // Remove this position index
5765
5766      positions.shift();
5767    }
5768
5769    newData[i] = bytes[sourceIndex];
5770  }
5771
5772  return newData;
5773};
5774
5775exports.discardEmulationPreventionBytes = discardEmulationPreventionBytes;
5776
5777var findNal = function findNal(bytes, dataType, types, nalLimit) {
5778  if (nalLimit === void 0) {
5779    nalLimit = Infinity;
5780  }
5781
5782  bytes = (0, _byteHelpers.toUint8)(bytes);
5783  types = [].concat(types);
5784  var i = 0;
5785  var nalStart;
5786  var nalsFound = 0; // keep searching until:
5787  // we reach the end of bytes
5788  // we reach the maximum number of nals they want to seach
5789  // NOTE: that we disregard nalLimit when we have found the start
5790  // of the nal we want so that we can find the end of the nal we want.
5791
5792  while (i < bytes.length && (nalsFound < nalLimit || nalStart)) {
5793    var nalOffset = void 0;
5794
5795    if ((0, _byteHelpers.bytesMatch)(bytes.subarray(i), NAL_TYPE_ONE)) {
5796      nalOffset = 4;
5797    } else if ((0, _byteHelpers.bytesMatch)(bytes.subarray(i), NAL_TYPE_TWO)) {
5798      nalOffset = 3;
5799    } // we are unsynced,
5800    // find the next nal unit
5801
5802
5803    if (!nalOffset) {
5804      i++;
5805      continue;
5806    }
5807
5808    nalsFound++;
5809
5810    if (nalStart) {
5811      return discardEmulationPreventionBytes(bytes.subarray(nalStart, i));
5812    }
5813
5814    var nalType = void 0;
5815
5816    if (dataType === 'h264') {
5817      nalType = bytes[i + nalOffset] & 0x1f;
5818    } else if (dataType === 'h265') {
5819      nalType = bytes[i + nalOffset] >> 1 & 0x3f;
5820    }
5821
5822    if (types.indexOf(nalType) !== -1) {
5823      nalStart = i + nalOffset;
5824    } // nal header is 1 length for h264, and 2 for h265
5825
5826
5827    i += nalOffset + (dataType === 'h264' ? 1 : 2);
5828  }
5829
5830  return bytes.subarray(0, 0);
5831};
5832
5833exports.findNal = findNal;
5834
5835var findH264Nal = function findH264Nal(bytes, type, nalLimit) {
5836  return findNal(bytes, 'h264', type, nalLimit);
5837};
5838
5839exports.findH264Nal = findH264Nal;
5840
5841var findH265Nal = function findH265Nal(bytes, type, nalLimit) {
5842  return findNal(bytes, 'h265', type, nalLimit);
5843};
5844
5845exports.findH265Nal = findH265Nal;
5846},{"./byte-helpers.js":11}],22:[function(require,module,exports){
5847"use strict";
5848
5849Object.defineProperty(exports, "__esModule", {
5850  value: true
5851});
5852exports.setOpusHead = exports.parseOpusHead = exports.OPUS_HEAD = void 0;
5853var OPUS_HEAD = new Uint8Array([// O, p, u, s
58540x4f, 0x70, 0x75, 0x73, // H, e, a, d
58550x48, 0x65, 0x61, 0x64]); // https://wiki.xiph.org/OggOpus
5856// https://vfrmaniac.fushizen.eu/contents/opus_in_isobmff.html
5857// https://opus-codec.org/docs/opusfile_api-0.7/structOpusHead.html
5858
5859exports.OPUS_HEAD = OPUS_HEAD;
5860
5861var parseOpusHead = function parseOpusHead(bytes) {
5862  var view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
5863  var version = view.getUint8(0); // version 0, from mp4, does not use littleEndian.
5864
5865  var littleEndian = version !== 0;
5866  var config = {
5867    version: version,
5868    channels: view.getUint8(1),
5869    preSkip: view.getUint16(2, littleEndian),
5870    sampleRate: view.getUint32(4, littleEndian),
5871    outputGain: view.getUint16(8, littleEndian),
5872    channelMappingFamily: view.getUint8(10)
5873  };
5874
5875  if (config.channelMappingFamily > 0 && bytes.length > 10) {
5876    config.streamCount = view.getUint8(11);
5877    config.twoChannelStreamCount = view.getUint8(12);
5878    config.channelMapping = [];
5879
5880    for (var c = 0; c < config.channels; c++) {
5881      config.channelMapping.push(view.getUint8(13 + c));
5882    }
5883  }
5884
5885  return config;
5886};
5887
5888exports.parseOpusHead = parseOpusHead;
5889
5890var setOpusHead = function setOpusHead(config) {
5891  var size = config.channelMappingFamily <= 0 ? 11 : 12 + config.channels;
5892  var view = new DataView(new ArrayBuffer(size));
5893  var littleEndian = config.version !== 0;
5894  view.setUint8(0, config.version);
5895  view.setUint8(1, config.channels);
5896  view.setUint16(2, config.preSkip, littleEndian);
5897  view.setUint32(4, config.sampleRate, littleEndian);
5898  view.setUint16(8, config.outputGain, littleEndian);
5899  view.setUint8(10, config.channelMappingFamily);
5900
5901  if (config.channelMappingFamily > 0) {
5902    view.setUint8(11, config.streamCount);
5903    config.channelMapping.foreach(function (cm, i) {
5904      view.setUint8(12 + i, cm);
5905    });
5906  }
5907
5908  return new Uint8Array(view.buffer);
5909};
5910
5911exports.setOpusHead = setOpusHead;
5912},{}],23:[function(require,module,exports){
5913"use strict";
5914
5915var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
5916
5917Object.defineProperty(exports, "__esModule", {
5918  value: true
5919});
5920exports.default = void 0;
5921
5922var _window = _interopRequireDefault(require("global/window"));
5923
5924var DEFAULT_LOCATION = 'https://example.com';
5925
5926var resolveUrl = function resolveUrl(baseUrl, relativeUrl) {
5927  // return early if we don't need to resolve
5928  if (/^[a-z]+:/i.test(relativeUrl)) {
5929    return relativeUrl;
5930  } // if baseUrl is a data URI, ignore it and resolve everything relative to window.location
5931
5932
5933  if (/^data:/.test(baseUrl)) {
5934    baseUrl = _window.default.location && _window.default.location.href || '';
5935  }
5936
5937  var protocolLess = /^\/\//.test(baseUrl); // remove location if window.location isn't available (i.e. we're in node)
5938  // and if baseUrl isn't an absolute url
5939
5940  var removeLocation = !_window.default.location && !/\/\//i.test(baseUrl); // if the base URL is relative then combine with the current location
5941
5942  baseUrl = new _window.default.URL(baseUrl, _window.default.location || DEFAULT_LOCATION);
5943  var newUrl = new URL(relativeUrl, baseUrl); // if we're a protocol-less url, remove the protocol
5944  // and if we're location-less, remove the location
5945  // otherwise, return the url unmodified
5946
5947  if (removeLocation) {
5948    return newUrl.href.slice(DEFAULT_LOCATION.length);
5949  } else if (protocolLess) {
5950    return newUrl.href.slice(newUrl.protocol.length);
5951  }
5952
5953  return newUrl.href;
5954};
5955
5956var _default = resolveUrl;
5957exports.default = _default;
5958module.exports = exports.default;
5959},{"@babel/runtime/helpers/interopRequireDefault":10,"global/window":39}
vendor: 9,186 bytes, lines 5959-6304
5959],24:[function(require,module,exports){
5960"use strict";
5961
5962Object.defineProperty(exports, "__esModule", {
5963  value: true
5964});
5965exports.default = void 0;
5966
5967/**
5968 * @file stream.js
5969 */
5970
5971/**
5972 * A lightweight readable stream implemention that handles event dispatching.
5973 *
5974 * @class Stream
5975 */
5976var Stream = /*#__PURE__*/function () {
5977  function Stream() {
5978    this.listeners = {};
5979  }
5980  /**
5981   * Add a listener for a specified event type.
5982   *
5983   * @param {string} type the event name
5984   * @param {Function} listener the callback to be invoked when an event of
5985   * the specified type occurs
5986   */
5987
5988
5989  var _proto = Stream.prototype;
5990
5991  _proto.on = function on(type, listener) {
5992    if (!this.listeners[type]) {
5993      this.listeners[type] = [];
5994    }
5995
5996    this.listeners[type].push(listener);
5997  }
5998  /**
5999   * Remove a listener for a specified event type.
6000   *
6001   * @param {string} type the event name
6002   * @param {Function} listener  a function previously registered for this
6003   * type of event through `on`
6004   * @return {boolean} if we could turn it off or not
6005   */
6006  ;
6007
6008  _proto.off = function off(type, listener) {
6009    if (!this.listeners[type]) {
6010      return false;
6011    }
6012
6013    var index = this.listeners[type].indexOf(listener); // TODO: which is better?
6014    // In Video.js we slice listener functions
6015    // on trigger so that it does not mess up the order
6016    // while we loop through.
6017    //
6018    // Here we slice on off so that the loop in trigger
6019    // can continue using it's old reference to loop without
6020    // messing up the order.
6021
6022    this.listeners[type] = this.listeners[type].slice(0);
6023    this.listeners[type].splice(index, 1);
6024    return index > -1;
6025  }
6026  /**
6027   * Trigger an event of the specified type on this stream. Any additional
6028   * arguments to this function are passed as parameters to event listeners.
6029   *
6030   * @param {string} type the event name
6031   */
6032  ;
6033
6034  _proto.trigger = function trigger(type) {
6035    var callbacks = this.listeners[type];
6036
6037    if (!callbacks) {
6038      return;
6039    } // Slicing the arguments on every invocation of this method
6040    // can add a significant amount of overhead. Avoid the
6041    // intermediate object creation for the common case of a
6042    // single callback argument
6043
6044
6045    if (arguments.length === 2) {
6046      var length = callbacks.length;
6047
6048      for (var i = 0; i < length; ++i) {
6049        callbacks[i].call(this, arguments[1]);
6050      }
6051    } else {
6052      var args = Array.prototype.slice.call(arguments, 1);
6053      var _length = callbacks.length;
6054
6055      for (var _i = 0; _i < _length; ++_i) {
6056        callbacks[_i].apply(this, args);
6057      }
6058    }
6059  }
6060  /**
6061   * Destroys the stream and cleans up.
6062   */
6063  ;
6064
6065  _proto.dispose = function dispose() {
6066    this.listeners = {};
6067  }
6068  /**
6069   * Forwards all `data` events on this stream to the destination stream. The
6070   * destination stream should provide a method `push` to receive the data
6071   * events as they arrive.
6072   *
6073   * @param {Stream} destination the stream that will receive all `data` events
6074   * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
6075   */
6076  ;
6077
6078  _proto.pipe = function pipe(destination) {
6079    this.on('data', function (data) {
6080      destination.push(data);
6081    });
6082  };
6083
6084  return Stream;
6085}();
6086
6087exports.default = Stream;
6088module.exports = exports.default;
6089},{}],25:[function(require,module,exports){
6090"use strict";
6091
6092var window = require('global/window');
6093
6094var httpResponseHandler = function httpResponseHandler(callback, decodeResponseBody) {
6095  if (decodeResponseBody === void 0) {
6096    decodeResponseBody = false;
6097  }
6098
6099  return function (err, response, responseBody) {
6100    // if the XHR failed, return that error
6101    if (err) {
6102      callback(err);
6103      return;
6104    } // if the HTTP status code is 4xx or 5xx, the request also failed
6105
6106
6107    if (response.statusCode >= 400 && response.statusCode <= 599) {
6108      var cause = responseBody;
6109
6110      if (decodeResponseBody) {
6111        if (window.TextDecoder) {
6112          var charset = getCharset(response.headers && response.headers['content-type']);
6113
6114          try {
6115            cause = new TextDecoder(charset).decode(responseBody);
6116          } catch (e) {}
6117        } else {
6118          cause = String.fromCharCode.apply(null, new Uint8Array(responseBody));
6119        }
6120      }
6121
6122      callback({
6123        cause: cause
6124      });
6125      return;
6126    } // otherwise, request succeeded
6127
6128
6129    callback(null, responseBody);
6130  };
6131};
6132
6133function getCharset(contentTypeHeader) {
6134  if (contentTypeHeader === void 0) {
6135    contentTypeHeader = '';
6136  }
6137
6138  return contentTypeHeader.toLowerCase().split(';').reduce(function (charset, contentType) {
6139    var _contentType$split = contentType.split('='),
6140        type = _contentType$split[0],
6141        value = _contentType$split[1];
6142
6143    if (type.trim() === 'charset') {
6144      return value.trim();
6145    }
6146
6147    return charset;
6148  }, 'utf-8');
6149}
6150
6151module.exports = httpResponseHandler;
6152},{"global/window":39}],26:[function(require,module,exports){
6153"use strict";
6154
6155var window = require("global/window");
6156
6157var _extends = require("@babel/runtime/helpers/extends");
6158
6159var isFunction = require('is-function');
6160
6161var InterceptorsStorage = require('./interceptors.js');
6162
6163var RetryManager = require("./retry.js");
6164
6165createXHR.httpHandler = require('./http-handler.js');
6166createXHR.requestInterceptorsStorage = new InterceptorsStorage();
6167createXHR.responseInterceptorsStorage = new InterceptorsStorage();
6168createXHR.retryManager = new RetryManager();
6169/**
6170 * @license
6171 * slighly modified parse-headers 2.0.2 <https://github.com/kesla/parse-headers/>
6172 * Copyright (c) 2014 David Björklund
6173 * Available under the MIT license
6174 * <https://github.com/kesla/parse-headers/blob/master/LICENCE>
6175 */
6176
6177var parseHeaders = function parseHeaders(headers) {
6178  var result = {};
6179
6180  if (!headers) {
6181    return result;
6182  }
6183
6184  headers.trim().split('\n').forEach(function (row) {
6185    var index = row.indexOf(':');
6186    var key = row.slice(0, index).trim().toLowerCase();
6187    var value = row.slice(index + 1).trim();
6188
6189    if (typeof result[key] === 'undefined') {
6190      result[key] = value;
6191    } else if (Array.isArray(result[key])) {
6192      result[key].push(value);
6193    } else {
6194      result[key] = [result[key], value];
6195    }
6196  });
6197  return result;
6198};
6199
6200module.exports = createXHR; // Allow use of default import syntax in TypeScript
6201
6202module.exports.default = createXHR;
6203createXHR.XMLHttpRequest = window.XMLHttpRequest || noop;
6204createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window.XDomainRequest;
6205forEachArray(["get", "put", "post", "patch", "head", "delete"], function (method) {
6206  createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
6207    options = initParams(uri, options, callback);
6208    options.method = method.toUpperCase();
6209    return _createXHR(options);
6210  };
6211});
6212
6213function forEachArray(array, iterator) {
6214  for (var i = 0; i < array.length; i++) {
6215    iterator(array[i]);
6216  }
6217}
6218
6219function isEmpty(obj) {
6220  for (var i in obj) {
6221    if (obj.hasOwnProperty(i)) return false;
6222  }
6223
6224  return true;
6225}
6226
6227function initParams(uri, options, callback) {
6228  var params = uri;
6229
6230  if (isFunction(options)) {
6231    callback = options;
6232
6233    if (typeof uri === "string") {
6234      params = {
6235        uri: uri
6236      };
6237    }
6238  } else {
6239    params = _extends({}, options, {
6240      uri: uri
6241    });
6242  }
6243
6244  params.callback = callback;
6245  return params;
6246}
6247
6248function createXHR(uri, options, callback) {
6249  options = initParams(uri, options, callback);
6250  return _createXHR(options);
6251}
6252
6253function _createXHR(options) {
6254  if (typeof options.callback === "undefined") {
6255    throw new Error("callback argument missing");
6256  } // call all registered request interceptors for a given request type:
6257
6258
6259  if (options.requestType && createXHR.requestInterceptorsStorage.getIsEnabled()) {
6260    var requestInterceptorPayload = {
6261      uri: options.uri || options.url,
6262      headers: options.headers || {},
6263      body: options.body,
6264      metadata: options.metadata || {},
6265      retry: options.retry,
6266      timeout: options.timeout
6267    };
6268    var updatedPayload = createXHR.requestInterceptorsStorage.execute(options.requestType, requestInterceptorPayload);
6269    options.uri = updatedPayload.uri;
6270    options.headers = updatedPayload.headers;
6271    options.body = updatedPayload.body;
6272    options.metadata = updatedPayload.metadata;
6273    options.retry = updatedPayload.retry;
6274    options.timeout = updatedPayload.timeout;
6275  }
6276
6277  var called = false;
6278
6279  var callback = function cbOnce(err, response, body) {
6280    if (!called) {
6281      called = true;
6282      options.callback(err, response, body);
6283    }
6284  };
6285
6286  function readystatechange() {
6287    // do not call load 2 times when response interceptors are enabled
6288    // why do we even need this 2nd load?
6289    if (xhr.readyState === 4 && !createXHR.responseInterceptorsStorage.getIsEnabled()) {
6290      setTimeout(loadFunc, 0);
6291    }
6292  }
6293
6294  function getBody() {
6295    // Chrome with requestType=blob throws errors arround when even testing access to responseText
6296    var body = undefined;
6297
6298    if (xhr.response) {
6299      body = xhr.response;
6300    } else {
6301      body = xhr.responseText || getXml(xhr);
6302    }
6303
6304    if (isJson) {
vendor: 14,664 bytes, lines 6305-6779
6305      try {
6306        body = JSON.parse(body);
6307      } catch (e) {}
6308    }
6309
6310    return body;
6311  }
6312
6313  function errorFunc(evt) {
6314    clearTimeout(timeoutTimer);
6315    clearTimeout(options.retryTimeout);
6316
6317    if (!(evt instanceof Error)) {
6318      evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
6319    }
6320
6321    evt.statusCode = 0; // we would like to retry on error:
6322
6323    if (!aborted && createXHR.retryManager.getIsEnabled() && options.retry && options.retry.shouldRetry()) {
6324      options.retryTimeout = setTimeout(function () {
6325        options.retry.moveToNextAttempt(); // we want to re-use the same options and the same xhr object:
6326
6327        options.xhr = xhr;
6328
6329        _createXHR(options);
6330      }, options.retry.getCurrentFuzzedDelay());
6331      return;
6332    } // call all registered response interceptors for a given request type:
6333
6334
6335    if (options.requestType && createXHR.responseInterceptorsStorage.getIsEnabled()) {
6336      var responseInterceptorPayload = {
6337        headers: failureResponse.headers || {},
6338        body: failureResponse.body,
6339        responseUrl: xhr.responseURL,
6340        responseType: xhr.responseType
6341      };
6342
6343      var _updatedPayload = createXHR.responseInterceptorsStorage.execute(options.requestType, responseInterceptorPayload);
6344
6345      failureResponse.body = _updatedPayload.body;
6346      failureResponse.headers = _updatedPayload.headers;
6347    }
6348
6349    return callback(evt, failureResponse);
6350  } // will load the data & process the response in a special response object
6351
6352
6353  function loadFunc() {
6354    if (aborted) return;
6355    var status;
6356    clearTimeout(timeoutTimer);
6357    clearTimeout(options.retryTimeout);
6358
6359    if (options.useXDR && xhr.status === undefined) {
6360      //IE8 CORS GET successful response doesn't have a status field, but body is fine
6361      status = 200;
6362    } else {
6363      status = xhr.status === 1223 ? 204 : xhr.status;
6364    }
6365
6366    var response = failureResponse;
6367    var err = null;
6368
6369    if (status !== 0) {
6370      response = {
6371        body: getBody(),
6372        statusCode: status,
6373        method: method,
6374        headers: {},
6375        url: uri,
6376        rawRequest: xhr
6377      };
6378
6379      if (xhr.getAllResponseHeaders) {
6380        //remember xhr can in fact be XDR for CORS in IE
6381        response.headers = parseHeaders(xhr.getAllResponseHeaders());
6382      }
6383    } else {
6384      err = new Error("Internal XMLHttpRequest Error");
6385    } // call all registered response interceptors for a given request type:
6386
6387
6388    if (options.requestType && createXHR.responseInterceptorsStorage.getIsEnabled()) {
6389      var responseInterceptorPayload = {
6390        headers: response.headers || {},
6391        body: response.body,
6392        responseUrl: xhr.responseURL,
6393        responseType: xhr.responseType
6394      };
6395
6396      var _updatedPayload2 = createXHR.responseInterceptorsStorage.execute(options.requestType, responseInterceptorPayload);
6397
6398      response.body = _updatedPayload2.body;
6399      response.headers = _updatedPayload2.headers;
6400    }
6401
6402    return callback(err, response, response.body);
6403  }
6404
6405  var xhr = options.xhr || null;
6406
6407  if (!xhr) {
6408    if (options.cors || options.useXDR) {
6409      xhr = new createXHR.XDomainRequest();
6410    } else {
6411      xhr = new createXHR.XMLHttpRequest();
6412    }
6413  }
6414
6415  var key;
6416  var aborted;
6417  var uri = xhr.url = options.uri || options.url;
6418  var method = xhr.method = options.method || "GET";
6419  var body = options.body || options.data;
6420  var headers = xhr.headers = options.headers || {};
6421  var sync = !!options.sync;
6422  var isJson = false;
6423  var timeoutTimer;
6424  var failureResponse = {
6425    body: undefined,
6426    headers: {},
6427    statusCode: 0,
6428    method: method,
6429    url: uri,
6430    rawRequest: xhr
6431  };
6432
6433  if ("json" in options && options.json !== false) {
6434    isJson = true;
6435    headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
6436
6437    if (method !== "GET" && method !== "HEAD") {
6438      headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
6439
6440      body = JSON.stringify(options.json === true ? body : options.json);
6441    }
6442  }
6443
6444  xhr.onreadystatechange = readystatechange;
6445  xhr.onload = loadFunc;
6446  xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function.
6447
6448  xhr.onprogress = function () {// IE must die
6449  };
6450
6451  xhr.onabort = function () {
6452    aborted = true;
6453    clearTimeout(options.retryTimeout);
6454  };
6455
6456  xhr.ontimeout = errorFunc;
6457  xhr.open(method, uri, !sync, options.username, options.password); //has to be after open
6458
6459  if (!sync) {
6460    xhr.withCredentials = !!options.withCredentials;
6461  } // Cannot set timeout with sync request
6462  // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
6463  // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
6464
6465
6466  if (!sync && options.timeout > 0) {
6467    timeoutTimer = setTimeout(function () {
6468      if (aborted) return;
6469      aborted = true; //IE9 may still call readystatechange
6470
6471      xhr.abort("timeout");
6472      var e = new Error("XMLHttpRequest timeout");
6473      e.code = "ETIMEDOUT";
6474      errorFunc(e);
6475    }, options.timeout);
6476  }
6477
6478  if (xhr.setRequestHeader) {
6479    for (key in headers) {
6480      if (headers.hasOwnProperty(key)) {
6481        xhr.setRequestHeader(key, headers[key]);
6482      }
6483    }
6484  } else if (options.headers && !isEmpty(options.headers)) {
6485    throw new Error("Headers cannot be set on an XDomainRequest object");
6486  }
6487
6488  if ("responseType" in options) {
6489    xhr.responseType = options.responseType;
6490  }
6491
6492  if ("beforeSend" in options && typeof options.beforeSend === "function") {
6493    options.beforeSend(xhr);
6494  } // Microsoft Edge browser sends "undefined" when send is called with undefined value.
6495  // XMLHttpRequest spec says to pass null as body to indicate no body
6496  // See https://github.com/naugtur/xhr/issues/100.
6497
6498
6499  xhr.send(body || null);
6500  return xhr;
6501}
6502
6503function getXml(xhr) {
6504  // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException"
6505  // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML.
6506  try {
6507    if (xhr.responseType === "document") {
6508      return xhr.responseXML;
6509    }
6510
6511    var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
6512
6513    if (xhr.responseType === "" && !firefoxBugTakenEffect) {
6514      return xhr.responseXML;
6515    }
6516  } catch (e) {}
6517
6518  return null;
6519}
6520
6521function noop() {}
6522},{"./http-handler.js":25,"./interceptors.js":27,"./retry.js":28,"@babel/runtime/helpers/extends":9,"global/window":39,"is-function":41}],27:[function(require,module,exports){
6523"use strict";
6524
6525function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
6526
6527function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
6528
6529function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
6530
6531var InterceptorsStorage = /*#__PURE__*/function () {
6532  function InterceptorsStorage() {
6533    this.typeToInterceptorsMap_ = new Map();
6534    this.enabled_ = false;
6535  }
6536
6537  var _proto = InterceptorsStorage.prototype;
6538
6539  _proto.getIsEnabled = function getIsEnabled() {
6540    return this.enabled_;
6541  };
6542
6543  _proto.enable = function enable() {
6544    this.enabled_ = true;
6545  };
6546
6547  _proto.disable = function disable() {
6548    this.enabled_ = false;
6549  };
6550
6551  _proto.reset = function reset() {
6552    this.typeToInterceptorsMap_ = new Map();
6553    this.enabled_ = false;
6554  };
6555
6556  _proto.addInterceptor = function addInterceptor(type, interceptor) {
6557    if (!this.typeToInterceptorsMap_.has(type)) {
6558      this.typeToInterceptorsMap_.set(type, new Set());
6559    }
6560
6561    var interceptorsSet = this.typeToInterceptorsMap_.get(type);
6562
6563    if (interceptorsSet.has(interceptor)) {
6564      // already have this interceptor
6565      return false;
6566    }
6567
6568    interceptorsSet.add(interceptor);
6569    return true;
6570  };
6571
6572  _proto.removeInterceptor = function removeInterceptor(type, interceptor) {
6573    var interceptorsSet = this.typeToInterceptorsMap_.get(type);
6574
6575    if (interceptorsSet && interceptorsSet.has(interceptor)) {
6576      interceptorsSet.delete(interceptor);
6577      return true;
6578    }
6579
6580    return false;
6581  };
6582
6583  _proto.clearInterceptorsByType = function clearInterceptorsByType(type) {
6584    var interceptorsSet = this.typeToInterceptorsMap_.get(type);
6585
6586    if (!interceptorsSet) {
6587      return false;
6588    }
6589
6590    this.typeToInterceptorsMap_.delete(type);
6591    this.typeToInterceptorsMap_.set(type, new Set());
6592    return true;
6593  };
6594
6595  _proto.clear = function clear() {
6596    if (!this.typeToInterceptorsMap_.size) {
6597      return false;
6598    }
6599
6600    this.typeToInterceptorsMap_ = new Map();
6601    return true;
6602  };
6603
6604  _proto.getForType = function getForType(type) {
6605    return this.typeToInterceptorsMap_.get(type) || new Set();
6606  };
6607
6608  _proto.execute = function execute(type, payload) {
6609    var interceptors = this.getForType(type);
6610
6611    for (var _iterator = _createForOfIteratorHelperLoose(interceptors), _step; !(_step = _iterator()).done;) {
6612      var interceptor = _step.value;
6613
6614      try {
6615        payload = interceptor(payload);
6616      } catch (e) {//ignore
6617      }
6618    }
6619
6620    return payload;
6621  };
6622
6623  return InterceptorsStorage;
6624}();
6625
6626module.exports = InterceptorsStorage;
6627},{}],28:[function(require,module,exports){
6628"use strict";
6629
6630var RetryManager = /*#__PURE__*/function () {
6631  function RetryManager() {
6632    this.maxAttempts_ = 1;
6633    this.delayFactor_ = 0.1;
6634    this.fuzzFactor_ = 0.1;
6635    this.initialDelay_ = 1000;
6636    this.enabled_ = false;
6637  }
6638
6639  var _proto = RetryManager.prototype;
6640
6641  _proto.getIsEnabled = function getIsEnabled() {
6642    return this.enabled_;
6643  };
6644
6645  _proto.enable = function enable() {
6646    this.enabled_ = true;
6647  };
6648
6649  _proto.disable = function disable() {
6650    this.enabled_ = false;
6651  };
6652
6653  _proto.reset = function reset() {
6654    this.maxAttempts_ = 1;
6655    this.delayFactor_ = 0.1;
6656    this.fuzzFactor_ = 0.1;
6657    this.initialDelay_ = 1000;
6658    this.enabled_ = false;
6659  };
6660
6661  _proto.getMaxAttempts = function getMaxAttempts() {
6662    return this.maxAttempts_;
6663  };
6664
6665  _proto.setMaxAttempts = function setMaxAttempts(maxAttempts) {
6666    this.maxAttempts_ = maxAttempts;
6667  };
6668
6669  _proto.getDelayFactor = function getDelayFactor() {
6670    return this.delayFactor_;
6671  };
6672
6673  _proto.setDelayFactor = function setDelayFactor(delayFactor) {
6674    this.delayFactor_ = delayFactor;
6675  };
6676
6677  _proto.getFuzzFactor = function getFuzzFactor() {
6678    return this.fuzzFactor_;
6679  };
6680
6681  _proto.setFuzzFactor = function setFuzzFactor(fuzzFactor) {
6682    this.fuzzFactor_ = fuzzFactor;
6683  };
6684
6685  _proto.getInitialDelay = function getInitialDelay() {
6686    return this.initialDelay_;
6687  };
6688
6689  _proto.setInitialDelay = function setInitialDelay(initialDelay) {
6690    this.initialDelay_ = initialDelay;
6691  };
6692
6693  _proto.createRetry = function createRetry(_temp) {
6694    var _ref = _temp === void 0 ? {} : _temp,
6695        maxAttempts = _ref.maxAttempts,
6696        delayFactor = _ref.delayFactor,
6697        fuzzFactor = _ref.fuzzFactor,
6698        initialDelay = _ref.initialDelay;
6699
6700    return new Retry({
6701      maxAttempts: maxAttempts || this.maxAttempts_,
6702      delayFactor: delayFactor || this.delayFactor_,
6703      fuzzFactor: fuzzFactor || this.fuzzFactor_,
6704      initialDelay: initialDelay || this.initialDelay_
6705    });
6706  };
6707
6708  return RetryManager;
6709}();
6710
6711var Retry = /*#__PURE__*/function () {
6712  function Retry(options) {
6713    this.maxAttempts_ = options.maxAttempts;
6714    this.delayFactor_ = options.delayFactor;
6715    this.fuzzFactor_ = options.fuzzFactor;
6716    this.currentDelay_ = options.initialDelay;
6717    this.currentAttempt_ = 1;
6718  }
6719
6720  var _proto2 = Retry.prototype;
6721
6722  _proto2.moveToNextAttempt = function moveToNextAttempt() {
6723    this.currentAttempt_++;
6724    var delayDelta = this.currentDelay_ * this.delayFactor_;
6725    this.currentDelay_ = this.currentDelay_ + delayDelta;
6726  };
6727
6728  _proto2.shouldRetry = function shouldRetry() {
6729    return this.currentAttempt_ < this.maxAttempts_;
6730  };
6731
6732  _proto2.getCurrentDelay = function getCurrentDelay() {
6733    return this.currentDelay_;
6734  };
6735
6736  _proto2.getCurrentMinPossibleDelay = function getCurrentMinPossibleDelay() {
6737    return (1 - this.fuzzFactor_) * this.currentDelay_;
6738  };
6739
6740  _proto2.getCurrentMaxPossibleDelay = function getCurrentMaxPossibleDelay() {
6741    return (1 + this.fuzzFactor_) * this.currentDelay_;
6742  }
6743  /**
6744   * For example fuzzFactor is 0.1
6745   * This means ±10% deviation
6746   * So if we have delay as 1000
6747   * This function can generate any value from 900 to 1100
6748   */
6749  ;
6750
6751  _proto2.getCurrentFuzzedDelay = function getCurrentFuzzedDelay() {
6752    var lowValue = this.getCurrentMinPossibleDelay();
6753    var highValue = this.getCurrentMaxPossibleDelay();
6754    return lowValue + Math.random() * (highValue - lowValue);
6755  };
6756
6757  return Retry;
6758}();
6759
6760module.exports = RetryManager;
6761},{}],29:[function(require,module,exports){
6762'use strict'
6763
6764/**
6765 * Ponyfill for `Array.prototype.find` which is only available in ES6 runtimes.
6766 *
6767 * Works with anything that has a `length` property and index access properties, including NodeList.
6768 *
6769 * @template {unknown} T
6770 * @param {Array<T> | ({length:number, [number]: T})} list
6771 * @param {function (item: T, index: number, list:Array<T> | ({length:number, [number]: T})):boolean} predicate
6772 * @param {Partial<Pick<ArrayConstructor['prototype'], 'find'>>?} ac `Array.prototype` by default,
6773 * 				allows injecting a custom implementation in tests
6774 * @returns {T | undefined}
6775 *
6776 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
6777 * @see https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.find
6778 */
6779function find(list, predicate, ac) {
vendor: 4,714 bytes, lines 6780-6920
6780	if (ac === undefined) {
6781		ac = Array.prototype;
6782	}
6783	if (list && typeof ac.find === 'function') {
6784		return ac.find.call(list, predicate);
6785	}
6786	for (var i = 0; i < list.length; i++) {
6787		if (Object.prototype.hasOwnProperty.call(list, i)) {
6788			var item = list[i];
6789			if (predicate.call(undefined, item, i, list)) {
6790				return item;
6791			}
6792		}
6793	}
6794}
6795
6796/**
6797 * "Shallow freezes" an object to render it immutable.
6798 * Uses `Object.freeze` if available,
6799 * otherwise the immutability is only in the type.
6800 *
6801 * Is used to create "enum like" objects.
6802 *
6803 * @template T
6804 * @param {T} object the object to freeze
6805 * @param {Pick<ObjectConstructor, 'freeze'> = Object} oc `Object` by default,
6806 * 				allows to inject custom object constructor for tests
6807 * @returns {Readonly<T>}
6808 *
6809 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
6810 */
6811function freeze(object, oc) {
6812	if (oc === undefined) {
6813		oc = Object
6814	}
6815	return oc && typeof oc.freeze === 'function' ? oc.freeze(object) : object
6816}
6817
6818/**
6819 * Since we can not rely on `Object.assign` we provide a simplified version
6820 * that is sufficient for our needs.
6821 *
6822 * @param {Object} target
6823 * @param {Object | null | undefined} source
6824 *
6825 * @returns {Object} target
6826 * @throws TypeError if target is not an object
6827 *
6828 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
6829 * @see https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.assign
6830 */
6831function assign(target, source) {
6832	if (target === null || typeof target !== 'object') {
6833		throw new TypeError('target is not an object')
6834	}
6835	for (var key in source) {
6836		if (Object.prototype.hasOwnProperty.call(source, key)) {
6837			target[key] = source[key]
6838		}
6839	}
6840	return target
6841}
6842
6843/**
6844 * All mime types that are allowed as input to `DOMParser.parseFromString`
6845 *
6846 * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02 MDN
6847 * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#domparsersupportedtype WHATWG HTML Spec
6848 * @see DOMParser.prototype.parseFromString
6849 */
6850var MIME_TYPE = freeze({
6851	/**
6852	 * `text/html`, the only mime type that triggers treating an XML document as HTML.
6853	 *
6854	 * @see DOMParser.SupportedType.isHTML
6855	 * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration
6856	 * @see https://en.wikipedia.org/wiki/HTML Wikipedia
6857	 * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN
6858	 * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring WHATWG HTML Spec
6859	 */
6860	HTML: 'text/html',
6861
6862	/**
6863	 * Helper method to check a mime type if it indicates an HTML document
6864	 *
6865	 * @param {string} [value]
6866	 * @returns {boolean}
6867	 *
6868	 * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration
6869	 * @see https://en.wikipedia.org/wiki/HTML Wikipedia
6870	 * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN
6871	 * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring 	 */
6872	isHTML: function (value) {
6873		return value === MIME_TYPE.HTML
6874	},
6875
6876	/**
6877	 * `application/xml`, the standard mime type for XML documents.
6878	 *
6879	 * @see https://www.iana.org/assignments/media-types/application/xml IANA MimeType registration
6880	 * @see https://tools.ietf.org/html/rfc7303#section-9.1 RFC 7303
6881	 * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia
6882	 */
6883	XML_APPLICATION: 'application/xml',
6884
6885	/**
6886	 * `text/html`, an alias for `application/xml`.
6887	 *
6888	 * @see https://tools.ietf.org/html/rfc7303#section-9.2 RFC 7303
6889	 * @see https://www.iana.org/assignments/media-types/text/xml IANA MimeType registration
6890	 * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia
6891	 */
6892	XML_TEXT: 'text/xml',
6893
6894	/**
6895	 * `application/xhtml+xml`, indicates an XML document that has the default HTML namespace,
6896	 * but is parsed as an XML document.
6897	 *
6898	 * @see https://www.iana.org/assignments/media-types/application/xhtml+xml IANA MimeType registration
6899	 * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument WHATWG DOM Spec
6900	 * @see https://en.wikipedia.org/wiki/XHTML Wikipedia
6901	 */
6902	XML_XHTML_APPLICATION: 'application/xhtml+xml',
6903
6904	/**
6905	 * `image/svg+xml`,
6906	 *
6907	 * @see https://www.iana.org/assignments/media-types/image/svg+xml IANA MimeType registration
6908	 * @see https://www.w3.org/TR/SVG11/ W3C SVG 1.1
6909	 * @see https://en.wikipedia.org/wiki/Scalable_Vector_Graphics Wikipedia
6910	 */
6911	XML_SVG_IMAGE: 'image/svg+xml',
6912})
6913
6914/**
6915 * Namespaces that are used in this code base.
6916 *
6917 * @see http://www.w3.org/TR/REC-xml-names
6918 */
6919var NAMESPACE = freeze({
6920	/**
6921	 * The XHTML namespace.
6922	 *
6923	 * @see http://www.w3.org/1999/xhtml
6924	 */
6925	HTML: 'http://www.w3.org/1999/xhtml',
6926
6927	/**
6928	 * Checks if `uri` equals `NAMESPACE.HTML`.
6929	 *
6930	 * @param {string} [uri]
6931	 *
6932	 * @see NAMESPACE.HTML
6933	 */
6934	isHTML: function (uri) {
6935		return uri === NAMESPACE.HTML
6936	},
6937
6938	/**
6939	 * The SVG namespace.
6940	 *
6941	 * @see http://www.w3.org/2000/svg
6942	 */
6943	SVG: 'http://www.w3.org/2000/svg',
6944
6945	/**
6946	 * The `xml:` namespace.
6947	 *
6948	 * @see http://www.w3.org/XML/1998/namespace
6949	 */
6950	XML: 'http://www.w3.org/XML/1998/namespace',
6951
6952	/**
6953	 * The `xmlns:` namespace
6954	 *
6955	 * @see https://www.w3.org/2000/xmlns/
6956	 */
6957	XMLNS: 'http://www.w3.org/2000/xmlns/',
6958})
6959
6960//[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]
6961//[4a]   	NameChar	   ::=   	NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
6962//[5]   	Name	   ::=   	NameStartChar (NameChar)*
6963var 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
6964var nameChar = new RegExp("[\\-\\.0-9"+nameStartChar.source.slice(1,-1)+"\\u00B7\\u0300-\\u036F\\u203F-\\u2040]");
6965var tagNamePattern = new RegExp('^'+nameStartChar.source+nameChar.source+'*(?:\:'+nameStartChar.source+nameChar.source+'*)?$');
6966//var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/
6967
6968exports.assign = assign;
6969exports.find = find;
6970exports.freeze = freeze;
6971exports.MIME_TYPE = MIME_TYPE;
6972exports.NAMESPACE = NAMESPACE;
6973exports.nameStartChar = nameStartChar;
6974exports.nameChar = nameChar;
6975exports.tagNamePattern = tagNamePattern;
6976
6977},{}],30:[function(require,module,exports){
6978var conventions = require("./conventions");
6979var dom = require('./dom')
6980var entities = require('./entities');
6981var sax = require('./sax');
6982
6983var DOMImplementation = dom.DOMImplementation;
6984
6985var NAMESPACE = conventions.NAMESPACE;
6986
6987var ParseError = sax.ParseError;
6988var XMLReader = sax.XMLReader;
6989
6990/**
6991 * Normalizes line ending according to https://www.w3.org/TR/xml11/#sec-line-ends:
6992 *
6993 * > XML parsed entities are often stored in computer files which,
6994 * > for editing convenience, are organized into lines.
6995 * > These lines are typically separated by some combination
6996 * > of the characters CARRIAGE RETURN (#xD) and LINE FEED (#xA).
6997 * >
6998 * > To simplify the tasks of applications, the XML processor must behave
6999 * > as if it normalized all line breaks in external parsed entities (including the document entity)
7000 * > on input, before parsing, by translating all of the following to a single #xA character:
7001 * >
7002 * > 1. the two-character sequence #xD #xA
7003 * > 2. the two-character sequence #xD #x85
7004 * > 3. the single character #x85
7005 * > 4. the single character #x2028
7006 * > 5. any #xD character that is not immediately followed by #xA or #x85.
7007 *
7008 * @param {string} input
7009 * @returns {string}
7010 */
7011function normalizeLineEndings(input) {
7012	return input
7013		.replace(/\r[\n\u0085]/g, '\n')
7014		.replace(/[\r\u0085\u2028]/g, '\n')
7015}
7016
7017/**
7018 * @typedef Locator
7019 * @property {number} [columnNumber]
7020 * @property {number} [lineNumber]
7021 */
7022
7023/**
7024 * @typedef DOMParserOptions
7025 * @property {DOMHandler} [domBuilder]
7026 * @property {Function} [errorHandler]
7027 * @property {(string) => string} [normalizeLineEndings] used to replace line endings before parsing
7028 * 						defaults to `normalizeLineEndings`
7029 * @property {Locator} [locator]
7030 * @property {Record<string, string>} [xmlns]
7031 *
7032 * @see normalizeLineEndings
7033 */
7034
7035/**
7036 * The DOMParser interface provides the ability to parse XML or HTML source code
7037 * from a string into a DOM `Document`.
7038 *
7039 * _xmldom is different from the spec in that it allows an `options` parameter,
7040 * to override the default behavior._
7041 *
7042 * @param {DOMParserOptions} [options]
7043 * @constructor
7044 *
7045 * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
7046 * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-parsing-and-serialization
7047 */
7048function DOMParser(options){
7049	this.options = options ||{locator:{}};
7050}
7051
7052DOMParser.prototype.parseFromString = function(source,mimeType){
7053	var options = this.options;
7054	var sax =  new XMLReader();
7055	var domBuilder = options.domBuilder || new DOMHandler();//contentHandler and LexicalHandler
7056	var errorHandler = options.errorHandler;
7057	var locator = options.locator;
7058	var defaultNSMap = options.xmlns||{};
7059	var isHTML = /\/x?html?$/.test(mimeType);//mimeType.toLowerCase().indexOf('html') > -1;
7060  	var entityMap = isHTML ? entities.HTML_ENTITIES : entities.XML_ENTITIES;
7061	if(locator){
7062		domBuilder.setDocumentLocator(locator)
7063	}
7064
7065	sax.errorHandler = buildErrorHandler(errorHandler,domBuilder,locator);
7066	sax.domBuilder = options.domBuilder || domBuilder;
7067	if(isHTML){
7068		defaultNSMap[''] = NAMESPACE.HTML;
7069	}
7070	defaultNSMap.xml = defaultNSMap.xml || NAMESPACE.XML;
7071	var normalize = options.normalizeLineEndings || normalizeLineEndings;
7072	if (source && typeof source === 'string') {
7073		sax.parse(
7074			normalize(source),
7075			defaultNSMap,
7076			entityMap
7077		)
7078	} else {
7079		sax.errorHandler.error('invalid doc source')
7080	}
7081	return domBuilder.doc;
7082}
7083function buildErrorHandler(errorImpl,domBuilder,locator){
7084	if(!errorImpl){
7085		if(domBuilder instanceof DOMHandler){
7086			return domBuilder;
7087		}
7088		errorImpl = domBuilder ;
7089	}
7090	var errorHandler = {}
7091	var isCallback = errorImpl instanceof Function;
7092	locator = locator||{}
7093	function build(key){
7094		var fn = errorImpl[key];
7095		if(!fn && isCallback){
7096			fn = errorImpl.length == 2?function(msg){errorImpl(key,msg)}:errorImpl;
7097		}
7098		errorHandler[key] = fn && function(msg){
7099			fn('[xmldom '+key+']\t'+msg+_locator(locator));
7100		}||function(){};
7101	}
7102	build('warning');
7103	build('error');
7104	build('fatalError');
7105	return errorHandler;
7106}
7107
7108//console.log('#\n\n\n\n\n\n\n####')
7109/**
7110 * +ContentHandler+ErrorHandler
7111 * +LexicalHandler+EntityResolver2
7112 * -DeclHandler-DTDHandler
7113 *
7114 * DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler
7115 * DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2
7116 * @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html
7117 */
7118function DOMHandler() {
7119    this.cdata = false;
vendor: 17,585 bytes, lines 7120-7639
7120}
7121function position(locator,node){
7122	node.lineNumber = locator.lineNumber;
7123	node.columnNumber = locator.columnNumber;
7124}
7125/**
7126 * @see org.xml.sax.ContentHandler#startDocument
7127 * @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html
7128 */
7129DOMHandler.prototype = {
7130	startDocument : function() {
7131    	this.doc = new DOMImplementation().createDocument(null, null, null);
7132    	if (this.locator) {
7133        	this.doc.documentURI = this.locator.systemId;
7134    	}
7135	},
7136	startElement:function(namespaceURI, localName, qName, attrs) {
7137		var doc = this.doc;
7138	    var el = doc.createElementNS(namespaceURI, qName||localName);
7139	    var len = attrs.length;
7140	    appendElement(this, el);
7141	    this.currentElement = el;
7142
7143		this.locator && position(this.locator,el)
7144	    for (var i = 0 ; i < len; i++) {
7145	        var namespaceURI = attrs.getURI(i);
7146	        var value = attrs.getValue(i);
7147	        var qName = attrs.getQName(i);
7148			var attr = doc.createAttributeNS(namespaceURI, qName);
7149			this.locator &&position(attrs.getLocator(i),attr);
7150			attr.value = attr.nodeValue = value;
7151			el.setAttributeNode(attr)
7152	    }
7153	},
7154	endElement:function(namespaceURI, localName, qName) {
7155		var current = this.currentElement
7156		var tagName = current.tagName;
7157		this.currentElement = current.parentNode;
7158	},
7159	startPrefixMapping:function(prefix, uri) {
7160	},
7161	endPrefixMapping:function(prefix) {
7162	},
7163	processingInstruction:function(target, data) {
7164	    var ins = this.doc.createProcessingInstruction(target, data);
7165	    this.locator && position(this.locator,ins)
7166	    appendElement(this, ins);
7167	},
7168	ignorableWhitespace:function(ch, start, length) {
7169	},
7170	characters:function(chars, start, length) {
7171		chars = _toString.apply(this,arguments)
7172		//console.log(chars)
7173		if(chars){
7174			if (this.cdata) {
7175				var charNode = this.doc.createCDATASection(chars);
7176			} else {
7177				var charNode = this.doc.createTextNode(chars);
7178			}
7179			if(this.currentElement){
7180				this.currentElement.appendChild(charNode);
7181			}else if(/^\s*$/.test(chars)){
7182				this.doc.appendChild(charNode);
7183				//process xml
7184			}
7185			this.locator && position(this.locator,charNode)
7186		}
7187	},
7188	skippedEntity:function(name) {
7189	},
7190	endDocument:function() {
7191		this.doc.normalize();
7192	},
7193	setDocumentLocator:function (locator) {
7194	    if(this.locator = locator){// && !('lineNumber' in locator)){
7195	    	locator.lineNumber = 0;
7196	    }
7197	},
7198	//LexicalHandler
7199	comment:function(chars, start, length) {
7200		chars = _toString.apply(this,arguments)
7201	    var comm = this.doc.createComment(chars);
7202	    this.locator && position(this.locator,comm)
7203	    appendElement(this, comm);
7204	},
7205
7206	startCDATA:function() {
7207	    //used in characters() methods
7208	    this.cdata = true;
7209	},
7210	endCDATA:function() {
7211	    this.cdata = false;
7212	},
7213
7214	startDTD:function(name, publicId, systemId) {
7215		var impl = this.doc.implementation;
7216	    if (impl && impl.createDocumentType) {
7217	        var dt = impl.createDocumentType(name, publicId, systemId);
7218	        this.locator && position(this.locator,dt)
7219	        appendElement(this, dt);
7220					this.doc.doctype = dt;
7221	    }
7222	},
7223	/**
7224	 * @see org.xml.sax.ErrorHandler
7225	 * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html
7226	 */
7227	warning:function(error) {
7228		console.warn('[xmldom warning]\t'+error,_locator(this.locator));
7229	},
7230	error:function(error) {
7231		console.error('[xmldom error]\t'+error,_locator(this.locator));
7232	},
7233	fatalError:function(error) {
7234		throw new ParseError(error, this.locator);
7235	}
7236}
7237function _locator(l){
7238	if(l){
7239		return '\n@'+(l.systemId ||'')+'#[line:'+l.lineNumber+',col:'+l.columnNumber+']'
7240	}
7241}
7242function _toString(chars,start,length){
7243	if(typeof chars == 'string'){
7244		return chars.substr(start,length)
7245	}else{//java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)")
7246		if(chars.length >= start+length || start){
7247			return new java.lang.String(chars,start,length)+'';
7248		}
7249		return chars;
7250	}
7251}
7252
7253/*
7254 * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html
7255 * used method of org.xml.sax.ext.LexicalHandler:
7256 *  #comment(chars, start, length)
7257 *  #startCDATA()
7258 *  #endCDATA()
7259 *  #startDTD(name, publicId, systemId)
7260 *
7261 *
7262 * IGNORED method of org.xml.sax.ext.LexicalHandler:
7263 *  #endDTD()
7264 *  #startEntity(name)
7265 *  #endEntity(name)
7266 *
7267 *
7268 * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html
7269 * IGNORED method of org.xml.sax.ext.DeclHandler
7270 * 	#attributeDecl(eName, aName, type, mode, value)
7271 *  #elementDecl(name, model)
7272 *  #externalEntityDecl(name, publicId, systemId)
7273 *  #internalEntityDecl(name, value)
7274 * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html
7275 * IGNORED method of org.xml.sax.EntityResolver2
7276 *  #resolveEntity(String name,String publicId,String baseURI,String systemId)
7277 *  #resolveEntity(publicId, systemId)
7278 *  #getExternalSubset(name, baseURI)
7279 * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html
7280 * IGNORED method of org.xml.sax.DTDHandler
7281 *  #notationDecl(name, publicId, systemId) {};
7282 *  #unparsedEntityDecl(name, publicId, systemId, notationName) {};
7283 */
7284"endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g,function(key){
7285	DOMHandler.prototype[key] = function(){return null}
7286})
7287
7288/* 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 */
7289function appendElement (hander,node) {
7290    if (!hander.currentElement) {
7291        hander.doc.appendChild(node);
7292    } else {
7293        hander.currentElement.appendChild(node);
7294    }
7295}//appendChild and setAttributeNS are preformance key
7296
7297exports.__DOMHandler = DOMHandler;
7298exports.normalizeLineEndings = normalizeLineEndings;
7299exports.DOMParser = DOMParser;
7300
7301},{"./conventions":29,"./dom":31,"./entities":32,"./sax":34}],31:[function(require,module,exports){
7302var conventions = require("./conventions");
7303
7304var find = conventions.find;
7305var NAMESPACE = conventions.NAMESPACE;
7306var tagNamePattern = conventions.tagNamePattern;
7307
7308/**
7309 * A prerequisite for `[].filter`, to drop elements that are empty
7310 * @param {string} input
7311 * @returns {boolean}
7312 */
7313function notEmptyString (input) {
7314	return input !== ''
7315}
7316/**
7317 * @see https://infra.spec.whatwg.org/#split-on-ascii-whitespace
7318 * @see https://infra.spec.whatwg.org/#ascii-whitespace
7319 *
7320 * @param {string} input
7321 * @returns {string[]} (can be empty)
7322 */
7323function splitOnASCIIWhitespace(input) {
7324	// U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, U+0020 SPACE
7325	return input ? input.split(/[\t\n\f\r ]+/).filter(notEmptyString) : []
7326}
7327
7328/**
7329 * Adds element as a key to current if it is not already present.
7330 *
7331 * @param {Record<string, boolean | undefined>} current
7332 * @param {string} element
7333 * @returns {Record<string, boolean | undefined>}
7334 */
7335function orderedSetReducer (current, element) {
7336	if (!current.hasOwnProperty(element)) {
7337		current[element] = true;
7338	}
7339	return current;
7340}
7341
7342/**
7343 * @see https://infra.spec.whatwg.org/#ordered-set
7344 * @param {string} input
7345 * @returns {string[]}
7346 */
7347function toOrderedSet(input) {
7348	if (!input) return [];
7349	var list = splitOnASCIIWhitespace(input);
7350	return Object.keys(list.reduce(orderedSetReducer, {}))
7351}
7352
7353/**
7354 * Uses `list.indexOf` to implement something like `Array.prototype.includes`,
7355 * which we can not rely on being available.
7356 *
7357 * @param {any[]} list
7358 * @returns {function(any): boolean}
7359 */
7360function arrayIncludes (list) {
7361	return function(element) {
7362		return list && list.indexOf(element) !== -1;
7363	}
7364}
7365
7366function copy(src,dest){
7367	for(var p in src){
7368		if (Object.prototype.hasOwnProperty.call(src, p)) {
7369			dest[p] = src[p];
7370		}
7371	}
7372}
7373
7374/**
7375^\w+\.prototype\.([_\w]+)\s*=\s*((?:.*\{\s*?[\r\n][\s\S]*?^})|\S.*?(?=[;\r\n]));?
7376^\w+\.prototype\.([_\w]+)\s*=\s*(\S.*?(?=[;\r\n]));?
7377 */
7378function _extends(Class,Super){
7379	var pt = Class.prototype;
7380	if(!(pt instanceof Super)){
7381		function t(){};
7382		t.prototype = Super.prototype;
7383		t = new t();
7384		copy(pt,t);
7385		Class.prototype = pt = t;
7386	}
7387	if(pt.constructor != Class){
7388		if(typeof Class != 'function'){
7389			console.error("unknown Class:"+Class)
7390		}
7391		pt.constructor = Class
7392	}
7393}
7394
7395// Node Types
7396var NodeType = {}
7397var ELEMENT_NODE                = NodeType.ELEMENT_NODE                = 1;
7398var ATTRIBUTE_NODE              = NodeType.ATTRIBUTE_NODE              = 2;
7399var TEXT_NODE                   = NodeType.TEXT_NODE                   = 3;
7400var CDATA_SECTION_NODE          = NodeType.CDATA_SECTION_NODE          = 4;
7401var ENTITY_REFERENCE_NODE       = NodeType.ENTITY_REFERENCE_NODE       = 5;
7402var ENTITY_NODE                 = NodeType.ENTITY_NODE                 = 6;
7403var PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE = 7;
7404var COMMENT_NODE                = NodeType.COMMENT_NODE                = 8;
7405var DOCUMENT_NODE               = NodeType.DOCUMENT_NODE               = 9;
7406var DOCUMENT_TYPE_NODE          = NodeType.DOCUMENT_TYPE_NODE          = 10;
7407var DOCUMENT_FRAGMENT_NODE      = NodeType.DOCUMENT_FRAGMENT_NODE      = 11;
7408var NOTATION_NODE               = NodeType.NOTATION_NODE               = 12;
7409
7410// ExceptionCode
7411var ExceptionCode = {}
7412var ExceptionMessage = {};
7413var INDEX_SIZE_ERR              = ExceptionCode.INDEX_SIZE_ERR              = ((ExceptionMessage[1]="Index size error"),1);
7414var DOMSTRING_SIZE_ERR          = ExceptionCode.DOMSTRING_SIZE_ERR          = ((ExceptionMessage[2]="DOMString size error"),2);
7415var HIERARCHY_REQUEST_ERR       = ExceptionCode.HIERARCHY_REQUEST_ERR       = ((ExceptionMessage[3]="Hierarchy request error"),3);
7416var WRONG_DOCUMENT_ERR          = ExceptionCode.WRONG_DOCUMENT_ERR          = ((ExceptionMessage[4]="Wrong document"),4);
7417var INVALID_CHARACTER_ERR       = ExceptionCode.INVALID_CHARACTER_ERR       = ((ExceptionMessage[5]="Invalid character"),5);
7418var NO_DATA_ALLOWED_ERR         = ExceptionCode.NO_DATA_ALLOWED_ERR         = ((ExceptionMessage[6]="No data allowed"),6);
7419var NO_MODIFICATION_ALLOWED_ERR = ExceptionCode.NO_MODIFICATION_ALLOWED_ERR = ((ExceptionMessage[7]="No modification allowed"),7);
7420var NOT_FOUND_ERR               = ExceptionCode.NOT_FOUND_ERR               = ((ExceptionMessage[8]="Not found"),8);
7421var NOT_SUPPORTED_ERR           = ExceptionCode.NOT_SUPPORTED_ERR           = ((ExceptionMessage[9]="Not supported"),9);
7422var INUSE_ATTRIBUTE_ERR         = ExceptionCode.INUSE_ATTRIBUTE_ERR         = ((ExceptionMessage[10]="Attribute in use"),10);
7423//level2
7424var INVALID_STATE_ERR        	= ExceptionCode.INVALID_STATE_ERR        	= ((ExceptionMessage[11]="Invalid state"),11);
7425var SYNTAX_ERR               	= ExceptionCode.SYNTAX_ERR               	= ((ExceptionMessage[12]="Syntax error"),12);
7426var INVALID_MODIFICATION_ERR 	= ExceptionCode.INVALID_MODIFICATION_ERR 	= ((ExceptionMessage[13]="Invalid modification"),13);
7427var NAMESPACE_ERR            	= ExceptionCode.NAMESPACE_ERR           	= ((ExceptionMessage[14]="Invalid namespace"),14);
7428var INVALID_ACCESS_ERR       	= ExceptionCode.INVALID_ACCESS_ERR      	= ((ExceptionMessage[15]="Invalid access"),15);
7429
7430/**
7431 * DOM Level 2
7432 * Object DOMException
7433 * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html
7434 * @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html
7435 */
7436function DOMException(code, message) {
7437	if(message instanceof Error){
7438		var error = message;
7439	}else{
7440		error = this;
7441		Error.call(this, ExceptionMessage[code]);
7442		this.message = ExceptionMessage[code];
7443		if(Error.captureStackTrace) Error.captureStackTrace(this, DOMException);
7444	}
7445	error.code = code;
7446	if(message) this.message = this.message + ": " + message;
7447	return error;
7448};
7449DOMException.prototype = Error.prototype;
7450copy(ExceptionCode,DOMException)
7451
7452/**
7453 * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177
7454 * 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.
7455 * The items in the NodeList are accessible via an integral index, starting from 0.
7456 */
7457function NodeList() {
7458};
7459NodeList.prototype = {
7460	/**
7461	 * The number of nodes in the list. The range of valid child node indices is 0 to length-1 inclusive.
7462	 * @standard level1
7463	 */
7464	length:0,
7465	/**
7466	 * 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.
7467	 * @standard level1
7468	 * @param index  unsigned long
7469	 *   Index into the collection.
7470	 * @return Node
7471	 * 	The node at the indexth position in the NodeList, or null if that is not a valid index.
7472	 */
7473	item: function(index) {
7474		return index >= 0 && index < this.length ? this[index] : null;
7475	},
7476	toString:function(isHTML,nodeFilter,options){
7477		var requireWellFormed = !!options && !!options.requireWellFormed;
7478		for(var buf = [], i = 0;i<this.length;i++){
7479			serializeToString(this[i],buf,isHTML,nodeFilter,null,requireWellFormed);
7480		}
7481		return buf.join('');
7482	},
7483	/**
7484	 * @private
7485	 * @param {function (Node):boolean} predicate
7486	 * @returns {Node[]}
7487	 */
7488	filter: function (predicate) {
7489		return Array.prototype.filter.call(this, predicate);
7490	},
7491	/**
7492	 * @private
7493	 * @param {Node} item
7494	 * @returns {number}
7495	 */
7496	indexOf: function (item) {
7497		return Array.prototype.indexOf.call(this, item);
7498	},
7499};
7500
7501function LiveNodeList(node,refresh){
7502	this._node = node;
7503	this._refresh = refresh
7504	_updateLiveList(this);
7505}
7506function _updateLiveList(list){
7507	var inc = list._node._inc || list._node.ownerDocument._inc;
7508	if (list._inc !== inc) {
7509		var ls = list._refresh(list._node);
7510		__set__(list,'length',ls.length);
7511		if (!list.$$length || ls.length < list.$$length) {
7512			for (var i = ls.length; i in list; i++) {
7513				if (Object.prototype.hasOwnProperty.call(list, i)) {
7514					delete list[i];
7515				}
7516			}
7517		}
7518		copy(ls,list);
7519		list._inc = inc;
7520	}
7521}
7522LiveNodeList.prototype.item = function(i){
7523	_updateLiveList(this);
7524	return this[i] || null;
7525}
7526
7527_extends(LiveNodeList,NodeList);
7528
7529/**
7530 * Objects implementing the NamedNodeMap interface are used
7531 * to represent collections of nodes that can be accessed by name.
7532 * Note that NamedNodeMap does not inherit from NodeList;
7533 * NamedNodeMaps are not maintained in any particular order.
7534 * Objects contained in an object implementing NamedNodeMap may also be accessed by an ordinal index,
7535 * but this is simply to allow convenient enumeration of the contents of a NamedNodeMap,
7536 * and does not imply that the DOM specifies an order to these Nodes.
7537 * NamedNodeMap objects in the DOM are live.
7538 * used for attributes or DocumentType entities
7539 */
7540function NamedNodeMap() {
7541	// nodeName -> Attr membership index; lets the parse-time de-duplication in
7542	// setNamedItem resolve an existing attribute in O(1) instead of scanning the
7543	// list, so building an element with M attributes costs O(M) rather than O(M^2).
7544	// The numbered entries and length remain the sole authority for attribute order.
7545	// Object.create(null) so an attribute named __proto__ or constructor is an
7546	// ordinary key.
7547	this._nameIndex = Object.create(null);
7548};
7549
7550function _findNodeIndex(list,node){
7551	var i = list.length;
7552	while(i--){
7553		if(list[i] === node){return i}
7554	}
7555}
7556
7557function _nnmIndexAdd(list, attr) {
7558	list._nameIndex[attr.nodeName] = attr;
7559}
7560function _nnmIndexRemove(list, attr) {
7561	// Only drop the entry if it still points at this attribute; a replacement with
7562	// a different nodeName may already have re-keyed it.
7563	if (list._nameIndex[attr.nodeName] === attr) {
7564		delete list._nameIndex[attr.nodeName];
7565	}
7566}
7567
7568function _addNamedNode(el,list,newAttr,oldAttr){
7569	if(oldAttr){
7570		list[_findNodeIndex(list,oldAttr)] = newAttr;
7571		// oldAttr leaves the list; drop its index entry (its nodeName may differ
7572		// from newAttr's when the replacement came via setNamedItemNS).
7573		_nnmIndexRemove(list, oldAttr);
7574	}else{
7575		list[list.length++] = newAttr;
7576	}
7577	_nnmIndexAdd(list, newAttr);
7578	if(el){
7579		newAttr.ownerElement = el;
7580		var doc = el.ownerDocument;
7581		if(doc){
7582			oldAttr && _onRemoveAttribute(doc,el,oldAttr);
7583			_onAddAttribute(doc,el,newAttr);
7584		}
7585	}
7586}
7587function _removeNamedNode(el,list,attr){
7588	//console.log('remove attr:'+attr)
7589	var i = _findNodeIndex(list,attr);
7590	if(i>=0){
7591		var lastIndex = list.length-1
7592		while(i<lastIndex){
7593			list[i] = list[++i]
7594		}
7595		list.length = lastIndex;
7596		_nnmIndexRemove(list, attr);
7597		if(el){
7598			var doc = el.ownerDocument;
7599			if(doc){
7600				_onRemoveAttribute(doc,el,attr);
7601				attr.ownerElement = null;
7602			}
7603		}
7604	}else{
7605		throw new DOMException(NOT_FOUND_ERR,new Error(el.tagName+'@'+attr))
7606	}
7607}
7608NamedNodeMap.prototype = {
7609	length:0,
7610	item:NodeList.prototype.item,
7611	getNamedItem: function(key) {
7612//		if(key.indexOf(':')>0 || key == 'xmlns'){
7613//			return null;
7614//		}
7615		//console.log()
7616		var i = this.length;
7617		while(i--){
7618			var attr = this[i];
7619			//console.log(attr.nodeName,key)
7620			if(attr.nodeName == key){
7621				return attr;
7622			}
7623		}
7624	},
7625	setNamedItem: function(attr) {
7626		var el = attr.ownerElement;
7627		if(el && el!=this._ownerElement){
7628			throw new DOMException(INUSE_ATTRIBUTE_ERR);
7629		}
7630		// Resolve any existing attribute with the same nodeName through the O(1)
7631		// membership index rather than an O(M) scan — this is the parse-dedup hot
7632		// path (setAttributeNode per attribute during parse). Absent -> undefined,
7633		// matching getNamedItem's contract.
7634		var oldAttr = this._nameIndex[attr.nodeName];
7635		_addNamedNode(this._ownerElement,this,attr,oldAttr);
7636		return oldAttr;
7637	},
7638	/* returns Node */
7639	setNamedItemNS: function(attr) {// raises: WRONG_DOCUMENT_ERR,NO_MODIFICATION_ALLOWED_ER
vendor: 4,335 bytes, lines 7639-7752
7639R,INUSE_ATTRIBUTE_ERR
7640		var el = attr.ownerElement, oldAttr;
7641		if(el && el!=this._ownerElement){
7642			throw new DOMException(INUSE_ATTRIBUTE_ERR);
7643		}
7644		oldAttr = this.getNamedItemNS(attr.namespaceURI,attr.localName);
7645		_addNamedNode(this._ownerElement,this,attr,oldAttr);
7646		return oldAttr;
7647	},
7648
7649	/* returns Node */
7650	removeNamedItem: function(key) {
7651		var attr = this.getNamedItem(key);
7652		_removeNamedNode(this._ownerElement,this,attr);
7653		return attr;
7654
7655
7656	},// raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR
7657
7658	//for level2
7659	removeNamedItemNS:function(namespaceURI,localName){
7660		var attr = this.getNamedItemNS(namespaceURI,localName);
7661		_removeNamedNode(this._ownerElement,this,attr);
7662		return attr;
7663	},
7664	getNamedItemNS: function(namespaceURI, localName) {
7665		var i = this.length;
7666		while(i--){
7667			var node = this[i];
7668			if(node.localName == localName && node.namespaceURI == namespaceURI){
7669				return node;
7670			}
7671		}
7672		return null;
7673	}
7674};
7675
7676/**
7677 * The DOMImplementation interface represents an object providing methods
7678 * which are not dependent on any particular document.
7679 * Such an object is returned by the `Document.implementation` property.
7680 *
7681 * __The individual methods describe the differences compared to the specs.__
7682 *
7683 * @constructor
7684 *
7685 * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation MDN
7686 * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490 DOM Level 1 Core (Initial)
7687 * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-102161490 DOM Level 2 Core
7688 * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-102161490 DOM Level 3 Core
7689 * @see https://dom.spec.whatwg.org/#domimplementation DOM Living Standard
7690 */
7691function DOMImplementation() {
7692}
7693
7694DOMImplementation.prototype = {
7695	/**
7696	 * The DOMImplementation.hasFeature() method returns a Boolean flag indicating if a given feature is supported.
7697	 * The different implementations fairly diverged in what kind of features were reported.
7698	 * The latest version of the spec settled to force this method to always return true, where the functionality was accurate and in use.
7699	 *
7700	 * @deprecated It is deprecated and modern browsers return true in all cases.
7701	 *
7702	 * @param {string} feature
7703	 * @param {string} [version]
7704	 * @returns {boolean} always true
7705	 *
7706	 * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/hasFeature MDN
7707	 * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-5CED94D7 DOM Level 1 Core
7708	 * @see https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature DOM Living Standard
7709	 */
7710	hasFeature: function(feature, version) {
7711			return true;
7712	},
7713	/**
7714	 * Creates an XML Document object of the specified type with its document element.
7715	 *
7716	 * __It behaves slightly different from the description in the living standard__:
7717	 * - There is no interface/class `XMLDocument`, it returns a `Document` instance.
7718	 * - `contentType`, `encoding`, `mode`, `origin`, `url` fields are currently not declared.
7719	 * - this implementation is not validating names or qualified names
7720	 *   (when parsing XML strings, the SAX parser takes care of that)
7721	 *
7722	 * @param {string|null} namespaceURI
7723	 * @param {string} qualifiedName
7724	 * @param {DocumentType=null} doctype
7725	 * @returns {Document}
7726	 *
7727	 * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocument MDN
7728	 * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocument DOM Level 2 Core (initial)
7729	 * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument  DOM Level 2 Core
7730	 *
7731	 * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract
7732	 * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names
7733	 * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names
7734	 */
7735	createDocument: function(namespaceURI,  qualifiedName, doctype){
7736		var doc = new Document();
7737		doc.implementation = this;
7738		doc.childNodes = new NodeList();
7739		doc.doctype = doctype || null;
7740		if (doctype){
7741			doc.appendChild(doctype);
7742		}
7743		if (qualifiedName){
7744			var root = doc.createElementNS(namespaceURI, qualifiedName);
7745			doc.appendChild(root);
7746		}
7747		return doc;
7748	},
7749	/**
7750	 * Returns a doctype, with the given `qualifiedName`, `publicId`, and `systemId`.
7751	 *
7752	 * __This implementation differs from the specification:__
7753	 * - this implementation is not validating names or qualified names
7754	 *   (when parsing XML strings, the SAX parser takes care of that)
7755	 *
7756	 * Note: `internalSubset` can only be introduced via a direct property write to `node.internalSubset` after creation.
7757	 * Creation-time validation of `publicId`, `systemId` is not enforced.
7758	 * The serializer-level check covers all mutation vectors, including direct property writes.
7759	 * `internalSubset` is only serialized as `[ ... ]` when both `publicId` and `systemId` are
7760	 * absent (empty or `'.'`) — if either external identifier is present, `internalSubset` is
7761	 * silently omitted from the serialized output.
7762	 *
7763	 * @param {string} qualifiedName
7764	 * @param {string} [publicId]
7765	 * The external subset public identifier. Stored verbatim including surrounding quotes.
7766	 * When serialized with `requireWellFormed: true` (via the 4th-parameter options object),
7767	 * throws `DOMException` with code `INVALID_STATE_ERR` if the value is non-empty and does
7768	 * not match the XML `PubidLiteral` production (W3C DOM Parsing §3.2.1.3; XML 1.0 [12]).
7769	 * @param {string} [systemId]
7770	 * The external subset system identifier. Stored verbatim including surrounding quotes.
7771	 * When serialized with `requireWellFormed: true`, throws `DOMException` with code
7772	 * `INVALID_STATE_ERR` if the value is non-empty and does not match the XML `SystemLiteral`
7773	 * production (W3C DOM Parsing §3.2.1.3; XML 1.0 [11]).
7774	 * @returns {DocumentType} which can either be used with `DOMImplementation.createDocument` upon document creation
7775	 * 				  or can be put into the document via methods like `Node.insertBefore()` or `Node.replaceChild()`
7776	 *
7777	 * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocumentType MDN
7778	 * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocType DOM Level 2 Core
7779	 * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype DOM Living Standard
7780	 *
7781	 * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract
7782	 * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names
7783	 * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names
7784	 */
7785	createDocumentType: function(qualifiedName, publicId, systemId){
7786		var node = new DocumentType();
7787		node.name = qualifiedName;
7788		node.nodeName = qualifiedName;
7789		node.publicId = publicId || '';
7790		node.systemId = systemId || '';
7791
7792		return node;
7793	}
7794};
7795
7796
7797/**
7798 * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247
7799 */
7800
7801function Node() {
7802};
7803
7804Node.prototype = {
7805	firstChild : null,
7806	lastChild : null,
7807	previousSibling : null,
7808	nextSibling : null,
7809	attributes : null,
7810	parentNode : null,
7811	childNodes : null,
7812	ownerDocument : null,
7813	nodeValue : null,
7814	namespaceURI : null,
7815	prefix : null,
7816	localName : null,
7817	// Modified in DOM Level 2:
7818	insertBefore:function(newChild, refChild){//raises
7819		return _insertBefore(this,newChild,refChild);
7820	},
7821	replaceChild:function(newChild, oldChild){//raises
7822		_insertBefore(this, newChild,oldChild, assertPreReplacementValidityInDocument);
7823		if(oldChild){
7824			this.removeChild(oldChild);
7825		}
7826	},
7827	removeChild:function(oldChild){
7828		return _removeChild(this,oldChild);
7829	},
7830	appendChild:function(newChild){
7831		return this.insertBefore(newChild,null);
7832	},
7833	hasChildNodes:function(){
7834		return this.firstChild != null;
7835	},
7836	cloneNode:function(deep){
7837		return cloneNode(this.ownerDocument||this,this,deep);
7838	},
7839	// Modified in DOM Level 2:
7840	/**
7841	 * Puts the specified node and all of its subtree into a "normalized" form. In a normalized
7842	 * subtree, no text nodes in the subtree are empty and there are no adjacent text nodes.
7843	 *
7844	 * Specifically, this method merges any adjacent text nodes (i.e., nodes for which `nodeType`
7845	 * is `TEXT_NODE`) into a single node with the combined data. It also removes any empty text
7846	 * nodes.
7847	 *
7848	 * This method iteratively traverses all child nodes to normalize all descendant nodes within
7849	 * the subtree.
7850	 *
7851	 * @throws {DOMException}
7852	 * May throw a DOMException if operations within removeChild or appendData (which are
7853	 * potentially invoked in this method) do not meet their specific constraints.
7854	 * @see {@link Node.removeChild}
7855	 * @see {@link CharacterData.appendData}
7856	 * @see ../docs/walk-dom.md.
7857	 */
7858	normalize: function () {
7859		walkDOM(this, null, {
7860			enter: function (node) {
7861				// Merge adjacent text children of node before walkDOM schedules them.
7862				// walkDOM reads lastChild/previousSibling after enter returns, so the
7863				// surviving post-merge children are what it descends into.
7864				var child = node.firstChild;
7865				while (child) {
7866					var next = child.nextSibling;
7867					if (next !== null && next.nodeType === TEXT_NODE && child.nodeType === TEXT_NODE) {
7868						// Merge the whole run of adjacent text nodes at once: gather the
7869						// following text siblings' data, unlink them in a single pass, and
7870						// re-index the child list a single time. Per-sibling `removeChild`
7871						// (each an O(K) re-index) plus per-sibling `appendData` (each an O(K)
7872						// string rebuild) is O(K^2) over a long run of single-character text
7873						// nodes; this keeps it O(K). The first text node of the run survives
7874						// and carries the concatenated data, preserving node identity and
7875						// locator semantics.
7876						var tail = [];
7877						var sibling = next;
7878						while (sibling !== null && sibling.nodeType === TEXT_NODE) {
7879							tail.push(sibling.data);
7880							sibling = sibling.nextSibling;
7881						}
7882						// `sibling` is now the first non-text node after the run, or null.
7883						var removed = child.nextSibling;
7884						while (removed !== sibling) {
7885							var following = removed.nextSibling;
7886							removed.parentNode = null;
7887							removed.previousSibling = null;
7888							removed.nextSibling = null;
7889							removed = following;
7890						}
7891						child.nextSibling = sibling;
7892						if (sibling !== null) {
7893							sibling.previousSibling = child;
7894						} else {
7895							node.lastChild = child;
7896						}
vendor: 22,247 bytes, lines 7897-8533
7897						child.appendData(tail.join('')); // single O(K) string rebuild
7898						_onUpdateChild(node.ownerDocument, node); // single O(K) re-index
7899						child = sibling;
7900					} else {
7901						child = next;
7902					}
7903				}
7904				return true; // descend into surviving children
7905			},
7906		});
7907	},
7908  	// Introduced in DOM Level 2:
7909	isSupported:function(feature, version){
7910		return this.ownerDocument.implementation.hasFeature(feature,version);
7911	},
7912    // Introduced in DOM Level 2:
7913    hasAttributes:function(){
7914    	return this.attributes.length>0;
7915    },
7916	/**
7917	 * Look up the prefix associated to the given namespace URI, starting from this node.
7918	 * **The default namespace declarations are ignored by this method.**
7919	 * See Namespace Prefix Lookup for details on the algorithm used by this method.
7920	 *
7921	 * _Note: The implementation seems to be incomplete when compared to the algorithm described in the specs._
7922	 *
7923	 * @param {string | null} namespaceURI
7924	 * @returns {string | null}
7925	 * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespacePrefix
7926	 * @see https://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#lookupNamespacePrefixAlgo
7927	 * @see https://dom.spec.whatwg.org/#dom-node-lookupprefix
7928	 * @see https://github.com/xmldom/xmldom/issues/322
7929	 */
7930    lookupPrefix:function(namespaceURI){
7931    	var el = this;
7932    	while(el){
7933    		var map = el._nsMap;
7934    		//console.dir(map)
7935    		if(map){
7936    			for(var n in map){
7937						if (Object.prototype.hasOwnProperty.call(map, n) && map[n] === namespaceURI) {
7938							return n;
7939						}
7940    			}
7941    		}
7942    		el = el.nodeType == ATTRIBUTE_NODE?el.ownerDocument : el.parentNode;
7943    	}
7944    	return null;
7945    },
7946    // Introduced in DOM Level 3:
7947    lookupNamespaceURI:function(prefix){
7948    	var el = this;
7949    	while(el){
7950    		var map = el._nsMap;
7951    		//console.dir(map)
7952    		if(map){
7953    			if(Object.prototype.hasOwnProperty.call(map, prefix)){
7954    				return map[prefix] ;
7955    			}
7956    		}
7957    		el = el.nodeType == ATTRIBUTE_NODE?el.ownerDocument : el.parentNode;
7958    	}
7959    	return null;
7960    },
7961    // Introduced in DOM Level 3:
7962    isDefaultNamespace:function(namespaceURI){
7963    	var prefix = this.lookupPrefix(namespaceURI);
7964    	return prefix == null;
7965    }
7966};
7967
7968
7969function _xmlEncoder(c){
7970	return c == '<' && '&lt;' ||
7971         c == '>' && '&gt;' ||
7972         c == '&' && '&amp;' ||
7973         c == '"' && '&quot;' ||
7974         '&#'+c.charCodeAt()+';'
7975}
7976
7977
7978copy(NodeType,Node);
7979copy(NodeType,Node.prototype);
7980
7981/**
7982 * @param {Node} node
7983 * Root of the subtree to visit.
7984 * @param {function(Node): boolean} callback
7985 * Called for each node in depth-first pre-order. Return a truthy value to stop traversal early.
7986 * @return {boolean} `true` if traversal was aborted by the callback, `false` otherwise.
7987 */
7988function _visitNode(node, callback) {
7989	return walkDOM(node, null, { enter: function (n) { return callback(n) ? walkDOM.STOP : true; } }) === walkDOM.STOP;
7990}
7991
7992/**
7993 * Depth-first pre/post-order DOM tree walker.
7994 *
7995 * Visits every node in the subtree rooted at `node`. For each node:
7996 *
7997 * 1. Calls `callbacks.enter(node, context)` before descending into the node's children. The
7998 * return value becomes the `context` passed to each child's `enter` call and to the matching
7999 * `exit` call.
8000 * 2. If `enter` returns `null` or `undefined`, the node's children are skipped;
8001 * sibling traversal continues normally.
8002 * 3. If `enter` returns `walkDOM.STOP`, the entire traversal is aborted immediately — no
8003 * further `enter` or `exit` calls are made.
8004 * 4. `lastChild` and `previousSibling` are read **after** `enter` returns, so `enter` may
8005 * safely modify the node's own child list before the walker descends. Modifying siblings of
8006 * the current node or any other part of the tree produces unpredictable results: nodes already
8007 * queued on the stack are visited regardless of DOM changes, and newly inserted nodes outside
8008 * the current child list are never visited.
8009 * 5. Calls `callbacks.exit(node, context)` (if provided) after all of a node's children have
8010 * been visited, passing the same `context` that `enter`
8011 * returned for that node.
8012 *
8013 * This implementation uses an explicit stack and does not recurse — it is safe on arbitrarily
8014 * deep trees.
8015 *
8016 * @param {Node} node
8017 * Root of the subtree to walk.
8018 * @param {*} context
8019 * Initial context value passed to the root node's `enter`.
8020 * @param {{ enter: function(Node, *): *, exit?: function(Node, *): void }} callbacks
8021 * @returns {void | walkDOM.STOP}
8022 * @see ../docs/walk-dom.md.
8023 */
8024function walkDOM(node, context, callbacks) {
8025	// Each stack frame is {node, context, phase}:
8026	//   walkDOM.ENTER — call enter, then push children
8027	//   walkDOM.EXIT  — call exit
8028	var stack = [{ node: node, context: context, phase: walkDOM.ENTER }];
8029	while (stack.length > 0) {
8030		var frame = stack.pop();
8031		if (frame.phase === walkDOM.ENTER) {
8032			var childContext = callbacks.enter(frame.node, frame.context);
8033			if (childContext === walkDOM.STOP) {
8034				return walkDOM.STOP;
8035			}
8036			// Push exit frame before children so it fires after all children are processed (Last In First Out)
8037			stack.push({ node: frame.node, context: childContext, phase: walkDOM.EXIT });
8038			if (childContext === null || childContext === undefined) {
8039				continue; // skip children
8040			}
8041			// lastChild is read after enter returns, so enter may modify the child list.
8042			var child = frame.node.lastChild;
8043			// Traverse from lastChild backwards so that pushing onto the stack
8044			// naturally yields firstChild on top (processed first).
8045			while (child) {
8046				stack.push({ node: child, context: childContext, phase: walkDOM.ENTER });
8047				child = child.previousSibling;
8048			}
8049		} else {
8050			// frame.phase === walkDOM.EXIT
8051			if (callbacks.exit) {
8052				callbacks.exit(frame.node, frame.context);
8053			}
8054		}
8055	}
8056}
8057
8058/**
8059 * Sentinel value returned from a `walkDOM` `enter` callback to abort the entire traversal
8060 * immediately.
8061 *
8062 * @type {symbol}
8063 */
8064walkDOM.STOP = Symbol('walkDOM.STOP');
8065/**
8066 * Phase constant for a stack frame that has not yet been visited.
8067 * The `enter` callback is called and children are scheduled.
8068 *
8069 * @type {number}
8070 */
8071walkDOM.ENTER = 0;
8072/**
8073 * Phase constant for a stack frame whose subtree has been fully visited.
8074 * The `exit` callback is called.
8075 *
8076 * @type {number}
8077 */
8078walkDOM.EXIT = 1;
8079
8080function Document(){
8081	this.ownerDocument = this;
8082}
8083
8084function _onAddAttribute(doc,el,newAttr){
8085	doc && doc._inc++;
8086	var ns = newAttr.namespaceURI ;
8087	if(ns === NAMESPACE.XMLNS){
8088		//update namespace
8089		el._nsMap[newAttr.prefix?newAttr.localName:''] = newAttr.value
8090	}
8091}
8092
8093function _onRemoveAttribute(doc,el,newAttr,remove){
8094	doc && doc._inc++;
8095	var ns = newAttr.namespaceURI ;
8096	if(ns === NAMESPACE.XMLNS){
8097		//update namespace
8098		delete el._nsMap[newAttr.prefix?newAttr.localName:'']
8099	}
8100}
8101
8102/**
8103 * Updates `el.childNodes`, updating the indexed items and it's `length`.
8104 * Passing `newChild` means it will be appended.
8105 * Otherwise it's assumed that an item has been removed,
8106 * and `el.firstNode` and it's `.nextSibling` are used
8107 * to walk the current list of child nodes.
8108 *
8109 * @param {Document} doc
8110 * @param {Node} el
8111 * @param {Node} [newChild]
8112 * @private
8113 */
8114function _onUpdateChild (doc, el, newChild) {
8115	if(doc && doc._inc){
8116		doc._inc++;
8117		//update childNodes
8118		var cs = el.childNodes;
8119		if (newChild) {
8120			cs[cs.length++] = newChild;
8121		} else {
8122			var child = el.firstChild;
8123			var i = 0;
8124			while (child) {
8125				cs[i++] = child;
8126				child = child.nextSibling;
8127			}
8128			cs.length = i;
8129			delete cs[cs.length];
8130		}
8131	}
8132}
8133
8134/**
8135 * Removes the connections between `parentNode` and `child`
8136 * and any existing `child.previousSibling` or `child.nextSibling`.
8137 *
8138 * @see https://github.com/xmldom/xmldom/issues/135
8139 * @see https://github.com/xmldom/xmldom/issues/145
8140 *
8141 * @param {Node} parentNode
8142 * @param {Node} child
8143 * @returns {Node} the child that was removed.
8144 * @private
8145 */
8146function _removeChild (parentNode, child) {
8147	var previous = child.previousSibling;
8148	var next = child.nextSibling;
8149	if (previous) {
8150		previous.nextSibling = next;
8151	} else {
8152		parentNode.firstChild = next;
8153	}
8154	if (next) {
8155		next.previousSibling = previous;
8156	} else {
8157		parentNode.lastChild = previous;
8158	}
8159	child.parentNode = null;
8160	child.previousSibling = null;
8161	child.nextSibling = null;
8162	_onUpdateChild(parentNode.ownerDocument, parentNode);
8163	return child;
8164}
8165
8166/**
8167 * Returns `true` if `node` can be a parent for insertion.
8168 * @param {Node} node
8169 * @returns {boolean}
8170 */
8171function hasValidParentNodeType(node) {
8172	return (
8173		node &&
8174		(node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.ELEMENT_NODE)
8175	);
8176}
8177
8178/**
8179 * Returns `true` if `node` can be inserted according to it's `nodeType`.
8180 * @param {Node} node
8181 * @returns {boolean}
8182 */
8183function hasInsertableNodeType(node) {
8184	return (
8185		node &&
8186		(isElementNode(node) ||
8187			isTextNode(node) ||
8188			isDocTypeNode(node) ||
8189			node.nodeType === Node.DOCUMENT_FRAGMENT_NODE ||
8190			node.nodeType === Node.COMMENT_NODE ||
8191			node.nodeType === Node.PROCESSING_INSTRUCTION_NODE)
8192	);
8193}
8194
8195/**
8196 * Returns true if `node` is a DOCTYPE node
8197 * @param {Node} node
8198 * @returns {boolean}
8199 */
8200function isDocTypeNode(node) {
8201	return node && node.nodeType === Node.DOCUMENT_TYPE_NODE;
8202}
8203
8204/**
8205 * Returns true if the node is an element
8206 * @param {Node} node
8207 * @returns {boolean}
8208 */
8209function isElementNode(node) {
8210	return node && node.nodeType === Node.ELEMENT_NODE;
8211}
8212/**
8213 * Returns true if `node` is a text node
8214 * @param {Node} node
8215 * @returns {boolean}
8216 */
8217function isTextNode(node) {
8218	return node && node.nodeType === Node.TEXT_NODE;
8219}
8220
8221/**
8222 * Check if en element node can be inserted before `child`, or at the end if child is falsy,
8223 * according to the presence and position of a doctype node on the same level.
8224 *
8225 * @param {Document} doc The document node
8226 * @param {Node} child the node that would become the nextSibling if the element would be inserted
8227 * @returns {boolean} `true` if an element can be inserted before child
8228 * @private
8229 * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
8230 */
8231function isElementInsertionPossible(doc, child) {
8232	var parentChildNodes = doc.childNodes || [];
8233	if (find(parentChildNodes, isElementNode) || isDocTypeNode(child)) {
8234		return false;
8235	}
8236	var docTypeNode = find(parentChildNodes, isDocTypeNode);
8237	return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
8238}
8239
8240/**
8241 * Check if en element node can be inserted before `child`, or at the end if child is falsy,
8242 * according to the presence and position of a doctype node on the same level.
8243 *
8244 * @param {Node} doc The document node
8245 * @param {Node} child the node that would become the nextSibling if the element would be inserted
8246 * @returns {boolean} `true` if an element can be inserted before child
8247 * @private
8248 * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
8249 */
8250function isElementReplacementPossible(doc, child) {
8251	var parentChildNodes = doc.childNodes || [];
8252
8253	function hasElementChildThatIsNotChild(node) {
8254		return isElementNode(node) && node !== child;
8255	}
8256
8257	if (find(parentChildNodes, hasElementChildThatIsNotChild)) {
8258		return false;
8259	}
8260	var docTypeNode = find(parentChildNodes, isDocTypeNode);
8261	return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
8262}
8263
8264/**
8265 * @private
8266 * Steps 1-5 of the checks before inserting and before replacing a child are the same.
8267 *
8268 * @param {Node} parent the parent node to insert `node` into
8269 * @param {Node} node the node to insert
8270 * @param {Node=} child the node that should become the `nextSibling` of `node`
8271 * @returns {Node}
8272 * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
8273 * @throws DOMException if `child` is provided but is not a child of `parent`.
8274 * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
8275 * @see https://dom.spec.whatwg.org/#concept-node-replace
8276 */
8277function assertPreInsertionValidity1to5(parent, node, child) {
8278	// 1. If `parent` is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException.
8279	if (!hasValidParentNodeType(parent)) {
8280		throw new DOMException(HIERARCHY_REQUEST_ERR, 'Unexpected parent node type ' + parent.nodeType);
8281	}
8282	// 2. If `node` is a host-including inclusive ancestor of `parent`, then throw a "HierarchyRequestError" DOMException.
8283	// not implemented!
8284	// 3. If `child` is non-null and its parent is not `parent`, then throw a "NotFoundError" DOMException.
8285	if (child && child.parentNode !== parent) {
8286		throw new DOMException(NOT_FOUND_ERR, 'child not in parent');
8287	}
8288	if (
8289		// 4. If `node` is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException.
8290		!hasInsertableNodeType(node) ||
8291		// 5. If either `node` is a Text node and `parent` is a document,
8292		// the sax parser currently adds top level text nodes, this will be fixed in 0.9.0
8293		// || (node.nodeType === Node.TEXT_NODE && parent.nodeType === Node.DOCUMENT_NODE)
8294		// or `node` is a doctype and `parent` is not a document, then throw a "HierarchyRequestError" DOMException.
8295		(isDocTypeNode(node) && parent.nodeType !== Node.DOCUMENT_NODE)
8296	) {
8297		throw new DOMException(
8298			HIERARCHY_REQUEST_ERR,
8299			'Unexpected node type ' + node.nodeType + ' for parent node type ' + parent.nodeType
8300		);
8301	}
8302}
8303
8304/**
8305 * @private
8306 * Step 6 of the checks before inserting and before replacing a child are different.
8307 *
8308 * @param {Document} parent the parent node to insert `node` into
8309 * @param {Node} node the node to insert
8310 * @param {Node | undefined} child the node that should become the `nextSibling` of `node`
8311 * @returns {Node}
8312 * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
8313 * @throws DOMException if `child` is provided but is not a child of `parent`.
8314 * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
8315 * @see https://dom.spec.whatwg.org/#concept-node-replace
8316 */
8317function assertPreInsertionValidityInDocument(parent, node, child) {
8318	var parentChildNodes = parent.childNodes || [];
8319	var nodeChildNodes = node.childNodes || [];
8320
8321	// DocumentFragment
8322	if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
8323		var nodeChildElements = nodeChildNodes.filter(isElementNode);
8324		// If node has more than one element child or has a Text node child.
8325		if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) {
8326			throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
8327		}
8328		// Otherwise, if `node` has one element child and either `parent` has an element child,
8329		// `child` is a doctype, or `child` is non-null and a doctype is following `child`.
8330		if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) {
8331			throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
8332		}
8333	}
8334	// Element
8335	if (isElementNode(node)) {
8336		// `parent` has an element child, `child` is a doctype,
8337		// or `child` is non-null and a doctype is following `child`.
8338		if (!isElementInsertionPossible(parent, child)) {
8339			throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
8340		}
8341	}
8342	// DocumentType
8343	if (isDocTypeNode(node)) {
8344		// `parent` has a doctype child,
8345		if (find(parentChildNodes, isDocTypeNode)) {
8346			throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
8347		}
8348		var parentElementChild = find(parentChildNodes, isElementNode);
8349		// `child` is non-null and an element is preceding `child`,
8350		if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
8351			throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
8352		}
8353		// or `child` is null and `parent` has an element child.
8354		if (!child && parentElementChild) {
8355			throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present');
8356		}
8357	}
8358}
8359
8360/**
8361 * @private
8362 * Step 6 of the checks before inserting and before replacing a child are different.
8363 *
8364 * @param {Document} parent the parent node to insert `node` into
8365 * @param {Node} node the node to insert
8366 * @param {Node | undefined} child the node that should become the `nextSibling` of `node`
8367 * @returns {Node}
8368 * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
8369 * @throws DOMException if `child` is provided but is not a child of `parent`.
8370 * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
8371 * @see https://dom.spec.whatwg.org/#concept-node-replace
8372 */
8373function assertPreReplacementValidityInDocument(parent, node, child) {
8374	var parentChildNodes = parent.childNodes || [];
8375	var nodeChildNodes = node.childNodes || [];
8376
8377	// DocumentFragment
8378	if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
8379		var nodeChildElements = nodeChildNodes.filter(isElementNode);
8380		// If `node` has more than one element child or has a Text node child.
8381		if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) {
8382			throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
8383		}
8384		// Otherwise, if `node` has one element child and either `parent` has an element child that is not `child` or a doctype is following `child`.
8385		if (nodeChildElements.length === 1 && !isElementReplacementPossible(parent, child)) {
8386			throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
8387		}
8388	}
8389	// Element
8390	if (isElementNode(node)) {
8391		// `parent` has an element child that is not `child` or a doctype is following `child`.
8392		if (!isElementReplacementPossible(parent, child)) {
8393			throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
8394		}
8395	}
8396	// DocumentType
8397	if (isDocTypeNode(node)) {
8398		function hasDoctypeChildThatIsNotChild(node) {
8399			return isDocTypeNode(node) && node !== child;
8400		}
8401
8402		// `parent` has a doctype child that is not `child`,
8403		if (find(parentChildNodes, hasDoctypeChildThatIsNotChild)) {
8404			throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
8405		}
8406		var parentElementChild = find(parentChildNodes, isElementNode);
8407		// or an element is preceding `child`.
8408		if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
8409			throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
8410		}
8411	}
8412}
8413
8414/**
8415 * @private
8416 * @param {Node} parent the parent node to insert `node` into
8417 * @param {Node} node the node to insert
8418 * @param {Node=} child the node that should become the `nextSibling` of `node`
8419 * @returns {Node}
8420 * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
8421 * @throws DOMException if `child` is provided but is not a child of `parent`.
8422 * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
8423 */
8424function _insertBefore(parent, node, child, _inDocumentAssertion) {
8425	// To ensure pre-insertion validity of a node into a parent before a child, run these steps:
8426	assertPreInsertionValidity1to5(parent, node, child);
8427
8428	// If parent is a document, and any of the statements below, switched on the interface node implements,
8429	// are true, then throw a "HierarchyRequestError" DOMException.
8430	if (parent.nodeType === Node.DOCUMENT_NODE) {
8431		(_inDocumentAssertion || assertPreInsertionValidityInDocument)(parent, node, child);
8432	}
8433
8434	var cp = node.parentNode;
8435	if(cp){
8436		cp.removeChild(node);//remove and update
8437	}
8438	if(node.nodeType === DOCUMENT_FRAGMENT_NODE){
8439		var newFirst = node.firstChild;
8440		if (newFirst == null) {
8441			return node;
8442		}
8443		var newLast = node.lastChild;
8444	}else{
8445		newFirst = newLast = node;
8446	}
8447	var pre = child ? child.previousSibling : parent.lastChild;
8448
8449	newFirst.previousSibling = pre;
8450	newLast.nextSibling = child;
8451
8452
8453	if(pre){
8454		pre.nextSibling = newFirst;
8455	}else{
8456		parent.firstChild = newFirst;
8457	}
8458	if(child == null){
8459		parent.lastChild = newLast;
8460	}else{
8461		child.previousSibling = newLast;
8462	}
8463	do{
8464		newFirst.parentNode = parent;
8465		// Update ownerDocument for each node being inserted
8466		var targetDoc = parent.ownerDocument || parent;
8467		_updateOwnerDocument(newFirst, targetDoc);
8468	}while(newFirst !== newLast && (newFirst= newFirst.nextSibling))
8469	_onUpdateChild(parent.ownerDocument||parent, parent);
8470	//console.log(parent.lastChild.nextSibling == null)
8471	if (node.nodeType == DOCUMENT_FRAGMENT_NODE) {
8472		node.firstChild = node.lastChild = null;
8473	}
8474	return node;
8475}
8476
8477/**
8478 * Recursively updates the ownerDocument property for a node and all its descendants
8479 * @param {Node} node
8480 * @param {Document} newOwnerDocument
8481 * @private
8482 */
8483function _updateOwnerDocument(node, newOwnerDocument) {
8484	if (node.ownerDocument === newOwnerDocument) {
8485		return;
8486	}
8487	
8488	node.ownerDocument = newOwnerDocument;
8489	
8490	// Update attributes if this is an element
8491	if (node.nodeType === ELEMENT_NODE && node.attributes) {
8492		for (var i = 0; i < node.attributes.length; i++) {
8493			var attr = node.attributes.item(i);
8494			if (attr) {
8495				attr.ownerDocument = newOwnerDocument;
8496			}
8497		}
8498	}
8499	
8500	// Recursively update child nodes
8501	var child = node.firstChild;
8502	while (child) {
8503		_updateOwnerDocument(child, newOwnerDocument);
8504		child = child.nextSibling;
8505	}
8506}
8507
8508/**
8509 * Appends `newChild` to `parentNode`.
8510 * If `newChild` is already connected to a `parentNode` it is first removed from it.
8511 *
8512 * @see https://github.com/xmldom/xmldom/issues/135
8513 * @see https://github.com/xmldom/xmldom/issues/145
8514 * @param {Node} parentNode
8515 * @param {Node} newChild
8516 * @returns {Node}
8517 * @private
8518 */
8519function _appendSingleChild (parentNode, newChild) {
8520	if (newChild.parentNode) {
8521		newChild.parentNode.removeChild(newChild);
8522	}
8523	newChild.parentNode = parentNode;
8524	newChild.previousSibling = parentNode.lastChild;
8525	newChild.nextSibling = null;
8526	if (newChild.previousSibling) {
8527		newChild.previousSibling.nextSibling = newChild;
8528	} else {
8529		parentNode.firstChild = newChild;
8530	}
8531	parentNode.lastChild = newChild;
8532	_onUpdateChild(parentNode.ownerDocument, parentNode, newChild);
8533	
vendor: 6,650 bytes, lines 8534-8741
8534	// Update ownerDocument for the new child and all its descendants
8535	var targetDoc = parentNode.ownerDocument || parentNode;
8536	_updateOwnerDocument(newChild, targetDoc);
8537	
8538	return newChild;
8539}
8540
8541Document.prototype = {
8542	//implementation : null,
8543	nodeName :  '#document',
8544	nodeType :  DOCUMENT_NODE,
8545	/**
8546	 * The DocumentType node of the document.
8547	 *
8548	 * @readonly
8549	 * @type DocumentType
8550	 */
8551	doctype :  null,
8552	documentElement :  null,
8553	_inc : 1,
8554
8555	insertBefore :  function(newChild, refChild){//raises
8556		if(newChild.nodeType == DOCUMENT_FRAGMENT_NODE){
8557			var child = newChild.firstChild;
8558			while(child){
8559				var next = child.nextSibling;
8560				this.insertBefore(child,refChild);
8561				child = next;
8562			}
8563			return newChild;
8564		}
8565		_insertBefore(this, newChild, refChild);
8566		_updateOwnerDocument(newChild, this);
8567		if (this.documentElement === null && newChild.nodeType === ELEMENT_NODE) {
8568			this.documentElement = newChild;
8569		}
8570
8571		return newChild;
8572	},
8573	removeChild :  function(oldChild){
8574		if(this.documentElement == oldChild){
8575			this.documentElement = null;
8576		}
8577		return _removeChild(this,oldChild);
8578	},
8579	replaceChild: function (newChild, oldChild) {
8580		//raises
8581		_insertBefore(this, newChild, oldChild, assertPreReplacementValidityInDocument);
8582		_updateOwnerDocument(newChild, this);
8583		if (oldChild) {
8584			this.removeChild(oldChild);
8585		}
8586		if (isElementNode(newChild)) {
8587			this.documentElement = newChild;
8588		}
8589	},
8590	// Introduced in DOM Level 2:
8591	importNode : function(importedNode,deep){
8592		return importNode(this,importedNode,deep);
8593	},
8594	// Introduced in DOM Level 2:
8595	getElementById :	function(id){
8596		var rtv = null;
8597		_visitNode(this.documentElement,function(node){
8598			if(node.nodeType == ELEMENT_NODE){
8599				if(node.getAttribute('id') == id){
8600					rtv = node;
8601					return true;
8602				}
8603			}
8604		})
8605		return rtv;
8606	},
8607
8608	/**
8609	 * The `getElementsByClassName` method of `Document` interface returns an array-like object
8610	 * of all child elements which have **all** of the given class name(s).
8611	 *
8612	 * Returns an empty list if `classeNames` is an empty string or only contains HTML white space characters.
8613	 *
8614	 *
8615	 * Warning: This is a live LiveNodeList.
8616	 * Changes in the DOM will reflect in the array as the changes occur.
8617	 * If an element selected by this array no longer qualifies for the selector,
8618	 * it will automatically be removed. Be aware of this for iteration purposes.
8619	 *
8620	 * @param {string} classNames is a string representing the class name(s) to match; multiple class names are separated by (ASCII-)whitespace
8621	 *
8622	 * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
8623	 * @see https://dom.spec.whatwg.org/#concept-getelementsbyclassname
8624	 */
8625	getElementsByClassName: function(classNames) {
8626		var classNamesSet = toOrderedSet(classNames)
8627		return new LiveNodeList(this, function(base) {
8628			var ls = [];
8629			if (classNamesSet.length > 0) {
8630				_visitNode(base.documentElement, function(node) {
8631					if(node !== base && node.nodeType === ELEMENT_NODE) {
8632						var nodeClassNames = node.getAttribute('class')
8633						// can be null if the attribute does not exist
8634						if (nodeClassNames) {
8635							// before splitting and iterating just compare them for the most common case
8636							var matches = classNames === nodeClassNames;
8637							if (!matches) {
8638								var nodeClassNamesSet = toOrderedSet(nodeClassNames)
8639								matches = classNamesSet.every(arrayIncludes(nodeClassNamesSet))
8640							}
8641							if(matches) {
8642								ls.push(node);
8643							}
8644						}
8645					}
8646				});
8647			}
8648			return ls;
8649		});
8650	},
8651
8652	//document factory method:
8653	createElement :	function(tagName){
8654		var node = new Element();
8655		node.ownerDocument = this;
8656		node.nodeName = tagName;
8657		node.tagName = tagName;
8658		node.localName = tagName;
8659		node.childNodes = new NodeList();
8660		var attrs	= node.attributes = new NamedNodeMap();
8661		attrs._ownerElement = node;
8662		return node;
8663	},
8664	createDocumentFragment :	function(){
8665		var node = new DocumentFragment();
8666		node.ownerDocument = this;
8667		node.childNodes = new NodeList();
8668		return node;
8669	},
8670	createTextNode :	function(data){
8671		var node = new Text();
8672		node.ownerDocument = this;
8673		node.appendData(data)
8674		return node;
8675	},
8676	createComment :	function(data){
8677		var node = new Comment();
8678		node.ownerDocument = this;
8679		node.appendData(data)
8680		return node;
8681	},
8682	/**
8683	 * Returns a new CDATASection node whose data is `data`.
8684	 *
8685	 * __This implementation differs from the specification:__
8686	 * - calling this method on an HTML document does not throw `NotSupportedError`.
8687	 *
8688	 * @param {string} data
8689	 * @returns {CDATASection}
8690	 * @throws DOMException with code `INVALID_CHARACTER_ERR` if `data` contains `"]]>"`.
8691	 * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createCDATASection
8692	 * @see https://dom.spec.whatwg.org/#dom-document-createcdatasection
8693	 */
8694	createCDATASection :	function(data){
8695		if (data.indexOf(']]>') !== -1) {
8696			throw new DOMException(INVALID_CHARACTER_ERR, 'data contains "]]>"');
8697		}
8698		var node = new CDATASection();
8699		node.ownerDocument = this;
8700		node.appendData(data)
8701		return node;
8702	},
8703	/**
8704	 * Returns a ProcessingInstruction node whose target is target and data is data.
8705	 *
8706	 * __This implementation differs from the specification:__
8707	 * - it does not do any input validation on the arguments and doesn't throw "InvalidCharacterError".
8708	 *
8709	 * Note: When the resulting document is serialized with `requireWellFormed: true`, the
8710	 * serializer throws with code `INVALID_STATE_ERR` if `.target` is not a valid XML `NCName`
8711	 * (a `Name` with no colon) or is an ASCII case-insensitive match for `"xml"`, or if `.data`
8712	 * contains `?>` (W3C DOM Parsing §3.2.1.7). Without that option the target and data are
8713	 * emitted verbatim.
8714	 *
8715	 * @param {string} target
8716	 * @param {string} data
8717	 * @returns {ProcessingInstruction}
8718	 * @see https://developer.mozilla.org/docs/Web/API/Document/createProcessingInstruction
8719	 * @see https://dom.spec.whatwg.org/#dom-document-createprocessinginstruction
8720	 * @see https://www.w3.org/TR/DOM-Parsing/#dfn-concept-serialize-xml §3.2.1.7
8721	 */
8722	createProcessingInstruction :	function(target,data){
8723		var node = new ProcessingInstruction();
8724		node.ownerDocument = this;
8725		node.tagName = node.nodeName = node.target = target;
8726		node.nodeValue = node.data = data;
8727		return node;
8728	},
8729	createAttribute :	function(name){
8730		var node = new Attr();
8731		node.ownerDocument	= this;
8732		node.name = name;
8733		node.nodeName	= name;
8734		node.localName = name;
8735		node.specified = true;
8736		return node;
8737	},
8738	/**
8739	 * Creates an EntityReference object, serialized as `&name;`.
8740	 *
8741	 * The `name` is validated against the XML `Name` production at creation time;
8741 an invalid name
8742	 * throws a `DOMException` with code `INVALID_CHARACTER_ERR`. When the resulting node is
8743	 * serialized with `requireWellFormed: true`, the serializer re-validates `nodeName` and throws
8744	 * a `DOMException` with code `INVALID_STATE_ERR` if a later `nodeName` mutation made it invalid;
8745	 * without that option the name is emitted verbatim.
8746	 *
8747	 * Note: xmldom does not expand entities — the parser resolves entity references inline and never
8748	 * constructs `EntityReference` nodes, so this method is the only producer.
8749	 *
8750	 * @param {string} name The name of the entity to reference.
8751	 * @returns {EntityReference}
8752	 * @throws {DOMException} With code `INVALID_CHARACTER_ERR` when `name` is not a valid XML `Name`.
8753	 * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-392B75AE
8754	 */
8755	createEntityReference :	function(name){
8756		if (!tagNamePattern.test(name)) {
8757			throw new DOMException(INVALID_CHARACTER_ERR, 'not a valid xml name "' + name + '"');
8758		}
8759		var node = new EntityReference();
8760		node.ownerDocument	= this;
8761		node.nodeName	= name;
8762		return node;
8763	},
8764	// Introduced in DOM Level 2:
8765	createElementNS :	function(namespaceURI,qualifiedName){
8766		var node = new Element();
8767		var pl = qualifiedName.split(':');
8768		var attrs	= node.attributes = new NamedNodeMap();
8769		node.childNodes = new NodeList();
8770		node.ownerDocument = this;
8771		node.nodeName = qualifiedName;
8772		node.tagName = qualifiedName;
8773		node.namespaceURI = namespaceURI;
8774		if(pl.length == 2){
8775			node.prefix = pl[0];
8776			node.localName = pl[1];
8777		}else{
8778			//el.prefix = null;
8779			node.localName = qualifiedName;
8780		}
8781		attrs._ownerElement = node;
8782		return node;
8783	},
8784	// Introduced in DOM Level 2:
8785	createAttributeNS :	function(namespaceURI,qualifiedName){
8786		var node = new Attr();
8787		var pl = qualifiedName.split(':');
8788		node.ownerDocument = this;
8789		node.nodeName = qualifiedName;
8790		node.name = qualifiedName;
8791		node.namespaceURI = namespaceURI;
8792		node.specified = true;
8793		if(pl.length == 2){
8794			node.prefix = pl[0];
8795			node.localName = pl[1];
8796		}else{
8797			//el.prefix = null;
8798			node.localName = qualifiedName;
8799		}
8800		return node;
8801	}
8802};
8803_extends(Document,Node);
8804
8805
8806function Element() {
8807	this._nsMap = {};
8808};
8809Element.prototype = {
8810	nodeType : ELEMENT_NODE,
8811	hasAttribute : function(name){
8812		return this.getAttributeNode(name)!=null;
8813	},
8814	getAttribute : function(name){
8815		var attr = this.getAttributeNode(name);
8816		return attr && attr.value || '';
8817	},
8818	getAttributeNode : function(name){
8819		return this.attributes.getNamedItem(name);
8820	},
8821	setAttribute : function(name, value){
8822		var attr = this.ownerDocument.createAttribute(name);
8823		attr.value = attr.nodeValue = "" + value;
8824		this.setAttributeNode(attr)
8825	},
8826	removeAttribute : function(name){
8827		var attr = this.getAttributeNode(name)
8828		attr && this.removeAttributeNode(attr);
8829	},
8830
8831	//four real opeartion method
8832	appendChild:function(newChild){
8833		if(newChild.nodeType === DOCUMENT_FRAGMENT_NODE){
8834			return this.insertBefore(newChild,null);
8835		}else{
8836			return _appendSingleChild(this,newChild);
8837		}
8838	},
8839	setAttributeNode : function(newAttr){
8840		return this.attributes.setNamedItem(newAttr);
8841	},
8842	setAttributeNodeNS : function(newAttr){
8843		return this.attributes.setNamedItemNS(newAttr);
8844	},
8845	removeAttributeNode : function(oldAttr){
8846		//console.log(this == oldAttr.ownerElement)
8847		return this.attributes.removeNamedItem(oldAttr.nodeName);
8848	},
8849	//get real attribute name,and remove it by removeAttributeNode
8850	removeAttributeNS : function(namespaceURI, localName){
8851		var old = this.getAttributeNodeNS(namespaceURI, localName);
8852		old && this.removeAttributeNode(old);
8853	},
8854
8855	hasAttributeNS : function(namespaceURI, localName){
8856		return this.getAttributeNodeNS(namespaceURI, localName)!=null;
8857	},
8858	getAttributeNS : function(namespaceURI, localName){
8859		var attr = this.getAttributeNodeNS(namespaceURI, localName);
8860		return attr && attr.value || '';
8861	},
8862	setAttributeNS : function(namespaceURI, qualifiedName, value){
8863		var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName);
8864		attr.value = attr.nodeValue = "" + value;
8865		this.setAttributeNode(attr)
8866	},
8867	getAttributeNodeNS : function(namespaceURI, localName){
8868		return this.attributes.getNamedItemNS(namespaceURI, localName);
8869	},
8870
8871	getElementsByTagName : function(tagName){
8872		return new LiveNodeList(this,function(base){
8873			var ls = [];
8874			_visitNode(base,function(node){
8875				if(node !== base && node.nodeType == ELEMENT_NODE && (tagName === '*' || node.tagName == tagName)){
8876					ls.push(node);
8877				}
8878			});
8879			return ls;
8880		});
8881	},
8882	getElementsByTagNameNS : function(namespaceURI, localName){
8883		return new LiveNodeList(this,function(base){
8884			var ls = [];
8885			_visitNode(base,function(node){
8886				if(node !== base && node.nodeType === ELEMENT_NODE && (namespaceURI === '*' || node.namespaceURI === namespaceURI) && (localName === '*' || node.localName == localName)){
8887					ls.push(node);
8888				}
8889			});
8890			return ls;
8891
8892		});
8893	}
8894};
8895Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName;
8896Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS;
8897
8898
8899_extends(Element,Node);
8900function Attr() {
8901};
8902Attr.prototype.nodeType = ATTRIBUTE_NODE;
8903_extends(Attr,Node);
8904
8905
8906function CharacterData() {
8907};
8908CharacterData.prototype = {
8909	data : '',
8910	substringData : function(offset, count) {
8911		return this.data.substring(offset, offset+count);
8912	},
8913	appendData: function(text) {
8914		text = this.data+text;
8915		this.nodeValue = this.data = text;
8916		this.length = text.length;
8917	},
8918	insertData: function(offset,text) {
8919		this.replaceData(offset,0,text);
8920
8921	},
8922	appendChild:function(newChild){
8923		throw new Error(ExceptionMessage[HIERARCHY_REQUEST_ERR])
8924	},
8925	deleteData: function(offset, count) {
8926		this.replaceData(offset,count,"");
8927	},
8928	replaceData: function(offset, count, text) {
8929		var start = this.data.substring(0,offset);
8930		var end = this.data.substring(offset+count);
8931		text = start + text + end;
8932		this.nodeValue = this.data = text;
8933		this.length = text.length;
8934	}
8935}
8936_extends(CharacterData,Node);
8937function Text() {
8938};
8939Text.prototype = {
8940	nodeName : "#text",
8941	nodeType : TEXT_NODE,
8942	splitText : function(offset) {
8943		var text = this.data;
8944		var newText = text.substring(offset);
8945		text = text.substring(0, offset);
8946		this.data = this.nodeValue = text;
8947		this.length = text.length;
8948		var newNode = this.ownerDocument.createTextNode(newText);
8949		if(this.parentNode){
8950			this.parentNode.insertBefore(newNode, this.nextSibling);
8951		}
8952		return newNode;
8953	}
8954}
8955_extends(Text,CharacterData);
8956function Comment() {
8957};
8958Comment.prototype = {
8959	nodeName : "#comment",
8960	nodeType : COMMENT_NODE
8961}
8962_extends(Comment,CharacterData);
8963
8964function CDATASection() {
8965};
8966CDATASection.prototype = {
8967	nodeName : "#cdata-section",
8968	nodeType : CDATA_SECTION_NODE
8969}
8970_extends(CDATASection,CharacterData);
8971
8972
8973/**
8974 * Represents a DocumentType node (the `<!DOCTYPE ...>` declaration).
8975 *
8976 * `name`, `publicId`, `systemId`, and `internalSubset` are plain own-property assignments.
8977 * xmldom does not enforce the `readonly` constraint declared by the WHATWG DOM spec —
8978 * direct property writes succeed silently. Values are serialized verbatim when
8979 * `requireWellFormed` is false (the default). When the serializer is invoked with
8980 * `requireWellFormed: true` (via the 4th-parameter options object), it validates each
8981 * field — including `name`, which is checked against the XML `Name` production — and throws
8982 * `DOMException` with code `INVALID_STATE_ERR` on invalid values.
8983 *
8984 * @class
8985 * @see https://developer.mozilla.org/docs/Web/API/DocumentType MDN
8986 */
8987function DocumentType() {
8988};
8989DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE;
8990_extends(DocumentType,Node);
8991
8992function Notation() {
8993};
8994Notation.prototype.nodeType = NOTATION_NODE;
8995_extends(Notation,Node);
8996
8997function Entity() {
8998};
8999Entity.prototype.nodeType = ENTITY_NODE;
9000_extends(Entity,Node);
9001
9002/**
9003 * Represents an EntityReference node, serialized as `&nodeName;`.
9004 *
9005 * `nodeName` is the referenced entity's name, stored verbatim. When serialized with
9006 * `requireWellFormed: true`, the serializer validates `nodeName` against the XML `Name` production
9007 * and throws a `DOMException` with code `INVALID_STATE_ERR` if it does not match; without that
9008 * option the name is emitted verbatim between `&` and `;`.
9009 *
9010 * Note: xmldom does not expand entities — the parser resolves entity references inline and never
9011 * constructs `EntityReference` nodes, so the only producer is `Document.createEntityReference`.
9012 *
9013 * @class
9014 * @see https://www.w3.org/TR/xml/#NT-Name
9015 */
9016function EntityReference() {
9017};
9018EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE;
9019_extends(EntityReference,Node);
9020
9021function DocumentFragment() {
9022};
9023DocumentFragment.prototype.nodeName =	"#document-fragment";
9024DocumentFragment.prototype.nodeType =	DOCUMENT_FRAGMENT_NODE;
9025_extends(DocumentFragment,Node);
9026
9027
9028function ProcessingInstruction() {
9029}
9030ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE;
9031_extends(ProcessingInstruction,Node);
9032function XMLSerializer(){}
9033/**
9034 * Returns the result of serializing `node` to XML.
9035 *
9036 * When `options.requireWellFormed` is `true`, the serializer throws for c
9036ontent that would
9037 * produce ill-formed XML.
9038 *
9039 * __This implementation differs from the specification:__
9040 * - CDATASection nodes whose data contains `]]>` are serialized by splitting the section
9041 *   at each `]]>` occurrence (following W3C DOM Level 3 Core `split-cdata-sections`
9042 *   default behaviour) unless `requireWellFormed` is `true`.
9043 * - when `requireWellFormed` is `true`, `DOMException` with code `INVALID_STATE_ERR`
9044 *   is only thrown to prevent injection vectors, not for all the spec mandated checks.
9045 *
9046 * @param {Node} node
9047 * @param {boolean} [isHtml]
9048 * @param {function} [nodeFilter]
9049 * @param {Object} [options]
9050 * @param {boolean} [options.requireWellFormed=false]
9051 * When `true`, throws for content that would produce ill-formed XML.
9052 * @returns {string}
9053 * @throws {DOMException}
9054 * With code `INVALID_STATE_ERR` when `requireWellFormed` is `true` and:
9055 * - an Element's qualified name (including any namespace prefix) is not a valid XML QName,
9056 * - an attribute's qualified name (including a synthesized `xmlns:` namespace declaration) is
9057 *   not a valid XML QName,
9058 * - a CDATASection node's data contains `"]]>"`,
9059 * - a Comment node's data contains `"-->"` (bare `"--"` does not throw on this branch),
9060 * - a ProcessingInstruction's target is not a valid XML `NCName` (a `Name` with no colon) or is
9061 *   an ASCII case-insensitive match for `"xml"`, or its data contains `"?>"`,
9062 * - a DocumentType's `name` is not a valid XML `Name` (XML 1.0 production [5]),
9063 * - a DocumentType's `publicId` is non-empty and does not match the XML `PubidLiteral`
9064 *   production,
9065 * - a DocumentType's `systemId` is non-empty and does not match the XML `SystemLiteral`
9066 *   production,
9067 * - a DocumentType's `internalSubset` contains `"]>"`, or
9068 * - an EntityReference's `nodeName` is not a valid XML `Name` (XML 1.0 production [5]).
9069 * Note: xmldom does not enforce `readonly` on DocumentType fields — direct property
9070 * writes succeed and are covered by the serializer-level checks above.
9071 * @see https://html.spec.whatwg.org/#dom-xmlserializer-serializetostring
9072 * @see https://w3c.github.io/DOM-Parsing/#xml-serialization
9073 * @see https://github.com/w3c/DOM-Parsing/issues/84
9074 */
9075XMLSerializer.prototype.serializeToString = function(node,isHtml,nodeFilter,options){
9076	return nodeSerializeToString.call(node,isHtml,nodeFilter,options);
9077}
9078Node.prototype.toString = nodeSerializeToString;
9079function nodeSerializeToString(isHtml,nodeFilter,options){
9080	var requireWellFormed = !!options && !!options.requireWellFormed;
9081	var buf = [];
9082	var refNode = this.nodeType == 9 && this.documentElement || this;
9083	var prefix = refNode.prefix;
9084	var uri = refNode.namespaceURI;
9085
9086	if(uri && prefix == null){
9087		//console.log(prefix)
9088		var prefix = refNode.lookupPrefix(uri);
9089		if(prefix == null){
9090			//isHTML = true;
9091			var visibleNamespaces=[
9092			{namespace:uri,prefix:null}
9093			//{namespace:uri,prefix:''}
9094			]
9095		}
9096	}
9097	serializeToString(this,buf,isHtml,nodeFilter,visibleNamespaces,requireWellFormed);
9098	//console.log('###',this.nodeType,uri,prefix,buf.join(''))
9099	return buf.join('');
9100}
9101
9102function needNamespaceDefine(node, isHTML, visibleNamespaces) {
9103	var prefix = node.prefix || '';
9104	var uri = node.namespaceURI;
9105	// According to [Namespaces in XML 1.0](https://www.w3.org/TR/REC-xml-names/#ns-using) ,
9106	// and more specifically https://www.w3.org/TR/REC-xml-names/#nsc-NoPrefixUndecl :
9107	// > In a namespace declaration for a prefix [...], the attribute value MUST NOT be empty.
9108	// in a similar manner [Namespaces in XML 1.1](https://www.w3.org/TR/xml-names11/#ns-using)
9109	// and more specifically https://www.w3.org/TR/xml-names11/#nsc-NSDeclared :
9110	// > [...] Furthermore, the attribute value [...] must not be an empty string.
9111	// so serializing empty namespace value like xmlns:ds="" would produce an invalid XML document.
9112	if (!uri) {
9113		return false;
9114	}
9115	if (prefix === "xml" && uri === NAMESPACE.XML || uri === NAMESPACE.XMLNS) {
9116		return false;
9117	}
9118
9119	var i = visibleNamespaces.length
9120	while (i--) {
9121		var ns = visibleNamespaces[i];
9122		// get namespace prefix
9123		if (ns.prefix === prefix) {
9124			return ns.namespace !== uri;
9125		}
9126	}
9127	return true;
9128}
9129/**
9130 * Well-formed constraint: No < in Attribute Values
9131 * > The replacement text of any entity referred to directly or indirectly
9132 * > in an attribute value must not contain a <.
9133 * @see https://www.w3.org/TR/xml11/#CleanAttrVals
9134 * @see https://www.w3.org/TR/xml11/#NT-AttValue
9135 *
9136 * Literal whitespace other than space that appear in attribute values
9137 * are serialized as their entity references, so they will be preserved.
9138 * (In contrast to whitespace literals in the input which are normalized to spaces)
9139 * @see https://www.w3.org/TR/xml11/#AVNormalize
9140 * @see https://w3c.github.io/DOM-Parsing/#serializing-an-element-s-attributes
9141 */
vendor: 4,812 bytes, lines 9142-9282
9142function addSerializedAttribute(buf, qualifiedName, value, requireWellFormed) {
9143	if (requireWellFormed && !tagNamePattern.test(qualifiedName)) {
9144		throw new DOMException(INVALID_STATE_ERR, 'The attribute name "' + qualifiedName + '" is not a valid XML QName');
9145	}
9146	buf.push(' ', qualifiedName, '="', value.replace(/[<>&"\t\n\r]/g, _xmlEncoder), '"')
9147}
9148
9149function serializeToString(node, buf, isHTML, nodeFilter, visibleNamespaces, requireWellFormed) {
9150	if (!visibleNamespaces) {
9151		visibleNamespaces = [];
9152	}
9153	walkDOM(node, { ns: visibleNamespaces, isHTML: isHTML }, {
9154		enter: function (n, ctx) {
9155			var ns = ctx.ns;
9156			var html = ctx.isHTML;
9157
9158			if (nodeFilter) {
9159				n = nodeFilter(n);
9160				if (n) {
9161					if (typeof n == 'string') {
9162						buf.push(n);
9163						return null;
9164					}
9165				} else {
9166					return null;
9167				}
9168			}
9169
9170			switch (n.nodeType) {
9171				case ELEMENT_NODE:
9172					var attrs = n.attributes;
9173					var len = attrs.length;
9174					var nodeName = n.tagName;
9175
9176					html = NAMESPACE.isHTML(n.namespaceURI) || html;
9177
9178					var prefixedNodeName = nodeName;
9179					if (!html && !n.prefix && n.namespaceURI) {
9180						var defaultNS;
9181						// lookup current default ns from `xmlns` attribute
9182						for (var ai = 0; ai < attrs.length; ai++) {
9183							if (attrs.item(ai).name === 'xmlns') {
9184								defaultNS = attrs.item(ai).value;
9185								break;
9186							}
9187						}
9188						if (!defaultNS) {
9189							// lookup current default ns in visibleNamespaces
9190							for (var nsi = ns.length - 1; nsi >= 0; nsi--) {
9191								var nsEntry = ns[nsi];
9192								if (nsEntry.prefix === '' && nsEntry.namespace === n.namespaceURI) {
9193									defaultNS = nsEntry.namespace;
9194									break;
9195								}
9196							}
9197						}
9198						if (defaultNS !== n.namespaceURI) {
9199							for (var nsi = ns.length - 1; nsi >= 0; nsi--) {
9200								var nsEntry = ns[nsi];
9201								if (nsEntry.namespace === n.namespaceURI) {
9202									if (nsEntry.prefix) {
9203										prefixedNodeName = nsEntry.prefix + ':' + nodeName;
9204									}
9205									break;
9206								}
9207							}
9208						}
9209					}
9210
9211					if (requireWellFormed && !tagNamePattern.test(prefixedNodeName)) {
9212						throw new DOMException(INVALID_STATE_ERR, 'The element name "' + prefixedNodeName + '" is not a valid XML QName');
9213					}
9214					buf.push('<', prefixedNodeName);
9215
9216					// Build a fresh namespace snapshot for this element's children.
9217					// The slice prevents sibling elements from inheriting each other's declarations.
9218					var childNs = ns.slice();
9219					for (var i = 0; i < len; i++) {
9220						var attr = attrs.item(i);
9221						if (attr.prefix == 'xmlns') {
9222							childNs.push({ prefix: attr.localName, namespace: attr.value });
9223						} else if (attr.nodeName == 'xmlns') {
9224							childNs.push({ prefix: '', namespace: attr.value });
9225						}
9226					}
9227
9228					for (var i = 0; i < len; i++) {
9229						var attr = attrs.item(i);
9230						if (needNamespaceDefine(attr, html, childNs)) {
9231							var attrPrefix = attr.prefix || '';
9232							var uri = attr.namespaceURI;
9233							addSerializedAttribute(buf, attrPrefix ? 'xmlns:' + attrPrefix : 'xmlns', uri, requireWellFormed);
9234							childNs.push({ prefix: attrPrefix, namespace: uri });
9235						}
9236						// Apply nodeFilter and serialize the attribute.
9237						var filteredAttr = nodeFilter ? nodeFilter(attr) : attr;
9238						if (filteredAttr) {
9239							if (typeof filteredAttr === 'string') {
9240								buf.push(filteredAttr);
9241							} else {
9242								addSerializedAttribute(buf, filteredAttr.name, filteredAttr.value, requireWellFormed);
9243							}
9244						}
9245					}
9246
9247					// add namespace for current node
9248					if (nodeName === prefixedNodeName && needNamespaceDefine(n, html, childNs)) {
9249						var nodePrefix = n.prefix || '';
9250						var uri = n.namespaceURI;
9251						addSerializedAttribute(buf, nodePrefix ? 'xmlns:' + nodePrefix : 'xmlns', uri, requireWellFormed);
9252						childNs.push({ prefix: nodePrefix, namespace: uri });
9253					}
9254
9255					var child = n.firstChild;
9256					if (child || html && !/^(?:meta|link|img|br|hr|input)$/i.test(nodeName)) {
9257						buf.push('>');
9258						if (html && /^script$/i.test(nodeName)) {
9259							// Inline serialization for <script> children; return null to skip walkDOM descent.
9260							while (child) {
9261								if (child.data) {
9262									buf.push(child.data);
9263								} else {
9264									serializeToString(child, buf, html, nodeFilter, childNs.slice(), requireWellFormed);
9265								}
9266								child = child.nextSibling;
9267							}
9268							buf.push('</', nodeName, '>');
9269							return null;
9270						}
9271						// Return child context; walkDOM descends and exit emits the closing tag.
9272						return { ns: childNs, isHTML: html, tag: prefixedNodeName };
9273					} else {
9274						buf.push('/>');
9275						return null;
9276					}
9277
9278				case DOCUMENT_NODE:
9279				case DOCUMENT_FRAGMENT_NODE:
9280					// Descend into children; exit is a no-op (tag is null).
9281					return { ns: ns.slice(), isHTML: html, tag: null };
9282
vendor: 9,265 bytes, lines 9283-9559
9283				case ATTRIBUTE_NODE:
9284					addSerializedAttribute(buf, n.name, n.value, requireWellFormed);
9285					return null;
9286
9287				case TEXT_NODE:
9288					/**
9289					 * The ampersand character (&) and the left angle bracket (<) must not appear in their literal form,
9290					 * except when used as markup delimiters, or within a comment, a processing instruction, or a CDATA section.
9291					 * If they are needed elsewhere, they must be escaped using either numeric character references or the strings
9292					 * `&amp;` and `&lt;` respectively.
9293					 * The right angle bracket (>) may be represented using the string " &gt; ", and must, for compatibility,
9294					 * be escaped using either `&gt;` or a character reference when it appears in the string `]]>` in content,
9295					 * when that string is not marking the end of a CDATA section.
9296					 *
9297					 * In the content of elements, character data is any string of characters
9298					 * which does not contain the start-delimiter of any markup
9299					 * and does not include the CDATA-section-close delimiter, `]]>`.
9300					 *
9301					 * @see https://www.w3.org/TR/xml/#NT-CharData
9302					 * @see https://w3c.github.io/DOM-Parsing/#xml-serializing-a-text-node
9303					 */
9304					buf.push(n.data.replace(/[<&>]/g, _xmlEncoder));
9305					return null;
9306
9307				case CDATA_SECTION_NODE:
9308					if (requireWellFormed && n.data.indexOf(']]>') !== -1) {
9309						throw new DOMException(INVALID_STATE_ERR, 'The CDATASection data contains "]]>"');
9310					}
9311					buf.push('<![CDATA[', n.data.replace(/]]>/g, ']]]]><![CDATA[>'), ']]>');
9312					return null;
9313
9314				case COMMENT_NODE:
9315					if (requireWellFormed && n.data.indexOf('-->') !== -1) {
9316						throw new DOMException(INVALID_STATE_ERR, 'The comment node data contains "-->"');
9317					}
9318					buf.push('<!--', n.data, '-->');
9319					return null;
9320
9321				case DOCUMENT_TYPE_NODE:
9322					if (requireWellFormed) {
9323						if (!tagNamePattern.test(n.name)) {
9324							throw new DOMException(INVALID_STATE_ERR, 'The doctype name "' + n.name + '" is not a valid XML Name');
9325						}
9326						if (n.publicId && !/^("[\x20\r\na-zA-Z0-9\-()+,.\/:=?;!*#@$_%']*"|'[\x20\r\na-zA-Z0-9\-()+,.\/:=?;!*#@$_%'"]*')$/.test(n.publicId)) {
9327							throw new DOMException(INVALID_STATE_ERR, 'DocumentType publicId is not a valid PubidLiteral');
9328						}
9329						if (n.systemId && !/^("[^"]*"|'[^']*')$/.test(n.systemId)) {
9330							throw new DOMException(INVALID_STATE_ERR, 'DocumentType systemId is not a valid SystemLiteral');
9331						}
9332						if (n.internalSubset && n.internalSubset.indexOf(']>') !== -1) {
9333							throw new DOMException(INVALID_STATE_ERR, 'DocumentType internalSubset contains "]>"');
9334						}
9335					}
9336					var pubid = n.publicId;
9337					var sysid = n.systemId;
9338					buf.push('<!DOCTYPE ', n.name);
9339					if (pubid) {
9340						buf.push(' PUBLIC ', pubid);
9341						if (sysid && sysid != '.') {
9342							buf.push(' ', sysid);
9343						}
9344						buf.push('>');
9345					} else if (sysid && sysid != '.') {
9346						buf.push(' SYSTEM ', sysid, '>');
9347					} else {
9348						var sub = n.internalSubset;
9349						if (sub) {
9350							buf.push(' [', sub, ']');
9351						}
9352						buf.push('>');
9353					}
9354					return null;
9355
9356				case PROCESSING_INSTRUCTION_NODE:
9357					if (requireWellFormed) {
9358						if (!tagNamePattern.test(n.target) || n.target.indexOf(':') !== -1 || n.target.toLowerCase() === 'xml') {
9359							throw new DOMException(
9360								INVALID_STATE_ERR,
9361								'The processing instruction target "' + n.target + '" is not a valid XML NCName or is reserved'
9362							);
9363						}
9364						if (n.data.indexOf('?>') !== -1) {
9365							throw new DOMException(INVALID_STATE_ERR, 'The ProcessingInstruction data contains "?>"');
9366						}
9367					}
9368					buf.push('<?', n.target, ' ', n.data, '?>');
9369					return null;
9370
9371				case ENTITY_REFERENCE_NODE:
9372					if (requireWellFormed && !tagNamePattern.test(n.nodeName)) {
9373						throw new DOMException(
9374							INVALID_STATE_ERR,
9375							'The entity reference name "' + n.nodeName + '" is not a valid XML Name'
9376						);
9377					}
9378					buf.push('&', n.nodeName, ';');
9379					return null;
9380
9381				//case ENTITY_NODE:
9382				//case NOTATION_NODE:
9383				default:
9384					buf.push('??', n.nodeName);
9385					return null;
9386			}
9387		},
9388		exit: function (n, childCtx) {
9389			if (childCtx && childCtx.tag) {
9390				buf.push('</', childCtx.tag, '>');
9391			}
9392		},
9393	});
9394}
9395/**
9396 * Imports a node from a different document into `doc`, creating a new copy.
9397 * Delegates to {@link walkDOM} for traversal. Each node in the subtree is shallow-cloned,
9398 * stamped with `doc` as its `ownerDocument`, and detached (`parentNode` set to `null`).
9399 * Children are imported recursively when `deep` is `true`; for {@link Attr} nodes `deep` is
9400 * always forced to `true`
9401 * because an attribute's value lives in a child text node.
9402 *
9403 * @param {Document} doc
9404 * The document that will own the imported node.
9405 * @param {Node} node
9406 * The node to import.
9407 * @param {boolean} deep
9408 * If `true`, descendants are imported recursively.
9409 * @returns {Node}
9410 * The newly imported node, now owned by `doc`.
9411 */
9412function importNode(doc, node, deep) {
9413	var destRoot;
9414	walkDOM(node, null, {
9415		enter: function (srcNode, destParent) {
9416			// Shallow-clone the node and stamp it into the target document.
9417			var destNode = srcNode.cloneNode(false);
9418			destNode.ownerDocument = doc;
9419			destNode.parentNode = null;
9420			// capture as the root of the imported subtree or attach to parent.
9421			if (destParent === null) {
9422				destRoot = destNode;
9423			} else {
9424				destParent.appendChild(destNode);
9425			}
9426			// ATTRIBUTE_NODE must always be imported deeply: its value lives in a child text node.
9427			var shouldDeep = srcNode.nodeType === ATTRIBUTE_NODE || deep;
9428			return shouldDeep ? destNode : null;
9429		},
9430	});
9431	return destRoot;
9432}
9433//
9434//var _relationMap = {firstChild:1,lastChild:1,previousSibling:1,nextSibling:1,
9435//					attributes:1,childNodes:1,parentNode:1,documentElement:1,doctype,};
9436function cloneNode(doc, node, deep) {
9437	var destRoot;
9438	walkDOM(node, null, {
9439		enter: function (srcNode, destParent) {
9440			// 1. Create a blank node of the same type and copy all scalar own properties.
9441			var destNode = new srcNode.constructor();
9442			for (var n in srcNode) {
9443				if (Object.prototype.hasOwnProperty.call(srcNode, n)) {
9444					var v = srcNode[n];
9445					if (typeof v != 'object') {
9446						if (v != destNode[n]) {
9447							destNode[n] = v;
9448						}
9449					}
9450				}
9451			}
9452			if (srcNode.childNodes) {
9453				destNode.childNodes = new NodeList();
9454			}
9455			destNode.ownerDocument = doc;
9456			// 2. Handle node-type-specific setup.
9457			//    Attributes are not DOM children, so they are cloned inline here
9458			//    rather than by walkDOM descent.
9459			//    ATTRIBUTE_NODE forces deep=true so its own children are walked.
9460			var shouldDeep = deep;
9461			switch (destNode.nodeType) {
9462				case ELEMENT_NODE:
9463					var attrs = srcNode.attributes;
9464					var attrs2 = (destNode.attributes = new NamedNodeMap());
9465					var len = attrs.length;
9466					attrs2._ownerElement = destNode;
9467					for (var i = 0; i < len; i++) {
9468						destNode.setAttributeNode(cloneNode(doc, attrs.item(i), true));
9469					}
9470					break;
9471				case ATTRIBUTE_NODE:
9472					shouldDeep = true;
9473			}
9474			// 3. Attach to parent, or capture as the root of the cloned subtree.
9475			if (destParent !== null) {
9476				destParent.appendChild(destNode);
9477			} else {
9478				destRoot = destNode;
9479			}
9480			// 4. Return destNode as the context for children (causes walkDOM to descend),
9481			//    or null to skip children (shallow clone).
9482			return shouldDeep ? destNode : null;
9483		},
9484	});
9485	return destRoot;
9486}
9487
9488function __set__(object,key,value){
9489	object[key] = value
9490}
9491//do dynamic
9492try{
9493	if(Object.defineProperty){
9494		Object.defineProperty(LiveNodeList.prototype,'length',{
9495			get:function(){
9496				_updateLiveList(this);
9497				return this.$$length;
9498			}
9499		});
9500
9501		/**
9502		 * The text content of this node and its descendants.
9503		 *
9504		 * Setting `textContent` on an element or document fragment replaces all child nodes with a
9505		 * single text node; on other nodes it sets `data`, `value`, and `nodeValue` directly.
9506		 *
9507		 * @type {string | null}
9508		 * @see {@link https://dom.spec.whatwg.org/#dom-node-textcontent}
9509		 */
9510		Object.defineProperty(Node.prototype, 'textContent', {
9511			get: function () {
9512				if (this.nodeType === ELEMENT_NODE || this.nodeType === DOCUMENT_FRAGMENT_NODE) {
9513					var buf = [];
9514					walkDOM(this, null, {
9515						enter: function (n) {
9516							if (n.nodeType === ELEMENT_NODE || n.nodeType === DOCUMENT_FRAGMENT_NODE) {
9517								return true; // enter children
9518							}
9519							if (n.nodeType === PROCESSING_INSTRUCTION_NODE || n.nodeType === COMMENT_NODE) {
9520								return null; // excluded from text content
9521							}
9522							buf.push(n.nodeValue);
9523						},
9524					});
9525					return buf.join('');
9526				}
9527				return this.nodeValue;
9528			},
9529
9530			set: function (data) {
9531				switch (this.nodeType) {
9532					case ELEMENT_NODE:
9533					case DOCUMENT_FRAGMENT_NODE:
9534						while (this.firstChild) {
9535							this.removeChild(this.firstChild);
9536						}
9537						if (data || String(data)) {
9538							this.appendChild(this.ownerDocument.createTextNode(data));
9539						}
9540						break;
9541
9542					default:
9543						this.data = data;
9544						this.value = data;
9545						this.nodeValue = data;
9546				}
9547			},
9548		})
9549
9550		__set__ = function(object,key,value){
9551			//console.log(value)
9552			object['$$'+key] = value
9553		}
9554	}
9555}catch(e){//ie8
9556}
9557
9558//if(typeof require == 'function'){
9559	exports.DocumentType = DocumentType;
9560	exports.DOMException = DOMException;
9561	exports.DOMImplementation = DOMImplementation;
9562	exports.Element = Element;
9563	exports.Node = Node;
9564	exports.NodeList = NodeList;
9565	exports.walkDOM = walkDOM;
9566	exports.XMLSerializer = XMLSerializer;
9567//}
9568
9569},{"./conventions":29}],32:[function(require,module,exports){
9570'use strict';
9571
9572var freeze = require('./conventions')
vendor: 44,413 bytes, lines 9572-11735
9572.freeze;
9573
9574/**
9575 * The entities that are predefined in every XML document.
9576 *
9577 * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-predefined-ent W3C XML 1.1
9578 * @see https://www.w3.org/TR/2008/REC-xml-20081126/#sec-predefined-ent W3C XML 1.0
9579 * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Predefined_entities_in_XML Wikipedia
9580 */
9581exports.XML_ENTITIES = freeze({
9582	amp: '&',
9583	apos: "'",
9584	gt: '>',
9585	lt: '<',
9586	quot: '"',
9587});
9588
9589/**
9590 * A map of all entities that are detected in an HTML document.
9591 * They contain all entries from `XML_ENTITIES`.
9592 *
9593 * @see XML_ENTITIES
9594 * @see DOMParser.parseFromString
9595 * @see DOMImplementation.prototype.createHTMLDocument
9596 * @see https://html.spec.whatwg.org/#named-character-references WHATWG HTML(5) Spec
9597 * @see https://html.spec.whatwg.org/entities.json JSON
9598 * @see https://www.w3.org/TR/xml-entity-names/ W3C XML Entity Names
9599 * @see https://www.w3.org/TR/html4/sgml/entities.html W3C HTML4/SGML
9600 * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Character_entity_references_in_HTML Wikipedia (HTML)
9601 * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Entities_representing_special_characters_in_XHTML Wikpedia (XHTML)
9602 */
9603exports.HTML_ENTITIES = freeze({
9604	Aacute: '\u00C1',
9605	aacute: '\u00E1',
9606	Abreve: '\u0102',
9607	abreve: '\u0103',
9608	ac: '\u223E',
9609	acd: '\u223F',
9610	acE: '\u223E\u0333',
9611	Acirc: '\u00C2',
9612	acirc: '\u00E2',
9613	acute: '\u00B4',
9614	Acy: '\u0410',
9615	acy: '\u0430',
9616	AElig: '\u00C6',
9617	aelig: '\u00E6',
9618	af: '\u2061',
9619	Afr: '\uD835\uDD04',
9620	afr: '\uD835\uDD1E',
9621	Agrave: '\u00C0',
9622	agrave: '\u00E0',
9623	alefsym: '\u2135',
9624	aleph: '\u2135',
9625	Alpha: '\u0391',
9626	alpha: '\u03B1',
9627	Amacr: '\u0100',
9628	amacr: '\u0101',
9629	amalg: '\u2A3F',
9630	AMP: '\u0026',
9631	amp: '\u0026',
9632	And: '\u2A53',
9633	and: '\u2227',
9634	andand: '\u2A55',
9635	andd: '\u2A5C',
9636	andslope: '\u2A58',
9637	andv: '\u2A5A',
9638	ang: '\u2220',
9639	ange: '\u29A4',
9640	angle: '\u2220',
9641	angmsd: '\u2221',
9642	angmsdaa: '\u29A8',
9643	angmsdab: '\u29A9',
9644	angmsdac: '\u29AA',
9645	angmsdad: '\u29AB',
9646	angmsdae: '\u29AC',
9647	angmsdaf: '\u29AD',
9648	angmsdag: '\u29AE',
9649	angmsdah: '\u29AF',
9650	angrt: '\u221F',
9651	angrtvb: '\u22BE',
9652	angrtvbd: '\u299D',
9653	angsph: '\u2222',
9654	angst: '\u00C5',
9655	angzarr: '\u237C',
9656	Aogon: '\u0104',
9657	aogon: '\u0105',
9658	Aopf: '\uD835\uDD38',
9659	aopf: '\uD835\uDD52',
9660	ap: '\u2248',
9661	apacir: '\u2A6F',
9662	apE: '\u2A70',
9663	ape: '\u224A',
9664	apid: '\u224B',
9665	apos: '\u0027',
9666	ApplyFunction: '\u2061',
9667	approx: '\u2248',
9668	approxeq: '\u224A',
9669	Aring: '\u00C5',
9670	aring: '\u00E5',
9671	Ascr: '\uD835\uDC9C',
9672	ascr: '\uD835\uDCB6',
9673	Assign: '\u2254',
9674	ast: '\u002A',
9675	asymp: '\u2248',
9676	asympeq: '\u224D',
9677	Atilde: '\u00C3',
9678	atilde: '\u00E3',
9679	Auml: '\u00C4',
9680	auml: '\u00E4',
9681	awconint: '\u2233',
9682	awint: '\u2A11',
9683	backcong: '\u224C',
9684	backepsilon: '\u03F6',
9685	backprime: '\u2035',
9686	backsim: '\u223D',
9687	backsimeq: '\u22CD',
9688	Backslash: '\u2216',
9689	Barv: '\u2AE7',
9690	barvee: '\u22BD',
9691	Barwed: '\u2306',
9692	barwed: '\u2305',
9693	barwedge: '\u2305',
9694	bbrk: '\u23B5',
9695	bbrktbrk: '\u23B6',
9696	bcong: '\u224C',
9697	Bcy: '\u0411',
9698	bcy: '\u0431',
9699	bdquo: '\u201E',
9700	becaus: '\u2235',
9701	Because: '\u2235',
9702	because: '\u2235',
9703	bemptyv: '\u29B0',
9704	bepsi: '\u03F6',
9705	bernou: '\u212C',
9706	Bernoullis: '\u212C',
9707	Beta: '\u0392',
9708	beta: '\u03B2',
9709	beth: '\u2136',
9710	between: '\u226C',
9711	Bfr: '\uD835\uDD05',
9712	bfr: '\uD835\uDD1F',
9713	bigcap: '\u22C2',
9714	bigcirc: '\u25EF',
9715	bigcup: '\u22C3',
9716	bigodot: '\u2A00',
9717	bigoplus: '\u2A01',
9718	bigotimes: '\u2A02',
9719	bigsqcup: '\u2A06',
9720	bigstar: '\u2605',
9721	bigtriangledown: '\u25BD',
9722	bigtriangleup: '\u25B3',
9723	biguplus: '\u2A04',
9724	bigvee: '\u22C1',
9725	bigwedge: '\u22C0',
9726	bkarow: '\u290D',
9727	blacklozenge: '\u29EB',
9728	blacksquare: '\u25AA',
9729	blacktriangle: '\u25B4',
9730	blacktriangledown: '\u25BE',
9731	blacktriangleleft: '\u25C2',
9732	blacktriangleright: '\u25B8',
9733	blank: '\u2423',
9734	blk12: '\u2592',
9735	blk14: '\u2591',
9736	blk34: '\u2593',
9737	block: '\u2588',
9738	bne: '\u003D\u20E5',
9739	bnequiv: '\u2261\u20E5',
9740	bNot: '\u2AED',
9741	bnot: '\u2310',
9742	Bopf: '\uD835\uDD39',
9743	bopf: '\uD835\uDD53',
9744	bot: '\u22A5',
9745	bottom: '\u22A5',
9746	bowtie: '\u22C8',
9747	boxbox: '\u29C9',
9748	boxDL: '\u2557',
9749	boxDl: '\u2556',
9750	boxdL: '\u2555',
9751	boxdl: '\u2510',
9752	boxDR: '\u2554',
9753	boxDr: '\u2553',
9754	boxdR: '\u2552',
9755	boxdr: '\u250C',
9756	boxH: '\u2550',
9757	boxh: '\u2500',
9758	boxHD: '\u2566',
9759	boxHd: '\u2564',
9760	boxhD: '\u2565',
9761	boxhd: '\u252C',
9762	boxHU: '\u2569',
9763	boxHu: '\u2567',
9764	boxhU: '\u2568',
9765	boxhu: '\u2534',
9766	boxminus: '\u229F',
9767	boxplus: '\u229E',
9768	boxtimes: '\u22A0',
9769	boxUL: '\u255D',
9770	boxUl: '\u255C',
9771	boxuL: '\u255B',
9772	boxul: '\u2518',
9773	boxUR: '\u255A',
9774	boxUr: '\u2559',
9775	boxuR: '\u2558',
9776	boxur: '\u2514',
9777	boxV: '\u2551',
9778	boxv: '\u2502',
9779	boxVH: '\u256C',
9780	boxVh: '\u256B',
9781	boxvH: '\u256A',
9782	boxvh: '\u253C',
9783	boxVL: '\u2563',
9784	boxVl: '\u2562',
9785	boxvL: '\u2561',
9786	boxvl: '\u2524',
9787	boxVR: '\u2560',
9788	boxVr: '\u255F',
9789	boxvR: '\u255E',
9790	boxvr: '\u251C',
9791	bprime: '\u2035',
9792	Breve: '\u02D8',
9793	breve: '\u02D8',
9794	brvbar: '\u00A6',
9795	Bscr: '\u212C',
9796	bscr: '\uD835\uDCB7',
9797	bsemi: '\u204F',
9798	bsim: '\u223D',
9799	bsime: '\u22CD',
9800	bsol: '\u005C',
9801	bsolb: '\u29C5',
9802	bsolhsub: '\u27C8',
9803	bull: '\u2022',
9804	bullet: '\u2022',
9805	bump: '\u224E',
9806	bumpE: '\u2AAE',
9807	bumpe: '\u224F',
9808	Bumpeq: '\u224E',
9809	bumpeq: '\u224F',
9810	Cacute: '\u0106',
9811	cacute: '\u0107',
9812	Cap: '\u22D2',
9813	cap: '\u2229',
9814	capand: '\u2A44',
9815	capbrcup: '\u2A49',
9816	capcap: '\u2A4B',
9817	capcup: '\u2A47',
9818	capdot: '\u2A40',
9819	CapitalDifferentialD: '\u2145',
9820	caps: '\u2229\uFE00',
9821	caret: '\u2041',
9822	caron: '\u02C7',
9823	Cayleys: '\u212D',
9824	ccaps: '\u2A4D',
9825	Ccaron: '\u010C',
9826	ccaron: '\u010D',
9827	Ccedil: '\u00C7',
9828	ccedil: '\u00E7',
9829	Ccirc: '\u0108',
9830	ccirc: '\u0109',
9831	Cconint: '\u2230',
9832	ccups: '\u2A4C',
9833	ccupssm: '\u2A50',
9834	Cdot: '\u010A',
9835	cdot: '\u010B',
9836	cedil: '\u00B8',
9837	Cedilla: '\u00B8',
9838	cemptyv: '\u29B2',
9839	cent: '\u00A2',
9840	CenterDot: '\u00B7',
9841	centerdot: '\u00B7',
9842	Cfr: '\u212D',
9843	cfr: '\uD835\uDD20',
9844	CHcy: '\u0427',
9845	chcy: '\u0447',
9846	check: '\u2713',
9847	checkmark: '\u2713',
9848	Chi: '\u03A7',
9849	chi: '\u03C7',
9850	cir: '\u25CB',
9851	circ: '\u02C6',
9852	circeq: '\u2257',
9853	circlearrowleft: '\u21BA',
9854	circlearrowright: '\u21BB',
9855	circledast: '\u229B',
9856	circledcirc: '\u229A',
9857	circleddash: '\u229D',
9858	CircleDot: '\u2299',
9859	circledR: '\u00AE',
9860	circledS: '\u24C8',
9861	CircleMinus: '\u2296',
9862	CirclePlus: '\u2295',
9863	CircleTimes: '\u2297',
9864	cirE: '\u29C3',
9865	cire: '\u2257',
9866	cirfnint: '\u2A10',
9867	cirmid: '\u2AEF',
9868	cirscir: '\u29C2',
9869	ClockwiseContourIntegral: '\u2232',
9870	CloseCurlyDoubleQuote: '\u201D',
9871	CloseCurlyQuote: '\u2019',
9872	clubs: '\u2663',
9873	clubsuit: '\u2663',
9874	Colon: '\u2237',
9875	colon: '\u003A',
9876	Colone: '\u2A74',
9877	colone: '\u2254',
9878	coloneq: '\u2254',
9879	comma: '\u002C',
9880	commat: '\u0040',
9881	comp: '\u2201',
9882	compfn: '\u2218',
9883	complement: '\u2201',
9884	complexes: '\u2102',
9885	cong: '\u2245',
9886	congdot: '\u2A6D',
9887	Congruent: '\u2261',
9888	Conint: '\u222F',
9889	conint: '\u222E',
9890	ContourIntegral: '\u222E',
9891	Copf: '\u2102',
9892	copf: '\uD835\uDD54',
9893	coprod: '\u2210',
9894	Coproduct: '\u2210',
9895	COPY: '\u00A9',
9896	copy: '\u00A9',
9897	copysr: '\u2117',
9898	CounterClockwiseContourIntegral: '\u2233',
9899	crarr: '\u21B5',
9900	Cross: '\u2A2F',
9901	cross: '\u2717',
9902	Cscr: '\uD835\uDC9E',
9903	cscr: '\uD835\uDCB8',
9904	csub: '\u2ACF',
9905	csube: '\u2AD1',
9906	csup: '\u2AD0',
9907	csupe: '\u2AD2',
9908	ctdot: '\u22EF',
9909	cudarrl: '\u2938',
9910	cudarrr: '\u2935',
9911	cuepr: '\u22DE',
9912	cuesc: '\u22DF',
9913	cularr: '\u21B6',
9914	cularrp: '\u293D',
9915	Cup: '\u22D3',
9916	cup: '\u222A',
9917	cupbrcap: '\u2A48',
9918	CupCap: '\u224D',
9919	cupcap: '\u2A46',
9920	cupcup: '\u2A4A',
9921	cupdot: '\u228D',
9922	cupor: '\u2A45',
9923	cups: '\u222A\uFE00',
9924	curarr: '\u21B7',
9925	curarrm: '\u293C',
9926	curlyeqprec: '\u22DE',
9927	curlyeqsucc: '\u22DF',
9928	curlyvee: '\u22CE',
9929	curlywedge: '\u22CF',
9930	curren: '\u00A4',
9931	curvearrowleft: '\u21B6',
9932	curvearrowright: '\u21B7',
9933	cuvee: '\u22CE',
9934	cuwed: '\u22CF',
9935	cwconint: '\u2232',
9936	cwint: '\u2231',
9937	cylcty: '\u232D',
9938	Dagger: '\u2021',
9939	dagger: '\u2020',
9940	daleth: '\u2138',
9941	Darr: '\u21A1',
9942	dArr: '\u21D3',
9943	darr: '\u2193',
9944	dash: '\u2010',
9945	Dashv: '\u2AE4',
9946	dashv: '\u22A3',
9947	dbkarow: '\u290F',
9948	dblac: '\u02DD',
9949	Dcaron: '\u010E',
9950	dcaron: '\u010F',
9951	Dcy: '\u0414',
9952	dcy: '\u0434',
9953	DD: '\u2145',
9954	dd: '\u2146',
9955	ddagger: '\u2021',
9956	ddarr: '\u21CA',
9957	DDotrahd: '\u2911',
9958	ddotseq: '\u2A77',
9959	deg: '\u00B0',
9960	Del: '\u2207',
9961	Delta: '\u0394',
9962	delta: '\u03B4',
9963	demptyv: '\u29B1',
9964	dfisht: '\u297F',
9965	Dfr: '\uD835\uDD07',
9966	dfr: '\uD835\uDD21',
9967	dHar: '\u2965',
9968	dharl: '\u21C3',
9969	dharr: '\u21C2',
9970	DiacriticalAcute: '\u00B4',
9971	DiacriticalDot: '\u02D9',
9972	DiacriticalDoubleAcute: '\u02DD',
9973	DiacriticalGrave: '\u0060',
9974	DiacriticalTilde: '\u02DC',
9975	diam: '\u22C4',
9976	Diamond: '\u22C4',
9977	diamond: '\u22C4',
9978	diamondsuit: '\u2666',
9979	diams: '\u2666',
9980	die: '\u00A8',
9981	DifferentialD: '\u2146',
9982	digamma: '\u03DD',
9983	disin: '\u22F2',
9984	div: '\u00F7',
9985	divide: '\u00F7',
9986	divideontimes: '\u22C7',
9987	divonx: '\u22C7',
9988	DJcy: '\u0402',
9989	djcy: '\u0452',
9990	dlcorn: '\u231E',
9991	dlcrop: '\u230D',
9992	dollar: '\u0024',
9993	Dopf: '\uD835\uDD3B',
9994	dopf: '\uD835\uDD55',
9995	Dot: '\u00A8',
9996	dot: '\u02D9',
9997	DotDot: '\u20DC',
9998	doteq: '\u2250',
9999	doteqdot: '\u2251',
10000	DotEqual: '\u2250',
10001	dotminus: '\u2238',
10002	dotplus: '\u2214',
10003	dotsquare: '\u22A1',
10004	doublebarwedge: '\u2306',
10005	DoubleContourIntegral: '\u222F',
10006	DoubleDot: '\u00A8',
10007	DoubleDownArrow: '\u21D3',
10008	DoubleLeftArrow: '\u21D0',
10009	DoubleLeftRightArrow: '\u21D4',
10010	DoubleLeftTee: '\u2AE4',
10011	DoubleLongLeftArrow: '\u27F8',
10012	DoubleLongLeftRightArrow: '\u27FA',
10013	DoubleLongRightArrow: '\u27F9',
10014	DoubleRightArrow: '\u21D2',
10015	DoubleRightTee: '\u22A8',
10016	DoubleUpArrow: '\u21D1',
10017	DoubleUpDownArrow: '\u21D5',
10018	DoubleVerticalBar: '\u2225',
10019	DownArrow: '\u2193',
10020	Downarrow: '\u21D3',
10021	downarrow: '\u2193',
10022	DownArrowBar: '\u2913',
10023	DownArrowUpArrow: '\u21F5',
10024	DownBreve: '\u0311',
10025	downdownarrows: '\u21CA',
10026	downharpoonleft: '\u21C3',
10027	downharpoonright: '\u21C2',
10028	DownLeftRightVector: '\u2950',
10029	DownLeftTeeVector: '\u295E',
10030	DownLeftVector: '\u21BD',
10031	DownLeftVectorBar: '\u2956',
10032	DownRightTeeVector: '\u295F',
10033	DownRightVector: '\u21C1',
10034	DownRightVectorBar: '\u2957',
10035	DownTee: '\u22A4',
10036	DownTeeArrow: '\u21A7',
10037	drbkarow: '\u2910',
10038	drcorn: '\u231F',
10039	drcrop: '\u230C',
10040	Dscr: '\uD835\uDC9F',
10041	dscr: '\uD835\uDCB9',
10042	DScy: '\u0405',
10043	dscy: '\u0455',
10044	dsol: '\u29F6',
10045	Dstrok: '\u0110',
10046	dstrok: '\u0111',
10047	dtdot: '\u22F1',
10048	dtri: '\u25BF',
10049	dtrif: '\u25BE',
10050	duarr: '\u21F5',
10051	duhar: '\u296F',
10052	dwangle: '\u29A6',
10053	DZcy: '\u040F',
10054	dzcy: '\u045F',
10055	dzigrarr: '\u27FF',
10056	Eacute: '\u00C9',
10057	eacute: '\u00E9',
10058	easter: '\u2A6E',
10059	Ecaron: '\u011A',
10060	ecaron: '\u011B',
10061	ecir: '\u2256',
10062	Ecirc: '\u00CA',
10063	ecirc: '\u00EA',
10064	ecolon: '\u2255',
10065	Ecy: '\u042D',
10066	ecy: '\u044D',
10067	eDDot: '\u2A77',
10068	Edot: '\u0116',
10069	eDot: '\u2251',
10070	edot: '\u0117',
10071	ee: '\u2147',
10072	efDot: '\u2252',
10073	Efr: '\uD835\uDD08',
10074	efr: '\uD835\uDD22',
10075	eg: '\u2A9A',
10076	Egrave: '\u00C8',
10077	egrave: '\u00E8',
10078	egs: '\u2A96',
10079	egsdot: '\u2A98',
10080	el: '\u2A99',
10081	Element: '\u2208',
10082	elinters: '\u23E7',
10083	ell: '\u2113',
10084	els: '\u2A95',
10085	elsdot: '\u2A97',
10086	Emacr: '\u0112',
10087	emacr: '\u0113',
10088	empty: '\u2205',
10089	emptyset: '\u2205',
10090	EmptySmallSquare: '\u25FB',
10091	emptyv: '\u2205',
10092	EmptyVerySmallSquare: '\u25AB',
10093	emsp: '\u2003',
10094	emsp13: '\u2004',
10095	emsp14: '\u2005',
10096	ENG: '\u014A',
10097	eng: '\u014B',
10098	ensp: '\u2002',
10099	Eogon: '\u0118',
10100	eogon: '\u0119',
10101	Eopf: '\uD835\uDD3C',
10102	eopf: '\uD835\uDD56',
10103	epar: '\u22D5',
10104	eparsl: '\u29E3',
10105	eplus: '\u2A71',
10106	epsi: '\u03B5',
10107	Epsilon: '\u0395',
10108	epsilon: '\u03B5',
10109	epsiv: '\u03F5',
10110	eqcirc: '\u2256',
10111	eqcolon: '\u2255',
10112	eqsim: '\u2242',
10113	eqslantgtr: '\u2A96',
10114	eqslantless: '\u2A95',
10115	Equal: '\u2A75',
10116	equals: '\u003D',
10117	EqualTilde: '\u2242',
10118	equest: '\u225F',
10119	Equilibrium: '\u21CC',
10120	equiv: '\u2261',
10121	equivDD: '\u2A78',
10122	eqvparsl: '\u29E5',
10123	erarr: '\u2971',
10124	erDot: '\u2253',
10125	Escr: '\u2130',
10126	escr: '\u212F',
10127	esdot: '\u2250',
10128	Esim: '\u2A73',
10129	esim: '\u2242',
10130	Eta: '\u0397',
10131	eta: '\u03B7',
10132	ETH: '\u00D0',
10133	eth: '\u00F0',
10134	Euml: '\u00CB',
10135	euml: '\u00EB',
10136	euro: '\u20AC',
10137	excl: '\u0021',
10138	exist: '\u2203',
10139	Exists: '\u2203',
10140	expectation: '\u2130',
10141	ExponentialE: '\u2147',
10142	exponentiale: '\u2147',
10143	fallingdotseq: '\u2252',
10144	Fcy: '\u0424',
10145	fcy: '\u0444',
10146	female: '\u2640',
10147	ffilig: '\uFB03',
10148	fflig: '\uFB00',
10149	ffllig: '\uFB04',
10150	Ffr: '\uD835\uDD09',
10151	ffr: '\uD835\uDD23',
10152	filig: '\uFB01',
10153	FilledSmallSquare: '\u25FC',
10154	FilledVerySmallSquare: '\u25AA',
10155	fjlig: '\u0066\u006A',
10156	flat: '\u266D',
10157	fllig: '\uFB02',
10158	fltns: '\u25B1',
10159	fnof: '\u0192',
10160	Fopf: '\uD835\uDD3D',
10161	fopf: '\uD835\uDD57',
10162	ForAll: '\u2200',
10163	forall: '\u2200',
10164	fork: '\u22D4',
10165	forkv: '\u2AD9',
10166	Fouriertrf: '\u2131',
10167	fpartint: '\u2A0D',
10168	frac12: '\u00BD',
10169	frac13: '\u2153',
10170	frac14: '\u00BC',
10171	frac15: '\u2155',
10172	frac16: '\u2159',
10173	frac18: '\u215B',
10174	frac23: '\u2154',
10175	frac25: '\u2156',
10176	frac34: '\u00BE',
10177	frac35: '\u2157',
10178	frac38: '\u215C',
10179	frac45: '\u2158',
10180	frac56: '\u215A',
10181	frac58: '\u215D',
10182	frac78: '\u215E',
10183	frasl: '\u2044',
10184	frown: '\u2322',
10185	Fscr: '\u2131',
10186	fscr: '\uD835\uDCBB',
10187	gacute: '\u01F5',
10188	Gamma: '\u0393',
10189	gamma: '\u03B3',
10190	Gammad: '\u03DC',
10191	gammad: '\u03DD',
10192	gap: '\u2A86',
10193	Gbreve: '\u011E',
10194	gbreve: '\u011F',
10195	Gcedil: '\u0122',
10196	Gcirc: '\u011C',
10197	gcirc: '\u011D',
10198	Gcy: '\u0413',
10199	gcy: '\u0433',
10200	Gdot: '\u0120',
10201	gdot: '\u0121',
10202	gE: '\u2267',
10203	ge: '\u2265',
10204	gEl: '\u2A8C',
10205	gel: '\u22DB',
10206	geq: '\u2265',
10207	geqq: '\u2267',
10208	geqslant: '\u2A7E',
10209	ges: '\u2A7E',
10210	gescc: '\u2AA9',
10211	gesdot: '\u2A80',
10212	gesdoto: '\u2A82',
10213	gesdotol: '\u2A84',
10214	gesl: '\u22DB\uFE00',
10215	gesles: '\u2A94',
10216	Gfr: '\uD835\uDD0A',
10217	gfr: '\uD835\uDD24',
10218	Gg: '\u22D9',
10219	gg: '\u226B',
10220	ggg: '\u22D9',
10221	gimel: '\u2137',
10222	GJcy: '\u0403',
10223	gjcy: '\u0453',
10224	gl: '\u2277',
10225	gla: '\u2AA5',
10226	glE: '\u2A92',
10227	glj: '\u2AA4',
10228	gnap: '\u2A8A',
10229	gnapprox: '\u2A8A',
10230	gnE: '\u2269',
10231	gne: '\u2A88',
10232	gneq: '\u2A88',
10233	gneqq: '\u2269',
10234	gnsim: '\u22E7',
10235	Gopf: '\uD835\uDD3E',
10236	gopf: '\uD835\uDD58',
10237	grave: '\u0060',
10238	GreaterEqual: '\u2265',
10239	GreaterEqualLess: '\u22DB',
10240	GreaterFullEqual: '\u2267',
10241	GreaterGreater: '\u2AA2',
10242	GreaterLess: '\u2277',
10243	GreaterSlantEqual: '\u2A7E',
10244	GreaterTilde: '\u2273',
10245	Gscr: '\uD835\uDCA2',
10246	gscr: '\u210A',
10247	gsim: '\u2273',
10248	gsime: '\u2A8E',
10249	gsiml: '\u2A90',
10250	Gt: '\u226B',
10251	GT: '\u003E',
10252	gt: '\u003E',
10253	gtcc: '\u2AA7',
10254	gtcir: '\u2A7A',
10255	gtdot: '\u22D7',
10256	gtlPar: '\u2995',
10257	gtquest: '\u2A7C',
10258	gtrapprox: '\u2A86',
10259	gtrarr: '\u2978',
10260	gtrdot: '\u22D7',
10261	gtreqless: '\u22DB',
10262	gtreqqless: '\u2A8C',
10263	gtrless: '\u2277',
10264	gtrsim: '\u2273',
10265	gvertneqq: '\u2269\uFE00',
10266	gvnE: '\u2269\uFE00',
10267	Hacek: '\u02C7',
10268	hairsp: '\u200A',
10269	half: '\u00BD',
10270	hamilt: '\u210B',
10271	HARDcy: '\u042A',
10272	hardcy: '\u044A',
10273	hArr: '\u21D4',
10274	harr: '\u2194',
10275	harrcir: '\u2948',
10276	harrw: '\u21AD',
10277	Hat: '\u005E',
10278	hbar: '\u210F',
10279	Hcirc: '\u0124',
10280	hcirc: '\u0125',
10281	hearts: '\u2665',
10282	heartsuit: '\u2665',
10283	hellip: '\u2026',
10284	hercon: '\u22B9',
10285	Hfr: '\u210C',
10286	hfr: '\uD835\uDD25',
10287	HilbertSpace: '\u210B',
10288	hksearow: '\u2925',
10289	hkswarow: '\u2926',
10290	hoarr: '\u21FF',
10291	homtht: '\u223B',
10292	hookleftarrow: '\u21A9',
10293	hookrightarrow: '\u21AA',
10294	Hopf: '\u210D',
10295	hopf: '\uD835\uDD59',
10296	horbar: '\u2015',
10297	HorizontalLine: '\u2500',
10298	Hscr: '\u210B',
10299	hscr: '\uD835\uDCBD',
10300	hslash: '\u210F',
10301	Hstrok: '\u0126',
10302	hstrok: '\u0127',
10303	HumpDownHump: '\u224E',
10304	HumpEqual: '\u224F',
10305	hybull: '\u2043',
10306	hyphen: '\u2010',
10307	Iacute: '\u00CD',
10308	iacute: '\u00ED',
10309	ic: '\u2063',
10310	Icirc: '\u00CE',
10311	icirc: '\u00EE',
10312	Icy: '\u0418',
10313	icy: '\u0438',
10314	Idot: '\u0130',
10315	IEcy: '\u0415',
10316	iecy: '\u0435',
10317	iexcl: '\u00A1',
10318	iff: '\u21D4',
10319	Ifr: '\u2111',
10320	ifr: '\uD835\uDD26',
10321	Igrave: '\u00CC',
10322	igrave: '\u00EC',
10323	ii: '\u2148',
10324	iiiint: '\u2A0C',
10325	iiint: '\u222D',
10326	iinfin: '\u29DC',
10327	iiota: '\u2129',
10328	IJlig: '\u0132',
10329	ijlig: '\u0133',
10330	Im: '\u2111',
10331	Imacr: '\u012A',
10332	imacr: '\u012B',
10333	image: '\u2111',
10334	ImaginaryI: '\u2148',
10335	imagline: '\u2110',
10336	imagpart: '\u2111',
10337	imath: '\u0131',
10338	imof: '\u22B7',
10339	imped: '\u01B5',
10340	Implies: '\u21D2',
10341	in: '\u2208',
10342	incare: '\u2105',
10343	infin: '\u221E',
10344	infintie: '\u29DD',
10345	inodot: '\u0131',
10346	Int: '\u222C',
10347	int: '\u222B',
10348	intcal: '\u22BA',
10349	integers: '\u2124',
10350	Integral: '\u222B',
10351	intercal: '\u22BA',
10352	Intersection: '\u22C2',
10353	intlarhk: '\u2A17',
10354	intprod: '\u2A3C',
10355	InvisibleComma: '\u2063',
10356	InvisibleTimes: '\u2062',
10357	IOcy: '\u0401',
10358	iocy: '\u0451',
10359	Iogon: '\u012E',
10360	iogon: '\u012F',
10361	Iopf: '\uD835\uDD40',
10362	iopf: '\uD835\uDD5A',
10363	Iota: '\u0399',
10364	iota: '\u03B9',
10365	iprod: '\u2A3C',
10366	iquest: '\u00BF',
10367	Iscr: '\u2110',
10368	iscr: '\uD835\uDCBE',
10369	isin: '\u2208',
10370	isindot: '\u22F5',
10371	isinE: '\u22F9',
10372	isins: '\u22F4',
10373	isinsv: '\u22F3',
10374	isinv: '\u2208',
10375	it: '\u2062',
10376	Itilde: '\u0128',
10377	itilde: '\u0129',
10378	Iukcy: '\u0406',
10379	iukcy: '\u0456',
10380	Iuml: '\u00CF',
10381	iuml: '\u00EF',
10382	Jcirc: '\u0134',
10383	jcirc: '\u0135',
10384	Jcy: '\u0419',
10385	jcy: '\u0439',
10386	Jfr: '\uD835\uDD0D',
10387	jfr: '\uD835\uDD27',
10388	jmath: '\u0237',
10389	Jopf: '\uD835\uDD41',
10390	jopf: '\uD835\uDD5B',
10391	Jscr: '\uD835\uDCA5',
10392	jscr: '\uD835\uDCBF',
10393	Jsercy: '\u0408',
10394	jsercy: '\u0458',
10395	Jukcy: '\u0404',
10396	jukcy: '\u0454',
10397	Kappa: '\u039A',
10398	kappa: '\u03BA',
10399	kappav: '\u03F0',
10400	Kcedil: '\u0136',
10401	kcedil: '\u0137',
10402	Kcy: '\u041A',
10403	kcy: '\u043A',
10404	Kfr: '\uD835\uDD0E',
10405	kfr: '\uD835\uDD28',
10406	kgreen: '\u0138',
10407	KHcy: '\u0425',
10408	khcy: '\u0445',
10409	KJcy: '\u040C',
10410	kjcy: '\u045C',
10411	Kopf: '\uD835\uDD42',
10412	kopf: '\uD835\uDD5C',
10413	Kscr: '\uD835\uDCA6',
10414	kscr: '\uD835\uDCC0',
10415	lAarr: '\u21DA',
10416	Lacute: '\u0139',
10417	lacute: '\u013A',
10418	laemptyv: '\u29B4',
10419	lagran: '\u2112',
10420	Lambda: '\u039B',
10421	lambda: '\u03BB',
10422	Lang: '\u27EA',
10423	lang: '\u27E8',
10424	langd: '\u2991',
10425	langle: '\u27E8',
10426	lap: '\u2A85',
10427	Laplacetrf: '\u2112',
10428	laquo: '\u00AB',
10429	Larr: '\u219E',
10430	lArr: '\u21D0',
10431	larr: '\u2190',
10432	larrb: '\u21E4',
10433	larrbfs: '\u291F',
10434	larrfs: '\u291D',
10435	larrhk: '\u21A9',
10436	larrlp: '\u21AB',
10437	larrpl: '\u2939',
10438	larrsim: '\u2973',
10439	larrtl: '\u21A2',
10440	lat: '\u2AAB',
10441	lAtail: '\u291B',
10442	latail: '\u2919',
10443	late: '\u2AAD',
10444	lates: '\u2AAD\uFE00',
10445	lBarr: '\u290E',
10446	lbarr: '\u290C',
10447	lbbrk: '\u2772',
10448	lbrace: '\u007B',
10449	lbrack: '\u005B',
10450	lbrke: '\u298B',
10451	lbrksld: '\u298F',
10452	lbrkslu: '\u298D',
10453	Lcaron: '\u013D',
10454	lcaron: '\u013E',
10455	Lcedil: '\u013B',
10456	lcedil: '\u013C',
10457	lceil: '\u2308',
10458	lcub: '\u007B',
10459	Lcy: '\u041B',
10460	lcy: '\u043B',
10461	ldca: '\u2936',
10462	ldquo: '\u201C',
10463	ldquor: '\u201E',
10464	ldrdhar: '\u2967',
10465	ldrushar: '\u294B',
10466	ldsh: '\u21B2',
10467	lE: '\u2266',
10468	le: '\u2264',
10469	LeftAngleBracket: '\u27E8',
10470	LeftArrow: '\u2190',
10471	Leftarrow: '\u21D0',
10472	leftarrow: '\u2190',
10473	LeftArrowBar: '\u21E4',
10474	LeftArrowRightArrow: '\u21C6',
10475	leftarrowtail: '\u21A2',
10476	LeftCeiling: '\u2308',
10477	LeftDoubleBracket: '\u27E6',
10478	LeftDownTeeVector: '\u2961',
10479	LeftDownVector: '\u21C3',
10480	LeftDownVectorBar: '\u2959',
10481	LeftFloor: '\u230A',
10482	leftharpoondown: '\u21BD',
10483	leftharpoonup: '\u21BC',
10484	leftleftarrows: '\u21C7',
10485	LeftRightArrow: '\u2194',
10486	Leftrightarrow: '\u21D4',
10487	leftrightarrow: '\u2194',
10488	leftrightarrows: '\u21C6',
10489	leftrightharpoons: '\u21CB',
10490	leftrightsquigarrow: '\u21AD',
10491	LeftRightVector: '\u294E',
10492	LeftTee: '\u22A3',
10493	LeftTeeArrow: '\u21A4',
10494	LeftTeeVector: '\u295A',
10495	leftthreetimes: '\u22CB',
10496	LeftTriangle: '\u22B2',
10497	LeftTriangleBar: '\u29CF',
10498	LeftTriangleEqual: '\u22B4',
10499	LeftUpDownVector: '\u2951',
10500	LeftUpTeeVector: '\u2960',
10501	LeftUpVector: '\u21BF',
10502	LeftUpVectorBar: '\u2958',
10503	LeftVector: '\u21BC',
10504	LeftVectorBar: '\u2952',
10505	lEg: '\u2A8B',
10506	leg: '\u22DA',
10507	leq: '\u2264',
10508	leqq: '\u2266',
10509	leqslant: '\u2A7D',
10510	les: '\u2A7D',
10511	lescc: '\u2AA8',
10512	lesdot: '\u2A7F',
10513	lesdoto: '\u2A81',
10514	lesdotor: '\u2A83',
10515	lesg: '\u22DA\uFE00',
10516	lesges: '\u2A93',
10517	lessapprox: '\u2A85',
10518	lessdot: '\u22D6',
10519	lesseqgtr: '\u22DA',
10520	lesseqqgtr: '\u2A8B',
10521	LessEqualGreater: '\u22DA',
10522	LessFullEqual: '\u2266',
10523	LessGreater: '\u2276',
10524	lessgtr: '\u2276',
10525	LessLess: '\u2AA1',
10526	lesssim: '\u2272',
10527	LessSlantEqual: '\u2A7D',
10528	LessTilde: '\u2272',
10529	lfisht: '\u297C',
10530	lfloor: '\u230A',
10531	Lfr: '\uD835\uDD0F',
10532	lfr: '\uD835\uDD29',
10533	lg: '\u2276',
10534	lgE: '\u2A91',
10535	lHar: '\u2962',
10536	lhard: '\u21BD',
10537	lharu: '\u21BC',
10538	lharul: '\u296A',
10539	lhblk: '\u2584',
10540	LJcy: '\u0409',
10541	ljcy: '\u0459',
10542	Ll: '\u22D8',
10543	ll: '\u226A',
10544	llarr: '\u21C7',
10545	llcorner: '\u231E',
10546	Lleftarrow: '\u21DA',
10547	llhard: '\u296B',
10548	lltri: '\u25FA',
10549	Lmidot: '\u013F',
10550	lmidot: '\u0140',
10551	lmoust: '\u23B0',
10552	lmoustache: '\u23B0',
10553	lnap: '\u2A89',
10554	lnapprox: '\u2A89',
10555	lnE: '\u2268',
10556	lne: '\u2A87',
10557	lneq: '\u2A87',
10558	lneqq: '\u2268',
10559	lnsim: '\u22E6',
10560	loang: '\u27EC',
10561	loarr: '\u21FD',
10562	lobrk: '\u27E6',
10563	LongLeftArrow: '\u27F5',
10564	Longleftarrow: '\u27F8',
10565	longleftarrow: '\u27F5',
10566	LongLeftRightArrow: '\u27F7',
10567	Longleftrightarrow: '\u27FA',
10568	longleftrightarrow: '\u27F7',
10569	longmapsto: '\u27FC',
10570	LongRightArrow: '\u27F6',
10571	Longrightarrow: '\u27F9',
10572	longrightarrow: '\u27F6',
10573	looparrowleft: '\u21AB',
10574	looparrowright: '\u21AC',
10575	lopar: '\u2985',
10576	Lopf: '\uD835\uDD43',
10577	lopf: '\uD835\uDD5D',
10578	loplus: '\u2A2D',
10579	lotimes: '\u2A34',
10580	lowast: '\u2217',
10581	lowbar: '\u005F',
10582	LowerLeftArrow: '\u2199',
10583	LowerRightArrow: '\u2198',
10584	loz: '\u25CA',
10585	lozenge: '\u25CA',
10586	lozf: '\u29EB',
10587	lpar: '\u0028',
10588	lparlt: '\u2993',
10589	lrarr: '\u21C6',
10590	lrcorner: '\u231F',
10591	lrhar: '\u21CB',
10592	lrhard: '\u296D',
10593	lrm: '\u200E',
10594	lrtri: '\u22BF',
10595	lsaquo: '\u2039',
10596	Lscr: '\u2112',
10597	lscr: '\uD835\uDCC1',
10598	Lsh: '\u21B0',
10599	lsh: '\u21B0',
10600	lsim: '\u2272',
10601	lsime: '\u2A8D',
10602	lsimg: '\u2A8F',
10603	lsqb: '\u005B',
10604	lsquo: '\u2018',
10605	lsquor: '\u201A',
10606	Lstrok: '\u0141',
10607	lstrok: '\u0142',
10608	Lt: '\u226A',
10609	LT: '\u003C',
10610	lt: '\u003C',
10611	ltcc: '\u2AA6',
10612	ltcir: '\u2A79',
10613	ltdot: '\u22D6',
10614	lthree: '\u22CB',
10615	ltimes: '\u22C9',
10616	ltlarr: '\u2976',
10617	ltquest: '\u2A7B',
10618	ltri: '\u25C3',
10619	ltrie: '\u22B4',
10620	ltrif: '\u25C2',
10621	ltrPar: '\u2996',
10622	lurdshar: '\u294A',
10623	luruhar: '\u2966',
10624	lvertneqq: '\u2268\uFE00',
10625	lvnE: '\u2268\uFE00',
10626	macr: '\u00AF',
10627	male: '\u2642',
10628	malt: '\u2720',
10629	maltese: '\u2720',
10630	Map: '\u2905',
10631	map: '\u21A6',
10632	mapsto: '\u21A6',
10633	mapstodown: '\u21A7',
10634	mapstoleft: '\u21A4',
10635	mapstoup: '\u21A5',
10636	marker: '\u25AE',
10637	mcomma: '\u2A29',
10638	Mcy: '\u041C',
10639	mcy: '\u043C',
10640	mdash: '\u2014',
10641	mDDot: '\u223A',
10642	measuredangle: '\u2221',
10643	MediumSpace: '\u205F',
10644	Mellintrf: '\u2133',
10645	Mfr: '\uD835\uDD10',
10646	mfr: '\uD835\uDD2A',
10647	mho: '\u2127',
10648	micro: '\u00B5',
10649	mid: '\u2223',
10650	midast: '\u002A',
10651	midcir: '\u2AF0',
10652	middot: '\u00B7',
10653	minus: '\u2212',
10654	minusb: '\u229F',
10655	minusd: '\u2238',
10656	minusdu: '\u2A2A',
10657	MinusPlus: '\u2213',
10658	mlcp: '\u2ADB',
10659	mldr: '\u2026',
10660	mnplus: '\u2213',
10661	models: '\u22A7',
10662	Mopf: '\uD835\uDD44',
10663	mopf: '\uD835\uDD5E',
10664	mp: '\u2213',
10665	Mscr: '\u2133',
10666	mscr: '\uD835\uDCC2',
10667	mstpos: '\u223E',
10668	Mu: '\u039C',
10669	mu: '\u03BC',
10670	multimap: '\u22B8',
10671	mumap: '\u22B8',
10672	nabla: '\u2207',
10673	Nacute: '\u0143',
10674	nacute: '\u0144',
10675	nang: '\u2220\u20D2',
10676	nap: '\u2249',
10677	napE: '\u2A70\u0338',
10678	napid: '\u224B\u0338',
10679	napos: '\u0149',
10680	napprox: '\u2249',
10681	natur: '\u266E',
10682	natural: '\u266E',
10683	naturals: '\u2115',
10684	nbsp: '\u00A0',
10685	nbump: '\u224E\u0338',
10686	nbumpe: '\u224F\u0338',
10687	ncap: '\u2A43',
10688	Ncaron: '\u0147',
10689	ncaron: '\u0148',
10690	Ncedil: '\u0145',
10691	ncedil: '\u0146',
10692	ncong: '\u2247',
10693	ncongdot: '\u2A6D\u0338',
10694	ncup: '\u2A42',
10695	Ncy: '\u041D',
10696	ncy: '\u043D',
10697	ndash: '\u2013',
10698	ne: '\u2260',
10699	nearhk: '\u2924',
10700	neArr: '\u21D7',
10701	nearr: '\u2197',
10702	nearrow: '\u2197',
10703	nedot: '\u2250\u0338',
10704	NegativeMediumSpace: '\u200B',
10705	NegativeThickSpace: '\u200B',
10706	NegativeThinSpace: '\u200B',
10707	NegativeVeryThinSpace: '\u200B',
10708	nequiv: '\u2262',
10709	nesear: '\u2928',
10710	nesim: '\u2242\u0338',
10711	NestedGreaterGreater: '\u226B',
10712	NestedLessLess: '\u226A',
10713	NewLine: '\u000A',
10714	nexist: '\u2204',
10715	nexists: '\u2204',
10716	Nfr: '\uD835\uDD11',
10717	nfr: '\uD835\uDD2B',
10718	ngE: '\u2267\u0338',
10719	nge: '\u2271',
10720	ngeq: '\u2271',
10721	ngeqq: '\u2267\u0338',
10722	ngeqslant: '\u2A7E\u0338',
10723	nges: '\u2A7E\u0338',
10724	nGg: '\u22D9\u0338',
10725	ngsim: '\u2275',
10726	nGt: '\u226B\u20D2',
10727	ngt: '\u226F',
10728	ngtr: '\u226F',
10729	nGtv: '\u226B\u0338',
10730	nhArr: '\u21CE',
10731	nharr: '\u21AE',
10732	nhpar: '\u2AF2',
10733	ni: '\u220B',
10734	nis: '\u22FC',
10735	nisd: '\u22FA',
10736	niv: '\u220B',
10737	NJcy: '\u040A',
10738	njcy: '\u045A',
10739	nlArr: '\u21CD',
10740	nlarr: '\u219A',
10741	nldr: '\u2025',
10742	nlE: '\u2266\u0338',
10743	nle: '\u2270',
10744	nLeftarrow: '\u21CD',
10745	nleftarrow: '\u219A',
10746	nLeftrightarrow: '\u21CE',
10747	nleftrightarrow: '\u21AE',
10748	nleq: '\u2270',
10749	nleqq: '\u2266\u0338',
10750	nleqslant: '\u2A7D\u0338',
10751	nles: '\u2A7D\u0338',
10752	nless: '\u226E',
10753	nLl: '\u22D8\u0338',
10754	nlsim: '\u2274',
10755	nLt: '\u226A\u20D2',
10756	nlt: '\u226E',
10757	nltri: '\u22EA',
10758	nltrie: '\u22EC',
10759	nLtv: '\u226A\u0338',
10760	nmid: '\u2224',
10761	NoBreak: '\u2060',
10762	NonBreakingSpace: '\u00A0',
10763	Nopf: '\u2115',
10764	nopf: '\uD835\uDD5F',
10765	Not: '\u2AEC',
10766	not: '\u00AC',
10767	NotCongruent: '\u2262',
10768	NotCupCap: '\u226D',
10769	NotDoubleVerticalBar: '\u2226',
10770	NotElement: '\u2209',
10771	NotEqual: '\u2260',
10772	NotEqualTilde: '\u2242\u0338',
10773	NotExists: '\u2204',
10774	NotGreater: '\u226F',
10775	NotGreaterEqual: '\u2271',
10776	NotGreaterFullEqual: '\u2267\u0338',
10777	NotGreaterGreater: '\u226B\u0338',
10778	NotGreaterLess: '\u2279',
10779	NotGreaterSlantEqual: '\u2A7E\u0338',
10780	NotGreaterTilde: '\u2275',
10781	NotHumpDownHump: '\u224E\u0338',
10782	NotHumpEqual: '\u224F\u0338',
10783	notin: '\u2209',
10784	notindot: '\u22F5\u0338',
10785	notinE: '\u22F9\u0338',
10786	notinva: '\u2209',
10787	notinvb: '\u22F7',
10788	notinvc: '\u22F6',
10789	NotLeftTriangle: '\u22EA',
10790	NotLeftTriangleBar: '\u29CF\u0338',
10791	NotLeftTriangleEqual: '\u22EC',
10792	NotLess: '\u226E',
10793	NotLessEqual: '\u2270',
10794	NotLessGreater: '\u2278',
10795	NotLessLess: '\u226A\u0338',
10796	NotLessSlantEqual: '\u2A7D\u0338',
10797	NotLessTilde: '\u2274',
10798	NotNestedGreaterGreater: '\u2AA2\u0338',
10799	NotNestedLessLess: '\u2AA1\u0338',
10800	notni: '\u220C',
10801	notniva: '\u220C',
10802	notnivb: '\u22FE',
10803	notnivc: '\u22FD',
10804	NotPrecedes: '\u2280',
10805	NotPrecedesEqual: '\u2AAF\u0338',
10806	NotPrecedesSlantEqual: '\u22E0',
10807	NotReverseElement: '\u220C',
10808	NotRightTriangle: '\u22EB',
10809	NotRightTriangleBar: '\u29D0\u0338',
10810	NotRightTriangleEqual: '\u22ED',
10811	NotSquareSubset: '\u228F\u0338',
10812	NotSquareSubsetEqual: '\u22E2',
10813	NotSquareSuperset: '\u2290\u0338',
10814	NotSquareSupersetEqual: '\u22E3',
10815	NotSubset: '\u2282\u20D2',
10816	NotSubsetEqual: '\u2288',
10817	NotSucceeds: '\u2281',
10818	NotSucceedsEqual: '\u2AB0\u0338',
10819	NotSucceedsSlantEqual: '\u22E1',
10820	NotSucceedsTilde: '\u227F\u0338',
10821	NotSuperset: '\u2283\u20D2',
10822	NotSupersetEqual: '\u2289',
10823	NotTilde: '\u2241',
10824	NotTildeEqual: '\u2244',
10825	NotTildeFullEqual: '\u2247',
10826	NotTildeTilde: '\u2249',
10827	NotVerticalBar: '\u2224',
10828	npar: '\u2226',
10829	nparallel: '\u2226',
10830	nparsl: '\u2AFD\u20E5',
10831	npart: '\u2202\u0338',
10832	npolint: '\u2A14',
10833	npr: '\u2280',
10834	nprcue: '\u22E0',
10835	npre: '\u2AAF\u0338',
10836	nprec: '\u2280',
10837	npreceq: '\u2AAF\u0338',
10838	nrArr: '\u21CF',
10839	nrarr: '\u219B',
10840	nrarrc: '\u2933\u0338',
10841	nrarrw: '\u219D\u0338',
10842	nRightarrow: '\u21CF',
10843	nrightarrow: '\u219B',
10844	nrtri: '\u22EB',
10845	nrtrie: '\u22ED',
10846	nsc: '\u2281',
10847	nsccue: '\u22E1',
10848	nsce: '\u2AB0\u0338',
10849	Nscr: '\uD835\uDCA9',
10850	nscr: '\uD835\uDCC3',
10851	nshortmid: '\u2224',
10852	nshortparallel: '\u2226',
10853	nsim: '\u2241',
10854	nsime: '\u2244',
10855	nsimeq: '\u2244',
10856	nsmid: '\u2224',
10857	nspar: '\u2226',
10858	nsqsube: '\u22E2',
10859	nsqsupe: '\u22E3',
10860	nsub: '\u2284',
10861	nsubE: '\u2AC5\u0338',
10862	nsube: '\u2288',
10863	nsubset: '\u2282\u20D2',
10864	nsubseteq: '\u2288',
10865	nsubseteqq: '\u2AC5\u0338',
10866	nsucc: '\u2281',
10867	nsucceq: '\u2AB0\u0338',
10868	nsup: '\u2285',
10869	nsupE: '\u2AC6\u0338',
10870	nsupe: '\u2289',
10871	nsupset: '\u2283\u20D2',
10872	nsupseteq: '\u2289',
10873	nsupseteqq: '\u2AC6\u0338',
10874	ntgl: '\u2279',
10875	Ntilde: '\u00D1',
10876	ntilde: '\u00F1',
10877	ntlg: '\u2278',
10878	ntriangleleft: '\u22EA',
10879	ntrianglelefteq: '\u22EC',
10880	ntriangleright: '\u22EB',
10881	ntrianglerighteq: '\u22ED',
10882	Nu: '\u039D',
10883	nu: '\u03BD',
10884	num: '\u0023',
10885	numero: '\u2116',
10886	numsp: '\u2007',
10887	nvap: '\u224D\u20D2',
10888	nVDash: '\u22AF',
10889	nVdash: '\u22AE',
10890	nvDash: '\u22AD',
10891	nvdash: '\u22AC',
10892	nvge: '\u2265\u20D2',
10893	nvgt: '\u003E\u20D2',
10894	nvHarr: '\u2904',
10895	nvinfin: '\u29DE',
10896	nvlArr: '\u2902',
10897	nvle: '\u2264\u20D2',
10898	nvlt: '\u003C\u20D2',
10899	nvltrie: '\u22B4\u20D2',
10900	nvrArr: '\u2903',
10901	nvrtrie: '\u22B5\u20D2',
10902	nvsim: '\u223C\u20D2',
10903	nwarhk: '\u2923',
10904	nwArr: '\u21D6',
10905	nwarr: '\u2196',
10906	nwarrow: '\u2196',
10907	nwnear: '\u2927',
10908	Oacute: '\u00D3',
10909	oacute: '\u00F3',
10910	oast: '\u229B',
10911	ocir: '\u229A',
10912	Ocirc: '\u00D4',
10913	ocirc: '\u00F4',
10914	Ocy: '\u041E',
10915	ocy: '\u043E',
10916	odash: '\u229D',
10917	Odblac: '\u0150',
10918	odblac: '\u0151',
10919	odiv: '\u2A38',
10920	odot: '\u2299',
10921	odsold: '\u29BC',
10922	OElig: '\u0152',
10923	oelig: '\u0153',
10924	ofcir: '\u29BF',
10925	Ofr: '\uD835\uDD12',
10926	ofr: '\uD835\uDD2C',
10927	ogon: '\u02DB',
10928	Ograve: '\u00D2',
10929	ograve: '\u00F2',
10930	ogt: '\u29C1',
10931	ohbar: '\u29B5',
10932	ohm: '\u03A9',
10933	oint: '\u222E',
10934	olarr: '\u21BA',
10935	olcir: '\u29BE',
10936	olcross: '\u29BB',
10937	oline: '\u203E',
10938	olt: '\u29C0',
10939	Omacr: '\u014C',
10940	omacr: '\u014D',
10941	Omega: '\u03A9',
10942	omega: '\u03C9',
10943	Omicron: '\u039F',
10944	omicron: '\u03BF',
10945	omid: '\u29B6',
10946	ominus: '\u2296',
10947	Oopf: '\uD835\uDD46',
10948	oopf: '\uD835\uDD60',
10949	opar: '\u29B7',
10950	OpenCurlyDoubleQuote: '\u201C',
10951	OpenCurlyQuote: '\u2018',
10952	operp: '\u29B9',
10953	oplus: '\u2295',
10954	Or: '\u2A54',
10955	or: '\u2228',
10956	orarr: '\u21BB',
10957	ord: '\u2A5D',
10958	order: '\u2134',
10959	orderof: '\u2134',
10960	ordf: '\u00AA',
10961	ordm: '\u00BA',
10962	origof: '\u22B6',
10963	oror: '\u2A56',
10964	orslope: '\u2A57',
10965	orv: '\u2A5B',
10966	oS: '\u24C8',
10967	Oscr: '\uD835\uDCAA',
10968	oscr: '\u2134',
10969	Oslash: '\u00D8',
10970	oslash: '\u00F8',
10971	osol: '\u2298',
10972	Otilde: '\u00D5',
10973	otilde: '\u00F5',
10974	Otimes: '\u2A37',
10975	otimes: '\u2297',
10976	otimesas: '\u2A36',
10977	Ouml: '\u00D6',
10978	ouml: '\u00F6',
10979	ovbar: '\u233D',
10980	OverBar: '\u203E',
10981	OverBrace: '\u23DE',
10982	OverBracket: '\u23B4',
10983	OverParenthesis: '\u23DC',
10984	par: '\u2225',
10985	para: '\u00B6',
10986	parallel: '\u2225',
10987	parsim: '\u2AF3',
10988	parsl: '\u2AFD',
10989	part: '\u2202',
10990	PartialD: '\u2202',
10991	Pcy: '\u041F',
10992	pcy: '\u043F',
10993	percnt: '\u0025',
10994	period: '\u002E',
10995	permil: '\u2030',
10996	perp: '\u22A5',
10997	pertenk: '\u2031',
10998	Pfr: '\uD835\uDD13',
10999	pfr: '\uD835\uDD2D',
11000	Phi: '\u03A6',
11001	phi: '\u03C6',
11002	phiv: '\u03D5',
11003	phmmat: '\u2133',
11004	phone: '\u260E',
11005	Pi: '\u03A0',
11006	pi: '\u03C0',
11007	pitchfork: '\u22D4',
11008	piv: '\u03D6',
11009	planck: '\u210F',
11010	planckh: '\u210E',
11011	plankv: '\u210F',
11012	plus: '\u002B',
11013	plusacir: '\u2A23',
11014	plusb: '\u229E',
11015	pluscir: '\u2A22',
11016	plusdo: '\u2214',
11017	plusdu: '\u2A25',
11018	pluse: '\u2A72',
11019	PlusMinus: '\u00B1',
11020	plusmn: '\u00B1',
11021	plussim: '\u2A26',
11022	plustwo: '\u2A27',
11023	pm: '\u00B1',
11024	Poincareplane: '\u210C',
11025	pointint: '\u2A15',
11026	Popf: '\u2119',
11027	popf: '\uD835\uDD61',
11028	pound: '\u00A3',
11029	Pr: '\u2ABB',
11030	pr: '\u227A',
11031	prap: '\u2AB7',
11032	prcue: '\u227C',
11033	prE: '\u2AB3',
11034	pre: '\u2AAF',
11035	prec: '\u227A',
11036	precapprox: '\u2AB7',
11037	preccurlyeq: '\u227C',
11038	Precedes: '\u227A',
11039	PrecedesEqual: '\u2AAF',
11040	PrecedesSlantEqual: '\u227C',
11041	PrecedesTilde: '\u227E',
11042	preceq: '\u2AAF',
11043	precnapprox: '\u2AB9',
11044	precneqq: '\u2AB5',
11045	precnsim: '\u22E8',
11046	precsim: '\u227E',
11047	Prime: '\u2033',
11048	prime: '\u2032',
11049	primes: '\u2119',
11050	prnap: '\u2AB9',
11051	prnE: '\u2AB5',
11052	prnsim: '\u22E8',
11053	prod: '\u220F',
11054	Product: '\u220F',
11055	profalar: '\u232E',
11056	profline: '\u2312',
11057	profsurf: '\u2313',
11058	prop: '\u221D',
11059	Proportion: '\u2237',
11060	Proportional: '\u221D',
11061	propto: '\u221D',
11062	prsim: '\u227E',
11063	prurel: '\u22B0',
11064	Pscr: '\uD835\uDCAB',
11065	pscr: '\uD835\uDCC5',
11066	Psi: '\u03A8',
11067	psi: '\u03C8',
11068	puncsp: '\u2008',
11069	Qfr: '\uD835\uDD14',
11070	qfr: '\uD835\uDD2E',
11071	qint: '\u2A0C',
11072	Qopf: '\u211A',
11073	qopf: '\uD835\uDD62',
11074	qprime: '\u2057',
11075	Qscr: '\uD835\uDCAC',
11076	qscr: '\uD835\uDCC6',
11077	quaternions: '\u210D',
11078	quatint: '\u2A16',
11079	quest: '\u003F',
11080	questeq: '\u225F',
11081	QUOT: '\u0022',
11082	quot: '\u0022',
11083	rAarr: '\u21DB',
11084	race: '\u223D\u0331',
11085	Racute: '\u0154',
11086	racute: '\u0155',
11087	radic: '\u221A',
11088	raemptyv: '\u29B3',
11089	Rang: '\u27EB',
11090	rang: '\u27E9',
11091	rangd: '\u2992',
11092	range: '\u29A5',
11093	rangle: '\u27E9',
11094	raquo: '\u00BB',
11095	Rarr: '\u21A0',
11096	rArr: '\u21D2',
11097	rarr: '\u2192',
11098	rarrap: '\u2975',
11099	rarrb: '\u21E5',
11100	rarrbfs: '\u2920',
11101	rarrc: '\u2933',
11102	rarrfs: '\u291E',
11103	rarrhk: '\u21AA',
11104	rarrlp: '\u21AC',
11105	rarrpl: '\u2945',
11106	rarrsim: '\u2974',
11107	Rarrtl: '\u2916',
11108	rarrtl: '\u21A3',
11109	rarrw: '\u219D',
11110	rAtail: '\u291C',
11111	ratail: '\u291A',
11112	ratio: '\u2236',
11113	rationals: '\u211A',
11114	RBarr: '\u2910',
11115	rBarr: '\u290F',
11116	rbarr: '\u290D',
11117	rbbrk: '\u2773',
11118	rbrace: '\u007D',
11119	rbrack: '\u005D',
11120	rbrke: '\u298C',
11121	rbrksld: '\u298E',
11122	rbrkslu: '\u2990',
11123	Rcaron: '\u0158',
11124	rcaron: '\u0159',
11125	Rcedil: '\u0156',
11126	rcedil: '\u0157',
11127	rceil: '\u2309',
11128	rcub: '\u007D',
11129	Rcy: '\u0420',
11130	rcy: '\u0440',
11131	rdca: '\u2937',
11132	rdldhar: '\u2969',
11133	rdquo: '\u201D',
11134	rdquor: '\u201D',
11135	rdsh: '\u21B3',
11136	Re: '\u211C',
11137	real: '\u211C',
11138	realine: '\u211B',
11139	realpart: '\u211C',
11140	reals: '\u211D',
11141	rect: '\u25AD',
11142	REG: '\u00AE',
11143	reg: '\u00AE',
11144	ReverseElement: '\u220B',
11145	ReverseEquilibrium: '\u21CB',
11146	ReverseUpEquilibrium: '\u296F',
11147	rfisht: '\u297D',
11148	rfloor: '\u230B',
11149	Rfr: '\u211C',
11150	rfr: '\uD835\uDD2F',
11151	rHar: '\u2964',
11152	rhard: '\u21C1',
11153	rharu: '\u21C0',
11154	rharul: '\u296C',
11155	Rho: '\u03A1',
11156	rho: '\u03C1',
11157	rhov: '\u03F1',
11158	RightAngleBracket: '\u27E9',
11159	RightArrow: '\u2192',
11160	Rightarrow: '\u21D2',
11161	rightarrow: '\u2192',
11162	RightArrowBar: '\u21E5',
11163	RightArrowLeftArrow: '\u21C4',
11164	rightarrowtail: '\u21A3',
11165	RightCeiling: '\u2309',
11166	RightDoubleBracket: '\u27E7',
11167	RightDownTeeVector: '\u295D',
11168	RightDownVector: '\u21C2',
11169	RightDownVectorBar: '\u2955',
11170	RightFloor: '\u230B',
11171	rightharpoondown: '\u21C1',
11172	rightharpoonup: '\u21C0',
11173	rightleftarrows: '\u21C4',
11174	rightleftharpoons: '\u21CC',
11175	rightrightarrows: '\u21C9',
11176	rightsquigarrow: '\u219D',
11177	RightTee: '\u22A2',
11178	RightTeeArrow: '\u21A6',
11179	RightTeeVector: '\u295B',
11180	rightthreetimes: '\u22CC',
11181	RightTriangle: '\u22B3',
11182	RightTriangleBar: '\u29D0',
11183	RightTriangleEqual: '\u22B5',
11184	RightUpDownVector: '\u294F',
11185	RightUpTeeVector: '\u295C',
11186	RightUpVector: '\u21BE',
11187	RightUpVectorBar: '\u2954',
11188	RightVector: '\u21C0',
11189	RightVectorBar: '\u2953',
11190	ring: '\u02DA',
11191	risingdotseq: '\u2253',
11192	rlarr: '\u21C4',
11193	rlhar: '\u21CC',
11194	rlm: '\u200F',
11195	rmoust: '\u23B1',
11196	rmoustache: '\u23B1',
11197	rnmid: '\u2AEE',
11198	roang: '\u27ED',
11199	roarr: '\u21FE',
11200	robrk: '\u27E7',
11201	ropar: '\u2986',
11202	Ropf: '\u211D',
11203	ropf: '\uD835\uDD63',
11204	roplus: '\u2A2E',
11205	rotimes: '\u2A35',
11206	RoundImplies: '\u2970',
11207	rpar: '\u0029',
11208	rpargt: '\u2994',
11209	rppolint: '\u2A12',
11210	rrarr: '\u21C9',
11211	Rrightarrow: '\u21DB',
11212	rsaquo: '\u203A',
11213	Rscr: '\u211B',
11214	rscr: '\uD835\uDCC7',
11215	Rsh: '\u21B1',
11216	rsh: '\u21B1',
11217	rsqb: '\u005D',
11218	rsquo: '\u2019',
11219	rsquor: '\u2019',
11220	rthree: '\u22CC',
11221	rtimes: '\u22CA',
11222	rtri: '\u25B9',
11223	rtrie: '\u22B5',
11224	rtrif: '\u25B8',
11225	rtriltri: '\u29CE',
11226	RuleDelayed: '\u29F4',
11227	ruluhar: '\u2968',
11228	rx: '\u211E',
11229	Sacute: '\u015A',
11230	sacute: '\u015B',
11231	sbquo: '\u201A',
11232	Sc: '\u2ABC',
11233	sc: '\u227B',
11234	scap: '\u2AB8',
11235	Scaron: '\u0160',
11236	scaron: '\u0161',
11237	sccue: '\u227D',
11238	scE: '\u2AB4',
11239	sce: '\u2AB0',
11240	Scedil: '\u015E',
11241	scedil: '\u015F',
11242	Scirc: '\u015C',
11243	scirc: '\u015D',
11244	scnap: '\u2ABA',
11245	scnE: '\u2AB6',
11246	scnsim: '\u22E9',
11247	scpolint: '\u2A13',
11248	scsim: '\u227F',
11249	Scy: '\u0421',
11250	scy: '\u0441',
11251	sdot: '\u22C5',
11252	sdotb: '\u22A1',
11253	sdote: '\u2A66',
11254	searhk: '\u2925',
11255	seArr: '\u21D8',
11256	searr: '\u2198',
11257	searrow: '\u2198',
11258	sect: '\u00A7',
11259	semi: '\u003B',
11260	seswar: '\u2929',
11261	setminus: '\u2216',
11262	setmn: '\u2216',
11263	sext: '\u2736',
11264	Sfr: '\uD835\uDD16',
11265	sfr: '\uD835\uDD30',
11266	sfrown: '\u2322',
11267	sharp: '\u266F',
11268	SHCHcy: '\u0429',
11269	shchcy: '\u0449',
11270	SHcy: '\u0428',
11271	shcy: '\u0448',
11272	ShortDownArrow: '\u2193',
11273	ShortLeftArrow: '\u2190',
11274	shortmid: '\u2223',
11275	shortparallel: '\u2225',
11276	ShortRightArrow: '\u2192',
11277	ShortUpArrow: '\u2191',
11278	shy: '\u00AD',
11279	Sigma: '\u03A3',
11280	sigma: '\u03C3',
11281	sigmaf: '\u03C2',
11282	sigmav: '\u03C2',
11283	sim: '\u223C',
11284	simdot: '\u2A6A',
11285	sime: '\u2243',
11286	simeq: '\u2243',
11287	simg: '\u2A9E',
11288	simgE: '\u2AA0',
11289	siml: '\u2A9D',
11290	simlE: '\u2A9F',
11291	simne: '\u2246',
11292	simplus: '\u2A24',
11293	simrarr: '\u2972',
11294	slarr: '\u2190',
11295	SmallCircle: '\u2218',
11296	smallsetminus: '\u2216',
11297	smashp: '\u2A33',
11298	smeparsl: '\u29E4',
11299	smid: '\u2223',
11300	smile: '\u2323',
11301	smt: '\u2AAA',
11302	smte: '\u2AAC',
11303	smtes: '\u2AAC\uFE00',
11304	SOFTcy: '\u042C',
11305	softcy: '\u044C',
11306	sol: '\u002F',
11307	solb: '\u29C4',
11308	solbar: '\u233F',
11309	Sopf: '\uD835\uDD4A',
11310	sopf: '\uD835\uDD64',
11311	spades: '\u2660',
11312	spadesuit: '\u2660',
11313	spar: '\u2225',
11314	sqcap: '\u2293',
11315	sqcaps: '\u2293\uFE00',
11316	sqcup: '\u2294',
11317	sqcups: '\u2294\uFE00',
11318	Sqrt: '\u221A',
11319	sqsub: '\u228F',
11320	sqsube: '\u2291',
11321	sqsubset: '\u228F',
11322	sqsubseteq: '\u2291',
11323	sqsup: '\u2290',
11324	sqsupe: '\u2292',
11325	sqsupset: '\u2290',
11326	sqsupseteq: '\u2292',
11327	squ: '\u25A1',
11328	Square: '\u25A1',
11329	square: '\u25A1',
11330	SquareIntersection: '\u2293',
11331	SquareSubset: '\u228F',
11332	SquareSubsetEqual: '\u2291',
11333	SquareSuperset: '\u2290',
11334	SquareSupersetEqual: '\u2292',
11335	SquareUnion: '\u2294',
11336	squarf: '\u25AA',
11337	squf: '\u25AA',
11338	srarr: '\u2192',
11339	Sscr: '\uD835\uDCAE',
11340	sscr: '\uD835\uDCC8',
11341	ssetmn: '\u2216',
11342	ssmile: '\u2323',
11343	sstarf: '\u22C6',
11344	Star: '\u22C6',
11345	star: '\u2606',
11346	starf: '\u2605',
11347	straightepsilon: '\u03F5',
11348	straightphi: '\u03D5',
11349	strns: '\u00AF',
11350	Sub: '\u22D0',
11351	sub: '\u2282',
11352	subdot: '\u2ABD',
11353	subE: '\u2AC5',
11354	sube: '\u2286',
11355	subedot: '\u2AC3',
11356	submult: '\u2AC1',
11357	subnE: '\u2ACB',
11358	subne: '\u228A',
11359	subplus: '\u2ABF',
11360	subrarr: '\u2979',
11361	Subset: '\u22D0',
11362	subset: '\u2282',
11363	subseteq: '\u2286',
11364	subseteqq: '\u2AC5',
11365	SubsetEqual: '\u2286',
11366	subsetneq: '\u228A',
11367	subsetneqq: '\u2ACB',
11368	subsim: '\u2AC7',
11369	subsub: '\u2AD5',
11370	subsup: '\u2AD3',
11371	succ: '\u227B',
11372	succapprox: '\u2AB8',
11373	succcurlyeq: '\u227D',
11374	Succeeds: '\u227B',
11375	SucceedsEqual: '\u2AB0',
11376	SucceedsSlantEqual: '\u227D',
11377	SucceedsTilde: '\u227F',
11378	succeq: '\u2AB0',
11379	succnapprox: '\u2ABA',
11380	succneqq: '\u2AB6',
11381	succnsim: '\u22E9',
11382	succsim: '\u227F',
11383	SuchThat: '\u220B',
11384	Sum: '\u2211',
11385	sum: '\u2211',
11386	sung: '\u266A',
11387	Sup: '\u22D1',
11388	sup: '\u2283',
11389	sup1: '\u00B9',
11390	sup2: '\u00B2',
11391	sup3: '\u00B3',
11392	supdot: '\u2ABE',
11393	supdsub: '\u2AD8',
11394	supE: '\u2AC6',
11395	supe: '\u2287',
11396	supedot: '\u2AC4',
11397	Superset: '\u2283',
11398	SupersetEqual: '\u2287',
11399	suphsol: '\u27C9',
11400	suphsub: '\u2AD7',
11401	suplarr: '\u297B',
11402	supmult: '\u2AC2',
11403	supnE: '\u2ACC',
11404	supne: '\u228B',
11405	supplus: '\u2AC0',
11406	Supset: '\u22D1',
11407	supset: '\u2283',
11408	supseteq: '\u2287',
11409	supseteqq: '\u2AC6',
11410	supsetneq: '\u228B',
11411	supsetneqq: '\u2ACC',
11412	supsim: '\u2AC8',
11413	supsub: '\u2AD4',
11414	supsup: '\u2AD6',
11415	swarhk: '\u2926',
11416	swArr: '\u21D9',
11417	swarr: '\u2199',
11418	swarrow: '\u2199',
11419	swnwar: '\u292A',
11420	szlig: '\u00DF',
11421	Tab: '\u0009',
11422	target: '\u2316',
11423	Tau: '\u03A4',
11424	tau: '\u03C4',
11425	tbrk: '\u23B4',
11426	Tcaron: '\u0164',
11427	tcaron: '\u0165',
11428	Tcedil: '\u0162',
11429	tcedil: '\u0163',
11430	Tcy: '\u0422',
11431	tcy: '\u0442',
11432	tdot: '\u20DB',
11433	telrec: '\u2315',
11434	Tfr: '\uD835\uDD17',
11435	tfr: '\uD835\uDD31',
11436	there4: '\u2234',
11437	Therefore: '\u2234',
11438	therefore: '\u2234',
11439	Theta: '\u0398',
11440	theta: '\u03B8',
11441	thetasym: '\u03D1',
11442	thetav: '\u03D1',
11443	thickapprox: '\u2248',
11444	thicksim: '\u223C',
11445	ThickSpace: '\u205F\u200A',
11446	thinsp: '\u2009',
11447	ThinSpace: '\u2009',
11448	thkap: '\u2248',
11449	thksim: '\u223C',
11450	THORN: '\u00DE',
11451	thorn: '\u00FE',
11452	Tilde: '\u223C',
11453	tilde: '\u02DC',
11454	TildeEqual: '\u2243',
11455	TildeFullEqual: '\u2245',
11456	TildeTilde: '\u2248',
11457	times: '\u00D7',
11458	timesb: '\u22A0',
11459	timesbar: '\u2A31',
11460	timesd: '\u2A30',
11461	tint: '\u222D',
11462	toea: '\u2928',
11463	top: '\u22A4',
11464	topbot: '\u2336',
11465	topcir: '\u2AF1',
11466	Topf: '\uD835\uDD4B',
11467	topf: '\uD835\uDD65',
11468	topfork: '\u2ADA',
11469	tosa: '\u2929',
11470	tprime: '\u2034',
11471	TRADE: '\u2122',
11472	trade: '\u2122',
11473	triangle: '\u25B5',
11474	triangledown: '\u25BF',
11475	triangleleft: '\u25C3',
11476	trianglelefteq: '\u22B4',
11477	triangleq: '\u225C',
11478	triangleright: '\u25B9',
11479	trianglerighteq: '\u22B5',
11480	tridot: '\u25EC',
11481	trie: '\u225C',
11482	triminus: '\u2A3A',
11483	TripleDot: '\u20DB',
11484	triplus: '\u2A39',
11485	trisb: '\u29CD',
11486	tritime: '\u2A3B',
11487	trpezium: '\u23E2',
11488	Tscr: '\uD835\uDCAF',
11489	tscr: '\uD835\uDCC9',
11490	TScy: '\u0426',
11491	tscy: '\u0446',
11492	TSHcy: '\u040B',
11493	tshcy: '\u045B',
11494	Tstrok: '\u0166',
11495	tstrok: '\u0167',
11496	twixt: '\u226C',
11497	twoheadleftarrow: '\u219E',
11498	twoheadrightarrow: '\u21A0',
11499	Uacute: '\u00DA',
11500	uacute: '\u00FA',
11501	Uarr: '\u219F',
11502	uArr: '\u21D1',
11503	uarr: '\u2191',
11504	Uarrocir: '\u2949',
11505	Ubrcy: '\u040E',
11506	ubrcy: '\u045E',
11507	Ubreve: '\u016C',
11508	ubreve: '\u016D',
11509	Ucirc: '\u00DB',
11510	ucirc: '\u00FB',
11511	Ucy: '\u0423',
11512	ucy: '\u0443',
11513	udarr: '\u21C5',
11514	Udblac: '\u0170',
11515	udblac: '\u0171',
11516	udhar: '\u296E',
11517	ufisht: '\u297E',
11518	Ufr: '\uD835\uDD18',
11519	ufr: '\uD835\uDD32',
11520	Ugrave: '\u00D9',
11521	ugrave: '\u00F9',
11522	uHar: '\u2963',
11523	uharl: '\u21BF',
11524	uharr: '\u21BE',
11525	uhblk: '\u2580',
11526	ulcorn: '\u231C',
11527	ulcorner: '\u231C',
11528	ulcrop: '\u230F',
11529	ultri: '\u25F8',
11530	Umacr: '\u016A',
11531	umacr: '\u016B',
11532	uml: '\u00A8',
11533	UnderBar: '\u005F',
11534	UnderBrace: '\u23DF',
11535	UnderBracket: '\u23B5',
11536	UnderParenthesis: '\u23DD',
11537	Union: '\u22C3',
11538	UnionPlus: '\u228E',
11539	Uogon: '\u0172',
11540	uogon: '\u0173',
11541	Uopf: '\uD835\uDD4C',
11542	uopf: '\uD835\uDD66',
11543	UpArrow: '\u2191',
11544	Uparrow: '\u21D1',
11545	uparrow: '\u2191',
11546	UpArrowBar: '\u2912',
11547	UpArrowDownArrow: '\u21C5',
11548	UpDownArrow: '\u2195',
11549	Updownarrow: '\u21D5',
11550	updownarrow: '\u2195',
11551	UpEquilibrium: '\u296E',
11552	upharpoonleft: '\u21BF',
11553	upharpoonright: '\u21BE',
11554	uplus: '\u228E',
11555	UpperLeftArrow: '\u2196',
11556	UpperRightArrow: '\u2197',
11557	Upsi: '\u03D2',
11558	upsi: '\u03C5',
11559	upsih: '\u03D2',
11560	Upsilon: '\u03A5',
11561	upsilon: '\u03C5',
11562	UpTee: '\u22A5',
11563	UpTeeArrow: '\u21A5',
11564	upuparrows: '\u21C8',
11565	urcorn: '\u231D',
11566	urcorner: '\u231D',
11567	urcrop: '\u230E',
11568	Uring: '\u016E',
11569	uring: '\u016F',
11570	urtri: '\u25F9',
11571	Uscr: '\uD835\uDCB0',
11572	uscr: '\uD835\uDCCA',
11573	utdot: '\u22F0',
11574	Utilde: '\u0168',
11575	utilde: '\u0169',
11576	utri: '\u25B5',
11577	utrif: '\u25B4',
11578	uuarr: '\u21C8',
11579	Uuml: '\u00DC',
11580	uuml: '\u00FC',
11581	uwangle: '\u29A7',
11582	vangrt: '\u299C',
11583	varepsilon: '\u03F5',
11584	varkappa: '\u03F0',
11585	varnothing: '\u2205',
11586	varphi: '\u03D5',
11587	varpi: '\u03D6',
11588	varpropto: '\u221D',
11589	vArr: '\u21D5',
11590	varr: '\u2195',
11591	varrho: '\u03F1',
11592	varsigma: '\u03C2',
11593	varsubsetneq: '\u228A\uFE00',
11594	varsubsetneqq: '\u2ACB\uFE00',
11595	varsupsetneq: '\u228B\uFE00',
11596	varsupsetneqq: '\u2ACC\uFE00',
11597	vartheta: '\u03D1',
11598	vartriangleleft: '\u22B2',
11599	vartriangleright: '\u22B3',
11600	Vbar: '\u2AEB',
11601	vBar: '\u2AE8',
11602	vBarv: '\u2AE9',
11603	Vcy: '\u0412',
11604	vcy: '\u0432',
11605	VDash: '\u22AB',
11606	Vdash: '\u22A9',
11607	vDash: '\u22A8',
11608	vdash: '\u22A2',
11609	Vdashl: '\u2AE6',
11610	Vee: '\u22C1',
11611	vee: '\u2228',
11612	veebar: '\u22BB',
11613	veeeq: '\u225A',
11614	vellip: '\u22EE',
11615	Verbar: '\u2016',
11616	verbar: '\u007C',
11617	Vert: '\u2016',
11618	vert: '\u007C',
11619	VerticalBar: '\u2223',
11620	VerticalLine: '\u007C',
11621	VerticalSeparator: '\u2758',
11622	VerticalTilde: '\u2240',
11623	VeryThinSpace: '\u200A',
11624	Vfr: '\uD835\uDD19',
11625	vfr: '\uD835\uDD33',
11626	vltri: '\u22B2',
11627	vnsub: '\u2282\u20D2',
11628	vnsup: '\u2283\u20D2',
11629	Vopf: '\uD835\uDD4D',
11630	vopf: '\uD835\uDD67',
11631	vprop: '\u221D',
11632	vrtri: '\u22B3',
11633	Vscr: '\uD835\uDCB1',
11634	vscr: '\uD835\uDCCB',
11635	vsubnE: '\u2ACB\uFE00',
11636	vsubne: '\u228A\uFE00',
11637	vsupnE: '\u2ACC\uFE00',
11638	vsupne: '\u228B\uFE00',
11639	Vvdash: '\u22AA',
11640	vzigzag: '\u299A',
11641	Wcirc: '\u0174',
11642	wcirc: '\u0175',
11643	wedbar: '\u2A5F',
11644	Wedge: '\u22C0',
11645	wedge: '\u2227',
11646	wedgeq: '\u2259',
11647	weierp: '\u2118',
11648	Wfr: '\uD835\uDD1A',
11649	wfr: '\uD835\uDD34',
11650	Wopf: '\uD835\uDD4E',
11651	wopf: '\uD835\uDD68',
11652	wp: '\u2118',
11653	wr: '\u2240',
11654	wreath: '\u2240',
11655	Wscr: '\uD835\uDCB2',
11656	wscr: '\uD835\uDCCC',
11657	xcap: '\u22C2',
11658	xcirc: '\u25EF',
11659	xcup: '\u22C3',
11660	xdtri: '\u25BD',
11661	Xfr: '\uD835\uDD1B',
11662	xfr: '\uD835\uDD35',
11663	xhArr: '\u27FA',
11664	xharr: '\u27F7',
11665	Xi: '\u039E',
11666	xi: '\u03BE',
11667	xlArr: '\u27F8',
11668	xlarr: '\u27F5',
11669	xmap: '\u27FC',
11670	xnis: '\u22FB',
11671	xodot: '\u2A00',
11672	Xopf: '\uD835\uDD4F',
11673	xopf: '\uD835\uDD69',
11674	xoplus: '\u2A01',
11675	xotime: '\u2A02',
11676	xrArr: '\u27F9',
11677	xrarr: '\u27F6',
11678	Xscr: '\uD835\uDCB3',
11679	xscr: '\uD835\uDCCD',
11680	xsqcup: '\u2A06',
11681	xuplus: '\u2A04',
11682	xutri: '\u25B3',
11683	xvee: '\u22C1',
11684	xwedge: '\u22C0',
11685	Yacute: '\u00DD',
11686	yacute: '\u00FD',
11687	YAcy: '\u042F',
11688	yacy: '\u044F',
11689	Ycirc: '\u0176',
11690	ycirc: '\u0177',
11691	Ycy: '\u042B',
11692	ycy: '\u044B',
11693	yen: '\u00A5',
11694	Yfr: '\uD835\uDD1C',
11695	yfr: '\uD835\uDD36',
11696	YIcy: '\u0407',
11697	yicy: '\u0457',
11698	Yopf: '\uD835\uDD50',
11699	yopf: '\uD835\uDD6A',
11700	Yscr: '\uD835\uDCB4',
11701	yscr: '\uD835\uDCCE',
11702	YUcy: '\u042E',
11703	yucy: '\u044E',
11704	Yuml: '\u0178',
11705	yuml: '\u00FF',
11706	Zacute: '\u0179',
11707	zacute: '\u017A',
11708	Zcaron: '\u017D',
11709	zcaron: '\u017E',
11710	Zcy: '\u0417',
11711	zcy: '\u0437',
11712	Zdot: '\u017B',
11713	zdot: '\u017C',
11714	zeetrf: '\u2128',
11715	ZeroWidthSpace: '\u200B',
11716	Zeta: '\u0396',
11717	zeta: '\u03B6',
11718	Zfr: '\u2128',
11719	zfr: '\uD835\uDD37',
11720	ZHcy: '\u0416',
11721	zhcy: '\u0436',
11722	zigrarr: '\u21DD',
11723	Zopf: '\u2124',
11724	zopf: '\uD835\uDD6B',
11725	Zscr: '\uD835\uDCB5',
11726	zscr: '\uD835\uDCCF',
11727	zwj: '\u200D',
11728	zwnj: '\u200C',
11729});
11730
11731/**
11732 * @deprecated use `HTML_ENTITIES` instead
11733 * @see HTML_ENTITIES
11734 */
11735exports.entityMap = exports.HTML_ENTITIES;
11736
11737},{"./conventions":29}],33:[function(require,module,exports){
11738var dom = require('./dom')
11739exports.DOMImplementation = dom.DOMImplementation
11740exports.XMLSerializer = dom.XMLSerializer
11741exports.DOMParser = require('./dom-parser').DOMParser
11742
11743},{"./dom":31,"./dom-parser":30}],34:[function(require,module,exports){
11744var NAMESPACE = require("./conventions").NAMESPACE;
11745var tagNamePattern = require("./conventions").tagNamePattern;
11746
11747//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(',')
11748
11749//S_TAG,	S_ATTR,	S_EQ,	S_ATTR_NOQUOT_VALUE
11750//S_ATTR_SPACE,	S_ATTR_END,	S_TAG_SPACE, S_TAG_CLOSE
11751var S_TAG = 0;//tag name offerring
11752var S_ATTR = 1;//attr name offerring
11753var S_ATTR_SPACE=2;//attr name end and space offer
11754var S_EQ = 3;//=space?
11755var S_ATTR_NOQUOT_VALUE = 4;//attr value(no quot value only)
11756var S_ATTR_END = 5;//attr value end and no space(quot end)
11757var S_TAG_SPACE = 6;//(attr value end || tag end ) && (space offer)
11758var S_TAG_CLOSE = 7;//closed el<el />
11759
11760/**
11761 * Creates an error that will not be caught by XMLReader aka the SAX parser.
11762 *
11763 * @param {string} message
11764 * @param {any?} locator Optional, can provide details about the location in the source
11765 * @constructor
11766 */
11767function ParseError(message, locator) {
11768	this.message = message
11769	this.locator = locator
11770	if(Error.captureStackTrace) Error.captureStackTrace(this, ParseError);
11771}
11772ParseError.prototype = new Error();
11773ParseError.prototype.name = ParseError.name
11774
11775function XMLReader(){
11776
11777}
11778
11779XMLReader.prototype = {
11780	parse:function(source,defaultNSMap,entityMap){
11781		var domBuilder = this.domBuilder;
11782		domBuilder.startDocument();
11783		_copy(defaultNSMap ,defaultNSMap = {})
11784		parse(source,defaultNSMap,entityMap,
11785				domBuilder,this.errorHandler);
11786		domBuilder.endDocument();
11787	}
11788}
11789function parse(source,defaultNSMapCopy,entityMap,domBuilder,errorHandler){
11790	function fixedFromCharCode(code) {
11791		// String.prototype.fromCharCode does not supports
11792		// > 2 bytes unicode chars directly
11793		if (code > 0xffff) {
11794			code -= 0x10000;
11795			var surrogate1 = 0xd800 + (code >> 10)
vendor: 16,847 bytes, lines 11796-12339
11796				, surrogate2 = 0xdc00 + (code & 0x3ff);
11797
11798			return String.fromCharCode(surrogate1, surrogate2);
11799		} else {
11800			return String.fromCharCode(code);
11801		}
11802	}
11803	function entityReplacer(a){
11804		var k = a.slice(1,-1);
11805		if (Object.hasOwnProperty.call(entityMap, k)) {
11806			return entityMap[k];
11807		}else if(k.charAt(0) === '#'){
11808			return fixedFromCharCode(parseInt(k.substr(1).replace('x','0x')))
11809		}else{
11810			errorHandler.error('entity not found:'+a);
11811			return a;
11812		}
11813	}
11814	function appendText(end){//has some bugs
11815		if(end>start){
11816			var xt = source.substring(start,end).replace(/&#?\w+;/g,entityReplacer);
11817			locator&&position(start);
11818			domBuilder.characters(xt,0,end-start);
11819			start = end
11820		}
11821	}
11822	function position(p,m){
11823		while(p>=lineEnd && (m = linePattern.exec(source))){
11824			lineStart = m.index;
11825			lineEnd = lineStart + m[0].length;
11826			locator.lineNumber++;
11827			//console.log('line++:',locator,startPos,endPos)
11828		}
11829		locator.columnNumber = p-lineStart+1;
11830	}
11831	var lineStart = 0;
11832	var lineEnd = 0;
11833	var linePattern = /.*(?:\r\n?|\n)|.*$/g
11834	var locator = domBuilder.locator;
11835
11836	var parseStack = [{currentNSMap:defaultNSMapCopy}]
11837	var closeMap = {};
11838	var start = 0;
11839	while(true){
11840		try{
11841			var tagStart = source.indexOf('<',start);
11842			if(tagStart<0){
11843				if(!source.substr(start).match(/^\s*$/)){
11844					var doc = domBuilder.doc;
11845	    			var text = doc.createTextNode(source.substr(start));
11846	    			doc.appendChild(text);
11847	    			domBuilder.currentElement = text;
11848				}
11849				return;
11850			}
11851			if(tagStart>start){
11852				appendText(tagStart);
11853			}
11854			switch(source.charAt(tagStart+1)){
11855			case '/':
11856				var end = source.indexOf('>',tagStart+3);
11857				// Anchored trailing-whitespace trim. The former `/[ \t\n\r]+$/g` backtracked
11858				// quadratically on a name shaped `whitespace-run + non-whitespace char`
11859				// (an end tag `</   …   x>`): from every start position it extended `[ws]+`
11860				// to the end and then failed the `$` anchor. This anchored form matches the
11861				// whole string once and captures everything up to the last non-whitespace
11862				// character, so it trims in linear time with byte-identical output.
11863				var tagName = source.substring(tagStart + 2, end).replace(/^([\s\S]*?[^ \t\n\r])?[ \t\n\r]*$/, '$1');
11864				var config = parseStack.pop();
11865				if(end<0){
11866
11867	        		tagName = source.substring(tagStart+2).replace(/[\s<].*/,'');
11868	        		errorHandler.error("end tag name: "+tagName+' is not complete:'+config.tagName);
11869	        		end = tagStart+1+tagName.length;
11870	        	}else if(tagName.match(/\s</)){
11871	        		tagName = tagName.replace(/[\s<].*/,'');
11872	        		errorHandler.error("end tag name: "+tagName+' maybe not complete');
11873	        		end = tagStart+1+tagName.length;
11874				}else if(/[ \t\n\r]/.test(tagName) && tagNamePattern.test(tagName.split(/[ \t\n\r]/)[0])){
11875					// The XML `ETag` production is `'</' Name S? '>'`: only optional whitespace may follow
11876					// the `Name`. A valid `Name` followed by whitespace and non-whitespace residue (e.g.
11877					// `</a\nbogus>` or `</a bogus>`) is not well-formed, but historically it was silently
11878					// accepted (the malformed end tag ignored, the residue dropped, the element left on the
11879					// stack). Report it as a recoverable `error` (which a custom errorHandler may escalate to
11880					// fatal) while keeping the existing recovery so the parsed DOM stays byte-identical.
11881					errorHandler.error('end tag name is followed by whitespace and trailing content: "'+tagName+'"');
11882				}
11883				var localNSMap = config.localNSMap;
11884				var endMatch = config.tagName == tagName;
11885				var endIgnoreCaseMach = endMatch || config.tagName&&config.tagName.toLowerCase() == tagName.toLowerCase()
11886		        if(endIgnoreCaseMach){
11887		        	domBuilder.endElement(config.uri,config.localName,tagName);
11888					if(localNSMap){
11889						for (var prefix in localNSMap) {
11890							if (Object.prototype.hasOwnProperty.call(localNSMap, prefix)) {
11891								domBuilder.endPrefixMapping(prefix);
11892							}
11893						}
11894					}
11895					if(!endMatch){
11896		            	errorHandler.fatalError("end tag name: "+tagName+' is not match the current start tagName:'+config.tagName ); // No known test case
11897					}
11898		        }else{
11899		        	parseStack.push(config)
11900		        }
11901
11902				end++;
11903				break;
11904				// end elment
11905			case '?':// <?...?>
11906				locator&&position(tagStart);
11907				end = parseInstruction(source,tagStart,domBuilder);
11908				break;
11909			case '!':// <!doctype,<![CDATA,<!--
11910				locator&&position(tagStart);
11911				end = parseDCC(source,tagStart,domBuilder,errorHandler);
11912				break;
11913			default:
11914				locator&&position(tagStart);
11915				var el = new ElementAttributes();
11916				var currentNSMap = parseStack[parseStack.length-1].currentNSMap;
11917				//elStartEnd
11918				var end = parseElementStartPart(source,tagStart,el,currentNSMap,entityReplacer,errorHandler);
11919				var len = el.length;
11920
11921
11922				if(!el.closed && fixSelfClosed(source,end,el.tagName,closeMap)){
11923					el.closed = true;
11924					if(!entityMap.nbsp){
11925						errorHandler.warning('unclosed xml attribute');
11926					}
11927				}
11928				if(locator && len){
11929					var locator2 = copyLocator(locator,{});
11930					//try{//attribute position fixed
11931					for(var i = 0;i<len;i++){
11932						var a = el[i];
11933						position(a.offset);
11934						a.locator = copyLocator(locator,{});
11935					}
11936					domBuilder.locator = locator2
11937					if(appendElement(el,domBuilder,currentNSMap)){
11938						parseStack.push(el)
11939					}
11940					domBuilder.locator = locator;
11941				}else{
11942					if(appendElement(el,domBuilder,currentNSMap)){
11943						parseStack.push(el)
11944					}
11945				}
11946
11947				if (NAMESPACE.isHTML(el.uri) && !el.closed) {
11948					end = parseHtmlSpecialContent(source,end,el.tagName,entityReplacer,domBuilder)
11949				} else {
11950					end++;
11951				}
11952			}
11953		}catch(e){
11954			if (e instanceof ParseError) {
11955				throw e;
11956			}
11957			errorHandler.error('element parse error: '+e)
11958			end = -1;
11959		}
11960		if(end>start){
11961			start = end;
11962		}else{
11963			//TODO: 这里有可能sax回退,有位置错误风险
11964			appendText(Math.max(tagStart,start)+1);
11965		}
11966	}
11967}
11968function copyLocator(f,t){
11969	t.lineNumber = f.lineNumber;
11970	t.columnNumber = f.columnNumber;
11971	return t;
11972}
11973
11974/**
11975 * @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack);
11976 * @return end of the elementStartPart(end of elementEndPart for selfClosed el)
11977 */
11978function parseElementStartPart(source,start,el,currentNSMap,entityReplacer,errorHandler){
11979
11980	/**
11981	 * @param {string} qname
11982	 * @param {string} value
11983	 * @param {number} startIndex
11984	 */
11985	function addAttribute(qname, value, startIndex) {
11986		if (el.attributeNames.hasOwnProperty(qname)) {
11987			errorHandler.fatalError('Attribute ' + qname + ' redefined')
11988		}
11989		el.addValue(
11990			qname,
11991			// @see https://www.w3.org/TR/xml/#AVNormalize
11992			// since the xmldom sax parser does not "interpret" DTD the following is not implemented:
11993			// - recursive replacement of (DTD) entity references
11994			// - trimming and collapsing multiple spaces into a single one for attributes that are not of type CDATA
11995			value.replace(/[\t\n\r]/g, ' ').replace(/&#?\w+;/g, entityReplacer),
11996			startIndex
11997		)
11998	}
11999	var attrName;
12000	var value;
12001	var p = ++start;
12002	var s = S_TAG;//status
12003	while(true){
12004		var c = source.charAt(p);
12005		if (s === S_TAG && c === '<') {
12006			// A `<` can never occur inside a tag name. Without this guard the scan runs
12007			// on to the next `>` (or EOF) before the tag name is rejected, so a document
12008			// with many `<` inside a malformed tag makes each one-character recovery step
12009			// re-scan to the distant `>` — O(n^2). Stopping at the `<` keeps each recovery
12010			// step bounded. The candidate scanned so far is reported raw, consistent with
12011			// the sibling invalid-tag-name throw below.
12012			throw new Error('unexpected < in tag name: ' + source.slice(start, p));
12013		}
12014		switch(c){
12015		case '=':
12016			if(s === S_ATTR){//attrName
12017				attrName = source.slice(start,p);
12018				s = S_EQ;
12019			}else if(s === S_ATTR_SPACE){
12020				s = S_EQ;
12021			}else{
12022				//fatalError: equal must after attrName or space after attrName
12023				throw new Error('attribute equal must after attrName'); // No known test case
12024			}
12025			break;
12026		case '\'':
12027		case '"':
12028			if(s === S_EQ || s === S_ATTR //|| s == S_ATTR_SPACE
12029				){//equal
12030				if(s === S_ATTR){
12031					errorHandler.warning('attribute value must after "="')
12032					attrName = source.slice(start,p)
12033				}
12034				start = p+1;
12035				p = source.indexOf(c,start)
12036				if(p>0){
12037					value = source.slice(start, p);
12038					addAttribute(attrName, value, start-1);
12039					s = S_ATTR_END;
12040				}else{
12041					//fatalError: no end quot match
12042					throw new Error('attribute value no end \''+c+'\' match');
12043				}
12044			}else if(s == S_ATTR_NOQUOT_VALUE){
12045				value = source.slice(start, p);
12046				addAttribute(attrName, value, start);
12047				errorHandler.warning('attribute "'+attrName+'" missed start quot('+c+')!!');
12048				start = p+1;
12049				s = S_ATTR_END
12050			}else{
12051				//fatalError: no equal before
12052				throw new Error('attribute value must after "="'); // No known test case
12053			}
12054			break;
12055		case '/':
12056			switch(s){
12057			case S_TAG:
12058				el.setTagName(source.slice(start,p));
12059			case S_ATTR_END:
12060			case S_TAG_SPACE:
12061			case S_TAG_CLOSE:
12062				s =S_TAG_CLOSE;
12063				el.closed = true;
12064			case S_ATTR_NOQUOT_VALUE:
12065			case S_ATTR:
12066				break;
12067				case S_ATTR_SPACE:
12068					el.closed = true;
12069				break;
12070			//case S_EQ:
12071			default:
12072				throw new Error("attribute invalid close char('/')") // No known test case
12073			}
12074			break;
12075		case ''://end document
12076			errorHandler.error('unexpected end of input');
12077			if(s == S_TAG){
12078				el.setTagName(source.slice(start,p));
12079			}
12080			return p;
12081		case '>':
12082			switch(s){
12083			case S_TAG:
12084				el.setTagName(source.slice(start,p));
12085			case S_ATTR_END:
12086			case S_TAG_SPACE:
12087			case S_TAG_CLOSE:
12088				break;//normal
12089			case S_ATTR_NOQUOT_VALUE://Compatible state
12090			case S_ATTR:
12091				value = source.slice(start,p);
12092				if(value.slice(-1) === '/'){
12093					el.closed  = true;
12094					value = value.slice(0,-1)
12095				}
12096			case S_ATTR_SPACE:
12097				if(s === S_ATTR_SPACE){
12098					value = attrName;
12099				}
12100				if(s == S_ATTR_NOQUOT_VALUE){
12101					errorHandler.warning('attribute "'+value+'" missed quot(")!');
12102					addAttribute(attrName, value, start)
12103				}else{
12104					if(!NAMESPACE.isHTML(currentNSMap['']) || !value.match(/^(?:disabled|checked|selected)$/i)){
12105						errorHandler.warning('attribute "'+value+'" missed value!! "'+value+'" instead!!')
12106					}
12107					addAttribute(value, value, start)
12108				}
12109				break;
12110			case S_EQ:
12111				throw new Error('attribute value missed!!');
12112			}
12113//			console.log(tagName,tagNamePattern,tagNamePattern.test(tagName))
12114			return p;
12115		/*xml space '\x20' | #x9 | #xD | #xA; */
12116		case '\u0080':
12117			c = ' ';
12118		default:
12119			if(c<= ' '){//space
12120				switch(s){
12121				case S_TAG:
12122					el.setTagName(source.slice(start,p));//tagName
12123					s = S_TAG_SPACE;
12124					break;
12125				case S_ATTR:
12126					attrName = source.slice(start,p)
12127					s = S_ATTR_SPACE;
12128					break;
12129				case S_ATTR_NOQUOT_VALUE:
12130					var value = source.slice(start, p);
12131					errorHandler.warning('attribute "'+value+'" missed quot(")!!');
12132					addAttribute(attrName, value, start)
12133				case S_ATTR_END:
12134					s = S_TAG_SPACE;
12135					break;
12136				//case S_TAG_SPACE:
12137				//case S_EQ:
12138				//case S_ATTR_SPACE:
12139				//	void();break;
12140				//case S_TAG_CLOSE:
12141					//ignore warning
12142				}
12143			}else{//not space
12144//S_TAG,	S_ATTR,	S_EQ,	S_ATTR_NOQUOT_VALUE
12145//S_ATTR_SPACE,	S_ATTR_END,	S_TAG_SPACE, S_TAG_CLOSE
12146				switch(s){
12147				//case S_TAG:void();break;
12148				//case S_ATTR:void();break;
12149				//case S_ATTR_NOQUOT_VALUE:void();break;
12150				case S_ATTR_SPACE:
12151					var tagName =  el.tagName;
12152					if (!NAMESPACE.isHTML(currentNSMap['']) || !attrName.match(/^(?:disabled|checked|selected)$/i)) {
12153						errorHandler.warning('attribute "'+attrName+'" missed value!! "'+attrName+'" instead2!!')
12154					}
12155					addAttribute(attrName, attrName, start);
12156					start = p;
12157					s = S_ATTR;
12158					break;
12159				case S_ATTR_END:
12160					errorHandler.warning('attribute space is required"'+attrName+'"!!')
12161				case S_TAG_SPACE:
12162					s = S_ATTR;
12163					start = p;
12164					break;
12165				case S_EQ:
12166					s = S_ATTR_NOQUOT_VALUE;
12167					start = p;
12168					break;
12169				case S_TAG_CLOSE:
12170					throw new Error("elements closed character '/' and '>' must be connected to");
12171				}
12172			}
12173		}//end outer switch
12174		//console.log('p++',p)
12175		p++;
12176	}
12177}
12178/**
12179 * @return true if has new namespace define
12180 */
12181function appendElement(el,domBuilder,currentNSMap){
12182	var tagName = el.tagName;
12183	var localNSMap = null;
12184	//var currentNSMap = parseStack[parseStack.length-1].currentNSMap;
12185	var i = el.length;
12186	while(i--){
12187		var a = el[i];
12188		var qName = a.qName;
12189		var value = a.value;
12190		var nsp = qName.indexOf(':');
12191		if(nsp>0){
12192			var prefix = a.prefix = qName.slice(0,nsp);
12193			var localName = qName.slice(nsp+1);
12194			var nsPrefix = prefix === 'xmlns' && localName
12195		}else{
12196			localName = qName;
12197			prefix = null
12198			nsPrefix = qName === 'xmlns' && ''
12199		}
12200		//can not set prefix,because prefix !== ''
12201		a.localName = localName ;
12202		//prefix == null for no ns prefix attribute
12203		if(nsPrefix !== false){//hack!!
12204			if(localNSMap == null){
12205				localNSMap = {}
12206				// Derive the child scope's namespace map by prototype-chain inheritance
12207				// instead of a flat copy: lookups inherit ancestor prefixes transparently,
12208				// so a document nesting N scopes retains O(N) map entries rather than
12209				// sum(1..N) = O(N^2). localNSMap stays a flat own-only record of the
12210				// prefixes declared at THIS element, so own-property enumeration
12211				// (endPrefixMapping below) still reports only local declarations.
12212				currentNSMap = Object.create(currentNSMap)
12213			}
12214			currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value;
12215			a.uri = NAMESPACE.XMLNS
12216			domBuilder.startPrefixMapping(nsPrefix, value)
12217		}
12218	}
12219	var i = el.length;
12220	while(i--){
12221		a = el[i];
12222		var prefix = a.prefix;
12223		if(prefix){//no prefix attribute has no namespace
12224			if(prefix === 'xml'){
12225				a.uri = NAMESPACE.XML;
12226			}if(prefix !== 'xmlns'){
12227				a.uri = currentNSMap[prefix || '']
12228
12229				//{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)}
12230			}
12231		}
12232	}
12233	var nsp = tagName.indexOf(':');
12234	if(nsp>0){
12235		prefix = el.prefix = tagName.slice(0,nsp);
12236		localName = el.localName = tagName.slice(nsp+1);
12237	}else{
12238		prefix = null;//important!!
12239		localName = el.localName = tagName;
12240	}
12241	//no prefix element has default namespace
12242	var ns = el.uri = currentNSMap[prefix || ''];
12243	domBuilder.startElement(ns,localName,tagName,el);
12244	//endPrefixMapping and startPrefixMapping have not any help for dom builder
12245	//localNSMap = null
12246	if(el.closed){
12247		domBuilder.endElement(ns,localName,tagName);
12248		if(localNSMap){
12249			for (prefix in localNSMap) {
12250				if (Object.prototype.hasOwnProperty.call(localNSMap, prefix)) {
12251					domBuilder.endPrefixMapping(prefix);
12252				}
12253			}
12254		}
12255	}else{
12256		el.currentNSMap = currentNSMap;
12257		el.localNSMap = localNSMap;
12258		//parseStack.push(el);
12259		return true;
12260	}
12261}
12262function parseHtmlSpecialContent(source,elStartEnd,tagName,entityReplacer,domBuilder){
12263	if(/^(?:script|textarea)$/i.test(tagName)){
12264		var elEndStart =  source.indexOf('</'+tagName+'>',elStartEnd);
12265		var text = source.substring(elStartEnd+1,elEndStart);
12266		if(/[&<]/.test(text)){
12267			if(/^script$/i.test(tagName)){
12268				//if(!/\]\]>/.test(text)){
12269					//lexHandler.startCDATA();
12270					domBuilder.characters(text,0,text.length);
12271					//lexHandler.endCDATA();
12272					return elEndStart;
12273				//}
12274			}//}else{//text area
12275				text = text.replace(/&#?\w+;/g,entityReplacer);
12276				domBuilder.characters(text,0,text.length);
12277				return elEndStart;
12278			//}
12279
12280		}
12281	}
12282	return elStartEnd+1;
12283}
12284function fixSelfClosed(source,elStartEnd,tagName,closeMap){
12285	//if(tagName in closeMap){
12286	var pos = closeMap[tagName];
12287	if(pos == null){
12288		//console.log(tagName)
12289		pos =  source.lastIndexOf('</'+tagName+'>')
12290		if(pos<elStartEnd){//忘记闭合
12291			pos = source.lastIndexOf('</'+tagName)
12292		}
12293		closeMap[tagName] =pos
12294	}
12295	return pos<elStartEnd;
12296	//}
12297}
12298
12299function _copy (source, target) {
12300	for (var n in source) {
12301		if (Object.prototype.hasOwnProperty.call(source, n)) {
12302			target[n] = source[n];
12303		}
12304	}
12305}
12306
12307function parseDCC(source,start,domBuilder,errorHandler){//sure start with '<!'
12308	var next= source.charAt(start+2)
12309	switch(next){
12310	case '-':
12311		if(source.charAt(start + 3) === '-'){
12312			var end = source.indexOf('-->',start+4);
12313			//append comment source.substring(4,end)//<!--
12314			if(end>start){
12315				domBuilder.comment(source,start+4,end-start-4);
12316				return end+3;
12317			}else{
12318				errorHandler.error("Unclosed comment");
12319				return -1;
12320			}
12321		}else{
12322			//error
12323			return -1;
12324		}
12325	default:
12326		if(source.substr(start+3,6) == 'CDATA['){
12327			var end = source.indexOf(']]>',start+9);
12328			domBuilder.startCDATA();
12329			domBuilder.characters(source,start+9,end-start-9);
12330			domBuilder.endCDATA()
12331			return end+3;
12332		}
12333		//<!DOCTYPE
12334		//startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId)
12335		var matchs = split(source,start);
12336		var len = matchs.length;
12337		if(len>1 && /!doctype/i.test(matchs[0][0])){
12338			var name = matchs[1][0];
12339			var pubid = false;
12340			var sysid = false;
12341			if(len>3){
12342				if(/^public$/i.test(matchs[2][0])){
12343					pubid = matchs[3][0];
12344					sysid = len>4 && matchs[4][0];
12345				}else if(/^system$/i.test(matchs[2][0])){
12346					sysid = matchs[3][0];
12347				}
12348			}
12349			var lastMatch = matchs[len-1]
12350			domBuilder.startDTD(name, pubid, sysid);
12351			domBuilder.endDTD();
12352
12353			return lastMatch.index+lastMatch[0].length
12354		}
12355	}
12356	return -1;
12357}
12358
12359
12360
12361function parseInstruction(source,start,domBuilder){
12362	var end = source.indexOf('?>',start);
12363	if(end){
12364		var match = source.substring(start,end).match(/^<\?(\S*)\s*([\s\S]*?)$/);
12365		if(match){
12366			var len = match[0].length;
12367			domBuilder.processingInstruction(match[1], match[2]) ;
12368			return end+2;
12369		}else{//error
12370			return -1;
12371		}
12372	}
12373	return -1;
12374}
12375
12376function ElementAttributes(){
12377	this.attributeNames = {}
12378}
12379ElementAttributes.prototype = {
12380	setTagName:function(tagName){
12381		if(!tagNamePattern.test(tagName)){
12382			throw new Error('invalid tagName:'+tagName)
12383		}
12384		this.tagName = tagName
12385	},
12386	addValue:function(qName, value, offset) {
12387		if(!tagNamePattern.test(qName)){
12388			throw new Error('invalid attribute:'+qName)
12389		}
12390		this.attributeNames[qName] = this.length;
12391		this[this.length++] = {qName:qName,value:value,offset:offset}
12392	},
12393	length:0,
12394	getLocalName:function(i){return this[i].localName},
12395	getLocator:function(i){return this[i].locator},
12396	getQName:function(i){return this[i].qName},
12397	getURI:function(i){return this[i].uri},
12398	getValue:function(i){return this[i].value}
12399//	,getIndex:function(uri, localName)){
12400//		if(localName){
12401//
12402//		}else{
12403//			var qName = uri
12404//		}
12405//	},
12406//	getValue:function(){return this.getValue(this.getIndex.apply(this,arguments))},
12407//	getType:function(uri,localName){}
12408//	getType:function(i){},
12409}
12410
12411
12412
12413function split(source,start){
12414	var match;
12415	var buf = [];
12416	var reg = /'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/g;
12417	reg.lastIndex = start;
12418	reg.exec(source);//skip <
12419	while(match = reg.exec(source)){
12420		buf.push(match);
12421		if(match[1])return buf;
12422	}
12423}
12424
12425exports.XMLReader = XMLReader;
12426exports.ParseError = ParseError;
12427
12428},{"./conventions":29}],35:[function(require,module,exports){
12429var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
12430
12431;(function (exports) {
12432	'use strict';
12433
12434  var Arr = (typeof Uint8Array !== 'undefined')
12435    ? Uint8Array
12436    : Array
12437
12438	var PLUS   = '+'.charCodeAt(0)
12439	var SLASH  = '/'.charCodeAt(0)
12440	var NUMBER = '0'.charCodeAt(0)
12441	var LOWER  = 'a'.charCodeAt(0)
12442	var UPPER  = 'A'.charCodeAt(0)
12443	var PLUS_URL_SAFE = '-'.charCodeAt(0)
12444	var SLASH_URL_SAFE = '_'.charCodeAt(0)
12445
12446	function decode (elt) {
12447		var code = elt.charCodeAt(0)
12448		if (code === PLUS ||
12449		    code === PLUS_URL_SAFE)
12450			return 62 // '+'
12451		if (code === SLASH ||
12452		    code === SLASH_URL_SAFE)
12453			return 63 // '/'
12454		if (code < NUMBER)
12455			return -1 //no match
12456		if (code < NUMBER + 10)
12457			return code - NUMBER + 26 + 26
12458		if (code < UPPER + 26)
12459			return code - UPPER
12460		if (code < LOWER + 26)
12461			return code - LOWER + 26
12462	}
12463
12464	function b64ToByteArray (b64) {
12465		var i, j, l, tmp, placeHolders, arr
12466
12467		if (b64.length % 4 > 0) {
12468			throw new Error('Invalid string. Length must be a multiple of 4')
12469		}
12470
12471		// the number of equal signs (place holders)
12472		// if there are two placeholders, than the two characters before it
12473		// represent one byte
12474		// if there is only one, then the three characters before it represent 2 bytes
12475		// this is just a cheap hack to not do indexOf twice
12476		var len = b64.length
12477		placeHolders = '=' === b64.charAt(len - 2) ? 2 : '=' === b64.charAt(len - 1) ? 1 : 0
12478
12479		// base64 is 4/3 + up to two characters of the original data
12480		arr = new Arr(b64.length * 3 / 4 - placeHolders)
12481
12482		// if there are placeholders, only get up to the last complete 4 chars
12483		l = placeHolders > 0 ? b64.length - 4 : b64.length
12484
12485		var L = 0
12486
12487		function push (v) {
12488			arr[L++] = v
12489		}
12490
12491		for (i = 0, j = 0; i < l; i += 4, j += 3) {
12492			tmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3))
12493			push((tmp & 0xFF0000) >> 16)
12494			push((tmp & 0xFF00) >> 8)
12495			push(tmp & 0xFF)
12496		}
12497
12498		if (placeHolders === 2) {
12499			tmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4)
12500			push(tmp & 0xFF)
12501		} else if (placeHolders === 1) {
12502			tmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2)
12503			push((tmp >> 8) & 0xFF)
12504			push(tmp & 0xFF)
12505		}
12506
12507		return arr
12508	}
12509
12510	function uint8ToBase64 (uint8) {
12511		var i,
12512			extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes
12513			output = "",
12514			temp, length
12515
12516		function encode (num) {
12517			return lookup.charAt(num)
12518		}
12519
12520		function tripletToBase64 (num) {
12521			return encode(num >> 18 & 0x3F) + encode(num >> 12 & 0x3F) + encode(num >> 6 & 0x3F) + encode(num & 0x3F)
12522		}
12523
12524		// go through the array every three bytes, we'll deal with trailing stuff later
12525		for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) {
12526			temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
12527			output += tripletToBase64(temp)
12528		}
12529
12530		// pad the end with zeros, but make sure to not forget the extra bytes
12531		switch (extraBytes) {
12532			case 1:
12533				temp = uint8[uint8.length - 1]
12534				output += encode(temp >> 2)
12535				output += encode((temp << 4) & 0x3F)
12536				output += '=='
12537				break
12538			case 2:
12539				temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1])
12540				output += encode(temp >> 10)
12541				output += encode((temp >> 4) & 0x3F)
12542				output += encode((temp << 2) & 0x3F)
12543				output += '='
12544				break
12545		}
12546
12547		return output
12548	}
12549
12550	exports.toByteArray = b64ToByteArray
12551	exports.fromByteArray = uint8ToBase64
12552}(typeof exports === 'undefined' ? (this.base64js = {}
12552) : exports))
12553
12554},{}],36:[function(require,module,exports){
12555
12556},{}],37:[function(require,module,exports){
12557(function (global,Buffer){
12558/*!
12559 * The buffer module from node.js, for the browser.
12560 *
12561 * @author   Feross Aboukhadijeh <http://feross.org>
12562 * @license  MIT
12563 */
12564/* eslint-disable no-proto */
12565
12566'use strict'
12567
12568var base64 = require('base64-js')
12569var ieee754 = require('ieee754')
12570var isArray = require('isarray')
12571
12572exports.Buffer = Buffer
12573exports.SlowBuffer = SlowBuffer
12574exports.INSPECT_MAX_BYTES = 50
12575Buffer.poolSize = 8192 // not used by this implementation
12576
12577var rootParent = {}
12578
12579/**
12580 * If `Buffer.TYPED_ARRAY_SUPPORT`:
12581 *   === true    Use Uint8Array implementation (fastest)
12582 *   === false   Use Object implementation (most compatible, even IE6)
12583 *
12584 * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,
12585 * Opera 11.6+, iOS 4.2+.
12586 *
12587 * Due to various browser bugs, sometimes the Object implementation will be used even
12588 * when the browser supports typed arrays.
12589 *
12590 * Note:
12591 *
12592 *   - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances,
12593 *     See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438.
12594 *
12595 *   - Safari 5-7 lacks support for changing the `Object.prototype.constructor` property
12596 *     on objects.
12597 *
12598 *   - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function.
12599 *
12600 *   - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of
12601 *     incorrect length in some situations.
12602
12603 * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they
12604 * get the Object implementation, which is slower but behaves correctly.
12605 */
12606Buffer.TYPED_ARRAY_SUPPORT = global.TYPED_ARRAY_SUPPORT !== undefined
12607  ? global.TYPED_ARRAY_SUPPORT
12608  : typedArraySupport()
12609
12610function typedArraySupport () {
12611  function Bar () {}
12612  try {
12613    var arr = new Uint8Array(1)
12614    arr.foo = function () { return 42 }
12615    arr.constructor = Bar
12616    return arr.foo() === 42 && // typed array instances can be augmented
12617        arr.constructor === Bar && // constructor can be set
12618        typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray`
12619        arr.subarray(1, 1).byteLength === 0 // ie10 has broken `subarray`
12620  } catch (e) {
12621    return false
12622  }
12623}
12624
12625function kMaxLength () {
12626  return Buffer.TYPED_ARRAY_SUPPORT
12627    ? 0x7fffffff
12628    : 0x3fffffff
12629}
12630
12631/**
12632 * Class: Buffer
12633 * =============
12634 *
12635 * The Buffer constructor returns instances of `Uint8Array` that are augmented
12636 * with function properties for all the node `Buffer` API functions. We use
12637 * `Uint8Array` so that square bracket notation works as expected -- it returns
12638 * a single octet.
12639 *
12640 * By augmenting the instances, we can avoid modifying the `Uint8Array`
12641 * prototype.
12642 */
12643function Buffer (arg) {
12644  if (!(this instanceof Buffer)) {
12645    // Avoid going through an ArgumentsAdaptorTrampoline in the common case.
12646    if (arguments.length > 1) return new Buffer(arg, arguments[1])
12647    return new Buffer(arg)
12648  }
12649
12650  if (!Buffer.TYPED_ARRAY_SUPPORT) {
12651    this.length = 0
12652    this.parent = undefined
12653  }
12654
12655  // Common case.
12656  if (typeof arg === 'number') {
12657    return fromNumber(this, arg)
12658  }
12659
12660  // Slightly less common case.
12661  if (typeof arg === 'string') {
12662    return fromString(this, arg, arguments.length > 1 ? arguments[1] : 'utf8')
12663  }
12664
12665  // Unusual.
12666  return fromObject(this, arg)
12667}
12668
12669function fromNumber (that, length) {
12670  that = allocate(that, length < 0 ? 0 : checked(length) | 0)
12671  if (!Buffer.TYPED_ARRAY_SUPPORT) {
12672    for (var i = 0; i < length; i++) {
12673      that[i] = 0
12674    }
12675  }
12676  return that
12677}
12678
12679function fromString (that, string, encoding) {
12680  if (typeof encoding !== 'string' || encoding === '') encoding = 'utf8'
12681
12682  // Assumption: byteLength() return value is always < kMaxLength.
12683  var length = byteLength(string, encoding) | 0
12684  that = allocate(that, length)
12685
12686  that.write(string, encoding)
12687  return that
12688}
12689
12690function fromObject (that, object) {
12691  if (Buffer.isBuffer(object)) return fromBuffer(that, object)
12692
12693  if (isArray(object)) return fromArray(that, object)
12694
12695  if (object == null) {
12696    throw new TypeError('must start with number, buffer, array or string')
12697  }
12698
12699  if (typeof ArrayBuffer !== 'undefined') {
12700    if (object.buffer instanceof ArrayBuffer) {
12701      return fromTypedArray(that, object)
12702    }
12703    if (object instanceof ArrayBuffer) {
12704      return fromArrayBuffer(that, object)
12705    }
12706  }
12707
12708  if (object.length) return fromArrayLike(that, object)
12709
12710  return fromJsonObject(that, object)
12711}
12712
12713function fromBuffer (that, buffer) {
12714  var length = checked(buffer.length) | 0
12715  that = allocate(that, length)
12716  buffer.copy(that, 0, 0, length)
12717  return that
12718}
12719
12720function fromArray (that, array) {
12721  var length = checked(array.length) | 0
12722  that = allocate(that, length)
12723  for (var i = 0; i < length; i += 1) {
12724    that[i] = array[i] & 255
12725  }
12726  return that
12727}
12728
12729// Duplicate of fromArray() to keep fromArray() monomorphic.
12730function fromTypedArray (that, array) {
12731  var length = checked(array.length) | 0
12732  that = allocate(that, length)
12733  // Truncating the elements is probably not what people expect from typed
12734  // arrays with BYTES_PER_ELEMENT > 1 but it's compatible with the behavior
12735  // of the old Buffer constructor.
12736  for (var i = 0; i < length; i += 1) {
12737    that[i] = array[i] & 255
12738  }
12739  return that
12740}
12741
12742function fromArrayBuffer (that, array) {
12743  if (Buffer.TYPED_ARRAY_SUPPORT) {
12744    // Return an augmented `Uint8Array` instance, for best performance
12745    array.byteLength
12746    that = Buffer._augment(new Uint8Array(array))
12747  } else {
12748    // Fallback: Return an object instance of the Buffer class
12749    that = fromTypedArray(that, new Uint8Array(array))
12750  }
12751  return that
12752}
12753
12754function fromArrayLike (that, array) {
12755  var length = checked(array.length) | 0
12756  that = allocate(that, length)
12757  for (var i = 0; i < length; i += 1) {
12758    that[i] = array[i] & 255
12759  }
12760  return that
12761}
12762
12763// Deserialize { type: 'Buffer', data: [1,2,3,...] } into a Buffer object.
12764// Returns a zero-length buffer for inputs that don't conform to the spec.
12765function fromJsonObject (that, object) {
12766  var array
12767  var length = 0
12768
12769  if (object.type === 'Buffer' && isArray(object.data)) {
12770    array = object.data
12771    length = checked(array.length) | 0
12772  }
12773  that = allocate(that, length)
12774
12775  for (var i = 0; i < length; i += 1) {
12776    that[i] = array[i] & 255
12777  }
12778  return that
12779}
12780
12781if (Buffer.TYPED_ARRAY_SUPPORT) {
12782  Buffer.prototype.__proto__ = Uint8Array.prototype
12783  Buffer.__proto__ = Uint8Array
12784} else {
12785  // pre-set for values that may exist in the future
12786  Buffer.prototype.length = undefined
12787  Buffer.prototype.parent = undefined
12788}
12789
12790function allocate (that, length) {
12791  if (Buffer.TYPED_ARRAY_SUPPORT) {
12792    // Return an augmented `Uint8Array` instance, for best performance
12793    that = Buffer._augment(new Uint8Array(length))
12794    that.__proto__ = Buffer.prototype
12795  } else {
12796    // Fallback: Return an object instance of the Buffer class
12797    that.length = length
12798    that._isBuffer = true
12799  }
12800
12801  var fromPool = length !== 0 && length <= Buffer.poolSize >>> 1
12802  if (fromPool) that.parent = rootParent
12803
12804  return that
12805}
12806
12807function checked (length) {
12808  // Note: cannot use `length < kMaxLength` here because that fails when
12809  // length is NaN (which is otherwise coerced to zero.)
12810  if (length >= kMaxLength()) {
12811    throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
12812                         'size: 0x' + kMaxLength().toString(16) + ' bytes')
12813  }
12814  return length | 0
12815}
12816
12817function SlowBuffer (subject, encoding) {
12818  if (!(this instanceof SlowBuffer)) return new SlowBuffer(subject, encoding)
12819
12820  var buf = new Buffer(subject, encoding)
12821  delete buf.parent
12822  return buf
12823}
12824
12825Buffer.isBuffer = function isBuffer (b) {
12826  return !!(b != null && b._isBuffer)
12827}
12828
12829Buffer.compare = function compare (a, b) {
12830  if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
12831    throw new TypeError('Arguments must be Buffers')
12832  }
12833
12834  if (a === b) return 0
12835
12836  var x = a.length
12837  var y = b.length
12838
12839  var i = 0
12840  var len = Math.min(x, y)
12841  while (i < len) {
12842    if (a[i] !== b[i]) break
12843
12844    ++i
12845  }
12846
12847  if (i !== len) {
12848    x = a[i]
12849    y = b[i]
12850  }
12851
12852  if (x < y) return -1
12853  if (y < x) return 1
12854  return 0
12855}
12856
12857Buffer.isEncoding = function isEncoding (encoding) {
12858  switch (String(encoding).toLowerCase()) {
12859    case 'hex':
12860    case 'utf8':
12861    case 'utf-8':
12862    case 'ascii':
12863    case 'binary':
12864    case 'base64':
12865    case 'raw':
12866    case 'ucs2':
12867    case 'ucs-2':
12868    case 'utf16le':
12869    case 'utf-16le':
12870      return true
12871    default:
12872      return false
12873  }
12874}
12875
12876Buffer.concat = function concat (list, length) {
12877  if (!isArray(list)) throw new TypeError('list argument must be an Array of Buffers.')
12878
12879  if (list.length === 0) {
12880    return new Buffer(0)
12881  }
12882
12883  var i
12884  if (length === undefined) {
12885    length = 0
12886    for (i = 0; i < list.length; i++) {
12887      length += list[i].length
12888    }
12889  }
12890
12891  var buf = new Buffer(length)
12892  var pos = 0
12893  for (i = 0; i < list.length; i++) {
12894    var item = list[i]
12895    item.copy(buf, pos)
12896    pos += item.length
12897  }
12898  return buf
12899}
12900
12901function byteLength (string, encoding) {
12902  if (typeof string !== 'string') string = '' + string
12903
12904  var len = string.length
12905  if (len === 0) return 0
12906
12907  // Use a for loop to avoid recursion
12908  var loweredCase = false
12909  for (;;) {
12910    switch (encoding) {
12911      case 'ascii':
12912      case 'binary':
12913      // Deprecated
12914      case 'raw':
12915      case 'raws':
12916        return len
12917      case 'utf8':
12918      case 'utf-8':
12919        return utf8ToBytes(string).length
12920      case 'ucs2':
12921      case 'ucs-2':
12922      case 'utf16le':
12923      case 'utf-16le':
12924        return len * 2
12925      case 'hex':
12926        return len >>> 1
12927      case 'base64':
12928        return base64ToBytes(string).length
12929      default:
12930        if (loweredCase) return utf8ToBytes(string).length // assume utf8
12931        encoding = ('' + encoding).toLowerCase()
12932        loweredCase = true
12933    }
12934  }
12935}
12936Buffer.byteLength = byteLength
12937
12938function slowToString (encoding, start, end) {
12939  var loweredCase = false
12940
12941  start = start | 0
12942  end = end === undefined || end === Infinity ? this.length : end | 0
12943
12944  if (!encoding) encoding = 'utf8'
12945  if (start < 0) start = 0
12946  if (end > this.length) end = this.length
12947  if (end <= start) return ''
12948
12949  while (true) {
12950    switch (encoding) {
12951      case 'hex':
12952        return hexSlice(this, start, end)
12953
12954      case 'utf8':
12955      case 'utf-8':
12956        return utf8Slice(this, start, end)
12957
12958      case 'ascii':
12959        return asciiSlice(this, start, end)
12960
12961      case 'binary':
12962        return binarySlice(this, start, end)
12963
12964      case 'base64':
12965        return base64Slice(this, start, end)
12966
12967      case 'ucs2':
12968      case 'ucs-2':
12969      case 'utf16le':
12970      case 'utf-16le':
12971        return utf16leSlice(this, start, end)
12972
12973      default:
12974        if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
12975        encoding = (encoding + '').toLowerCase()
12976        loweredCase = true
12977    }
12978  }
12979}
12980
12981Buffer.prototype.toString = function toString () {
12982  var length = this.length | 0
12983  if (length === 0) return ''
12984  if (arguments.length === 0) return utf8Slice(this, 0, length)
12985  return slowToString.apply(this, arguments)
12986}
12987
12988Buffer.prototype.equals = function equals (b) {
12989  if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
12990  if (this === b) return true
12991  return Buffer.compare(this, b) === 0
12992}
12993
12994Buffer.prototype.inspect = function inspect () {
12995  var str = ''
12996  var max = exports.INSPECT_MAX_BYTES
12997  if (this.length > 0) {
12998    str = this.toString('hex', 0, max).match(/.{2}/g).join(' ')
12999    if (this.length > max) str += ' ... '
13000  }
13001  return '<Buffer ' + str + '>'
13002}
13003
13004Buffer.prototype.compare = function compare (b) {
13005  if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
13006  if (this === b) return 0
13007  return Buffer.compare(this, b)
13008}
13009
13010Buffer.prototype.indexOf = function indexOf (val, byteOffset) {
13011  if (byteOffset > 0x7fffffff) byteOffset = 0x7fffffff
13012  else if (byteOffset < -0x80000000) byteOffset = -0x80000000
13013  byteOffset >>= 0
13014
13015  if (this.length === 0) return -1
13016  if (byteOffset >= this.length) return -1
13017
13018  // Negative offsets start from the end of the buffer
13019  if (byteOffset < 0) byteOffset = Math.max(this.length + byteOffset, 0)
13020
13021  if (typeof val === 'string') {
13022    if (val.length === 0) return -1 // special case: looking for empty string always fails
13023    return String.prototype.indexOf.call(this, val, byteOffset)
13024  }
13025  if (Buffer.isBuffer(val)) {
13026    return arrayIndexOf(this, val, byteOffset)
13027  }
13028  if (typeof val === 'number') {
13029    if (Buffer.TYPED_ARRAY_SUPPORT && Uint8Array.prototype.indexOf === 'function') {
13030      return Uint8Array.prototype.indexOf.call(this, val, byteOffset)
13031    }
13032    return arrayIndexOf(this, [ val ], byteOffset)
13033  }
13034
13035  function arrayIndexOf (arr, val, byteOffset) {
13036    var foundIndex = -1
13037    for (var i = 0; byteOffset + i < arr.length; i++) {
13038      if (arr[byteOffset + i] === val[foundIndex === -1 ? 0 : i - foundIndex]) {
13039        if (foundIndex === -1) foundIndex = i
13040        if (i - foundIndex + 1 === val.length) return byteOffset + foundIndex
13041      } else {
13042        foundIndex = -1
13043      }
13044    }
13045    return -1
13046  }
13047
13048  throw new TypeError('val must be string, number or Buffer')
13049}
13050
13051// `get` is deprecated
13052Buffer.prototype.get = function get (offset) {
13053  console.log('.get() is deprecated. Access using array indexes instead.')
13054  return this.readUInt8(offset)
13055}
13056
13057// `set` is deprecated
13058Buffer.prototype.set = function set (v, offset) {
13059  console.log('.set() is deprecated. Access using array indexes instead.')
13060  return this.writeUInt8(v, offset)
13061}
13062
13063function hexWrite (buf, string, offset, length) {
13064  offset = Number(offset) || 0
13065  var remaining = buf.length - offset
13066  if (!length) {
13067    length = remaining
13068  } else {
13069    length = Number(length)
13070    if (length > remaining) {
13071      length = remaining
13072    }
13073  }
13074
13075  // must be an even number of digits
13076  var strLen = string.length
13077  if (strLen % 2 !== 0) throw new Error('Invalid hex string')
13078
13079  if (length > strLen / 2) {
13080    length = strLen / 2
13081  }
13082  for (var i = 0; i < length; i++) {
13083    var parsed = parseInt(string.substr(i * 2, 2), 16)
13084    if (isNaN(parsed)) throw new Error('Invalid hex string')
13085    buf[offset + i] = parsed
13086  }
13087  return i
13088}
13089
13090function utf8Write (buf, string, offset, length) {
13091  return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)
13092}
13093
13094function asciiWrite (buf, string, offset, length) {
13095  return blitBuffer(asciiToBytes(string), buf, offset, length)
13096}
13097
13098function binaryWrite (buf, string, offset, length) {
13099  return asciiWrite(buf, string, offset, length)
13100}
13101
13102function base64Write (buf, string, offset, length) {
13103  return blitBuffer(base64ToBytes(string), buf, offset, length)
13104}
13105
13106function ucs2Write (buf, string, offset, length) {
13107  return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)
13108}
13109
13110Buffer.prototype.write = function write (string, offset, length, encoding) {
13111  // Buffer#write(string)
13112  if (offset === undefined) {
13113    encoding = 'utf8'
13114    length = this.length
13115    offset = 0
13116  // Buffer#write(string, encoding)
13117  } else if (length === undefined && typeof offset === 'string') {
13118    encoding = offset
13119    length = this.length
13120    offset = 0
13121  // Buffer#write(string, offset[, length][, encoding])
13122  } else if (isFinite(offset)) {
13123    offset = offset | 0
13124    if (isFinite(length)) {
13125      length = length | 0
13126      if (encoding === undefined) encoding = 'utf8'
13127    } else {
13128      encoding = length
13129      length = undefined
13130    }
13131  // legacy write(string, encoding, offset, length) - remove in v0.13
13132  } else {
13133    var swap = encoding
13134    encoding = offset
13135    offset = length | 0
13136    length = swap
13137  }
13138
13139  var remaining = this.length - offset
13140  if (length === undefined || length > remaining) length = remaining
13141
13142  if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {
13143    throw new RangeError('attempt to write outside buffer bounds')
13144  }
13145
13146  if (!encoding) encoding = 'utf8'
13147
13148  var loweredCase = false
13149  for (;;) {
13150    switch (encoding) {
13151      case 'hex':
13152        return hexWrite(this, string, offset, length)
13153
13154      case 'utf8':
13155      case 'utf-8':
13156        return utf8Write(this, string, offset, length)
13157
13158      case 'ascii':
13159        return asciiWrite(this, string, offset, length)
13160
13161      case 'binary':
13162        return binaryWrite(this, string, offset, length)
13163
13164      case 'base64':
13165        // Warning: maxLength not taken into account in base64Write
13166        return base64Write(this, string, offset, length)
13167
13168      case 'ucs2':
13169      case 'ucs-2':
13170      case 'utf16le':
13171      case 'utf-16le':
13172        return ucs2Write(this, string, offset, length)
13173
13174      default:
13175        if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
13176        encoding = ('' + encoding).toLowerCase()
13177        loweredCase = true
13178    }
13179  }
13180}
13181
13182Buffer.prototype.toJSON = function toJSON () {
13183  return {
13184    type: 'Buffer',
13185    data: Array.prototype.slice.call(this._arr || this, 0)
13186  }
13187}
13188
13189function base64Slice (buf, start, end) {
13190  if (start === 0 && end === buf.length) {
13191    return base64.fromByteArray(buf)
13192  } else {
13193    return base64.fromByteArray(buf.slice(start, end))
13194  }
13195}
13196
13197function utf8Slice (buf, start, end) {
13198  end = Math.min(buf.length, end)
13199  var res = []
13200
13201  var i = start
13202  while (i < end) {
13203    var firstByte = buf[i]
13204    var codePoint = null
13205    var bytesPerSequence = (firstByte > 0xEF) ? 4
13206      : (firstByte > 0xDF) ? 3
13207      : (firstByte > 0xBF) ? 2
13208      : 1
13209
13210    if (i + bytesPerSequence <= end) {
13211      var secondByte, thirdByte, fourthByte, tempCodePoint
13212
13213      switch (bytesPerSequence) {
13214        case 1:
13215          if (firstByte < 0x80) {
13216            codePoint = firstByte
13217          }
13218          break
13219        case 2:
13220          secondByte = buf[i + 1]
13221          if ((secondByte & 0xC0) === 0x80) {
13222            tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)
13223            if (tempCodePoint > 0x7F) {
13224              codePoint = tempCodePoint
13225            }
13226          }
13227          break
13228        case 3:
13229          secondByte = buf[i + 1]
13230          thirdByte = buf[i + 2]
13231          if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {
13232            tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)
13233            if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {
13234              codePoint = tempCodePoint
13235            }
13236          }
13237          break
13238        case 4:
13239          secondByte = buf[i + 1]
13240          thirdByte = buf[i + 2]
13241          fourthByte = buf[i + 3]
13242          if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {
13243            tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)
13244            if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {
13245              codePoint = tempCodePoint
13246            }
13247          }
13248      }
13249    }
13250
13251    if (codePoint === null) {
13252      // we did not generate a valid codePoint so insert a
13253      // replacement char (U+FFFD) and advance only 1 byte
13254      codePoint = 0xFFFD
13255      bytesPerSequence = 1
13256    } else if (codePoint > 0xFFFF) {
13257      // encode to utf16 (surrogate pair dance)
13258      codePoint -= 0x10000
13259      res.push(codePoint >>>
vendor: 2,464 bytes, lines 13259-13374
13259 10 & 0x3FF | 0xD800)
13260      codePoint = 0xDC00 | codePoint & 0x3FF
13261    }
13262
13263    res.push(codePoint)
13264    i += bytesPerSequence
13265  }
13266
13267  return decodeCodePointsArray(res)
13268}
13269
13270// Based on http://stackoverflow.com/a/22747272/680742, the browser with
13271// the lowest limit is Chrome, with 0x10000 args.
13272// We go 1 magnitude less, for safety
13273var MAX_ARGUMENTS_LENGTH = 0x1000
13274
13275function decodeCodePointsArray (codePoints) {
13276  var len = codePoints.length
13277  if (len <= MAX_ARGUMENTS_LENGTH) {
13278    return String.fromCharCode.apply(String, codePoints) // avoid extra slice()
13279  }
13280
13281  // Decode in chunks to avoid "call stack size exceeded".
13282  var res = ''
13283  var i = 0
13284  while (i < len) {
13285    res += String.fromCharCode.apply(
13286      String,
13287      codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)
13288    )
13289  }
13290  return res
13291}
13292
13293function asciiSlice (buf, start, end) {
13294  var ret = ''
13295  end = Math.min(buf.length, end)
13296
13297  for (var i = start; i < end; i++) {
13298    ret += String.fromCharCode(buf[i] & 0x7F)
13299  }
13300  return ret
13301}
13302
13303function binarySlice (buf, start, end) {
13304  var ret = ''
13305  end = Math.min(buf.length, end)
13306
13307  for (var i = start; i < end; i++) {
13308    ret += String.fromCharCode(buf[i])
13309  }
13310  return ret
13311}
13312
13313function hexSlice (buf, start, end) {
13314  var len = buf.length
13315
13316  if (!start || start < 0) start = 0
13317  if (!end || end < 0 || end > len) end = len
13318
13319  var out = ''
13320  for (var i = start; i < end; i++) {
13321    out += toHex(buf[i])
13322  }
13323  return out
13324}
13325
13326function utf16leSlice (buf, start, end) {
13327  var bytes = buf.slice(start, end)
13328  var res = ''
13329  for (var i = 0; i < bytes.length; i += 2) {
13330    res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256)
13331  }
13332  return res
13333}
13334
13335Buffer.prototype.slice = function slice (start, end) {
13336  var len = this.length
13337  start = ~~start
13338  end = end === undefined ? len : ~~end
13339
13340  if (start < 0) {
13341    start += len
13342    if (start < 0) start = 0
13343  } else if (start > len) {
13344    start = len
13345  }
13346
13347  if (end < 0) {
13348    end += len
13349    if (end < 0) end = 0
13350  } else if (end > len) {
13351    end = len
13352  }
13353
13354  if (end < start) end = start
13355
13356  var newBuf
13357  if (Buffer.TYPED_ARRAY_SUPPORT) {
13358    newBuf = Buffer._augment(this.subarray(start, end))
13359  } else {
13360    var sliceLen = end - start
13361    newBuf = new Buffer(sliceLen, undefined)
13362    for (var i = 0; i < sliceLen; i++) {
13363      newBuf[i] = this[i + start]
13364    }
13365  }
13366
13367  if (newBuf.length) newBuf.parent = this.parent || this
13368
13369  return newBuf
13370}
13371
13372/*
13373 * Need to make sure that buffer isn't trying to write out of bounds.
13374 */
vendor: 9,737 bytes, lines 13375-13703
13375function checkOffset (offset, ext, length) {
13376  if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')
13377  if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')
13378}
13379
13380Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {
13381  offset = offset | 0
13382  byteLength = byteLength | 0
13383  if (!noAssert) checkOffset(offset, byteLength, this.length)
13384
13385  var val = this[offset]
13386  var mul = 1
13387  var i = 0
13388  while (++i < byteLength && (mul *= 0x100)) {
13389    val += this[offset + i] * mul
13390  }
13391
13392  return val
13393}
13394
13395Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {
13396  offset = offset | 0
13397  byteLength = byteLength | 0
13398  if (!noAssert) {
13399    checkOffset(offset, byteLength, this.length)
13400  }
13401
13402  var val = this[offset + --byteLength]
13403  var mul = 1
13404  while (byteLength > 0 && (mul *= 0x100)) {
13405    val += this[offset + --byteLength] * mul
13406  }
13407
13408  return val
13409}
13410
13411Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {
13412  if (!noAssert) checkOffset(offset, 1, this.length)
13413  return this[offset]
13414}
13415
13416Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {
13417  if (!noAssert) checkOffset(offset, 2, this.length)
13418  return this[offset] | (this[offset + 1] << 8)
13419}
13420
13421Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {
13422  if (!noAssert) checkOffset(offset, 2, this.length)
13423  return (this[offset] << 8) | this[offset + 1]
13424}
13425
13426Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {
13427  if (!noAssert) checkOffset(offset, 4, this.length)
13428
13429  return ((this[offset]) |
13430      (this[offset + 1] << 8) |
13431      (this[offset + 2] << 16)) +
13432      (this[offset + 3] * 0x1000000)
13433}
13434
13435Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {
13436  if (!noAssert) checkOffset(offset, 4, this.length)
13437
13438  return (this[offset] * 0x1000000) +
13439    ((this[offset + 1] << 16) |
13440    (this[offset + 2] << 8) |
13441    this[offset + 3])
13442}
13443
13444Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {
13445  offset = offset | 0
13446  byteLength = byteLength | 0
13447  if (!noAssert) checkOffset(offset, byteLength, this.length)
13448
13449  var val = this[offset]
13450  var mul = 1
13451  var i = 0
13452  while (++i < byteLength && (mul *= 0x100)) {
13453    val += this[offset + i] * mul
13454  }
13455  mul *= 0x80
13456
13457  if (val >= mul) val -= Math.pow(2, 8 * byteLength)
13458
13459  return val
13460}
13461
13462Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {
13463  offset = offset | 0
13464  byteLength = byteLength | 0
13465  if (!noAssert) checkOffset(offset, byteLength, this.length)
13466
13467  var i = byteLength
13468  var mul = 1
13469  var val = this[offset + --i]
13470  while (i > 0 && (mul *= 0x100)) {
13471    val += this[offset + --i] * mul
13472  }
13473  mul *= 0x80
13474
13475  if (val >= mul) val -= Math.pow(2, 8 * byteLength)
13476
13477  return val
13478}
13479
13480Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) {
13481  if (!noAssert) checkOffset(offset, 1, this.length)
13482  if (!(this[offset] & 0x80)) return (this[offset])
13483  return ((0xff - this[offset] + 1) * -1)
13484}
13485
13486Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {
13487  if (!noAssert) checkOffset(offset, 2, this.length)
13488  var val = this[offset] | (this[offset + 1] << 8)
13489  return (val & 0x8000) ? val | 0xFFFF0000 : val
13490}
13491
13492Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {
13493  if (!noAssert) checkOffset(offset, 2, this.length)
13494  var val = this[offset + 1] | (this[offset] << 8)
13495  return (val & 0x8000) ? val | 0xFFFF0000 : val
13496}
13497
13498Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {
13499  if (!noAssert) checkOffset(offset, 4, this.length)
13500
13501  return (this[offset]) |
13502    (this[offset + 1] << 8) |
13503    (this[offset + 2] << 16) |
13504    (this[offset + 3] << 24)
13505}
13506
13507Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {
13508  if (!noAssert) checkOffset(offset, 4, this.length)
13509
13510  return (this[offset] << 24) |
13511    (this[offset + 1] << 16) |
13512    (this[offset + 2] << 8) |
13513    (this[offset + 3])
13514}
13515
13516Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {
13517  if (!noAssert) checkOffset(offset, 4, this.length)
13518  return ieee754.read(this, offset, true, 23, 4)
13519}
13520
13521Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {
13522  if (!noAssert) checkOffset(offset, 4, this.length)
13523  return ieee754.read(this, offset, false, 23, 4)
13524}
13525
13526Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {
13527  if (!noAssert) checkOffset(offset, 8, this.length)
13528  return ieee754.read(this, offset, true, 52, 8)
13529}
13530
13531Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {
13532  if (!noAssert) checkOffset(offset, 8, this.length)
13533  return ieee754.read(this, offset, false, 52, 8)
13534}
13535
13536function checkInt (buf, value, offset, ext, max, min) {
13537  if (!Buffer.isBuffer(buf)) throw new TypeError('buffer must be a Buffer instance')
13538  if (value > max || value < min) throw new RangeError('value is out of bounds')
13539  if (offset + ext > buf.length) throw new RangeError('index out of range')
13540}
13541
13542Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {
13543  value = +value
13544  offset = offset | 0
13545  byteLength = byteLength | 0
13546  if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0)
13547
13548  var mul = 1
13549  var i = 0
13550  this[offset] = value & 0xFF
13551  while (++i < byteLength && (mul *= 0x100)) {
13552    this[offset + i] = (value / mul) & 0xFF
13553  }
13554
13555  return offset + byteLength
13556}
13557
13558Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {
13559  value = +value
13560  offset = offset | 0
13561  byteLength = byteLength | 0
13562  if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0)
13563
13564  var i = byteLength - 1
13565  var mul = 1
13566  this[offset + i] = value & 0xFF
13567  while (--i >= 0 && (mul *= 0x100)) {
13568    this[offset + i] = (value / mul) & 0xFF
13569  }
13570
13571  return offset + byteLength
13572}
13573
13574Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {
13575  value = +value
13576  offset = offset | 0
13577  if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)
13578  if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
13579  this[offset] = (value & 0xff)
13580  return offset + 1
13581}
13582
13583function objectWriteUInt16 (buf, value, offset, littleEndian) {
13584  if (value < 0) value = 0xffff + value + 1
13585  for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; i++) {
13586    buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>>
13587      (littleEndian ? i : 1 - i) * 8
13588  }
13589}
13590
13591Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {
13592  value = +value
13593  offset = offset | 0
13594  if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
13595  if (Buffer.TYPED_ARRAY_SUPPORT) {
13596    this[offset] = (value & 0xff)
13597    this[offset + 1] = (value >>> 8)
13598  } else {
13599    objectWriteUInt16(this, value, offset, true)
13600  }
13601  return offset + 2
13602}
13603
13604Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {
13605  value = +value
13606  offset = offset | 0
13607  if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
13608  if (Buffer.TYPED_ARRAY_SUPPORT) {
13609    this[offset] = (value >>> 8)
13610    this[offset + 1] = (value & 0xff)
13611  } else {
13612    objectWriteUInt16(this, value, offset, false)
13613  }
13614  return offset + 2
13615}
13616
13617function objectWriteUInt32 (buf, value, offset, littleEndian) {
13618  if (value < 0) value = 0xffffffff + value + 1
13619  for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; i++) {
13620    buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff
13621  }
13622}
13623
13624Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {
13625  value = +value
13626  offset = offset | 0
13627  if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
13628  if (Buffer.TYPED_ARRAY_SUPPORT) {
13629    this[offset + 3] = (value >>> 24)
13630    this[offset + 2] = (value >>> 16)
13631    this[offset + 1] = (value >>> 8)
13632    this[offset] = (value & 0xff)
13633  } else {
13634    objectWriteUInt32(this, value, offset, true)
13635  }
13636  return offset + 4
13637}
13638
13639Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {
13640  value = +value
13641  offset = offset | 0
13642  if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
13643  if (Buffer.TYPED_ARRAY_SUPPORT) {
13644    this[offset] = (value >>> 24)
13645    this[offset + 1] = (value >>> 16)
13646    this[offset + 2] = (value >>> 8)
13647    this[offset + 3] = (value & 0xff)
13648  } else {
13649    objectWriteUInt32(this, value, offset, false)
13650  }
13651  return offset + 4
13652}
13653
13654Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {
13655  value = +value
13656  offset = offset | 0
13657  if (!noAssert) {
13658    var limit = Math.pow(2, 8 * byteLength - 1)
13659
13660    checkInt(this, value, offset, byteLength, limit - 1, -limit)
13661  }
13662
13663  var i = 0
13664  var mul = 1
13665  var sub = value < 0 ? 1 : 0
13666  this[offset] = value & 0xFF
13667  while (++i < byteLength && (mul *= 0x100)) {
13668    this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
13669  }
13670
13671  return offset + byteLength
13672}
13673
13674Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {
13675  value = +value
13676  offset = offset | 0
13677  if (!noAssert) {
13678    var limit = Math.pow(2, 8 * byteLength - 1)
13679
13680    checkInt(this, value, offset, byteLength, limit - 1, -limit)
13681  }
13682
13683  var i = byteLength - 1
13684  var mul = 1
13685  var sub = value < 0 ? 1 : 0
13686  this[offset + i] = value & 0xFF
13687  while (--i >= 0 && (mul *= 0x100)) {
13688    this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
13689  }
13690
13691  return offset + byteLength
13692}
13693
13694Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {
13695  value = +value
13696  offset = offset | 0
13697  if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)
13698  if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
13699  if (value < 0) value = 0xff + value + 1
13700  this[offset] = (value & 0xff)
13701  return offset + 1
13702}
13703
vendor: 4,133 bytes, lines 13704-13827
13704Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {
13705  value = +value
13706  offset = offset | 0
13707  if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
13708  if (Buffer.TYPED_ARRAY_SUPPORT) {
13709    this[offset] = (value & 0xff)
13710    this[offset + 1] = (value >>> 8)
13711  } else {
13712    objectWriteUInt16(this, value, offset, true)
13713  }
13714  return offset + 2
13715}
13716
13717Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {
13718  value = +value
13719  offset = offset | 0
13720  if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
13721  if (Buffer.TYPED_ARRAY_SUPPORT) {
13722    this[offset] = (value >>> 8)
13723    this[offset + 1] = (value & 0xff)
13724  } else {
13725    objectWriteUInt16(this, value, offset, false)
13726  }
13727  return offset + 2
13728}
13729
13730Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {
13731  value = +value
13732  offset = offset | 0
13733  if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
13734  if (Buffer.TYPED_ARRAY_SUPPORT) {
13735    this[offset] = (value & 0xff)
13736    this[offset + 1] = (value >>> 8)
13737    this[offset + 2] = (value >>> 16)
13738    this[offset + 3] = (value >>> 24)
13739  } else {
13740    objectWriteUInt32(this, value, offset, true)
13741  }
13742  return offset + 4
13743}
13744
13745Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) {
13746  value = +value
13747  offset = offset | 0
13748  if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
13749  if (value < 0) value = 0xffffffff + value + 1
13750  if (Buffer.TYPED_ARRAY_SUPPORT) {
13751    this[offset] = (value >>> 24)
13752    this[offset + 1] = (value >>> 16)
13753    this[offset + 2] = (value >>> 8)
13754    this[offset + 3] = (value & 0xff)
13755  } else {
13756    objectWriteUInt32(this, value, offset, false)
13757  }
13758  return offset + 4
13759}
13760
13761function checkIEEE754 (buf, value, offset, ext, max, min) {
13762  if (value > max || value < min) throw new RangeError('value is out of bounds')
13763  if (offset + ext > buf.length) throw new RangeError('index out of range')
13764  if (offset < 0) throw new RangeError('index out of range')
13765}
13766
13767function writeFloat (buf, value, offset, littleEndian, noAssert) {
13768  if (!noAssert) {
13769    checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)
13770  }
13771  ieee754.write(buf, value, offset, littleEndian, 23, 4)
13772  return offset + 4
13773}
13774
13775Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) {
13776  return writeFloat(this, value, offset, true, noAssert)
13777}
13778
13779Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) {
13780  return writeFloat(this, value, offset, false, noAssert)
13781}
13782
13783function writeDouble (buf, value, offset, littleEndian, noAssert) {
13784  if (!noAssert) {
13785    checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)
13786  }
13787  ieee754.write(buf, value, offset, littleEndian, 52, 8)
13788  return offset + 8
13789}
13790
13791Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) {
13792  return writeDouble(this, value, offset, true, noAssert)
13793}
13794
13795Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) {
13796  return writeDouble(this, value, offset, false, noAssert)
13797}
13798
13799// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)
13800Buffer.prototype.copy = function copy (target, targetStart, start, end) {
13801  if (!start) start = 0
13802  if (!end && end !== 0) end = this.length
13803  if (targetStart >= target.length) targetStart = target.length
13804  if (!targetStart) targetStart = 0
13805  if (end > 0 && end < start) end = start
13806
13807  // Copy 0 bytes; we're done
13808  if (end === start) return 0
13809  if (target.length === 0 || this.length === 0) return 0
13810
13811  // Fatal error conditions
13812  if (targetStart < 0) {
13813    throw new RangeError('targetStart out of bounds')
13814  }
13815  if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds')
13816  if (end < 0) throw new RangeError('sourceEnd out of bounds')
13817
13818  // Are we oob?
13819  if (end > this.length) end = this.length
13820  if (target.length - targetStart < end - start) {
13821    end = target.length - targetStart + start
13822  }
13823
13824  var len = end - start
13825  var i
13826
13827  if (this === target && start < targetStart && targetStart < end) {
13828    // descending copy from end
13829    for (i = len - 1; i >= 0; i--) {
13830      target[i + targetStart] = this[i + start]
13831    }
13832  } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) {
13833    // ascending copy from start
13834    for (i = 0; i < len; i++) {
13835      target[i + targetStart] = this[i + start]
13836    }
13837  } else {
13838    target._set(this.subarray(start, start + len), targetStart)
13839  }
13840
13841  return len
13842}
13843
13844// fill(value, start=0, end=buffer.length)
13845Buffer.prototype.fill = function fill (value, start, end) {
13846  if (!value) value = 0
13847  if (!start) start = 0
13848  if (!end) end = this.length
13849
13850  if (end < start) throw new RangeError('end < start')
13851
13852  // Fill 0 bytes; we're done
13853  if (end === start) return
13854  if (this.length === 0) return
13855
13856  if (start < 0 || start >= this.length) throw new RangeError('start out of bounds')
13857  if (end < 0 || end > this.length) throw new RangeError('end out of bounds')
13858
13859  var i
13860  if (typeof value === 'number') {
13861    for (i = start; i < end; i++) {
13862      this[i] = value
13863    }
13864  } else {
13865    var bytes = utf8ToBytes(value.toString())
13866    var len = bytes.length
13867    for (i = start; i < end; i++) {
13868      this[i] = bytes[i % len]
13869    }
13870  }
13871
13872  return this
13873}
13874
13875/**
13876 * Creates a new `ArrayBuffer` with the *copied* memory of the buffer instance.
13877 * Added in Node 0.12. Only available in browsers that support ArrayBuffer.
13878 */
13879Buffer.prototype.toArrayBuffer = function toArrayBuffer () {
13880  if (typeof Uint8Array !== 'undefined') {
13881    if (Buffer.TYPED_ARRAY_SUPPORT) {
13882      return (new Buffer(this)).buffer
13883    } else {
13884      var buf = new Uint8Array(this.length)
13885      for (var i = 0, len = buf.length; i < len; i += 1) {
13886        buf[i] = this[i]
13887      }
13888      return buf.buffer
13889    }
13890  } else {
13891    throw new TypeError('Buffer.toArrayBuffer not supported in this browser')
13892  }
13893}
13894
13895// HELPER FUNCTIONS
13896// ================
13897
13898var BP = Buffer.prototype
13899
13900/**
13901 * Augment a Uint8Array *instance* (not the Uint8Array class!) with Buffer methods
13902 */
13903Buffer._augment = function _augment (arr) {
13904  arr.constructor = Buffer
13905  arr._isBuffer = true
13906
13907  // save reference to original Uint8Array set method before overwriting
13908  arr._set = arr.set
13909
13910  // deprecated
13911  arr.get = BP.get
13912  arr.set = BP.set
13913
13914  arr.write = BP.write
13915  arr.toString = BP.toString
13916  arr.toLocaleString = BP.toString
13917  arr.toJSON = BP.toJSON
13918  arr.equals = BP.equals
13919  arr.compare = BP.compare
13920  arr.indexOf = BP.indexOf
13921  arr.copy = BP.copy
13922  arr.slice = BP.slice
13923  arr.readUIntLE = BP.readUIntLE
13924  arr.readUIntBE = BP.readUIntBE
13925  arr.readUInt8 = BP.readUInt8
13926  arr.readUInt16LE = BP.readUInt16LE
13927  arr.readUInt16BE = BP.readUInt16BE
13928  arr.readUInt32LE = BP.readUInt32LE
13929  arr.readUInt32BE = BP.readUInt32BE
13930  arr.readIntLE = BP.readIntLE
13931  arr.readIntBE = BP.readIntBE
13932  arr.readInt8 = BP.readInt8
13933  arr.readInt16LE = BP.readInt16LE
13934  arr.readInt16BE = BP.readInt16BE
13935  arr.readInt32LE = BP.readInt32LE
13936  arr.readInt32BE = BP.readInt32BE
13937  arr.readFloatLE = BP.readFloatLE
13938  arr.readFloatBE = BP.readFloatBE
13939  arr.readDoubleLE = BP.readDoubleLE
13940  arr.readDoubleBE = BP.readDoubleBE
13941  arr.writeUInt8 = BP.writeUInt8
13942  arr.writeUIntLE = BP.writeUIntLE
13943  arr.writeUIntBE = BP.writeUIntBE
13944  arr.writeUInt16LE = BP.writeUInt16LE
13945  arr.writeUInt16BE = BP.writeUInt16BE
13946  arr.writeUInt32LE = BP.writeUInt32LE
13947  arr.writeUInt32BE = BP.writeUInt32BE
13948  arr.writeIntLE = BP.writeIntLE
13949  arr.writeIntBE = BP.writeIntBE
13950  arr.writeInt8 = BP.writeInt8
13951  arr.writeInt16LE = BP.writeInt16LE
13952  arr.writeInt16BE = BP.writeInt16BE
13953  arr.writeInt32LE = BP.writeInt32LE
13954  arr.writeInt32BE = BP.writeInt32BE
13955  arr.writeFloatLE = BP.writeFloatLE
13956  arr.writeFloatBE = BP.writeFloatBE
13957  arr.writeDoubleLE = BP.writeDoubleLE
13958  arr.writeDoubleBE = BP.writeDoubleBE
13959  arr.fill = BP.fill
13960  arr.inspect = BP.inspect
13961  arr.toArrayBuffer = BP.toArrayBuffer
13962
13963  return arr
13964}
13965
13966var INVALID_BASE64_RE = /[^+\/0-9A-Za-z-_]/g
13967
13968function base64clean (str) {
13969  // Node strips out invalid characters like \n and \t from the string, base64-js does not
13970  str = stringtrim(str).replace(INVALID_BASE64_RE, '')
13971  // Node converts strings with length < 2 to ''
13972  if (str.length < 2) return ''
13973  // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not
13974  while (str.length % 4 !== 0) {
13975    str = str + '='
13976  }
13977  return str
13978}
13979
13980function stringtrim (str) {
13981  if (str.trim) return str.trim()
13982  return str.replace(/^\s+|\s+$/g, '')
13983}
13984
13985function toHex (n) {
13986  if (n < 16) return '0' + n.toString(16)
13987  return n.toString(16)
13988}
13989
13990function utf8ToBytes (string, units) {
13991  units = units || Infinity
13992  var codePoint
13993  var length = string.length
13994  var leadSurrogate = null
13995  var bytes = []
13996
13997  for (var i = 0; i < length; i++) {
13998    codePoint = string.charCodeAt(i)
13999
14000    // is surrogate component
14001    if (codePoint > 0xD7FF && codePoint < 0xE000) {
14002      // last char was a lead
14003      if (!leadSurrogate) {
14004        // no lead yet
14005        if (codePoint > 0xDBFF) {
14006          // unexpected trail
14007          if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
14008          continue
14009        } else if (i + 1 === length) {
14010          // unpaired lead
14011          if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
14012          continue
14013        }
14014
14015        // valid lead
14016        leadSurrogate = codePoint
14017
14018        continue
14019      }
14020
14021      // 2 leads in a row
14022      if (codePoint < 0xDC00) {
14023        if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
14024        leadSurrogate = codePoint
14025        continue
14026      }
14027
14028      // valid surrogate pair
14029      codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000
14030    } else if (leadSurrogate) {
14031      // valid bmp char, but last char was a lead
14032      if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
14033    }
14034
14035    leadSurrogate = null
14036
14037    // encode utf8
14038    if (codePoint < 0x80) {
14039      if ((units -= 1) < 0) break
14040      bytes.push(codePoint)
14041    } else if (codePoint < 0x800) {
14042      if ((units -= 2) < 0) break
14043      bytes.push(
14044        codePoint >> 0x6 | 0xC0,
vendor: 4,615 bytes, lines 14045-14227
14045        codePoint & 0x3F | 0x80
14046      )
14047    } else if (codePoint < 0x10000) {
14048      if ((units -= 3) < 0) break
14049      bytes.push(
14050        codePoint >> 0xC | 0xE0,
14051        codePoint >> 0x6 & 0x3F | 0x80,
14052        codePoint & 0x3F | 0x80
14053      )
14054    } else if (codePoint < 0x110000) {
14055      if ((units -= 4) < 0) break
14056      bytes.push(
14057        codePoint >> 0x12 | 0xF0,
14058        codePoint >> 0xC & 0x3F | 0x80,
14059        codePoint >> 0x6 & 0x3F | 0x80,
14060        codePoint & 0x3F | 0x80
14061      )
14062    } else {
14063      throw new Error('Invalid code point')
14064    }
14065  }
14066
14067  return bytes
14068}
14069
14070function asciiToBytes (str) {
14071  var byteArray = []
14072  for (var i = 0; i < str.length; i++) {
14073    // Node's code seems to be doing this and not & 0x7F..
14074    byteArray.push(str.charCodeAt(i) & 0xFF)
14075  }
14076  return byteArray
14077}
14078
14079function utf16leToBytes (str, units) {
14080  var c, hi, lo
14081  var byteArray = []
14082  for (var i = 0; i < str.length; i++) {
14083    if ((units -= 2) < 0) break
14084
14085    c = str.charCodeAt(i)
14086    hi = c >> 8
14087    lo = c % 256
14088    byteArray.push(lo)
14089    byteArray.push(hi)
14090  }
14091
14092  return byteArray
14093}
14094
14095function base64ToBytes (str) {
14096  return base64.toByteArray(base64clean(str))
14097}
14098
14099function blitBuffer (src, dst, offset, length) {
14100  for (var i = 0; i < length; i++) {
14101    if ((i + offset >= dst.length) || (i >= src.length)) break
14102    dst[i + offset] = src[i]
14103  }
14104  return i
14105}
14106
14107}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer)
14108},{"base64-js":35,"buffer":37,"ieee754":40,"isarray":42}],38:[function(require,module,exports){
14109(function (global){
14110var topLevel = typeof global !== 'undefined' ? global :
14111    typeof window !== 'undefined' ? window : {}
14112var minDoc = require('min-document');
14113
14114var doccy;
14115
14116if (typeof document !== 'undefined') {
14117    doccy = document;
14118} else {
14119    doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
14120
14121    if (!doccy) {
14122        doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
14123    }
14124}
14125
14126module.exports = doccy;
14127
14128}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
14129},{"min-document":36}],39:[function(require,module,exports){
14130(function (global){
14131var win;
14132
14133if (typeof window !== "undefined") {
14134    win = window;
14135} else if (typeof global !== "undefined") {
14136    win = global;
14137} else if (typeof self !== "undefined"){
14138    win = self;
14139} else {
14140    win = {};
14141}
14142
14143module.exports = win;
14144
14145}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
14146},{}],40:[function(require,module,exports){
14147exports.read = function (buffer, offset, isLE, mLen, nBytes) {
14148  var e, m
14149  var eLen = (nBytes * 8) - mLen - 1
14150  var eMax = (1 << eLen) - 1
14151  var eBias = eMax >> 1
14152  var nBits = -7
14153  var i = isLE ? (nBytes - 1) : 0
14154  var d = isLE ? -1 : 1
14155  var s = buffer[offset + i]
14156
14157  i += d
14158
14159  e = s & ((1 << (-nBits)) - 1)
14160  s >>= (-nBits)
14161  nBits += eLen
14162  for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {}
14163
14164  m = e & ((1 << (-nBits)) - 1)
14165  e >>= (-nBits)
14166  nBits += mLen
14167  for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {}
14168
14169  if (e === 0) {
14170    e = 1 - eBias
14171  } else if (e === eMax) {
14172    return m ? NaN : ((s ? -1 : 1) * Infinity)
14173  } else {
14174    m = m + Math.pow(2, mLen)
14175    e = e - eBias
14176  }
14177  return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
14178}
14179
14180exports.write = function (buffer, value, offset, isLE, mLen, nBytes) {
14181  var e, m, c
14182  var eLen = (nBytes * 8) - mLen - 1
14183  var eMax = (1 << eLen) - 1
14184  var eBias = eMax >> 1
14185  var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
14186  var i = isLE ? 0 : (nBytes - 1)
14187  var d = isLE ? 1 : -1
14188  var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0
14189
14190  value = Math.abs(value)
14191
14192  if (isNaN(value) || value === Infinity) {
14193    m = isNaN(value) ? 1 : 0
14194    e = eMax
14195  } else {
14196    e = Math.floor(Math.log(value) / Math.LN2)
14197    if (value * (c = Math.pow(2, -e)) < 1) {
14198      e--
14199      c *= 2
14200    }
14201    if (e + eBias >= 1) {
14202      value += rt / c
14203    } else {
14204      value += rt * Math.pow(2, 1 - eBias)
14205    }
14206    if (value * c >= 2) {
14207      e++
14208      c /= 2
14209    }
14210
14211    if (e + eBias >= eMax) {
14212      m = 0
14213      e = eMax
14214    } else if (e + eBias >= 1) {
14215      m = ((value * c) - 1) * Math.pow(2, mLen)
14216      e = e + eBias
14217    } else {
14218      m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
14219      e = 0
14220    }
14221  }
14222
14223  for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
14224
14225  e = (e << mLen) | m
14226  eLen += mLen
14227  for (; eLen > 0;
14227 buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
14228
14229  buffer[offset + i - d] |= s * 128
14230}
14231
14232},{}],41:[function(require,module,exports){
14233module.exports = isFunction
14234
14235var toString = Object.prototype.toString
14236
14237function isFunction (fn) {
14238  if (!fn) {
14239    return false
14240  }
14241  var string = toString.call(fn)
14242  return string === '[object Function]' ||
14243    (typeof fn === 'function' && string !== '[object RegExp]') ||
14244    (typeof window !== 'undefined' &&
14245     // IE8 and below
14246     (fn === window.setTimeout ||
14247      fn === window.alert ||
14248      fn === window.confirm ||
14249      fn === window.prompt))
14250};
14251
14252},{}],42:[function(require,module,exports){
14253var toString = {}.toString;
14254
14255module.exports = Array.isArray || function (arr) {
14256  return toString.call(arr) == '[object Array]';
14257};
14258
14259},{}],43:[function(require,module,exports){
14260/*!
14261 * jQuery JavaScript Library v3.5.1
14262 * https://jquery.com/
14263 *
14264 * Includes Sizzle.js
14265 * https://sizzlejs.com/
14266 *
14267 * Copyright JS Foundation and other contributors
14268 * Released under the MIT license
14269 * https://jquery.org/license
14270 *
14271 * Date: 2020-05-04T22:49Z
14272 */
14273( function( global, factory ) {
14274
14275	"use strict";
14276
14277	if ( typeof module === "object" && typeof module.exports === "object" ) {
14278
14279		// For CommonJS and CommonJS-like environments where a proper `window`
14280		// is present, execute the factory and get jQuery.
14281		// For environments that do not have a `window` with a `document`
14282		// (such as Node.js), expose a factory as module.exports.
14283		// This accentuates the need for the creation of a real `window`.
14284		// e.g. var jQuery = require("jquery")(window);
14285		// See ticket #14549 for more info.
14286		module.exports = global.document ?
14287			factory( global, true ) :
14288			function( w ) {
14289				if ( !w.document ) {
14290					throw new Error( "jQuery requires a window with a document" );
14291				}
14292				return factory( w );
14293			};
14294	} else {
14295		factory( global );
14296	}
14297
14298// Pass this if window is not defined yet
14299} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
14300
14301// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
14302// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
14303// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
14304// enough that all such attempts are guarded in a try block.
14305"use strict";
14306
14307var arr = [];
14308
14309var getProto = Object.getPrototypeOf;
14310
14311var slice = arr.slice;
14312
14313var flat = arr.flat ? function( array ) {
14314	return arr.flat.call( array );
14315} : function( array ) {
14316	return arr.concat.apply( [], array );
14317};
14318
14319
14320var push = arr.push;
14321
14322var indexOf = arr.indexOf;
14323
14324var class2type = {};
14325
14326var toString = class2type.toString;
14327
14328var hasOwn = class2type.hasOwnProperty;
14329
14330var fnToString = hasOwn.toString;
14331
14332var ObjectFunctionString = fnToString.call( Object );
14333
14334var support = {};
14335
14336var isFunction = function isFunction( obj ) {
14337
14338      // Support: Chrome <=57, Firefox <=52
14339      // In some browsers, typeof returns "function" for HTML <object> elements
14340      // (i.e., `typeof document.createElement( "object" ) === "function"`).
14341      // We don't want to classify *any* DOM node as a function.
14342      return typeof obj === "function" && typeof obj.nodeType !== "number";
14343  };
14344
14345
14346var isWindow = function isWindow( obj ) {
14347		return obj != null && obj === obj.window;
14348	};
14349
14350
14351var document = window.document;
14352
14353
14354
14355	var preservedScriptAttributes = {
14356		type: true,
14357		src: true,
14358		nonce: true,
14359		noModule: true
14360	};
14361
14362	function DOMEval( code, node, doc ) {
14363		doc = doc || document;
14364
14365		var i, val,
14366			script = doc.createElement( "script" );
14367
14368		script.text = code;
14369		if ( node ) {
14370			for ( i in preservedScriptAttributes ) {
14371
14372				// Support: Firefox 64+, Edge 18+
14373				// Some browsers don't support the "nonce" property on scripts.
14374				// On the other hand, just using `getAttribute` is not enough as
14375				// the `nonce` attribute is reset to an empty string whenever it
14376				// becomes browsing-context connected.
14377				// See https://github.com/whatwg/html/issues/2369
14378				// See https://html.spec.whatwg.org/#nonce-attributes
14379				// The `node.getAttribute` check was added for the sake of
14380				// `jQuery.globalEval` so that it can fake a nonce-containing node
14381				// via an object.
14382				val = node[ i ] || node.getAttribute && node.getAttribute( i );
14383				if ( val ) {
14384					script.setAttribute( i, val );
14385				}
14386			}
14387		}
14388		doc.head.appendChild( script ).parentNode.removeChild( script );
14389	}
14390
14391
14392function toType( obj ) {
14393	if ( obj == null ) {
14394		return obj + "";
14395	}
14396
14397	// Support: Android <=2.3 only (functionish RegExp)
14398	return typeof obj === "object" || typeof obj === "function" ?
14399		class2type[ toString.call( obj ) ] || "object" :
14400		typeof obj;
14401}
14402/* global Symbol */
14403// Defining this global in .eslintrc.json would create a danger of using the global
14404// unguarded in another place, it seems safer to define global only for this module
14405
14406
14407
14408var
14409	version = "3.5.1",
14410
14411	// Define a local copy of jQuery
14412	jQuery = function( selector, context ) {
14413
14414		// The jQuery object is actually just the init constructor 'enhanced'
14415		// Need init if jQuery is called (just allow error to be thrown if not included)
14416		return new jQuery.fn.init( selector, context );
14417	};
14418
14419jQuery.fn = jQuery.prototype = {
14420
14421	// The current version of jQuery being used
14422	jquery: version,
14423
14424	constructor: jQuery,
14425
14426	// The default length of a jQuery object is 0
14427	length: 0,
14428
14429	toArray: function() {
14430		return slice.call( this );
14431	},
14432
14433	// Get the Nth element in the matched element set OR
14434	// Get the whole matched element set as a clean array
14435	get: function( num ) {
14436
14437		// Return all the elements in a clean array
14438		if ( num == null ) {
14439			return slice.call( this );
14440		}
14441
14442		// Return just the one element from the set
14443		return num < 0 ? this[ num + this.length ] : this[ num ];
14444	},
14445
14446	// Take an array of elements and push it onto the stack
14447	// (returning the new matched element set)
14448	pushStack: function( elems ) {
14449
14450		// Build a new jQuery matched element set
14451		var ret = jQuery.merge( this.constructor(), elems );
14452
14453		// Add the old object onto the stack (as a reference)
vendor: 6,004 bytes, lines 14454-14724
14454		ret.prevObject = this;
14455
14456		// Return the newly-formed element set
14457		return ret;
14458	},
14459
14460	// Execute a callback for every element in the matched set.
14461	each: function( callback ) {
14462		return jQuery.each( this, callback );
14463	},
14464
14465	map: function( callback ) {
14466		return this.pushStack( jQuery.map( this, function( elem, i ) {
14467			return callback.call( elem, i, elem );
14468		} ) );
14469	},
14470
14471	slice: function() {
14472		return this.pushStack( slice.apply( this, arguments ) );
14473	},
14474
14475	first: function() {
14476		return this.eq( 0 );
14477	},
14478
14479	last: function() {
14480		return this.eq( -1 );
14481	},
14482
14483	even: function() {
14484		return this.pushStack( jQuery.grep( this, function( _elem, i ) {
14485			return ( i + 1 ) % 2;
14486		} ) );
14487	},
14488
14489	odd: function() {
14490		return this.pushStack( jQuery.grep( this, function( _elem, i ) {
14491			return i % 2;
14492		} ) );
14493	},
14494
14495	eq: function( i ) {
14496		var len = this.length,
14497			j = +i + ( i < 0 ? len : 0 );
14498		return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
14499	},
14500
14501	end: function() {
14502		return this.prevObject || this.constructor();
14503	},
14504
14505	// For internal use only.
14506	// Behaves like an Array's method, not like a jQuery method.
14507	push: push,
14508	sort: arr.sort,
14509	splice: arr.splice
14510};
14511
14512jQuery.extend = jQuery.fn.extend = function() {
14513	var options, name, src, copy, copyIsArray, clone,
14514		target = arguments[ 0 ] || {},
14515		i = 1,
14516		length = arguments.length,
14517		deep = false;
14518
14519	// Handle a deep copy situation
14520	if ( typeof target === "boolean" ) {
14521		deep = target;
14522
14523		// Skip the boolean and the target
14524		target = arguments[ i ] || {};
14525		i++;
14526	}
14527
14528	// Handle case when target is a string or something (possible in deep copy)
14529	if ( typeof target !== "object" && !isFunction( target ) ) {
14530		target = {};
14531	}
14532
14533	// Extend jQuery itself if only one argument is passed
14534	if ( i === length ) {
14535		target = this;
14536		i--;
14537	}
14538
14539	for ( ; i < length; i++ ) {
14540
14541		// Only deal with non-null/undefined values
14542		if ( ( options = arguments[ i ] ) != null ) {
14543
14544			// Extend the base object
14545			for ( name in options ) {
14546				copy = options[ name ];
14547
14548				// Prevent Object.prototype pollution
14549				// Prevent never-ending loop
14550				if ( name === "__proto__" || target === copy ) {
14551					continue;
14552				}
14553
14554				// Recurse if we're merging plain objects or arrays
14555				if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
14556					( copyIsArray = Array.isArray( copy ) ) ) ) {
14557					src = target[ name ];
14558
14559					// Ensure proper type for the source value
14560					if ( copyIsArray && !Array.isArray( src ) ) {
14561						clone = [];
14562					} else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
14563						clone = {};
14564					} else {
14565						clone = src;
14566					}
14567					copyIsArray = false;
14568
14569					// Never move original objects, clone them
14570					target[ name ] = jQuery.extend( deep, clone, copy );
14571
14572				// Don't bring in undefined values
14573				} else if ( copy !== undefined ) {
14574					target[ name ] = copy;
14575				}
14576			}
14577		}
14578	}
14579
14580	// Return the modified object
14581	return target;
14582};
14583
14584jQuery.extend( {
14585
14586	// Unique for each copy of jQuery on the page
14587	expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
14588
14589	// Assume jQuery is ready without the ready module
14590	isReady: true,
14591
14592	error: function( msg ) {
14593		throw new Error( msg );
14594	},
14595
14596	noop: function() {},
14597
14598	isPlainObject: function( obj ) {
14599		var proto, Ctor;
14600
14601		// Detect obvious negatives
14602		// Use toString instead of jQuery.type to catch host objects
14603		if ( !obj || toString.call( obj ) !== "[object Object]" ) {
14604			return false;
14605		}
14606
14607		proto = getProto( obj );
14608
14609		// Objects with no prototype (e.g., `Object.create( null )`) are plain
14610		if ( !proto ) {
14611			return true;
14612		}
14613
14614		// Objects with prototype are plain iff they were constructed by a global Object function
14615		Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
14616		return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
14617	},
14618
14619	isEmptyObject: function( obj ) {
14620		var name;
14621
14622		for ( name in obj ) {
14623			return false;
14624		}
14625		return true;
14626	},
14627
14628	// Evaluates a script in a provided context; falls back to the global one
14629	// if not specified.
14630	globalEval: function( code, options, doc ) {
14631		DOMEval( code, { nonce: options && options.nonce }, doc );
14632	},
14633
14634	each: function( obj, callback ) {
14635		var length, i = 0;
14636
14637		if ( isArrayLike( obj ) ) {
14638			length = obj.length;
14639			for ( ; i < length; i++ ) {
14640				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
14641					break;
14642				}
14643			}
14644		} else {
14645			for ( i in obj ) {
14646				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
14647					break;
14648				}
14649			}
14650		}
14651
14652		return obj;
14653	},
14654
14655	// results is for internal usage only
14656	makeArray: function( arr, results ) {
14657		var ret = results || [];
14658
14659		if ( arr != null ) {
14660			if ( isArrayLike( Object( arr ) ) ) {
14661				jQuery.merge( ret,
14662					typeof arr === "string" ?
14663					[ arr ] : arr
14664				);
14665			} else {
14666				push.call( ret, arr );
14667			}
14668		}
14669
14670		return ret;
14671	},
14672
14673	inArray: function( elem, arr, i ) {
14674		return arr == null ? -1 : indexOf.call( arr, elem, i );
14675	},
14676
14677	// Support: Android <=4.0 only, PhantomJS 1 only
14678	// push.apply(_, arraylike) throws on ancient WebKit
14679	merge: function( first, second ) {
14680		var len = +second.length,
14681			j = 0,
14682			i = first.length;
14683
14684		for ( ; j < len; j++ ) {
14685			first[ i++ ] = second[ j ];
14686		}
14687
14688		first.length = i;
14689
14690		return first;
14691	},
14692
14693	grep: function( elems, callback, invert ) {
14694		var callbackInverse,
14695			matches = [],
14696			i = 0,
14697			length = elems.length,
14698			callbackExpect = !invert;
14699
14700		// Go through the array, only saving the items
14701		// that pass the validator function
14702		for ( ; i < length; i++ ) {
14703			callbackInverse = !callback( elems[ i ], i );
14704			if ( callbackInverse !== callbackExpect ) {
14705				matches.push( elems[ i ] );
14706			}
14707		}
14708
14709		return matches;
14710	},
14711
14712	// arg is for internal usage only
14713	map: function( elems, callback, arg ) {
14714		var length, value,
14715			i = 0,
14716			ret = [];
14717
14718		// Go through the array, translating each of the items to their new values
14719		if ( isArrayLike( elems ) ) {
14720			length = elems.length;
14721			for ( ; i < length; i++ ) {
14722				value = callback( elems[ i ], i, arg );
14723
14724				if ( value != null ) {
14725					ret.push( value );
14726				}
14727			}
14728
14729		// Go through every key on the object,
14730		} else {
14731			for ( i in elems ) {
14732				value = callback( elems[ i ], i, arg );
14733
14734				if ( value != null ) {
14735					ret.push( value );
14736				}
14737			}
14738		}
14739
14740		// Flatten any nested arrays
14741		return flat( ret );
14742	},
14743
14744	// A global GUID counter for objects
14745	guid: 1,
14746
14747	// jQuery.support is not used in Core but other projects attach their
14748	// properties to it so it needs to exist.
14749	support: support
14750} );
14751
14752if ( typeof Symbol === "function" ) {
14753	jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
14754}
14755
14756// Populate the class2type map
14757jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
14758function( _i, name ) {
14759	class2type[ "[object " + name + "]" ] = name.toLowerCase();
14760} );
14761
14762function isArrayLike( obj ) {
14763
14764	// Support: real iOS 8.2 only (not reproducible in simulator)
14765	// `in` check used to prevent JIT error (gh-2145)
14766	// hasOwn isn't used here due to false negatives
14767	// regarding Nodelist length in IE
14768	var length = !!obj && "length" in obj && obj.length,
14769		type = toType( obj );
14770
14771	if ( isFunction( obj ) || isWindow( obj ) ) {
14772		return false;
14773	}
14774
14775	return type === "array" || length === 0 ||
14776		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
14777}
14778var Sizzle =
14779/*!
14780 * Sizzle CSS Selector Engine v2.3.5
14781 * https://sizzlejs.com/
14782 *
14783 * Copyright JS Foundation and other contributors
14784 * Released under the MIT license
14785 * https://js.foundation/
14786 *
14787 * Date: 2020-03-14
14788 */
14789( function( window ) {
14790var i,
14791	support,
14792	Expr,
14793	getText,
14794	isXML,
14795	tokenize,
14796	compile,
14797	select,
14798	outermostContext,
14799	sortInput,
14800	hasDuplicate,
14801
14802	// Local document vars
14803	setDocument,
14804	document,
14805	docElem,
14806	documentIsHTML,
14807	rbuggyQSA,
14808	rbuggyMatches,
14809	matches,
14810	contains,
14811
14812	// Instance-specific data
14813	expando = "sizzle" + 1 * new Date(),
14814	preferredDoc = window.document,
14815	dirruns = 0,
14816	done = 0,
14817	classCache = createCache(),
14818	tokenCache = createCache(),
14819	compilerCache = createCache(),
14820	nonnativeSelectorCache = createCache(),
14821	sortOrder = function( a, b ) {
14822		if ( a === b ) {
14823			hasDuplicate = true;
14824		}
14825		return 0;
14826	},
14827
14828	// Instance methods
14829	hasOwn = ( {} ).hasOwnProperty,
14830	arr = [],
14831	pop = arr.pop,
14832	pushNative = arr.push,
14833	push = arr.push,
14834	slice = arr.slice,
14835
14836	// Use a stripped-down indexOf as it's faster than native
14837	// https://jsperf.com/thor-indexof-vs-for/5
14838	indexOf = function( list, elem ) {
14839		var i = 0,
14840			len = list.length;
14841		for ( ; i < len; i++ ) {
14842			if ( list[ i ] === elem ) {
14843				return i;
14844			}
14845		}
14846		return -1;
14847	},
14848
14849	booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" +
14850		"ismap|loop|multiple|open|readonly|required|scoped",
14851
14852	// Regular expressions
14853
14854	// http://www.w3.org/TR/css3-selectors/#whitespace
14855	whitespace = "[\\x20\\t\\r\\n\\f]",
14856
14857	// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
14858	identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace +
14859		"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",
14860
14861	// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
14862	attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
14863
14864		// Operator (capture 2)
14865		"*([*^$|!~]?=)" + whitespace +
14866
14867		// "Attribute values must be CSS identifiers [capture 5]
14868		// or strings [capture 3 or capture 4]"
14869		"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" +
14870		whitespace + "*\\]",
14871
14872	pseudos = ":(" + identifier + ")(?:\\((" +
14873
14874		// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
14875		// 1. quoted (capture 3; capture 4 or capture 5)
14876		"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
14877
14878		// 2. simple (capture 6)
14879		"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
14880
14881		// 3. anything else (capture 2)
14882		".*" +
14883		")\\)|)",
14884
14885	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
14886	rwhitespace = new RegExp( whitespace + "+", "g" ),
14887	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" +
14888		whitespace + "+$", "g" ),
14889
14890	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
14891	rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace +
14892		"*" ),
14893	rdescend = new RegExp( whitespace + "|>" ),
14894
14895	rpseudo = new RegExp( pseudos ),
14896	ridentifier = new RegExp( "^" + identifier + "$" ),
14897
14898	matchExpr = {
14899		"ID": new RegExp( "^#(" + identifier + ")" ),
14900		"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
14901		"TAG": new RegExp( "^(" + identifier + "|[*])" ),
14902		"ATTR": new RegExp( "^" + attributes ),
14903		"PSEUDO": new RegExp( "^" + pseudos ),
14904		"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" +
14905			whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" +
14906			whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
14907		"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
14908
14909		// For use in libraries implementing .is()
14910		// We use this for POS matching in `select`
14911		"needsContext": new RegExp( "^" + whitespace +
14912			"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
14913			"*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
14914	},
14915
14916	rhtml = /HTML$/i,
14917	rinputs = /^(?:input|select|textarea|button)$/i,
14918	rheader = /^h\d$/i,
14919
14920	rnative = /^[^{]+\{\s*\[native \w/,
14921
14922	// Easily-parseable/retrievable ID or TAG or CLASS selectors
14923	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
14924
14925	rsibling = /[+~]/,
14926
14927	// CSS escapes
14928	// http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
14929	runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ),
14930	funescape = function( escape, nonHex ) {
14931		var high = "0x" + escape.slice( 1 ) - 0x10000;
14932
14933		return nonHex ?
14934
14935			// Strip the backslash prefix from a non-hex escape sequence
14936			nonHex :
14937
14938			// Replace a hexadecimal escape sequence with the encoded Unicode code point
14939			// Support: IE <=11+
14940			// For values outside the Basic Multilingual Plane (BMP), manually construct a
14941			// surrogate pair
14942			high < 0 ?
14943				String.fromCharCode( high + 0x10000 ) :
14944				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
14945	},
14946
14947	// CSS string/identifier serialization
14948	// https://drafts.csswg.org/cssom/#common-serializing-idioms
14949	rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,
14950	fcssescape = function( ch, asCodePoint ) {
14951		if ( asCodePoint ) {
14952
14953			// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
14954			if ( ch === "\0" ) {
14955				return "\uFFFD";
14956			}
14957
14958			// Control characters and (dependent upon position) numbers get escaped as code points
14959			return ch.slice( 0, -1 ) + "\\" +
14960				ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
14961		}
14962
14963		// Other potentially-special ASCII characters get backslash-escaped
14964		return "\\" + ch;
14965	},
14966
14967	// Used for iframes
14968	// See setDocument()
14969	// Removing the function wrapper causes a "Permission Denied"
14970	// error in IE
14971	unloadHandler = function() {
14972		setDocument();
14973	},
14974
14975	inDisabledFieldset = addCombinator(
14976		function( elem ) {
14977			return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset";
14978		},
14979		{ dir: "parentNode", next: "legend" }
14980	);
14981
14982// Optimize for push.apply( _, NodeList )
14983try {
14984	push.apply(
14985		( arr = slice.call( preferredDoc.childNodes ) ),
14986		preferredDoc.childNodes
14987	);
14988
14989	// Support: Android<4.0
14990	// Detect silently failing push.apply
14991	// eslint-disable-next-line no-unused-expressions
14992	arr[ preferredDoc.childNodes.length ].nodeType;
14993} catch ( e ) {
14994	push = { apply: arr.length ?
14995
14996		// Leverage slice if possible
14997		function( target, els ) {
14998			pushNative.apply( target, slice.call( els ) );
14999		} :
15000
15001		// Support: IE<9
15002		// Otherwise append directly
15003		function( target, els ) {
15004			var j = target.length,
15005				i = 0;
15006
15007			// Can't trust NodeList.length
15008			while ( ( target[ j++ ] = els[ i++ ] ) ) {}
15009			target.length = j - 1;
15010		}
15011	};
15012}
15013
15014function Sizzle( selector, context, results, seed ) {
15015	var m, i, elem, nid, match, groups, newSelector,
15016		newContext = context && context.ownerDocument,
15017
15018		// nodeType defaults to 9, since context defaults to document
15019		nodeType = context ? context.nodeType : 9;
15020
15021	results = results || [];
15022
15023	// Return early from calls with invalid selector or context
15024	if ( typeof selector !== "string" || !selector ||
15025		nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
15026
15027		return results;
15028	}
15029
15030	// Try to shortcut find operations (as opposed to filters) in HTML documents
15031	if ( !seed ) {
15032		setDocument( context );
15033		context = context || document;
15034
15035		if ( documentIsHTML ) {
15036
15037			// If the selector is sufficiently simple, try using a "get*By*" DOM method
15038			// (excepting DocumentFragment context, where the methods don't exist)
15039			if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) {
15040
15041				// ID selector
15042				if ( ( m = match[ 1 ] ) ) {
15043
15044					// Document context
15045					if ( nodeType === 9 ) {
15046						if ( ( elem = context.getElementById( m ) ) ) {
15047
vendor: 4,251 bytes, lines 15048-15197
15048							// Support: IE, Opera, Webkit
15049							// TODO: identify versions
15050							// getElementById can match elements by name instead of ID
15051							if ( elem.id === m ) {
15052								results.push( elem );
15053								return results;
15054							}
15055						} else {
15056							return results;
15057						}
15058
15059					// Element context
15060					} else {
15061
15062						// Support: IE, Opera, Webkit
15063						// TODO: identify versions
15064						// getElementById can match elements by name instead of ID
15065						if ( newContext && ( elem = newContext.getElementById( m ) ) &&
15066							contains( context, elem ) &&
15067							elem.id === m ) {
15068
15069							results.push( elem );
15070							return results;
15071						}
15072					}
15073
15074				// Type selector
15075				} else if ( match[ 2 ] ) {
15076					push.apply( results, context.getElementsByTagName( selector ) );
15077					return results;
15078
15079				// Class selector
15080				} else if ( ( m = match[ 3 ] ) && support.getElementsByClassName &&
15081					context.getElementsByClassName ) {
15082
15083					push.apply( results, context.getElementsByClassName( m ) );
15084					return results;
15085				}
15086			}
15087
15088			// Take advantage of querySelectorAll
15089			if ( support.qsa &&
15090				!nonnativeSelectorCache[ selector + " " ] &&
15091				( !rbuggyQSA || !rbuggyQSA.test( selector ) ) &&
15092
15093				// Support: IE 8 only
15094				// Exclude object elements
15095				( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) {
15096
15097				newSelector = selector;
15098				newContext = context;
15099
15100				// qSA considers elements outside a scoping root when evaluating child or
15101				// descendant combinators, which is not what we want.
15102				// In such cases, we work around the behavior by prefixing every selector in the
15103				// list with an ID selector referencing the scope context.
15104				// The technique has to be used as well when a leading combinator is used
15105				// as such selectors are not recognized by querySelectorAll.
15106				// Thanks to Andrew Dupont for this technique.
15107				if ( nodeType === 1 &&
15108					( rdescend.test( selector ) || rcombinators.test( selector ) ) ) {
15109
15110					// Expand context for sibling selectors
15111					newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
15112						context;
15113
15114					// We can use :scope instead of the ID hack if the browser
15115					// supports it & if we're not changing the context.
15116					if ( newContext !== context || !support.scope ) {
15117
15118						// Capture the context ID, setting it first if necessary
15119						if ( ( nid = context.getAttribute( "id" ) ) ) {
15120							nid = nid.replace( rcssescape, fcssescape );
15121						} else {
15122							context.setAttribute( "id", ( nid = expando ) );
15123						}
15124					}
15125
15126					// Prefix every selector in the list
15127					groups = tokenize( selector );
15128					i = groups.length;
15129					while ( i-- ) {
15130						groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " +
15131							toSelector( groups[ i ] );
15132					}
15133					newSelector = groups.join( "," );
15134				}
15135
15136				try {
15137					push.apply( results,
15138						newContext.querySelectorAll( newSelector )
15139					);
15140					return results;
15141				} catch ( qsaError ) {
15142					nonnativeSelectorCache( selector, true );
15143				} finally {
15144					if ( nid === expando ) {
15145						context.removeAttribute( "id" );
15146					}
15147				}
15148			}
15149		}
15150	}
15151
15152	// All others
15153	return select( selector.replace( rtrim, "$1" ), context, results, seed );
15154}
15155
15156/**
15157 * Create key-value caches of limited size
15158 * @returns {function(string, object)} Returns the Object data after storing it on itself with
15159 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
15160 *	deleting the oldest entry
15161 */
15162function createCache() {
15163	var keys = [];
15164
15165	function cache( key, value ) {
15166
15167		// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
15168		if ( keys.push( key + " " ) > Expr.cacheLength ) {
15169
15170			// Only keep the most recent entries
15171			delete cache[ keys.shift() ];
15172		}
15173		return ( cache[ key + " " ] = value );
15174	}
15175	return cache;
15176}
15177
15178/**
15179 * Mark a function for special use by Sizzle
15180 * @param {Function} fn The function to mark
15181 */
15182function markFunction( fn ) {
15183	fn[ expando ] = true;
15184	return fn;
15185}
15186
15187/**
15188 * Support testing using an element
15189 * @param {Function} fn Passed the created element and returns a boolean result
15190 */
15191function assert( fn ) {
15192	var el = document.createElement( "fieldset" );
15193
15194	try {
15195		return !!fn( el );
15196	} catch ( e ) {
15197		return false;
15198	} finally {
15199
15200		// Remove from its parent by default
15201		if ( el.parentNode ) {
15202			el.parentNode.removeChild( el );
15203		}
15204
15205		// release memory in IE
15206		el = null;
15207	}
15208}
15209
15210/**
15211 * Adds the same handler for all of the specified attrs
15212 * @param {String} attrs Pipe-separated list of attributes
15213 * @param {Function} handler The method that will be applied
15214 */
15215function addHandle( attrs, handler ) {
15216	var arr = attrs.split( "|" ),
15217		i = arr.length;
15218
15219	while ( i-- ) {
15220		Expr.attrHandle[ arr[ i ] ] = handler;
15221	}
15222}
15223
15224/**
15225 * Checks document order of two siblings
15226 * @param {Element} a
15227 * @param {Element} b
15228 * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
15229 */
15230function siblingCheck( a, b ) {
15231	var cur = b && a,
15232		diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
15233			a.sourceIndex - b.sourceIndex;
15234
15235	// Use IE sourceIndex if available on both nodes
15236	if ( diff ) {
15237		return diff;
15238	}
15239
15240	// Check if b follows a
15241	if ( cur ) {
15242		while ( ( cur = cur.nextSibling ) ) {
15243			if ( cur === b ) {
15244				return -1;
15245			}
15246		}
15247	}
15248
15249	return a ? 1 : -1;
15250}
15251
15252/**
15253 * Returns a function to use in pseudos for input types
15254 * @param {String} type
15255 */
15256function createInputPseudo( type ) {
15257	return function( elem ) {
15258		var name = elem.nodeName.toLowerCase();
15259		return name === "input" && elem.type === type;
15260	};
15261}
15262
15263/**
15264 * Returns a function to use in pseudos for buttons
15265 * @param {String} type
15266 */
15267function createButtonPseudo( type ) {
15268	return function( elem ) {
15269		var name = elem.nodeName.toLowerCase();
15270		return ( name === "input" || name === "button" ) && elem.type === type;
15271	};
15272}
15273
15274/**
15275 * Returns a function to use in pseudos for :enabled/:disabled
15276 * @param {Boolean} disabled true for :disabled; false for :enabled
15277 */
15278function createDisabledPseudo( disabled ) {
15279
15280	// Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
15281	return function( elem ) {
15282
15283		// Only certain elements can match :enabled or :disabled
15284		// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
15285		// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
15286		if ( "form" in elem ) {
15287
15288			// Check for inherited disabledness on relevant non-disabled elements:
15289			// * listed form-associated elements in a disabled fieldset
15290			//   https://html.spec.whatwg.org/multipage/forms.html#category-listed
15291			//   https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
15292			// * option elements in a disabled optgroup
15293			//   https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
15294			// All such elements have a "form" property.
15295			if ( elem.parentNode && elem.disabled === false ) {
15296
15297				// Option elements defer to a parent optgroup if present
15298				if ( "label" in elem ) {
15299					if ( "label" in elem.parentNode ) {
15300						return elem.parentNode.disabled === disabled;
15301					} else {
15302						return elem.disabled === disabled;
15303					}
15304				}
15305
15306				// Support: IE 6 - 11
15307				// Use the isDisabled shortcut property to check for disabled fieldset ancestors
15308				return elem.isDisabled === disabled ||
15309
15310					// Where there is no isDisabled, check manually
15311					/* jshint -W018 */
15312					elem.isDisabled !== !disabled &&
15313					inDisabledFieldset( elem ) === disabled;
15314			}
15315
15316			return elem.disabled === disabled;
15317
15318		// Try to winnow out elements that can't be disabled before trusting the disabled property.
15319		// Some victims get caught in our net (label, legend, menu, track), but it shouldn't
15320		// even exist on them, let alone have a boolean value.
15321		} else if ( "label" in elem ) {
15322			return elem.disabled === disabled;
15323		}
15324
15325		// Remaining elements are neither :enabled nor :disabled
15326		return false;
15327	};
15328}
15329
15330/**
15331 * Returns a function to use in pseudos for positionals
15332 * @param {Function} fn
15333 */
15334function createPositionalPseudo( fn ) {
15335	return markFunction( function( argument ) {
15336		argument = +argument;
15337		return markFunction( function( seed, matches ) {
15338			var j,
15339				matchIndexes = fn( [], seed.length, argument ),
15340				i = matchIndexes.length;
15341
15342			// Match elements found at the specified indexes
15343			while ( i-- ) {
15344				if ( seed[ ( j = matchIndexes[ i ] ) ] ) {
15345					seed[ j ] = !( matches[ j ] = seed[ j ] );
15346				}
15347			}
15348		} );
15349	} );
15350}
15351
15352/**
15353 * Checks a node for validity as a Sizzle context
15354 * @param {Element|Object=} context
15355 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
15356 */
15357function testContext( context ) {
15358	return context && typeof context.getElementsByTagName !== "undefined" && context;
15359}
15360
15361// Expose support vars for convenience
15362support = Sizzle.support = {};
15363
15364/**
15365 * Detects XML nodes
15366 * @param {Element|Object} elem An element or a document
15367 * @returns {Boolean} True iff elem is a non-HTML XML node
15368 */
15369isXML = Sizzle.isXML = function( elem ) {
15370	var namespace = elem.namespaceURI,
15371		docElem = ( elem.ownerDocument || elem ).documentElement;
15372
15373	// Support: IE <=8
15374	// Assume HTML when documentElement doesn't yet exist, such as inside loading iframes
15375	// https://bugs.jquery.com/ticket/4833
15376	return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" );
15377};
15378
15379/**
15380 * Sets document-related variables once based on the current document
15381 * @param {Element|Object} [doc] An element or document object to use to set the document
15382 * @returns {Object} Returns the current document
15383 */
15384setDocument = Sizzle.setDocument = function( node ) {
15385	var hasCompare, subWindow,
15386		doc = node ? node.ownerDocument || node : preferredDoc;
15387
15388	// Return early if doc is invalid or already selected
15389	// Support: IE 11+, Edge 17 - 18+
15390	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
15391	// two documents; shallow comparisons work.
15392	// eslint-disable-next-line eqeqeq
15393	if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) {
15394		return document;
15395	}
15396
15397	// Update global variables
15398	document = doc;
15399	docElem = document.documentElement;
15400	documentIsHTML = !isXML( document );
15401
15402	// Support: IE 9 - 11+, Edge 12 - 18+
15403	// Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
15404	// Support: IE 11+, Edge 17 - 18+
15405	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
15406	// two documents; shallow comparisons work.
15407	// eslint-disable-next-line eqeqeq
15408	if ( preferredDoc != document &&
15409		( subWindow = document.defaultView ) && subWindow.top !== subWindow ) {
15410
15411		// Support: IE 11, Edge
15412		if ( subWindow.addEventListener ) {
15413			subWindow.addEventListener( "unload", unloadHandler, false );
15414
15415		// Support: IE 9 - 10 only
15416		} else if ( subWindow.attachEvent ) {
15417			subWindow.attachEvent( "onunload", unloadHandler );
15418		}
15419	}
15420
15421	// Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only,
15422	// Safari 4 - 5 only, Opera <=11.6 - 12.x only
15423	// IE/Edge & older browsers don't support the :scope pseudo-class.
15424	// Support: Safari 6.0 only
15425	// Safari 6.0 supports :scope but it's an alias of :root there.
15426	support.scope = assert( function( el ) {
15427		docElem.appendChild( el ).appendChild( document.createElement( "div" ) );
15428		return typeof el.querySelectorAll !== "undefined" &&
15429			!el.querySelectorAll( ":scope fieldset div" ).length;
15430	} );
15431
15432	/* Attributes
15433	---------------------------------------------------------------------- */
15434
15435	// Support: IE<8
15436	// Verify that getAttribute really returns attributes and not properties
15437	// (excepting IE8 booleans)
15438	support.attributes = assert( function( el ) {
15439		el.className = "i";
15440		return !el.getAttribute( "className" );
15441	} );
15442
15443	/* getElement(s)By*
15444	---------------------------------------------------------------------- */
15445
15446	// Check if getElementsByTagName("*") returns only elements
15447	support.getElementsByTagName = assert( function( el ) {
15448		el.appendChild( document.createComment( "" ) );
15449		return !el.getElementsByTagName( "*" ).length;
15450	} );
15451
15452	// Support: IE<9
15453	support.getElementsByClassName = rnative.test( document.getElementsByClassName );
15454
15455	// Support: IE<10
15456	// Check if getElementById returns elements by name
15457	// The broken getElementById methods don't pick up programmatically-set names,
15458	// so use a roundabout getElementsByName test
15459	support.getById = assert( function( el ) {
15460		docElem.appendChild( el ).id = expando;
15461		return !document.getElementsByName || !document.getElementsByName( expando ).length;
15462	} );
15463
15464	// ID filter and find
15465	if ( support.getById ) {
15466		Expr.filter[ "ID" ] = function( id ) {
15467			var attrId = id.replace( runescape, funescape );
15468			return function( elem ) {
15469				return elem.getAttribute( "id" ) === attrId;
15470			};
15471		};
15472		Expr.find[ "ID" ] = function( id, context ) {
15473			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
15474				var elem = context.getElementById( id );
15475				return elem ? [ elem ] : [];
15476			}
15477		};
15478	} else {
15479		Expr.filter[ "ID" ] =  function( id ) {
15480			var attrId = id.replace( runescape, funescape );
15481			return function( elem ) {
15482				var node = typeof elem.getAttributeNode !== "undefined" &&
15483					elem.getAttributeNode( "id" );
15484				return node && node.value === attrId;
15485			};
15486		};
15487
15488		// Support: IE 6 - 7 only
15489		// getElementById is not reliable as a find shortcut
15490		Expr.find[ "ID" ] = function( id, context ) {
15491			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
15492				var node, i, elems,
15493					elem = context.getElementById( id );
15494
15495				if ( elem ) {
15496
15497					// Verify the id attribute
15498					node = elem.getAttributeNode( "id" );
15499					if ( node && node.value === id ) {
15500						return [ elem ];
15501					}
15502
15503					// Fall back on getElementsByName
15504					elems = context.getElementsByName( id );
15505					i = 0;
15506					while ( ( elem = elems[ i++ ] ) ) {
15507						node = elem.getAttributeNode( "id" );
15508						if ( node && node.value === id ) {
15509							return [ elem ];
15510						}
15511					}
15512				}
15513
15514				return [];
15515			}
15516		};
15517	}
15518
15519	// Tag
15520	Expr.find[ "TAG" ] = support.getElementsByTagName ?
15521		function( tag, context ) {
15522			if ( typeof context.getElementsByTagName !== "undefined" ) {
15523				return context.getElementsByTagName( tag );
15524
15525			// DocumentFragment nodes don't have gEBTN
15526			} else if ( support.qsa ) {
15527				return context.querySelectorAll( tag );
15528			}
15529		} :
15530
15531		function( tag, context ) {
15532			var elem,
15533				tmp = [],
15534				i = 0,
15535
15536				// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
15537				results = context.getElementsByTagName( tag );
15538
15539			// Filter out possible comments
15540			if ( tag === "*" ) {
15541				while ( ( elem = results[ i++ ] ) ) {
15542					if ( elem.nodeType === 1 ) {
15543						tmp.push( elem );
15544					}
15545				}
15546
15547				return tmp;
15548			}
15549			return results;
15550		};
15551
15552	// Class
15553	Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) {
15554		if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
15555			return context.getElementsByClassName( className );
15556		}
15557	};
15558
15559	/* QSA/matchesSelector
15560	---------------------------------------------------------------------- */
15561
15562	// QSA and matchesSelector support
15563
15564	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
15565	rbuggyMatches = [];
15566
15567	// qSa(:focus) reports false when true (Chrome 21)
15568	// We allow this because of a bug in IE8/9 that throws an error
15569	// whenever `document.activeElement` is accessed on an iframe
15570	// So, we allow :focus to pass through QSA all the time to avoid the IE error
15571	// See https://bugs.jquery.com/ticket/13378
15572	rbuggyQSA = [];
15573
15574	if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) {
15575
15576		// Build QSA regex
15577		// Regex strategy adopted from Diego Perini
15578		assert( function( el ) {
15579
15580			var input;
15581
15582			// Select is set to empty string on purpose
15583			// This is to test IE's treatment of not explicitly
15584			// setting a boolean content attribute,
15585			// since its presence should be enough
15586			// https://bugs.jquery.com/ticket/12359
15587			docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" +
15588				"<select id='" + expando + "-\r\\' msallowcapture=''>" +
15589				"<option selected=''></option></select>";
15590
15591			// Support: IE8, Opera 11-12.16
15592			// Nothing should be selected when empty strings follow ^= or $= or *=
15593			// The test attribute must be unknown in Opera but "safe" for WinRT
15594			// https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
15595			if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) {
15596				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
15597			}
15598
15599			// Support: IE8
15600			// Boolean attributes and "value" are not treated correctly
15601			if ( !el.querySelectorAll( "[selected]" ).length ) {
15602				rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
15603			}
15604
15605			// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
15606			if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
15607				rbuggyQSA.push( "~=" );
15608			}
15609
15610			// Support: IE 11+, Edge 15 - 18+
15611			// IE 11/Edge don't find elements on a `[name='']` query in some cases.
15612			// Adding a temporary attribute to the document before the selection works
15613			// around the issue.
15614			// Interestingly, IE 10 & older don't seem to have the issue.
15615			input = document.createElement( "input" );
15616			input.setAttribute( "name", "" );
15617			el.appendChild( input );
15618			if ( !el.querySelectorAll( "[name='']" ).length ) {
15619				rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" 
15619+
15620					whitespace + "*(?:''|\"\")" );
15621			}
15622
15623			// Webkit/Opera - :checked should return selected option elements
15624			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
15625			// IE8 throws error here and will not see later tests
15626			if ( !el.querySelectorAll( ":checked" ).length ) {
15627				rbuggyQSA.push( ":checked" );
15628			}
15629
15630			// Support: Safari 8+, iOS 8+
15631			// https://bugs.webkit.org/show_bug.cgi?id=136851
15632			// In-page `selector#id sibling-combinator selector` fails
15633			if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
15634				rbuggyQSA.push( ".#.+[+~]" );
15635			}
15636
15637			// Support: Firefox <=3.6 - 5 only
15638			// Old Firefox doesn't throw on a badly-escaped identifier.
15639			el.querySelectorAll( "\\\f" );
15640			rbuggyQSA.push( "[\\r\\n\\f]" );
15641		} );
15642
15643		assert( function( el ) {
15644			el.innerHTML = "<a href='' disabled='disabled'></a>" +
15645				"<select disabled='disabled'><option/></select>";
15646
15647			// Support: Windows 8 Native Apps
15648			// The type and name attributes are restricted during .innerHTML assignment
15649			var input = document.createElement( "input" );
15650			input.setAttribute( "type", "hidden" );
15651			el.appendChild( input ).setAttribute( "name", "D" );
15652
15653			// Support: IE8
15654			// Enforce case-sensitivity of name attribute
15655			if ( el.querySelectorAll( "[name=d]" ).length ) {
15656				rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
15657			}
15658
15659			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
15660			// IE8 throws error here and will not see later tests
15661			if ( el.querySelectorAll( ":enabled" ).length !== 2 ) {
15662				rbuggyQSA.push( ":enabled", ":disabled" );
15663			}
15664
15665			// Support: IE9-11+
15666			// IE's :disabled selector does not pick up the children of disabled fieldsets
15667			docElem.appendChild( el ).disabled = true;
15668			if ( el.querySelectorAll( ":disabled" ).length !== 2 ) {
15669				rbuggyQSA.push( ":enabled", ":disabled" );
15670			}
15671
15672			// Support: Opera 10 - 11 only
15673			// Opera 10-11 does not throw on post-comma invalid pseudos
15674			el.querySelectorAll( "*,:x" );
15675			rbuggyQSA.push( ",.*:" );
15676		} );
15677	}
15678
15679	if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches ||
15680		docElem.webkitMatchesSelector ||
15681		docElem.mozMatchesSelector ||
15682		docElem.oMatchesSelector ||
15683		docElem.msMatchesSelector ) ) ) ) {
15684
15685		assert( function( el ) {
15686
15687			// Check to see if it's possible to do matchesSelector
15688			// on a disconnected node (IE 9)
15689			support.disconnectedMatch = matches.call( el, "*" );
15690
15691			// This should fail with an exception
15692			// Gecko does not error, returns false instead
15693			matches.call( el, "[s!='']:x" );
15694			rbuggyMatches.push( "!=", pseudos );
15695		} );
15696	}
15697
15698	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) );
15699	rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) );
15700
15701	/* Contains
15702	---------------------------------------------------------------------- */
15703	hasCompare = rnative.test( docElem.compareDocumentPosition );
15704
15705	// Element contains another
15706	// Purposefully self-exclusive
15707	// As in, an element does not contain itself
15708	contains = hasCompare || rnative.test( docElem.contains ) ?
15709		function( a, b ) {
15710			var adown = a.nodeType === 9 ? a.documentElement : a,
15711				bup = b && b.parentNode;
15712			return a === bup || !!( bup && bup.nodeType === 1 && (
15713				adown.contains ?
15714					adown.contains( bup ) :
15715					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
15716			) );
15717		} :
15718		function( a, b ) {
15719			if ( b ) {
15720				while ( ( b = b.parentNode ) ) {
15721					if ( b === a ) {
15722						return true;
15723					}
15724				}
15725			}
15726			return false;
15727		};
15728
15729	/* Sorting
15730	---------------------------------------------------------------------- */
15731
15732	// Document order sorting
15733	sortOrder = hasCompare ?
15734	function( a, b ) {
15735
15736		// Flag for duplicate removal
15737		if ( a === b ) {
15738			hasDuplicate = true;
15739			return 0;
15740		}
15741
15742		// Sort on method existence if only one input has compareDocumentPosition
15743		var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
15744		if ( compare ) {
15745			return compare;
15746		}
15747
15748		// Calculate position if both inputs belong to the same document
15749		// Support: IE 11+, Edge 17 - 18+
15750		// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
15751		// two documents; shallow comparisons work.
15752		// eslint-disable-next-line eqeqeq
15753		compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ?
15754			a.compareDocumentPosition( b ) :
15755
15756			// Otherwise we know they are disconnected
15757			1;
15758
15759		// Disconnected nodes
15760		if ( compare & 1 ||
15761			( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) {
15762
15763			// Choose the first element that is related to our preferred document
15764			// Support: IE 11+, Edge 17 - 18+
15765			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
15766			// two documents; shallow comparisons work.
15767			// eslint-disable-next-line eqeqeq
15768			if ( a == document || a.ownerDocument == preferredDoc &&
15769				contains( preferredDoc, a ) ) {
15770				return -1;
15771			}
15772
15773			// Support: IE 11+, Edge 17 - 18+
15774			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
15775			// two documents; shallow comparisons work.
15776			// eslint-disable-next-line eqeqeq
15777			if ( b == document || b.ownerDocument == preferredDoc &&
15778				contains( preferredDoc, b ) ) {
15779				return 1;
15780			}
15781
15782			// Maintain original order
15783			return sortInput ?
15784				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
15785				0;
15786		}
15787
15788		return compare & 4 ? -1 : 1;
15789	} :
15790	function( a, b ) {
15791
15792		// Exit early if the nodes are identical
15793		if ( a === b ) {
15794			hasDuplicate = true;
15795			return 0;
15796		}
15797
15798		var cur,
15799			i = 0,
15800			aup = a.parentNode,
15801			bup = b.parentNode,
15802			ap = [ a ],
15803			bp = [ b ];
15804
15805		// Parentless nodes are either documents or disconnected
15806		if ( !aup || !bup ) {
15807
15808			// Support: IE 11+, Edge 17 - 18+
15809			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
15810			// two documents; shallow comparisons work.
15811			/* eslint-disable eqeqeq */
15812			return a == document ? -1 :
15813				b == document ? 1 :
15814				/* eslint-enable eqeqeq */
15815				aup ? -1 :
15816				bup ? 1 :
15817				sortInput ?
15818				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
15819				0;
15820
15821		// If the nodes are siblings, we can do a quick check
15822		} else if ( aup === bup ) {
15823			return siblingCheck( a, b );
15824		}
15825
15826		// Otherwise we need full lists of their ancestors for comparison
15827		cur = a;
15828		while ( ( cur = cur.parentNode ) ) {
15829			ap.unshift( cur );
15830		}
15831		cur = b;
15832		while ( ( cur = cur.parentNode ) ) {
15833			bp.unshift( cur );
15834		}
15835
15836		// Walk down the tree looking for a discrepancy
15837		while ( ap[ i ] === bp[ i ] ) {
15838			i++;
15839		}
15840
15841		return i ?
15842
15843			// Do a sibling check if the nodes have a common ancestor
15844			siblingCheck( ap[ i ], bp[ i ] ) :
15845
15846			// Otherwise nodes in our document sort first
15847			// Support: IE 11+, Edge 17 - 18+
15848			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
15849			// two documents; shallow comparisons work.
15850			/* eslint-disable eqeqeq */
15851			ap[ i ] == preferredDoc ? -1 :
15852			bp[ i ] == preferredDoc ? 1 :
15853			/* eslint-enable eqeqeq */
15854			0;
15855	};
15856
15857	return document;
15858};
15859
15860Sizzle.matches = function( expr, elements ) {
15861	return Sizzle( expr, null, null, elements );
15862};
15863
15864Sizzle.matchesSelector = function( elem, expr ) {
15865	setDocument( elem );
15866
15867	if ( support.matchesSelector && documentIsHTML &&
15868		!nonnativeSelectorCache[ expr + " " ] &&
15869		( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
15870		( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {
15871
15872		try {
15873			var ret = matches.call( elem, expr );
15874
15875			// IE 9's matchesSelector returns false on disconnected nodes
15876			if ( ret || support.disconnectedMatch ||
15877
15878				// As well, disconnected nodes are said to be in a document
15879				// fragment in IE 9
15880				elem.document && elem.document.nodeType !== 11 ) {
15881				return ret;
15882			}
15883		} catch ( e ) {
15884			nonnativeSelectorCache( expr, true );
15885		}
15886	}
15887
15888	return Sizzle( expr, document, null, [ elem ] ).length > 0;
15889};
15890
15891Sizzle.contains = function( context, elem ) {
15892
15893	// Set document vars if needed
15894	// Support: IE 11+, Edge 17 - 18+
15895	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
15896	// two documents; shallow comparisons work.
15897	// eslint-disable-next-line eqeqeq
15898	if ( ( context.ownerDocument || context ) != document ) {
15899		setDocument( context );
15900	}
15901	return contains( context, elem );
15902};
15903
15904Sizzle.attr = function( elem, name ) {
15905
15906	// Set document vars if needed
vendor: 6,321 bytes, lines 15907-16159
15907	// Support: IE 11+, Edge 17 - 18+
15908	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
15909	// two documents; shallow comparisons work.
15910	// eslint-disable-next-line eqeqeq
15911	if ( ( elem.ownerDocument || elem ) != document ) {
15912		setDocument( elem );
15913	}
15914
15915	var fn = Expr.attrHandle[ name.toLowerCase() ],
15916
15917		// Don't get fooled by Object.prototype properties (jQuery #13807)
15918		val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
15919			fn( elem, name, !documentIsHTML ) :
15920			undefined;
15921
15922	return val !== undefined ?
15923		val :
15924		support.attributes || !documentIsHTML ?
15925			elem.getAttribute( name ) :
15926			( val = elem.getAttributeNode( name ) ) && val.specified ?
15927				val.value :
15928				null;
15929};
15930
15931Sizzle.escape = function( sel ) {
15932	return ( sel + "" ).replace( rcssescape, fcssescape );
15933};
15934
15935Sizzle.error = function( msg ) {
15936	throw new Error( "Syntax error, unrecognized expression: " + msg );
15937};
15938
15939/**
15940 * Document sorting and removing duplicates
15941 * @param {ArrayLike} results
15942 */
15943Sizzle.uniqueSort = function( results ) {
15944	var elem,
15945		duplicates = [],
15946		j = 0,
15947		i = 0;
15948
15949	// Unless we *know* we can detect duplicates, assume their presence
15950	hasDuplicate = !support.detectDuplicates;
15951	sortInput = !support.sortStable && results.slice( 0 );
15952	results.sort( sortOrder );
15953
15954	if ( hasDuplicate ) {
15955		while ( ( elem = results[ i++ ] ) ) {
15956			if ( elem === results[ i ] ) {
15957				j = duplicates.push( i );
15958			}
15959		}
15960		while ( j-- ) {
15961			results.splice( duplicates[ j ], 1 );
15962		}
15963	}
15964
15965	// Clear input after sorting to release objects
15966	// See https://github.com/jquery/sizzle/pull/225
15967	sortInput = null;
15968
15969	return results;
15970};
15971
15972/**
15973 * Utility function for retrieving the text value of an array of DOM nodes
15974 * @param {Array|Element} elem
15975 */
15976getText = Sizzle.getText = function( elem ) {
15977	var node,
15978		ret = "",
15979		i = 0,
15980		nodeType = elem.nodeType;
15981
15982	if ( !nodeType ) {
15983
15984		// If no nodeType, this is expected to be an array
15985		while ( ( node = elem[ i++ ] ) ) {
15986
15987			// Do not traverse comment nodes
15988			ret += getText( node );
15989		}
15990	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
15991
15992		// Use textContent for elements
15993		// innerText usage removed for consistency of new lines (jQuery #11153)
15994		if ( typeof elem.textContent === "string" ) {
15995			return elem.textContent;
15996		} else {
15997
15998			// Traverse its children
15999			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
16000				ret += getText( elem );
16001			}
16002		}
16003	} else if ( nodeType === 3 || nodeType === 4 ) {
16004		return elem.nodeValue;
16005	}
16006
16007	// Do not include comment or processing instruction nodes
16008
16009	return ret;
16010};
16011
16012Expr = Sizzle.selectors = {
16013
16014	// Can be adjusted by the user
16015	cacheLength: 50,
16016
16017	createPseudo: markFunction,
16018
16019	match: matchExpr,
16020
16021	attrHandle: {},
16022
16023	find: {},
16024
16025	relative: {
16026		">": { dir: "parentNode", first: true },
16027		" ": { dir: "parentNode" },
16028		"+": { dir: "previousSibling", first: true },
16029		"~": { dir: "previousSibling" }
16030	},
16031
16032	preFilter: {
16033		"ATTR": function( match ) {
16034			match[ 1 ] = match[ 1 ].replace( runescape, funescape );
16035
16036			// Move the given value to match[3] whether quoted or unquoted
16037			match[ 3 ] = ( match[ 3 ] || match[ 4 ] ||
16038				match[ 5 ] || "" ).replace( runescape, funescape );
16039
16040			if ( match[ 2 ] === "~=" ) {
16041				match[ 3 ] = " " + match[ 3 ] + " ";
16042			}
16043
16044			return match.slice( 0, 4 );
16045		},
16046
16047		"CHILD": function( match ) {
16048
16049			/* matches from matchExpr["CHILD"]
16050				1 type (only|nth|...)
16051				2 what (child|of-type)
16052				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
16053				4 xn-component of xn+y argument ([+-]?\d*n|)
16054				5 sign of xn-component
16055				6 x of xn-component
16056				7 sign of y-component
16057				8 y of y-component
16058			*/
16059			match[ 1 ] = match[ 1 ].toLowerCase();
16060
16061			if ( match[ 1 ].slice( 0, 3 ) === "nth" ) {
16062
16063				// nth-* requires argument
16064				if ( !match[ 3 ] ) {
16065					Sizzle.error( match[ 0 ] );
16066				}
16067
16068				// numeric x and y parameters for Expr.filter.CHILD
16069				// remember that false/true cast respectively to 0/1
16070				match[ 4 ] = +( match[ 4 ] ?
16071					match[ 5 ] + ( match[ 6 ] || 1 ) :
16072					2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) );
16073				match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" );
16074
16075				// other types prohibit arguments
16076			} else if ( match[ 3 ] ) {
16077				Sizzle.error( match[ 0 ] );
16078			}
16079
16080			return match;
16081		},
16082
16083		"PSEUDO": function( match ) {
16084			var excess,
16085				unquoted = !match[ 6 ] && match[ 2 ];
16086
16087			if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) {
16088				return null;
16089			}
16090
16091			// Accept quoted arguments as-is
16092			if ( match[ 3 ] ) {
16093				match[ 2 ] = match[ 4 ] || match[ 5 ] || "";
16094
16095			// Strip excess characters from unquoted arguments
16096			} else if ( unquoted && rpseudo.test( unquoted ) &&
16097
16098				// Get excess from tokenize (recursively)
16099				( excess = tokenize( unquoted, true ) ) &&
16100
16101				// advance to the next closing parenthesis
16102				( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) {
16103
16104				// excess is a negative index
16105				match[ 0 ] = match[ 0 ].slice( 0, excess );
16106				match[ 2 ] = unquoted.slice( 0, excess );
16107			}
16108
16109			// Return only captures needed by the pseudo filter method (type and argument)
16110			return match.slice( 0, 3 );
16111		}
16112	},
16113
16114	filter: {
16115
16116		"TAG": function( nodeNameSelector ) {
16117			var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
16118			return nodeNameSelector === "*" ?
16119				function() {
16120					return true;
16121				} :
16122				function( elem ) {
16123					return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
16124				};
16125		},
16126
16127		"CLASS": function( className ) {
16128			var pattern = classCache[ className + " " ];
16129
16130			return pattern ||
16131				( pattern = new RegExp( "(^|" + whitespace +
16132					")" + className + "(" + whitespace + "|$)" ) ) && classCache(
16133						className, function( elem ) {
16134							return pattern.test(
16135								typeof elem.className === "string" && elem.className ||
16136								typeof elem.getAttribute !== "undefined" &&
16137									elem.getAttribute( "class" ) ||
16138								""
16139							);
16140				} );
16141		},
16142
16143		"ATTR": function( name, operator, check ) {
16144			return function( elem ) {
16145				var result = Sizzle.attr( elem, name );
16146
16147				if ( result == null ) {
16148					return operator === "!=";
16149				}
16150				if ( !operator ) {
16151					return true;
16152				}
16153
16154				result += "";
16155
16156				/* eslint-disable max-len */
16157
16158				return operator === "=" ? result === check :
16159					operator === "!=" ? result !== check :
16160					operator === "^=" ? check && result.indexOf( check ) === 0 :
16161					operator === "*=" ? check && result.indexOf( check ) > -1 :
16162					operator === "$=" ? check && result.slice( -check.length ) === check :
16163					operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
16164					operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
16165					false;
16166				/* eslint-enable max-len */
16167
16168			};
16169		},
16170
16171		"CHILD": function( type, what, _argument, first, last ) {
16172			var simple = type.slice( 0, 3 ) !== "nth",
16173				forward = type.slice( -4 ) !== "last",
16174				ofType = what === "of-type";
16175
16176			return first === 1 && last === 0 ?
16177
16178				// Shortcut for :nth-*(n)
16179				function( elem ) {
16180					return !!elem.parentNode;
16181				} :
16182
16183				function( elem, _context, xml ) {
16184					var cache, uniqueCache, outerCache, node, nodeIndex, start,
16185						dir = simple !== forward ? "nextSibling" : "previousSibling",
16186						parent = elem.parentNode,
16187						name = ofType && elem.nodeName.toLowerCase(),
16188						useCache = !xml && !ofType,
16189						diff = false;
vendor: 4,427 bytes, lines 16190-16334
16190
16191					if ( parent ) {
16192
16193						// :(first|last|only)-(child|of-type)
16194						if ( simple ) {
16195							while ( dir ) {
16196								node = elem;
16197								while ( ( node = node[ dir ] ) ) {
16198									if ( ofType ?
16199										node.nodeName.toLowerCase() === name :
16200										node.nodeType === 1 ) {
16201
16202										return false;
16203									}
16204								}
16205
16206								// Reverse direction for :only-* (if we haven't yet done so)
16207								start = dir = type === "only" && !start && "nextSibling";
16208							}
16209							return true;
16210						}
16211
16212						start = [ forward ? parent.firstChild : parent.lastChild ];
16213
16214						// non-xml :nth-child(...) stores cache data on `parent`
16215						if ( forward && useCache ) {
16216
16217							// Seek `elem` from a previously-cached index
16218
16219							// ...in a gzip-friendly way
16220							node = parent;
16221							outerCache = node[ expando ] || ( node[ expando ] = {} );
16222
16223							// Support: IE <9 only
16224							// Defend against cloned attroperties (jQuery gh-1709)
16225							uniqueCache = outerCache[ node.uniqueID ] ||
16226								( outerCache[ node.uniqueID ] = {} );
16227
16228							cache = uniqueCache[ type ] || [];
16229							nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
16230							diff = nodeIndex && cache[ 2 ];
16231							node = nodeIndex && parent.childNodes[ nodeIndex ];
16232
16233							while ( ( node = ++nodeIndex && node && node[ dir ] ||
16234
16235								// Fallback to seeking `elem` from the start
16236								( diff = nodeIndex = 0 ) || start.pop() ) ) {
16237
16238								// When found, cache indexes on `parent` and break
16239								if ( node.nodeType === 1 && ++diff && node === elem ) {
16240									uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
16241									break;
16242								}
16243							}
16244
16245						} else {
16246
16247							// Use previously-cached element index if available
16248							if ( useCache ) {
16249
16250								// ...in a gzip-friendly way
16251								node = elem;
16252								outerCache = node[ expando ] || ( node[ expando ] = {} );
16253
16254								// Support: IE <9 only
16255								// Defend against cloned attroperties (jQuery gh-1709)
16256								uniqueCache = outerCache[ node.uniqueID ] ||
16257									( outerCache[ node.uniqueID ] = {} );
16258
16259								cache = uniqueCache[ type ] || [];
16260								nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
16261								diff = nodeIndex;
16262							}
16263
16264							// xml :nth-child(...)
16265							// or :nth-last-child(...) or :nth(-last)?-of-type(...)
16266							if ( diff === false ) {
16267
16268								// Use the same loop as above to seek `elem` from the start
16269								while ( ( node = ++nodeIndex && node && node[ dir ] ||
16270									( diff = nodeIndex = 0 ) || start.pop() ) ) {
16271
16272									if ( ( ofType ?
16273										node.nodeName.toLowerCase() === name :
16274										node.nodeType === 1 ) &&
16275										++diff ) {
16276
16277										// Cache the index of each encountered element
16278										if ( useCache ) {
16279											outerCache = node[ expando ] ||
16280												( node[ expando ] = {} );
16281
16282											// Support: IE <9 only
16283											// Defend against cloned attroperties (jQuery gh-1709)
16284											uniqueCache = outerCache[ node.uniqueID ] ||
16285												( outerCache[ node.uniqueID ] = {} );
16286
16287											uniqueCache[ type ] = [ dirruns, diff ];
16288										}
16289
16290										if ( node === elem ) {
16291											break;
16292										}
16293									}
16294								}
16295							}
16296						}
16297
16298						// Incorporate the offset, then check against cycle size
16299						diff -= last;
16300						return diff === first || ( diff % first === 0 && diff / first >= 0 );
16301					}
16302				};
16303		},
16304
16305		"PSEUDO": function( pseudo, argument ) {
16306
16307			// pseudo-class names are case-insensitive
16308			// http://www.w3.org/TR/selectors/#pseudo-classes
16309			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
16310			// Remember that setFilters inherits from pseudos
16311			var args,
16312				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
16313					Sizzle.error( "unsupported pseudo: " + pseudo );
16314
16315			// The user may use createPseudo to indicate that
16316			// arguments are needed to create the filter function
16317			// just as Sizzle does
16318			if ( fn[ expando ] ) {
16319				return fn( argument );
16320			}
16321
16322			// But maintain support for old signatures
16323			if ( fn.length > 1 ) {
16324				args = [ pseudo, pseudo, "", argument ];
16325				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
16326					markFunction( function( seed, matches ) {
16327						var idx,
16328							matched = fn( seed, argument ),
16329							i = matched.length;
16330						while ( i-- ) {
16331							idx = indexOf( seed, matched[ i ] );
16332							seed[ idx ] = !( matches[ idx ] = matched[ i ] );
16333						}
16334					}
16334 ) :
16335					function( elem ) {
16336						return fn( elem, 0, args );
16337					};
16338			}
16339
16340			return fn;
16341		}
16342	},
16343
16344	pseudos: {
16345
16346		// Potentially complex pseudos
16347		"not": markFunction( function( selector ) {
16348
16349			// Trim the selector passed to compile
16350			// to avoid treating leading and trailing
16351			// spaces as combinators
16352			var input = [],
16353				results = [],
16354				matcher = compile( selector.replace( rtrim, "$1" ) );
16355
16356			return matcher[ expando ] ?
16357				markFunction( function( seed, matches, _context, xml ) {
16358					var elem,
16359						unmatched = matcher( seed, null, xml, [] ),
16360						i = seed.length;
16361
16362					// Match elements unmatched by `matcher`
16363					while ( i-- ) {
16364						if ( ( elem = unmatched[ i ] ) ) {
16365							seed[ i ] = !( matches[ i ] = elem );
16366						}
16367					}
16368				} ) :
16369				function( elem, _context, xml ) {
16370					input[ 0 ] = elem;
16371					matcher( input, null, xml, results );
16372
16373					// Don't keep the element (issue #299)
16374					input[ 0 ] = null;
16375					return !results.pop();
16376				};
16377		} ),
16378
16379		"has": markFunction( function( selector ) {
16380			return function( elem ) {
16381				return Sizzle( selector, elem ).length > 0;
16382			};
16383		} ),
16384
16385		"contains": markFunction( function( text ) {
16386			text = text.replace( runescape, funescape );
16387			return function( elem ) {
16388				return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1;
16389			};
16390		} ),
16391
16392		// "Whether an element is represented by a :lang() selector
16393		// is based solely on the element's language value
16394		// being equal to the identifier C,
16395		// or beginning with the identifier C immediately followed by "-".
16396		// The matching of C against the element's language value is performed case-insensitively.
16397		// The identifier C does not have to be a valid language name."
16398		// http://www.w3.org/TR/selectors/#lang-pseudo
16399		"lang": markFunction( function( lang ) {
16400
16401			// lang value must be a valid identifier
16402			if ( !ridentifier.test( lang || "" ) ) {
16403				Sizzle.error( "unsupported lang: " + lang );
16404			}
16405			lang = lang.replace( runescape, funescape ).toLowerCase();
16406			return function( elem ) {
16407				var elemLang;
16408				do {
16409					if ( ( elemLang = documentIsHTML ?
16410						elem.lang :
16411						elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) {
16412
16413						elemLang = elemLang.toLowerCase();
16414						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
16415					}
16416				} while ( ( elem = elem.parentNode ) && elem.nodeType === 1 );
16417				return false;
16418			};
16419		} ),
16420
16421		// Miscellaneous
16422		"target": function( elem ) {
16423			var hash = window.location && window.location.hash;
16424			return hash && hash.slice( 1 ) === elem.id;
16425		},
16426
16427		"root": function( elem ) {
16428			return elem === docElem;
16429		},
16430
16431		"focus": function( elem ) {
16432			return elem === document.activeElement &&
16433				( !document.hasFocus || document.hasFocus() ) &&
16434				!!( elem.type || elem.href || ~elem.tabIndex );
16435		},
16436
16437		// Boolean properties
16438		"enabled": createDisabledPseudo( false ),
16439		"disabled": createDisabledPseudo( true ),
16440
16441		"checked": function( elem ) {
16442
16443			// In CSS3, :checked should return both checked and selected elements
16444			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
16445			var nodeName = elem.nodeName.toLowerCase();
16446			return ( nodeName === "input" && !!elem.checked ) ||
16447				( nodeName === "option" && !!elem.selected );
16448		},
16449
16450		"selected": function( elem ) {
16451
16452			// Accessing this property makes selected-by-default
16453			// options in Safari work properly
16454			if ( elem.parentNode ) {
16455				// eslint-disable-next-line no-unused-expressions
16456				elem.parentNode.selectedIndex;
16457			}
16458
16459			return elem.selected === true;
16460		},
16461
16462		// Contents
16463		"empty": function( elem ) {
16464
16465			// http://www.w3.org/TR/selectors/#empty-pseudo
16466			// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
16467			//   but not by others (comment: 8; processing instruction: 7; etc.)
16468			// nodeType < 6 works because attributes (2) do not appear as children
16469			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
16470				if ( elem.nodeType < 6 ) {
16471					return false;
16472				}
16473			}
16474			return true;
16475		},
16476
16477		"parent": function( elem ) {
16478			return !Expr.pseudos[ "empty" ]( elem );
16479		},
16480
16481		// Element/input types
16482		"header": function( elem ) {
16483			return rheader.test( elem.nodeName );
16484		},
16485
16486		"input": function( elem ) {
16487			return rinputs.test( elem.nodeName );
16488		},
16489
16490		"button": function( elem ) {
16491			var name = elem.nodeName.toLowerCase();
16492			return name === "input" && elem.type === "button" || name === "button";
16493		},
16494
16495		"text": function( elem ) {
16496			var attr;
16497			return elem.nodeName.toLowerCase() === "input" &&
16498				elem.type === "text" &&
16499
16500				// Support: IE<8
16501				// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
16502				( ( attr = elem.getAttribute( "type" ) ) == null ||
16503					attr.toLowerCase() === "text" );
16504		},
16505
16506		// Position-in-collection
16507		"first": createPositionalPseudo( function() {
16508			return [ 0 ];
16509		} ),
16510
16511		"last": createPositionalPseudo( function( _matchIndexes, length ) {
16512			return [ length - 1 ];
16513		} ),
16514
16515		"eq": createPositionalPseudo( function( _matchIndexes, length, argument ) {
16516			return [ argument < 0 ? argument + length : argument ];
16517		} ),
16518
16519		"even": createPositionalPseudo( function( matchIndexes, length ) {
16520			var i = 0;
16521			for ( ; i < length; i += 2 ) {
16522				matchIndexes.push( i );
16523			}
16524			return matchIndexes;
16525		} ),
16526
16527		"odd": createPositionalPseudo( function( matchIndexes, length ) {
16528			var i = 1;
16529			for ( ; i < length; i += 2 ) {
16530				matchIndexes.push( i );
16531			}
16532			return matchIndexes;
16533		} ),
16534
16535		"lt": createPositionalPseudo( function( matchIndexes, length, argument ) {
16536			var i = argument < 0 ?
16537				argument + length :
16538				argument > length ?
16539					length :
16540					argument;
16541			for ( ; --i >= 0; ) {
16542				matchIndexes.push( i );
16543			}
16544			return matchIndexes;
16545		} ),
16546
16547		"gt": createPositionalPseudo( function( matchIndexes, length, argument ) {
16548			var i = argument < 0 ? argument + length : argument;
16549			for ( ; ++i < length; ) {
16550				matchIndexes.push( i );
16551			}
16552			return matchIndexes;
16553		} )
16554	}
16555};
16556
16557Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ];
16558
16559// Add button/input type pseudos
16560for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
16561	Expr.pseudos[ i ] = createInputPseudo( i );
16562}
16563for ( i in { submit: true, reset: true } ) {
16564	Expr.pseudos[ i ] = createButtonPseudo( i );
16565}
16566
16567// Easy API for creating new setFilters
16568function setFilters() {}
16569setFilters.prototype = Expr.filters = Expr.pseudos;
16570Expr.setFilters = new setFilters();
16571
16572tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
16573	var matched, match, tokens, type,
16574		soFar, groups, preFilters,
16575		cached = tokenCache[ selector + " " ];
16576
16577	if ( cached ) {
16578		return parseOnly ? 0 : cached.slice( 0 );
16579	}
16580
16581	soFar = selector;
16582	groups = [];
16583	preFilters = Expr.preFilter;
16584
16585	while ( soFar ) {
16586
16587		// Comma and first run
16588		if ( !matched || ( match = rcomma.exec( soFar ) ) ) {
16589			if ( match ) {
16590
16591				// Don't consume trailing commas as valid
16592				soFar = soFar.slice( match[ 0 ].length ) || soFar;
16593			}
16594			groups.push( ( tokens = [] ) );
16595		}
16596
16597		matched = false;
16598
16599		// Combinators
16600		if ( ( match = rcombinators.exec( soFar ) ) ) {
16601			matched = match.shift();
16602			tokens.push( {
16603				value: matched,
16604
16605				// Cast descendant combinators to space
16606				type: match[ 0 ].replace( rtrim, " " )
16607			} );
16608			soFar = soFar.slice( matched.length );
16609		}
16610
16611		// Filters
16612		for ( type in Expr.filter ) {
16613			if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] ||
16614				( match = preFilters[ type ]( match ) ) ) ) {
16615				matched = match.shift();
16616				tokens.push( {
16617					value: matched,
16618					type: type,
16619					matches: match
16620				} );
16621				soFar = soFar.slice( matched.length );
16622			}
16623		}
16624
16625		if ( !matched ) {
16626			break;
16627		}
16628	}
16629
16630	// Return the length of the invalid excess
16631	// if we're just parsing
16632	// Otherwise, throw an error or return tokens
16633	return parseOnly ?
16634		soFar.length :
16635		soFar ?
16636			Sizzle.error( selector ) :
16637
16638			// Cache the tokens
16639			tokenCache( selector, groups ).slice( 0 );
16640};
16641
16642function toSelector( tokens ) {
16643	var i = 0,
16644		len = tokens.length,
16645		selector = "";
16646	for ( ; i < len; i++ ) {
16647		selector += tokens[ i ].value;
16648	}
16649	return selector;
16650}
16651
16652function addCombinator( matcher, combinator, base ) {
16653	var dir = combinator.dir,
16654		skip = combinator.next,
16655		key = skip || dir,
16656		checkNonElements = base && key === "parentNode",
16657		doneName = done++;
16658
16659	return combinator.first ?
16660
16661		// Check against closest ancestor/preceding element
16662		function( elem, context, xml ) {
16663			while ( ( elem = elem[ dir ] ) ) {
16664				if ( elem.nodeType === 1 || checkNonElements ) {
16665					return matcher( elem, context, xml );
16666				}
16667			}
16668			return false;
16669		} :
16670
16671		// Check against all ancestor/preceding elements
16672		function( elem, context, xml ) {
16673			var oldCache, uniqueCache, outerCache,
16674				newCache = [ dirruns, doneName ];
16675
16676			// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
16677			if ( xml ) {
16678				while ( ( elem = elem[ dir ] ) ) {
16679					if ( elem.nodeType === 1 || checkNonElements ) {
16680						if ( matcher( elem, context, xml ) ) {
16681							return true;
16682						}
16683					}
16684				}
16685			} else {
16686				while ( ( elem = elem[ dir ] ) ) {
16687					if ( elem.nodeType === 1 || checkNonElements ) {
16688						outerCache = elem[ expando ] || ( elem[ expando ] = {} );
16689
vendor: 12,981 bytes, lines 16690-17136
16690						// Support: IE <9 only
16691						// Defend against cloned attroperties (jQuery gh-1709)
16692						uniqueCache = outerCache[ elem.uniqueID ] ||
16693							( outerCache[ elem.uniqueID ] = {} );
16694
16695						if ( skip && skip === elem.nodeName.toLowerCase() ) {
16696							elem = elem[ dir ] || elem;
16697						} else if ( ( oldCache = uniqueCache[ key ] ) &&
16698							oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
16699
16700							// Assign to newCache so results back-propagate to previous elements
16701							return ( newCache[ 2 ] = oldCache[ 2 ] );
16702						} else {
16703
16704							// Reuse newcache so results back-propagate to previous elements
16705							uniqueCache[ key ] = newCache;
16706
16707							// A match means we're done; a fail means we have to keep checking
16708							if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) {
16709								return true;
16710							}
16711						}
16712					}
16713				}
16714			}
16715			return false;
16716		};
16717}
16718
16719function elementMatcher( matchers ) {
16720	return matchers.length > 1 ?
16721		function( elem, context, xml ) {
16722			var i = matchers.length;
16723			while ( i-- ) {
16724				if ( !matchers[ i ]( elem, context, xml ) ) {
16725					return false;
16726				}
16727			}
16728			return true;
16729		} :
16730		matchers[ 0 ];
16731}
16732
16733function multipleContexts( selector, contexts, results ) {
16734	var i = 0,
16735		len = contexts.length;
16736	for ( ; i < len; i++ ) {
16737		Sizzle( selector, contexts[ i ], results );
16738	}
16739	return results;
16740}
16741
16742function condense( unmatched, map, filter, context, xml ) {
16743	var elem,
16744		newUnmatched = [],
16745		i = 0,
16746		len = unmatched.length,
16747		mapped = map != null;
16748
16749	for ( ; i < len; i++ ) {
16750		if ( ( elem = unmatched[ i ] ) ) {
16751			if ( !filter || filter( elem, context, xml ) ) {
16752				newUnmatched.push( elem );
16753				if ( mapped ) {
16754					map.push( i );
16755				}
16756			}
16757		}
16758	}
16759
16760	return newUnmatched;
16761}
16762
16763function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
16764	if ( postFilter && !postFilter[ expando ] ) {
16765		postFilter = setMatcher( postFilter );
16766	}
16767	if ( postFinder && !postFinder[ expando ] ) {
16768		postFinder = setMatcher( postFinder, postSelector );
16769	}
16770	return markFunction( function( seed, results, context, xml ) {
16771		var temp, i, elem,
16772			preMap = [],
16773			postMap = [],
16774			preexisting = results.length,
16775
16776			// Get initial elements from seed or context
16777			elems = seed || multipleContexts(
16778				selector || "*",
16779				context.nodeType ? [ context ] : context,
16780				[]
16781			),
16782
16783			// Prefilter to get matcher input, preserving a map for seed-results synchronization
16784			matcherIn = preFilter && ( seed || !selector ) ?
16785				condense( elems, preMap, preFilter, context, xml ) :
16786				elems,
16787
16788			matcherOut = matcher ?
16789
16790				// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
16791				postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
16792
16793					// ...intermediate processing is necessary
16794					[] :
16795
16796					// ...otherwise use results directly
16797					results :
16798				matcherIn;
16799
16800		// Find primary matches
16801		if ( matcher ) {
16802			matcher( matcherIn, matcherOut, context, xml );
16803		}
16804
16805		// Apply postFilter
16806		if ( postFilter ) {
16807			temp = condense( matcherOut, postMap );
16808			postFilter( temp, [], context, xml );
16809
16810			// Un-match failing elements by moving them back to matcherIn
16811			i = temp.length;
16812			while ( i-- ) {
16813				if ( ( elem = temp[ i ] ) ) {
16814					matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem );
16815				}
16816			}
16817		}
16818
16819		if ( seed ) {
16820			if ( postFinder || preFilter ) {
16821				if ( postFinder ) {
16822
16823					// Get the final matcherOut by condensing this intermediate into postFinder contexts
16824					temp = [];
16825					i = matcherOut.length;
16826					while ( i-- ) {
16827						if ( ( elem = matcherOut[ i ] ) ) {
16828
16829							// Restore matcherIn since elem is not yet a final match
16830							temp.push( ( matcherIn[ i ] = elem ) );
16831						}
16832					}
16833					postFinder( null, ( matcherOut = [] ), temp, xml );
16834				}
16835
16836				// Move matched elements from seed to results to keep them synchronized
16837				i = matcherOut.length;
16838				while ( i-- ) {
16839					if ( ( elem = matcherOut[ i ] ) &&
16840						( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) {
16841
16842						seed[ temp ] = !( results[ temp ] = elem );
16843					}
16844				}
16845			}
16846
16847		// Add elements to results, through postFinder if defined
16848		} else {
16849			matcherOut = condense(
16850				matcherOut === results ?
16851					matcherOut.splice( preexisting, matcherOut.length ) :
16852					matcherOut
16853			);
16854			if ( postFinder ) {
16855				postFinder( null, results, matcherOut, xml );
16856			} else {
16857				push.apply( results, matcherOut );
16858			}
16859		}
16860	} );
16861}
16862
16863function matcherFromTokens( tokens ) {
16864	var checkContext, matcher, j,
16865		len = tokens.length,
16866		leadingRelative = Expr.relative[ tokens[ 0 ].type ],
16867		implicitRelative = leadingRelative || Expr.relative[ " " ],
16868		i = leadingRelative ? 1 : 0,
16869
16870		// The foundational matcher ensures that elements are reachable from top-level context(s)
16871		matchContext = addCombinator( function( elem ) {
16872			return elem === checkContext;
16873		}, implicitRelative, true ),
16874		matchAnyContext = addCombinator( function( elem ) {
16875			return indexOf( checkContext, elem ) > -1;
16876		}, implicitRelative, true ),
16877		matchers = [ function( elem, context, xml ) {
16878			var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
16879				( checkContext = context ).nodeType ?
16880					matchContext( elem, context, xml ) :
16881					matchAnyContext( elem, context, xml ) );
16882
16883			// Avoid hanging onto element (issue #299)
16884			checkContext = null;
16885			return ret;
16886		} ];
16887
16888	for ( ; i < len; i++ ) {
16889		if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) {
16890			matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
16891		} else {
16892			matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches );
16893
16894			// Return special upon seeing a positional matcher
16895			if ( matcher[ expando ] ) {
16896
16897				// Find the next relative operator (if any) for proper handling
16898				j = ++i;
16899				for ( ; j < len; j++ ) {
16900					if ( Expr.relative[ tokens[ j ].type ] ) {
16901						break;
16902					}
16903				}
16904				return setMatcher(
16905					i > 1 && elementMatcher( matchers ),
16906					i > 1 && toSelector(
16907
16908					// If the preceding token was a descendant combinator, insert an implicit any-element `*`
16909					tokens
16910						.slice( 0, i - 1 )
16911						.concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } )
16912					).replace( rtrim, "$1" ),
16913					matcher,
16914					i < j && matcherFromTokens( tokens.slice( i, j ) ),
16915					j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ),
16916					j < len && toSelector( tokens )
16917				);
16918			}
16919			matchers.push( matcher );
16920		}
16921	}
16922
16923	return elementMatcher( matchers );
16924}
16925
16926function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
16927	var bySet = setMatchers.length > 0,
16928		byElement = elementMatchers.length > 0,
16929		superMatcher = function( seed, context, xml, results, outermost ) {
16930			var elem, j, matcher,
16931				matchedCount = 0,
16932				i = "0",
16933				unmatched = seed && [],
16934				setMatched = [],
16935				contextBackup = outermostContext,
16936
16937				// We must always have either seed elements or outermost context
16938				elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ),
16939
16940				// Use integer dirruns iff this is the outermost matcher
16941				dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ),
16942				len = elems.length;
16943
16944			if ( outermost ) {
16945
16946				// Support: IE 11+, Edge 17 - 18+
16947				// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
16948				// two documents; shallow comparisons work.
16949				// eslint-disable-next-line eqeqeq
16950				outermostContext = context == document || context || outermost;
16951			}
16952
16953			// Add elements passing elementMatchers directly to results
16954			// Support: IE<9, Safari
16955			// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
16956			for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) {
16957				if ( byElement && elem ) {
16958					j = 0;
16959
16960					// Support: IE 11+, Edge 17 - 18+
16961					// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
16962					// two documents; shallow comparisons work.
16963					// eslint-disable-next-line eqeqeq
16964					if ( !context && elem.ownerDocument != document ) {
16965						setDocument( elem );
16966						xml = !documentIsHTML;
16967					}
16968					while ( ( matcher = elementMatchers[ j++ ] ) ) {
16969						if ( matcher( elem, context || document, xml ) ) {
16970							results.push( elem );
16971							break;
16972						}
16973					}
16974					if ( outermost ) {
16975						dirruns = dirrunsUnique;
16976					}
16977				}
16978
16979				// Track unmatched elements for set filters
16980				if ( bySet ) {
16981
16982					// They will have gone through all possible matchers
16983					if ( ( elem = !matcher && elem ) ) {
16984						matchedCount--;
16985					}
16986
16987					// Lengthen the array for every element, matched or not
16988					if ( seed ) {
16989						unmatched.push( elem );
16990					}
16991				}
16992			}
16993
16994			// `i` is now the count of elements visited above, and adding it to `matchedCount`
16995			// makes the latter nonnegative.
16996			matchedCount += i;
16997
16998			// Apply set filters to unmatched elements
16999			// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
17000			// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
17001			// no element matchers and no seed.
17002			// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
17003			// case, which will result in a "00" `matchedCount` that differs from `i` but is also
17004			// numerically zero.
17005			if ( bySet && i !== matchedCount ) {
17006				j = 0;
17007				while ( ( matcher = setMatchers[ j++ ] ) ) {
17008					matcher( unmatched, setMatched, context, xml );
17009				}
17010
17011				if ( seed ) {
17012
17013					// Reintegrate element matches to eliminate the need for sorting
17014					if ( matchedCount > 0 ) {
17015						while ( i-- ) {
17016							if ( !( unmatched[ i ] || setMatched[ i ] ) ) {
17017								setMatched[ i ] = pop.call( results );
17018							}
17019						}
17020					}
17021
17022					// Discard index placeholder values to get only actual matches
17023					setMatched = condense( setMatched );
17024				}
17025
17026				// Add matches to results
17027				push.apply( results, setMatched );
17028
17029				// Seedless set matches succeeding multiple successful matchers stipulate sorting
17030				if ( outermost && !seed && setMatched.length > 0 &&
17031					( matchedCount + setMatchers.length ) > 1 ) {
17032
17033					Sizzle.uniqueSort( results );
17034				}
17035			}
17036
17037			// Override manipulation of globals by nested matchers
17038			if ( outermost ) {
17039				dirruns = dirrunsUnique;
17040				outermostContext = contextBackup;
17041			}
17042
17043			return unmatched;
17044		};
17045
17046	return bySet ?
17047		markFunction( superMatcher ) :
17048		superMatcher;
17049}
17050
17051compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
17052	var i,
17053		setMatchers = [],
17054		elementMatchers = [],
17055		cached = compilerCache[ selector + " " ];
17056
17057	if ( !cached ) {
17058
17059		// Generate a function of recursive functions that can be used to check each element
17060		if ( !match ) {
17061			match = tokenize( selector );
17062		}
17063		i = match.length;
17064		while ( i-- ) {
17065			cached = matcherFromTokens( match[ i ] );
17066			if ( cached[ expando ] ) {
17067				setMatchers.push( cached );
17068			} else {
17069				elementMatchers.push( cached );
17070			}
17071		}
17072
17073		// Cache the compiled function
17074		cached = compilerCache(
17075			selector,
17076			matcherFromGroupMatchers( elementMatchers, setMatchers )
17077		);
17078
17079		// Save selector and tokenization
17080		cached.selector = selector;
17081	}
17082	return cached;
17083};
17084
17085/**
17086 * A low-level selection function that works with Sizzle's compiled
17087 *  selector functions
17088 * @param {String|Function} selector A selector or a pre-compiled
17089 *  selector function built with Sizzle.compile
17090 * @param {Element} context
17091 * @param {Array} [results]
17092 * @param {Array} [seed] A set of elements to match against
17093 */
17094select = Sizzle.select = function( selector, context, results, seed ) {
17095	var i, tokens, token, type, find,
17096		compiled = typeof selector === "function" && selector,
17097		match = !seed && tokenize( ( selector = compiled.selector || selector ) );
17098
17099	results = results || [];
17100
17101	// Try to minimize operations if there is only one selector in the list and no seed
17102	// (the latter of which guarantees us context)
17103	if ( match.length === 1 ) {
17104
17105		// Reduce context if the leading compound selector is an ID
17106		tokens = match[ 0 ] = match[ 0 ].slice( 0 );
17107		if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" &&
17108			context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) {
17109
17110			context = ( Expr.find[ "ID" ]( token.matches[ 0 ]
17111				.replace( runescape, funescape ), context ) || [] )[ 0 ];
17112			if ( !context ) {
17113				return results;
17114
17115			// Precompiled matchers will still verify ancestry, so step up a level
17116			} else if ( compiled ) {
17117				context = context.parentNode;
17118			}
17119
17120			selector = selector.slice( tokens.shift().value.length );
17121		}
17122
17123		// Fetch a seed set for right-to-left matching
17124		i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length;
17125		while ( i-- ) {
17126			token = tokens[ i ];
17127
17128			// Abort if we hit a combinator
17129			if ( Expr.relative[ ( type = token.type ) ] ) {
17130				break;
17131			}
17132			if ( ( find = Expr.find[ type ] ) ) {
17133
17134				// Search, expanding context for leading sibling combinators
17135				if ( ( seed = find(
17136					token.matches[ 0 ].replace( runescape, funescape ),
17137					rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) ||
17138						context
17139				) ) ) {
17140
17141					// If seed is empty or no tokens remain, we can return early
17142					tokens.splice( i, 1 );
17143					selector = seed.length && toSelector( tokens );
17144					if ( !selector ) {
17145						push.apply( results, seed );
17146						return results;
17147					}
17148
17149					break;
17150				}
17151			}
17152		}
17153	}
17154
17155	// Compile and execute a filtering function if one is not provided
17156	// Provide `match` to avoid retokenization if we modified the selector above
17157	( compiled || compile( selector, match ) )(
17158		seed,
17159		context,
17160		!documentIsHTML,
17161		results,
17162		!context || rsibling.test( selector ) && testContext( context.parentNode ) || context
17163	);
17164	return results;
17165};
17166
17167// One-time assignments
17168
17169// Sort stability
17170support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando;
17171
17172// Support: Chrome 14-35+
17173// Always assume duplicates if they aren't passed to the comparison function
17174support.detectDuplicates = !!hasDuplicate;
17175
17176// Initialize against the default document
17177setDocument();
17178
17179// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
17180// Detached nodes confoundingly follow *each other*
17181support.sortDetached = assert( function( el ) {
17182
17183	// Should return 1, but returns 4 (following)
17184	return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1;
17185} );
17186
17187// Support: IE<8
17188// Prevent attribute/property "interpolation"
17189// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
17190if ( !assert( function( el ) {
17191	el.innerHTML = "<a href='#'></a>";
17192	return el.firstChild.getAttribute( "href" ) === "#";
17193} ) ) {
17194	addHandle( "type|href|height|width", function( elem, name, isXML ) {
17195		if ( !isXML ) {
17196			return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
17197		}
17198	} );
17199}
17200
17201// Support: IE<9
17202// Use defaultValue in place of getAttribute("value")
17203if ( !support.attributes || !assert( function( el ) {
17204	el.innerHTML = "<input/>";
17205	el.firstChild.setAttribute( "value", "" );
17206	return el.firstChild.getAttribute( "value" ) === "";
17207} ) ) {
17208	addHandle( "value", function( elem, _name, isXML ) {
17209		if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
17210			return elem.defaultValue;
17211		}
17212	} );
17213}
17214
17215// Support: IE<9
17216// Use getAttributeNode to fetch booleans when getAttribute lies
17217if ( !assert( function( el ) {
17218	return el.getAttribute( "disabled" ) == null;
17219} ) ) {
17220	addHandle( booleans, function( elem, name, isXML ) {
17221		var val;
17222		if ( !isXML ) {
17223			return elem[ name ] === true ? name.toLowerCase() :
17224				( val = elem.getAttributeNode( name ) ) && val.specified ?
17225					val.value :
17226					null;
17227		}
17228	} );
17229}
17230
17231return Sizzle;
17232
17233} )( window );
17234
17235
17236
17237jQuery.find = Sizzle;
17238jQuery.expr = Sizzle.selectors;
17239
17240// Deprecated
17241jQuery.expr[ ":" ] = jQuery.expr.pseudos;
17242jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
17243jQuery.text = Sizzle.getText;
17244jQuery.isXMLDoc = Sizzle.isXML;
17245jQuery.contains = Sizzle.contains;
17246jQuery.escapeSelector = Sizzle.escape;
17247
17248
17249
17250
17251var dir = function( elem, dir, until ) {
17252	var matched = [],
17253		truncate = until !== undefined;
17254
17255	while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
17256		if ( elem.nodeType === 1 ) {
17257			if ( truncate && jQuery( elem ).is( until ) ) {
17258				break;
17259			}
17260			matched.push( elem );
17261		}
17262	}
17263	return matched;
17264};
17265
17266
17267var siblings = function( n, elem ) {
17268	var matched = [];
17269
17270	for ( ; n; n = n.nextSibling ) {
17271		if ( n.nodeType === 1 && n !== elem ) {
17272			matched.push( n );
17273		}
17274	}
17275
17276	return matched;
17277};
17278
17279
17280var rneedsContext = jQuery.expr.match.needsContext;
17281
17282
17283
17284function nodeName( elem, name ) {
17285
17286  return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
17287
17288};
17289var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
17290
17291
17292
17293// Implement the identical functionality for filter and not
17294function winnow( elements, qualifier, not ) {
17295	if ( isFunction( qualifier ) ) {
17296		return jQuery.grep( elements, function( elem, i ) {
17297			return !!qualifier.call( elem, i, elem ) !== not;
17298		} );
17299	}
17300
17301	// Single element
17302	if ( qualifier.nodeType ) {
17303		return jQuery.grep( elements, function( elem ) {
17304			return ( elem === qualifier ) !== not;
17305		} );
17306	}
17307
17308	// Arraylike of elements (jQuery, arguments, Array)
17309	if ( typeof qualifier !== "string" ) {
17310		return jQuery.grep( elements, function( elem ) {
17311			return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
17312		} );
17313	}
17314
17315	// Filtered directly for both simple and complex selectors
17316	return jQuery.filter( qualifier, elements, not );
17317}
17318
17319jQuery.filter = function( expr, elems, not ) {
17320	var elem = elems[ 0 ];
17321
17322	if ( not ) {
17323		expr = ":not(" + expr + ")";
17324	}
17325
17326	if ( elems.length === 1 && elem.nodeType === 1 ) {
17327		return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
17328	}
17329
17330	return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
17331		return elem.nodeType === 1;
17332	} ) );
17333};
17334
17335jQuery.fn.extend( {
17336	find: function( selector ) {
17337		var i, ret,
17338			len = this.length,
17339			self = this;
17340
17341		if ( typeof selector !== "string" ) {
vendor: 4,662 bytes, lines 17342-17527
17342			return this.pushStack( jQuery( selector ).filter( function() {
17343				for ( i = 0; i < len; i++ ) {
17344					if ( jQuery.contains( self[ i ], this ) ) {
17345						return true;
17346					}
17347				}
17348			} ) );
17349		}
17350
17351		ret = this.pushStack( [] );
17352
17353		for ( i = 0; i < len; i++ ) {
17354			jQuery.find( selector, self[ i ], ret );
17355		}
17356
17357		return len > 1 ? jQuery.uniqueSort( ret ) : ret;
17358	},
17359	filter: function( selector ) {
17360		return this.pushStack( winnow( this, selector || [], false ) );
17361	},
17362	not: function( selector ) {
17363		return this.pushStack( winnow( this, selector || [], true ) );
17364	},
17365	is: function( selector ) {
17366		return !!winnow(
17367			this,
17368
17369			// If this is a positional/relative selector, check membership in the returned set
17370			// so $("p:first").is("p:last") won't return true for a doc with two "p".
17371			typeof selector === "string" && rneedsContext.test( selector ) ?
17372				jQuery( selector ) :
17373				selector || [],
17374			false
17375		).length;
17376	}
17377} );
17378
17379
17380// Initialize a jQuery object
17381
17382
17383// A central reference to the root jQuery(document)
17384var rootjQuery,
17385
17386	// A simple way to check for HTML strings
17387	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
17388	// Strict HTML recognition (#11290: must start with <)
17389	// Shortcut simple #id case for speed
17390	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
17391
17392	init = jQuery.fn.init = function( selector, context, root ) {
17393		var match, elem;
17394
17395		// HANDLE: $(""), $(null), $(undefined), $(false)
17396		if ( !selector ) {
17397			return this;
17398		}
17399
17400		// Method init() accepts an alternate rootjQuery
17401		// so migrate can support jQuery.sub (gh-2101)
17402		root = root || rootjQuery;
17403
17404		// Handle HTML strings
17405		if ( typeof selector === "string" ) {
17406			if ( selector[ 0 ] === "<" &&
17407				selector[ selector.length - 1 ] === ">" &&
17408				selector.length >= 3 ) {
17409
17410				// Assume that strings that start and end with <> are HTML and skip the regex check
17411				match = [ null, selector, null ];
17412
17413			} else {
17414				match = rquickExpr.exec( selector );
17415			}
17416
17417			// Match html or make sure no context is specified for #id
17418			if ( match && ( match[ 1 ] || !context ) ) {
17419
17420				// HANDLE: $(html) -> $(array)
17421				if ( match[ 1 ] ) {
17422					context = context instanceof jQuery ? context[ 0 ] : context;
17423
17424					// Option to run scripts is true for back-compat
17425					// Intentionally let the error be thrown if parseHTML is not present
17426					jQuery.merge( this, jQuery.parseHTML(
17427						match[ 1 ],
17428						context && context.nodeType ? context.ownerDocument || context : document,
17429						true
17430					) );
17431
17432					// HANDLE: $(html, props)
17433					if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
17434						for ( match in context ) {
17435
17436							// Properties of context are called as methods if possible
17437							if ( isFunction( this[ match ] ) ) {
17438								this[ match ]( context[ match ] );
17439
17440							// ...and otherwise set as attributes
17441							} else {
17442								this.attr( match, context[ match ] );
17443							}
17444						}
17445					}
17446
17447					return this;
17448
17449				// HANDLE: $(#id)
17450				} else {
17451					elem = document.getElementById( match[ 2 ] );
17452
17453					if ( elem ) {
17454
17455						// Inject the element directly into the jQuery object
17456						this[ 0 ] = elem;
17457						this.length = 1;
17458					}
17459					return this;
17460				}
17461
17462			// HANDLE: $(expr, $(...))
17463			} else if ( !context || context.jquery ) {
17464				return ( context || root ).find( selector );
17465
17466			// HANDLE: $(expr, context)
17467			// (which is just equivalent to: $(context).find(expr)
17468			} else {
17469				return this.constructor( context ).find( selector );
17470			}
17471
17472		// HANDLE: $(DOMElement)
17473		} else if ( selector.nodeType ) {
17474			this[ 0 ] = selector;
17475			this.length = 1;
17476			return this;
17477
17478		// HANDLE: $(function)
17479		// Shortcut for document ready
17480		} else if ( isFunction( selector ) ) {
17481			return root.ready !== undefined ?
17482				root.ready( selector ) :
17483
17484				// Execute immediately if ready is not present
17485				selector( jQuery );
17486		}
17487
17488		return jQuery.makeArray( selector, this );
17489	};
17490
17491// Give the init function the jQuery prototype for later instantiation
17492init.prototype = jQuery.fn;
17493
17494// Initialize central reference
17495rootjQuery = jQuery( document );
17496
17497
17498var rparentsprev = /^(?:parents|prev(?:Until|All))/,
17499
17500	// Methods guaranteed to produce a unique set when starting from a unique set
17501	guaranteedUnique = {
17502		children: true,
17503		contents: true,
17504		next: true,
17505		prev: true
17506	};
17507
17508jQuery.fn.extend( {
17509	has: function( target ) {
17510		var targets = jQuery( target, this ),
17511			l = targets.length;
17512
17513		return this.filter( function() {
17514			var i = 0;
17515			for ( ; i < l; i++ ) {
17516				if ( jQuery.contains( this, targets[ i ] ) ) {
17517					return true;
17518				}
17519			}
17520		} );
17521	},
17522
17523	closest: function( selectors, context ) {
17524		var cur,
17525			i = 0,
17526			l = this.length,
17527			matched = [],
17528			targets = typeof selectors !== "string" && jQuery( selectors );
17529
17530		// Positional selectors never match, since there's no _selection_ context
17531		if ( !rneedsContext.test( selectors ) ) {
17532			for ( ; i < l; i++ ) {
17533				for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
17534
17535					// Always skip document fragments
17536					if ( cur.nodeType < 11 && ( targets ?
17537						targets.index( cur ) > -1 :
17538
17539						// Don't pass non-elements to Sizzle
vendor: 14,565 bytes, lines 17539-18094
17539
17540						cur.nodeType === 1 &&
17541							jQuery.find.matchesSelector( cur, selectors ) ) ) {
17542
17543						matched.push( cur );
17544						break;
17545					}
17546				}
17547			}
17548		}
17549
17550		return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
17551	},
17552
17553	// Determine the position of an element within the set
17554	index: function( elem ) {
17555
17556		// No argument, return index in parent
17557		if ( !elem ) {
17558			return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
17559		}
17560
17561		// Index in selector
17562		if ( typeof elem === "string" ) {
17563			return indexOf.call( jQuery( elem ), this[ 0 ] );
17564		}
17565
17566		// Locate the position of the desired element
17567		return indexOf.call( this,
17568
17569			// If it receives a jQuery object, the first element is used
17570			elem.jquery ? elem[ 0 ] : elem
17571		);
17572	},
17573
17574	add: function( selector, context ) {
17575		return this.pushStack(
17576			jQuery.uniqueSort(
17577				jQuery.merge( this.get(), jQuery( selector, context ) )
17578			)
17579		);
17580	},
17581
17582	addBack: function( selector ) {
17583		return this.add( selector == null ?
17584			this.prevObject : this.prevObject.filter( selector )
17585		);
17586	}
17587} );
17588
17589function sibling( cur, dir ) {
17590	while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
17591	return cur;
17592}
17593
17594jQuery.each( {
17595	parent: function( elem ) {
17596		var parent = elem.parentNode;
17597		return parent && parent.nodeType !== 11 ? parent : null;
17598	},
17599	parents: function( elem ) {
17600		return dir( elem, "parentNode" );
17601	},
17602	parentsUntil: function( elem, _i, until ) {
17603		return dir( elem, "parentNode", until );
17604	},
17605	next: function( elem ) {
17606		return sibling( elem, "nextSibling" );
17607	},
17608	prev: function( elem ) {
17609		return sibling( elem, "previousSibling" );
17610	},
17611	nextAll: function( elem ) {
17612		return dir( elem, "nextSibling" );
17613	},
17614	prevAll: function( elem ) {
17615		return dir( elem, "previousSibling" );
17616	},
17617	nextUntil: function( elem, _i, until ) {
17618		return dir( elem, "nextSibling", until );
17619	},
17620	prevUntil: function( elem, _i, until ) {
17621		return dir( elem, "previousSibling", until );
17622	},
17623	siblings: function( elem ) {
17624		return siblings( ( elem.parentNode || {} ).firstChild, elem );
17625	},
17626	children: function( elem ) {
17627		return siblings( elem.firstChild );
17628	},
17629	contents: function( elem ) {
17630		if ( elem.contentDocument != null &&
17631
17632			// Support: IE 11+
17633			// <object> elements with no `data` attribute has an object
17634			// `contentDocument` with a `null` prototype.
17635			getProto( elem.contentDocument ) ) {
17636
17637			return elem.contentDocument;
17638		}
17639
17640		// Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
17641		// Treat the template element as a regular one in browsers that
17642		// don't support it.
17643		if ( nodeName( elem, "template" ) ) {
17644			elem = elem.content || elem;
17645		}
17646
17647		return jQuery.merge( [], elem.childNodes );
17648	}
17649}, function( name, fn ) {
17650	jQuery.fn[ name ] = function( until, selector ) {
17651		var matched = jQuery.map( this, fn, until );
17652
17653		if ( name.slice( -5 ) !== "Until" ) {
17654			selector = until;
17655		}
17656
17657		if ( selector && typeof selector === "string" ) {
17658			matched = jQuery.filter( selector, matched );
17659		}
17660
17661		if ( this.length > 1 ) {
17662
17663			// Remove duplicates
17664			if ( !guaranteedUnique[ name ] ) {
17665				jQuery.uniqueSort( matched );
17666			}
17667
17668			// Reverse order for parents* and prev-derivatives
17669			if ( rparentsprev.test( name ) ) {
17670				matched.reverse();
17671			}
17672		}
17673
17674		return this.pushStack( matched );
17675	};
17676} );
17677var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );
17678
17679
17680
17681// Convert String-formatted options into Object-formatted ones
17682function createOptions( options ) {
17683	var object = {};
17684	jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
17685		object[ flag ] = true;
17686	} );
17687	return object;
17688}
17689
17690/*
17691 * Create a callback list using the following parameters:
17692 *
17693 *	options: an optional list of space-separated options that will change how
17694 *			the callback list behaves or a more traditional option object
17695 *
17696 * By default a callback list will act like an event callback list and can be
17697 * "fired" multiple times.
17698 *
17699 * Possible options:
17700 *
17701 *	once:			will ensure the callback list can only be fired once (like a Deferred)
17702 *
17703 *	memory:			will keep track of previous values and will call any callback added
17704 *					after the list has been fired right away with the latest "memorized"
17705 *					values (like a Deferred)
17706 *
17707 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
17708 *
17709 *	stopOnFalse:	interrupt callings when a callback returns false
17710 *
17711 */
17712jQuery.Callbacks = function( options ) {
17713
17714	// Convert options from String-formatted to Object-formatted if needed
17715	// (we check in cache first)
17716	options = typeof options === "string" ?
17717		createOptions( options ) :
17718		jQuery.extend( {}, options );
17719
17720	var // Flag to know if list is currently firing
17721		firing,
17722
17723		// Last fire value for non-forgettable lists
17724		memory,
17725
17726		// Flag to know if list was already fired
17727		fired,
17728
17729		// Flag to prevent firing
17730		locked,
17731
17732		// Actual callback list
17733		list = [],
17734
17735		// Queue of execution data for repeatable lists
17736		queue = [],
17737
17738		// Index of currently firing callback (modified by add/remove as needed)
17739		firingIndex = -1,
17740
17741		// Fire callbacks
17742		fire = function() {
17743
17744			// Enforce single-firing
17745			locked = locked || options.once;
17746
17747			// Execute callbacks for all pending executions,
17748			// respecting firingIndex overrides and runtime changes
17749			fired = firing = true;
17750			for ( ; queue.length; firingIndex = -1 ) {
17751				memory = queue.shift();
17752				while ( ++firingIndex < list.length ) {
17753
17754					// Run callback and check for early termination
17755					if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
17756						options.stopOnFalse ) {
17757
17758						// Jump to end and forget the data so .add doesn't re-fire
17759						firingIndex = list.length;
17760						memory = false;
17761					}
17762				}
17763			}
17764
17765			// Forget the data if we're done with it
17766			if ( !options.memory ) {
17767				memory = false;
17768			}
17769
17770			firing = false;
17771
17772			// Clean up if we're done firing for good
17773			if ( locked ) {
17774
17775				// Keep an empty list if we have data for future add calls
17776				if ( memory ) {
17777					list = [];
17778
17779				// Otherwise, this object is spent
17780				} else {
17781					list = "";
17782				}
17783			}
17784		},
17785
17786		// Actual Callbacks object
17787		self = {
17788
17789			// Add a callback or a collection of callbacks to the list
17790			add: function() {
17791				if ( list ) {
17792
17793					// If we have memory from a past run, we should fire after adding
17794					if ( memory && !firing ) {
17795						firingIndex = list.length - 1;
17796						queue.push( memory );
17797					}
17798
17799					( function add( args ) {
17800						jQuery.each( args, function( _, arg ) {
17801							if ( isFunction( arg ) ) {
17802								if ( !options.unique || !self.has( arg ) ) {
17803									list.push( arg );
17804								}
17805							} else if ( arg && arg.length && toType( arg ) !== "string" ) {
17806
17807								// Inspect recursively
17808								add( arg );
17809							}
17810						} );
17811					} )( arguments );
17812
17813					if ( memory && !firing ) {
17814						fire();
17815					}
17816				}
17817				return this;
17818			},
17819
17820			// Remove a callback from the list
17821			remove: function() {
17822				jQuery.each( arguments, function( _, arg ) {
17823					var index;
17824					while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
17825						list.splice( index, 1 );
17826
17827						// Handle firing indexes
17828						if ( index <= firingIndex ) {
17829							firingIndex--;
17830						}
17831					}
17832				} );
17833				return this;
17834			},
17835
17836			// Check if a given callback is in the list.
17837			// If no argument is given, return whether or not list has callbacks attached.
17838			has: function( fn ) {
17839				return fn ?
17840					jQuery.inArray( fn, list ) > -1 :
17841					list.length > 0;
17842			},
17843
17844			// Remove all callbacks from the list
17845			empty: function() {
17846				if ( list ) {
17847					list = [];
17848				}
17849				return this;
17850			},
17851
17852			// Disable .fire and .add
17853			// Abort any current/pending executions
17854			// Clear all callbacks and values
17855			disable: function() {
17856				locked = queue = [];
17857				list = memory = "";
17858				return this;
17859			},
17860			disabled: function() {
17861				return !list;
17862			},
17863
17864			// Disable .fire
17865			// Also disable .add unless we have memory (since it would have no effect)
17866			// Abort any pending executions
17867			lock: function() {
17868				locked = queue = [];
17869				if ( !memory && !firing ) {
17870					list = memory = "";
17871				}
17872				return this;
17873			},
17874			locked: function() {
17875				return !!locked;
17876			},
17877
17878			// Call all callbacks with the given context and arguments
17879			fireWith: function( context, args ) {
17880				if ( !locked ) {
17881					args = args || [];
17882					args = [ context, args.slice ? args.slice() : args ];
17883					queue.push( args );
17884					if ( !firing ) {
17885						fire();
17886					}
17887				}
17888				return this;
17889			},
17890
17891			// Call all the callbacks with the given arguments
17892			fire: function() {
17893				self.fireWith( this, arguments );
17894				return this;
17895			},
17896
17897			// To know if the callbacks have already been called at least once
17898			fired: function() {
17899				return !!fired;
17900			}
17901		};
17902
17903	return self;
17904};
17905
17906
17907function Identity( v ) {
17908	return v;
17909}
17910function Thrower( ex ) {
17911	throw ex;
17912}
17913
17914function adoptValue( value, resolve, reject, noValue ) {
17915	var method;
17916
17917	try {
17918
17919		// Check for promise aspect first to privilege synchronous behavior
17920		if ( value && isFunction( ( method = value.promise ) ) ) {
17921			method.call( value ).done( resolve ).fail( reject );
17922
17923		// Other thenables
17924		} else if ( value && isFunction( ( method = value.then ) ) ) {
17925			method.call( value, resolve, reject );
17926
17927		// Other non-thenables
17928		} else {
17929
17930			// Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
17931			// * false: [ value ].slice( 0 ) => resolve( value )
17932			// * true: [ value ].slice( 1 ) => resolve()
17933			resolve.apply( undefined, [ value ].slice( noValue ) );
17934		}
17935
17936	// For Promises/A+, convert exceptions into rejections
17937	// Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
17938	// Deferred#then to conditionally suppress rejection.
17939	} catch ( value ) {
17940
17941		// Support: Android 4.0 only
17942		// Strict mode functions invoked without .call/.apply get global-object context
17943		reject.apply( undefined, [ value ] );
17944	}
17945}
17946
17947jQuery.extend( {
17948
17949	Deferred: function( func ) {
17950		var tuples = [
17951
17952				// action, add listener, callbacks,
17953				// ... .then handlers, argument index, [final state]
17954				[ "notify", "progress", jQuery.Callbacks( "memory" ),
17955					jQuery.Callbacks( "memory" ), 2 ],
17956				[ "resolve", "done", jQuery.Callbacks( "once memory" ),
17957					jQuery.Callbacks( "once memory" ), 0, "resolved" ],
17958				[ "reject", "fail", jQuery.Callbacks( "once memory" ),
17959					jQuery.Callbacks( "once memory" ), 1, "rejected" ]
17960			],
17961			state = "pending",
17962			promise = {
17963				state: function() {
17964					return state;
17965				},
17966				always: function() {
17967					deferred.done( arguments ).fail( arguments );
17968					return this;
17969				},
17970				"catch": function( fn ) {
17971					return promise.then( null, fn );
17972				},
17973
17974				// Keep pipe for back-compat
17975				pipe: function( /* fnDone, fnFail, fnProgress */ ) {
17976					var fns = arguments;
17977
17978					return jQuery.Deferred( function( newDefer ) {
17979						jQuery.each( tuples, function( _i, tuple ) {
17980
17981							// Map tuples (progress, done, fail) to arguments (done, fail, progress)
17982							var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
17983
17984							// deferred.progress(function() { bind to newDefer or newDefer.notify })
17985							// deferred.done(function() { bind to newDefer or newDefer.resolve })
17986							// deferred.fail(function() { bind to newDefer or newDefer.reject })
17987							deferred[ tuple[ 1 ] ]( function() {
17988								var returned = fn && fn.apply( this, arguments );
17989								if ( returned && isFunction( returned.promise ) ) {
17990									returned.promise()
17991										.progress( newDefer.notify )
17992										.done( newDefer.resolve )
17993										.fail( newDefer.reject );
17994								} else {
17995									newDefer[ tuple[ 0 ] + "With" ](
17996										this,
17997										fn ? [ returned ] : arguments
17998									);
17999								}
18000							} );
18001						} );
18002						fns = null;
18003					} ).promise();
18004				},
18005				then: function( onFulfilled, onRejected, onProgress ) {
18006					var maxDepth = 0;
18007					function resolve( depth, deferred, handler, special ) {
18008						return function() {
18009							var that = this,
18010								args = arguments,
18011								mightThrow = function() {
18012									var returned, then;
18013
18014									// Support: Promises/A+ section 2.3.3.3.3
18015									// https://promisesaplus.com/#point-59
18016									// Ignore double-resolution attempts
18017									if ( depth < maxDepth ) {
18018										return;
18019									}
18020
18021									returned = handler.apply( that, args );
18022
18023									// Support: Promises/A+ section 2.3.1
18024									// https://promisesaplus.com/#point-48
18025									if ( returned === deferred.promise() ) {
18026										throw new TypeError( "Thenable self-resolution" );
18027									}
18028
18029									// Support: Promises/A+ sections 2.3.3.1, 3.5
18030									// https://promisesaplus.com/#point-54
18031									// https://promisesaplus.com/#point-75
18032									// Retrieve `then` only once
18033									then = returned &&
18034
18035										// Support: Promises/A+ section 2.3.4
18036										// https://promisesaplus.com/#point-64
18037										// Only check objects and functions for thenability
18038										( typeof returned === "object" ||
18039											typeof returned === "function" ) &&
18040										returned.then;
18041
18042									// Handle a returned thenable
18043									if ( isFunction( then ) ) {
18044
18045										// Special processors (notify) just wait for resolution
18046										if ( special ) {
18047											then.call(
18048												returned,
18049												resolve( maxDepth, deferred, Identity, special ),
18050												resolve( maxDepth, deferred, Thrower, special )
18051											);
18052
18053										// Normal processors (resolve) also hook into progress
18054										} else {
18055
18056											// ...and disregard older resolution values
18057											maxDepth++;
18058
18059											then.call(
18060												returned,
18061												resolve( maxDepth, deferred, Identity, special ),
18062												resolve( maxDepth, deferred, Thrower, special ),
18063												resolve( maxDepth, deferred, Identity,
18064													deferred.notifyWith )
18065											);
18066										}
18067
18068									// Handle all other returned values
18069									} else {
18070
18071										// Only substitute handlers pass on context
18072										// and multiple values (non-spec behavior)
18073										if ( handler !== Identity ) {
18074											that = undefined;
18075											args = [ returned ];
18076										}
18077
18078										// Process the value(s)
18079										// Default process is resolve
18080										( special || deferred.resolveWith )( that, args );
18081									}
18082								},
18083
18084								// Only normal processors (resolve) catch and reject exceptions
18085								process = special ?
18086									mightThrow :
18087									function() {
18088										try {
18089											mightThrow();
18090										} catch ( e ) {
18091
18092											if ( jQuery.Deferred.exceptionHook ) {
18093												jQuery.Deferred.exceptionHook( e,
18094													process.
18094stackTrace );
18095											}
18096
18097											// Support: Promises/A+ section 2.3.3.3.4.1
18098											// https://promisesaplus.com/#point-61
18099											// Ignore post-resolution exceptions
18100											if ( depth + 1 >= maxDepth ) {
18101
18102												// Only substitute handlers pass on context
18103												// and multiple values (non-spec behavior)
18104												if ( handler !== Thrower ) {
18105													that = undefined;
18106													args = [ e ];
18107												}
18108
18109												deferred.rejectWith( that, args );
18110											}
18111										}
18112									};
18113
vendor: 10,656 bytes, lines 18114-18562
18114							// Support: Promises/A+ section 2.3.3.3.1
18115							// https://promisesaplus.com/#point-57
18116							// Re-resolve promises immediately to dodge false rejection from
18117							// subsequent errors
18118							if ( depth ) {
18119								process();
18120							} else {
18121
18122								// Call an optional hook to record the stack, in case of exception
18123								// since it's otherwise lost when execution goes async
18124								if ( jQuery.Deferred.getStackHook ) {
18125									process.stackTrace = jQuery.Deferred.getStackHook();
18126								}
18127								window.setTimeout( process );
18128							}
18129						};
18130					}
18131
18132					return jQuery.Deferred( function( newDefer ) {
18133
18134						// progress_handlers.add( ... )
18135						tuples[ 0 ][ 3 ].add(
18136							resolve(
18137								0,
18138								newDefer,
18139								isFunction( onProgress ) ?
18140									onProgress :
18141									Identity,
18142								newDefer.notifyWith
18143							)
18144						);
18145
18146						// fulfilled_handlers.add( ... )
18147						tuples[ 1 ][ 3 ].add(
18148							resolve(
18149								0,
18150								newDefer,
18151								isFunction( onFulfilled ) ?
18152									onFulfilled :
18153									Identity
18154							)
18155						);
18156
18157						// rejected_handlers.add( ... )
18158						tuples[ 2 ][ 3 ].add(
18159							resolve(
18160								0,
18161								newDefer,
18162								isFunction( onRejected ) ?
18163									onRejected :
18164									Thrower
18165							)
18166						);
18167					} ).promise();
18168				},
18169
18170				// Get a promise for this deferred
18171				// If obj is provided, the promise aspect is added to the object
18172				promise: function( obj ) {
18173					return obj != null ? jQuery.extend( obj, promise ) : promise;
18174				}
18175			},
18176			deferred = {};
18177
18178		// Add list-specific methods
18179		jQuery.each( tuples, function( i, tuple ) {
18180			var list = tuple[ 2 ],
18181				stateString = tuple[ 5 ];
18182
18183			// promise.progress = list.add
18184			// promise.done = list.add
18185			// promise.fail = list.add
18186			promise[ tuple[ 1 ] ] = list.add;
18187
18188			// Handle state
18189			if ( stateString ) {
18190				list.add(
18191					function() {
18192
18193						// state = "resolved" (i.e., fulfilled)
18194						// state = "rejected"
18195						state = stateString;
18196					},
18197
18198					// rejected_callbacks.disable
18199					// fulfilled_callbacks.disable
18200					tuples[ 3 - i ][ 2 ].disable,
18201
18202					// rejected_handlers.disable
18203					// fulfilled_handlers.disable
18204					tuples[ 3 - i ][ 3 ].disable,
18205
18206					// progress_callbacks.lock
18207					tuples[ 0 ][ 2 ].lock,
18208
18209					// progress_handlers.lock
18210					tuples[ 0 ][ 3 ].lock
18211				);
18212			}
18213
18214			// progress_handlers.fire
18215			// fulfilled_handlers.fire
18216			// rejected_handlers.fire
18217			list.add( tuple[ 3 ].fire );
18218
18219			// deferred.notify = function() { deferred.notifyWith(...) }
18220			// deferred.resolve = function() { deferred.resolveWith(...) }
18221			// deferred.reject = function() { deferred.rejectWith(...) }
18222			deferred[ tuple[ 0 ] ] = function() {
18223				deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
18224				return this;
18225			};
18226
18227			// deferred.notifyWith = list.fireWith
18228			// deferred.resolveWith = list.fireWith
18229			// deferred.rejectWith = list.fireWith
18230			deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
18231		} );
18232
18233		// Make the deferred a promise
18234		promise.promise( deferred );
18235
18236		// Call given func if any
18237		if ( func ) {
18238			func.call( deferred, deferred );
18239		}
18240
18241		// All done!
18242		return deferred;
18243	},
18244
18245	// Deferred helper
18246	when: function( singleValue ) {
18247		var
18248
18249			// count of uncompleted subordinates
18250			remaining = arguments.length,
18251
18252			// count of unprocessed arguments
18253			i = remaining,
18254
18255			// subordinate fulfillment data
18256			resolveContexts = Array( i ),
18257			resolveValues = slice.call( arguments ),
18258
18259			// the master Deferred
18260			master = jQuery.Deferred(),
18261
18262			// subordinate callback factory
18263			updateFunc = function( i ) {
18264				return function( value ) {
18265					resolveContexts[ i ] = this;
18266					resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
18267					if ( !( --remaining ) ) {
18268						master.resolveWith( resolveContexts, resolveValues );
18269					}
18270				};
18271			};
18272
18273		// Single- and empty arguments are adopted like Promise.resolve
18274		if ( remaining <= 1 ) {
18275			adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject,
18276				!remaining );
18277
18278			// Use .then() to unwrap secondary thenables (cf. gh-3000)
18279			if ( master.state() === "pending" ||
18280				isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
18281
18282				return master.then();
18283			}
18284		}
18285
18286		// Multiple arguments are aggregated like Promise.all array elements
18287		while ( i-- ) {
18288			adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
18289		}
18290
18291		return master.promise();
18292	}
18293} );
18294
18295
18296// These usually indicate a programmer mistake during development,
18297// warn about them ASAP rather than swallowing them by default.
18298var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
18299
18300jQuery.Deferred.exceptionHook = function( error, stack ) {
18301
18302	// Support: IE 8 - 9 only
18303	// Console exists when dev tools are open, which can happen at any time
18304	if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
18305		window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack );
18306	}
18307};
18308
18309
18310
18311
18312jQuery.readyException = function( error ) {
18313	window.setTimeout( function() {
18314		throw error;
18315	} );
18316};
18317
18318
18319
18320
18321// The deferred used on DOM ready
18322var readyList = jQuery.Deferred();
18323
18324jQuery.fn.ready = function( fn ) {
18325
18326	readyList
18327		.then( fn )
18328
18329		// Wrap jQuery.readyException in a function so that the lookup
18330		// happens at the time of error handling instead of callback
18331		// registration.
18332		.catch( function( error ) {
18333			jQuery.readyException( error );
18334		} );
18335
18336	return this;
18337};
18338
18339jQuery.extend( {
18340
18341	// Is the DOM ready to be used? Set to true once it occurs.
18342	isReady: false,
18343
18344	// A counter to track how many items to wait for before
18345	// the ready event fires. See #6781
18346	readyWait: 1,
18347
18348	// Handle when the DOM is ready
18349	ready: function( wait ) {
18350
18351		// Abort if there are pending holds or we're already ready
18352		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
18353			return;
18354		}
18355
18356		// Remember that the DOM is ready
18357		jQuery.isReady = true;
18358
18359		// If a normal DOM Ready event fired, decrement, and wait if need be
18360		if ( wait !== true && --jQuery.readyWait > 0 ) {
18361			return;
18362		}
18363
18364		// If there are functions bound, to execute
18365		readyList.resolveWith( document, [ jQuery ] );
18366	}
18367} );
18368
18369jQuery.ready.then = readyList.then;
18370
18371// The ready event handler and self cleanup method
18372function completed() {
18373	document.removeEventListener( "DOMContentLoaded", completed );
18374	window.removeEventListener( "load", completed );
18375	jQuery.ready();
18376}
18377
18378// Catch cases where $(document).ready() is called
18379// after the browser event has already occurred.
18380// Support: IE <=9 - 10 only
18381// Older IE sometimes signals "interactive" too soon
18382if ( document.readyState === "complete" ||
18383	( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
18384
18385	// Handle it asynchronously to allow scripts the opportunity to delay ready
18386	window.setTimeout( jQuery.ready );
18387
18388} else {
18389
18390	// Use the handy event callback
18391	document.addEventListener( "DOMContentLoaded", completed );
18392
18393	// A fallback to window.onload, that will always work
18394	window.addEventListener( "load", completed );
18395}
18396
18397
18398
18399
18400// Multifunctional method to get and set values of a collection
18401// The value/s can optionally be executed if it's a function
18402var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
18403	var i = 0,
18404		len = elems.length,
18405		bulk = key == null;
18406
18407	// Sets many values
18408	if ( toType( key ) === "object" ) {
18409		chainable = true;
18410		for ( i in key ) {
18411			access( elems, fn, i, key[ i ], true, emptyGet, raw );
18412		}
18413
18414	// Sets one value
18415	} else if ( value !== undefined ) {
18416		chainable = true;
18417
18418		if ( !isFunction( value ) ) {
18419			raw = true;
18420		}
18421
18422		if ( bulk ) {
18423
18424			// Bulk operations run against the entire set
18425			if ( raw ) {
18426				fn.call( elems, value );
18427				fn = null;
18428
18429			// ...except when executing function values
18430			} else {
18431				bulk = fn;
18432				fn = function( elem, _key, value ) {
18433					return bulk.call( jQuery( elem ), value );
18434				};
18435			}
18436		}
18437
18438		if ( fn ) {
18439			for ( ; i < len; i++ ) {
18440				fn(
18441					elems[ i ], key, raw ?
18442					value :
18443					value.call( elems[ i ], i, fn( elems[ i ], key ) )
18444				);
18445			}
18446		}
18447	}
18448
18449	if ( chainable ) {
18450		return elems;
18451	}
18452
18453	// Gets
18454	if ( bulk ) {
18455		return fn.call( elems );
18456	}
18457
18458	return len ? fn( elems[ 0 ], key ) : emptyGet;
18459};
18460
18461
18462// Matches dashed string for camelizing
18463var rmsPrefix = /^-ms-/,
18464	rdashAlpha = /-([a-z])/g;
18465
18466// Used by camelCase as callback to replace()
18467function fcamelCase( _all, letter ) {
18468	return letter.toUpperCase();
18469}
18470
18471// Convert dashed to camelCase; used by the css and data modules
18472// Support: IE <=9 - 11, Edge 12 - 15
18473// Microsoft forgot to hump their vendor prefix (#9572)
18474function camelCase( string ) {
18475	return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
18476}
18477var acceptData = function( owner ) {
18478
18479	// Accepts only:
18480	//  - Node
18481	//    - Node.ELEMENT_NODE
18482	//    - Node.DOCUMENT_NODE
18483	//  - Object
18484	//    - Any
18485	return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
18486};
18487
18488
18489
18490
18491function Data() {
18492	this.expando = jQuery.expando + Data.uid++;
18493}
18494
18495Data.uid = 1;
18496
18497Data.prototype = {
18498
18499	cache: function( owner ) {
18500
18501		// Check if the owner object already has a cache
18502		var value = owner[ this.expando ];
18503
18504		// If not, create one
18505		if ( !value ) {
18506			value = {};
18507
18508			// We can accept data for non-element nodes in modern browsers,
18509			// but we should not, see #8335.
18510			// Always return an empty object.
18511			if ( acceptData( owner ) ) {
18512
18513				// If it is a node unlikely to be stringify-ed or looped over
18514				// use plain assignment
18515				if ( owner.nodeType ) {
18516					owner[ this.expando ] = value;
18517
18518				// Otherwise secure it in a non-enumerable property
18519				// configurable must be true to allow the property to be
18520				// deleted when data is removed
18521				} else {
18522					Object.defineProperty( owner, this.expando, {
18523						value: value,
18524						configurable: true
18525					} );
18526				}
18527			}
18528		}
18529
18530		return value;
18531	},
18532	set: function( owner, data, value ) {
18533		var prop,
18534			cache = this.cache( owner );
18535
18536		// Handle: [ owner, key, value ] args
18537		// Always use camelCase key (gh-2257)
18538		if ( typeof data === "string" ) {
18539			cache[ camelCase( data ) ] = value;
18540
18541		// Handle: [ owner, { properties } ] args
18542		} else {
18543
18544			// Copy the properties one-by-one to the cache object
18545			for ( prop in data ) {
18546				cache[ camelCase( prop ) ] = data[ prop ];
18547			}
18548		}
18549		return cache;
18550	},
18551	get: function( owner, key ) {
18552		return key === undefined ?
18553			this.cache( owner ) :
18554
18555			// Always use camelCase key (gh-2257)
18556			owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
18557	},
18558	access: function( owner, key, value ) {
18559
18560		// In cases where either:
18561		//
18562		//   1. No key was specified
vendor: 4,579 bytes, lines 18563-18746
18563		//   2. A string key was specified, but no value provided
18564		//
18565		// Take the "read" path and allow the get method to determine
18566		// which value to return, respectively either:
18567		//
18568		//   1. The entire cache object
18569		//   2. The data stored at the key
18570		//
18571		if ( key === undefined ||
18572				( ( key && typeof key === "string" ) && value === undefined ) ) {
18573
18574			return this.get( owner, key );
18575		}
18576
18577		// When the key is not a string, or both a key and value
18578		// are specified, set or extend (existing objects) with either:
18579		//
18580		//   1. An object of properties
18581		//   2. A key and value
18582		//
18583		this.set( owner, key, value );
18584
18585		// Since the "set" path can have two possible entry points
18586		// return the expected data based on which path was taken[*]
18587		return value !== undefined ? value : key;
18588	},
18589	remove: function( owner, key ) {
18590		var i,
18591			cache = owner[ this.expando ];
18592
18593		if ( cache === undefined ) {
18594			return;
18595		}
18596
18597		if ( key !== undefined ) {
18598
18599			// Support array or space separated string of keys
18600			if ( Array.isArray( key ) ) {
18601
18602				// If key is an array of keys...
18603				// We always set camelCase keys, so remove that.
18604				key = key.map( camelCase );
18605			} else {
18606				key = camelCase( key );
18607
18608				// If a key with the spaces exists, use it.
18609				// Otherwise, create an array by matching non-whitespace
18610				key = key in cache ?
18611					[ key ] :
18612					( key.match( rnothtmlwhite ) || [] );
18613			}
18614
18615			i = key.length;
18616
18617			while ( i-- ) {
18618				delete cache[ key[ i ] ];
18619			}
18620		}
18621
18622		// Remove the expando if there's no more data
18623		if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
18624
18625			// Support: Chrome <=35 - 45
18626			// Webkit & Blink performance suffers when deleting properties
18627			// from DOM nodes, so set to undefined instead
18628			// https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
18629			if ( owner.nodeType ) {
18630				owner[ this.expando ] = undefined;
18631			} else {
18632				delete owner[ this.expando ];
18633			}
18634		}
18635	},
18636	hasData: function( owner ) {
18637		var cache = owner[ this.expando ];
18638		return cache !== undefined && !jQuery.isEmptyObject( cache );
18639	}
18640};
18641var dataPriv = new Data();
18642
18643var dataUser = new Data();
18644
18645
18646
18647//	Implementation Summary
18648//
18649//	1. Enforce API surface and semantic compatibility with 1.9.x branch
18650//	2. Improve the module's maintainability by reducing the storage
18651//		paths to a single mechanism.
18652//	3. Use the same single mechanism to support "private" and "user" data.
18653//	4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
18654//	5. Avoid exposing implementation details on user objects (eg. expando properties)
18655//	6. Provide a clear path for implementation upgrade to WeakMap in 2014
18656
18657var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
18658	rmultiDash = /[A-Z]/g;
18659
18660function getData( data ) {
18661	if ( data === "true" ) {
18662		return true;
18663	}
18664
18665	if ( data === "false" ) {
18666		return false;
18667	}
18668
18669	if ( data === "null" ) {
18670		return null;
18671	}
18672
18673	// Only convert to a number if it doesn't change the string
18674	if ( data === +data + "" ) {
18675		return +data;
18676	}
18677
18678	if ( rbrace.test( data ) ) {
18679		return JSON.parse( data );
18680	}
18681
18682	return data;
18683}
18684
18685function dataAttr( elem, key, data ) {
18686	var name;
18687
18688	// If nothing was found internally, try to fetch any
18689	// data from the HTML5 data-* attribute
18690	if ( data === undefined && elem.nodeType === 1 ) {
18691		name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
18692		data = elem.getAttribute( name );
18693
18694		if ( typeof data === "string" ) {
18695			try {
18696				data = getData( data );
18697			} catch ( e ) {}
18698
18699			// Make sure we set the data so it isn't changed later
18700			dataUser.set( elem, key, data );
18701		} else {
18702			data = undefined;
18703		}
18704	}
18705	return data;
18706}
18707
18708jQuery.extend( {
18709	hasData: function( elem ) {
18710		return dataUser.hasData( elem ) || dataPriv.hasData( elem );
18711	},
18712
18713	data: function( elem, name, data ) {
18714		return dataUser.access( elem, name, data );
18715	},
18716
18717	removeData: function( elem, name ) {
18718		dataUser.remove( elem, name );
18719	},
18720
18721	// TODO: Now that all calls to _data and _removeData have been replaced
18722	// with direct calls to dataPriv methods, these can be deprecated.
18723	_data: function( elem, name, data ) {
18724		return dataPriv.access( elem, name, data );
18725	},
18726
18727	_removeData: function( elem, name ) {
18728		dataPriv.remove( elem, name );
18729	}
18730} );
18731
18732jQuery.fn.extend( {
18733	data: function( key, value ) {
18734		var i, name, data,
18735			elem = this[ 0 ],
18736			attrs = elem && elem.attributes;
18737
18738		// Gets all values
18739		if ( key === undefined ) {
18740			if ( this.length ) {
18741				data = dataUser.get( elem );
18742
18743				if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
18744					i = attrs.length;
18745					while ( i-- ) {
18746
18747						// Support: IE 11 only
18748						// The attrs elements can be null (#
vendor: 10,424 bytes, lines 18748-19171
1874814894)
18749						if ( attrs[ i ] ) {
18750							name = attrs[ i ].name;
18751							if ( name.indexOf( "data-" ) === 0 ) {
18752								name = camelCase( name.slice( 5 ) );
18753								dataAttr( elem, name, data[ name ] );
18754							}
18755						}
18756					}
18757					dataPriv.set( elem, "hasDataAttrs", true );
18758				}
18759			}
18760
18761			return data;
18762		}
18763
18764		// Sets multiple values
18765		if ( typeof key === "object" ) {
18766			return this.each( function() {
18767				dataUser.set( this, key );
18768			} );
18769		}
18770
18771		return access( this, function( value ) {
18772			var data;
18773
18774			// The calling jQuery object (element matches) is not empty
18775			// (and therefore has an element appears at this[ 0 ]) and the
18776			// `value` parameter was not undefined. An empty jQuery object
18777			// will result in `undefined` for elem = this[ 0 ] which will
18778			// throw an exception if an attempt to read a data cache is made.
18779			if ( elem && value === undefined ) {
18780
18781				// Attempt to get data from the cache
18782				// The key will always be camelCased in Data
18783				data = dataUser.get( elem, key );
18784				if ( data !== undefined ) {
18785					return data;
18786				}
18787
18788				// Attempt to "discover" the data in
18789				// HTML5 custom data-* attrs
18790				data = dataAttr( elem, key );
18791				if ( data !== undefined ) {
18792					return data;
18793				}
18794
18795				// We tried really hard, but the data doesn't exist.
18796				return;
18797			}
18798
18799			// Set the data...
18800			this.each( function() {
18801
18802				// We always store the camelCased key
18803				dataUser.set( this, key, value );
18804			} );
18805		}, null, value, arguments.length > 1, null, true );
18806	},
18807
18808	removeData: function( key ) {
18809		return this.each( function() {
18810			dataUser.remove( this, key );
18811		} );
18812	}
18813} );
18814
18815
18816jQuery.extend( {
18817	queue: function( elem, type, data ) {
18818		var queue;
18819
18820		if ( elem ) {
18821			type = ( type || "fx" ) + "queue";
18822			queue = dataPriv.get( elem, type );
18823
18824			// Speed up dequeue by getting out quickly if this is just a lookup
18825			if ( data ) {
18826				if ( !queue || Array.isArray( data ) ) {
18827					queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
18828				} else {
18829					queue.push( data );
18830				}
18831			}
18832			return queue || [];
18833		}
18834	},
18835
18836	dequeue: function( elem, type ) {
18837		type = type || "fx";
18838
18839		var queue = jQuery.queue( elem, type ),
18840			startLength = queue.length,
18841			fn = queue.shift(),
18842			hooks = jQuery._queueHooks( elem, type ),
18843			next = function() {
18844				jQuery.dequeue( elem, type );
18845			};
18846
18847		// If the fx queue is dequeued, always remove the progress sentinel
18848		if ( fn === "inprogress" ) {
18849			fn = queue.shift();
18850			startLength--;
18851		}
18852
18853		if ( fn ) {
18854
18855			// Add a progress sentinel to prevent the fx queue from being
18856			// automatically dequeued
18857			if ( type === "fx" ) {
18858				queue.unshift( "inprogress" );
18859			}
18860
18861			// Clear up the last queue stop function
18862			delete hooks.stop;
18863			fn.call( elem, next, hooks );
18864		}
18865
18866		if ( !startLength && hooks ) {
18867			hooks.empty.fire();
18868		}
18869	},
18870
18871	// Not public - generate a queueHooks object, or return the current one
18872	_queueHooks: function( elem, type ) {
18873		var key = type + "queueHooks";
18874		return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
18875			empty: jQuery.Callbacks( "once memory" ).add( function() {
18876				dataPriv.remove( elem, [ type + "queue", key ] );
18877			} )
18878		} );
18879	}
18880} );
18881
18882jQuery.fn.extend( {
18883	queue: function( type, data ) {
18884		var setter = 2;
18885
18886		if ( typeof type !== "string" ) {
18887			data = type;
18888			type = "fx";
18889			setter--;
18890		}
18891
18892		if ( arguments.length < setter ) {
18893			return jQuery.queue( this[ 0 ], type );
18894		}
18895
18896		return data === undefined ?
18897			this :
18898			this.each( function() {
18899				var queue = jQuery.queue( this, type, data );
18900
18901				// Ensure a hooks for this queue
18902				jQuery._queueHooks( this, type );
18903
18904				if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
18905					jQuery.dequeue( this, type );
18906				}
18907			} );
18908	},
18909	dequeue: function( type ) {
18910		return this.each( function() {
18911			jQuery.dequeue( this, type );
18912		} );
18913	},
18914	clearQueue: function( type ) {
18915		return this.queue( type || "fx", [] );
18916	},
18917
18918	// Get a promise resolved when queues of a certain type
18919	// are emptied (fx is the type by default)
18920	promise: function( type, obj ) {
18921		var tmp,
18922			count = 1,
18923			defer = jQuery.Deferred(),
18924			elements = this,
18925			i = this.length,
18926			resolve = function() {
18927				if ( !( --count ) ) {
18928					defer.resolveWith( elements, [ elements ] );
18929				}
18930			};
18931
18932		if ( typeof type !== "string" ) {
18933			obj = type;
18934			type = undefined;
18935		}
18936		type = type || "fx";
18937
18938		while ( i-- ) {
18939			tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
18940			if ( tmp && tmp.empty ) {
18941				count++;
18942				tmp.empty.add( resolve );
18943			}
18944		}
18945		resolve();
18946		return defer.promise( obj );
18947	}
18948} );
18949var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
18950
18951var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
18952
18953
18954var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
18955
18956var documentElement = document.documentElement;
18957
18958
18959
18960	var isAttached = function( elem ) {
18961			return jQuery.contains( elem.ownerDocument, elem );
18962		},
18963		composed = { composed: true };
18964
18965	// Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only
18966	// Check attachment across shadow DOM boundaries when possible (gh-3504)
18967	// Support: iOS 10.0-10.2 only
18968	// Early iOS 10 versions support `attachShadow` but not `getRootNode`,
18969	// leading to errors. We need to check for `getRootNode`.
18970	if ( documentElement.getRootNode ) {
18971		isAttached = function( elem ) {
18972			return jQuery.contains( elem.ownerDocument, elem ) ||
18973				elem.getRootNode( composed ) === elem.ownerDocument;
18974		};
18975	}
18976var isHiddenWithinTree = function( elem, el ) {
18977
18978		// isHiddenWithinTree might be called from jQuery#filter function;
18979		// in that case, element will be second argument
18980		elem = el || elem;
18981
18982		// Inline style trumps all
18983		return elem.style.display === "none" ||
18984			elem.style.display === "" &&
18985
18986			// Otherwise, check computed style
18987			// Support: Firefox <=43 - 45
18988			// Disconnected elements can have computed display: none, so first confirm that elem is
18989			// in the document.
18990			isAttached( elem ) &&
18991
18992			jQuery.css( elem, "display" ) === "none";
18993	};
18994
18995
18996
18997function adjustCSS( elem, prop, valueParts, tween ) {
18998	var adjusted, scale,
18999		maxIterations = 20,
19000		currentValue = tween ?
19001			function() {
19002				return tween.cur();
19003			} :
19004			function() {
19005				return jQuery.css( elem, prop, "" );
19006			},
19007		initial = currentValue(),
19008		unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
19009
19010		// Starting value computation is required for potential unit mismatches
19011		initialInUnit = elem.nodeType &&
19012			( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
19013			rcssNum.exec( jQuery.css( elem, prop ) );
19014
19015	if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
19016
19017		// Support: Firefox <=54
19018		// Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)
19019		initial = initial / 2;
19020
19021		// Trust units reported by jQuery.css
19022		unit = unit || initialInUnit[ 3 ];
19023
19024		// Iteratively approximate from a nonzero starting point
19025		initialInUnit = +initial || 1;
19026
19027		while ( maxIterations-- ) {
19028
19029			// Evaluate and update our best guess (doubling guesses that zero out).
19030			// Finish if the scale equals or crosses 1 (making the old*new product non-positive).
19031			jQuery.style( elem, prop, initialInUnit + unit );
19032			if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) {
19033				maxIterations = 0;
19034			}
19035			initialInUnit = initialInUnit / scale;
19036
19037		}
19038
19039		initialInUnit = initialInUnit * 2;
19040		jQuery.style( elem, prop, initialInUnit + unit );
19041
19042		// Make sure we update the tween properties later on
19043		valueParts = valueParts || [];
19044	}
19045
19046	if ( valueParts ) {
19047		initialInUnit = +initialInUnit || +initial || 0;
19048
19049		// Apply relative offset (+=/-=) if specified
19050		adjusted = valueParts[ 1 ] ?
19051			initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
19052			+valueParts[ 2 ];
19053		if ( tween ) {
19054			tween.unit = unit;
19055			tween.start = initialInUnit;
19056			tween.end = adjusted;
19057		}
19058	}
19059	return adjusted;
19060}
19061
19062
19063var defaultDisplayMap = {};
19064
19065function getDefaultDisplay( elem ) {
19066	var temp,
19067		doc = elem.ownerDocument,
19068		nodeName = elem.nodeName,
19069		display = defaultDisplayMap[ nodeName ];
19070
19071	if ( display ) {
19072		return display;
19073	}
19074
19075	temp = doc.body.appendChild( doc.createElement( nodeName ) );
19076	display = jQuery.css( temp, "display" );
19077
19078	temp.parentNode.removeChild( temp );
19079
19080	if ( display === "none" ) {
19081		display = "block";
19082	}
19083	defaultDisplayMap[ nodeName ] = display;
19084
19085	return display;
19086}
19087
19088function showHide( elements, show ) {
19089	var display, elem,
19090		values = [],
19091		index = 0,
19092		length = elements.length;
19093
19094	// Determine new display value for elements that need to change
19095	for ( ; index < length; index++ ) {
19096		elem = elements[ index ];
19097		if ( !elem.style ) {
19098			continue;
19099		}
19100
19101		display = elem.style.display;
19102		if ( show ) {
19103
19104			// Since we force visibility upon cascade-hidden elements, an immediate (and slow)
19105			// check is required in this first loop unless we have a nonempty display value (either
19106			// inline or about-to-be-restored)
19107			if ( display === "none" ) {
19108				values[ index ] = dataPriv.get( elem, "display" ) || null;
19109				if ( !values[ index ] ) {
19110					elem.style.display = "";
19111				}
19112			}
19113			if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
19114				values[ index ] = getDefaultDisplay( elem );
19115			}
19116		} else {
19117			if ( display !== "none" ) {
19118				values[ index ] = "none";
19119
19120				// Remember what we're overwriting
19121				dataPriv.set( elem, "display", display );
19122			}
19123		}
19124	}
19125
19126	// Set the display of the elements in a second loop to avoid constant reflow
19127	for ( index = 0; index < length; index++ ) {
19128		if ( values[ index ] != null ) {
19129			elements[ index ].style.display = values[ index ];
19130		}
19131	}
19132
19133	return elements;
19134}
19135
19136jQuery.fn.extend( {
19137	show: function() {
19138		return showHide( this, true );
19139	},
19140	hide: function() {
19141		return showHide( this );
19142	},
19143	toggle: function( state ) {
19144		if ( typeof state === "boolean" ) {
19145			return state ? this.show() : this.hide();
19146		}
19147
19148		return this.each( function() {
19149			if ( isHiddenWithinTree( this ) ) {
19150				jQuery( this ).show();
19151			} else {
19152				jQuery( this ).hide();
19153			}
19154		} );
19155	}
19156} );
19157var rcheckableType = ( /^(?:checkbox|radio)$/i );
19158
19159var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i );
19160
19161var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
19162
19163
19164
19165( function() {
19166	var fragment = document.createDocumentFragment(),
19167		div = fragment.appendChild( document.createElement( "div" ) ),
19168		input = document.createElement( "input" );
19169
19170	// Support: Android 4.0 - 4.3 only
19171	// Check state lost if the name is set (
vendor: 10,796 bytes, lines 19171-19577
19171#11217)
19172	// Support: Windows Web Apps (WWA)
19173	// `name` and `type` must use .setAttribute for WWA (#14901)
19174	input.setAttribute( "type", "radio" );
19175	input.setAttribute( "checked", "checked" );
19176	input.setAttribute( "name", "t" );
19177
19178	div.appendChild( input );
19179
19180	// Support: Android <=4.1 only
19181	// Older WebKit doesn't clone checked state correctly in fragments
19182	support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
19183
19184	// Support: IE <=11 only
19185	// Make sure textarea (and checkbox) defaultValue is properly cloned
19186	div.innerHTML = "<textarea>x</textarea>";
19187	support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
19188
19189	// Support: IE <=9 only
19190	// IE <=9 replaces <option> tags with their contents when inserted outside of
19191	// the select element.
19192	div.innerHTML = "<option></option>";
19193	support.option = !!div.lastChild;
19194} )();
19195
19196
19197// We have to close these tags to support XHTML (#13200)
19198var wrapMap = {
19199
19200	// XHTML parsers do not magically insert elements in the
19201	// same way that tag soup parsers do. So we cannot shorten
19202	// this by omitting <tbody> or other required elements.
19203	thead: [ 1, "<table>", "</table>" ],
19204	col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
19205	tr: [ 2, "<table><tbody>", "</tbody></table>" ],
19206	td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
19207
19208	_default: [ 0, "", "" ]
19209};
19210
19211wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
19212wrapMap.th = wrapMap.td;
19213
19214// Support: IE <=9 only
19215if ( !support.option ) {
19216	wrapMap.optgroup = wrapMap.option = [ 1, "<select multiple='multiple'>", "</select>" ];
19217}
19218
19219
19220function getAll( context, tag ) {
19221
19222	// Support: IE <=9 - 11 only
19223	// Use typeof to avoid zero-argument method invocation on host objects (#15151)
19224	var ret;
19225
19226	if ( typeof context.getElementsByTagName !== "undefined" ) {
19227		ret = context.getElementsByTagName( tag || "*" );
19228
19229	} else if ( typeof context.querySelectorAll !== "undefined" ) {
19230		ret = context.querySelectorAll( tag || "*" );
19231
19232	} else {
19233		ret = [];
19234	}
19235
19236	if ( tag === undefined || tag && nodeName( context, tag ) ) {
19237		return jQuery.merge( [ context ], ret );
19238	}
19239
19240	return ret;
19241}
19242
19243
19244// Mark scripts as having already been evaluated
19245function setGlobalEval( elems, refElements ) {
19246	var i = 0,
19247		l = elems.length;
19248
19249	for ( ; i < l; i++ ) {
19250		dataPriv.set(
19251			elems[ i ],
19252			"globalEval",
19253			!refElements || dataPriv.get( refElements[ i ], "globalEval" )
19254		);
19255	}
19256}
19257
19258
19259var rhtml = /<|&#?\w+;/;
19260
19261function buildFragment( elems, context, scripts, selection, ignored ) {
19262	var elem, tmp, tag, wrap, attached, j,
19263		fragment = context.createDocumentFragment(),
19264		nodes = [],
19265		i = 0,
19266		l = elems.length;
19267
19268	for ( ; i < l; i++ ) {
19269		elem = elems[ i ];
19270
19271		if ( elem || elem === 0 ) {
19272
19273			// Add nodes directly
19274			if ( toType( elem ) === "object" ) {
19275
19276				// Support: Android <=4.0 only, PhantomJS 1 only
19277				// push.apply(_, arraylike) throws on ancient WebKit
19278				jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
19279
19280			// Convert non-html into a text node
19281			} else if ( !rhtml.test( elem ) ) {
19282				nodes.push( context.createTextNode( elem ) );
19283
19284			// Convert html into DOM nodes
19285			} else {
19286				tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
19287
19288				// Deserialize a standard representation
19289				tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
19290				wrap = wrapMap[ tag ] || wrapMap._default;
19291				tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
19292
19293				// Descend through wrappers to the right content
19294				j = wrap[ 0 ];
19295				while ( j-- ) {
19296					tmp = tmp.lastChild;
19297				}
19298
19299				// Support: Android <=4.0 only, PhantomJS 1 only
19300				// push.apply(_, arraylike) throws on ancient WebKit
19301				jQuery.merge( nodes, tmp.childNodes );
19302
19303				// Remember the top-level container
19304				tmp = fragment.firstChild;
19305
19306				// Ensure the created nodes are orphaned (#12392)
19307				tmp.textContent = "";
19308			}
19309		}
19310	}
19311
19312	// Remove wrapper from fragment
19313	fragment.textContent = "";
19314
19315	i = 0;
19316	while ( ( elem = nodes[ i++ ] ) ) {
19317
19318		// Skip elements already in the context collection (trac-4087)
19319		if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
19320			if ( ignored ) {
19321				ignored.push( elem );
19322			}
19323			continue;
19324		}
19325
19326		attached = isAttached( elem );
19327
19328		// Append to fragment
19329		tmp = getAll( fragment.appendChild( elem ), "script" );
19330
19331		// Preserve script evaluation history
19332		if ( attached ) {
19333			setGlobalEval( tmp );
19334		}
19335
19336		// Capture executables
19337		if ( scripts ) {
19338			j = 0;
19339			while ( ( elem = tmp[ j++ ] ) ) {
19340				if ( rscriptType.test( elem.type || "" ) ) {
19341					scripts.push( elem );
19342				}
19343			}
19344		}
19345	}
19346
19347	return fragment;
19348}
19349
19350
19351var
19352	rkeyEvent = /^key/,
19353	rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
19354	rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
19355
19356function returnTrue() {
19357	return true;
19358}
19359
19360function returnFalse() {
19361	return false;
19362}
19363
19364// Support: IE <=9 - 11+
19365// focus() and blur() are asynchronous, except when they are no-op.
19366// So expect focus to be synchronous when the element is already active,
19367// and blur to be synchronous when the element is not already active.
19368// (focus and blur are always synchronous in other supported browsers,
19369// this just defines when we can count on it).
19370function expectSync( elem, type ) {
19371	return ( elem === safeActiveElement() ) === ( type === "focus" );
19372}
19373
19374// Support: IE <=9 only
19375// Accessing document.activeElement can throw unexpectedly
19376// https://bugs.jquery.com/ticket/13393
19377function safeActiveElement() {
19378	try {
19379		return document.activeElement;
19380	} catch ( err ) { }
19381}
19382
19383function on( elem, types, selector, data, fn, one ) {
19384	var origFn, type;
19385
19386	// Types can be a map of types/handlers
19387	if ( typeof types === "object" ) {
19388
19389		// ( types-Object, selector, data )
19390		if ( typeof selector !== "string" ) {
19391
19392			// ( types-Object, data )
19393			data = data || selector;
19394			selector = undefined;
19395		}
19396		for ( type in types ) {
19397			on( elem, type, selector, data, types[ type ], one );
19398		}
19399		return elem;
19400	}
19401
19402	if ( data == null && fn == null ) {
19403
19404		// ( types, fn )
19405		fn = selector;
19406		data = selector = undefined;
19407	} else if ( fn == null ) {
19408		if ( typeof selector === "string" ) {
19409
19410			// ( types, selector, fn )
19411			fn = data;
19412			data = undefined;
19413		} else {
19414
19415			// ( types, data, fn )
19416			fn = data;
19417			data = selector;
19418			selector = undefined;
19419		}
19420	}
19421	if ( fn === false ) {
19422		fn = returnFalse;
19423	} else if ( !fn ) {
19424		return elem;
19425	}
19426
19427	if ( one === 1 ) {
19428		origFn = fn;
19429		fn = function( event ) {
19430
19431			// Can use an empty set, since event contains the info
19432			jQuery().off( event );
19433			return origFn.apply( this, arguments );
19434		};
19435
19436		// Use same guid so caller can remove using origFn
19437		fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
19438	}
19439	return elem.each( function() {
19440		jQuery.event.add( this, types, fn, data, selector );
19441	} );
19442}
19443
19444/*
19445 * Helper functions for managing events -- not part of the public interface.
19446 * Props to Dean Edwards' addEvent library for many of the ideas.
19447 */
19448jQuery.event = {
19449
19450	global: {},
19451
19452	add: function( elem, types, handler, data, selector ) {
19453
19454		var handleObjIn, eventHandle, tmp,
19455			events, t, handleObj,
19456			special, handlers, type, namespaces, origType,
19457			elemData = dataPriv.get( elem );
19458
19459		// Only attach events to objects that accept data
19460		if ( !acceptData( elem ) ) {
19461			return;
19462		}
19463
19464		// Caller can pass in an object of custom data in lieu of the handler
19465		if ( handler.handler ) {
19466			handleObjIn = handler;
19467			handler = handleObjIn.handler;
19468			selector = handleObjIn.selector;
19469		}
19470
19471		// Ensure that invalid selectors throw exceptions at attach time
19472		// Evaluate against documentElement in case elem is a non-element node (e.g., document)
19473		if ( selector ) {
19474			jQuery.find.matchesSelector( documentElement, selector );
19475		}
19476
19477		// Make sure that the handler has a unique ID, used to find/remove it later
19478		if ( !handler.guid ) {
19479			handler.guid = jQuery.guid++;
19480		}
19481
19482		// Init the element's event structure and main handler, if this is the first
19483		if ( !( events = elemData.events ) ) {
19484			events = elemData.events = Object.create( null );
19485		}
19486		if ( !( eventHandle = elemData.handle ) ) {
19487			eventHandle = elemData.handle = function( e ) {
19488
19489				// Discard the second event of a jQuery.event.trigger() and
19490				// when an event is called after a page has unloaded
19491				return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
19492					jQuery.event.dispatch.apply( elem, arguments ) : undefined;
19493			};
19494		}
19495
19496		// Handle multiple events separated by a space
19497		types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
19498		t = types.length;
19499		while ( t-- ) {
19500			tmp = rtypenamespace.exec( types[ t ] ) || [];
19501			type = origType = tmp[ 1 ];
19502			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
19503
19504			// There *must* be a type, no attaching namespace-only handlers
19505			if ( !type ) {
19506				continue;
19507			}
19508
19509			// If event changes its type, use the special event handlers for the changed type
19510			special = jQuery.event.special[ type ] || {};
19511
19512			// If selector defined, determine special event api type, otherwise given type
19513			type = ( selector ? special.delegateType : special.bindType ) || type;
19514
19515			// Update special based on newly reset type
19516			special = jQuery.event.special[ type ] || {};
19517
19518			// handleObj is passed to all event handlers
19519			handleObj = jQuery.extend( {
19520				type: type,
19521				origType: origType,
19522				data: data,
19523				handler: handler,
19524				guid: handler.guid,
19525				selector: selector,
19526				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
19527				namespace: namespaces.join( "." )
19528			}, handleObjIn );
19529
19530			// Init the event handler queue if we're the first
19531			if ( !( handlers = events[ type ] ) ) {
19532				handlers = events[ type ] = [];
19533				handlers.delegateCount = 0;
19534
19535				// Only use addEventListener if the special events handler returns false
19536				if ( !special.setup ||
19537					special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
19538
19539					if ( elem.addEventListener ) {
19540						elem.addEventListener( type, eventHandle );
19541					}
19542				}
19543			}
19544
19545			if ( special.add ) {
19546				special.add.call( elem, handleObj );
19547
19548				if ( !handleObj.handler.guid ) {
19549					handleObj.handler.guid = handler.guid;
19550				}
19551			}
19552
19553			// Add to the element's handler list, delegates in front
19554			if ( selector ) {
19555				handlers.splice( handlers.delegateCount++, 0, handleObj );
19556			} else {
19557				handlers.push( handleObj );
19558			}
19559
19560			// Keep track of which events have ever been used, for event optimization
19561			jQuery.event.global[ type ] = true;
19562		}
19563
19564	},
19565
19566	// Detach an event or set of events from an element
19567	remove: function( elem, types, handler, selector, mappedTypes ) {
19568
19569		var j, origCount, tmp,
19570			events, t, handleObj,
19571			special, handlers, type, namespaces, origType,
19572			elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );
19573
19574		if ( !elemData || !( events = elemData.events ) ) {
19575			return;
19576		}
19577
vendor: 4,199 bytes, lines 19578-19716
19578		// Once for each type.namespace in types; type may be omitted
19579		types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
19580		t = types.length;
19581		while ( t-- ) {
19582			tmp = rtypenamespace.exec( types[ t ] ) || [];
19583			type = origType = tmp[ 1 ];
19584			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
19585
19586			// Unbind all events (on this namespace, if provided) for the element
19587			if ( !type ) {
19588				for ( type in events ) {
19589					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
19590				}
19591				continue;
19592			}
19593
19594			special = jQuery.event.special[ type ] || {};
19595			type = ( selector ? special.delegateType : special.bindType ) || type;
19596			handlers = events[ type ] || [];
19597			tmp = tmp[ 2 ] &&
19598				new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );
19599
19600			// Remove matching events
19601			origCount = j = handlers.length;
19602			while ( j-- ) {
19603				handleObj = handlers[ j ];
19604
19605				if ( ( mappedTypes || origType === handleObj.origType ) &&
19606					( !handler || handler.guid === handleObj.guid ) &&
19607					( !tmp || tmp.test( handleObj.namespace ) ) &&
19608					( !selector || selector === handleObj.selector ||
19609						selector === "**" && handleObj.selector ) ) {
19610					handlers.splice( j, 1 );
19611
19612					if ( handleObj.selector ) {
19613						handlers.delegateCount--;
19614					}
19615					if ( special.remove ) {
19616						special.remove.call( elem, handleObj );
19617					}
19618				}
19619			}
19620
19621			// Remove generic event handler if we removed something and no more handlers exist
19622			// (avoids potential for endless recursion during removal of special event handlers)
19623			if ( origCount && !handlers.length ) {
19624				if ( !special.teardown ||
19625					special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
19626
19627					jQuery.removeEvent( elem, type, elemData.handle );
19628				}
19629
19630				delete events[ type ];
19631			}
19632		}
19633
19634		// Remove data and the expando if it's no longer used
19635		if ( jQuery.isEmptyObject( events ) ) {
19636			dataPriv.remove( elem, "handle events" );
19637		}
19638	},
19639
19640	dispatch: function( nativeEvent ) {
19641
19642		var i, j, ret, matched, handleObj, handlerQueue,
19643			args = new Array( arguments.length ),
19644
19645			// Make a writable jQuery.Event from the native event object
19646			event = jQuery.event.fix( nativeEvent ),
19647
19648			handlers = (
19649					dataPriv.get( this, "events" ) || Object.create( null )
19650				)[ event.type ] || [],
19651			special = jQuery.event.special[ event.type ] || {};
19652
19653		// Use the fix-ed jQuery.Event rather than the (read-only) native event
19654		args[ 0 ] = event;
19655
19656		for ( i = 1; i < arguments.length; i++ ) {
19657			args[ i ] = arguments[ i ];
19658		}
19659
19660		event.delegateTarget = this;
19661
19662		// Call the preDispatch hook for the mapped type, and let it bail if desired
19663		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
19664			return;
19665		}
19666
19667		// Determine handlers
19668		handlerQueue = jQuery.event.handlers.call( this, event, handlers );
19669
19670		// Run delegates first; they may want to stop propagation beneath us
19671		i = 0;
19672		while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
19673			event.currentTarget = matched.elem;
19674
19675			j = 0;
19676			while ( ( handleObj = matched.handlers[ j++ ] ) &&
19677				!event.isImmediatePropagationStopped() ) {
19678
19679				// If the event is namespaced, then each handler is only invoked if it is
19680				// specially universal or its namespaces are a superset of the event's.
19681				if ( !event.rnamespace || handleObj.namespace === false ||
19682					event.rnamespace.test( handleObj.namespace ) ) {
19683
19684					event.handleObj = handleObj;
19685					event.data = handleObj.data;
19686
19687					ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
19688						handleObj.handler ).apply( matched.elem, args );
19689
19690					if ( ret !== undefined ) {
19691						if ( ( event.result = ret ) === false ) {
19692							event.preventDefault();
19693							event.stopPropagation();
19694						}
19695					}
19696				}
19697			}
19698		}
19699
19700		// Call the postDispatch hook for the mapped type
19701		if ( special.postDispatch ) {
19702			special.postDispatch.call( this, event );
19703		}
19704
19705		return event.result;
19706	},
19707
19708	handlers: function( event, handlers ) {
19709		var i, handleObj, sel, matchedHandlers, matchedSelectors,
19710			handlerQueue = [],
19711			delegateCount = handlers.delegateCount,
19712			cur = event.target;
19713
19714		// Find delegate handlers
19715		if ( delegateCount &&
19716
vendor: 5,718 bytes, lines 19717-19902
19717			// Support: IE <=9
19718			// Black-hole SVG <use> instance trees (trac-13180)
19719			cur.nodeType &&
19720
19721			// Support: Firefox <=42
19722			// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
19723			// https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
19724			// Support: IE 11 only
19725			// ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
19726			!( event.type === "click" && event.button >= 1 ) ) {
19727
19728			for ( ; cur !== this; cur = cur.parentNode || this ) {
19729
19730				// Don't check non-elements (#13208)
19731				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
19732				if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
19733					matchedHandlers = [];
19734					matchedSelectors = {};
19735					for ( i = 0; i < delegateCount; i++ ) {
19736						handleObj = handlers[ i ];
19737
19738						// Don't conflict with Object.prototype properties (#13203)
19739						sel = handleObj.selector + " ";
19740
19741						if ( matchedSelectors[ sel ] === undefined ) {
19742							matchedSelectors[ sel ] = handleObj.needsContext ?
19743								jQuery( sel, this ).index( cur ) > -1 :
19744								jQuery.find( sel, this, null, [ cur ] ).length;
19745						}
19746						if ( matchedSelectors[ sel ] ) {
19747							matchedHandlers.push( handleObj );
19748						}
19749					}
19750					if ( matchedHandlers.length ) {
19751						handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
19752					}
19753				}
19754			}
19755		}
19756
19757		// Add the remaining (directly-bound) handlers
19758		cur = this;
19759		if ( delegateCount < handlers.length ) {
19760			handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
19761		}
19762
19763		return handlerQueue;
19764	},
19765
19766	addProp: function( name, hook ) {
19767		Object.defineProperty( jQuery.Event.prototype, name, {
19768			enumerable: true,
19769			configurable: true,
19770
19771			get: isFunction( hook ) ?
19772				function() {
19773					if ( this.originalEvent ) {
19774							return hook( this.originalEvent );
19775					}
19776				} :
19777				function() {
19778					if ( this.originalEvent ) {
19779							return this.originalEvent[ name ];
19780					}
19781				},
19782
19783			set: function( value ) {
19784				Object.defineProperty( this, name, {
19785					enumerable: true,
19786					configurable: true,
19787					writable: true,
19788					value: value
19789				} );
19790			}
19791		} );
19792	},
19793
19794	fix: function( originalEvent ) {
19795		return originalEvent[ jQuery.expando ] ?
19796			originalEvent :
19797			new jQuery.Event( originalEvent );
19798	},
19799
19800	special: {
19801		load: {
19802
19803			// Prevent triggered image.load events from bubbling to window.load
19804			noBubble: true
19805		},
19806		click: {
19807
19808			// Utilize native event to ensure correct state for checkable inputs
19809			setup: function( data ) {
19810
19811				// For mutual compressibility with _default, replace `this` access with a local var.
19812				// `|| data` is dead code meant only to preserve the variable through minification.
19813				var el = this || data;
19814
19815				// Claim the first handler
19816				if ( rcheckableType.test( el.type ) &&
19817					el.click && nodeName( el, "input" ) ) {
19818
19819					// dataPriv.set( el, "click", ... )
19820					leverageNative( el, "click", returnTrue );
19821				}
19822
19823				// Return false to allow normal processing in the caller
19824				return false;
19825			},
19826			trigger: function( data ) {
19827
19828				// For mutual compressibility with _default, replace `this` access with a local var.
19829				// `|| data` is dead code meant only to preserve the variable through minification.
19830				var el = this || data;
19831
19832				// Force setup before triggering a click
19833				if ( rcheckableType.test( el.type ) &&
19834					el.click && nodeName( el, "input" ) ) {
19835
19836					leverageNative( el, "click" );
19837				}
19838
19839				// Return non-false to allow normal event-path propagation
19840				return true;
19841			},
19842
19843			// For cross-browser consistency, suppress native .click() on links
19844			// Also prevent it if we're currently inside a leveraged native-event stack
19845			_default: function( event ) {
19846				var target = event.target;
19847				return rcheckableType.test( target.type ) &&
19848					target.click && nodeName( target, "input" ) &&
19849					dataPriv.get( target, "click" ) ||
19850					nodeName( target, "a" );
19851			}
19852		},
19853
19854		beforeunload: {
19855			postDispatch: function( event ) {
19856
19857				// Support: Firefox 20+
19858				// Firefox doesn't alert if the returnValue field is not set.
19859				if ( event.result !== undefined && event.originalEvent ) {
19860					event.originalEvent.returnValue = event.result;
19861				}
19862			}
19863		}
19864	}
19865};
19866
19867// Ensure the presence of an event listener that handles manually-triggered
19868// synthetic events by interrupting progress until reinvoked in response to
19869// *native* events that it fires directly, ensuring that state changes have
19870// already occurred before other listeners are invoked.
19871function leverageNative( el, type, expectSync ) {
19872
19873	// Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add
19874	if ( !expectSync ) {
19875		if ( dataPriv.get( el, type ) === undefined ) {
19876			jQuery.event.add( el, type, returnTrue );
19877		}
19878		return;
19879	}
19880
19881	// Register the controller as a special universal handler for all event namespaces
19882	dataPriv.set( el, type, false );
19883	jQuery.event.add( el, type, {
19884		namespace: false,
19885		handler: function( event ) {
19886			var notAsync, result,
19887				saved = dataPriv.get( this, type );
19888
19889			if ( ( event.isTrigger & 1 ) && this[ type ] ) {
19890
19891				// Interrupt processing of the outer synthetic .trigger()ed event
19892				// Saved data should be false in such cases, but might be a leftover capture object
19893				// from an async native handler (gh-4350)
19894				if ( !saved.length ) {
19895
19896					// Store arguments for use when handling the inner native event
19897					// There will always be at least one argument (an event object), so this array
19898					// will not be confused with a leftover capture object.
19899					saved = slice.call( arguments );
19900					dataPriv.set( this, type, saved );
19901
19902					// Trigger the native event and capture its result
19903					// Support: IE <=9 - 11+
19904					// focus() and blur() are asynchronous
19905					notAsync = expectSync( this, type );
19906					this[ type ]();
19907					result = dataPriv.get( this, type );
19908					if ( saved !== result || notAsync ) {
19909						dataPriv.set( this, type, false );
19910					} else {
19911						result = {};
19912					}
19913					if ( saved !== result ) {
19914
19915						// Cancel the outer synthetic event
19916						event.stopImmediatePropagation();
19917						event.preventDefault();
19918						return result.value;
19919					}
19920
19921				// If this is an inner synthetic event for an event with a bubbling surrogate
19922				// (focus or blur), assume that the surrogate already propagated from triggering the
19923				// native event and prevent that from happening again here.
19924				// This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the
19925				// bubbling surrogate propagates *after* the non-bubbling base), but that seems
19926				// less bad than duplication.
19927				} else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) {
19928					event.stopPropagation();
19929				}
19930
19931			// If this is a native event triggered above, everything is now in order
19932			// Fire an inner synthetic event with the original arguments
19933			} else if ( saved.length ) {
19934
19935				// ...and capture the result
19936				dataPriv.set( this, type, {
19937					value: jQuery.event.trigger(
19938
19939						// Support: IE <=9 - 11+
19940						// Extend with the prototype to reset the above stopImmediatePropagation()
19941						jQuery.extend( saved[ 0 ], jQuery.Event.prototype ),
19942						saved.slice( 1 ),
19943						this
19944					)
19945				} );
19946
19947				// Abort handling of the native event
19948				event.stopImmediatePropagation();
19949			}
19950		}
19951	} );
19952}
19953
19954jQuery.removeEvent = function( elem, type, handle ) {
19955
19956	// This "if" is needed for plain objects
19957	if ( elem.removeEventListener ) {
19958		elem.removeEventListener( type, handle );
19959	}
19960};
19961
19962jQuery.Event = function( src, props ) {
19963
19964	// Allow instantiation without the 'new' keyword
19965	if ( !( this instanceof jQuery.Event ) ) {
19966		return new jQuery.Event( src, props );
19967	}
19968
19969	// Event object
19970	if ( src && src.type ) {
19971		this.originalEvent = src;
19972		this.type = src.type;
19973
19974		// Events bubbling up the document may have been marked as prevented
19975		// by a handler lower down the tree; reflect the correct value.
19976		this.isDefaultPrevented = src.defaultPrevented ||
19977				src.defaultPrevented === undefined &&
19978
19979				// Support: Android <=2.3 only
19980				src.returnValue === false ?
19981			returnTrue :
19982			returnFalse;
19983
19984		// Create target properties
19985		// Support: Safari <=6 - 7 only
19986		// Target should not be a text node (#504, #13143)
19987		this.target = ( src.target && src.target.nodeType === 3 ) ?
19988			src.target.parentNode :
19989			src.target;
19990
19991		this.currentTarget = src.currentTarget;
19992		this.relatedTarget = src.relatedTarget;
19993
19994	// Event type
19995	} else {
19996		this.type = src;
19997	}
19998
19999	// Put explicitly provided properties onto the event object
20000	if ( props ) {
20001		jQuery.extend( this, props );
20002	}
20003
20004	// Create a timestamp if incoming event doesn't have one
20005	this.timeStamp = src && src.timeStamp || Date.now();
20006
20007	// Mark it as fixed
20008	this[ jQuery.expando ] = true;
20009};
20010
20011// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
20012// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
20013jQuery.Event.prototype = {
20014	constructor: jQuery.Event,
20015	isDefaultPrevented: returnFalse,
20016	isPropagationStopped: returnFalse,
20017	isImmediatePropagationStopped: returnFalse,
20018	isSimulated: false,
20019
20020	preventDefault: function() {
20021		var e = this.originalEvent;
20022
20023		this.isDefaultPrevented = returnTrue;
20024
20025		if ( e && !this.isSimulated ) {
20026			e.preventDefault();
20027		}
20028	},
20029	stopPropagation: function() {
20030		var e = this.originalEvent;
20031
20032		this.isPropagationStopped = returnTrue;
20033
20034		if ( e && !this.isSimulated ) {
20035			e.stopPropagation();
20036		}
20037	},
20038	stopImmediatePropagation: function() {
20039		var e = this.originalEvent;
20040
20041		this.isImmediatePropagationStopped = returnTrue;
20042
20043		if ( e && !this.isSimulated ) {
20044			e.stopImmediatePropagation();
20045		}
20046
20047		this.stopPropagation();
20048	}
20049};
20050
20051// Includes all common event props including KeyEvent and MouseEvent specific props
20052jQuery.each( {
20053	altKey: true,
20054	bubbles: true,
20055	cancelable: true,
20056	changedTouches: true,
20057	ctrlKey: true,
20058	detail: true,
20059	eventPhase: true,
20060	metaKey: true,
20061	pageX: true,
20062	pageY: true,
20063	shiftKey: true,
20064	view: true,
20065	"char": true,
20066	code: true,
20067	charCode: true,
20068	key: true,
20069	keyCode: true,
20070	button: true,
20071	buttons: true,
20072	clientX: true,
20073	clientY: true,
20074	offsetX: true,
20075	offsetY: true,
20076	pointerId: true,
20077	pointerType: true,
20078	screenX: true,
20079	screenY: true,
20080	targetTouches: true,
20081	toElement: true,
20082	touches: true,
20083
20084	which: function( event ) {
20085		var button = event.button;
20086
20087		// Add which for key events
20088		if ( event.which == null && rkeyEvent.test( event.type ) ) {
20089			return event.charCode != null ? event.charCode : event.keyCode;
20090		}
20091
20092		// Add which for click: 1 === left; 2 === middle; 3 === right
20093		if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) {
20094			if ( button & 1 ) {
20095				return 1;
20096			}
20097
20098			if ( button & 2 ) {
20099				return 3;
20100			}
20101
20102			if ( button & 4 ) {
20103				return 2;
20104			}
20105
20106			return 0;
20107		}
20108
20109		return event.which;
20110	}
20111}, jQuery.event.addProp );
20112
20113jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) {
20114	jQuery.event.special[ type ] = {
20115
20116		// Utilize native event if possible so blur/focus sequence is correct
20117		setup: function() {
20118
20119			// Claim the first handler
20120			// dataPriv.set( this, "focus", ... )
20121			// dataPriv.set( this, "blur", ... )
20122			leverageNative( this, type, expectSync );
20123
20124			// Return false to allow normal processing in the caller
20125			return false;
vendor: 8,016 bytes, lines 20126-20427
20126		},
20127		trigger: function() {
20128
20129			// Force setup before trigger
20130			leverageNative( this, type );
20131
20132			// Return non-false to allow normal event-path propagation
20133			return true;
20134		},
20135
20136		delegateType: delegateType
20137	};
20138} );
20139
20140// Create mouseenter/leave events using mouseover/out and event-time checks
20141// so that event delegation works in jQuery.
20142// Do the same for pointerenter/pointerleave and pointerover/pointerout
20143//
20144// Support: Safari 7 only
20145// Safari sends mouseenter too often; see:
20146// https://bugs.chromium.org/p/chromium/issues/detail?id=470258
20147// for the description of the bug (it existed in older Chrome versions as well).
20148jQuery.each( {
20149	mouseenter: "mouseover",
20150	mouseleave: "mouseout",
20151	pointerenter: "pointerover",
20152	pointerleave: "pointerout"
20153}, function( orig, fix ) {
20154	jQuery.event.special[ orig ] = {
20155		delegateType: fix,
20156		bindType: fix,
20157
20158		handle: function( event ) {
20159			var ret,
20160				target = this,
20161				related = event.relatedTarget,
20162				handleObj = event.handleObj;
20163
20164			// For mouseenter/leave call the handler if related is outside the target.
20165			// NB: No relatedTarget if the mouse left/entered the browser window
20166			if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
20167				event.type = handleObj.origType;
20168				ret = handleObj.handler.apply( this, arguments );
20169				event.type = fix;
20170			}
20171			return ret;
20172		}
20173	};
20174} );
20175
20176jQuery.fn.extend( {
20177
20178	on: function( types, selector, data, fn ) {
20179		return on( this, types, selector, data, fn );
20180	},
20181	one: function( types, selector, data, fn ) {
20182		return on( this, types, selector, data, fn, 1 );
20183	},
20184	off: function( types, selector, fn ) {
20185		var handleObj, type;
20186		if ( types && types.preventDefault && types.handleObj ) {
20187
20188			// ( event )  dispatched jQuery.Event
20189			handleObj = types.handleObj;
20190			jQuery( types.delegateTarget ).off(
20191				handleObj.namespace ?
20192					handleObj.origType + "." + handleObj.namespace :
20193					handleObj.origType,
20194				handleObj.selector,
20195				handleObj.handler
20196			);
20197			return this;
20198		}
20199		if ( typeof types === "object" ) {
20200
20201			// ( types-object [, selector] )
20202			for ( type in types ) {
20203				this.off( type, selector, types[ type ] );
20204			}
20205			return this;
20206		}
20207		if ( selector === false || typeof selector === "function" ) {
20208
20209			// ( types [, fn] )
20210			fn = selector;
20211			selector = undefined;
20212		}
20213		if ( fn === false ) {
20214			fn = returnFalse;
20215		}
20216		return this.each( function() {
20217			jQuery.event.remove( this, types, fn, selector );
20218		} );
20219	}
20220} );
20221
20222
20223var
20224
20225	// Support: IE <=10 - 11, Edge 12 - 13 only
20226	// In IE/Edge using regex groups here causes severe slowdowns.
20227	// See https://connect.microsoft.com/IE/feedback/details/1736512/
20228	rnoInnerhtml = /<script|<style|<link/i,
20229
20230	// checked="checked" or checked
20231	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
20232	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
20233
20234// Prefer a tbody over its parent table for containing new rows
20235function manipulationTarget( elem, content ) {
20236	if ( nodeName( elem, "table" ) &&
20237		nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {
20238
20239		return jQuery( elem ).children( "tbody" )[ 0 ] || elem;
20240	}
20241
20242	return elem;
20243}
20244
20245// Replace/restore the type attribute of script elements for safe DOM manipulation
20246function disableScript( elem ) {
20247	elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;
20248	return elem;
20249}
20250function restoreScript( elem ) {
20251	if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) {
20252		elem.type = elem.type.slice( 5 );
20253	} else {
20254		elem.removeAttribute( "type" );
20255	}
20256
20257	return elem;
20258}
20259
20260function cloneCopyEvent( src, dest ) {
20261	var i, l, type, pdataOld, udataOld, udataCur, events;
20262
20263	if ( dest.nodeType !== 1 ) {
20264		return;
20265	}
20266
20267	// 1. Copy private data: events, handlers, etc.
20268	if ( dataPriv.hasData( src ) ) {
20269		pdataOld = dataPriv.get( src );
20270		events = pdataOld.events;
20271
20272		if ( events ) {
20273			dataPriv.remove( dest, "handle events" );
20274
20275			for ( type in events ) {
20276				for ( i = 0, l = events[ type ].length; i < l; i++ ) {
20277					jQuery.event.add( dest, type, events[ type ][ i ] );
20278				}
20279			}
20280		}
20281	}
20282
20283	// 2. Copy user data
20284	if ( dataUser.hasData( src ) ) {
20285		udataOld = dataUser.access( src );
20286		udataCur = jQuery.extend( {}, udataOld );
20287
20288		dataUser.set( dest, udataCur );
20289	}
20290}
20291
20292// Fix IE bugs, see support tests
20293function fixInput( src, dest ) {
20294	var nodeName = dest.nodeName.toLowerCase();
20295
20296	// Fails to persist the checked state of a cloned checkbox or radio button.
20297	if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
20298		dest.checked = src.checked;
20299
20300	// Fails to return the selected option to the default selected state when cloning options
20301	} else if ( nodeName === "input" || nodeName === "textarea" ) {
20302		dest.defaultValue = src.defaultValue;
20303	}
20304}
20305
20306function domManip( collection, args, callback, ignored ) {
20307
20308	// Flatten any nested arrays
20309	args = flat( args );
20310
20311	var fragment, first, scripts, hasScripts, node, doc,
20312		i = 0,
20313		l = collection.length,
20314		iNoClone = l - 1,
20315		value = args[ 0 ],
20316		valueIsFunction = isFunction( value );
20317
20318	// We can't cloneNode fragments that contain checked, in WebKit
20319	if ( valueIsFunction ||
20320			( l > 1 && typeof value === "string" &&
20321				!support.checkClone && rchecked.test( value ) ) ) {
20322		return collection.each( function( index ) {
20323			var self = collection.eq( index );
20324			if ( valueIsFunction ) {
20325				args[ 0 ] = value.call( this, index, self.html() );
20326			}
20327			domManip( self, args, callback, ignored );
20328		} );
20329	}
20330
20331	if ( l ) {
20332		fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
20333		first = fragment.firstChild;
20334
20335		if ( fragment.childNodes.length === 1 ) {
20336			fragment = first;
20337		}
20338
20339		// Require either new content or an interest in ignored elements to invoke the callback
20340		if ( first || ignored ) {
20341			scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
20342			hasScripts = scripts.length;
20343
20344			// Use the original fragment for the last item
20345			// instead of the first because it can end up
20346			// being emptied incorrectly in certain situations (#8070).
20347			for ( ; i < l; i++ ) {
20348				node = fragment;
20349
20350				if ( i !== iNoClone ) {
20351					node = jQuery.clone( node, true, true );
20352
20353					// Keep references to cloned scripts for later restoration
20354					if ( hasScripts ) {
20355
20356						// Support: Android <=4.0 only, PhantomJS 1 only
20357						// push.apply(_, arraylike) throws on ancient WebKit
20358						jQuery.merge( scripts, getAll( node, "script" ) );
20359					}
20360				}
20361
20362				callback.call( collection[ i ], node, i );
20363			}
20364
20365			if ( hasScripts ) {
20366				doc = scripts[ scripts.length - 1 ].ownerDocument;
20367
20368				// Reenable scripts
20369				jQuery.map( scripts, restoreScript );
20370
20371				// Evaluate executable scripts on first document insertion
20372				for ( i = 0; i < hasScripts; i++ ) {
20373					node = scripts[ i ];
20374					if ( rscriptType.test( node.type || "" ) &&
20375						!dataPriv.access( node, "globalEval" ) &&
20376						jQuery.contains( doc, node ) ) {
20377
20378						if ( node.src && ( node.type || "" ).toLowerCase()  !== "module" ) {
20379
20380							// Optional AJAX dependency, but won't run scripts if not present
20381							if ( jQuery._evalUrl && !node.noModule ) {
20382								jQuery._evalUrl( node.src, {
20383									nonce: node.nonce || node.getAttribute( "nonce" )
20384								}, doc );
20385							}
20386						} else {
20387							DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc );
20388						}
20389					}
20390				}
20391			}
20392		}
20393	}
20394
20395	return collection;
20396}
20397
20398function remove( elem, selector, keepData ) {
20399	var node,
20400		nodes = selector ? jQuery.filter( selector, elem ) : elem,
20401		i = 0;
20402
20403	for ( ; ( node = nodes[ i ] ) != null; i++ ) {
20404		if ( !keepData && node.nodeType === 1 ) {
20405			jQuery.cleanData( getAll( node ) );
20406		}
20407
20408		if ( node.parentNode ) {
20409			if ( keepData && isAttached( node ) ) {
20410				setGlobalEval( getAll( node, "script" ) );
20411			}
20412			node.parentNode.removeChild( node );
20413		}
20414	}
20415
20416	return elem;
20417}
20418
20419jQuery.extend( {
20420	htmlPrefilter: function( html ) {
20421		return html;
20422	},
20423
20424	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
20425		var i, l, srcElements, destElements,
20426			clone = elem.cloneNode( true ),
20427			inPage = isAttached(
vendor: 5,988 bytes, lines 20427-20667
20427 elem );
20428
20429		// Fix IE cloning issues
20430		if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
20431				!jQuery.isXMLDoc( elem ) ) {
20432
20433			// We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2
20434			destElements = getAll( clone );
20435			srcElements = getAll( elem );
20436
20437			for ( i = 0, l = srcElements.length; i < l; i++ ) {
20438				fixInput( srcElements[ i ], destElements[ i ] );
20439			}
20440		}
20441
20442		// Copy the events from the original to the clone
20443		if ( dataAndEvents ) {
20444			if ( deepDataAndEvents ) {
20445				srcElements = srcElements || getAll( elem );
20446				destElements = destElements || getAll( clone );
20447
20448				for ( i = 0, l = srcElements.length; i < l; i++ ) {
20449					cloneCopyEvent( srcElements[ i ], destElements[ i ] );
20450				}
20451			} else {
20452				cloneCopyEvent( elem, clone );
20453			}
20454		}
20455
20456		// Preserve script evaluation history
20457		destElements = getAll( clone, "script" );
20458		if ( destElements.length > 0 ) {
20459			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
20460		}
20461
20462		// Return the cloned set
20463		return clone;
20464	},
20465
20466	cleanData: function( elems ) {
20467		var data, elem, type,
20468			special = jQuery.event.special,
20469			i = 0;
20470
20471		for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
20472			if ( acceptData( elem ) ) {
20473				if ( ( data = elem[ dataPriv.expando ] ) ) {
20474					if ( data.events ) {
20475						for ( type in data.events ) {
20476							if ( special[ type ] ) {
20477								jQuery.event.remove( elem, type );
20478
20479							// This is a shortcut to avoid jQuery.event.remove's overhead
20480							} else {
20481								jQuery.removeEvent( elem, type, data.handle );
20482							}
20483						}
20484					}
20485
20486					// Support: Chrome <=35 - 45+
20487					// Assign undefined instead of using delete, see Data#remove
20488					elem[ dataPriv.expando ] = undefined;
20489				}
20490				if ( elem[ dataUser.expando ] ) {
20491
20492					// Support: Chrome <=35 - 45+
20493					// Assign undefined instead of using delete, see Data#remove
20494					elem[ dataUser.expando ] = undefined;
20495				}
20496			}
20497		}
20498	}
20499} );
20500
20501jQuery.fn.extend( {
20502	detach: function( selector ) {
20503		return remove( this, selector, true );
20504	},
20505
20506	remove: function( selector ) {
20507		return remove( this, selector );
20508	},
20509
20510	text: function( value ) {
20511		return access( this, function( value ) {
20512			return value === undefined ?
20513				jQuery.text( this ) :
20514				this.empty().each( function() {
20515					if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
20516						this.textContent = value;
20517					}
20518				} );
20519		}, null, value, arguments.length );
20520	},
20521
20522	append: function() {
20523		return domManip( this, arguments, function( elem ) {
20524			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
20525				var target = manipulationTarget( this, elem );
20526				target.appendChild( elem );
20527			}
20528		} );
20529	},
20530
20531	prepend: function() {
20532		return domManip( this, arguments, function( elem ) {
20533			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
20534				var target = manipulationTarget( this, elem );
20535				target.insertBefore( elem, target.firstChild );
20536			}
20537		} );
20538	},
20539
20540	before: function() {
20541		return domManip( this, arguments, function( elem ) {
20542			if ( this.parentNode ) {
20543				this.parentNode.insertBefore( elem, this );
20544			}
20545		} );
20546	},
20547
20548	after: function() {
20549		return domManip( this, arguments, function( elem ) {
20550			if ( this.parentNode ) {
20551				this.parentNode.insertBefore( elem, this.nextSibling );
20552			}
20553		} );
20554	},
20555
20556	empty: function() {
20557		var elem,
20558			i = 0;
20559
20560		for ( ; ( elem = this[ i ] ) != null; i++ ) {
20561			if ( elem.nodeType === 1 ) {
20562
20563				// Prevent memory leaks
20564				jQuery.cleanData( getAll( elem, false ) );
20565
20566				// Remove any remaining nodes
20567				elem.textContent = "";
20568			}
20569		}
20570
20571		return this;
20572	},
20573
20574	clone: function( dataAndEvents, deepDataAndEvents ) {
20575		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
20576		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
20577
20578		return this.map( function() {
20579			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
20580		} );
20581	},
20582
20583	html: function( value ) {
20584		return access( this, function( value ) {
20585			var elem = this[ 0 ] || {},
20586				i = 0,
20587				l = this.length;
20588
20589			if ( value === undefined && elem.nodeType === 1 ) {
20590				return elem.innerHTML;
20591			}
20592
20593			// See if we can take a shortcut and just use innerHTML
20594			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
20595				!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
20596
20597				value = jQuery.htmlPrefilter( value );
20598
20599				try {
20600					for ( ; i < l; i++ ) {
20601						elem = this[ i ] || {};
20602
20603						// Remove element nodes and prevent memory leaks
20604						if ( elem.nodeType === 1 ) {
20605							jQuery.cleanData( getAll( elem, false ) );
20606							elem.innerHTML = value;
20607						}
20608					}
20609
20610					elem = 0;
20611
20612				// If using innerHTML throws an exception, use the fallback method
20613				} catch ( e ) {}
20614			}
20615
20616			if ( elem ) {
20617				this.empty().append( value );
20618			}
20619		}, null, value, arguments.length );
20620	},
20621
20622	replaceWith: function() {
20623		var ignored = [];
20624
20625		// Make the changes, replacing each non-ignored context element with the new content
20626		return domManip( this, arguments, function( elem ) {
20627			var parent = this.parentNode;
20628
20629			if ( jQuery.inArray( this, ignored ) < 0 ) {
20630				jQuery.cleanData( getAll( this ) );
20631				if ( parent ) {
20632					parent.replaceChild( elem, this );
20633				}
20634			}
20635
20636		// Force callback invocation
20637		}, ignored );
20638	}
20639} );
20640
20641jQuery.each( {
20642	appendTo: "append",
20643	prependTo: "prepend",
20644	insertBefore: "before",
20645	insertAfter: "after",
20646	replaceAll: "replaceWith"
20647}, function( name, original ) {
20648	jQuery.fn[ name ] = function( selector ) {
20649		var elems,
20650			ret = [],
20651			insert = jQuery( selector ),
20652			last = insert.length - 1,
20653			i = 0;
20654
20655		for ( ; i <= last; i++ ) {
20656			elems = i === last ? this : this.clone( true );
20657			jQuery( insert[ i ] )[ original ]( elems );
20658
20659			// Support: Android <=4.0 only, PhantomJS 1 only
20660			// .get() because push.apply(_, arraylike) throws on ancient WebKit
20661			push.apply( ret, elems.get() );
20662		}
20663
20664		return this.pushStack( ret );
20665	};
20666} );
20667var r
vendor: 27,412 bytes, lines 20667-21648
20667numnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
20668
20669var getStyles = function( elem ) {
20670
20671		// Support: IE <=11 only, Firefox <=30 (#15098, #14150)
20672		// IE throws on elements created in popups
20673		// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
20674		var view = elem.ownerDocument.defaultView;
20675
20676		if ( !view || !view.opener ) {
20677			view = window;
20678		}
20679
20680		return view.getComputedStyle( elem );
20681	};
20682
20683var swap = function( elem, options, callback ) {
20684	var ret, name,
20685		old = {};
20686
20687	// Remember the old values, and insert the new ones
20688	for ( name in options ) {
20689		old[ name ] = elem.style[ name ];
20690		elem.style[ name ] = options[ name ];
20691	}
20692
20693	ret = callback.call( elem );
20694
20695	// Revert the old values
20696	for ( name in options ) {
20697		elem.style[ name ] = old[ name ];
20698	}
20699
20700	return ret;
20701};
20702
20703
20704var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
20705
20706
20707
20708( function() {
20709
20710	// Executing both pixelPosition & boxSizingReliable tests require only one layout
20711	// so they're executed at the same time to save the second computation.
20712	function computeStyleTests() {
20713
20714		// This is a singleton, we need to execute it only once
20715		if ( !div ) {
20716			return;
20717		}
20718
20719		container.style.cssText = "position:absolute;left:-11111px;width:60px;" +
20720			"margin-top:1px;padding:0;border:0";
20721		div.style.cssText =
20722			"position:relative;display:block;box-sizing:border-box;overflow:scroll;" +
20723			"margin:auto;border:1px;padding:1px;" +
20724			"width:60%;top:1%";
20725		documentElement.appendChild( container ).appendChild( div );
20726
20727		var divStyle = window.getComputedStyle( div );
20728		pixelPositionVal = divStyle.top !== "1%";
20729
20730		// Support: Android 4.0 - 4.3 only, Firefox <=3 - 44
20731		reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12;
20732
20733		// Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3
20734		// Some styles come back with percentage values, even though they shouldn't
20735		div.style.right = "60%";
20736		pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36;
20737
20738		// Support: IE 9 - 11 only
20739		// Detect misreporting of content dimensions for box-sizing:border-box elements
20740		boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36;
20741
20742		// Support: IE 9 only
20743		// Detect overflow:scroll screwiness (gh-3699)
20744		// Support: Chrome <=64
20745		// Don't get tricked when zoom affects offsetWidth (gh-4029)
20746		div.style.position = "absolute";
20747		scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12;
20748
20749		documentElement.removeChild( container );
20750
20751		// Nullify the div so it wouldn't be stored in the memory and
20752		// it will also be a sign that checks already performed
20753		div = null;
20754	}
20755
20756	function roundPixelMeasures( measure ) {
20757		return Math.round( parseFloat( measure ) );
20758	}
20759
20760	var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal,
20761		reliableTrDimensionsVal, reliableMarginLeftVal,
20762		container = document.createElement( "div" ),
20763		div = document.createElement( "div" );
20764
20765	// Finish early in limited (non-browser) environments
20766	if ( !div.style ) {
20767		return;
20768	}
20769
20770	// Support: IE <=9 - 11 only
20771	// Style of cloned element affects source element cloned (#8908)
20772	div.style.backgroundClip = "content-box";
20773	div.cloneNode( true ).style.backgroundClip = "";
20774	support.clearCloneStyle = div.style.backgroundClip === "content-box";
20775
20776	jQuery.extend( support, {
20777		boxSizingReliable: function() {
20778			computeStyleTests();
20779			return boxSizingReliableVal;
20780		},
20781		pixelBoxStyles: function() {
20782			computeStyleTests();
20783			return pixelBoxStylesVal;
20784		},
20785		pixelPosition: function() {
20786			computeStyleTests();
20787			return pixelPositionVal;
20788		},
20789		reliableMarginLeft: function() {
20790			computeStyleTests();
20791			return reliableMarginLeftVal;
20792		},
20793		scrollboxSize: function() {
20794			computeStyleTests();
20795			return scrollboxSizeVal;
20796		},
20797
20798		// Support: IE 9 - 11+, Edge 15 - 18+
20799		// IE/Edge misreport `getComputedStyle` of table rows with width/height
20800		// set in CSS while `offset*` properties report correct values.
20801		// Behavior in IE 9 is more subtle than in newer versions & it passes
20802		// some versions of this test; make sure not to make it pass there!
20803		reliableTrDimensions: function() {
20804			var table, tr, trChild, trStyle;
20805			if ( reliableTrDimensionsVal == null ) {
20806				table = document.createElement( "table" );
20807				tr = document.createElement( "tr" );
20808				trChild = document.createElement( "div" );
20809
20810				table.style.cssText = "position:absolute;left:-11111px";
20811				tr.style.height = "1px";
20812				trChild.style.height = "9px";
20813
20814				documentElement
20815					.appendChild( table )
20816					.appendChild( tr )
20817					.appendChild( trChild );
20818
20819				trStyle = window.getComputedStyle( tr );
20820				reliableTrDimensionsVal = parseInt( trStyle.height ) > 3;
20821
20822				documentElement.removeChild( table );
20823			}
20824			return reliableTrDimensionsVal;
20825		}
20826	} );
20827} )();
20828
20829
20830function curCSS( elem, name, computed ) {
20831	var width, minWidth, maxWidth, ret,
20832
20833		// Support: Firefox 51+
20834		// Retrieving style before computed somehow
20835		// fixes an issue with getting wrong values
20836		// on detached elements
20837		style = elem.style;
20838
20839	computed = computed || getStyles( elem );
20840
20841	// getPropertyValue is needed for:
20842	//   .css('filter') (IE 9 only, #12537)
20843	//   .css('--customProperty) (#3144)
20844	if ( computed ) {
20845		ret = computed.getPropertyValue( name ) || computed[ name ];
20846
20847		if ( ret === "" && !isAttached( elem ) ) {
20848			ret = jQuery.style( elem, name );
20849		}
20850
20851		// A tribute to the "awesome hack by Dean Edwards"
20852		// Android Browser returns percentage for some values,
20853		// but width seems to be reliably pixels.
20854		// This is against the CSSOM draft spec:
20855		// https://drafts.csswg.org/cssom/#resolved-values
20856		if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) {
20857
20858			// Remember the original values
20859			width = style.width;
20860			minWidth = style.minWidth;
20861			maxWidth = style.maxWidth;
20862
20863			// Put in the new values to get a computed value out
20864			style.minWidth = style.maxWidth = style.width = ret;
20865			ret = computed.width;
20866
20867			// Revert the changed values
20868			style.width = width;
20869			style.minWidth = minWidth;
20870			style.maxWidth = maxWidth;
20871		}
20872	}
20873
20874	return ret !== undefined ?
20875
20876		// Support: IE <=9 - 11 only
20877		// IE returns zIndex value as an integer.
20878		ret + "" :
20879		ret;
20880}
20881
20882
20883function addGetHookIf( conditionFn, hookFn ) {
20884
20885	// Define the hook, we'll check on the first run if it's really needed.
20886	return {
20887		get: function() {
20888			if ( conditionFn() ) {
20889
20890				// Hook not needed (or it's not possible to use it due
20891				// to missing dependency), remove it.
20892				delete this.get;
20893				return;
20894			}
20895
20896			// Hook needed; redefine it so that the support test is not executed again.
20897			return ( this.get = hookFn ).apply( this, arguments );
20898		}
20899	};
20900}
20901
20902
20903var cssPrefixes = [ "Webkit", "Moz", "ms" ],
20904	emptyStyle = document.createElement( "div" ).style,
20905	vendorProps = {};
20906
20907// Return a vendor-prefixed property or undefined
20908function vendorPropName( name ) {
20909
20910	// Check for vendor prefixed names
20911	var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
20912		i = cssPrefixes.length;
20913
20914	while ( i-- ) {
20915		name = cssPrefixes[ i ] + capName;
20916		if ( name in emptyStyle ) {
20917			return name;
20918		}
20919	}
20920}
20921
20922// Return a potentially-mapped jQuery.cssProps or vendor prefixed property
20923function finalPropName( name ) {
20924	var final = jQuery.cssProps[ name ] || vendorProps[ name ];
20925
20926	if ( final ) {
20927		return final;
20928	}
20929	if ( name in emptyStyle ) {
20930		return name;
20931	}
20932	return vendorProps[ name ] = vendorPropName( name ) || name;
20933}
20934
20935
20936var
20937
20938	// Swappable if display is none or starts with table
20939	// except "table", "table-cell", or "table-caption"
20940	// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
20941	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
20942	rcustomProp = /^--/,
20943	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
20944	cssNormalTransform = {
20945		letterSpacing: "0",
20946		fontWeight: "400"
20947	};
20948
20949function setPositiveNumber( _elem, value, subtract ) {
20950
20951	// Any relative (+/-) values have already been
20952	// normalized at this point
20953	var matches = rcssNum.exec( value );
20954	return matches ?
20955
20956		// Guard against undefined "subtract", e.g., when used as in cssHooks
20957		Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
20958		value;
20959}
20960
20961function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
20962	var i = dimension === "width" ? 1 : 0,
20963		extra = 0,
20964		delta = 0;
20965
20966	// Adjustment may not be necessary
20967	if ( box === ( isBorderBox ? "border" : "content" ) ) {
20968		return 0;
20969	}
20970
20971	for ( ; i < 4; i += 2 ) {
20972
20973		// Both box models exclude margin
20974		if ( box === "margin" ) {
20975			delta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
20976		}
20977
20978		// If we get here with a content-box, we're seeking "padding" or "border" or "margin"
20979		if ( !isBorderBox ) {
20980
20981			// Add padding
20982			delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
20983
20984			// For "border" or "margin", add border
20985			if ( box !== "padding" ) {
20986				delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
20987
20988			// But still keep track of it otherwise
20989			} else {
20990				extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
20991			}
20992
20993		// If we get here with a border-box (content + padding + border), we're seeking "content" or
20994		// "padding" or "margin"
20995		} else {
20996
20997			// For "content", subtract padding
20998			if ( box === "content" ) {
20999				delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
21000			}
21001
21002			// For "content" or "padding", subtract border
21003			if ( box !== "margin" ) {
21004				delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
21005			}
21006		}
21007	}
21008
21009	// Account for positive content-box scroll gutter when requested by providing computedVal
21010	if ( !isBorderBox && computedVal >= 0 ) {
21011
21012		// offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
21013		// Assuming integer scroll gutter, subtract the rest and round down
21014		delta += Math.max( 0, Math.ceil(
21015			elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
21016			computedVal -
21017			delta -
21018			extra -
21019			0.5
21020
21021		// If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter
21022		// Use an explicit zero to avoid NaN (gh-3964)
21023		) ) || 0;
21024	}
21025
21026	return delta;
21027}
21028
21029function getWidthOrHeight( elem, dimension, extra ) {
21030
21031	// Start with computed style
21032	var styles = getStyles( elem ),
21033
21034		// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322).
21035		// Fake content-box until we know it's needed to know the true value.
21036		boxSizingNeeded = !support.boxSizingReliable() || extra,
21037		isBorderBox = boxSizingNeeded &&
21038			jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
21039		valueIsBorderBox = isBorderBox,
21040
21041		val = curCSS( elem, dimension, styles ),
21042		offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 );
21043
21044	// Support: Firefox <=54
21045	// Return a confounding non-pixel value or feign ignorance, as appropriate.
21046	if ( rnumnonpx.test( val ) ) {
21047		if ( !extra ) {
21048			return val;
21049		}
21050		val = "auto";
21051	}
21052
21053
21054	// Support: IE 9 - 11 only
21055	// Use offsetWidth/offsetHeight for when box sizing is unreliable.
21056	// In those cases, the computed value can be trusted to be border-box.
21057	if ( ( !support.boxSizingReliable() && isBorderBox ||
21058
21059		// Support: IE 10 - 11+, Edge 15 - 18+
21060		// IE/Edge misreport `getComputedStyle` of table rows with width/height
21061		// set in CSS while `offset*` properties report correct values.
21062		// Interestingly, in some cases IE 9 doesn't suffer from this issue.
21063		!support.reliableTrDimensions() && nodeName( elem, "tr" ) ||
21064
21065		// Fall back to offsetWidth/offsetHeight when value is "auto"
21066		// This happens for inline elements with no explicit setting (gh-3571)
21067		val === "auto" ||
21068
21069		// Support: Android <=4.1 - 4.3 only
21070		// Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)
21071		!parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) &&
21072
21073		// Make sure the element is visible & connected
21074		elem.getClientRects().length ) {
21075
21076		isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
21077
21078		// Where available, offsetWidth/offsetHeight approximate border box dimensions.
21079		// Where not available (e.g., SVG), assume unreliable box-sizing and interpret the
21080		// retrieved value as a content box dimension.
21081		valueIsBorderBox = offsetProp in elem;
21082		if ( valueIsBorderBox ) {
21083			val = elem[ offsetProp ];
21084		}
21085	}
21086
21087	// Normalize "" and auto
21088	val = parseFloat( val ) || 0;
21089
21090	// Adjust for the element's box model
21091	return ( val +
21092		boxModelAdjustment(
21093			elem,
21094			dimension,
21095			extra || ( isBorderBox ? "border" : "content" ),
21096			valueIsBorderBox,
21097			styles,
21098
21099			// Provide the current computed size to request scroll gutter calculation (gh-3589)
21100			val
21101		)
21102	) + "px";
21103}
21104
21105jQuery.extend( {
21106
21107	// Add in style property hooks for overriding the default
21108	// behavior of getting and setting a style property
21109	cssHooks: {
21110		opacity: {
21111			get: function( elem, computed ) {
21112				if ( computed ) {
21113
21114					// We should always get a number back from opacity
21115					var ret = curCSS( elem, "opacity" );
21116					return ret === "" ? "1" : ret;
21117				}
21118			}
21119		}
21120	},
21121
21122	// Don't automatically add "px" to these possibly-unitless properties
21123	cssNumber: {
21124		"animationIterationCount": true,
21125		"columnCount": true,
21126		"fillOpacity": true,
21127		"flexGrow": true,
21128		"flexShrink": true,
21129		"fontWeight": true,
21130		"gridArea": true,
21131		"gridColumn": true,
21132		"gridColumnEnd": true,
21133		"gridColumnStart": true,
21134		"gridRow": true,
21135		"gridRowEnd": true,
21136		"gridRowStart": true,
21137		"lineHeight": true,
21138		"opacity": true,
21139		"order": true,
21140		"orphans": true,
21141		"widows": true,
21142		"zIndex": true,
21143		"zoom": true
21144	},
21145
21146	// Add in properties whose names you wish to fix before
21147	// setting or getting the value
21148	cssProps: {},
21149
21150	// Get and set the style property on a DOM Node
21151	style: function( elem, name, value, extra ) {
21152
21153		// Don't set styles on text and comment nodes
21154		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
21155			return;
21156		}
21157
21158		// Make sure that we're working with the right name
21159		var ret, type, hooks,
21160			origName = camelCase( name ),
21161			isCustomProp = rcustomProp.test( name ),
21162			style = elem.style;
21163
21164		// Make sure that we're working with the right name. We don't
21165		// want to query the value if it is a CSS custom property
21166		// since they are user-defined.
21167		if ( !isCustomProp ) {
21168			name = finalPropName( origName );
21169		}
21170
21171		// Gets hook for the prefixed version, then unprefixed version
21172		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
21173
21174		// Check if we're setting a value
21175		if ( value !== undefined ) {
21176			type = typeof value;
21177
21178			// Convert "+=" or "-=" to relative numbers (#7345)
21179			if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
21180				value = adjustCSS( elem, name, ret );
21181
21182				// Fixes bug #9237
21183				type = "number";
21184			}
21185
21186			// Make sure that null and NaN values aren't set (#7116)
21187			if ( value == null || value !== value ) {
21188				return;
21189			}
21190
21191			// If a number was passed in, add the unit (except for certain CSS properties)
21192			// The isCustomProp check can be removed in jQuery 4.0 when we only auto-append
21193			// "px" to a few hardcoded values.
21194			if ( type === "number" && !isCustomProp ) {
21195				value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
21196			}
21197
21198			// background-* props affect original clone's values
21199			if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
21200				style[ name ] = "inherit";
21201			}
21202
21203			// If a hook was provided, use that value, otherwise just set the specified value
21204			if ( !hooks || !( "set" in hooks ) ||
21205				( value = hooks.set( elem, value, extra ) ) !== undefined ) {
21206
21207				if ( isCustomProp ) {
21208					style.setProperty( name, value );
21209				} else {
21210					style[ name ] = value;
21211				}
21212			}
21213
21214		} else {
21215
21216			// If a hook was provided get the non-computed value from there
21217			if ( hooks && "get" in hooks &&
21218				( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
21219
21220				return ret;
21221			}
21222
21223			// Otherwise just get the value from the style object
21224			return style[ name ];
21225		}
21226	},
21227
21228	css: function( elem, name, extra, styles ) {
21229		var val, num, hooks,
21230			origName = camelCase( name ),
21231			isCustomProp = rcustomProp.test( name );
21232
21233		// Make sure that we're working with the right name. We don't
21234		// want to modify the value if it is a CSS custom property
21235		// since they are user-defined.
21236		if ( !isCustomProp ) {
21237			name = finalPropName( origName );
21238		}
21239
21240		// Try prefixed name followed by the unprefixed name
21241		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
21242
21243		// If a hook was provided get the computed value from there
21244		if ( hooks && "get" in hooks ) {
21245			val = hooks.get( elem, true, extra );
21246		}
21247
21248		// Otherwise, if a way to get the computed value exists, use that
21249		if ( val === undefined ) {
21250			val = curCSS( elem, name, styles );
21251		}
21252
21253		// Convert "normal" to computed value
21254		if ( val === "normal" && name in cssNormalTransform ) {
21255			val = cssNormalTransform[ name ];
21256		}
21257
21258		// Make numeric if forced or a qualifier was provided and val looks numeric
21259		if ( extra === "" || extra ) {
21260			num = parseFloat( val );
21261			return extra === true || isFinite( num ) ? num || 0 : val;
21262		}
21263
21264		return val;
21265	}
21266} );
21267
21268jQuery.each( [ "height", "width" ], function( _i, dimension ) {
21269	jQuery.cssHooks[ dimension ] = {
21270		get: function( elem, computed, extra ) {
21271			if ( computed ) {
21272
21273				// Certain elements can have dimension info if we invisibly show them
21274				// but it must have a current display style that would benefit
21275				return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
21276
21277					// Support: Safari 8+
21278					// Table columns in Safari have non-zero offsetWidth & zero
21279					// getBoundingClientRect().width unless display is changed.
21280					// Support: IE <=11 only
21281					// Running getBoundingClientRect on a disconnected node
21282					// in IE throws an error.
21283					( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
21284						swap( elem, cssShow, function() {
21285							return getWidthOrHeight( elem, dimension, extra );
21286						} ) :
21287						getWidthOrHeight( elem, dimension, extra );
21288			}
21289		},
21290
21291		set: function( elem, value, extra ) {
21292			var matches,
21293				styles = getStyles( elem ),
21294
21295				// Only read styles.position if the test has a chance to fail
21296				// to avoid forcing a reflow.
21297				scrollboxSizeBuggy = !support.scrollboxSize() &&
21298					styles.position === "absolute",
21299
21300				// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991)
21301				boxSizingNeeded = scrollboxSizeBuggy || extra,
21302				isBorderBox = boxSizingNeeded &&
21303					jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
21304				subtract = extra ?
21305					boxModelAdjustment(
21306						elem,
21307						dimension,
21308						extra,
21309						isBorderBox,
21310						styles
21311					) :
21312					0;
21313
21314			// Account for unreliable border-box dimensions by comparing offset* to computed and
21315			// faking a content-box to get border and padding (gh-3699)
21316			if ( isBorderBox && scrollboxSizeBuggy ) {
21317				subtract -= Math.ceil(
21318					elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
21319					parseFloat( styles[ dimension ] ) -
21320					boxModelAdjustment( elem, dimension, "border", false, styles ) -
21321					0.5
21322				);
21323			}
21324
21325			// Convert to pixels if value adjustment is needed
21326			if ( subtract && ( matches = rcssNum.exec( value ) ) &&
21327				( matches[ 3 ] || "px" ) !== "px" ) {
21328
21329				elem.style[ dimension ] = value;
21330				value = jQuery.css( elem, dimension );
21331			}
21332
21333			return setPositiveNumber( elem, value, subtract );
21334		}
21335	};
21336} );
21337
21338jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
21339	function( elem, computed ) {
21340		if ( computed ) {
21341			return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
21342				elem.getBoundingClientRect().left -
21343					swap( elem, { marginLeft: 0 }, function() {
21344						return elem.getBoundingClientRect().left;
21345					} )
21346				) + "px";
21347		}
21348	}
21349);
21350
21351// These hooks are used by animate to expand properties
21352jQuery.each( {
21353	margin: "",
21354	padding: "",
21355	border: "Width"
21356}, function( prefix, suffix ) {
21357	jQuery.cssHooks[ prefix + suffix ] = {
21358		expand: function( value ) {
21359			var i = 0,
21360				expanded = {},
21361
21362				// Assumes a single number if not a string
21363				parts = typeof value === "string" ? value.split( " " ) : [ value ];
21364
21365			for ( ; i < 4; i++ ) {
21366				expanded[ prefix + cssExpand[ i ] + suffix ] =
21367					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
21368			}
21369
21370			return expanded;
21371		}
21372	};
21373
21374	if ( prefix !== "margin" ) {
21375		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
21376	}
21377} );
21378
21379jQuery.fn.extend( {
21380	css: function( name, value ) {
21381		return access( this, function( elem, name, value ) {
21382			var styles, len,
21383				map = {},
21384				i = 0;
21385
21386			if ( Array.isArray( name ) ) {
21387				styles = getStyles( elem );
21388				len = name.length;
21389
21390				for ( ; i < len; i++ ) {
21391					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
21392				}
21393
21394				return map;
21395			}
21396
21397			return value !== undefined ?
21398				jQuery.style( elem, name, value ) :
21399				jQuery.css( elem, name );
21400		}, name, value, arguments.length > 1 );
21401	}
21402} );
21403
21404
21405function Tween( elem, options, prop, end, easing ) {
21406	return new Tween.prototype.init( elem, options, prop, end, easing );
21407}
21408jQuery.Tween = Tween;
21409
21410Tween.prototype = {
21411	constructor: Tween,
21412	init: function( elem, options, prop, end, easing, unit ) {
21413		this.elem = elem;
21414		this.prop = prop;
21415		this.easing = easing || jQuery.easing._default;
21416		this.options = options;
21417		this.start = this.now = this.cur();
21418		this.end = end;
21419		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
21420	},
21421	cur: function() {
21422		var hooks = Tween.propHooks[ this.prop ];
21423
21424		return hooks && hooks.get ?
21425			hooks.get( this ) :
21426			Tween.propHooks._default.get( this );
21427	},
21428	run: function( percent ) {
21429		var eased,
21430			hooks = Tween.propHooks[ this.prop ];
21431
21432		if ( this.options.duration ) {
21433			this.pos = eased = jQuery.easing[ this.easing ](
21434				percent, this.options.duration * percent, 0, 1, this.options.duration
21435			);
21436		} else {
21437			this.pos = eased = percent;
21438		}
21439		this.now = ( this.end - this.start ) * eased + this.start;
21440
21441		if ( this.options.step ) {
21442			this.options.step.call( this.elem, this.now, this );
21443		}
21444
21445		if ( hooks && hooks.set ) {
21446			hooks.set( this );
21447		} else {
21448			Tween.propHooks._default.set( this );
21449		}
21450		return this;
21451	}
21452};
21453
21454Tween.prototype.init.prototype = Tween.prototype;
21455
21456Tween.propHooks = {
21457	_default: {
21458		get: function( tween ) {
21459			var result;
21460
21461			// Use a property on the element directly when it is not a DOM element,
21462			// or when there is no matching style property that exists.
21463			if ( tween.elem.nodeType !== 1 ||
21464				tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {
21465				return tween.elem[ tween.prop ];
21466			}
21467
21468			// Passing an empty string as a 3rd parameter to .css will automatically
21469			// attempt a parseFloat and fallback to a string if the parse fails.
21470			// Simple values such as "10px" are parsed to Float;
21471			// complex values such as "rotate(1rad)" are returned as-is.
21472			result = jQuery.css( tween.elem, tween.prop, "" );
21473
21474			// Empty strings, null, undefined and "auto" are converted to 0.
21475			return !result || result === "auto" ? 0 : result;
21476		},
21477		set: function( tween ) {
21478
21479			// Use step hook for back compat.
21480			// Use cssHook if its there.
21481			// Use .style if available and use plain properties where available.
21482			if ( jQuery.fx.step[ tween.prop ] ) {
21483				jQuery.fx.step[ tween.prop ]( tween );
21484			} else if ( tween.elem.nodeType === 1 && (
21485					jQuery.cssHooks[ tween.prop ] ||
21486					tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) {
21487				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
21488			} else {
21489				tween.elem[ tween.prop ] = tween.now;
21490			}
21491		}
21492	}
21493};
21494
21495// Support: IE <=9 only
21496// Panic based approach to setting things on disconnected nodes
21497Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
21498	set: function( tween ) {
21499		if ( tween.elem.nodeType && tween.elem.parentNode ) {
21500			tween.elem[ tween.prop ] = tween.now;
21501		}
21502	}
21503};
21504
21505jQuery.easing = {
21506	linear: function( p ) {
21507		return p;
21508	},
21509	swing: function( p ) {
21510		return 0.5 - Math.cos( p * Math.PI ) / 2;
21511	},
21512	_default: "swing"
21513};
21514
21515jQuery.fx = Tween.prototype.init;
21516
21517// Back compat <1.8 extension point
21518jQuery.fx.step = {};
21519
21520
21521
21522
21523var
21524	fxNow, inProgress,
21525	rfxtypes = /^(?:toggle|show|hide)$/,
21526	rrun = /queueHooks$/;
21527
21528function schedule() {
21529	if ( inProgress ) {
21530		if ( document.hidden === false && window.requestAnimationFrame ) {
21531			window.requestAnimationFrame( schedule );
21532		} else {
21533			window.setTimeout( schedule, jQuery.fx.interval );
21534		}
21535
21536		jQuery.fx.tick();
21537	}
21538}
21539
21540// Animations created synchronously will run synchronously
21541function createFxNow() {
21542	window.setTimeout( function() {
21543		fxNow = undefined;
21544	} );
21545	return ( fxNow = Date.now() );
21546}
21547
21548// Generate parameters to create a standard animation
21549function genFx( type, includeWidth ) {
21550	var which,
21551		i = 0,
21552		attrs = { height: type };
21553
21554	// If we include width, step value is 1 to do all cssExpand values,
21555	// otherwise step value is 2 to skip over Left and Right
21556	includeWidth = includeWidth ? 1 : 0;
21557	for ( ; i < 4; i += 2 - includeWidth ) {
21558		which = cssExpand[ i ];
21559		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
21560	}
21561
21562	if ( includeWidth ) {
21563		attrs.opacity = attrs.width = type;
21564	}
21565
21566	return attrs;
21567}
21568
21569function createTween( value, prop, animation ) {
21570	var tween,
21571		collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
21572		index = 0,
21573		length = collection.length;
21574	for ( ; index < length; index++ ) {
21575		if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {
21576
21577			// We're done with this property
21578			return tween;
21579		}
21580	}
21581}
21582
21583function defaultPrefilter( elem, props, opts ) {
21584	var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,
21585		isBox = "width" in props || "height" in props,
21586		anim = this,
21587		orig = {},
21588		style = elem.style,
21589		hidden = elem.nodeType && isHiddenWithinTree( elem ),
21590		dataShow = dataPriv.get( elem, "fxshow" );
21591
21592	// Queue-skipping animations hijack the fx hooks
21593	if ( !opts.queue ) {
21594		hooks = jQuery._queueHooks( elem, "fx" );
21595		if ( hooks.unqueued == null ) {
21596			hooks.unqueued = 0;
21597			oldfire = hooks.empty.fire;
21598			hooks.empty.fire = function() {
21599				if ( !hooks.unqueued ) {
21600					oldfire();
21601				}
21602			};
21603		}
21604		hooks.unqueued++;
21605
21606		anim.always( function() {
21607
21608			// Ensure the complete handler is called before this completes
21609			anim.always( function() {
21610				hooks.unqueued--;
21611				if ( !jQuery.queue( elem, "fx" ).length ) {
21612					hooks.empty.fire();
21613				}
21614			} );
21615		} );
21616	}
21617
21618	// Detect show/hide animations
21619	for ( prop in props ) {
21620		value = props[ prop ];
21621		if ( rfxtypes.test( value ) ) {
21622			delete props[ prop ];
21623			toggle = toggle || value === "toggle";
21624			if ( value === ( hidden ? "hide" : "show" ) ) {
21625
21626				// Pretend to be hidden if this is a "show" and
21627				// there is still data from a stopped show/hide
21628				if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
21629					hidden = true;
21630
21631				// Ignore all other no-op show/hide data
21632				} else {
21633					continue;
21634				}
21635			}
21636			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
21637		}
21638	}
21639
21640	// Bail out if this is a no-op like .hide().hide()
21641	propTween = !jQuery.isEmptyObject( props );
21642	if ( !propTween && jQuery.isEmptyObject( orig ) ) {
21643		return;
21644	}
21645
21646	// Restrict "overflow" and "display" styles during box animations
21647	if ( isBox && elem.nodeType === 1 ) {
21648
vendor: 4,220 bytes, lines 21649-21808
21649		// Support: IE <=9 - 11, Edge 12 - 15
21650		// Record all 3 overflow attributes because IE does not infer the shorthand
21651		// from identically-valued overflowX and overflowY and Edge just mirrors
21652		// the overflowX value there.
21653		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
21654
21655		// Identify a display type, preferring old show/hide data over the CSS cascade
21656		restoreDisplay = dataShow && dataShow.display;
21657		if ( restoreDisplay == null ) {
21658			restoreDisplay = dataPriv.get( elem, "display" );
21659		}
21660		display = jQuery.css( elem, "display" );
21661		if ( display === "none" ) {
21662			if ( restoreDisplay ) {
21663				display = restoreDisplay;
21664			} else {
21665
21666				// Get nonempty value(s) by temporarily forcing visibility
21667				showHide( [ elem ], true );
21668				restoreDisplay = elem.style.display || restoreDisplay;
21669				display = jQuery.css( elem, "display" );
21670				showHide( [ elem ] );
21671			}
21672		}
21673
21674		// Animate inline elements as inline-block
21675		if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) {
21676			if ( jQuery.css( elem, "float" ) === "none" ) {
21677
21678				// Restore the original display value at the end of pure show/hide animations
21679				if ( !propTween ) {
21680					anim.done( function() {
21681						style.display = restoreDisplay;
21682					} );
21683					if ( restoreDisplay == null ) {
21684						display = style.display;
21685						restoreDisplay = display === "none" ? "" : display;
21686					}
21687				}
21688				style.display = "inline-block";
21689			}
21690		}
21691	}
21692
21693	if ( opts.overflow ) {
21694		style.overflow = "hidden";
21695		anim.always( function() {
21696			style.overflow = opts.overflow[ 0 ];
21697			style.overflowX = opts.overflow[ 1 ];
21698			style.overflowY = opts.overflow[ 2 ];
21699		} );
21700	}
21701
21702	// Implement show/hide animations
21703	propTween = false;
21704	for ( prop in orig ) {
21705
21706		// General show/hide setup for this element animation
21707		if ( !propTween ) {
21708			if ( dataShow ) {
21709				if ( "hidden" in dataShow ) {
21710					hidden = dataShow.hidden;
21711				}
21712			} else {
21713				dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } );
21714			}
21715
21716			// Store hidden/visible for toggle so `.stop().toggle()` "reverses"
21717			if ( toggle ) {
21718				dataShow.hidden = !hidden;
21719			}
21720
21721			// Show elements before animating them
21722			if ( hidden ) {
21723				showHide( [ elem ], true );
21724			}
21725
21726			/* eslint-disable no-loop-func */
21727
21728			anim.done( function() {
21729
21730			/* eslint-enable no-loop-func */
21731
21732				// The final step of a "hide" animation is actually hiding the element
21733				if ( !hidden ) {
21734					showHide( [ elem ] );
21735				}
21736				dataPriv.remove( elem, "fxshow" );
21737				for ( prop in orig ) {
21738					jQuery.style( elem, prop, orig[ prop ] );
21739				}
21740			} );
21741		}
21742
21743		// Per-property setup
21744		propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
21745		if ( !( prop in dataShow ) ) {
21746			dataShow[ prop ] = propTween.start;
21747			if ( hidden ) {
21748				propTween.end = propTween.start;
21749				propTween.start = 0;
21750			}
21751		}
21752	}
21753}
21754
21755function propFilter( props, specialEasing ) {
21756	var index, name, easing, value, hooks;
21757
21758	// camelCase, specialEasing and expand cssHook pass
21759	for ( index in props ) {
21760		name = camelCase( index );
21761		easing = specialEasing[ name ];
21762		value = props[ index ];
21763		if ( Array.isArray( value ) ) {
21764			easing = value[ 1 ];
21765			value = props[ index ] = value[ 0 ];
21766		}
21767
21768		if ( index !== name ) {
21769			props[ name ] = value;
21770			delete props[ index ];
21771		}
21772
21773		hooks = jQuery.cssHooks[ name ];
21774		if ( hooks && "expand" in hooks ) {
21775			value = hooks.expand( value );
21776			delete props[ name ];
21777
21778			// Not quite $.extend, this won't overwrite existing keys.
21779			// Reusing 'index' because we have the correct "name"
21780			for ( index in value ) {
21781				if ( !( index in props ) ) {
21782					props[ index ] = value[ index ];
21783					specialEasing[ index ] = easing;
21784				}
21785			}
21786		} else {
21787			specialEasing[ name ] = easing;
21788		}
21789	}
21790}
21791
21792function Animation( elem, properties, options ) {
21793	var result,
21794		stopped,
21795		index = 0,
21796		length = Animation.prefilters.length,
21797		deferred = jQuery.Deferred().always( function() {
21798
21799			// Don't match elem in the :animated selector
21800			delete tick.elem;
21801		} ),
21802		tick = function() {
21803			if ( stopped ) {
21804				return false;
21805			}
21806			var currentTime = fxNow || createFxNow(),
21807				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
21808
vendor: 9,640 bytes, lines 21809-22199
21809				// Support: Android 2.3 only
21810				// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
21811				temp = remaining / animation.duration || 0,
21812				percent = 1 - temp,
21813				index = 0,
21814				length = animation.tweens.length;
21815
21816			for ( ; index < length; index++ ) {
21817				animation.tweens[ index ].run( percent );
21818			}
21819
21820			deferred.notifyWith( elem, [ animation, percent, remaining ] );
21821
21822			// If there's more to do, yield
21823			if ( percent < 1 && length ) {
21824				return remaining;
21825			}
21826
21827			// If this was an empty animation, synthesize a final progress notification
21828			if ( !length ) {
21829				deferred.notifyWith( elem, [ animation, 1, 0 ] );
21830			}
21831
21832			// Resolve the animation and report its conclusion
21833			deferred.resolveWith( elem, [ animation ] );
21834			return false;
21835		},
21836		animation = deferred.promise( {
21837			elem: elem,
21838			props: jQuery.extend( {}, properties ),
21839			opts: jQuery.extend( true, {
21840				specialEasing: {},
21841				easing: jQuery.easing._default
21842			}, options ),
21843			originalProperties: properties,
21844			originalOptions: options,
21845			startTime: fxNow || createFxNow(),
21846			duration: options.duration,
21847			tweens: [],
21848			createTween: function( prop, end ) {
21849				var tween = jQuery.Tween( elem, animation.opts, prop, end,
21850						animation.opts.specialEasing[ prop ] || animation.opts.easing );
21851				animation.tweens.push( tween );
21852				return tween;
21853			},
21854			stop: function( gotoEnd ) {
21855				var index = 0,
21856
21857					// If we are going to the end, we want to run all the tweens
21858					// otherwise we skip this part
21859					length = gotoEnd ? animation.tweens.length : 0;
21860				if ( stopped ) {
21861					return this;
21862				}
21863				stopped = true;
21864				for ( ; index < length; index++ ) {
21865					animation.tweens[ index ].run( 1 );
21866				}
21867
21868				// Resolve when we played the last frame; otherwise, reject
21869				if ( gotoEnd ) {
21870					deferred.notifyWith( elem, [ animation, 1, 0 ] );
21871					deferred.resolveWith( elem, [ animation, gotoEnd ] );
21872				} else {
21873					deferred.rejectWith( elem, [ animation, gotoEnd ] );
21874				}
21875				return this;
21876			}
21877		} ),
21878		props = animation.props;
21879
21880	propFilter( props, animation.opts.specialEasing );
21881
21882	for ( ; index < length; index++ ) {
21883		result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
21884		if ( result ) {
21885			if ( isFunction( result.stop ) ) {
21886				jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
21887					result.stop.bind( result );
21888			}
21889			return result;
21890		}
21891	}
21892
21893	jQuery.map( props, createTween, animation );
21894
21895	if ( isFunction( animation.opts.start ) ) {
21896		animation.opts.start.call( elem, animation );
21897	}
21898
21899	// Attach callbacks from options
21900	animation
21901		.progress( animation.opts.progress )
21902		.done( animation.opts.done, animation.opts.complete )
21903		.fail( animation.opts.fail )
21904		.always( animation.opts.always );
21905
21906	jQuery.fx.timer(
21907		jQuery.extend( tick, {
21908			elem: elem,
21909			anim: animation,
21910			queue: animation.opts.queue
21911		} )
21912	);
21913
21914	return animation;
21915}
21916
21917jQuery.Animation = jQuery.extend( Animation, {
21918
21919	tweeners: {
21920		"*": [ function( prop, value ) {
21921			var tween = this.createTween( prop, value );
21922			adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
21923			return tween;
21924		} ]
21925	},
21926
21927	tweener: function( props, callback ) {
21928		if ( isFunction( props ) ) {
21929			callback = props;
21930			props = [ "*" ];
21931		} else {
21932			props = props.match( rnothtmlwhite );
21933		}
21934
21935		var prop,
21936			index = 0,
21937			length = props.length;
21938
21939		for ( ; index < length; index++ ) {
21940			prop = props[ index ];
21941			Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
21942			Animation.tweeners[ prop ].unshift( callback );
21943		}
21944	},
21945
21946	prefilters: [ defaultPrefilter ],
21947
21948	prefilter: function( callback, prepend ) {
21949		if ( prepend ) {
21950			Animation.prefilters.unshift( callback );
21951		} else {
21952			Animation.prefilters.push( callback );
21953		}
21954	}
21955} );
21956
21957jQuery.speed = function( speed, easing, fn ) {
21958	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
21959		complete: fn || !fn && easing ||
21960			isFunction( speed ) && speed,
21961		duration: speed,
21962		easing: fn && easing || easing && !isFunction( easing ) && easing
21963	};
21964
21965	// Go to the end state if fx are off
21966	if ( jQuery.fx.off ) {
21967		opt.duration = 0;
21968
21969	} else {
21970		if ( typeof opt.duration !== "number" ) {
21971			if ( opt.duration in jQuery.fx.speeds ) {
21972				opt.duration = jQuery.fx.speeds[ opt.duration ];
21973
21974			} else {
21975				opt.duration = jQuery.fx.speeds._default;
21976			}
21977		}
21978	}
21979
21980	// Normalize opt.queue - true/undefined/null -> "fx"
21981	if ( opt.queue == null || opt.queue === true ) {
21982		opt.queue = "fx";
21983	}
21984
21985	// Queueing
21986	opt.old = opt.complete;
21987
21988	opt.complete = function() {
21989		if ( isFunction( opt.old ) ) {
21990			opt.old.call( this );
21991		}
21992
21993		if ( opt.queue ) {
21994			jQuery.dequeue( this, opt.queue );
21995		}
21996	};
21997
21998	return opt;
21999};
22000
22001jQuery.fn.extend( {
22002	fadeTo: function( speed, to, easing, callback ) {
22003
22004		// Show any hidden elements after setting opacity to 0
22005		return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show()
22006
22007			// Animate to the value specified
22008			.end().animate( { opacity: to }, speed, easing, callback );
22009	},
22010	animate: function( prop, speed, easing, callback ) {
22011		var empty = jQuery.isEmptyObject( prop ),
22012			optall = jQuery.speed( speed, easing, callback ),
22013			doAnimation = function() {
22014
22015				// Operate on a copy of prop so per-property easing won't be lost
22016				var anim = Animation( this, jQuery.extend( {}, prop ), optall );
22017
22018				// Empty animations, or finishing resolves immediately
22019				if ( empty || dataPriv.get( this, "finish" ) ) {
22020					anim.stop( true );
22021				}
22022			};
22023			doAnimation.finish = doAnimation;
22024
22025		return empty || optall.queue === false ?
22026			this.each( doAnimation ) :
22027			this.queue( optall.queue, doAnimation );
22028	},
22029	stop: function( type, clearQueue, gotoEnd ) {
22030		var stopQueue = function( hooks ) {
22031			var stop = hooks.stop;
22032			delete hooks.stop;
22033			stop( gotoEnd );
22034		};
22035
22036		if ( typeof type !== "string" ) {
22037			gotoEnd = clearQueue;
22038			clearQueue = type;
22039			type = undefined;
22040		}
22041		if ( clearQueue ) {
22042			this.queue( type || "fx", [] );
22043		}
22044
22045		return this.each( function() {
22046			var dequeue = true,
22047				index = type != null && type + "queueHooks",
22048				timers = jQuery.timers,
22049				data = dataPriv.get( this );
22050
22051			if ( index ) {
22052				if ( data[ index ] && data[ index ].stop ) {
22053					stopQueue( data[ index ] );
22054				}
22055			} else {
22056				for ( index in data ) {
22057					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
22058						stopQueue( data[ index ] );
22059					}
22060				}
22061			}
22062
22063			for ( index = timers.length; index--; ) {
22064				if ( timers[ index ].elem === this &&
22065					( type == null || timers[ index ].queue === type ) ) {
22066
22067					timers[ index ].anim.stop( gotoEnd );
22068					dequeue = false;
22069					timers.splice( index, 1 );
22070				}
22071			}
22072
22073			// Start the next in the queue if the last step wasn't forced.
22074			// Timers currently will call their complete callbacks, which
22075			// will dequeue but only if they were gotoEnd.
22076			if ( dequeue || !gotoEnd ) {
22077				jQuery.dequeue( this, type );
22078			}
22079		} );
22080	},
22081	finish: function( type ) {
22082		if ( type !== false ) {
22083			type = type || "fx";
22084		}
22085		return this.each( function() {
22086			var index,
22087				data = dataPriv.get( this ),
22088				queue = data[ type + "queue" ],
22089				hooks = data[ type + "queueHooks" ],
22090				timers = jQuery.timers,
22091				length = queue ? queue.length : 0;
22092
22093			// Enable finishing flag on private data
22094			data.finish = true;
22095
22096			// Empty the queue first
22097			jQuery.queue( this, type, [] );
22098
22099			if ( hooks && hooks.stop ) {
22100				hooks.stop.call( this, true );
22101			}
22102
22103			// Look for any active animations, and finish them
22104			for ( index = timers.length; index--; ) {
22105				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
22106					timers[ index ].anim.stop( true );
22107					timers.splice( index, 1 );
22108				}
22109			}
22110
22111			// Look for any animations in the old queue and finish them
22112			for ( index = 0; index < length; index++ ) {
22113				if ( queue[ index ] && queue[ index ].finish ) {
22114					queue[ index ].finish.call( this );
22115				}
22116			}
22117
22118			// Turn off finishing flag
22119			delete data.finish;
22120		} );
22121	}
22122} );
22123
22124jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) {
22125	var cssFn = jQuery.fn[ name ];
22126	jQuery.fn[ name ] = function( speed, easing, callback ) {
22127		return speed == null || typeof speed === "boolean" ?
22128			cssFn.apply( this, arguments ) :
22129			this.animate( genFx( name, true ), speed, easing, callback );
22130	};
22131} );
22132
22133// Generate shortcuts for custom animations
22134jQuery.each( {
22135	slideDown: genFx( "show" ),
22136	slideUp: genFx( "hide" ),
22137	slideToggle: genFx( "toggle" ),
22138	fadeIn: { opacity: "show" },
22139	fadeOut: { opacity: "hide" },
22140	fadeToggle: { opacity: "toggle" }
22141}, function( name, props ) {
22142	jQuery.fn[ name ] = function( speed, easing, callback ) {
22143		return this.animate( props, speed, easing, callback );
22144	};
22145} );
22146
22147jQuery.timers = [];
22148jQuery.fx.tick = function() {
22149	var timer,
22150		i = 0,
22151		timers = jQuery.timers;
22152
22153	fxNow = Date.now();
22154
22155	for ( ; i < timers.length; i++ ) {
22156		timer = timers[ i ];
22157
22158		// Run the timer and safely remove it when done (allowing for external removal)
22159		if ( !timer() && timers[ i ] === timer ) {
22160			timers.splice( i--, 1 );
22161		}
22162	}
22163
22164	if ( !timers.length ) {
22165		jQuery.fx.stop();
22166	}
22167	fxNow = undefined;
22168};
22169
22170jQuery.fx.timer = function( timer ) {
22171	jQuery.timers.push( timer );
22172	jQuery.fx.start();
22173};
22174
22175jQuery.fx.interval = 13;
22176jQuery.fx.start = function() {
22177	if ( inProgress ) {
22178		return;
22179	}
22180
22181	inProgress = true;
22182	schedule();
22183};
22184
22185jQuery.fx.stop = function() {
22186	inProgress = null;
22187};
22188
22189jQuery.fx.speeds = {
22190	slow: 600,
22191	fast: 200,
22192
22193	// Default speed
22194	_default: 400
22195};
22196
22197
22198// Based off of the plugin by Clint Helfers, with permission.
22199// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
vendor: 5,321 bytes, lines 22199-22424
22199
22200jQuery.fn.delay = function( time, type ) {
22201	time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
22202	type = type || "fx";
22203
22204	return this.queue( type, function( next, hooks ) {
22205		var timeout = window.setTimeout( next, time );
22206		hooks.stop = function() {
22207			window.clearTimeout( timeout );
22208		};
22209	} );
22210};
22211
22212
22213( function() {
22214	var input = document.createElement( "input" ),
22215		select = document.createElement( "select" ),
22216		opt = select.appendChild( document.createElement( "option" ) );
22217
22218	input.type = "checkbox";
22219
22220	// Support: Android <=4.3 only
22221	// Default value for a checkbox should be "on"
22222	support.checkOn = input.value !== "";
22223
22224	// Support: IE <=11 only
22225	// Must access selectedIndex to make default options select
22226	support.optSelected = opt.selected;
22227
22228	// Support: IE <=11 only
22229	// An input loses its value after becoming a radio
22230	input = document.createElement( "input" );
22231	input.value = "t";
22232	input.type = "radio";
22233	support.radioValue = input.value === "t";
22234} )();
22235
22236
22237var boolHook,
22238	attrHandle = jQuery.expr.attrHandle;
22239
22240jQuery.fn.extend( {
22241	attr: function( name, value ) {
22242		return access( this, jQuery.attr, name, value, arguments.length > 1 );
22243	},
22244
22245	removeAttr: function( name ) {
22246		return this.each( function() {
22247			jQuery.removeAttr( this, name );
22248		} );
22249	}
22250} );
22251
22252jQuery.extend( {
22253	attr: function( elem, name, value ) {
22254		var ret, hooks,
22255			nType = elem.nodeType;
22256
22257		// Don't get/set attributes on text, comment and attribute nodes
22258		if ( nType === 3 || nType === 8 || nType === 2 ) {
22259			return;
22260		}
22261
22262		// Fallback to prop when attributes are not supported
22263		if ( typeof elem.getAttribute === "undefined" ) {
22264			return jQuery.prop( elem, name, value );
22265		}
22266
22267		// Attribute hooks are determined by the lowercase version
22268		// Grab necessary hook if one is defined
22269		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
22270			hooks = jQuery.attrHooks[ name.toLowerCase() ] ||
22271				( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );
22272		}
22273
22274		if ( value !== undefined ) {
22275			if ( value === null ) {
22276				jQuery.removeAttr( elem, name );
22277				return;
22278			}
22279
22280			if ( hooks && "set" in hooks &&
22281				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
22282				return ret;
22283			}
22284
22285			elem.setAttribute( name, value + "" );
22286			return value;
22287		}
22288
22289		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
22290			return ret;
22291		}
22292
22293		ret = jQuery.find.attr( elem, name );
22294
22295		// Non-existent attributes return null, we normalize to undefined
22296		return ret == null ? undefined : ret;
22297	},
22298
22299	attrHooks: {
22300		type: {
22301			set: function( elem, value ) {
22302				if ( !support.radioValue && value === "radio" &&
22303					nodeName( elem, "input" ) ) {
22304					var val = elem.value;
22305					elem.setAttribute( "type", value );
22306					if ( val ) {
22307						elem.value = val;
22308					}
22309					return value;
22310				}
22311			}
22312		}
22313	},
22314
22315	removeAttr: function( elem, value ) {
22316		var name,
22317			i = 0,
22318
22319			// Attribute names can contain non-HTML whitespace characters
22320			// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
22321			attrNames = value && value.match( rnothtmlwhite );
22322
22323		if ( attrNames && elem.nodeType === 1 ) {
22324			while ( ( name = attrNames[ i++ ] ) ) {
22325				elem.removeAttribute( name );
22326			}
22327		}
22328	}
22329} );
22330
22331// Hooks for boolean attributes
22332boolHook = {
22333	set: function( elem, value, name ) {
22334		if ( value === false ) {
22335
22336			// Remove boolean attributes when set to false
22337			jQuery.removeAttr( elem, name );
22338		} else {
22339			elem.setAttribute( name, name );
22340		}
22341		return name;
22342	}
22343};
22344
22345jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) {
22346	var getter = attrHandle[ name ] || jQuery.find.attr;
22347
22348	attrHandle[ name ] = function( elem, name, isXML ) {
22349		var ret, handle,
22350			lowercaseName = name.toLowerCase();
22351
22352		if ( !isXML ) {
22353
22354			// Avoid an infinite loop by temporarily removing this function from the getter
22355			handle = attrHandle[ lowercaseName ];
22356			attrHandle[ lowercaseName ] = ret;
22357			ret = getter( elem, name, isXML ) != null ?
22358				lowercaseName :
22359				null;
22360			attrHandle[ lowercaseName ] = handle;
22361		}
22362		return ret;
22363	};
22364} );
22365
22366
22367
22368
22369var rfocusable = /^(?:input|select|textarea|button)$/i,
22370	rclickable = /^(?:a|area)$/i;
22371
22372jQuery.fn.extend( {
22373	prop: function( name, value ) {
22374		return access( this, jQuery.prop, name, value, arguments.length > 1 );
22375	},
22376
22377	removeProp: function( name ) {
22378		return this.each( function() {
22379			delete this[ jQuery.propFix[ name ] || name ];
22380		} );
22381	}
22382} );
22383
22384jQuery.extend( {
22385	prop: function( elem, name, value ) {
22386		var ret, hooks,
22387			nType = elem.nodeType;
22388
22389		// Don't get/set properties on text, comment and attribute nodes
22390		if ( nType === 3 || nType === 8 || nType === 2 ) {
22391			return;
22392		}
22393
22394		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
22395
22396			// Fix name and attach hooks
22397			name = jQuery.propFix[ name ] || name;
22398			hooks = jQuery.propHooks[ name ];
22399		}
22400
22401		if ( value !== undefined ) {
22402			if ( hooks && "set" in hooks &&
22403				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
22404				return ret;
22405			}
22406
22407			return ( elem[ name ] = value );
22408		}
22409
22410		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
22411			return ret;
22412		}
22413
22414		return elem[ name ];
22415	},
22416
22417	propHooks: {
22418		tabIndex: {
22419			get: function( elem ) {
22420
22421				// Support: IE <=9 - 11 only
22422				// elem.tabIndex doesn't always return the
22423				// correct value when it hasn't been explicitly set
22424				// 
22424https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
22425				// Use proper attribute retrieval(#12072)
22426				var tabindex = jQuery.find.attr( elem, "tabindex" );
22427
22428				if ( tabindex ) {
22429					return parseInt( tabindex, 10 );
22430				}
22431
22432				if (
22433					rfocusable.test( elem.nodeName ) ||
22434					rclickable.test( elem.nodeName ) &&
22435					elem.href
22436				) {
22437					return 0;
22438				}
22439
22440				return -1;
22441			}
22442		}
22443	},
22444
22445	propFix: {
22446		"for": "htmlFor",
22447		"class": "className"
22448	}
22449} );
22450
22451// Support: IE <=11 only
22452// Accessing the selectedIndex property
22453// forces the browser to respect setting selected
22454// on the option
22455// The getter ensures a default option is selected
22456// when in an optgroup
22457// eslint rule "no-unused-expressions" is disabled for this code
22458// since it considers such accessions noop
22459if ( !support.optSelected ) {
22460	jQuery.propHooks.selected = {
22461		get: function( elem ) {
22462
22463			/* eslint no-unused-expressions: "off" */
22464
22465			var parent = elem.parentNode;
22466			if ( parent && parent.parentNode ) {
22467				parent.parentNode.selectedIndex;
22468			}
22469			return null;
22470		},
22471		set: function( elem ) {
22472
22473			/* eslint no-unused-expressions: "off" */
22474
22475			var parent = elem.parentNode;
22476			if ( parent ) {
22477				parent.selectedIndex;
22478
22479				if ( parent.parentNode ) {
22480					parent.parentNode.selectedIndex;
22481				}
22482			}
22483		}
22484	};
22485}
22486
22487jQuery.each( [
22488	"tabIndex",
22489	"readOnly",
22490	"maxLength",
22491	"cellSpacing",
22492	"cellPadding",
22493	"rowSpan",
22494	"colSpan",
22495	"useMap",
22496	"frameBorder",
22497	"contentEditable"
22498], function() {
22499	jQuery.propFix[ this.toLowerCase() ] = this;
22500} );
22501
22502
22503
22504
22505	// Strip and collapse whitespace according to HTML spec
22506	// https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
22507	function stripAndCollapse( value ) {
22508		var tokens = value.match( rnothtmlwhite ) || [];
22509		return tokens.join( " " );
22510	}
22511
22512
22513function getClass( elem ) {
22514	return elem.getAttribute && elem.getAttribute( "class" ) || "";
22515}
22516
22517function classesToArray( value ) {
22518	if ( Array.isArray( value ) ) {
22519		return value;
22520	}
22521	if ( typeof value === "string" ) {
22522		return value.match( rnothtmlwhite ) || [];
22523	}
22524	return [];
22525}
22526
22527jQuery.fn.extend( {
22528	addClass: function( value ) {
22529		var classes, elem, cur, curValue, clazz, j, finalValue,
22530			i = 0;
22531
22532		if ( isFunction( value ) ) {
22533			return this.each( function( j ) {
22534				jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
22535			} );
22536		}
22537
22538		classes = classesToArray( value );
22539
22540		if ( classes.length ) {
22541			while ( ( elem = this[ i++ ] ) ) {
22542				curValue = getClass( elem );
22543				cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
22544
22545				if ( cur ) {
22546					j = 0;
22547					while ( ( clazz = classes[ j++ ] ) ) {
22548						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
22549							cur += clazz + " ";
22550						}
22551					}
22552
22553					// Only assign if different to avoid unneeded rendering.
22554					finalValue = stripAndCollapse( cur );
22555					if ( curValue !== finalValue ) {
22556						elem.setAttribute( "class", finalValue );
22557					}
22558				}
22559			}
22560		}
22561
22562		return this;
22563	},
22564
22565	removeClass: function( value ) {
22566		var classes, elem, cur, curValue, clazz, j, finalValue,
22567			i = 0;
22568
22569		if ( isFunction( value ) ) {
22570			return this.each( function( j ) {
22571				jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
22572			} );
22573		}
22574
22575		if ( !arguments.length ) {
22576			return this.attr( "class", "" );
22577		}
22578
22579		classes = classesToArray( value );
22580
22581		if ( classes.length ) {
22582			while ( ( elem = this[ i++ ] ) ) {
22583				curValue = getClass( elem );
22584
22585				// This expression is here for better compressibility (see addClass)
22586				cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
22587
22588				if ( cur ) {
22589					j = 0;
22590					while ( ( clazz = classes[ j++ ] ) ) {
22591
22592						// Remove *all* instances
22593						while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
22594							cur = cur.replace( " " + clazz + " ", " " );
22595						}
22596					}
22597
22598					// Only assign if different to avoid unneeded rendering.
22599					finalValue = stripAndCollapse( cur );
22600					if ( curValue !== finalValue ) {
22601						elem.setAttribute( "class", finalValue );
22602					}
22603				}
22604			}
22605		}
22606
22607		return this;
22608	},
22609
22610	toggleClass: function( value, stateVal ) {
22611		var type = typeof value,
22612			isValidValue = type === "string" || Array.isArray( value );
22613
22614		if ( typeof stateVal === "boolean" && isValidValue ) {
22615			return stateVal ? this.addClass( value ) : this.removeClass( value );
22616		}
22617
22618		if ( isFunction( value ) ) {
22619			return this.each( function( i ) {
22620				jQuery( this ).toggleClass(
22621					value.call( this, i, getClass( this ), stateVal ),
22622					stateVal
22623				);
22624			} );
22625		}
22626
22627		return this.each( function() {
22628			var className, i, self, classNames;
22629
22630			if ( isValidValue ) {
22631
22632				// Toggle individual class names
22633				i = 0;
22634				self = jQuery( this );
22635				classNames = classesToArray( value );
22636
22637				while ( ( className = classNames[ i++ ] ) ) {
22638
22639					// Check each className given, space separated list
22640					if ( self.hasClass( className ) ) {
22641						self.removeClass( className );
22642					} else {
22643						self.addClass( className );
22644					}
22645				}
22646
22647			// Toggle whole class name
22648			} else if ( value === undefined || type === "boolean" ) {
22649				className = getClass( this );
22650				if ( className ) {
22651
22652					// Store className if set
22653					dataPriv.set( this, "__className__", className );
22654				}
22655
22656				// If the element has a class name or if we're passed `false`,
22657				// then remove the whole classname (if there was one, the above saved it).
22658				// Otherwise bring back whatever was previously saved (if anything),
22659				// falling back to the empty string if nothing was stored.
22660				if ( this.setAttribute ) {
22661					this.setAttribute( "class",
22662						className || value === false ?
22663						"" :
22664						dataPriv.get( this, "__className__" ) || ""
22665					);
22666				}
22667			}
22668		} );
22669	},
22670
22671	hasClass: function( selector ) {
22672		var className, elem,
22673			i = 0;
22674
22675		className = " " + selector + " ";
22676		while ( ( elem = this[ i++ ] ) ) {
22677			if ( elem.nodeType === 1 &&
22678				( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
22679	
vendor: 1,792 bytes, lines 22679-22771
22679				return true;
22680			}
22681		}
22682
22683		return false;
22684	}
22685} );
22686
22687
22688
22689
22690var rreturn = /\r/g;
22691
22692jQuery.fn.extend( {
22693	val: function( value ) {
22694		var hooks, ret, valueIsFunction,
22695			elem = this[ 0 ];
22696
22697		if ( !arguments.length ) {
22698			if ( elem ) {
22699				hooks = jQuery.valHooks[ elem.type ] ||
22700					jQuery.valHooks[ elem.nodeName.toLowerCase() ];
22701
22702				if ( hooks &&
22703					"get" in hooks &&
22704					( ret = hooks.get( elem, "value" ) ) !== undefined
22705				) {
22706					return ret;
22707				}
22708
22709				ret = elem.value;
22710
22711				// Handle most common string cases
22712				if ( typeof ret === "string" ) {
22713					return ret.replace( rreturn, "" );
22714				}
22715
22716				// Handle cases where value is null/undef or number
22717				return ret == null ? "" : ret;
22718			}
22719
22720			return;
22721		}
22722
22723		valueIsFunction = isFunction( value );
22724
22725		return this.each( function( i ) {
22726			var val;
22727
22728			if ( this.nodeType !== 1 ) {
22729				return;
22730			}
22731
22732			if ( valueIsFunction ) {
22733				val = value.call( this, i, jQuery( this ).val() );
22734			} else {
22735				val = value;
22736			}
22737
22738			// Treat null/undefined as ""; convert numbers to string
22739			if ( val == null ) {
22740				val = "";
22741
22742			} else if ( typeof val === "number" ) {
22743				val += "";
22744
22745			} else if ( Array.isArray( val ) ) {
22746				val = jQuery.map( val, function( value ) {
22747					return value == null ? "" : value + "";
22748				} );
22749			}
22750
22751			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
22752
22753			// If set returns undefined, fall back to normal setting
22754			if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
22755				this.value = val;
22756			}
22757		} );
22758	}
22759} );
22760
22761jQuery.extend( {
22762	valHooks: {
22763		option: {
22764			get: function( elem ) {
22765
22766				var val = jQuery.find.attr( elem, "value" );
22767				return val != null ?
22768					val :
22769
22770					// Support: IE <=10 - 11 only
22771					// option.text throws exceptions (
vendor: 7,890 bytes, lines 22771-23070
22771#14686, #14858)
22772					// Strip and collapse whitespace
22773					// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
22774					stripAndCollapse( jQuery.text( elem ) );
22775			}
22776		},
22777		select: {
22778			get: function( elem ) {
22779				var value, option, i,
22780					options = elem.options,
22781					index = elem.selectedIndex,
22782					one = elem.type === "select-one",
22783					values = one ? null : [],
22784					max = one ? index + 1 : options.length;
22785
22786				if ( index < 0 ) {
22787					i = max;
22788
22789				} else {
22790					i = one ? index : 0;
22791				}
22792
22793				// Loop through all the selected options
22794				for ( ; i < max; i++ ) {
22795					option = options[ i ];
22796
22797					// Support: IE <=9 only
22798					// IE8-9 doesn't update selected after form reset (#2551)
22799					if ( ( option.selected || i === index ) &&
22800
22801							// Don't return options that are disabled or in a disabled optgroup
22802							!option.disabled &&
22803							( !option.parentNode.disabled ||
22804								!nodeName( option.parentNode, "optgroup" ) ) ) {
22805
22806						// Get the specific value for the option
22807						value = jQuery( option ).val();
22808
22809						// We don't need an array for one selects
22810						if ( one ) {
22811							return value;
22812						}
22813
22814						// Multi-Selects return an array
22815						values.push( value );
22816					}
22817				}
22818
22819				return values;
22820			},
22821
22822			set: function( elem, value ) {
22823				var optionSet, option,
22824					options = elem.options,
22825					values = jQuery.makeArray( value ),
22826					i = options.length;
22827
22828				while ( i-- ) {
22829					option = options[ i ];
22830
22831					/* eslint-disable no-cond-assign */
22832
22833					if ( option.selected =
22834						jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
22835					) {
22836						optionSet = true;
22837					}
22838
22839					/* eslint-enable no-cond-assign */
22840				}
22841
22842				// Force browsers to behave consistently when non-matching value is set
22843				if ( !optionSet ) {
22844					elem.selectedIndex = -1;
22845				}
22846				return values;
22847			}
22848		}
22849	}
22850} );
22851
22852// Radios and checkboxes getter/setter
22853jQuery.each( [ "radio", "checkbox" ], function() {
22854	jQuery.valHooks[ this ] = {
22855		set: function( elem, value ) {
22856			if ( Array.isArray( value ) ) {
22857				return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
22858			}
22859		}
22860	};
22861	if ( !support.checkOn ) {
22862		jQuery.valHooks[ this ].get = function( elem ) {
22863			return elem.getAttribute( "value" ) === null ? "on" : elem.value;
22864		};
22865	}
22866} );
22867
22868
22869
22870
22871// Return jQuery for attributes-only inclusion
22872
22873
22874support.focusin = "onfocusin" in window;
22875
22876
22877var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
22878	stopPropagationCallback = function( e ) {
22879		e.stopPropagation();
22880	};
22881
22882jQuery.extend( jQuery.event, {
22883
22884	trigger: function( event, data, elem, onlyHandlers ) {
22885
22886		var i, cur, tmp, bubbleType, ontype, handle, special, lastElement,
22887			eventPath = [ elem || document ],
22888			type = hasOwn.call( event, "type" ) ? event.type : event,
22889			namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];
22890
22891		cur = lastElement = tmp = elem = elem || document;
22892
22893		// Don't do events on text and comment nodes
22894		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
22895			return;
22896		}
22897
22898		// focus/blur morphs to focusin/out; ensure we're not firing them right now
22899		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
22900			return;
22901		}
22902
22903		if ( type.indexOf( "." ) > -1 ) {
22904
22905			// Namespaced trigger; create a regexp to match event type in handle()
22906			namespaces = type.split( "." );
22907			type = namespaces.shift();
22908			namespaces.sort();
22909		}
22910		ontype = type.indexOf( ":" ) < 0 && "on" + type;
22911
22912		// Caller can pass in a jQuery.Event object, Object, or just an event type string
22913		event = event[ jQuery.expando ] ?
22914			event :
22915			new jQuery.Event( type, typeof event === "object" && event );
22916
22917		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
22918		event.isTrigger = onlyHandlers ? 2 : 3;
22919		event.namespace = namespaces.join( "." );
22920		event.rnamespace = event.namespace ?
22921			new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
22922			null;
22923
22924		// Clean up the event in case it is being reused
22925		event.result = undefined;
22926		if ( !event.target ) {
22927			event.target = elem;
22928		}
22929
22930		// Clone any incoming data and prepend the event, creating the handler arg list
22931		data = data == null ?
22932			[ event ] :
22933			jQuery.makeArray( data, [ event ] );
22934
22935		// Allow special events to draw outside the lines
22936		special = jQuery.event.special[ type ] || {};
22937		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
22938			return;
22939		}
22940
22941		// Determine event propagation path in advance, per W3C events spec (#9951)
22942		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
22943		if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {
22944
22945			bubbleType = special.delegateType || type;
22946			if ( !rfocusMorph.test( bubbleType + type ) ) {
22947				cur = cur.parentNode;
22948			}
22949			for ( ; cur; cur = cur.parentNode ) {
22950				eventPath.push( cur );
22951				tmp = cur;
22952			}
22953
22954			// Only add window if we got to document (e.g., not plain obj or detached DOM)
22955			if ( tmp === ( elem.ownerDocument || document ) ) {
22956				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
22957			}
22958		}
22959
22960		// Fire handlers on the event path
22961		i = 0;
22962		while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
22963			lastElement = cur;
22964			event.type = i > 1 ?
22965				bubbleType :
22966				special.bindType || type;
22967
22968			// jQuery handler
22969			handle = (
22970					dataPriv.get( cur, "events" ) || Object.create( null )
22971				)[ event.type ] &&
22972				dataPriv.get( cur, "handle" );
22973			if ( handle ) {
22974				handle.apply( cur, data );
22975			}
22976
22977			// Native handler
22978			handle = ontype && cur[ ontype ];
22979			if ( handle && handle.apply && acceptData( cur ) ) {
22980				event.result = handle.apply( cur, data );
22981				if ( event.result === false ) {
22982					event.preventDefault();
22983				}
22984			}
22985		}
22986		event.type = type;
22987
22988		// If nobody prevented the default action, do it now
22989		if ( !onlyHandlers && !event.isDefaultPrevented() ) {
22990
22991			if ( ( !special._default ||
22992				special._default.apply( eventPath.pop(), data ) === false ) &&
22993				acceptData( elem ) ) {
22994
22995				// Call a native DOM method on the target with the same name as the event.
22996				// Don't do default actions on window, that's where global variables be (#6170)
22997				if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {
22998
22999					// Don't re-trigger an onFOO event when we call its FOO() method
23000					tmp = elem[ ontype ];
23001
23002					if ( tmp ) {
23003						elem[ ontype ] = null;
23004					}
23005
23006					// Prevent re-triggering of the same event, since we already bubbled it above
23007					jQuery.event.triggered = type;
23008
23009					if ( event.isPropagationStopped() ) {
23010						lastElement.addEventListener( type, stopPropagationCallback );
23011					}
23012
23013					elem[ type ]();
23014
23015					if ( event.isPropagationStopped() ) {
23016						lastElement.removeEventListener( type, stopPropagationCallback );
23017					}
23018
23019					jQuery.event.triggered = undefined;
23020
23021					if ( tmp ) {
23022						elem[ ontype ] = tmp;
23023					}
23024				}
23025			}
23026		}
23027
23028		return event.result;
23029	},
23030
23031	// Piggyback on a donor event to simulate a different one
23032	// Used only for `focus(in | out)` events
23033	simulate: function( type, elem, event ) {
23034		var e = jQuery.extend(
23035			new jQuery.Event(),
23036			event,
23037			{
23038				type: type,
23039				isSimulated: true
23040			}
23041		);
23042
23043		jQuery.event.trigger( e, null, elem );
23044	}
23045
23046} );
23047
23048jQuery.fn.extend( {
23049
23050	trigger: function( type, data ) {
23051		return this.each( function() {
23052			jQuery.event.trigger( type, data, this );
23053		} );
23054	},
23055	triggerHandler: function( type, data ) {
23056		var elem = this[ 0 ];
23057		if ( elem ) {
23058			return jQuery.event.trigger( type, data, elem, true );
23059		}
23060	}
23061} );
23062
23063
23064// Support: Firefox <=44
23065// Firefox doesn't have focus(in | out) events
23066// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
23067//
23068// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1
23069// focus(in | out) events fire after focus & blur events,
23070// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
23071// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857
23072if ( !support.focusin ) {
23073	jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) {
23074
23075		// Attach a single capturing handler on the document while someone wants focusin/focusout
23076		var handler = function( event ) {
23077			jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );
23078		};
23079
23080		jQuery.event.special[ fix ] = {
23081			setup: function() {
23082
23083				// Handle: regular nodes (via `this.ownerDocument`), window
23084				// (via `this.document`) & document (via `this`).
23085				var doc = this.ownerDocument || this.document || this,
23086					attaches = dataPriv.access( doc, fix );
23087
23088				if ( !attaches ) {
23089					doc.addEventListener( orig, handler, true );
23090				}
23091				dataPriv.access( doc, fix, ( attaches || 0 ) + 1 );
23092			},
23093			teardown: function() {
23094				var doc = this.ownerDocument || this.document || this,
23095					attaches = dataPriv.access( doc, fix ) - 1;
23096
23097				if ( !attaches ) {
23098					doc.removeEventListener( orig, handler, true );
23099					dataPriv.remove( doc, fix );
23100
23101				} else {
23102					dataPriv.access( doc, fix, attaches );
23103				}
23104			}
23105		};
23106	} );
23107}
23108var location = window.location;
23109
23110var nonce = { guid: Date.now() };
23111
23112var rquery = ( /\?/ );
23113
23114
23115
23116// Cross-browser xml parsing
23117jQuery.parseXML = function( data ) {
23118	var xml;
23119	if ( !data || typeof data !== "string" ) {
23120		return null;
23121	}
23122
23123	// Support: IE 9 - 11 only
23124	// IE throws on parseFromString with invalid input.
23125	try {
23126		xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
23127	} catch ( e ) {
23128		xml = undefined;
23129	}
23130
23131	if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
23132		jQuery.error( "Invalid XML: " + data );
23133	}
23134	return xml;
23135};
23136
23137
23138var
23139	rbracket = /\[\]$/,
23140	rCRLF = /\r?\n/g,
23141	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
23142	rsubmittable = /^(?:input|select|textarea|keygen)/i;
23143
23144function buildParams( prefix, obj, traditional, add ) {
23145	var name;
23146
23147	if ( Array.isArray( obj ) ) {
23148
23149		// Serialize array item.
23150		jQuery.each( obj, function( i, v ) {
23151			if ( traditional || rbracket.test( prefix ) ) {
23152
23153				// Treat each array item as a scalar.
23154				add( prefix, v );
23155
23156			} else {
23157
23158				// Item is non-scalar (array or object), encode its numeric index.
23159				buildParams(
23160					prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
23161					v,
23162					traditional,
23163					add
23164				);
23165			}
23166		} );
23167
23168	} else if ( !traditional && toType( obj ) === "object" ) {
23169
23170		// Serialize object item.
23171		for ( name in obj ) {
23172			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
23173		}
23174
23175	} else {
23176
23177		// Serialize scalar item.
23178		add( prefix, obj );
23179	}
23180}
23181
23182// Serialize an array of form elements or a set of
23183// key/values into a query string
23184jQuery.param = function( a, traditional ) {
23185	var prefix,
23186		s = [],
23187		add = function( key, valueOrFunction ) {
23188
23189			// If value is a function, invoke it and use its return value
23190			var value = isFunction( valueOrFunction ) ?
23191				valueOrFunction() :
23192				valueOrFunction;
23193
23194			s[ s.length ] = encodeURIComponent( key ) + "=" +
23195				encodeURIComponent( value == null ? "" : value );
23196		};
23197
23198	if ( a == null ) {
23199		return "";
23200	}
23201
23202	// If an array was passed in, assume that it is an array of form elements.
23203	if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
23204
23205		// Serialize the form elements
23206		jQuery.each( a, function() {
23207			add( this.name, this.value );
23208		} );
23209
23210	} else {
23211
23212		// If traditional, encode the "old" way (the way 1.3.2 or older
23213		// did it), otherwise encode params recursively.
23214		for ( prefix in a ) {
23215			buildParams( prefix, a[ prefix ], traditional, add );
23216		}
23217	}
23218
23219	// Return the resulting serialization
23220	return s.join( "&" );
23221};
23222
23223jQuery.fn.extend( {
23224	serialize: function() {
23225		return jQuery.param( this.serializeArray() );
23226	},
23227	serializeArray: function() {
23228		return this.map( function() {
23229
23230			// Can add propHook for "elements" to filter or add form elements
23231			var elements = jQuery.prop( this, "elements" );
23232			return elements ? jQuery.makeArray( elements ) : this;
23233		} )
23234		.filter( function() {
23235			var type = this.type;
23236
vendor: 4,933 bytes, lines 23237-23409
23237			// Use .is( ":disabled" ) so that fieldset[disabled] works
23238			return this.name && !jQuery( this ).is( ":disabled" ) &&
23239				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
23240				( this.checked || !rcheckableType.test( type ) );
23241		} )
23242		.map( function( _i, elem ) {
23243			var val = jQuery( this ).val();
23244
23245			if ( val == null ) {
23246				return null;
23247			}
23248
23249			if ( Array.isArray( val ) ) {
23250				return jQuery.map( val, function( val ) {
23251					return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
23252				} );
23253			}
23254
23255			return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
23256		} ).get();
23257	}
23258} );
23259
23260
23261var
23262	r20 = /%20/g,
23263	rhash = /#.*$/,
23264	rantiCache = /([?&])_=[^&]*/,
23265	rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
23266
23267	// #7653, #8125, #8152: local protocol detection
23268	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
23269	rnoContent = /^(?:GET|HEAD)$/,
23270	rprotocol = /^\/\//,
23271
23272	/* Prefilters
23273	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
23274	 * 2) These are called:
23275	 *    - BEFORE asking for a transport
23276	 *    - AFTER param serialization (s.data is a string if s.processData is true)
23277	 * 3) key is the dataType
23278	 * 4) the catchall symbol "*" can be used
23279	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
23280	 */
23281	prefilters = {},
23282
23283	/* Transports bindings
23284	 * 1) key is the dataType
23285	 * 2) the catchall symbol "*" can be used
23286	 * 3) selection will start with transport dataType and THEN go to "*" if needed
23287	 */
23288	transports = {},
23289
23290	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
23291	allTypes = "*/".concat( "*" ),
23292
23293	// Anchor tag for parsing the document origin
23294	originAnchor = document.createElement( "a" );
23295	originAnchor.href = location.href;
23296
23297// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
23298function addToPrefiltersOrTransports( structure ) {
23299
23300	// dataTypeExpression is optional and defaults to "*"
23301	return function( dataTypeExpression, func ) {
23302
23303		if ( typeof dataTypeExpression !== "string" ) {
23304			func = dataTypeExpression;
23305			dataTypeExpression = "*";
23306		}
23307
23308		var dataType,
23309			i = 0,
23310			dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || [];
23311
23312		if ( isFunction( func ) ) {
23313
23314			// For each dataType in the dataTypeExpression
23315			while ( ( dataType = dataTypes[ i++ ] ) ) {
23316
23317				// Prepend if requested
23318				if ( dataType[ 0 ] === "+" ) {
23319					dataType = dataType.slice( 1 ) || "*";
23320					( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );
23321
23322				// Otherwise append
23323				} else {
23324					( structure[ dataType ] = structure[ dataType ] || [] ).push( func );
23325				}
23326			}
23327		}
23328	};
23329}
23330
23331// Base inspection function for prefilters and transports
23332function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
23333
23334	var inspected = {},
23335		seekingTransport = ( structure === transports );
23336
23337	function inspect( dataType ) {
23338		var selected;
23339		inspected[ dataType ] = true;
23340		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
23341			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
23342			if ( typeof dataTypeOrTransport === "string" &&
23343				!seekingTransport && !inspected[ dataTypeOrTransport ] ) {
23344
23345				options.dataTypes.unshift( dataTypeOrTransport );
23346				inspect( dataTypeOrTransport );
23347				return false;
23348			} else if ( seekingTransport ) {
23349				return !( selected = dataTypeOrTransport );
23350			}
23351		} );
23352		return selected;
23353	}
23354
23355	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
23356}
23357
23358// A special extend for ajax options
23359// that takes "flat" options (not to be deep extended)
23360// Fixes #9887
23361function ajaxExtend( target, src ) {
23362	var key, deep,
23363		flatOptions = jQuery.ajaxSettings.flatOptions || {};
23364
23365	for ( key in src ) {
23366		if ( src[ key ] !== undefined ) {
23367			( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
23368		}
23369	}
23370	if ( deep ) {
23371		jQuery.extend( true, target, deep );
23372	}
23373
23374	return target;
23375}
23376
23377/* Handles responses to an ajax request:
23378 * - finds the right dataType (mediates between content-type and expected dataType)
23379 * - returns the corresponding response
23380 */
23381function ajaxHandleResponses( s, jqXHR, responses ) {
23382
23383	var ct, type, finalDataType, firstDataType,
23384		contents = s.contents,
23385		dataTypes = s.dataTypes;
23386
23387	// Remove auto dataType and get content-type in the process
23388	while ( dataTypes[ 0 ] === "*" ) {
23389		dataTypes.shift();
23390		if ( ct === undefined ) {
23391			ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" );
23392		}
23393	}
23394
23395	// Check if we're dealing with a known content-type
23396	if ( ct ) {
23397		for ( type in contents ) {
23398			if ( contents[ type ] && contents[ type ].test( ct ) ) {
23399				dataTypes.unshift( type );
23400				break;
23401			}
23402		}
23403	}
23404
23405	// Check to see if we have a response for the expected dataType
23406	if ( dataTypes[ 0 ] in responses ) {
23407		finalDataType = dataTypes[ 0 ];
23408	} else {
23409
vendor: 9,145 bytes, lines 23410-23785
23410		// Try convertible dataTypes
23411		for ( type in responses ) {
23412			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) {
23413				finalDataType = type;
23414				break;
23415			}
23416			if ( !firstDataType ) {
23417				firstDataType = type;
23418			}
23419		}
23420
23421		// Or just use first one
23422		finalDataType = finalDataType || firstDataType;
23423	}
23424
23425	// If we found a dataType
23426	// We add the dataType to the list if needed
23427	// and return the corresponding response
23428	if ( finalDataType ) {
23429		if ( finalDataType !== dataTypes[ 0 ] ) {
23430			dataTypes.unshift( finalDataType );
23431		}
23432		return responses[ finalDataType ];
23433	}
23434}
23435
23436/* Chain conversions given the request and the original response
23437 * Also sets the responseXXX fields on the jqXHR instance
23438 */
23439function ajaxConvert( s, response, jqXHR, isSuccess ) {
23440	var conv2, current, conv, tmp, prev,
23441		converters = {},
23442
23443		// Work with a copy of dataTypes in case we need to modify it for conversion
23444		dataTypes = s.dataTypes.slice();
23445
23446	// Create converters map with lowercased keys
23447	if ( dataTypes[ 1 ] ) {
23448		for ( conv in s.converters ) {
23449			converters[ conv.toLowerCase() ] = s.converters[ conv ];
23450		}
23451	}
23452
23453	current = dataTypes.shift();
23454
23455	// Convert to each sequential dataType
23456	while ( current ) {
23457
23458		if ( s.responseFields[ current ] ) {
23459			jqXHR[ s.responseFields[ current ] ] = response;
23460		}
23461
23462		// Apply the dataFilter if provided
23463		if ( !prev && isSuccess && s.dataFilter ) {
23464			response = s.dataFilter( response, s.dataType );
23465		}
23466
23467		prev = current;
23468		current = dataTypes.shift();
23469
23470		if ( current ) {
23471
23472			// There's only work to do if current dataType is non-auto
23473			if ( current === "*" ) {
23474
23475				current = prev;
23476
23477			// Convert response if prev dataType is non-auto and differs from current
23478			} else if ( prev !== "*" && prev !== current ) {
23479
23480				// Seek a direct converter
23481				conv = converters[ prev + " " + current ] || converters[ "* " + current ];
23482
23483				// If none found, seek a pair
23484				if ( !conv ) {
23485					for ( conv2 in converters ) {
23486
23487						// If conv2 outputs current
23488						tmp = conv2.split( " " );
23489						if ( tmp[ 1 ] === current ) {
23490
23491							// If prev can be converted to accepted input
23492							conv = converters[ prev + " " + tmp[ 0 ] ] ||
23493								converters[ "* " + tmp[ 0 ] ];
23494							if ( conv ) {
23495
23496								// Condense equivalence converters
23497								if ( conv === true ) {
23498									conv = converters[ conv2 ];
23499
23500								// Otherwise, insert the intermediate dataType
23501								} else if ( converters[ conv2 ] !== true ) {
23502									current = tmp[ 0 ];
23503									dataTypes.unshift( tmp[ 1 ] );
23504								}
23505								break;
23506							}
23507						}
23508					}
23509				}
23510
23511				// Apply converter (if not an equivalence)
23512				if ( conv !== true ) {
23513
23514					// Unless errors are allowed to bubble, catch and return them
23515					if ( conv && s.throws ) {
23516						response = conv( response );
23517					} else {
23518						try {
23519							response = conv( response );
23520						} catch ( e ) {
23521							return {
23522								state: "parsererror",
23523								error: conv ? e : "No conversion from " + prev + " to " + current
23524							};
23525						}
23526					}
23527				}
23528			}
23529		}
23530	}
23531
23532	return { state: "success", data: response };
23533}
23534
23535jQuery.extend( {
23536
23537	// Counter for holding the number of active queries
23538	active: 0,
23539
23540	// Last-Modified header cache for next request
23541	lastModified: {},
23542	etag: {},
23543
23544	ajaxSettings: {
23545		url: location.href,
23546		type: "GET",
23547		isLocal: rlocalProtocol.test( location.protocol ),
23548		global: true,
23549		processData: true,
23550		async: true,
23551		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
23552
23553		/*
23554		timeout: 0,
23555		data: null,
23556		dataType: null,
23557		username: null,
23558		password: null,
23559		cache: null,
23560		throws: false,
23561		traditional: false,
23562		headers: {},
23563		*/
23564
23565		accepts: {
23566			"*": allTypes,
23567			text: "text/plain",
23568			html: "text/html",
23569			xml: "application/xml, text/xml",
23570			json: "application/json, text/javascript"
23571		},
23572
23573		contents: {
23574			xml: /\bxml\b/,
23575			html: /\bhtml/,
23576			json: /\bjson\b/
23577		},
23578
23579		responseFields: {
23580			xml: "responseXML",
23581			text: "responseText",
23582			json: "responseJSON"
23583		},
23584
23585		// Data converters
23586		// Keys separate source (or catchall "*") and destination types with a single space
23587		converters: {
23588
23589			// Convert anything to text
23590			"* text": String,
23591
23592			// Text to html (true = no transformation)
23593			"text html": true,
23594
23595			// Evaluate text as a json expression
23596			"text json": JSON.parse,
23597
23598			// Parse text as xml
23599			"text xml": jQuery.parseXML
23600		},
23601
23602		// For options that shouldn't be deep extended:
23603		// you can add your own custom options here if
23604		// and when you create one that shouldn't be
23605		// deep extended (see ajaxExtend)
23606		flatOptions: {
23607			url: true,
23608			context: true
23609		}
23610	},
23611
23612	// Creates a full fledged settings object into target
23613	// with both ajaxSettings and settings fields.
23614	// If target is omitted, writes into ajaxSettings.
23615	ajaxSetup: function( target, settings ) {
23616		return settings ?
23617
23618			// Building a settings object
23619			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
23620
23621			// Extending ajaxSettings
23622			ajaxExtend( jQuery.ajaxSettings, target );
23623	},
23624
23625	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
23626	ajaxTransport: addToPrefiltersOrTransports( transports ),
23627
23628	// Main method
23629	ajax: function( url, options ) {
23630
23631		// If url is an object, simulate pre-1.5 signature
23632		if ( typeof url === "object" ) {
23633			options = url;
23634			url = undefined;
23635		}
23636
23637		// Force options to be an object
23638		options = options || {};
23639
23640		var transport,
23641
23642			// URL without anti-cache param
23643			cacheURL,
23644
23645			// Response headers
23646			responseHeadersString,
23647			responseHeaders,
23648
23649			// timeout handle
23650			timeoutTimer,
23651
23652			// Url cleanup var
23653			urlAnchor,
23654
23655			// Request state (becomes false upon send and true upon completion)
23656			completed,
23657
23658			// To know if global events are to be dispatched
23659			fireGlobals,
23660
23661			// Loop variable
23662			i,
23663
23664			// uncached part of the url
23665			uncached,
23666
23667			// Create the final options object
23668			s = jQuery.ajaxSetup( {}, options ),
23669
23670			// Callbacks context
23671			callbackContext = s.context || s,
23672
23673			// Context for global events is callbackContext if it is a DOM node or jQuery collection
23674			globalEventContext = s.context &&
23675				( callbackContext.nodeType || callbackContext.jquery ) ?
23676					jQuery( callbackContext ) :
23677					jQuery.event,
23678
23679			// Deferreds
23680			deferred = jQuery.Deferred(),
23681			completeDeferred = jQuery.Callbacks( "once memory" ),
23682
23683			// Status-dependent callbacks
23684			statusCode = s.statusCode || {},
23685
23686			// Headers (they are sent all at once)
23687			requestHeaders = {},
23688			requestHeadersNames = {},
23689
23690			// Default abort message
23691			strAbort = "canceled",
23692
23693			// Fake xhr
23694			jqXHR = {
23695				readyState: 0,
23696
23697				// Builds headers hashtable if needed
23698				getResponseHeader: function( key ) {
23699					var match;
23700					if ( completed ) {
23701						if ( !responseHeaders ) {
23702							responseHeaders = {};
23703							while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
23704								responseHeaders[ match[ 1 ].toLowerCase() + " " ] =
23705									( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] )
23706										.concat( match[ 2 ] );
23707							}
23708						}
23709						match = responseHeaders[ key.toLowerCase() + " " ];
23710					}
23711					return match == null ? null : match.join( ", " );
23712				},
23713
23714				// Raw string
23715				getAllResponseHeaders: function() {
23716					return completed ? responseHeadersString : null;
23717				},
23718
23719				// Caches the header
23720				setRequestHeader: function( name, value ) {
23721					if ( completed == null ) {
23722						name = requestHeadersNames[ name.toLowerCase() ] =
23723							requestHeadersNames[ name.toLowerCase() ] || name;
23724						requestHeaders[ name ] = value;
23725					}
23726					return this;
23727				},
23728
23729				// Overrides response content-type header
23730				overrideMimeType: function( type ) {
23731					if ( completed == null ) {
23732						s.mimeType = type;
23733					}
23734					return this;
23735				},
23736
23737				// Status-dependent callbacks
23738				statusCode: function( map ) {
23739					var code;
23740					if ( map ) {
23741						if ( completed ) {
23742
23743							// Execute the appropriate callbacks
23744							jqXHR.always( map[ jqXHR.status ] );
23745						} else {
23746
23747							// Lazy-add the new callbacks in a way that preserves old ones
23748							for ( code in map ) {
23749								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
23750							}
23751						}
23752					}
23753					return this;
23754				},
23755
23756				// Cancel the request
23757				abort: function( statusText ) {
23758					var finalText = statusText || strAbort;
23759					if ( transport ) {
23760						transport.abort( finalText );
23761					}
23762					done( 0, finalText );
23763					return this;
23764				}
23765			};
23766
23767		// Attach deferreds
23768		deferred.promise( jqXHR );
23769
23770		// Add protocol if not provided (prefilters might expect it)
23771		// Handle falsy url in the settings object (#10093: consistency with old signature)
23772		// We also use the url parameter if available
23773		s.url = ( ( url || s.url || location.href ) + "" )
23774			.replace( rprotocol, location.protocol + "//" );
23775
23776		// Alias method option to type as per ticket #12004
23777		s.type = options.method || options.type || s.method || s.type;
23778
23779		// Extract dataTypes list
23780		s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ];
23781
23782		// A cross-domain request is in order when the origin doesn't match the current origin.
23783		if ( s.crossDomain == null ) {
23784			urlAnchor = document.createElement( "a" );
23785
vendor: 15,133 bytes, lines 23786-24361
23786			// Support: IE <=8 - 11, Edge 12 - 15
23787			// IE throws exception on accessing the href property if url is malformed,
23788			// e.g. http://example.com:80x/
23789			try {
23790				urlAnchor.href = s.url;
23791
23792				// Support: IE <=8 - 11 only
23793				// Anchor's host property isn't correctly set when s.url is relative
23794				urlAnchor.href = urlAnchor.href;
23795				s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !==
23796					urlAnchor.protocol + "//" + urlAnchor.host;
23797			} catch ( e ) {
23798
23799				// If there is an error parsing the URL, assume it is crossDomain,
23800				// it can be rejected by the transport if it is invalid
23801				s.crossDomain = true;
23802			}
23803		}
23804
23805		// Convert data if not already a string
23806		if ( s.data && s.processData && typeof s.data !== "string" ) {
23807			s.data = jQuery.param( s.data, s.traditional );
23808		}
23809
23810		// Apply prefilters
23811		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
23812
23813		// If request was aborted inside a prefilter, stop there
23814		if ( completed ) {
23815			return jqXHR;
23816		}
23817
23818		// We can fire global events as of now if asked to
23819		// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
23820		fireGlobals = jQuery.event && s.global;
23821
23822		// Watch for a new set of requests
23823		if ( fireGlobals && jQuery.active++ === 0 ) {
23824			jQuery.event.trigger( "ajaxStart" );
23825		}
23826
23827		// Uppercase the type
23828		s.type = s.type.toUpperCase();
23829
23830		// Determine if request has content
23831		s.hasContent = !rnoContent.test( s.type );
23832
23833		// Save the URL in case we're toying with the If-Modified-Since
23834		// and/or If-None-Match header later on
23835		// Remove hash to simplify url manipulation
23836		cacheURL = s.url.replace( rhash, "" );
23837
23838		// More options handling for requests with no content
23839		if ( !s.hasContent ) {
23840
23841			// Remember the hash so we can put it back
23842			uncached = s.url.slice( cacheURL.length );
23843
23844			// If data is available and should be processed, append data to url
23845			if ( s.data && ( s.processData || typeof s.data === "string" ) ) {
23846				cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data;
23847
23848				// #9682: remove data so that it's not used in an eventual retry
23849				delete s.data;
23850			}
23851
23852			// Add or update anti-cache param if needed
23853			if ( s.cache === false ) {
23854				cacheURL = cacheURL.replace( rantiCache, "$1" );
23855				uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) +
23856					uncached;
23857			}
23858
23859			// Put hash and anti-cache on the URL that will be requested (gh-1732)
23860			s.url = cacheURL + uncached;
23861
23862		// Change '%20' to '+' if this is encoded form body content (gh-2658)
23863		} else if ( s.data && s.processData &&
23864			( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) {
23865			s.data = s.data.replace( r20, "+" );
23866		}
23867
23868		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
23869		if ( s.ifModified ) {
23870			if ( jQuery.lastModified[ cacheURL ] ) {
23871				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
23872			}
23873			if ( jQuery.etag[ cacheURL ] ) {
23874				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
23875			}
23876		}
23877
23878		// Set the correct header, if data is being sent
23879		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
23880			jqXHR.setRequestHeader( "Content-Type", s.contentType );
23881		}
23882
23883		// Set the Accepts header for the server, depending on the dataType
23884		jqXHR.setRequestHeader(
23885			"Accept",
23886			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
23887				s.accepts[ s.dataTypes[ 0 ] ] +
23888					( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
23889				s.accepts[ "*" ]
23890		);
23891
23892		// Check for headers option
23893		for ( i in s.headers ) {
23894			jqXHR.setRequestHeader( i, s.headers[ i ] );
23895		}
23896
23897		// Allow custom headers/mimetypes and early abort
23898		if ( s.beforeSend &&
23899			( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) {
23900
23901			// Abort if not done already and return
23902			return jqXHR.abort();
23903		}
23904
23905		// Aborting is no longer a cancellation
23906		strAbort = "abort";
23907
23908		// Install callbacks on deferreds
23909		completeDeferred.add( s.complete );
23910		jqXHR.done( s.success );
23911		jqXHR.fail( s.error );
23912
23913		// Get transport
23914		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
23915
23916		// If no transport, we auto-abort
23917		if ( !transport ) {
23918			done( -1, "No Transport" );
23919		} else {
23920			jqXHR.readyState = 1;
23921
23922			// Send global event
23923			if ( fireGlobals ) {
23924				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
23925			}
23926
23927			// If request was aborted inside ajaxSend, stop there
23928			if ( completed ) {
23929				return jqXHR;
23930			}
23931
23932			// Timeout
23933			if ( s.async && s.timeout > 0 ) {
23934				timeoutTimer = window.setTimeout( function() {
23935					jqXHR.abort( "timeout" );
23936				}, s.timeout );
23937			}
23938
23939			try {
23940				completed = false;
23941				transport.send( requestHeaders, done );
23942			} catch ( e ) {
23943
23944				// Rethrow post-completion exceptions
23945				if ( completed ) {
23946					throw e;
23947				}
23948
23949				// Propagate others as results
23950				done( -1, e );
23951			}
23952		}
23953
23954		// Callback for when everything is done
23955		function done( status, nativeStatusText, responses, headers ) {
23956			var isSuccess, success, error, response, modified,
23957				statusText = nativeStatusText;
23958
23959			// Ignore repeat invocations
23960			if ( completed ) {
23961				return;
23962			}
23963
23964			completed = true;
23965
23966			// Clear timeout if it exists
23967			if ( timeoutTimer ) {
23968				window.clearTimeout( timeoutTimer );
23969			}
23970
23971			// Dereference transport for early garbage collection
23972			// (no matter how long the jqXHR object will be used)
23973			transport = undefined;
23974
23975			// Cache response headers
23976			responseHeadersString = headers || "";
23977
23978			// Set readyState
23979			jqXHR.readyState = status > 0 ? 4 : 0;
23980
23981			// Determine if successful
23982			isSuccess = status >= 200 && status < 300 || status === 304;
23983
23984			// Get response data
23985			if ( responses ) {
23986				response = ajaxHandleResponses( s, jqXHR, responses );
23987			}
23988
23989			// Use a noop converter for missing script
23990			if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) {
23991				s.converters[ "text script" ] = function() {};
23992			}
23993
23994			// Convert no matter what (that way responseXXX fields are always set)
23995			response = ajaxConvert( s, response, jqXHR, isSuccess );
23996
23997			// If successful, handle type chaining
23998			if ( isSuccess ) {
23999
24000				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
24001				if ( s.ifModified ) {
24002					modified = jqXHR.getResponseHeader( "Last-Modified" );
24003					if ( modified ) {
24004						jQuery.lastModified[ cacheURL ] = modified;
24005					}
24006					modified = jqXHR.getResponseHeader( "etag" );
24007					if ( modified ) {
24008						jQuery.etag[ cacheURL ] = modified;
24009					}
24010				}
24011
24012				// if no content
24013				if ( status === 204 || s.type === "HEAD" ) {
24014					statusText = "nocontent";
24015
24016				// if not modified
24017				} else if ( status === 304 ) {
24018					statusText = "notmodified";
24019
24020				// If we have data, let's convert it
24021				} else {
24022					statusText = response.state;
24023					success = response.data;
24024					error = response.error;
24025					isSuccess = !error;
24026				}
24027			} else {
24028
24029				// Extract error from statusText and normalize for non-aborts
24030				error = statusText;
24031				if ( status || !statusText ) {
24032					statusText = "error";
24033					if ( status < 0 ) {
24034						status = 0;
24035					}
24036				}
24037			}
24038
24039			// Set data for the fake xhr object
24040			jqXHR.status = status;
24041			jqXHR.statusText = ( nativeStatusText || statusText ) + "";
24042
24043			// Success/Error
24044			if ( isSuccess ) {
24045				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
24046			} else {
24047				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
24048			}
24049
24050			// Status-dependent callbacks
24051			jqXHR.statusCode( statusCode );
24052			statusCode = undefined;
24053
24054			if ( fireGlobals ) {
24055				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
24056					[ jqXHR, s, isSuccess ? success : error ] );
24057			}
24058
24059			// Complete
24060			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
24061
24062			if ( fireGlobals ) {
24063				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
24064
24065				// Handle the global AJAX counter
24066				if ( !( --jQuery.active ) ) {
24067					jQuery.event.trigger( "ajaxStop" );
24068				}
24069			}
24070		}
24071
24072		return jqXHR;
24073	},
24074
24075	getJSON: function( url, data, callback ) {
24076		return jQuery.get( url, data, callback, "json" );
24077	},
24078
24079	getScript: function( url, callback ) {
24080		return jQuery.get( url, undefined, callback, "script" );
24081	}
24082} );
24083
24084jQuery.each( [ "get", "post" ], function( _i, method ) {
24085	jQuery[ method ] = function( url, data, callback, type ) {
24086
24087		// Shift arguments if data argument was omitted
24088		if ( isFunction( data ) ) {
24089			type = type || callback;
24090			callback = data;
24091			data = undefined;
24092		}
24093
24094		// The url can be an options object (which then must have .url)
24095		return jQuery.ajax( jQuery.extend( {
24096			url: url,
24097			type: method,
24098			dataType: type,
24099			data: data,
24100			success: callback
24101		}, jQuery.isPlainObject( url ) && url ) );
24102	};
24103} );
24104
24105jQuery.ajaxPrefilter( function( s ) {
24106	var i;
24107	for ( i in s.headers ) {
24108		if ( i.toLowerCase() === "content-type" ) {
24109			s.contentType = s.headers[ i ] || "";
24110		}
24111	}
24112} );
24113
24114
24115jQuery._evalUrl = function( url, options, doc ) {
24116	return jQuery.ajax( {
24117		url: url,
24118
24119		// Make this explicit, since user can override this through ajaxSetup (#11264)
24120		type: "GET",
24121		dataType: "script",
24122		cache: true,
24123		async: false,
24124		global: false,
24125
24126		// Only evaluate the response if it is successful (gh-4126)
24127		// dataFilter is not invoked for failure responses, so using it instead
24128		// of the default converter is kludgy but it works.
24129		converters: {
24130			"text script": function() {}
24131		},
24132		dataFilter: function( response ) {
24133			jQuery.globalEval( response, options, doc );
24134		}
24135	} );
24136};
24137
24138
24139jQuery.fn.extend( {
24140	wrapAll: function( html ) {
24141		var wrap;
24142
24143		if ( this[ 0 ] ) {
24144			if ( isFunction( html ) ) {
24145				html = html.call( this[ 0 ] );
24146			}
24147
24148			// The elements to wrap the target around
24149			wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
24150
24151			if ( this[ 0 ].parentNode ) {
24152				wrap.insertBefore( this[ 0 ] );
24153			}
24154
24155			wrap.map( function() {
24156				var elem = this;
24157
24158				while ( elem.firstElementChild ) {
24159					elem = elem.firstElementChild;
24160				}
24161
24162				return elem;
24163			} ).append( this );
24164		}
24165
24166		return this;
24167	},
24168
24169	wrapInner: function( html ) {
24170		if ( isFunction( html ) ) {
24171			return this.each( function( i ) {
24172				jQuery( this ).wrapInner( html.call( this, i ) );
24173			} );
24174		}
24175
24176		return this.each( function() {
24177			var self = jQuery( this ),
24178				contents = self.contents();
24179
24180			if ( contents.length ) {
24181				contents.wrapAll( html );
24182
24183			} else {
24184				self.append( html );
24185			}
24186		} );
24187	},
24188
24189	wrap: function( html ) {
24190		var htmlIsFunction = isFunction( html );
24191
24192		return this.each( function( i ) {
24193			jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html );
24194		} );
24195	},
24196
24197	unwrap: function( selector ) {
24198		this.parent( selector ).not( "body" ).each( function() {
24199			jQuery( this ).replaceWith( this.childNodes );
24200		} );
24201		return this;
24202	}
24203} );
24204
24205
24206jQuery.expr.pseudos.hidden = function( elem ) {
24207	return !jQuery.expr.pseudos.visible( elem );
24208};
24209jQuery.expr.pseudos.visible = function( elem ) {
24210	return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
24211};
24212
24213
24214
24215
24216jQuery.ajaxSettings.xhr = function() {
24217	try {
24218		return new window.XMLHttpRequest();
24219	} catch ( e ) {}
24220};
24221
24222var xhrSuccessStatus = {
24223
24224		// File protocol always yields status code 0, assume 200
24225		0: 200,
24226
24227		// Support: IE <=9 only
24228		// #1450: sometimes IE returns 1223 when it should be 204
24229		1223: 204
24230	},
24231	xhrSupported = jQuery.ajaxSettings.xhr();
24232
24233support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
24234support.ajax = xhrSupported = !!xhrSupported;
24235
24236jQuery.ajaxTransport( function( options ) {
24237	var callback, errorCallback;
24238
24239	// Cross domain only allowed if supported through XMLHttpRequest
24240	if ( support.cors || xhrSupported && !options.crossDomain ) {
24241		return {
24242			send: function( headers, complete ) {
24243				var i,
24244					xhr = options.xhr();
24245
24246				xhr.open(
24247					options.type,
24248					options.url,
24249					options.async,
24250					options.username,
24251					options.password
24252				);
24253
24254				// Apply custom fields if provided
24255				if ( options.xhrFields ) {
24256					for ( i in options.xhrFields ) {
24257						xhr[ i ] = options.xhrFields[ i ];
24258					}
24259				}
24260
24261				// Override mime type if needed
24262				if ( options.mimeType && xhr.overrideMimeType ) {
24263					xhr.overrideMimeType( options.mimeType );
24264				}
24265
24266				// X-Requested-With header
24267				// For cross-domain requests, seeing as conditions for a preflight are
24268				// akin to a jigsaw puzzle, we simply never set it to be sure.
24269				// (it can always be set on a per-request basis or even using ajaxSetup)
24270				// For same-domain requests, won't change header if already provided.
24271				if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
24272					headers[ "X-Requested-With" ] = "XMLHttpRequest";
24273				}
24274
24275				// Set headers
24276				for ( i in headers ) {
24277					xhr.setRequestHeader( i, headers[ i ] );
24278				}
24279
24280				// Callback
24281				callback = function( type ) {
24282					return function() {
24283						if ( callback ) {
24284							callback = errorCallback = xhr.onload =
24285								xhr.onerror = xhr.onabort = xhr.ontimeout =
24286									xhr.onreadystatechange = null;
24287
24288							if ( type === "abort" ) {
24289								xhr.abort();
24290							} else if ( type === "error" ) {
24291
24292								// Support: IE <=9 only
24293								// On a manual native abort, IE9 throws
24294								// errors on any property access that is not readyState
24295								if ( typeof xhr.status !== "number" ) {
24296									complete( 0, "error" );
24297								} else {
24298									complete(
24299
24300										// File: protocol always yields status 0; see #8605, #14207
24301										xhr.status,
24302										xhr.statusText
24303									);
24304								}
24305							} else {
24306								complete(
24307									xhrSuccessStatus[ xhr.status ] || xhr.status,
24308									xhr.statusText,
24309
24310									// Support: IE <=9 only
24311									// IE9 has no XHR2 but throws on binary (trac-11426)
24312									// For XHR2 non-text, let the caller handle it (gh-2498)
24313									( xhr.responseType || "text" ) !== "text"  ||
24314									typeof xhr.responseText !== "string" ?
24315										{ binary: xhr.response } :
24316										{ text: xhr.responseText },
24317									xhr.getAllResponseHeaders()
24318								);
24319							}
24320						}
24321					};
24322				};
24323
24324				// Listen to events
24325				xhr.onload = callback();
24326				errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" );
24327
24328				// Support: IE 9 only
24329				// Use onreadystatechange to replace onabort
24330				// to handle uncaught aborts
24331				if ( xhr.onabort !== undefined ) {
24332					xhr.onabort = errorCallback;
24333				} else {
24334					xhr.onreadystatechange = function() {
24335
24336						// Check readyState before timeout as it changes
24337						if ( xhr.readyState === 4 ) {
24338
24339							// Allow onerror to be called first,
24340							// but that will not handle a native abort
24341							// Also, save errorCallback to a variable
24342							// as xhr.onerror cannot be accessed
24343							window.setTimeout( function() {
24344								if ( callback ) {
24345									errorCallback();
24346								}
24347							} );
24348						}
24349					};
24350				}
24351
24352				// Create the abort callback
24353				callback = callback( "abort" );
24354
24355				try {
24356
24357					// Do send the request (this may raise an exception)
24358					xhr.send( options.hasContent && options.data || null );
24359				} catch ( e ) {
24360
24361					// #
vendor: 9,449 bytes, lines 24361-24728
2436114683: Only rethrow if this hasn't been notified as an error yet
24362					if ( callback ) {
24363						throw e;
24364					}
24365				}
24366			},
24367
24368			abort: function() {
24369				if ( callback ) {
24370					callback();
24371				}
24372			}
24373		};
24374	}
24375} );
24376
24377
24378
24379
24380// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432)
24381jQuery.ajaxPrefilter( function( s ) {
24382	if ( s.crossDomain ) {
24383		s.contents.script = false;
24384	}
24385} );
24386
24387// Install script dataType
24388jQuery.ajaxSetup( {
24389	accepts: {
24390		script: "text/javascript, application/javascript, " +
24391			"application/ecmascript, application/x-ecmascript"
24392	},
24393	contents: {
24394		script: /\b(?:java|ecma)script\b/
24395	},
24396	converters: {
24397		"text script": function( text ) {
24398			jQuery.globalEval( text );
24399			return text;
24400		}
24401	}
24402} );
24403
24404// Handle cache's special case and crossDomain
24405jQuery.ajaxPrefilter( "script", function( s ) {
24406	if ( s.cache === undefined ) {
24407		s.cache = false;
24408	}
24409	if ( s.crossDomain ) {
24410		s.type = "GET";
24411	}
24412} );
24413
24414// Bind script tag hack transport
24415jQuery.ajaxTransport( "script", function( s ) {
24416
24417	// This transport only deals with cross domain or forced-by-attrs requests
24418	if ( s.crossDomain || s.scriptAttrs ) {
24419		var script, callback;
24420		return {
24421			send: function( _, complete ) {
24422				script = jQuery( "<script>" )
24423					.attr( s.scriptAttrs || {} )
24424					.prop( { charset: s.scriptCharset, src: s.url } )
24425					.on( "load error", callback = function( evt ) {
24426						script.remove();
24427						callback = null;
24428						if ( evt ) {
24429							complete( evt.type === "error" ? 404 : 200, evt.type );
24430						}
24431					} );
24432
24433				// Use native DOM manipulation to avoid our domManip AJAX trickery
24434				document.head.appendChild( script[ 0 ] );
24435			},
24436			abort: function() {
24437				if ( callback ) {
24438					callback();
24439				}
24440			}
24441		};
24442	}
24443} );
24444
24445
24446
24447
24448var oldCallbacks = [],
24449	rjsonp = /(=)\?(?=&|$)|\?\?/;
24450
24451// Default jsonp settings
24452jQuery.ajaxSetup( {
24453	jsonp: "callback",
24454	jsonpCallback: function() {
24455		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce.guid++ ) );
24456		this[ callback ] = true;
24457		return callback;
24458	}
24459} );
24460
24461// Detect, normalize options and install callbacks for jsonp requests
24462jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
24463
24464	var callbackName, overwritten, responseContainer,
24465		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
24466			"url" :
24467			typeof s.data === "string" &&
24468				( s.contentType || "" )
24469					.indexOf( "application/x-www-form-urlencoded" ) === 0 &&
24470				rjsonp.test( s.data ) && "data"
24471		);
24472
24473	// Handle iff the expected data type is "jsonp" or we have a parameter to set
24474	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
24475
24476		// Get callback name, remembering preexisting value associated with it
24477		callbackName = s.jsonpCallback = isFunction( s.jsonpCallback ) ?
24478			s.jsonpCallback() :
24479			s.jsonpCallback;
24480
24481		// Insert callback into url or form data
24482		if ( jsonProp ) {
24483			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
24484		} else if ( s.jsonp !== false ) {
24485			s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
24486		}
24487
24488		// Use data converter to retrieve json after script execution
24489		s.converters[ "script json" ] = function() {
24490			if ( !responseContainer ) {
24491				jQuery.error( callbackName + " was not called" );
24492			}
24493			return responseContainer[ 0 ];
24494		};
24495
24496		// Force json dataType
24497		s.dataTypes[ 0 ] = "json";
24498
24499		// Install callback
24500		overwritten = window[ callbackName ];
24501		window[ callbackName ] = function() {
24502			responseContainer = arguments;
24503		};
24504
24505		// Clean-up function (fires after converters)
24506		jqXHR.always( function() {
24507
24508			// If previous value didn't exist - remove it
24509			if ( overwritten === undefined ) {
24510				jQuery( window ).removeProp( callbackName );
24511
24512			// Otherwise restore preexisting value
24513			} else {
24514				window[ callbackName ] = overwritten;
24515			}
24516
24517			// Save back as free
24518			if ( s[ callbackName ] ) {
24519
24520				// Make sure that re-using the options doesn't screw things around
24521				s.jsonpCallback = originalSettings.jsonpCallback;
24522
24523				// Save the callback name for future use
24524				oldCallbacks.push( callbackName );
24525			}
24526
24527			// Call if it was a function and we have a response
24528			if ( responseContainer && isFunction( overwritten ) ) {
24529				overwritten( responseContainer[ 0 ] );
24530			}
24531
24532			responseContainer = overwritten = undefined;
24533		} );
24534
24535		// Delegate to script
24536		return "script";
24537	}
24538} );
24539
24540
24541
24542
24543// Support: Safari 8 only
24544// In Safari 8 documents created via document.implementation.createHTMLDocument
24545// collapse sibling forms: the second one becomes a child of the first one.
24546// Because of that, this security measure has to be disabled in Safari 8.
24547// https://bugs.webkit.org/show_bug.cgi?id=137337
24548support.createHTMLDocument = ( function() {
24549	var body = document.implementation.createHTMLDocument( "" ).body;
24550	body.innerHTML = "<form></form><form></form>";
24551	return body.childNodes.length === 2;
24552} )();
24553
24554
24555// Argument "data" should be string of html
24556// context (optional): If specified, the fragment will be created in this context,
24557// defaults to document
24558// keepScripts (optional): If true, will include scripts passed in the html string
24559jQuery.parseHTML = function( data, context, keepScripts ) {
24560	if ( typeof data !== "string" ) {
24561		return [];
24562	}
24563	if ( typeof context === "boolean" ) {
24564		keepScripts = context;
24565		context = false;
24566	}
24567
24568	var base, parsed, scripts;
24569
24570	if ( !context ) {
24571
24572		// Stop scripts or inline event handlers from being executed immediately
24573		// by using document.implementation
24574		if ( support.createHTMLDocument ) {
24575			context = document.implementation.createHTMLDocument( "" );
24576
24577			// Set the base href for the created document
24578			// so any parsed elements with URLs
24579			// are based on the document's URL (gh-2965)
24580			base = context.createElement( "base" );
24581			base.href = document.location.href;
24582			context.head.appendChild( base );
24583		} else {
24584			context = document;
24585		}
24586	}
24587
24588	parsed = rsingleTag.exec( data );
24589	scripts = !keepScripts && [];
24590
24591	// Single tag
24592	if ( parsed ) {
24593		return [ context.createElement( parsed[ 1 ] ) ];
24594	}
24595
24596	parsed = buildFragment( [ data ], context, scripts );
24597
24598	if ( scripts && scripts.length ) {
24599		jQuery( scripts ).remove();
24600	}
24601
24602	return jQuery.merge( [], parsed.childNodes );
24603};
24604
24605
24606/**
24607 * Load a url into a page
24608 */
24609jQuery.fn.load = function( url, params, callback ) {
24610	var selector, type, response,
24611		self = this,
24612		off = url.indexOf( " " );
24613
24614	if ( off > -1 ) {
24615		selector = stripAndCollapse( url.slice( off ) );
24616		url = url.slice( 0, off );
24617	}
24618
24619	// If it's a function
24620	if ( isFunction( params ) ) {
24621
24622		// We assume that it's the callback
24623		callback = params;
24624		params = undefined;
24625
24626	// Otherwise, build a param string
24627	} else if ( params && typeof params === "object" ) {
24628		type = "POST";
24629	}
24630
24631	// If we have elements to modify, make the request
24632	if ( self.length > 0 ) {
24633		jQuery.ajax( {
24634			url: url,
24635
24636			// If "type" variable is undefined, then "GET" method will be used.
24637			// Make value of this field explicit since
24638			// user can override it through ajaxSetup method
24639			type: type || "GET",
24640			dataType: "html",
24641			data: params
24642		} ).done( function( responseText ) {
24643
24644			// Save response for use in complete callback
24645			response = arguments;
24646
24647			self.html( selector ?
24648
24649				// If a selector was specified, locate the right elements in a dummy div
24650				// Exclude scripts to avoid IE 'Permission Denied' errors
24651				jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :
24652
24653				// Otherwise use the full result
24654				responseText );
24655
24656		// If the request succeeds, this function gets "data", "status", "jqXHR"
24657		// but they are ignored because response was set above.
24658		// If it fails, this function gets "jqXHR", "status", "error"
24659		} ).always( callback && function( jqXHR, status ) {
24660			self.each( function() {
24661				callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );
24662			} );
24663		} );
24664	}
24665
24666	return this;
24667};
24668
24669
24670
24671
24672jQuery.expr.pseudos.animated = function( elem ) {
24673	return jQuery.grep( jQuery.timers, function( fn ) {
24674		return elem === fn.elem;
24675	} ).length;
24676};
24677
24678
24679
24680
24681jQuery.offset = {
24682	setOffset: function( elem, options, i ) {
24683		var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
24684			position = jQuery.css( elem, "position" ),
24685			curElem = jQuery( elem ),
24686			props = {};
24687
24688		// Set position first, in-case top/left are set even on static elem
24689		if ( position === "static" ) {
24690			elem.style.position = "relative";
24691		}
24692
24693		curOffset = curElem.offset();
24694		curCSSTop = jQuery.css( elem, "top" );
24695		curCSSLeft = jQuery.css( elem, "left" );
24696		calculatePosition = ( position === "absolute" || position === "fixed" ) &&
24697			( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;
24698
24699		// Need to be able to calculate position if either
24700		// top or left is auto and position is either absolute or fixed
24701		if ( calculatePosition ) {
24702			curPosition = curElem.position();
24703			curTop = curPosition.top;
24704			curLeft = curPosition.left;
24705
24706		} else {
24707			curTop = parseFloat( curCSSTop ) || 0;
24708			curLeft = parseFloat( curCSSLeft ) || 0;
24709		}
24710
24711		if ( isFunction( options ) ) {
24712
24713			// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
24714			options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
24715		}
24716
24717		if ( options.top != null ) {
24718			props.top = ( options.top - curOffset.top ) + curTop;
24719		}
24720		if ( options.left != null ) {
24721			props.left = ( options.left - curOffset.left ) + curLeft;
24722		}
24723
24724		if ( "using" in options ) {
24725			options.using.call( elem, props );
24726
24727		} else {
24728			
vendor: 9,879 bytes, lines 24728-25072
24728if ( typeof props.top === "number" ) {
24729				props.top += "px";
24730			}
24731			if ( typeof props.left === "number" ) {
24732				props.left += "px";
24733			}
24734			curElem.css( props );
24735		}
24736	}
24737};
24738
24739jQuery.fn.extend( {
24740
24741	// offset() relates an element's border box to the document origin
24742	offset: function( options ) {
24743
24744		// Preserve chaining for setter
24745		if ( arguments.length ) {
24746			return options === undefined ?
24747				this :
24748				this.each( function( i ) {
24749					jQuery.offset.setOffset( this, options, i );
24750				} );
24751		}
24752
24753		var rect, win,
24754			elem = this[ 0 ];
24755
24756		if ( !elem ) {
24757			return;
24758		}
24759
24760		// Return zeros for disconnected and hidden (display: none) elements (gh-2310)
24761		// Support: IE <=11 only
24762		// Running getBoundingClientRect on a
24763		// disconnected node in IE throws an error
24764		if ( !elem.getClientRects().length ) {
24765			return { top: 0, left: 0 };
24766		}
24767
24768		// Get document-relative position by adding viewport scroll to viewport-relative gBCR
24769		rect = elem.getBoundingClientRect();
24770		win = elem.ownerDocument.defaultView;
24771		return {
24772			top: rect.top + win.pageYOffset,
24773			left: rect.left + win.pageXOffset
24774		};
24775	},
24776
24777	// position() relates an element's margin box to its offset parent's padding box
24778	// This corresponds to the behavior of CSS absolute positioning
24779	position: function() {
24780		if ( !this[ 0 ] ) {
24781			return;
24782		}
24783
24784		var offsetParent, offset, doc,
24785			elem = this[ 0 ],
24786			parentOffset = { top: 0, left: 0 };
24787
24788		// position:fixed elements are offset from the viewport, which itself always has zero offset
24789		if ( jQuery.css( elem, "position" ) === "fixed" ) {
24790
24791			// Assume position:fixed implies availability of getBoundingClientRect
24792			offset = elem.getBoundingClientRect();
24793
24794		} else {
24795			offset = this.offset();
24796
24797			// Account for the *real* offset parent, which can be the document or its root element
24798			// when a statically positioned element is identified
24799			doc = elem.ownerDocument;
24800			offsetParent = elem.offsetParent || doc.documentElement;
24801			while ( offsetParent &&
24802				( offsetParent === doc.body || offsetParent === doc.documentElement ) &&
24803				jQuery.css( offsetParent, "position" ) === "static" ) {
24804
24805				offsetParent = offsetParent.parentNode;
24806			}
24807			if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) {
24808
24809				// Incorporate borders into its offset, since they are outside its content origin
24810				parentOffset = jQuery( offsetParent ).offset();
24811				parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true );
24812				parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true );
24813			}
24814		}
24815
24816		// Subtract parent offsets and element margins
24817		return {
24818			top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
24819			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
24820		};
24821	},
24822
24823	// This method will return documentElement in the following cases:
24824	// 1) For the element inside the iframe without offsetParent, this method will return
24825	//    documentElement of the parent window
24826	// 2) For the hidden or detached element
24827	// 3) For body or html element, i.e. in case of the html node - it will return itself
24828	//
24829	// but those exceptions were never presented as a real life use-cases
24830	// and might be considered as more preferable results.
24831	//
24832	// This logic, however, is not guaranteed and can change at any point in the future
24833	offsetParent: function() {
24834		return this.map( function() {
24835			var offsetParent = this.offsetParent;
24836
24837			while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
24838				offsetParent = offsetParent.offsetParent;
24839			}
24840
24841			return offsetParent || documentElement;
24842		} );
24843	}
24844} );
24845
24846// Create scrollLeft and scrollTop methods
24847jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
24848	var top = "pageYOffset" === prop;
24849
24850	jQuery.fn[ method ] = function( val ) {
24851		return access( this, function( elem, method, val ) {
24852
24853			// Coalesce documents and windows
24854			var win;
24855			if ( isWindow( elem ) ) {
24856				win = elem;
24857			} else if ( elem.nodeType === 9 ) {
24858				win = elem.defaultView;
24859			}
24860
24861			if ( val === undefined ) {
24862				return win ? win[ prop ] : elem[ method ];
24863			}
24864
24865			if ( win ) {
24866				win.scrollTo(
24867					!top ? val : win.pageXOffset,
24868					top ? val : win.pageYOffset
24869				);
24870
24871			} else {
24872				elem[ method ] = val;
24873			}
24874		}, method, val, arguments.length );
24875	};
24876} );
24877
24878// Support: Safari <=7 - 9.1, Chrome <=37 - 49
24879// Add the top/left cssHooks using jQuery.fn.position
24880// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
24881// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347
24882// getComputedStyle returns percent when specified for top/left/bottom/right;
24883// rather than make the css module depend on the offset module, just check for it here
24884jQuery.each( [ "top", "left" ], function( _i, prop ) {
24885	jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
24886		function( elem, computed ) {
24887			if ( computed ) {
24888				computed = curCSS( elem, prop );
24889
24890				// If curCSS returns percentage, fallback to offset
24891				return rnumnonpx.test( computed ) ?
24892					jQuery( elem ).position()[ prop ] + "px" :
24893					computed;
24894			}
24895		}
24896	);
24897} );
24898
24899
24900// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
24901jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
24902	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
24903		function( defaultExtra, funcName ) {
24904
24905		// Margin is only for outerHeight, outerWidth
24906		jQuery.fn[ funcName ] = function( margin, value ) {
24907			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
24908				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
24909
24910			return access( this, function( elem, type, value ) {
24911				var doc;
24912
24913				if ( isWindow( elem ) ) {
24914
24915					// $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
24916					return funcName.indexOf( "outer" ) === 0 ?
24917						elem[ "inner" + name ] :
24918						elem.document.documentElement[ "client" + name ];
24919				}
24920
24921				// Get document width or height
24922				if ( elem.nodeType === 9 ) {
24923					doc = elem.documentElement;
24924
24925					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
24926					// whichever is greatest
24927					return Math.max(
24928						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
24929						elem.body[ "offset" + name ], doc[ "offset" + name ],
24930						doc[ "client" + name ]
24931					);
24932				}
24933
24934				return value === undefined ?
24935
24936					// Get width or height on the element, requesting but not forcing parseFloat
24937					jQuery.css( elem, type, extra ) :
24938
24939					// Set width or height on the element
24940					jQuery.style( elem, type, value, extra );
24941			}, type, chainable ? margin : undefined, chainable );
24942		};
24943	} );
24944} );
24945
24946
24947jQuery.each( [
24948	"ajaxStart",
24949	"ajaxStop",
24950	"ajaxComplete",
24951	"ajaxError",
24952	"ajaxSuccess",
24953	"ajaxSend"
24954], function( _i, type ) {
24955	jQuery.fn[ type ] = function( fn ) {
24956		return this.on( type, fn );
24957	};
24958} );
24959
24960
24961
24962
24963jQuery.fn.extend( {
24964
24965	bind: function( types, data, fn ) {
24966		return this.on( types, null, data, fn );
24967	},
24968	unbind: function( types, fn ) {
24969		return this.off( types, null, fn );
24970	},
24971
24972	delegate: function( selector, types, data, fn ) {
24973		return this.on( types, selector, data, fn );
24974	},
24975	undelegate: function( selector, types, fn ) {
24976
24977		// ( namespace ) or ( selector, types [, fn] )
24978		return arguments.length === 1 ?
24979			this.off( selector, "**" ) :
24980			this.off( types, selector || "**", fn );
24981	},
24982
24983	hover: function( fnOver, fnOut ) {
24984		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
24985	}
24986} );
24987
24988jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
24989	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
24990	"change select submit keydown keypress keyup contextmenu" ).split( " " ),
24991	function( _i, name ) {
24992
24993		// Handle event binding
24994		jQuery.fn[ name ] = function( data, fn ) {
24995			return arguments.length > 0 ?
24996				this.on( name, null, data, fn ) :
24997				this.trigger( name );
24998		};
24999	} );
25000
25001
25002
25003
25004// Support: Android <=4.0 only
25005// Make sure we trim BOM and NBSP
25006var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
25007
25008// Bind a function to a context, optionally partially applying any
25009// arguments.
25010// jQuery.proxy is deprecated to promote standards (specifically Function#bind)
25011// However, it is not slated for removal any time soon
25012jQuery.proxy = function( fn, context ) {
25013	var tmp, args, proxy;
25014
25015	if ( typeof context === "string" ) {
25016		tmp = fn[ context ];
25017		context = fn;
25018		fn = tmp;
25019	}
25020
25021	// Quick check to determine if target is callable, in the spec
25022	// this throws a TypeError, but we will just return undefined.
25023	if ( !isFunction( fn ) ) {
25024		return undefined;
25025	}
25026
25027	// Simulated bind
25028	args = slice.call( arguments, 2 );
25029	proxy = function() {
25030		return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
25031	};
25032
25033	// Set the guid of unique handler to the same of original handler, so it can be removed
25034	proxy.guid = fn.guid = fn.guid || jQuery.guid++;
25035
25036	return proxy;
25037};
25038
25039jQuery.holdReady = function( hold ) {
25040	if ( hold ) {
25041		jQuery.readyWait++;
25042	} else {
25043		jQuery.ready( true );
25044	}
25045};
25046jQuery.isArray = Array.isArray;
25047jQuery.parseJSON = JSON.parse;
25048jQuery.nodeName = nodeName;
25049jQuery.isFunction = isFunction;
25050jQuery.isWindow = isWindow;
25051jQuery.camelCase = camelCase;
25052jQuery.type = toType;
25053
25054jQuery.now = Date.now;
25055
25056jQuery.isNumeric = function( obj ) {
25057
25058	// As of jQuery 3.0, isNumeric is limited to
25059	// strings and numbers (primitives or objects)
25060	// that can be coerced to finite numbers (gh-2662)
25061	var type = jQuery.type( obj );
25062	return ( type === "number" || type === "string" ) &&
25063
25064		// parseFloat NaNs numeric-cast false positives ("")
25065		// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
25066		// subtraction forces infinities to NaN
25067		!isNaN( obj - parseFloat( obj ) );
25068};
25069
25070jQuery.trim = function( text ) {
25071	return text == null ?
25072		"" :
25073		( text + "" ).replace( rtrim, "" );
25074};
25075
25076
25077
25078// Register as a named AMD module, since jQuery can be concatenated with other
25079// files that may use define, but not via a proper concatenation script that
25080// understands anonymous AMD modules. A named AMD is safest and most robust
25081// way to register. Lowercase jquery is used because AMD module names are
25082// derived from file names, and jQuery is normally delivered in a lowercase
25083// file name. Do this after creating the global so that if an AMD module wants
25084// to call noConflict to hide this version of jQuery, it will work.
25085
25086// Note that for maximum portability, libraries that are not jQuery should
25087// declare themselves as anonymous modules, and avoid setting a global if an
25088// AMD loader is present. jQuery is a special case. For more information, see
25089// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
25090
25091if ( typeof define === "function" && define.amd ) {
25092	define( "jquery", [], function() {
25093		return jQuery;
25094	} );
25095}
25096
25097
25098
25099
25100var
25101
25102	// Map over jQuery in case of overwrite
25103	_jQuery = window.jQuery,
25104
25105	// Map over the $ in case of overwrite
25106	_$ = window.$;
25107
25108jQuery.noConflict = function( deep ) {
25109	if ( window.$ === jQuery ) {
25110		window.$ = _$;
25111	}
25112
25113	if ( deep && window.jQuery === jQuery ) {
25114		window.jQuery = _jQuery;
25115	}
25116
25117	return jQuery;
25118};
25119
25120// Expose jQuery and $ identifiers, even in AMD
25121// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
25122// and CommonJS for browser emulators (#13566)
25123if ( typeof noGlobal === "undefined" ) {
25124	window.jQuery = window.$ = jQuery;
25125}
25126
25127
25128
25129
25130return jQuery;
25131} );
25132
25133},{}],44:[function(require,module,exports){
25134/*! @name m3u8-parser @version 7.2.0 @license Apache-2.0 */
25135'use strict';
25136
25137Object.defineProperty(exports, '__esModule', { value: true });
25138
25139var Stream = require('@videojs/vhs-utils/cjs/stream.js');
25140var _extends = require('@babel/runtime/helpers/extends');
25141var decodeB64ToUint8Array = require('@videojs/vhs-utils/cjs/decode-b64-to-uint8-array.js');
25142
25143function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
25144
25145var Stream__default = /*#__PURE__*/_interopDefaultLegacy(Stream);
25146var _extends__default = /*#__PURE__*/_interopDefaultLegacy(_extends);
25147var decodeB64ToUint8Array__default = /*#__PURE__*/_interopDefaultLegacy(decodeB64ToUint8Array);
25148
25149/**
25150 * @file m3u8/line-stream.js
25151 */
25152/**
25153 * A stream that buffers string input and generates a `data` event for each
25154 * line.
25155 *
25156 * @class LineStream
25157 * @extends Stream
25158 */
25159
25160class LineStream extends Stream__default["default"] {
25161  constructor() {
25162    super();
25163    this.buffer = '';
25164  }
25165  /**
25166   * Add new data to be parsed.
25167   *
25168   * @param {string} data the text to process
25169   */
25170
25171
25172  push(data) {
25173    let nextNewline;
25174    this.buffer += data;
25175    nextNewline = this.buffer.indexOf('\n');
25176
25177    for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
25178      this.trigger('data', this.buffer.substring(0, nextNewline));
25179      this.buffer = this.buffer.substring(nextNewline + 1);
25180    }
25181  }
25182
25183}
25184
25185const TAB = String.fromCharCode(0x09);
25186
25187const parseByterange = function (byterangeString) {
25188  // optionally match and capture 0+ digits before `@`
25189  // optionally match and capture 0+ digits after `@`
25190  const match = /([0-9.]*)?@?([0-9.]*)?/.exec(byterangeString || '');
25191  const result = {};
25192
25193  if (match[1]) {
25194    result.length = parseInt(match[1], 10);
25195  }
25196
25197  if (match[2]) {
25198    result.offset = parseInt(match[2], 10);
25199  }
25200
25201  return result;
25202};
25203/**
25204 * "forgiving" attribute list psuedo-grammar:
25205 * attributes -> keyvalue (',' keyvalue)*
25206 * keyvalue   -> key '=' value
25207 * key        -> [^=]*
25208 * value      -> '"' [^"]* '"' | [^,]*
25209 */
25210
25211
25212const attributeSeparator = function () {
25213  const key = '[^=]*';
25214  const value = '"[^"]*"|[^,]*';
25215  const keyvalue = '(?:' + key + ')=(?:' + value + ')';
25216  return new RegExp('(?:^|,)(' + keyvalue + ')');
25217};
25218/**
25219 * Parse attributes from a line given the separator
25220 *
25221 * @param {string} attributes the attribute line to parse
25222 */
25223
25224
25225const parseAttributes = function (attributes) {
25226  const result = {};
25227
25228  if (!attributes) {
25229    return result;
25230  } // split the string using attributes as the separator
25231
25232
25233  const attrs = attributes.split(attributeSeparator());
25234  let i = attrs.length;
25235  let attr;
25236
25237  while (i--) {
25238    // filter out unmatched portions of the string
25239    if (attrs[i] === '') {
25240      continue;
25241    } // split the key and value
25242
25243
25244    attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value
25245
25246    attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
25247    attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
25248    attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
25249    result[attr[0]] = attr[1];
25250  }
25251
25252  return result;
25253};
25254/**
25255 * Converts a string into a resolution object
25256 *
25257 * @param {string} resolution a string such as 3840x2160
25258 *
25259 * @return {Object} An object representing the resolution
25260 *
25261 */
25262
25263
25264const parseResolution = resolution => {
25265  const split = resolution.split('x');
25266  const result = {};
25267
25268  if (split[0]) {
25269    result.width = parseInt(split[0], 10);
25270  }
25271
25272  if (split[1]) {
25273    result.height = parseInt(split[1], 10);
25274  }
25275
25276  return result;
25277};
25278/**
25279 * A line-level M3U8 parser event stream. It expects to receive input one
25280 * line at a time and performs a context-free parse of its contents. A stream
25281 * interpretation of a manifest can be useful if the manifest is expected to
25282 * be too large to fit comfortably into memory or the entirety of the input
25283 * is not immediately available. Otherwise, it's probably much easier to work
25284 * with a regular `Parser` object.
25285 *
25286 * Produces `data` events with an object that captures the parser's
25287 * interpretation of the input. That object has a property `tag` that is one
25288 * of `uri`, `comment`, or `tag`. URIs only have a single additional
25289 * property, `line`, which captures the entirety of the input without
25290 * interpretation. Comments similarly have a single additional property
25291 * `text` which is the input without the leading `#`.
25292 *
25293 * Tags always have a property `tagType` which is the lower-cased version of
25294 * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
25295 * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
25296 * tags are given the tag type `unknown` and a single additional property
25297 * `data` with the remainder of the input.
25298 *
25299 * @class ParseStream
25300 * @extends Stream
25301 */
25302
25303
25304class ParseStream extends Stream__default["default"] {
25305  constructor() {
25306    super();
25307    this.customParsers = [];
25308    this.tagMappers = [];
25309  }
25310  /**
25311   * Parses an additional line of input.
25312   *
25313   * @param {string} line a single line of an M3U8 file to parse
25314   */
25315
25316
25317  push(line) {
25318    let match;
25319    let event; // strip whitespace
25320
25321    line = line.trim();
25322
25323    if (line.length === 0) {
25324      // ignore empty lines
25325      return;
25326    } // URIs
25327
25328
25329    if (line[0] !== '#') {
25330      this.trigger('data', {
25331        type: 'uri',
25332        uri: line
25333      });
25334      return;
25335    } // map tags
25336
25337
25338    const newLines = this.tagMappers.reduce((acc, mapper) => {
25339      const mappedLine = mapper(line);
vendor: 55,710 bytes, lines 25339-27042
25339 // skip if unchanged
25340
25341      if (mappedLine === line) {
25342        return acc;
25343      }
25344
25345      return acc.concat([mappedLine]);
25346    }, [line]);
25347    newLines.forEach(newLine => {
25348      for (let i = 0; i < this.customParsers.length; i++) {
25349        if (this.customParsers[i].call(this, newLine)) {
25350          return;
25351        }
25352      } // Comments
25353
25354
25355      if (newLine.indexOf('#EXT') !== 0) {
25356        this.trigger('data', {
25357          type: 'comment',
25358          text: newLine.slice(1)
25359        });
25360        return;
25361      } // strip off any carriage returns here so the regex matching
25362      // doesn't have to account for them.
25363
25364
25365      newLine = newLine.replace('\r', ''); // Tags
25366
25367      match = /^#EXTM3U/.exec(newLine);
25368
25369      if (match) {
25370        this.trigger('data', {
25371          type: 'tag',
25372          tagType: 'm3u'
25373        });
25374        return;
25375      }
25376
25377      match = /^#EXTINF:([0-9\.]*)?,?(.*)?$/.exec(newLine);
25378
25379      if (match) {
25380        event = {
25381          type: 'tag',
25382          tagType: 'inf'
25383        };
25384
25385        if (match[1]) {
25386          event.duration = parseFloat(match[1]);
25387        }
25388
25389        if (match[2]) {
25390          event.title = match[2];
25391        }
25392
25393        this.trigger('data', event);
25394        return;
25395      }
25396
25397      match = /^#EXT-X-TARGETDURATION:([0-9.]*)?/.exec(newLine);
25398
25399      if (match) {
25400        event = {
25401          type: 'tag',
25402          tagType: 'targetduration'
25403        };
25404
25405        if (match[1]) {
25406          event.duration = parseInt(match[1], 10);
25407        }
25408
25409        this.trigger('data', event);
25410        return;
25411      }
25412
25413      match = /^#EXT-X-VERSION:([0-9.]*)?/.exec(newLine);
25414
25415      if (match) {
25416        event = {
25417          type: 'tag',
25418          tagType: 'version'
25419        };
25420
25421        if (match[1]) {
25422          event.version = parseInt(match[1], 10);
25423        }
25424
25425        this.trigger('data', event);
25426        return;
25427      }
25428
25429      match = /^#EXT-X-MEDIA-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
25430
25431      if (match) {
25432        event = {
25433          type: 'tag',
25434          tagType: 'media-sequence'
25435        };
25436
25437        if (match[1]) {
25438          event.number = parseInt(match[1], 10);
25439        }
25440
25441        this.trigger('data', event);
25442        return;
25443      }
25444
25445      match = /^#EXT-X-DISCONTINUITY-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
25446
25447      if (match) {
25448        event = {
25449          type: 'tag',
25450          tagType: 'discontinuity-sequence'
25451        };
25452
25453        if (match[1]) {
25454          event.number = parseInt(match[1], 10);
25455        }
25456
25457        this.trigger('data', event);
25458        return;
25459      }
25460
25461      match = /^#EXT-X-PLAYLIST-TYPE:(.*)?$/.exec(newLine);
25462
25463      if (match) {
25464        event = {
25465          type: 'tag',
25466          tagType: 'playlist-type'
25467        };
25468
25469        if (match[1]) {
25470          event.playlistType = match[1];
25471        }
25472
25473        this.trigger('data', event);
25474        return;
25475      }
25476
25477      match = /^#EXT-X-BYTERANGE:(.*)?$/.exec(newLine);
25478
25479      if (match) {
25480        event = _extends__default["default"](parseByterange(match[1]), {
25481          type: 'tag',
25482          tagType: 'byterange'
25483        });
25484        this.trigger('data', event);
25485        return;
25486      }
25487
25488      match = /^#EXT-X-ALLOW-CACHE:(YES|NO)?/.exec(newLine);
25489
25490      if (match) {
25491        event = {
25492          type: 'tag',
25493          tagType: 'allow-cache'
25494        };
25495
25496        if (match[1]) {
25497          event.allowed = !/NO/.test(match[1]);
25498        }
25499
25500        this.trigger('data', event);
25501        return;
25502      }
25503
25504      match = /^#EXT-X-MAP:(.*)$/.exec(newLine);
25505
25506      if (match) {
25507        event = {
25508          type: 'tag',
25509          tagType: 'map'
25510        };
25511
25512        if (match[1]) {
25513          const attributes = parseAttributes(match[1]);
25514
25515          if (attributes.URI) {
25516            event.uri = attributes.URI;
25517          }
25518
25519          if (attributes.BYTERANGE) {
25520            event.byterange = parseByterange(attributes.BYTERANGE);
25521          }
25522        }
25523
25524        this.trigger('data', event);
25525        return;
25526      }
25527
25528      match = /^#EXT-X-STREAM-INF:(.*)$/.exec(newLine);
25529
25530      if (match) {
25531        event = {
25532          type: 'tag',
25533          tagType: 'stream-inf'
25534        };
25535
25536        if (match[1]) {
25537          event.attributes = parseAttributes(match[1]);
25538
25539          if (event.attributes.RESOLUTION) {
25540            event.attributes.RESOLUTION = parseResolution(event.attributes.RESOLUTION);
25541          }
25542
25543          if (event.attributes.BANDWIDTH) {
25544            event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
25545          }
25546
25547          if (event.attributes['FRAME-RATE']) {
25548            event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']);
25549          }
25550
25551          if (event.attributes['PROGRAM-ID']) {
25552            event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
25553          }
25554        }
25555
25556        this.trigger('data', event);
25557        return;
25558      }
25559
25560      match = /^#EXT-X-MEDIA:(.*)$/.exec(newLine);
25561
25562      if (match) {
25563        event = {
25564          type: 'tag',
25565          tagType: 'media'
25566        };
25567
25568        if (match[1]) {
25569          event.attributes = parseAttributes(match[1]);
25570        }
25571
25572        this.trigger('data', event);
25573        return;
25574      }
25575
25576      match = /^#EXT-X-ENDLIST/.exec(newLine);
25577
25578      if (match) {
25579        this.trigger('data', {
25580          type: 'tag',
25581          tagType: 'endlist'
25582        });
25583        return;
25584      }
25585
25586      match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
25587
25588      if (match) {
25589        this.trigger('data', {
25590          type: 'tag',
25591          tagType: 'discontinuity'
25592        });
25593        return;
25594      }
25595
25596      match = /^#EXT-X-PROGRAM-DATE-TIME:(.*)$/.exec(newLine);
25597
25598      if (match) {
25599        event = {
25600          type: 'tag',
25601          tagType: 'program-date-time'
25602        };
25603
25604        if (match[1]) {
25605          event.dateTimeString = match[1];
25606          event.dateTimeObject = new Date(match[1]);
25607        }
25608
25609        this.trigger('data', event);
25610        return;
25611      }
25612
25613      match = /^#EXT-X-KEY:(.*)$/.exec(newLine);
25614
25615      if (match) {
25616        event = {
25617          type: 'tag',
25618          tagType: 'key'
25619        };
25620
25621        if (match[1]) {
25622          event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array
25623
25624          if (event.attributes.IV) {
25625            if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
25626              event.attributes.IV = event.attributes.IV.substring(2);
25627            }
25628
25629            event.attributes.IV = event.attributes.IV.match(/.{8}/g);
25630            event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
25631            event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
25632            event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
25633            event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
25634            event.attributes.IV = new Uint32Array(event.attributes.IV);
25635          }
25636        }
25637
25638        this.trigger('data', event);
25639        return;
25640      }
25641
25642      match = /^#EXT-X-START:(.*)$/.exec(newLine);
25643
25644      if (match) {
25645        event = {
25646          type: 'tag',
25647          tagType: 'start'
25648        };
25649
25650        if (match[1]) {
25651          event.attributes = parseAttributes(match[1]);
25652          event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']);
25653          event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
25654        }
25655
25656        this.trigger('data', event);
25657        return;
25658      }
25659
25660      match = /^#EXT-X-CUE-OUT-CONT:(.*)?$/.exec(newLine);
25661
25662      if (match) {
25663        event = {
25664          type: 'tag',
25665          tagType: 'cue-out-cont'
25666        };
25667
25668        if (match[1]) {
25669          event.data = match[1];
25670        } else {
25671          event.data = '';
25672        }
25673
25674        this.trigger('data', event);
25675        return;
25676      }
25677
25678      match = /^#EXT-X-CUE-OUT:(.*)?$/.exec(newLine);
25679
25680      if (match) {
25681        event = {
25682          type: 'tag',
25683          tagType: 'cue-out'
25684        };
25685
25686        if (match[1]) {
25687          event.data = match[1];
25688        } else {
25689          event.data = '';
25690        }
25691
25692        this.trigger('data', event);
25693        return;
25694      }
25695
25696      match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine);
25697
25698      if (match) {
25699        event = {
25700          type: 'tag',
25701          tagType: 'cue-in'
25702        };
25703
25704        if (match[1]) {
25705          event.data = match[1];
25706        } else {
25707          event.data = '';
25708        }
25709
25710        this.trigger('data', event);
25711        return;
25712      }
25713
25714      match = /^#EXT-X-SKIP:(.*)$/.exec(newLine);
25715
25716      if (match && match[1]) {
25717        event = {
25718          type: 'tag',
25719          tagType: 'skip'
25720        };
25721        event.attributes = parseAttributes(match[1]);
25722
25723        if (event.attributes.hasOwnProperty('SKIPPED-SEGMENTS')) {
25724          event.attributes['SKIPPED-SEGMENTS'] = parseInt(event.attributes['SKIPPED-SEGMENTS'], 10);
25725        }
25726
25727        if (event.attributes.hasOwnProperty('RECENTLY-REMOVED-DATERANGES')) {
25728          event.attributes['RECENTLY-REMOVED-DATERANGES'] = event.attributes['RECENTLY-REMOVED-DATERANGES'].split(TAB);
25729        }
25730
25731        this.trigger('data', event);
25732        return;
25733      }
25734
25735      match = /^#EXT-X-PART:(.*)$/.exec(newLine);
25736
25737      if (match && match[1]) {
25738        event = {
25739          type: 'tag',
25740          tagType: 'part'
25741        };
25742        event.attributes = parseAttributes(match[1]);
25743        ['DURATION'].forEach(function (key) {
25744          if (event.attributes.hasOwnProperty(key)) {
25745            event.attributes[key] = parseFloat(event.attributes[key]);
25746          }
25747        });
25748        ['INDEPENDENT', 'GAP'].forEach(function (key) {
25749          if (event.attributes.hasOwnProperty(key)) {
25750            event.attributes[key] = /YES/.test(event.attributes[key]);
25751          }
25752        });
25753
25754        if (event.attributes.hasOwnProperty('BYTERANGE')) {
25755          event.attributes.byterange = parseByterange(event.attributes.BYTERANGE);
25756        }
25757
25758        this.trigger('data', event);
25759        return;
25760      }
25761
25762      match = /^#EXT-X-SERVER-CONTROL:(.*)$/.exec(newLine);
25763
25764      if (match && match[1]) {
25765        event = {
25766          type: 'tag',
25767          tagType: 'server-control'
25768        };
25769        event.attributes = parseAttributes(match[1]);
25770        ['CAN-SKIP-UNTIL', 'PART-HOLD-BACK', 'HOLD-BACK'].forEach(function (key) {
25771          if (event.attributes.hasOwnProperty(key)) {
25772            event.attributes[key] = parseFloat(event.attributes[key]);
25773          }
25774        });
25775        ['CAN-SKIP-DATERANGES', 'CAN-BLOCK-RELOAD'].forEach(function (key) {
25776          if (event.attributes.hasOwnProperty(key)) {
25777            event.attributes[key] = /YES/.test(event.attributes[key]);
25778          }
25779        });
25780        this.trigger('data', event);
25781        return;
25782      }
25783
25784      match = /^#EXT-X-PART-INF:(.*)$/.exec(newLine);
25785
25786      if (match && match[1]) {
25787        event = {
25788          type: 'tag',
25789          tagType: 'part-inf'
25790        };
25791        event.attributes = parseAttributes(match[1]);
25792        ['PART-TARGET'].forEach(function (key) {
25793          if (event.attributes.hasOwnProperty(key)) {
25794            event.attributes[key] = parseFloat(event.attributes[key]);
25795          }
25796        });
25797        this.trigger('data', event);
25798        return;
25799      }
25800
25801      match = /^#EXT-X-PRELOAD-HINT:(.*)$/.exec(newLine);
25802
25803      if (match && match[1]) {
25804        event = {
25805          type: 'tag',
25806          tagType: 'preload-hint'
25807        };
25808        event.attributes = parseAttributes(match[1]);
25809        ['BYTERANGE-START', 'BYTERANGE-LENGTH'].forEach(function (key) {
25810          if (event.attributes.hasOwnProperty(key)) {
25811            event.attributes[key] = parseInt(event.attributes[key], 10);
25812            const subkey = key === 'BYTERANGE-LENGTH' ? 'length' : 'offset';
25813            event.attributes.byterange = event.attributes.byterange || {};
25814            event.attributes.byterange[subkey] = event.attributes[key]; // only keep the parsed byterange object.
25815
25816            delete event.attributes[key];
25817          }
25818        });
25819        this.trigger('data', event);
25820        return;
25821      }
25822
25823      match = /^#EXT-X-RENDITION-REPORT:(.*)$/.exec(newLine);
25824
25825      if (match && match[1]) {
25826        event = {
25827          type: 'tag',
25828          tagType: 'rendition-report'
25829        };
25830        event.attributes = parseAttributes(match[1]);
25831        ['LAST-MSN', 'LAST-PART'].forEach(function (key) {
25832          if (event.attributes.hasOwnProperty(key)) {
25833            event.attributes[key] = parseInt(event.attributes[key], 10);
25834          }
25835        });
25836        this.trigger('data', event);
25837        return;
25838      }
25839
25840      match = /^#EXT-X-DATERANGE:(.*)$/.exec(newLine);
25841
25842      if (match && match[1]) {
25843        event = {
25844          type: 'tag',
25845          tagType: 'daterange'
25846        };
25847        event.attributes = parseAttributes(match[1]);
25848        ['ID', 'CLASS'].forEach(function (key) {
25849          if (event.attributes.hasOwnProperty(key)) {
25850            event.attributes[key] = String(event.attributes[key]);
25851          }
25852        });
25853        ['START-DATE', 'END-DATE'].forEach(function (key) {
25854          if (event.attributes.hasOwnProperty(key)) {
25855            event.attributes[key] = new Date(event.attributes[key]);
25856          }
25857        });
25858        ['DURATION', 'PLANNED-DURATION'].forEach(function (key) {
25859          if (event.attributes.hasOwnProperty(key)) {
25860            event.attributes[key] = parseFloat(event.attributes[key]);
25861          }
25862        });
25863        ['END-ON-NEXT'].forEach(function (key) {
25864          if (event.attributes.hasOwnProperty(key)) {
25865            event.attributes[key] = /YES/i.test(event.attributes[key]);
25866          }
25867        });
25868        ['SCTE35-CMD', ' SCTE35-OUT', 'SCTE35-IN'].forEach(function (key) {
25869          if (event.attributes.hasOwnProperty(key)) {
25870            event.attributes[key] = event.attributes[key].toString(16);
25871          }
25872        });
25873        const clientAttributePattern = /^X-([A-Z]+-)+[A-Z]+$/;
25874
25875        for (const key in event.attributes) {
25876          if (!clientAttributePattern.test(key)) {
25877            continue;
25878          }
25879
25880          const isHexaDecimal = /[0-9A-Fa-f]{6}/g.test(event.attributes[key]);
25881          const isDecimalFloating = /^\d+(\.\d+)?$/.test(event.attributes[key]);
25882          event.attributes[key] = isHexaDecimal ? event.attributes[key].toString(16) : isDecimalFloating ? parseFloat(event.attributes[key]) : String(event.attributes[key]);
25883        }
25884
25885        this.trigger('data', event);
25886        return;
25887      }
25888
25889      match = /^#EXT-X-INDEPENDENT-SEGMENTS/.exec(newLine);
25890
25891      if (match) {
25892        this.trigger('data', {
25893          type: 'tag',
25894          tagType: 'independent-segments'
25895        });
25896        return;
25897      }
25898
25899      match = /^#EXT-X-I-FRAMES-ONLY/.exec(newLine);
25900
25901      if (match) {
25902        this.trigger('data', {
25903          type: 'tag',
25904          tagType: 'i-frames-only'
25905        });
25906        return;
25907      }
25908
25909      match = /^#EXT-X-CONTENT-STEERING:(.*)$/.exec(newLine);
25910
25911      if (match) {
25912        event = {
25913          type: 'tag',
25914          tagType: 'content-steering'
25915        };
25916        event.attributes = parseAttributes(match[1]);
25917        this.trigger('data', event);
25918        return;
25919      }
25920
25921      match = /^#EXT-X-I-FRAME-STREAM-INF:(.*)$/.exec(newLine);
25922
25923      if (match) {
25924        event = {
25925          type: 'tag',
25926          tagType: 'i-frame-playlist'
25927        };
25928        event.attributes = parseAttributes(match[1]);
25929
25930        if (event.attributes.URI) {
25931          event.uri = event.attributes.URI;
25932        }
25933
25934        if (event.attributes.BANDWIDTH) {
25935          event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
25936        }
25937
25938        if (event.attributes.RESOLUTION) {
25939          event.attributes.RESOLUTION = parseResolution(event.attributes.RESOLUTION);
25940        }
25941
25942        if (event.attributes['AVERAGE-BANDWIDTH']) {
25943          event.attributes['AVERAGE-BANDWIDTH'] = parseInt(event.attributes['AVERAGE-BANDWIDTH'], 10);
25944        }
25945
25946        if (event.attributes['FRAME-RATE']) {
25947          event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']);
25948        }
25949
25950        this.trigger('data', event);
25951        return;
25952      }
25953
25954      match = /^#EXT-X-DEFINE:(.*)$/.exec(newLine);
25955
25956      if (match) {
25957        event = {
25958          type: 'tag',
25959          tagType: 'define'
25960        };
25961        event.attributes = parseAttributes(match[1]);
25962        this.trigger('data', event);
25963        return;
25964      } // unknown tag type
25965
25966
25967      this.trigger('data', {
25968        type: 'tag',
25969        data: newLine.slice(4)
25970      });
25971    });
25972  }
25973  /**
25974   * Add a parser for custom headers
25975   *
25976   * @param {Object}   options              a map of options for the added parser
25977   * @param {RegExp}   options.expression   a regular expression to match the custom header
25978   * @param {string}   options.customType   the custom type to register to the output
25979   * @param {Function} [options.dataParser] function to parse the line into an object
25980   * @param {boolean}  [options.segment]    should tag data be attached to the segment object
25981   */
25982
25983
25984  addParser({
25985    expression,
25986    customType,
25987    dataParser,
25988    segment
25989  }) {
25990    if (typeof dataParser !== 'function') {
25991      dataParser = line => line;
25992    }
25993
25994    this.customParsers.push(line => {
25995      const match = expression.exec(line);
25996
25997      if (match) {
25998        this.trigger('data', {
25999          type: 'custom',
26000          data: dataParser(line),
26001          customType,
26002          segment
26003        });
26004        return true;
26005      }
26006    });
26007  }
26008  /**
26009   * Add a custom header mapper
26010   *
26011   * @param {Object}   options
26012   * @param {RegExp}   options.expression   a regular expression to match the custom header
26013   * @param {Function} options.map          function to translate tag into a different tag
26014   */
26015
26016
26017  addTagMapper({
26018    expression,
26019    map
26020  }) {
26021    const mapFn = line => {
26022      if (expression.test(line)) {
26023        return map(line);
26024      }
26025
26026      return line;
26027    };
26028
26029    this.tagMappers.push(mapFn);
26030  }
26031
26032}
26033
26034const camelCase = str => str.toLowerCase().replace(/-(\w)/g, a => a[1].toUpperCase());
26035
26036const camelCaseKeys = function (attributes) {
26037  const result = {};
26038  Object.keys(attributes).forEach(function (key) {
26039    result[camelCase(key)] = attributes[key];
26040  });
26041  return result;
26042}; // set SERVER-CONTROL hold back based upon targetDuration and partTargetDuration
26043// we need this helper because defaults are based upon targetDuration and
26044// partTargetDuration being set, but they may not be if SERVER-CONTROL appears before
26045// target durations are set.
26046
26047
26048const setHoldBack = function (manifest) {
26049  const {
26050    serverControl,
26051    targetDuration,
26052    partTargetDuration
26053  } = manifest;
26054
26055  if (!serverControl) {
26056    return;
26057  }
26058
26059  const tag = '#EXT-X-SERVER-CONTROL';
26060  const hb = 'holdBack';
26061  const phb = 'partHoldBack';
26062  const minTargetDuration = targetDuration && targetDuration * 3;
26063  const minPartDuration = partTargetDuration && partTargetDuration * 2;
26064
26065  if (targetDuration && !serverControl.hasOwnProperty(hb)) {
26066    serverControl[hb] = minTargetDuration;
26067    this.trigger('info', {
26068      message: `${tag} defaulting HOLD-BACK to targetDuration * 3 (${minTargetDuration}).`
26069    });
26070  }
26071
26072  if (minTargetDuration && serverControl[hb] < minTargetDuration) {
26073    this.trigger('warn', {
26074      message: `${tag} clamping HOLD-BACK (${serverControl[hb]}) to targetDuration * 3 (${minTargetDuration})`
26075    });
26076    serverControl[hb] = minTargetDuration;
26077  } // default no part hold back to part target duration * 3
26078
26079
26080  if (partTargetDuration && !serverControl.hasOwnProperty(phb)) {
26081    serverControl[phb] = partTargetDuration * 3;
26082    this.trigger('info', {
26083      message: `${tag} defaulting PART-HOLD-BACK to partTargetDuration * 3 (${serverControl[phb]}).`
26084    });
26085  } // if part hold back is too small default it to part target duration * 2
26086
26087
26088  if (partTargetDuration && serverControl[phb] < minPartDuration) {
26089    this.trigger('warn', {
26090      message: `${tag} clamping PART-HOLD-BACK (${serverControl[phb]}) to partTargetDuration * 2 (${minPartDuration}).`
26091    });
26092    serverControl[phb] = minPartDuration;
26093  }
26094};
26095/**
26096 * A parser for M3U8 files. The current interpretation of the input is
26097 * exposed as a property `manifest` on parser objects. It's just two lines to
26098 * create and parse a manifest once you have the contents available as a string:
26099 *
26100 * ```js
26101 * var parser = new m3u8.Parser();
26102 * parser.push(xhr.responseText);
26103 * ```
26104 *
26105 * New input can later be applied to update the manifest object by calling
26106 * `push` again.
26107 *
26108 * The parser attempts to create a usable manifest object even if the
26109 * underlying input is somewhat nonsensical. It emits `info` and `warning`
26110 * events during the parse if it encounters input that seems invalid or
26111 * requires some property of the manifest object to be defaulted.
26112 *
26113 * @class Parser
26114 * @param {Object} [opts] Options for the constructor, needed for substitutions
26115 * @param {string} [opts.uri] URL to check for query params
26116 * @param {Object} [opts.mainDefinitions] Definitions on main playlist that can be imported
26117 * @extends Stream
26118 */
26119
26120
26121class Parser extends Stream__default["default"] {
26122  constructor(opts = {}) {
26123    super();
26124    this.lineStream = new LineStream();
26125    this.parseStream = new ParseStream();
26126    this.lineStream.pipe(this.parseStream);
26127    this.mainDefinitions = opts.mainDefinitions || {};
26128    this.params = new URL(opts.uri, 'https://a.com').searchParams;
26129    this.lastProgramDateTime = null;
26130    /* eslint-disable consistent-this */
26131
26132    const self = this;
26133    /* eslint-enable consistent-this */
26134
26135    const uris = [];
26136    let currentUri = {}; // if specified, the active EXT-X-MAP definition
26137
26138    let currentMap; // if specified, the active decryption key
26139
26140    let key;
26141    let hasParts = false;
26142
26143    const noop = function () {};
26144
26145    const defaultMediaGroups = {
26146      'AUDIO': {},
26147      'VIDEO': {},
26148      'CLOSED-CAPTIONS': {},
26149      'SUBTITLES': {}
26150    }; // This is the Widevine UUID from DASH IF IOP. The same exact string is
26151    // used in MPDs with Widevine encrypted streams.
26152
26153    const widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities
26154
26155    let currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
26156
26157    this.manifest = {
26158      allowCache: true,
26159      discontinuityStarts: [],
26160      dateRanges: [],
26161      iFramePlaylists: [],
26162      segments: []
26163    }; // keep track of the last seen segment's byte range end, as segments are not required
26164    // to provide the offset, in which case it defaults to the next byte after the
26165    // previous segment
26166
26167    let lastByterangeEnd = 0; // keep track of the last seen part's byte range end.
26168
26169    let lastPartByterangeEnd = 0;
26170    const dateRangeTags = {};
26171    this.on('end', () => {
26172      // only add preloadSegment if we don't yet have a uri for it.
26173      // and we actually have parts/preloadHints
26174      if (currentUri.uri || !currentUri.parts && !currentUri.preloadHints) {
26175        return;
26176      }
26177
26178      if (!currentUri.map && currentMap) {
26179        currentUri.map = currentMap;
26180      }
26181
26182      if (!currentUri.key && key) {
26183        currentUri.key = key;
26184      }
26185
26186      if (!currentUri.timeline && typeof currentTimeline === 'number') {
26187        currentUri.timeline = currentTimeline;
26188      }
26189
26190      this.manifest.preloadSegment = currentUri;
26191    }); // update the manifest with the m3u8 entry from the parse stream
26192
26193    this.parseStream.on('data', function (entry) {
26194      let mediaGroup;
26195      let rendition; // Replace variables in uris and attributes as defined in #EXT-X-DEFINE tags
26196
26197      if (self.manifest.definitions) {
26198        for (const def in self.manifest.definitions) {
26199          if (entry.uri) {
26200            entry.uri = entry.uri.replace(`{$${def}}`, self.manifest.definitions[def]);
26201          }
26202
26203          if (entry.attributes) {
26204            for (const attr in entry.attributes) {
26205              if (typeof entry.attributes[attr] === 'string') {
26206                entry.attributes[attr] = entry.attributes[attr].replace(`{$${def}}`, self.manifest.definitions[def]);
26207              }
26208            }
26209          }
26210        }
26211      }
26212
26213      ({
26214        tag() {
26215          // switch based on the tag type
26216          (({
26217            version() {
26218              if (entry.version) {
26219                this.manifest.version = entry.version;
26220              }
26221            },
26222
26223            'allow-cache'() {
26224              this.manifest.allowCache = entry.allowed;
26225
26226              if (!('allowed' in entry)) {
26227                this.trigger('info', {
26228                  message: 'defaulting allowCache to YES'
26229                });
26230                this.manifest.allowCache = true;
26231              }
26232            },
26233
26234            byterange() {
26235              const byterange = {};
26236
26237              if ('length' in entry) {
26238                currentUri.byterange = byterange;
26239                byterange.length = entry.length;
26240
26241                if (!('offset' in entry)) {
26242                  /*
26243                   * From the latest spec (as of this writing):
26244                   * https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.2
26245                   *
26246                   * Same text since EXT-X-BYTERANGE's introduction in draft 7:
26247                   * https://tools.ietf.org/html/draft-pantos-http-live-streaming-07#section-3.3.1)
26248                   *
26249                   * "If o [offset] is not present, the sub-range begins at the next byte
26250                   * following the sub-range of the previous media segment."
26251                   */
26252                  entry.offset = lastByterangeEnd;
26253                }
26254              }
26255
26256              if ('offset' in entry) {
26257                currentUri.byterange = byterange;
26258                byterange.offset = entry.offset;
26259              }
26260
26261              lastByterangeEnd = byterange.offset + byterange.length;
26262            },
26263
26264            endlist() {
26265              this.manifest.endList = true;
26266            },
26267
26268            inf() {
26269              if (!('mediaSequence' in this.manifest)) {
26270                this.manifest.mediaSequence = 0;
26271                this.trigger('info', {
26272                  message: 'defaulting media sequence to zero'
26273                });
26274              }
26275
26276              if (!('discontinuitySequence' in this.manifest)) {
26277                this.manifest.discontinuitySequence = 0;
26278                this.trigger('info', {
26279                  message: 'defaulting discontinuity sequence to zero'
26280                });
26281              }
26282
26283              if (entry.title) {
26284                currentUri.title = entry.title;
26285              }
26286
26287              if (entry.duration > 0) {
26288                currentUri.duration = entry.duration;
26289              }
26290
26291              if (entry.duration === 0) {
26292                currentUri.duration = 0.01;
26293                this.trigger('info', {
26294                  message: 'updating zero segment duration to a small value'
26295                });
26296              }
26297
26298              this.manifest.segments = uris;
26299            },
26300
26301            key() {
26302              if (!entry.attributes) {
26303                this.trigger('warn', {
26304                  message: 'ignoring key declaration without attribute list'
26305                });
26306                return;
26307              } // clear the active encryption key
26308
26309
26310              if (entry.attributes.METHOD === 'NONE') {
26311                key = null;
26312                return;
26313              }
26314
26315              if (!entry.attributes.URI) {
26316                this.trigger('warn', {
26317                  message: 'ignoring key declaration without URI'
26318                });
26319                return;
26320              }
26321
26322              if (entry.attributes.KEYFORMAT === 'com.apple.streamingkeydelivery') {
26323                this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
26324
26325                this.manifest.contentProtection['com.apple.fps.1_0'] = {
26326                  attributes: entry.attributes
26327                };
26328                return;
26329              }
26330
26331              if (entry.attributes.KEYFORMAT === 'com.microsoft.playready') {
26332                this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
26333
26334                this.manifest.contentProtection['com.microsoft.playready'] = {
26335                  uri: entry.attributes.URI
26336                };
26337                return;
26338              } // check if the content is encrypted for Widevine
26339              // Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf
26340
26341
26342              if (entry.attributes.KEYFORMAT === widevineUuid) {
26343                const VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC'];
26344
26345                if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) {
26346                  this.trigger('warn', {
26347                    message: 'invalid key method provided for Widevine'
26348                  });
26349                  return;
26350                }
26351
26352                if (entry.attributes.METHOD === 'SAMPLE-AES-CENC') {
26353                  this.trigger('warn', {
26354                    message: 'SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead'
26355                  });
26356                }
26357
26358                if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') {
26359                  this.trigger('warn', {
26360                    message: 'invalid key URI provided for Widevine'
26361                  });
26362                  return;
26363                }
26364
26365                if (!(entry.attributes.KEYID && entry.attributes.KEYID.substring(0, 2) === '0x')) {
26366                  this.trigger('warn', {
26367                    message: 'invalid key ID provided for Widevine'
26368                  });
26369                  return;
26370                } // if Widevine key attributes are valid, store them as `contentProtection`
26371                // on the manifest to emulate Widevine tag structure in a DASH mpd
26372
26373
26374                this.manifest.contentProtection = this.manifest.contentProtection || {};
26375                this.manifest.contentProtection['com.widevine.alpha'] = {
26376                  attributes: {
26377                    schemeIdUri: entry.attributes.KEYFORMAT,
26378                    // remove '0x' from the key id string
26379                    keyId: entry.attributes.KEYID.substring(2)
26380                  },
26381                  // decode the base64-encoded PSSH box
26382                  pssh: decodeB64ToUint8Array__default["default"](entry.attributes.URI.split(',')[1])
26383                };
26384                return;
26385              }
26386
26387              if (!entry.attributes.METHOD) {
26388                this.trigger('warn', {
26389                  message: 'defaulting key method to AES-128'
26390                });
26391              } // setup an encryption key for upcoming segments
26392
26393
26394              key = {
26395                method: entry.attributes.METHOD || 'AES-128',
26396                uri: entry.attributes.URI
26397              };
26398
26399              if (typeof entry.attributes.IV !== 'undefined') {
26400                key.iv = entry.attributes.IV;
26401              }
26402            },
26403
26404            'media-sequence'() {
26405              if (!isFinite(entry.number)) {
26406                this.trigger('warn', {
26407                  message: 'ignoring invalid media sequence: ' + entry.number
26408                });
26409                return;
26410              }
26411
26412              this.manifest.mediaSequence = entry.number;
26413            },
26414
26415            'discontinuity-sequence'() {
26416              if (!isFinite(entry.number)) {
26417                this.trigger('warn', {
26418                  message: 'ignoring invalid discontinuity sequence: ' + entry.number
26419                });
26420                return;
26421              }
26422
26423              this.manifest.discontinuitySequence = entry.number;
26424              currentTimeline = entry.number;
26425            },
26426
26427            'playlist-type'() {
26428              if (!/VOD|EVENT/.test(entry.playlistType)) {
26429                this.trigger('warn', {
26430                  message: 'ignoring unknown playlist type: ' + entry.playlist
26431                });
26432                return;
26433              }
26434
26435              this.manifest.playlistType = entry.playlistType;
26436            },
26437
26438            map() {
26439              currentMap = {};
26440
26441              if (entry.uri) {
26442                currentMap.uri = entry.uri;
26443              }
26444
26445              if (entry.byterange) {
26446                currentMap.byterange = entry.byterange;
26447              }
26448
26449              if (key) {
26450                currentMap.key = key;
26451              }
26452            },
26453
26454            'stream-inf'() {
26455              this.manifest.playlists = uris;
26456              this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
26457
26458              if (!entry.attributes) {
26459                this.trigger('warn', {
26460                  message: 'ignoring empty stream-inf attributes'
26461                });
26462                return;
26463              }
26464
26465              if (!currentUri.attributes) {
26466                currentUri.attributes = {};
26467              }
26468
26469              _extends__default["default"](currentUri.attributes, entry.attributes);
26470            },
26471
26472            media() {
26473              this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
26474
26475              if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
26476                this.trigger('warn', {
26477                  message: 'ignoring incomplete or missing media group'
26478                });
26479                return;
26480              } // find the media group, creating defaults as necessary
26481
26482
26483              const mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
26484              mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
26485              mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata
26486
26487              rendition = {
26488                default: /yes/i.test(entry.attributes.DEFAULT)
26489              };
26490
26491              if (rendition.default) {
26492                rendition.autoselect = true;
26493              } else {
26494                rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
26495              }
26496
26497              if (entry.attributes.LANGUAGE) {
26498                rendition.language = entry.attributes.LANGUAGE;
26499              }
26500
26501              if (entry.attributes.URI) {
26502                rendition.uri = entry.attributes.URI;
26503              }
26504
26505              if (entry.attributes['INSTREAM-ID']) {
26506                rendition.instreamId = entry.attributes['INSTREAM-ID'];
26507              }
26508
26509              if (entry.attributes.CHARACTERISTICS) {
26510                rendition.characteristics = entry.attributes.CHARACTERISTICS;
26511              }
26512
26513              if (entry.attributes.FORCED) {
26514                rendition.forced = /yes/i.test(entry.attributes.FORCED);
26515              } // insert the new rendition
26516
26517
26518              mediaGroup[entry.attributes.NAME] = rendition;
26519            },
26520
26521            discontinuity() {
26522              currentTimeline += 1;
26523              currentUri.discontinuity = true;
26524              this.manifest.discontinuityStarts.push(uris.length);
26525            },
26526
26527            'program-date-time'() {
26528              if (typeof this.manifest.dateTimeString === 'undefined') {
26529                // PROGRAM-DATE-TIME is a media-segment tag, but for backwards
26530                // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
26531                // to the manifest object
26532                // TODO: Consider removing this in future major version
26533                this.manifest.dateTimeString = entry.dateTimeString;
26534                this.manifest.dateTimeObject = entry.dateTimeObject;
26535              }
26536
26537              currentUri.dateTimeString = entry.dateTimeString;
26538              currentUri.dateTimeObject = entry.dateTimeObject;
26539              const {
26540                lastProgramDateTime
26541              } = this;
26542              this.lastProgramDateTime = new Date(entry.dateTimeString).getTime(); // We should extrapolate Program Date Time backward only during first program date time occurrence.
26543              // Once we have at least one program date time point, we can always extrapolate it forward using lastProgramDateTime reference.
26544
26545              if (lastProgramDateTime === null) {
26546                // Extrapolate Program Date Time backward
26547                // Since it is first program date time occurrence we're assuming that
26548                // all this.manifest.segments have no program date time info
26549                this.manifest.segments.reduceRight((programDateTime, segment) => {
26550                  segment.programDateTime = programDateTime - segment.duration * 1000;
26551                  return segment.programDateTime;
26552                }, this.lastProgramDateTime);
26553              }
26554            },
26555
26556            targetduration() {
26557              if (!isFinite(entry.duration) || entry.duration < 0) {
26558                this.trigger('warn', {
26559                  message: 'ignoring invalid target duration: ' + entry.duration
26560                });
26561                return;
26562              }
26563
26564              this.manifest.targetDuration = entry.duration;
26565              setHoldBack.call(this, this.manifest);
26566            },
26567
26568            start() {
26569              if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
26570                this.trigger('warn', {
26571                  message: 'ignoring start declaration without appropriate attribute list'
26572                });
26573                return;
26574              }
26575
26576              this.manifest.start = {
26577                timeOffset: entry.attributes['TIME-OFFSET'],
26578                precise: entry.attributes.PRECISE
26579              };
26580            },
26581
26582            'cue-out'() {
26583              currentUri.cueOut = entry.data;
26584            },
26585
26586            'cue-out-cont'() {
26587              currentUri.cueOutCont = entry.data;
26588            },
26589
26590            'cue-in'() {
26591              currentUri.cueIn = entry.data;
26592            },
26593
26594            'skip'() {
26595              this.manifest.skip = camelCaseKeys(entry.attributes);
26596              this.warnOnMissingAttributes_('#EXT-X-SKIP', entry.attributes, ['SKIPPED-SEGMENTS']);
26597            },
26598
26599            'part'() {
26600              hasParts = true; // parts are always specifed before a segment
26601
26602              const segmentIndex = this.manifest.segments.length;
26603              const part = camelCaseKeys(entry.attributes);
26604              currentUri.parts = currentUri.parts || [];
26605              currentUri.parts.push(part);
26606
26607              if (part.byterange) {
26608                if (!part.byterange.hasOwnProperty('offset')) {
26609                  part.byterange.offset = lastPartByterangeEnd;
26610                }
26611
26612                lastPartByterangeEnd = part.byterange.offset + part.byterange.length;
26613              }
26614
26615              const partIndex = currentUri.parts.length - 1;
26616              this.warnOnMissingAttributes_(`#EXT-X-PART #${partIndex} for segment #${segmentIndex}`, entry.attributes, ['URI', 'DURATION']);
26617
26618              if (this.manifest.renditionReports) {
26619                this.manifest.renditionReports.forEach((r, i) => {
26620                  if (!r.hasOwnProperty('lastPart')) {
26621                    this.trigger('warn', {
26622                      message: `#EXT-X-RENDITION-REPORT #${i} lacks required attribute(s): LAST-PART`
26623                    });
26624                  }
26625                });
26626              }
26627            },
26628
26629            'server-control'() {
26630              const attrs = this.manifest.serverControl = camelCaseKeys(entry.attributes);
26631
26632              if (!attrs.hasOwnProperty('canBlockReload')) {
26633                attrs.canBlockReload = false;
26634                this.trigger('info', {
26635                  message: '#EXT-X-SERVER-CONTROL defaulting CAN-BLOCK-RELOAD to false'
26636                });
26637              }
26638
26639              setHoldBack.call(this, this.manifest);
26640
26641              if (attrs.canSkipDateranges && !attrs.hasOwnProperty('canSkipUntil')) {
26642                this.trigger('warn', {
26643                  message: '#EXT-X-SERVER-CONTROL lacks required attribute CAN-SKIP-UNTIL which is required when CAN-SKIP-DATERANGES is set'
26644                });
26645              }
26646            },
26647
26648            'preload-hint'() {
26649              // parts are always specifed before a segment
26650              const segmentIndex = this.manifest.segments.length;
26651              const hint = camelCaseKeys(entry.attributes);
26652              const isPart = hint.type && hint.type === 'PART';
26653              currentUri.preloadHints = currentUri.preloadHints || [];
26654              currentUri.preloadHints.push(hint);
26655
26656              if (hint.byterange) {
26657                if (!hint.byterange.hasOwnProperty('offset')) {
26658                  // use last part byterange end or zero if not a part.
26659                  hint.byterange.offset = isPart ? lastPartByterangeEnd : 0;
26660
26661                  if (isPart) {
26662                    lastPartByterangeEnd = hint.byterange.offset + hint.byterange.length;
26663                  }
26664                }
26665              }
26666
26667              const index = currentUri.preloadHints.length - 1;
26668              this.warnOnMissingAttributes_(`#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex}`, entry.attributes, ['TYPE', 'URI']);
26669
26670              if (!hint.type) {
26671                return;
26672              } // search through all preload hints except for the current one for
26673              // a duplicate type.
26674
26675
26676              for (let i = 0; i < currentUri.preloadHints.length - 1; i++) {
26677                const otherHint = currentUri.preloadHints[i];
26678
26679                if (!otherHint.type) {
26680                  continue;
26681                }
26682
26683                if (otherHint.type === hint.type) {
26684                  this.trigger('warn', {
26685                    message: `#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex} has the same TYPE ${hint.type} as preload hint #${i}`
26686                  });
26687                }
26688              }
26689            },
26690
26691            'rendition-report'() {
26692              const report = camelCaseKeys(entry.attributes);
26693              this.manifest.renditionReports = this.manifest.renditionReports || [];
26694              this.manifest.renditionReports.push(report);
26695              const index = this.manifest.renditionReports.length - 1;
26696              const required = ['LAST-MSN', 'URI'];
26697
26698              if (hasParts) {
26699                required.push('LAST-PART');
26700              }
26701
26702              this.warnOnMissingAttributes_(`#EXT-X-RENDITION-REPORT #${index}`, entry.attributes, required);
26703            },
26704
26705            'part-inf'() {
26706              this.manifest.partInf = camelCaseKeys(entry.attributes);
26707              this.warnOnMissingAttributes_('#EXT-X-PART-INF', entry.attributes, ['PART-TARGET']);
26708
26709              if (this.manifest.partInf.partTarget) {
26710                this.manifest.partTargetDuration = this.manifest.partInf.partTarget;
26711              }
26712
26713              setHoldBack.call(this, this.manifest);
26714            },
26715
26716            'daterange'() {
26717              this.manifest.dateRanges.push(camelCaseKeys(entry.attributes));
26718              const index = this.manifest.dateRanges.length - 1;
26719              this.warnOnMissingAttributes_(`#EXT-X-DATERANGE #${index}`, entry.attributes, ['ID', 'START-DATE']);
26720              const dateRange = this.manifest.dateRanges[index];
26721
26722              if (dateRange.endDate && dateRange.startDate && new Date(dateRange.endDate) < new Date(dateRange.startDate)) {
26723                this.trigger('warn', {
26724                  message: 'EXT-X-DATERANGE END-DATE must be equal to or later than the value of the START-DATE'
26725                });
26726              }
26727
26728              if (dateRange.duration && dateRange.duration < 0) {
26729                this.trigger('warn', {
26730                  message: 'EXT-X-DATERANGE DURATION must not be negative'
26731                });
26732              }
26733
26734              if (dateRange.plannedDuration && dateRange.plannedDuration < 0) {
26735                this.trigger('warn', {
26736                  message: 'EXT-X-DATERANGE PLANNED-DURATION must not be negative'
26737                });
26738              }
26739
26740              const endOnNextYes = !!dateRange.endOnNext;
26741
26742              if (endOnNextYes && !dateRange.class) {
26743                this.trigger('warn', {
26744                  message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must have a CLASS attribute'
26745                });
26746              }
26747
26748              if (endOnNextYes && (dateRange.duration || dateRange.endDate)) {
26749                this.trigger('warn', {
26750                  message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must not contain DURATION or END-DATE attributes'
26751                });
26752              }
26753
26754              if (dateRange.duration && dateRange.endDate) {
26755                const startDate = dateRange.startDate;
26756                const newDateInSeconds = startDate.getTime() + dateRange.duration * 1000;
26757                this.manifest.dateRanges[index].endDate = new Date(newDateInSeconds);
26758              }
26759
26760              if (!dateRangeTags[dateRange.id]) {
26761                dateRangeTags[dateRange.id] = dateRange;
26762              } else {
26763                for (const attribute in dateRangeTags[dateRange.id]) {
26764                  if (!!dateRange[attribute] && JSON.stringify(dateRangeTags[dateRange.id][attribute]) !== JSON.stringify(dateRange[attribute])) {
26765                    this.trigger('warn', {
26766                      message: 'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes values'
26767                    });
26768                    break;
26769                  }
26770                } // if tags with the same ID do not have conflicting attributes, merge them
26771
26772
26773                const dateRangeWithSameId = this.manifest.dateRanges.findIndex(dateRangeToFind => dateRangeToFind.id === dateRange.id);
26774                this.manifest.dateRanges[dateRangeWithSameId] = _extends__default["default"](this.manifest.dateRanges[dateRangeWithSameId], dateRange);
26775                dateRangeTags[dateRange.id] = _extends__default["default"](dateRangeTags[dateRange.id], dateRange); // after merging, delete the duplicate dateRange that was added last
26776
26777                this.manifest.dateRanges.pop();
26778              }
26779            },
26780
26781            'independent-segments'() {
26782              this.manifest.independentSegments = true;
26783            },
26784
26785            'i-frames-only'() {
26786              this.manifest.iFramesOnly = true;
26787              this.requiredCompatibilityversion(this.manifest.version, 4);
26788            },
26789
26790            'content-steering'() {
26791              this.manifest.contentSteering = camelCaseKeys(entry.attributes);
26792              this.warnOnMissingAttributes_('#EXT-X-CONTENT-STEERING', entry.attributes, ['SERVER-URI']);
26793            },
26794
26795            /** @this {Parser} */
26796            define() {
26797              this.manifest.definitions = this.manifest.definitions || {};
26798
26799              const addDef = (n, v) => {
26800                if (n in this.manifest.definitions) {
26801                  // An EXT-X-DEFINE tag MUST NOT specify the same Variable Name as any other
26802                  // EXT-X-DEFINE tag in the same Playlist.  Parsers that encounter duplicate
26803                  // Variable Name declarations MUST fail to parse the Playlist.
26804                  this.trigger('error', {
26805                    message: `EXT-X-DEFINE: Duplicate name ${n}`
26806                  });
26807                  return;
26808                }
26809
26810                this.manifest.definitions[n] = v;
26811              };
26812
26813              if ('QUERYPARAM' in entry.attributes) {
26814                if ('NAME' in entry.attributes || 'IMPORT' in entry.attributes) {
26815                  // An EXT-X-DEFINE tag MUST contain either a NAME, an IMPORT, or a
26816                  // QUERYPARAM attribute, but only one of the three.  Otherwise, the
26817                  // client MUST fail to parse the Playlist.
26818                  this.trigger('error', {
26819                    message: 'EXT-X-DEFINE: Invalid attributes'
26820                  });
26821                  return;
26822                }
26823
26824                const val = this.params.get(entry.attributes.QUERYPARAM);
26825
26826                if (!val) {
26827                  // If the QUERYPARAM attribute value does not match any query parameter in
26828                  // the URI or the matching parameter has no associated value, the parser
26829                  // MUST fail to parse the Playlist.  If more than one parameter matches,
26830                  // any of the associated values MAY be used.
26831                  this.trigger('error', {
26832                    message: `EXT-X-DEFINE: No query param ${entry.attributes.QUERYPARAM}`
26833                  });
26834                  return;
26835                }
26836
26837                addDef(entry.attributes.QUERYPARAM, decodeURIComponent(val));
26838                return;
26839              }
26840
26841              if ('NAME' in entry.attributes) {
26842                if ('IMPORT' in entry.attributes) {
26843                  // An EXT-X-DEFINE tag MUST contain either a NAME, an IMPORT, or a
26844                  // QUERYPARAM attribute, but only one of the three.  Otherwise, the
26845                  // client MUST fail to parse the Playlist.
26846                  this.trigger('error', {
26847                    message: 'EXT-X-DEFINE: Invalid attributes'
26848                  });
26849                  return;
26850                }
26851
26852                if (!('VALUE' in entry.attributes) || typeof entry.attributes.VALUE !== 'string') {
26853                  // This attribute is REQUIRED if the EXT-X-DEFINE tag has a NAME attribute.
26854                  // The quoted-string MAY be empty.
26855                  this.trigger('error', {
26856                    message: `EXT-X-DEFINE: No value for ${entry.attributes.NAME}`
26857                  });
26858                  return;
26859                }
26860
26861                addDef(entry.attributes.NAME, entry.attributes.VALUE);
26862                return;
26863              }
26864
26865              if ('IMPORT' in entry.attributes) {
26866                if (!this.mainDefinitions[entry.attributes.IMPORT]) {
26867                  // Covers two conditions, as mainDefinitions will always be empty on main
26868                  //
26869                  // EXT-X-DEFINE tags containing the IMPORT attribute MUST NOT occur in
26870                  // Multivariant Playlists; they are only allowed in Media Playlists.
26871                  //
26872                  // If the IMPORT attribute value does not match any Variable Name in the
26873                  // Multivariant Playlist, or if the Media Playlist loaded from a
26874                  // Multivariant Playlist, the parser MUST fail the Playlist.
26875                  this.trigger('error', {
26876                    message: `EXT-X-DEFINE: No value ${entry.attributes.IMPORT} to import, or IMPORT used on main playlist`
26877                  });
26878                  return;
26879                }
26880
26881                addDef(entry.attributes.IMPORT, this.mainDefinitions[entry.attributes.IMPORT]);
26882                return;
26883              } // An EXT-X-DEFINE tag MUST contain either a NAME, an IMPORT, or a QUERYPARAM
26884              // attribute, but only one of the three.  Otherwise, the client MUST fail to
26885              // parse the Playlist.
26886
26887
26888              this.trigger('error', {
26889                message: 'EXT-X-DEFINE: No attribute'
26890              });
26891            },
26892
26893            'i-frame-playlist'() {
26894              this.manifest.iFramePlaylists.push({
26895                attributes: entry.attributes,
26896                uri: entry.uri,
26897                timeline: currentTimeline
26898              });
26899              this.warnOnMissingAttributes_('#EXT-X-I-FRAME-STREAM-INF', entry.attributes, ['BANDWIDTH', 'URI']);
26900            }
26901
26902          })[entry.tagType] || noop).call(self);
26903        },
26904
26905        uri() {
26906          currentUri.uri = entry.uri;
26907          uris.push(currentUri); // if no explicit duration was declared, use the target duration
26908
26909          if (this.manifest.targetDuration && !('duration' in currentUri)) {
26910            this.trigger('warn', {
26911              message: 'defaulting segment duration to the target duration'
26912            });
26913            currentUri.duration = this.manifest.targetDuration;
26914          } // annotate with encryption information, if necessary
26915
26916
26917          if (key) {
26918            currentUri.key = key;
26919          }
26920
26921          currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary
26922
26923          if (currentMap) {
26924            currentUri.map = currentMap;
26925          } // reset the last byterange end as it needs to be 0 between parts
26926
26927
26928          lastPartByterangeEnd = 0; // Once we have at least one program date time we can always extrapolate it forward
26929
26930          if (this.lastProgramDateTime !== null) {
26931            currentUri.programDateTime = this.lastProgramDateTime;
26932            this.lastProgramDateTime += currentUri.duration * 1000;
26933          } // prepare for the next URI
26934
26935
26936          currentUri = {};
26937        },
26938
26939        comment() {// comments are not important for playback
26940        },
26941
26942        custom() {
26943          // if this is segment-level data attach the output to the segment
26944          if (entry.segment) {
26945            currentUri.custom = currentUri.custom || {};
26946            currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object
26947          } else {
26948            this.manifest.custom = this.manifest.custom || {};
26949            this.manifest.custom[entry.customType] = entry.data;
26950          }
26951        }
26952
26953      })[entry.type].call(self);
26954    });
26955  }
26956
26957  requiredCompatibilityversion(currentVersion, targetVersion) {
26958    if (currentVersion < targetVersion || !currentVersion) {
26959      this.trigger('warn', {
26960        message: `manifest must be at least version ${targetVersion}`
26961      });
26962    }
26963  }
26964
26965  warnOnMissingAttributes_(identifier, attributes, required) {
26966    const missing = [];
26967    required.forEach(function (key) {
26968      if (!attributes.hasOwnProperty(key)) {
26969        missing.push(key);
26970      }
26971    });
26972
26973    if (missing.length) {
26974      this.trigger('warn', {
26975        message: `${identifier} lacks required attribute(s): ${missing.join(', ')}`
26976      });
26977    }
26978  }
26979  /**
26980   * Parse the input string and update the manifest object.
26981   *
26982   * @param {string} chunk a potentially incomplete portion of the manifest
26983   */
26984
26985
26986  push(chunk) {
26987    this.lineStream.push(chunk);
26988  }
26989  /**
26990   * Flush any remaining input. This can be handy if the last line of an M3U8
26991   * manifest did not contain a trailing newline but the file has been
26992   * completely received.
26993   */
26994
26995
26996  end() {
26997    // flush any buffered input
26998    this.lineStream.push('\n');
26999
27000    if (this.manifest.dateRanges.length && this.lastProgramDateTime === null) {
27001      this.trigger('warn', {
27002        message: 'A playlist with EXT-X-DATERANGE tag must contain atleast one EXT-X-PROGRAM-DATE-TIME tag'
27003      });
27004    }
27005
27006    this.lastProgramDateTime = null;
27007    this.trigger('end');
27008  }
27009  /**
27010   * Add an additional parser for non-standard tags
27011   *
27012   * @param {Object}   options              a map of options for the added parser
27013   * @param {RegExp}   options.expression   a regular expression to match the custom header
27014   * @param {string}   options.customType   the custom type to register to the output
27015   * @param {Function} [options.dataParser] function to parse the line into an object
27016   * @param {boolean}  [options.segment]    should tag data be attached to the segment object
27017   */
27018
27019
27020  addParser(options) {
27021    this.parseStream.addParser(options);
27022  }
27023  /**
27024   * Add a custom header mapper
27025   *
27026   * @param {Object}   options
27027   * @param {RegExp}   options.expression   a regular expression to match the custom header
27028   * @param {Function} options.map          function to translate tag into a different tag
27029   */
27030
27031
27032  addTagMapper(options) {
27033    this.parseStream.addTagMapper(options);
27034  }
27035
27036}
27037
27038exports.LineStream = LineStream;
27039exports.ParseStream = ParseStream;
27040exports.Parser = Parser;
27041
27042},{"@babel/runtime/helpers/extends":9,"@videojs/vhs-utils/cjs/decode-b64-to-uint8-array.js":15,"@videojs/vhs-utils/cjs/stream.js":24}],45:[function(require,module,exports){
vendor: 5,440 bytes, lines 27043-27218
27043/*! @name mpd-parser @version 1.4.0 @license Apache-2.0 */
27044'use strict';
27045
27046Object.defineProperty(exports, '__esModule', { value: true });
27047
27048var resolveUrl = require('@videojs/vhs-utils/cjs/resolve-url');
27049var window = require('global/window');
27050var mediaGroups = require('@videojs/vhs-utils/cjs/media-groups');
27051var decodeB64ToUint8Array = require('@videojs/vhs-utils/cjs/decode-b64-to-uint8-array');
27052var xmldom = require('@xmldom/xmldom');
27053
27054function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
27055
27056var resolveUrl__default = /*#__PURE__*/_interopDefaultLegacy(resolveUrl);
27057var window__default = /*#__PURE__*/_interopDefaultLegacy(window);
27058var decodeB64ToUint8Array__default = /*#__PURE__*/_interopDefaultLegacy(decodeB64ToUint8Array);
27059
27060var version = "1.4.0";
27061
27062const isObject = obj => {
27063  return !!obj && typeof obj === 'object';
27064};
27065
27066const merge = (...objects) => {
27067  return objects.reduce((result, source) => {
27068    if (typeof source !== 'object') {
27069      return result;
27070    }
27071
27072    Object.keys(source).forEach(key => {
27073      if (Array.isArray(result[key]) && Array.isArray(source[key])) {
27074        result[key] = result[key].concat(source[key]);
27075      } else if (isObject(result[key]) && isObject(source[key])) {
27076        result[key] = merge(result[key], source[key]);
27077      } else {
27078        result[key] = source[key];
27079      }
27080    });
27081    return result;
27082  }, {});
27083};
27084const values = o => Object.keys(o).map(k => o[k]);
27085
27086const range = (start, end) => {
27087  const result = [];
27088
27089  for (let i = start; i < end; i++) {
27090    result.push(i);
27091  }
27092
27093  return result;
27094};
27095const flatten = lists => lists.reduce((x, y) => x.concat(y), []);
27096const from = list => {
27097  if (!list.length) {
27098    return [];
27099  }
27100
27101  const result = [];
27102
27103  for (let i = 0; i < list.length; i++) {
27104    result.push(list[i]);
27105  }
27106
27107  return result;
27108};
27109const findIndexes = (l, key) => l.reduce((a, e, i) => {
27110  if (e[key]) {
27111    a.push(i);
27112  }
27113
27114  return a;
27115}, []);
27116/**
27117 * Returns a union of the included lists provided each element can be identified by a key.
27118 *
27119 * @param {Array} list - list of lists to get the union of
27120 * @param {Function} keyFunction - the function to use as a key for each element
27121 *
27122 * @return {Array} the union of the arrays
27123 */
27124
27125const union = (lists, keyFunction) => {
27126  return values(lists.reduce((acc, list) => {
27127    list.forEach(el => {
27128      acc[keyFunction(el)] = el;
27129    });
27130    return acc;
27131  }, {}));
27132};
27133
27134var errors = {
27135  INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
27136  INVALID_NUMBER_OF_CONTENT_STEERING: 'INVALID_NUMBER_OF_CONTENT_STEERING',
27137  DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
27138  DASH_INVALID_XML: 'DASH_INVALID_XML',
27139  NO_BASE_URL: 'NO_BASE_URL',
27140  MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
27141  SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED',
27142  UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME'
27143};
27144
27145/**
27146 * @typedef {Object} SingleUri
27147 * @property {string} uri - relative location of segment
27148 * @property {string} resolvedUri - resolved location of segment
27149 * @property {Object} byterange - Object containing information on how to make byte range
27150 *   requests following byte-range-spec per RFC2616.
27151 * @property {String} byterange.length - length of range request
27152 * @property {String} byterange.offset - byte offset of range request
27153 *
27154 * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
27155 */
27156
27157/**
27158 * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object
27159 * that conforms to how m3u8-parser is structured
27160 *
27161 * @see https://github.com/videojs/m3u8-parser
27162 *
27163 * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes
27164 * @param {string} source - source url for segment
27165 * @param {string} range - optional range used for range calls,
27166 *   follows  RFC 2616, Clause 14.35.1
27167 * @return {SingleUri} full segment information transformed into a format similar
27168 *   to m3u8-parser
27169 */
27170
27171const urlTypeToSegment = ({
27172  baseUrl = '',
27173  source = '',
27174  range = '',
27175  indexRange = ''
27176}) => {
27177  const segment = {
27178    uri: source,
27179    resolvedUri: resolveUrl__default['default'](baseUrl || '', source)
27180  };
27181
27182  if (range || indexRange) {
27183    const rangeStr = range ? range : indexRange;
27184    const ranges = rangeStr.split('-'); // default to parsing this as a BigInt if possible
27185
27186    let startRange = window__default['default'].BigInt ? window__default['default'].BigInt(ranges[0]) : parseInt(ranges[0], 10);
27187    let endRange = window__default['default'].BigInt ? window__default['default'].BigInt(ranges[1]) : parseInt(ranges[1], 10); // convert back to a number if less than MAX_SAFE_INTEGER
27188
27189    if (startRange < Number.MAX_SAFE_INTEGER && typeof startRange === 'bigint') {
27190      startRange = Number(startRange);
27191    }
27192
27193    if (endRange < Number.MAX_SAFE_INTEGER && typeof endRange === 'bigint') {
27194      endRange = Number(endRange);
27195    }
27196
27197    let length;
27198
27199    if (typeof endRange === 'bigint' || typeof startRange === 'bigint') {
27200      length = window__default['default'].BigInt(endRange) - window__default['default'].BigInt(startRange) + window__default['default'].BigInt(1);
27201    } else {
27202      length = endRange - startRange + 1;
27203    }
27204
27205    if (typeof length === 'bigint' && length < Number.MAX_SAFE_INTEGER) {
27206      length = Number(length);
27207    } // byterange should be inclusive according to
27208    // RFC 2616, Clause 14.35.1
27209
27210
27211    segment.byterange = {
27212      length,
27213      offset: startRange
27214    };
27215  }
27216
27217  return segment;
27218};
vendor: 52,189 bytes, lines 27219-28808
27219const byteRangeToString = byterange => {
27220  // `endRange` is one less than `offset + length` because the HTTP range
27221  // header uses inclusive ranges
27222  let endRange;
27223
27224  if (typeof byterange.offset === 'bigint' || typeof byterange.length === 'bigint') {
27225    endRange = window__default['default'].BigInt(byterange.offset) + window__default['default'].BigInt(byterange.length) - window__default['default'].BigInt(1);
27226  } else {
27227    endRange = byterange.offset + byterange.length - 1;
27228  }
27229
27230  return `${byterange.offset}-${endRange}`;
27231};
27232
27233/**
27234 * parse the end number attribue that can be a string
27235 * number, or undefined.
27236 *
27237 * @param {string|number|undefined} endNumber
27238 *        The end number attribute.
27239 *
27240 * @return {number|null}
27241 *          The result of parsing the end number.
27242 */
27243
27244const parseEndNumber = endNumber => {
27245  if (endNumber && typeof endNumber !== 'number') {
27246    endNumber = parseInt(endNumber, 10);
27247  }
27248
27249  if (isNaN(endNumber)) {
27250    return null;
27251  }
27252
27253  return endNumber;
27254};
27255/**
27256 * Functions for calculating the range of available segments in static and dynamic
27257 * manifests.
27258 */
27259
27260
27261const segmentRange = {
27262  /**
27263   * Returns the entire range of available segments for a static MPD
27264   *
27265   * @param {Object} attributes
27266   *        Inheritied MPD attributes
27267   * @return {{ start: number, end: number }}
27268   *         The start and end numbers for available segments
27269   */
27270  static(attributes) {
27271    const {
27272      duration,
27273      timescale = 1,
27274      sourceDuration,
27275      periodDuration
27276    } = attributes;
27277    const endNumber = parseEndNumber(attributes.endNumber);
27278    const segmentDuration = duration / timescale;
27279
27280    if (typeof endNumber === 'number') {
27281      return {
27282        start: 0,
27283        end: endNumber
27284      };
27285    }
27286
27287    if (typeof periodDuration === 'number') {
27288      return {
27289        start: 0,
27290        end: periodDuration / segmentDuration
27291      };
27292    }
27293
27294    return {
27295      start: 0,
27296      end: sourceDuration / segmentDuration
27297    };
27298  },
27299
27300  /**
27301   * Returns the current live window range of available segments for a dynamic MPD
27302   *
27303   * @param {Object} attributes
27304   *        Inheritied MPD attributes
27305   * @return {{ start: number, end: number }}
27306   *         The start and end numbers for available segments
27307   */
27308  dynamic(attributes) {
27309    const {
27310      NOW,
27311      clientOffset,
27312      availabilityStartTime,
27313      timescale = 1,
27314      duration,
27315      periodStart = 0,
27316      minimumUpdatePeriod = 0,
27317      timeShiftBufferDepth = Infinity
27318    } = attributes;
27319    const endNumber = parseEndNumber(attributes.endNumber); // clientOffset is passed in at the top level of mpd-parser and is an offset calculated
27320    // after retrieving UTC server time.
27321
27322    const now = (NOW + clientOffset) / 1000; // WC stands for Wall Clock.
27323    // Convert the period start time to EPOCH.
27324
27325    const periodStartWC = availabilityStartTime + periodStart; // Period end in EPOCH is manifest's retrieval time + time until next update.
27326
27327    const periodEndWC = now + minimumUpdatePeriod;
27328    const periodDuration = periodEndWC - periodStartWC;
27329    const segmentCount = Math.ceil(periodDuration * timescale / duration);
27330    const availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration);
27331    const availableEnd = Math.floor((now - periodStartWC) * timescale / duration);
27332    return {
27333      start: Math.max(0, availableStart),
27334      end: typeof endNumber === 'number' ? endNumber : Math.min(segmentCount, availableEnd)
27335    };
27336  }
27337
27338};
27339/**
27340 * Maps a range of numbers to objects with information needed to build the corresponding
27341 * segment list
27342 *
27343 * @name toSegmentsCallback
27344 * @function
27345 * @param {number} number
27346 *        Number of the segment
27347 * @param {number} index
27348 *        Index of the number in the range list
27349 * @return {{ number: Number, duration: Number, timeline: Number, time: Number }}
27350 *         Object with segment timing and duration info
27351 */
27352
27353/**
27354 * Returns a callback for Array.prototype.map for mapping a range of numbers to
27355 * information needed to build the segment list.
27356 *
27357 * @param {Object} attributes
27358 *        Inherited MPD attributes
27359 * @return {toSegmentsCallback}
27360 *         Callback map function
27361 */
27362
27363const toSegments = attributes => number => {
27364  const {
27365    duration,
27366    timescale = 1,
27367    periodStart,
27368    startNumber = 1
27369  } = attributes;
27370  return {
27371    number: startNumber + number,
27372    duration: duration / timescale,
27373    timeline: periodStart,
27374    time: number * duration
27375  };
27376};
27377/**
27378 * Returns a list of objects containing segment timing and duration info used for
27379 * building the list of segments. This uses the @duration attribute specified
27380 * in the MPD manifest to derive the range of segments.
27381 *
27382 * @param {Object} attributes
27383 *        Inherited MPD attributes
27384 * @return {{number: number, duration: number, time: number, timeline: number}[]}
27385 *         List of Objects with segment timing and duration info
27386 */
27387
27388const parseByDuration = attributes => {
27389  const {
27390    type,
27391    duration,
27392    timescale = 1,
27393    periodDuration,
27394    sourceDuration
27395  } = attributes;
27396  const {
27397    start,
27398    end
27399  } = segmentRange[type](attributes);
27400  const segments = range(start, end).map(toSegments(attributes));
27401
27402  if (type === 'static') {
27403    const index = segments.length - 1; // section is either a period or the full source
27404
27405    const sectionDuration = typeof periodDuration === 'number' ? periodDuration : sourceDuration; // final segment may be less than full segment duration
27406
27407    segments[index].duration = sectionDuration - duration / timescale * index;
27408  }
27409
27410  return segments;
27411};
27412
27413/**
27414 * Translates SegmentBase into a set of segments.
27415 * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
27416 * node should be translated into segment.
27417 *
27418 * @param {Object} attributes
27419 *   Object containing all inherited attributes from parent elements with attribute
27420 *   names as keys
27421 * @return {Object.<Array>} list of segments
27422 */
27423
27424const segmentsFromBase = attributes => {
27425  const {
27426    baseUrl,
27427    initialization = {},
27428    sourceDuration,
27429    indexRange = '',
27430    periodStart,
27431    presentationTime,
27432    number = 0,
27433    duration
27434  } = attributes; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
27435
27436  if (!baseUrl) {
27437    throw new Error(errors.NO_BASE_URL);
27438  }
27439
27440  const initSegment = urlTypeToSegment({
27441    baseUrl,
27442    source: initialization.sourceURL,
27443    range: initialization.range
27444  });
27445  const segment = urlTypeToSegment({
27446    baseUrl,
27447    source: baseUrl,
27448    indexRange
27449  });
27450  segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source
27451  // (since SegmentBase is only for one total segment)
27452
27453  if (duration) {
27454    const segmentTimeInfo = parseByDuration(attributes);
27455
27456    if (segmentTimeInfo.length) {
27457      segment.duration = segmentTimeInfo[0].duration;
27458      segment.timeline = segmentTimeInfo[0].timeline;
27459    }
27460  } else if (sourceDuration) {
27461    segment.duration = sourceDuration;
27462    segment.timeline = periodStart;
27463  } // If presentation time is provided, these segments are being generated by SIDX
27464  // references, and should use the time provided. For the general case of SegmentBase,
27465  // there should only be one segment in the period, so its presentation time is the same
27466  // as its period start.
27467
27468
27469  segment.presentationTime = presentationTime || periodStart;
27470  segment.number = number;
27471  return [segment];
27472};
27473/**
27474 * Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist
27475 * according to the sidx information given.
27476 *
27477 * playlist.sidx has metadadata about the sidx where-as the sidx param
27478 * is the parsed sidx box itself.
27479 *
27480 * @param {Object} playlist the playlist to update the sidx information for
27481 * @param {Object} sidx the parsed sidx box
27482 * @return {Object} the playlist object with the updated sidx information
27483 */
27484
27485const addSidxSegmentsToPlaylist$1 = (playlist, sidx, baseUrl) => {
27486  // Retain init segment information
27487  const initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial main manifest parsing
27488
27489  const sourceDuration = playlist.sidx.duration; // Retain source timeline
27490
27491  const timeline = playlist.timeline || 0;
27492  const sidxByteRange = playlist.sidx.byterange;
27493  const sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx
27494
27495  const timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes
27496
27497  const mediaReferences = sidx.references.filter(r => r.referenceType !== 1);
27498  const segments = [];
27499  const type = playlist.endList ? 'static' : 'dynamic';
27500  const periodStart = playlist.sidx.timeline;
27501  let presentationTime = periodStart;
27502  let number = playlist.mediaSequence || 0; // firstOffset is the offset from the end of the sidx box
27503
27504  let startIndex; // eslint-disable-next-line
27505
27506  if (typeof sidx.firstOffset === 'bigint') {
27507    startIndex = window__default['default'].BigInt(sidxEnd) + sidx.firstOffset;
27508  } else {
27509    startIndex = sidxEnd + sidx.firstOffset;
27510  }
27511
27512  for (let i = 0; i < mediaReferences.length; i++) {
27513    const reference = sidx.references[i]; // size of the referenced (sub)segment
27514
27515    const size = reference.referencedSize; // duration of the referenced (sub)segment, in  the  timescale
27516    // this will be converted to seconds when generating segments
27517
27518    const duration = reference.subsegmentDuration; // should be an inclusive range
27519
27520    let endIndex; // eslint-disable-next-line
27521
27522    if (typeof startIndex === 'bigint') {
27523      endIndex = startIndex + window__default['default'].BigInt(size) - window__default['default'].BigInt(1);
27524    } else {
27525      endIndex = startIndex + size - 1;
27526    }
27527
27528    const indexRange = `${startIndex}-${endIndex}`;
27529    const attributes = {
27530      baseUrl,
27531      timescale,
27532      timeline,
27533      periodStart,
27534      presentationTime,
27535      number,
27536      duration,
27537      sourceDuration,
27538      indexRange,
27539      type
27540    };
27541    const segment = segmentsFromBase(attributes)[0];
27542
27543    if (initSegment) {
27544      segment.map = initSegment;
27545    }
27546
27547    segments.push(segment);
27548
27549    if (typeof startIndex === 'bigint') {
27550      startIndex += window__default['default'].BigInt(size);
27551    } else {
27552      startIndex += size;
27553    }
27554
27555    presentationTime += duration / timescale;
27556    number++;
27557  }
27558
27559  playlist.segments = segments;
27560  return playlist;
27561};
27562
27563const SUPPORTED_MEDIA_TYPES = ['AUDIO', 'SUBTITLES']; // allow one 60fps frame as leniency (arbitrarily chosen)
27564
27565const TIME_FUDGE = 1 / 60;
27566/**
27567 * Given a list of timelineStarts, combines, dedupes, and sorts them.
27568 *
27569 * @param {TimelineStart[]} timelineStarts - list of timeline starts
27570 *
27571 * @return {TimelineStart[]} the combined and deduped timeline starts
27572 */
27573
27574const getUniqueTimelineStarts = timelineStarts => {
27575  return union(timelineStarts, ({
27576    timeline
27577  }) => timeline).sort((a, b) => a.timeline > b.timeline ? 1 : -1);
27578};
27579/**
27580 * Finds the playlist with the matching NAME attribute.
27581 *
27582 * @param {Array} playlists - playlists to search through
27583 * @param {string} name - the NAME attribute to search for
27584 *
27585 * @return {Object|null} the matching playlist object, or null
27586 */
27587
27588const findPlaylistWithName = (playlists, name) => {
27589  for (let i = 0; i < playlists.length; i++) {
27590    if (playlists[i].attributes.NAME === name) {
27591      return playlists[i];
27592    }
27593  }
27594
27595  return null;
27596};
27597/**
27598 * Gets a flattened array of media group playlists.
27599 *
27600 * @param {Object} manifest - the main manifest object
27601 *
27602 * @return {Array} the media group playlists
27603 */
27604
27605const getMediaGroupPlaylists = manifest => {
27606  let mediaGroupPlaylists = [];
27607  mediaGroups.forEachMediaGroup(manifest, SUPPORTED_MEDIA_TYPES, (properties, type, group, label) => {
27608    mediaGroupPlaylists = mediaGroupPlaylists.concat(properties.playlists || []);
27609  });
27610  return mediaGroupPlaylists;
27611};
27612/**
27613 * Updates the playlist's media sequence numbers.
27614 *
27615 * @param {Object} config - options object
27616 * @param {Object} config.playlist - the playlist to update
27617 * @param {number} config.mediaSequence - the mediaSequence number to start with
27618 */
27619
27620const updateMediaSequenceForPlaylist = ({
27621  playlist,
27622  mediaSequence
27623}) => {
27624  playlist.mediaSequence = mediaSequence;
27625  playlist.segments.forEach((segment, index) => {
27626    segment.number = playlist.mediaSequence + index;
27627  });
27628};
27629/**
27630 * Updates the media and discontinuity sequence numbers of newPlaylists given oldPlaylists
27631 * and a complete list of timeline starts.
27632 *
27633 * If no matching playlist is found, only the discontinuity sequence number of the playlist
27634 * will be updated.
27635 *
27636 * Since early available timelines are not supported, at least one segment must be present.
27637 *
27638 * @param {Object} config - options object
27639 * @param {Object[]} oldPlaylists - the old playlists to use as a reference
27640 * @param {Object[]} newPlaylists - the new playlists to update
27641 * @param {Object} timelineStarts - all timelineStarts seen in the stream to this point
27642 */
27643
27644const updateSequenceNumbers = ({
27645  oldPlaylists,
27646  newPlaylists,
27647  timelineStarts
27648}) => {
27649  newPlaylists.forEach(playlist => {
27650    playlist.discontinuitySequence = timelineStarts.findIndex(function ({
27651      timeline
27652    }) {
27653      return timeline === playlist.timeline;
27654    }); // Playlists NAMEs come from DASH Representation IDs, which are mandatory
27655    // (see ISO_23009-1-2012 5.3.5.2).
27656    //
27657    // If the same Representation existed in a prior Period, it will retain the same NAME.
27658
27659    const oldPlaylist = findPlaylistWithName(oldPlaylists, playlist.attributes.NAME);
27660
27661    if (!oldPlaylist) {
27662      // Since this is a new playlist, the media sequence values can start from 0 without
27663      // consequence.
27664      return;
27665    } // TODO better support for live SIDX
27666    //
27667    // As of this writing, mpd-parser does not support multiperiod SIDX (in live or VOD).
27668    // This is evident by a playlist only having a single SIDX reference. In a multiperiod
27669    // playlist there would need to be multiple SIDX references. In addition, live SIDX is
27670    // not supported when the SIDX properties change on refreshes.
27671    //
27672    // In the future, if support needs to be added, the merging logic here can be called
27673    // after SIDX references are resolved. For now, exit early to prevent exceptions being
27674    // thrown due to undefined references.
27675
27676
27677    if (playlist.sidx) {
27678      return;
27679    } // Since we don't yet support early available timelines, we don't need to support
27680    // playlists with no segments.
27681
27682
27683    const firstNewSegment = playlist.segments[0];
27684    const oldMatchingSegmentIndex = oldPlaylist.segments.findIndex(function (oldSegment) {
27685      return Math.abs(oldSegment.presentationTime - firstNewSegment.presentationTime) < TIME_FUDGE;
27686    }); // No matching segment from the old playlist means the entire playlist was refreshed.
27687    // In this case the media sequence should account for this update, and the new segments
27688    // should be marked as discontinuous from the prior content, since the last prior
27689    // timeline was removed.
27690
27691    if (oldMatchingSegmentIndex === -1) {
27692      updateMediaSequenceForPlaylist({
27693        playlist,
27694        mediaSequence: oldPlaylist.mediaSequence + oldPlaylist.segments.length
27695      });
27696      playlist.segments[0].discontinuity = true;
27697      playlist.discontinuityStarts.unshift(0); // No matching segment does not necessarily mean there's missing content.
27698      //
27699      // If the new playlist's timeline is the same as the last seen segment's timeline,
27700      // then a discontinuity can be added to identify that there's potentially missing
27701      // content. If there's no missing content, the discontinuity should still be rather
27702      // harmless. It's possible that if segment durations are accurate enough, that the
27703      // existence of a gap can be determined using the presentation times and durations,
27704      // but if the segment timing info is off, it may introduce more problems than simply
27705      // adding the discontinuity.
27706      //
27707      // If the new playlist's timeline is different from the last seen segment's timeline,
27708      // then a discontinuity can be added to identify that this is the first seen segment
27709      // of a new timeline. However, the logic at the start of this function that
27710      // determined the disconinuity sequence by timeline index is now off by one (the
27711      // discontinuity of the newest timeline hasn't yet fallen off the manifest...since
27712      // we added it), so the disconinuity sequence must be decremented.
27713      //
27714      // A period may also have a duration of zero, so the case of no segments is handled
27715      // here even though we don't yet support early available periods.
27716
27717      if (!oldPlaylist.segments.length && playlist.timeline > oldPlaylist.timeline || oldPlaylist.segments.length && playlist.timeline > oldPlaylist.segments[oldPlaylist.segments.length - 1].timeline) {
27718        playlist.discontinuitySequence--;
27719      }
27720
27721      return;
27722    } // If the first segment matched with a prior segment on a discontinuity (it's matching
27723    // on the first segment of a period), then the discontinuitySequence shouldn't be the
27724    // timeline's matching one, but instead should be the one prior, and the first segment
27725    // of the new manifest should be marked with a discontinuity.
27726    //
27727    // The reason for this special case is that discontinuity sequence shows how many
27728    // discontinuities have fallen off of the playlist, and discontinuities are marked on
27729    // the first segment of a new "timeline." Because of this, while DASH will retain that
27730    // Period while the "timeline" exists, HLS keeps track of it via the discontinuity
27731    // sequence, and that first segment is an indicator, but can be removed before that
27732    // timeline is gone.
27733
27734
27735    const oldMatchingSegment = oldPlaylist.segments[oldMatchingSegmentIndex];
27736
27737    if (oldMatchingSegment.discontinuity && !firstNewSegment.discontinuity) {
27738      firstNewSegment.discontinuity = true;
27739      playlist.discontinuityStarts.unshift(0);
27740      playlist.discontinuitySequence--;
27741    }
27742
27743    updateMediaSequenceForPlaylist({
27744      playlist,
27745      mediaSequence: oldPlaylist.segments[oldMatchingSegmentIndex].number
27746    });
27747  });
27748};
27749/**
27750 * Given an old parsed manifest object and a new parsed manifest object, updates the
27751 * sequence and timing values within the new manifest to ensure that it lines up with the
27752 * old.
27753 *
27754 * @param {Array} oldManifest - the old main manifest object
27755 * @param {Array} newManifest - the new main manifest object
27756 *
27757 * @return {Object} the updated new manifest object
27758 */
27759
27760const positionManifestOnTimeline = ({
27761  oldManifest,
27762  newManifest
27763}) => {
27764  // Starting from v4.1.2 of the IOP, section 4.4.3.3 states:
27765  //
27766  // "MPD@availabilityStartTime and Period@start shall not be changed over MPD updates."
27767  //
27768  // This was added from https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/160
27769  //
27770  // Because of this change, and the difficulty of supporting periods with changing start
27771  // times, periods with changing start times are not supported. This makes the logic much
27772  // simpler, since periods with the same start time can be considerred the same period
27773  // across refreshes.
27774  //
27775  // To give an example as to the difficulty of handling periods where the start time may
27776  // change, if a single period manifest is refreshed with another manifest with a single
27777  // period, and both the start and end times are increased, then the only way to determine
27778  // if it's a new period or an old one that has changed is to look through the segments of
27779  // each playlist and determine the presentation time bounds to find a match. In addition,
27780  // if the period start changed to exceed the old period end, then there would be no
27781  // match, and it would not be possible to determine whether the refreshed period is a new
27782  // one or the old one.
27783  const oldPlaylists = oldManifest.playlists.concat(getMediaGroupPlaylists(oldManifest));
27784  const newPlaylists = newManifest.playlists.concat(getMediaGroupPlaylists(newManifest)); // Save all seen timelineStarts to the new manifest. Although this potentially means that
27785  // there's a "memory leak" in that it will never stop growing, in reality, only a couple
27786  // of properties are saved for each seen Period. Even long running live streams won't
27787  // generate too many Periods, unless the stream is watched for decades. In the future,
27788  // this can be optimized by mapping to discontinuity sequence numbers for each timeline,
27789  // but it may not become an issue, and the additional info can be useful for debugging.
27790
27791  newManifest.timelineStarts = getUniqueTimelineStarts([oldManifest.timelineStarts, newManifest.timelineStarts]);
27792  updateSequenceNumbers({
27793    oldPlaylists,
27794    newPlaylists,
27795    timelineStarts: newManifest.timelineStarts
27796  });
27797  return newManifest;
27798};
27799
27800const generateSidxKey = sidx => sidx && sidx.uri + '-' + byteRangeToString(sidx.byterange);
27801
27802const mergeDiscontiguousPlaylists = playlists => {
27803  // Break out playlists into groups based on their baseUrl
27804  const playlistsByBaseUrl = playlists.reduce(function (acc, cur) {
27805    if (!acc[cur.attributes.baseUrl]) {
27806      acc[cur.attributes.baseUrl] = [];
27807    }
27808
27809    acc[cur.attributes.baseUrl].push(cur);
27810    return acc;
27811  }, {});
27812  let allPlaylists = [];
27813  Object.values(playlistsByBaseUrl).forEach(playlistGroup => {
27814    const mergedPlaylists = values(playlistGroup.reduce((acc, playlist) => {
27815      // assuming playlist IDs are the same across periods
27816      // TODO: handle multiperiod where representation sets are not the same
27817      // across periods
27818      const name = playlist.attributes.id + (playlist.attributes.lang || '');
27819
27820      if (!acc[name]) {
27821        // First Period
27822        acc[name] = playlist;
27823        acc[name].attributes.timelineStarts = [];
27824      } else {
27825        // Subsequent Periods
27826        if (playlist.segments) {
27827          // first segment of subsequent periods signal a discontinuity
27828          if (playlist.segments[0]) {
27829            playlist.segments[0].discontinuity = true;
27830          }
27831
27832          acc[name].segments.push(...playlist.segments);
27833        } // bubble up contentProtection, this assumes all DRM content
27834        // has the same contentProtection
27835
27836
27837        if (playlist.attributes.contentProtection) {
27838          acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
27839        }
27840      }
27841
27842      acc[name].attributes.timelineStarts.push({
27843        // Although they represent the same number, it's important to have both to make it
27844        // compatible with HLS potentially having a similar attribute.
27845        start: playlist.attributes.periodStart,
27846        timeline: playlist.attributes.periodStart
27847      });
27848      return acc;
27849    }, {}));
27850    allPlaylists = allPlaylists.concat(mergedPlaylists);
27851  });
27852  return allPlaylists.map(playlist => {
27853    playlist.discontinuityStarts = findIndexes(playlist.segments || [], 'discontinuity');
27854    return playlist;
27855  });
27856};
27857
27858const addSidxSegmentsToPlaylist = (playlist, sidxMapping) => {
27859  const sidxKey = generateSidxKey(playlist.sidx);
27860  const sidxMatch = sidxKey && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;
27861
27862  if (sidxMatch) {
27863    addSidxSegmentsToPlaylist$1(playlist, sidxMatch, playlist.sidx.resolvedUri);
27864  }
27865
27866  return playlist;
27867};
27868const addSidxSegmentsToPlaylists = (playlists, sidxMapping = {}) => {
27869  if (!Object.keys(sidxMapping).length) {
27870    return playlists;
27871  }
27872
27873  for (const i in playlists) {
27874    playlists[i] = addSidxSegmentsToPlaylist(playlists[i], sidxMapping);
27875  }
27876
27877  return playlists;
27878};
27879const formatAudioPlaylist = ({
27880  attributes,
27881  segments,
27882  sidx,
27883  mediaSequence,
27884  discontinuitySequence,
27885  discontinuityStarts
27886}, isAudioOnly) => {
27887  const playlist = {
27888    attributes: {
27889      NAME: attributes.id,
27890      BANDWIDTH: attributes.bandwidth,
27891      CODECS: attributes.codecs,
27892      ['PROGRAM-ID']: 1
27893    },
27894    uri: '',
27895    endList: attributes.type === 'static',
27896    timeline: attributes.periodStart,
27897    resolvedUri: attributes.baseUrl || '',
27898    targetDuration: attributes.duration,
27899    discontinuitySequence,
27900    discontinuityStarts,
27901    timelineStarts: attributes.timelineStarts,
27902    mediaSequence,
27903    segments
27904  };
27905
27906  if (attributes.contentProtection) {
27907    playlist.contentProtection = attributes.contentProtection;
27908  }
27909
27910  if (attributes.serviceLocation) {
27911    playlist.attributes.serviceLocation = attributes.serviceLocation;
27912  }
27913
27914  if (sidx) {
27915    playlist.sidx = sidx;
27916  }
27917
27918  if (isAudioOnly) {
27919    playlist.attributes.AUDIO = 'audio';
27920    playlist.attributes.SUBTITLES = 'subs';
27921  }
27922
27923  return playlist;
27924};
27925const formatVttPlaylist = ({
27926  attributes,
27927  segments,
27928  mediaSequence,
27929  discontinuityStarts,
27930  discontinuitySequence
27931}) => {
27932  if (typeof segments === 'undefined') {
27933    // vtt tracks may use single file in BaseURL
27934    segments = [{
27935      uri: attributes.baseUrl,
27936      timeline: attributes.periodStart,
27937      resolvedUri: attributes.baseUrl || '',
27938      duration: attributes.sourceDuration,
27939      number: 0
27940    }]; // targetDuration should be the same duration as the only segment
27941
27942    attributes.duration = attributes.sourceDuration;
27943  }
27944
27945  const m3u8Attributes = {
27946    NAME: attributes.id,
27947    BANDWIDTH: attributes.bandwidth,
27948    ['PROGRAM-ID']: 1
27949  };
27950
27951  if (attributes.codecs) {
27952    m3u8Attributes.CODECS = attributes.codecs;
27953  }
27954
27955  const vttPlaylist = {
27956    attributes: m3u8Attributes,
27957    uri: '',
27958    endList: attributes.type === 'static',
27959    timeline: attributes.periodStart,
27960    resolvedUri: attributes.baseUrl || '',
27961    targetDuration: attributes.duration,
27962    timelineStarts: attributes.timelineStarts,
27963    discontinuityStarts,
27964    discontinuitySequence,
27965    mediaSequence,
27966    segments
27967  };
27968
27969  if (attributes.serviceLocation) {
27970    vttPlaylist.attributes.serviceLocation = attributes.serviceLocation;
27971  }
27972
27973  return vttPlaylist;
27974};
27975const organizeAudioPlaylists = (playlists, sidxMapping = {}, isAudioOnly = false) => {
27976  let mainPlaylist;
27977  const formattedPlaylists = playlists.reduce((a, playlist) => {
27978    const role = playlist.attributes.role && playlist.attributes.role.value || '';
27979    const language = playlist.attributes.lang || '';
27980    let label = playlist.attributes.label || 'main';
27981
27982    if (language && !playlist.attributes.label) {
27983      const roleLabel = role ? ` (${role})` : '';
27984      label = `${playlist.attributes.lang}${roleLabel}`;
27985    }
27986
27987    if (!a[label]) {
27988      a[label] = {
27989        language,
27990        autoselect: true,
27991        default: role === 'main',
27992        playlists: [],
27993        uri: ''
27994      };
27995    }
27996
27997    const formatted = addSidxSegmentsToPlaylist(formatAudioPlaylist(playlist, isAudioOnly), sidxMapping);
27998    a[label].playlists.push(formatted);
27999
28000    if (typeof mainPlaylist === 'undefined' && role === 'main') {
28001      mainPlaylist = playlist;
28002      mainPlaylist.default = true;
28003    }
28004
28005    return a;
28006  }, {}); // if no playlists have role "main", mark the first as main
28007
28008  if (!mainPlaylist) {
28009    const firstLabel = Object.keys(formattedPlaylists)[0];
28010    formattedPlaylists[firstLabel].default = true;
28011  }
28012
28013  return formattedPlaylists;
28014};
28015const organizeVttPlaylists = (playlists, sidxMapping = {}) => {
28016  return playlists.reduce((a, playlist) => {
28017    const label = playlist.attributes.label || playlist.attributes.lang || 'text';
28018    const language = playlist.attributes.lang || 'und';
28019
28020    if (!a[label]) {
28021      a[label] = {
28022        language,
28023        default: false,
28024        autoselect: false,
28025        playlists: [],
28026        uri: ''
28027      };
28028    }
28029
28030    a[label].playlists.push(addSidxSegmentsToPlaylist(formatVttPlaylist(playlist), sidxMapping));
28031    return a;
28032  }, {});
28033};
28034
28035const organizeCaptionServices = captionServices => captionServices.reduce((svcObj, svc) => {
28036  if (!svc) {
28037    return svcObj;
28038  }
28039
28040  svc.forEach(service => {
28041    const {
28042      channel,
28043      language
28044    } = service;
28045    svcObj[language] = {
28046      autoselect: false,
28047      default: false,
28048      instreamId: channel,
28049      language
28050    };
28051
28052    if (service.hasOwnProperty('aspectRatio')) {
28053      svcObj[language].aspectRatio = service.aspectRatio;
28054    }
28055
28056    if (service.hasOwnProperty('easyReader')) {
28057      svcObj[language].easyReader = service.easyReader;
28058    }
28059
28060    if (service.hasOwnProperty('3D')) {
28061      svcObj[language]['3D'] = service['3D'];
28062    }
28063  });
28064  return svcObj;
28065}, {});
28066
28067const formatVideoPlaylist = ({
28068  attributes,
28069  segments,
28070  sidx,
28071  discontinuityStarts
28072}) => {
28073  const playlist = {
28074    attributes: {
28075      NAME: attributes.id,
28076      AUDIO: 'audio',
28077      SUBTITLES: 'subs',
28078      RESOLUTION: {
28079        width: attributes.width,
28080        height: attributes.height
28081      },
28082      CODECS: attributes.codecs,
28083      BANDWIDTH: attributes.bandwidth,
28084      ['PROGRAM-ID']: 1
28085    },
28086    uri: '',
28087    endList: attributes.type === 'static',
28088    timeline: attributes.periodStart,
28089    resolvedUri: attributes.baseUrl || '',
28090    targetDuration: attributes.duration,
28091    discontinuityStarts,
28092    timelineStarts: attributes.timelineStarts,
28093    segments
28094  };
28095
28096  if (attributes.frameRate) {
28097    playlist.attributes['FRAME-RATE'] = attributes.frameRate;
28098  } // qualityRanking is inversely related to quality (higher value = lower quality)
28099  // so SCORE is calculated as 1/(qualityRanking+1) (higher SCORE = higher quality)
28100  // The +1 ensures we avoid division by zero when qualityRanking is 0
28101
28102
28103  if (attributes.qualityRanking !== undefined) {
28104    playlist.attributes.SCORE = 1 / (attributes.qualityRanking + 1);
28105  }
28106
28107  if (attributes.contentProtection) {
28108    playlist.contentProtection = attributes.contentProtection;
28109  }
28110
28111  if (attributes.serviceLocation) {
28112    playlist.attributes.serviceLocation = attributes.serviceLocation;
28113  }
28114
28115  if (sidx) {
28116    playlist.sidx = sidx;
28117  }
28118
28119  return playlist;
28120};
28121
28122const videoOnly = ({
28123  attributes
28124}) => attributes.mimeType === 'video/mp4' || attributes.mimeType === 'video/webm' || attributes.contentType === 'video';
28125
28126const audioOnly = ({
28127  attributes
28128}) => attributes.mimeType === 'audio/mp4' || attributes.mimeType === 'audio/webm' || attributes.contentType === 'audio';
28129
28130const vttOnly = ({
28131  attributes
28132}) => attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
28133/**
28134 * Contains start and timeline properties denoting a timeline start. For DASH, these will
28135 * be the same number.
28136 *
28137 * @typedef {Object} TimelineStart
28138 * @property {number} start - the start time of the timeline
28139 * @property {number} timeline - the timeline number
28140 */
28141
28142/**
28143 * Adds appropriate media and discontinuity sequence values to the segments and playlists.
28144 *
28145 * Throughout mpd-parser, the `number` attribute is used in relation to `startNumber`, a
28146 * DASH specific attribute used in constructing segment URI's from templates. However, from
28147 * an HLS perspective, the `number` attribute on a segment would be its `mediaSequence`
28148 * value, which should start at the original media sequence value (or 0) and increment by 1
28149 * for each segment thereafter. Since DASH's `startNumber` values are independent per
28150 * period, it doesn't make sense to use it for `number`. Instead, assume everything starts
28151 * from a 0 mediaSequence value and increment from there.
28152 *
28153 * Note that VHS currently doesn't use the `number` property, but it can be helpful for
28154 * debugging and making sense of the manifest.
28155 *
28156 * For live playlists, to account for values increasing in manifests when periods are
28157 * removed on refreshes, merging logic should be used to update the numbers to their
28158 * appropriate values (to ensure they're sequential and increasing).
28159 *
28160 * @param {Object[]} playlists - the playlists to update
28161 * @param {TimelineStart[]} timelineStarts - the timeline starts for the manifest
28162 */
28163
28164
28165const addMediaSequenceValues = (playlists, timelineStarts) => {
28166  // increment all segments sequentially
28167  playlists.forEach(playlist => {
28168    playlist.mediaSequence = 0;
28169    playlist.discontinuitySequence = timelineStarts.findIndex(function ({
28170      timeline
28171    }) {
28172      return timeline === playlist.timeline;
28173    });
28174
28175    if (!playlist.segments) {
28176      return;
28177    }
28178
28179    playlist.segments.forEach((segment, index) => {
28180      segment.number = index;
28181    });
28182  });
28183};
28184/**
28185 * Given a media group object, flattens all playlists within the media group into a single
28186 * array.
28187 *
28188 * @param {Object} mediaGroupObject - the media group object
28189 *
28190 * @return {Object[]}
28191 *         The media group playlists
28192 */
28193
28194const flattenMediaGroupPlaylists = mediaGroupObject => {
28195  if (!mediaGroupObject) {
28196    return [];
28197  }
28198
28199  return Object.keys(mediaGroupObject).reduce((acc, label) => {
28200    const labelContents = mediaGroupObject[label];
28201    return acc.concat(labelContents.playlists);
28202  }, []);
28203};
28204const toM3u8 = ({
28205  dashPlaylists,
28206  locations,
28207  contentSteering,
28208  sidxMapping = {},
28209  previousManifest,
28210  eventStream
28211}) => {
28212  if (!dashPlaylists.length) {
28213    return {};
28214  } // grab all main manifest attributes
28215
28216
28217  const {
28218    sourceDuration: duration,
28219    type,
28220    suggestedPresentationDelay,
28221    minimumUpdatePeriod
28222  } = dashPlaylists[0].attributes;
28223  const videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist);
28224  const audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly));
28225  const vttPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(vttOnly));
28226  const captions = dashPlaylists.map(playlist => playlist.attributes.captionServices).filter(Boolean);
28227  const manifest = {
28228    allowCache: true,
28229    discontinuityStarts: [],
28230    segments: [],
28231    endList: true,
28232    mediaGroups: {
28233      AUDIO: {},
28234      VIDEO: {},
28235      ['CLOSED-CAPTIONS']: {},
28236      SUBTITLES: {}
28237    },
28238    uri: '',
28239    duration,
28240    playlists: addSidxSegmentsToPlaylists(videoPlaylists, sidxMapping)
28241  };
28242
28243  if (minimumUpdatePeriod >= 0) {
28244    manifest.minimumUpdatePeriod = minimumUpdatePeriod * 1000;
28245  }
28246
28247  if (locations) {
28248    manifest.locations = locations;
28249  }
28250
28251  if (contentSteering) {
28252    manifest.contentSteering = contentSteering;
28253  }
28254
28255  if (type === 'dynamic') {
28256    manifest.suggestedPresentationDelay = suggestedPresentationDelay;
28257  }
28258
28259  if (eventStream && eventStream.length > 0) {
28260    manifest.eventStream = eventStream;
28261  }
28262
28263  const isAudioOnly = manifest.playlists.length === 0;
28264  const organizedAudioGroup = audioPlaylists.length ? organizeAudioPlaylists(audioPlaylists, sidxMapping, isAudioOnly) : null;
28265  const organizedVttGroup = vttPlaylists.length ? organizeVttPlaylists(vttPlaylists, sidxMapping) : null;
28266  const formattedPlaylists = videoPlaylists.concat(flattenMediaGroupPlaylists(organizedAudioGroup), flattenMediaGroupPlaylists(organizedVttGroup));
28267  const playlistTimelineStarts = formattedPlaylists.map(({
28268    timelineStarts
28269  }) => timelineStarts);
28270  manifest.timelineStarts = getUniqueTimelineStarts(playlistTimelineStarts);
28271  addMediaSequenceValues(formattedPlaylists, manifest.timelineStarts);
28272
28273  if (organizedAudioGroup) {
28274    manifest.mediaGroups.AUDIO.audio = organizedAudioGroup;
28275  }
28276
28277  if (organizedVttGroup) {
28278    manifest.mediaGroups.SUBTITLES.subs = organizedVttGroup;
28279  }
28280
28281  if (captions.length) {
28282    manifest.mediaGroups['CLOSED-CAPTIONS'].cc = organizeCaptionServices(captions);
28283  }
28284
28285  if (previousManifest) {
28286    return positionManifestOnTimeline({
28287      oldManifest: previousManifest,
28288      newManifest: manifest
28289    });
28290  }
28291
28292  return manifest;
28293};
28294
28295/**
28296 * Calculates the R (repetition) value for a live stream (for the final segment
28297 * in a manifest where the r value is negative 1)
28298 *
28299 * @param {Object} attributes
28300 *        Object containing all inherited attributes from parent elements with attribute
28301 *        names as keys
28302 * @param {number} time
28303 *        current time (typically the total time up until the final segment)
28304 * @param {number} duration
28305 *        duration property for the given <S />
28306 *
28307 * @return {number}
28308 *        R value to reach the end of the given period
28309 */
28310const getLiveRValue = (attributes, time, duration) => {
28311  const {
28312    NOW,
28313    clientOffset,
28314    availabilityStartTime,
28315    timescale = 1,
28316    periodStart = 0,
28317    minimumUpdatePeriod = 0
28318  } = attributes;
28319  const now = (NOW + clientOffset) / 1000;
28320  const periodStartWC = availabilityStartTime + periodStart;
28321  const periodEndWC = now + minimumUpdatePeriod;
28322  const periodDuration = periodEndWC - periodStartWC;
28323  return Math.ceil((periodDuration * timescale - time) / duration);
28324};
28325/**
28326 * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
28327 * timing and duration
28328 *
28329 * @param {Object} attributes
28330 *        Object containing all inherited attributes from parent elements with attribute
28331 *        names as keys
28332 * @param {Object[]} segmentTimeline
28333 *        List of objects representing the attributes of each S element contained within
28334 *
28335 * @return {{number: number, duration: number, time: number, timeline: number}[]}
28336 *         List of Objects with segment timing and duration info
28337 */
28338
28339
28340const parseByTimeline = (attributes, segmentTimeline) => {
28341  const {
28342    type,
28343    minimumUpdatePeriod = 0,
28344    media = '',
28345    sourceDuration,
28346    timescale = 1,
28347    startNumber = 1,
28348    periodStart: timeline
28349  } = attributes;
28350  const segments = [];
28351  let time = -1;
28352
28353  for (let sIndex = 0; sIndex < segmentTimeline.length; sIndex++) {
28354    const S = segmentTimeline[sIndex];
28355    const duration = S.d;
28356    const repeat = S.r || 0;
28357    const segmentTime = S.t || 0;
28358
28359    if (time < 0) {
28360      // first segment
28361      time = segmentTime;
28362    }
28363
28364    if (segmentTime && segmentTime > time) {
28365      // discontinuity
28366      // TODO: How to handle this type of discontinuity
28367      // timeline++ here would treat it like HLS discontuity and content would
28368      // get appended without gap
28369      // E.G.
28370      //  <S t="0" d="1" />
28371      //  <S d="1" />
28372      //  <S d="1" />
28373      //  <S t="5" d="1" />
28374      // would have $Time$ values of [0, 1, 2, 5]
28375      // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY)
28376      // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP)
28377      // does the value of sourceDuration consider this when calculating arbitrary
28378      // negative @r repeat value?
28379      // E.G. Same elements as above with this added at the end
28380      //  <S d="1" r="-1" />
28381      //  with a sourceDuration of 10
28382      // Would the 2 gaps be included in the time duration calculations resulting in
28383      // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments
28384      // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ?
28385      time = segmentTime;
28386    }
28387
28388    let count;
28389
28390    if (repeat < 0) {
28391      const nextS = sIndex + 1;
28392
28393      if (nextS === segmentTimeline.length) {
28394        // last segment
28395        if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) {
28396          count = getLiveRValue(attributes, time, duration);
28397        } else {
28398          // TODO: This may be incorrect depending on conclusion of TODO above
28399          count = (sourceDuration * timescale - time) / duration;
28400        }
28401      } else {
28402        count = (segmentTimeline[nextS].t - time) / duration;
28403      }
28404    } else {
28405      count = repeat + 1;
28406    }
28407
28408    const end = startNumber + segments.length + count;
28409    let number = startNumber + segments.length;
28410
28411    while (number < end) {
28412      segments.push({
28413        number,
28414        duration: duration / timescale,
28415        time,
28416        timeline
28417      });
28418      time += duration;
28419      number++;
28420    }
28421  }
28422
28423  return segments;
28424};
28425
28426const identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
28427/**
28428 * Replaces template identifiers with corresponding values. To be used as the callback
28429 * for String.prototype.replace
28430 *
28431 * @name replaceCallback
28432 * @function
28433 * @param {string} match
28434 *        Entire match of identifier
28435 * @param {string} identifier
28436 *        Name of matched identifier
28437 * @param {string} format
28438 *        Format tag string. Its presence indicates that padding is expected
28439 * @param {string} width
28440 *        Desired length of the replaced value. Values less than this width shall be left
28441 *        zero padded
28442 * @return {string}
28443 *         Replacement for the matched identifier
28444 */
28445
28446/**
28447 * Returns a function to be used as a callback for String.prototype.replace to replace
28448 * template identifiers
28449 *
28450 * @param {Obect} values
28451 *        Object containing values that shall be used to replace known identifiers
28452 * @param {number} values.RepresentationID
28453 *        Value of the Representation@id attribute
28454 * @param {number} values.Number
28455 *        Number of the corresponding segment
28456 * @param {number} values.Bandwidth
28457 *        Value of the Representation@bandwidth attribute.
28458 * @param {number} values.Time
28459 *        Timestamp value of the corresponding segment
28460 * @return {replaceCallback}
28461 *         Callback to be used with String.prototype.replace to replace identifiers
28462 */
28463
28464const identifierReplacement = values => (match, identifier, format, width) => {
28465  if (match === '$$') {
28466    // escape sequence
28467    return '$';
28468  }
28469
28470  if (typeof values[identifier] === 'undefined') {
28471    return match;
28472  }
28473
28474  const value = '' + values[identifier];
28475
28476  if (identifier === 'RepresentationID') {
28477    // Format tag shall not be present with RepresentationID
28478    return value;
28479  }
28480
28481  if (!format) {
28482    width = 1;
28483  } else {
28484    width = parseInt(width, 10);
28485  }
28486
28487  if (value.length >= width) {
28488    return value;
28489  }
28490
28491  return `${new Array(width - value.length + 1).join('0')}${value}`;
28492};
28493/**
28494 * Constructs a segment url from a template string
28495 *
28496 * @param {string} url
28497 *        Template string to construct url from
28498 * @param {Obect} values
28499 *        Object containing values that shall be used to replace known identifiers
28500 * @param {number} values.RepresentationID
28501 *        Value of the Representation@id attribute
28502 * @param {number} values.Number
28503 *        Number of the corresponding segment
28504 * @param {number} values.Bandwidth
28505 *        Value of the Representation@bandwidth attribute.
28506 * @param {number} values.Time
28507 *        Timestamp value of the corresponding segment
28508 * @return {string}
28509 *         Segment url with identifiers replaced
28510 */
28511
28512const constructTemplateUrl = (url, values) => url.replace(identifierPattern, identifierReplacement(values));
28513/**
28514 * Generates a list of objects containing timing and duration information about each
28515 * segment needed to generate segment uris and the complete segment object
28516 *
28517 * @param {Object} attributes
28518 *        Object containing all inherited attributes from parent elements with attribute
28519 *        names as keys
28520 * @param {Object[]|undefined} segmentTimeline
28521 *        List of objects representing the attributes of each S element contained within
28522 *        the SegmentTimeline element
28523 * @return {{number: number, duration: number, time: number, timeline: number}[]}
28524 *         List of Objects with segment timing and duration info
28525 */
28526
28527const parseTemplateInfo = (attributes, segmentTimeline) => {
28528  if (!attributes.duration && !segmentTimeline) {
28529    // if neither @duration or SegmentTimeline are present, then there shall be exactly
28530    // one media segment
28531    return [{
28532      number: attributes.startNumber || 1,
28533      duration: attributes.sourceDuration,
28534      time: 0,
28535      timeline: attributes.periodStart
28536    }];
28537  }
28538
28539  if (attributes.duration) {
28540    return parseByDuration(attributes);
28541  }
28542
28543  return parseByTimeline(attributes, segmentTimeline);
28544};
28545/**
28546 * Generates a list of segments using information provided by the SegmentTemplate element
28547 *
28548 * @param {Object} attributes
28549 *        Object containing all inherited attributes from parent elements with attribute
28550 *        names as keys
28551 * @param {Object[]|undefined} segmentTimeline
28552 *        List of objects representing the attributes of each S element contained within
28553 *        the SegmentTimeline element
28554 * @return {Object[]}
28555 *         List of segment objects
28556 */
28557
28558const segmentsFromTemplate = (attributes, segmentTimeline) => {
28559  const templateValues = {
28560    RepresentationID: attributes.id,
28561    Bandwidth: attributes.bandwidth || 0
28562  };
28563  const {
28564    initialization = {
28565      sourceURL: '',
28566      range: ''
28567    }
28568  } = attributes;
28569  const mapSegment = urlTypeToSegment({
28570    baseUrl: attributes.baseUrl,
28571    source: constructTemplateUrl(initialization.sourceURL, templateValues),
28572    range: initialization.range
28573  });
28574  const segments = parseTemplateInfo(attributes, segmentTimeline);
28575  return segments.map(segment => {
28576    templateValues.Number = segment.number;
28577    templateValues.Time = segment.time;
28578    const uri = constructTemplateUrl(attributes.media || '', templateValues); // See DASH spec section 5.3.9.2.2
28579    // - if timescale isn't present on any level, default to 1.
28580
28581    const timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0
28582
28583    const presentationTimeOffset = attributes.presentationTimeOffset || 0;
28584    const presentationTime = // Even if the @t attribute is not specified for the segment, segment.time is
28585    // calculated in mpd-parser prior to this, so it's assumed to be available.
28586    attributes.periodStart + (segment.time - presentationTimeOffset) / timescale;
28587    const map = {
28588      uri,
28589      timeline: segment.timeline,
28590      duration: segment.duration,
28591      resolvedUri: resolveUrl__default['default'](attributes.baseUrl || '', uri),
28592      map: mapSegment,
28593      number: segment.number,
28594      presentationTime
28595    };
28596    return map;
28597  });
28598};
28599
28600/**
28601 * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14)
28602 * to an object that matches the output of a segment in videojs/mpd-parser
28603 *
28604 * @param {Object} attributes
28605 *   Object containing all inherited attributes from parent elements with attribute
28606 *   names as keys
28607 * @param {Object} segmentUrl
28608 *   <SegmentURL> node to translate into a segment object
28609 * @return {Object} translated segment object
28610 */
28611
28612const SegmentURLToSegmentObject = (attributes, segmentUrl) => {
28613  const {
28614    baseUrl,
28615    initialization = {}
28616  } = attributes;
28617  const initSegment = urlTypeToSegment({
28618    baseUrl,
28619    source: initialization.sourceURL,
28620    range: initialization.range
28621  });
28622  const segment = urlTypeToSegment({
28623    baseUrl,
28624    source: segmentUrl.media,
28625    range: segmentUrl.mediaRange
28626  });
28627  segment.map = initSegment;
28628  return segment;
28629};
28630/**
28631 * Generates a list of segments using information provided by the SegmentList element
28632 * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
28633 * node should be translated into segment.
28634 *
28635 * @param {Object} attributes
28636 *   Object containing all inherited attributes from parent elements with attribute
28637 *   names as keys
28638 * @param {Object[]|undefined} segmentTimeline
28639 *        List of objects representing the attributes of each S element contained within
28640 *        the SegmentTimeline element
28641 * @return {Object.<Array>} list of segments
28642 */
28643
28644
28645const segmentsFromList = (attributes, segmentTimeline) => {
28646  const {
28647    duration,
28648    segmentUrls = [],
28649    periodStart
28650  } = attributes; // Per spec (5.3.9.2.1) no way to determine segment duration OR
28651  // if both SegmentTimeline and @duration are defined, it is outside of spec.
28652
28653  if (!duration && !segmentTimeline || duration && segmentTimeline) {
28654    throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
28655  }
28656
28657  const segmentUrlMap = segmentUrls.map(segmentUrlObject => SegmentURLToSegmentObject(attributes, segmentUrlObject));
28658  let segmentTimeInfo;
28659
28660  if (duration) {
28661    segmentTimeInfo = parseByDuration(attributes);
28662  }
28663
28664  if (segmentTimeline) {
28665    segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
28666  }
28667
28668  const segments = segmentTimeInfo.map((segmentTime, index) => {
28669    if (segmentUrlMap[index]) {
28670      const segment = segmentUrlMap[index]; // See DASH spec section 5.3.9.2.2
28671      // - if timescale isn't present on any level, default to 1.
28672
28673      const timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0
28674
28675      const presentationTimeOffset = attributes.presentationTimeOffset || 0;
28676      segment.timeline = segmentTime.timeline;
28677      segment.duration = segmentTime.duration;
28678      segment.number = segmentTime.number;
28679      segment.presentationTime = periodStart + (segmentTime.time - presentationTimeOffset) / timescale;
28680      return segment;
28681    } // Since we're mapping we should get rid of any blank segments (in case
28682    // the given SegmentTimeline is handling for more elements than we have
28683    // SegmentURLs for).
28684
28685  }).filter(segment => segment);
28686  return segments;
28687};
28688
28689const generateSegments = ({
28690  attributes,
28691  segmentInfo
28692}) => {
28693  let segmentAttributes;
28694  let segmentsFn;
28695
28696  if (segmentInfo.template) {
28697    segmentsFn = segmentsFromTemplate;
28698    segmentAttributes = merge(attributes, segmentInfo.template);
28699  } else if (segmentInfo.base) {
28700    segmentsFn = segmentsFromBase;
28701    segmentAttributes = merge(attributes, segmentInfo.base);
28702  } else if (segmentInfo.list) {
28703    segmentsFn = segmentsFromList;
28704    segmentAttributes = merge(attributes, segmentInfo.list);
28705  }
28706
28707  const segmentsInfo = {
28708    attributes
28709  };
28710
28711  if (!segmentsFn) {
28712    return segmentsInfo;
28713  }
28714
28715  const segments = segmentsFn(segmentAttributes, segmentInfo.segmentTimeline); // The @duration attribute will be used to determin the playlist's targetDuration which
28716  // must be in seconds. Since we've generated the segment list, we no longer need
28717  // @duration to be in @timescale units, so we can convert it here.
28718
28719  if (segmentAttributes.duration) {
28720    const {
28721      duration,
28722      timescale = 1
28723    } = segmentAttributes;
28724    segmentAttributes.duration = duration / timescale;
28725  } else if (segments.length) {
28726    // if there is no @duration attribute, use the largest segment duration as
28727    // as target duration
28728    segmentAttributes.duration = segments.reduce((max, segment) => {
28729      return Math.max(max, Math.ceil(segment.duration));
28730    }, 0);
28731  } else {
28732    segmentAttributes.duration = 0;
28733  }
28734
28735  segmentsInfo.attributes = segmentAttributes;
28736  segmentsInfo.segments = segments; // This is a sidx box without actual segment information
28737
28738  if (segmentInfo.base && segmentAttributes.indexRange) {
28739    segmentsInfo.sidx = segments[0];
28740    segmentsInfo.segments = [];
28741  }
28742
28743  return segmentsInfo;
28744};
28745const toPlaylists = representations => representations.map(generateSegments);
28746
28747const findChildren = (element, name) => from(element.childNodes).filter(({
28748  tagName
28749}) => tagName === name);
28750const getContent = element => element.textContent.trim();
28751
28752/**
28753 * Converts the provided string that may contain a division operation to a number.
28754 *
28755 * @param {string} value - the provided string value
28756 *
28757 * @return {number} the parsed string value
28758 */
28759const parseDivisionValue = value => {
28760  return parseFloat(value.split('/').reduce((prev, current) => prev / current));
28761};
28762
28763const parseDuration = str => {
28764  const SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
28765  const SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
28766  const SECONDS_IN_DAY = 24 * 60 * 60;
28767  const SECONDS_IN_HOUR = 60 * 60;
28768  const SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S
28769
28770  const durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
28771  const match = durationRegex.exec(str);
28772
28773  if (!match) {
28774    return 0;
28775  }
28776
28777  const [year, month, day, hour, minute, second] = match.slice(1);
28778  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);
28779};
28780const parseDate = str => {
28781  // Date format without timezone according to ISO 8601
28782  // YYY-MM-DDThh:mm:ss.ssssss
28783  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
28784  // expressed by ending with 'Z'
28785
28786  if (dateRegex.test(str)) {
28787    str += 'Z';
28788  }
28789
28790  return Date.parse(str);
28791};
28792
28793const parsers = {
28794  /**
28795   * Specifies the duration of the entire Media Presentation. Format is a duration string
28796   * as specified in ISO 8601
28797   *
28798   * @param {string} value
28799   *        value of attribute as a string
28800   * @return {number}
28801   *         The duration in seconds
28802   */
28803  mediaPresentationDuration(value) {
28804    return parseDuration(value);
28805  },
28806
28807  /**
28808   * Specifies the Segment availability start time for all Segments referred to in this
28809   * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability
28810   * time. Format is a date string as specified in ISO 8601
28811   *
28812   * @param {string} value
28813   *        value of attribute as a string
28814   * @return {number}
28815   *         The date as seconds from unix epoch
28816   */
28817  availabilityStartTime(value) {
28818    return parseDate(value) / 1000;
28819  },
28820
28821  /**
28822   * Specifies the smallest period between potential changes to the MPD. Format is a
28823   * duration string as specified in ISO 8601
28824   *
28825   * @param {string} value
28826   *        value of attribute as a string
28827   * @return {number}
28828   *         The duration in seconds
28829   */
28830  minimumUpdatePeriod(value) {
28831    return parseDuration(value);
28832  },
28833
28834  /**
28835   * Specifies the suggested presentation delay. Format is a
28836   * duration string as specified in ISO 8601
28837   *
28838   * @param {string} value
28839   *        value of attribute as a string
28840   * @return {number}
28841   *         The duration in seconds
28842   */
28843  suggestedPresentationDelay(value) {
28844    return parseDuration(value);
28845  },
28846
28847  /**
28848   * specifices the type of mpd. Can be either "static" or "dynamic"
28849   *
28850   * @param {string} value
28851   *        value of attribute as a string
28852   *
28853   * @return {string}
28854   *         The type as a string
28855   */
28856  type(value) {
28857    return value;
28858  },
28859
28860  /**
28861   * Specifies the duration of the smallest time shifting buffer for any Representation
28862   * in the MPD. Format is a duration string as specified in ISO 8601
28863   *
28864   * @param {string} value
28865   *        value of attribute as a string
28866   * @return {number}
28867   *         The duration in seconds
28868   */
28869  timeShiftBufferDepth(value) {
28870    return parseDuration(value);
28871  },
28872
28873  /**
28874   * Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
28875   * Format is a duration string as specified in ISO 8601
28876   *
28877   * @param {string} value
28878   *        value of attribute as a string
28879   * @return {number}
28880   *         The duration in seconds
28881   */
28882  start(value) {
28883    return parseDuration(value);
28884  },
28885
28886  /**
28887   * Specifies the width of the visual presentation
28888   *
28889   * @param {string} value
28890   *        value of attribute as a string
28891   * @return {number}
28892   *         The parsed width
28893   */
28894  width(value) {
28895    return parseInt(value, 10);
28896  },
28897
28898  /**
28899   * Specifies the height of the visual presentation
28900   *
28901   * @param {string} value
28902   *        value of attribute as a string
28903   * @return {number}
28904   *         The parsed height
28905   */
28906  height(value) {
28907    return parseInt(value, 10);
28908  },
28909
28910  /**
28911   * Specifies the bitrate of the representation
28912   *
28913   * @param {string} value
28914   *        value of attribute as a string
28915   * @return {number}
28916   *         The parsed bandwidth
28917   */
28918  bandwidth(value) {
28919    return parseInt(value, 10);
28920  },
28921
28922  /**
28923   * Specifies the frame rate of the representation
28924   *
28925   * @param {string} value
28926   *        value of attribute as a string
28927   * @return {number}
28928   *         The parsed frame rate
28929   */
28930  frameRate(value) {
28931    return parseDivisionValue(value);
28932  },
28933
28934  /**
28935   * Specifies the number of the first Media Segment in this Representation in the Period
28936   *
28937   * @param {string} value
28938   *        value of attribute as a string
28939   * @return {number}
28940   *         The parsed number
28941   */
28942  startNumber(value) {
28943    return parseInt(value, 10);
28944  },
28945
28946  /**
28947   * Specifies the timescale in units per seconds
28948   *
28949   * @param {string} value
28950   *        value of attribute as a string
28951   * @return {number}
28952   *         The parsed timescale
28953   */
28954  timescale(value) {
28955    return parseInt(value, 10);
28956  },
28957
28958  /**
28959   * Specifies the presentationTimeOffset.
28960   *
28961   * @param {string} value
28962   *        value of the attribute as a string
28963   *
28964   * @return {number}
28965   *         The parsed presentationTimeOffset
28966   */
28967  presentationTimeOffset(value) {
28968    return parseInt(value, 10);
28969  },
28970
28971  /**
28972   * Specifies the constant approximate Segment duration
28973   * NOTE: The <Period> element also contains an @duration attribute. This duration
28974   *       specifies the duration of the Period. This attribute is currently not
28975   *       supported by the rest of the parser, however we still check for it to prevent
28976   *       errors.
28977   *
28978   * @param {string} value
28979   *        value of attribute as a string
28980   * @return {number}
28981   *         The parsed duration
28982   */
28983  duration(value) {
28984    const parsedValue = parseInt(value, 10);
28985
28986    if (isNaN(parsedValue)) {
28987      return parseDuration(value);
28988    }
28989
28990    return parsedValue;
28991  },
28992
28993  /**
28994   * Specifies the Segment duration, in units of the value of the @timescale.
28995   *
28996   * @param {string} value
28997   *        value of attribute as a string
28998   * @return {number}
28999   *         The parsed duration
29000   */
29001  d(value) {
29002    return parseInt(value, 10);
29003  },
29004
29005  /**
29006   * Specifies the MPD start time, in @timescale units, the first Segment in the series
29007   * starts relative to the beginning of the Period
29008   *
29009   * @param {string} value
29010   *        value of attribute as a string
29011   * @return {number}
29012   *         The parsed time
29013   */
29014  t(value) {
29015    return parseInt(value, 10);
29016  },
29017
29018  /**
29019   * Specifies the repeat count of the number of following contiguous Segments with the
29020   * same duration expressed by the value of @d
29021   *
29022   * @param {string} value
29023   *        value of attribute as a string
29024   * @return {number}
29025   *         The parsed number
29026   */
29027  r(value) {
29028    return parseInt(value, 10);
29029  },
29030
29031  /**
29032   * Specifies the presentationTime.
29033   *
29034   * @param {string} value
29035   *        value of the attribute as a string
29036   *
29037   * @return {number}
29038   *         The parsed presentationTime
29039   */
29040  presentationTime(value) {
29041    return parseInt(value, 10);
29042  },
29043
29044  /**
29045   * Specifies the quality ranking of the representation relative to others in the same
29046   * adaptation set. This is inversely related to quality - higher values represent
29047   * lower quality content (e.g., 1 = highest quality, 10 = lowest quality).
29048   *
29049   * @param {string} value
29050   *        value of the attribute as a string
29051   *
29052   * @return {number}
29053   *         The parsed qualityRanking
29054   */
29055  qualityRanking(value) {
29056    return parseInt(value, 10);
29057  },
29058
29059  /**
29060   * Alias for qualityRanking to handle lowercase attribute names from HTML DOM.
29061   *
29062   * @param {string} value
29063   *        value of the attribute as a string
29064   *
29065   * @return {number}
29066   *         The parsed qualityRanking
29067   */
29068  qualityranking(value) {
29069    return parseInt(value, 10);
29070  },
29071
29072  /**
29073   * Default parser for all other attributes. Acts as a no-op and just returns the value
29074   * as a string
29075   *
29076   * @param {string} value
29077   *        value of attribute as a string
29078   * @return {string}
29079   *         Unparsed value
29080   */
29081  DEFAULT(value) {
29082    return value;
29083  }
29084
29085};
29086/**
29087 * Maps lowercase attribute names to their camelCase equivalents for internal use.
29088 * HTML DOM converts attributes to lowercase, but we want to maintain camelCase
29089 * naming conventions in the parsed output. XML parsers like xmldom preserve
29090 * the original casing.
29091 */
29092
29093const attributeNameMap = {
29094  qualityranking: 'qualityRanking'
29095};
29096/**
29097 * Gets all the attributes and values of the provided node, parses attributes with known
29098 * types, and returns an object with attribute names mapped to values.
29099 *
29100 * @param {Node} el
29101 *        The node to parse attributes from
29102 * @return {Object}
29103 *         Object with all attributes of el parsed
29104 */
29105
29106const parseAttributes = el => {
29107  if (!(el && el.attributes)) {
29108    return {};
29109  }
29110
29111  return from(el.attributes).reduce((a, e) => {
29112    const parseFn = parsers[e.name] || parsers.DEFAULT;
29113    const attributeName = attributeNameMap[e.name] || e.name;
29114    a[attributeName] = parseFn(e.value);
29115    return a;
29116  }, {});
29117};
29118
29119const keySystemsMap = {
29120  'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
29121  'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
29122  'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
29123  'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime',
29124  // ISO_IEC 23009-1_2022 5.8.5.2.2 The mp4 Protection Scheme
29125  'urn:mpeg:dash:mp4protection:2011': 'mp4protection'
29126};
29127/**
29128 * Builds a list of urls that is the product of the reference urls and BaseURL values
29129 *
29130 * @param {Object[]} references
29131 *        List of objects containing the reference URL as well as its attributes
29132 * @param {Node[]} baseUrlElements
29133 *        List of BaseURL nodes from the mpd
29134 * @return {Object[]}
29135 *         List of objects with resolved urls and attributes
29136 */
29137
29138const buildBaseUrls = (references, baseUrlElements) => {
29139  if (!baseUrlElements.length) {
29140    return references;
29141  }
29142
29143  return flatten(references.map(function (reference) {
29144    return baseUrlElements.map(function (baseUrlElement) {
29145      const initialBaseUrl = getContent(baseUrlElement);
29146      const resolvedBaseUrl = resolveUrl__default['default'](reference.baseUrl, initialBaseUrl);
29147      const finalBaseUrl = merge(parseAttributes(baseUrlElement), {
29148        baseUrl: resolvedBaseUrl
29149      }); // If the URL is resolved, we want to get the serviceLocation from the reference
29150      // assuming there is no serviceLocation on the initialBaseUrl
29151
29152      if (resolvedBaseUrl !== initialBaseUrl && !finalBaseUrl.serviceLocation && reference.serviceLocation) {
29153        finalBaseUrl.serviceLocation = reference.serviceLocation;
29154      }
29155
29156      return finalBaseUrl;
29157    });
29158  }));
29159};
29160/**
29161 * Contains all Segment information for its containing AdaptationSet
29162 *
29163 * @typedef {Object} SegmentInformation
29164 * @property {Object|undefined} template
29165 *           Contains the attributes for the SegmentTemplate node
29166 * @property {Object[]|undefined} segmentTimeline
29167 *           Contains a list of atrributes for each S node within the SegmentTimeline node
29168 * @property {Object|undefined} list
29169 *           Contains the attributes for the SegmentList node
29170 * @property {Object|undefined} base
29171 *           Contains the attributes for the SegmentBase node
29172 */
29173
29174/**
29175 * Returns all available Segment information contained within the AdaptationSet node
29176 *
29177 * @param {Node} adaptationSet
29178 *        The AdaptationSet node to get Segment information from
29179 * @return {SegmentInformation}
29180 *         The Segment information contained within the provided AdaptationSet
29181 */
29182
29183const getSegmentInformation = adaptationSet => {
29184  const segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
29185  const segmentList = findChildren(adaptationSet, 'SegmentList')[0];
29186  const segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(s => merge({
29187    tag: 'SegmentURL'
29188  }, parseAttributes(s)));
29189  const segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];
29190  const segmentTimelineParentNode = segmentList || segmentTemplate;
29191  const segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0];
29192  const segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate;
29193  const segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both
29194  // @initialization and an <Initialization> node.  @initialization can be templated,
29195  // while the node can have a url and range specified.  If the <SegmentTemplate> has
29196  // both @initialization and an <Initialization> subelement we opt to override with
29197  // the node, as this interaction is not defined in the spec.
29198
29199  const template = segmentTemplate && parseAttributes(segmentTemplate);
29200
29201  if (template && segmentInitialization) {
29202    template.initialization = segmentInitialization && parseAttributes(segmentInitialization);
29203  } else if (template && template.initialization) {
29204    // If it is @initialization we convert it to an object since this is the format that
29205    // later functions will rely on for the initialization segment.  This is only valid
29206    // for <SegmentTemplate>
29207    template.initialization = {
29208      sourceURL: template.initialization
29209    };
29210  }
29211
29212  const segmentInfo = {
29213    template,
29214    segmentTimeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(s => parseAttributes(s)),
29215    list: segmentList && merge(parseAttributes(segmentList), {
29216      segmentUrls,
29217      initialization: parseAttributes(segmentInitialization)
29218    }),
29219    base: segmentBase && merge(parseAttributes(segmentBase), {
29220      initialization: parseAttributes(segmentInitialization)
29221    })
29222  };
vendor: 12,819 bytes, lines 29223-29578
29223  Object.keys(segmentInfo).forEach(key => {
29224    if (!segmentInfo[key]) {
29225      delete segmentInfo[key];
29226    }
29227  });
29228  return segmentInfo;
29229};
29230/**
29231 * Contains Segment information and attributes needed to construct a Playlist object
29232 * from a Representation
29233 *
29234 * @typedef {Object} RepresentationInformation
29235 * @property {SegmentInformation} segmentInfo
29236 *           Segment information for this Representation
29237 * @property {Object} attributes
29238 *           Inherited attributes for this Representation
29239 */
29240
29241/**
29242 * Maps a Representation node to an object containing Segment information and attributes
29243 *
29244 * @name inheritBaseUrlsCallback
29245 * @function
29246 * @param {Node} representation
29247 *        Representation node from the mpd
29248 * @return {RepresentationInformation}
29249 *         Representation information needed to construct a Playlist object
29250 */
29251
29252/**
29253 * Returns a callback for Array.prototype.map for mapping Representation nodes to
29254 * Segment information and attributes using inherited BaseURL nodes.
29255 *
29256 * @param {Object} adaptationSetAttributes
29257 *        Contains attributes inherited by the AdaptationSet
29258 * @param {Object[]} adaptationSetBaseUrls
29259 *        List of objects containing resolved base URLs and attributes
29260 *        inherited by the AdaptationSet
29261 * @param {SegmentInformation} adaptationSetSegmentInfo
29262 *        Contains Segment information for the AdaptationSet
29263 * @return {inheritBaseUrlsCallback}
29264 *         Callback map function
29265 */
29266
29267const inheritBaseUrls = (adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) => representation => {
29268  const repBaseUrlElements = findChildren(representation, 'BaseURL');
29269  const repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
29270  const attributes = merge(adaptationSetAttributes, parseAttributes(representation));
29271  const representationSegmentInfo = getSegmentInformation(representation);
29272  return repBaseUrls.map(baseUrl => {
29273    return {
29274      segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo),
29275      attributes: merge(attributes, baseUrl)
29276    };
29277  });
29278};
29279/**
29280 * Tranforms a series of content protection nodes to
29281 * an object containing pssh data by key system
29282 *
29283 * @param {Node[]} contentProtectionNodes
29284 *        Content protection nodes
29285 * @return {Object}
29286 *        Object containing pssh data by key system
29287 */
29288
29289const generateKeySystemInformation = contentProtectionNodes => {
29290  return contentProtectionNodes.reduce((acc, node) => {
29291    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
29292    // as a lowercase string it also mentions it should be treated as case-insensitive on input. Since the key system
29293    // UUIDs in the keySystemsMap are hardcoded as lowercase in the codebase there isn't any reason not to do
29294    // .toLowerCase() on the input UUID string from the manifest (at least I could not think of one).
29295
29296    if (attributes.schemeIdUri) {
29297      attributes.schemeIdUri = attributes.schemeIdUri.toLowerCase();
29298    }
29299
29300    const keySystem = keySystemsMap[attributes.schemeIdUri];
29301
29302    if (keySystem) {
29303      acc[keySystem] = {
29304        attributes
29305      };
29306      const psshNode = findChildren(node, 'cenc:pssh')[0];
29307
29308      if (psshNode) {
29309        const pssh = getContent(psshNode);
29310        acc[keySystem].pssh = pssh && decodeB64ToUint8Array__default['default'](pssh);
29311      }
29312    }
29313
29314    return acc;
29315  }, {});
29316}; // defined in ANSI_SCTE 214-1 2016
29317
29318
29319const parseCaptionServiceMetadata = service => {
29320  // 608 captions
29321  if (service.schemeIdUri === 'urn:scte:dash:cc:cea-608:2015') {
29322    const values = typeof service.value !== 'string' ? [] : service.value.split(';');
29323    return values.map(value => {
29324      let channel;
29325      let language; // default language to value
29326
29327      language = value;
29328
29329      if (/^CC\d=/.test(value)) {
29330        [channel, language] = value.split('=');
29331      } else if (/^CC\d$/.test(value)) {
29332        channel = value;
29333      }
29334
29335      return {
29336        channel,
29337        language
29338      };
29339    });
29340  } else if (service.schemeIdUri === 'urn:scte:dash:cc:cea-708:2015') {
29341    const values = typeof service.value !== 'string' ? [] : service.value.split(';');
29342    return values.map(value => {
29343      const flags = {
29344        // service or channel number 1-63
29345        'channel': undefined,
29346        // language is a 3ALPHA per ISO 639.2/B
29347        // field is required
29348        'language': undefined,
29349        // BIT 1/0 or ?
29350        // default value is 1, meaning 16:9 aspect ratio, 0 is 4:3, ? is unknown
29351        'aspectRatio': 1,
29352        // BIT 1/0
29353        // easy reader flag indicated the text is tailed to the needs of beginning readers
29354        // default 0, or off
29355        'easyReader': 0,
29356        // BIT 1/0
29357        // If 3d metadata is present (CEA-708.1) then 1
29358        // default 0
29359        '3D': 0
29360      };
29361
29362      if (/=/.test(value)) {
29363        const [channel, opts = ''] = value.split('=');
29364        flags.channel = channel;
29365        flags.language = value;
29366        opts.split(',').forEach(opt => {
29367          const [name, val] = opt.split(':');
29368
29369          if (name === 'lang') {
29370            flags.language = val; // er for easyReadery
29371          } else if (name === 'er') {
29372            flags.easyReader = Number(val); // war for wide aspect ratio
29373          } else if (name === 'war') {
29374            flags.aspectRatio = Number(val);
29375          } else if (name === '3D') {
29376            flags['3D'] = Number(val);
29377          }
29378        });
29379      } else {
29380        flags.language = value;
29381      }
29382
29383      if (flags.channel) {
29384        flags.channel = 'SERVICE' + flags.channel;
29385      }
29386
29387      return flags;
29388    });
29389  }
29390};
29391/**
29392 * A map callback that will parse all event stream data for a collection of periods
29393 * DASH ISO_IEC_23009 5.10.2.2
29394 * https://dashif-documents.azurewebsites.net/Events/master/event.html#mpd-event-timing
29395 *
29396 * @param {PeriodInformation} period object containing necessary period information
29397 * @return a collection of parsed eventstream event objects
29398 */
29399
29400const toEventStream = period => {
29401  // get and flatten all EventStreams tags and parse attributes and children
29402  return flatten(findChildren(period.node, 'EventStream').map(eventStream => {
29403    const eventStreamAttributes = parseAttributes(eventStream);
29404    const schemeIdUri = eventStreamAttributes.schemeIdUri; // find all Events per EventStream tag and map to return objects
29405
29406    return findChildren(eventStream, 'Event').map(event => {
29407      const eventAttributes = parseAttributes(event);
29408      const presentationTime = eventAttributes.presentationTime || 0;
29409      const timescale = eventStreamAttributes.timescale || 1;
29410      const duration = eventAttributes.duration || 0;
29411      const start = presentationTime / timescale + period.attributes.start;
29412      return {
29413        schemeIdUri,
29414        value: eventStreamAttributes.value,
29415        id: eventAttributes.id,
29416        start,
29417        end: start + duration / timescale,
29418        messageData: getContent(event) || eventAttributes.messageData,
29419        contentEncoding: eventStreamAttributes.contentEncoding,
29420        presentationTimeOffset: eventStreamAttributes.presentationTimeOffset || 0
29421      };
29422    });
29423  }));
29424};
29425/**
29426 * Maps an AdaptationSet node to a list of Representation information objects
29427 *
29428 * @name toRepresentationsCallback
29429 * @function
29430 * @param {Node} adaptationSet
29431 *        AdaptationSet node from the mpd
29432 * @return {RepresentationInformation[]}
29433 *         List of objects containing Representaion information
29434 */
29435
29436/**
29437 * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of
29438 * Representation information objects
29439 *
29440 * @param {Object} periodAttributes
29441 *        Contains attributes inherited by the Period
29442 * @param {Object[]} periodBaseUrls
29443 *        Contains list of objects with resolved base urls and attributes
29444 *        inherited by the Period
29445 * @param {string[]} periodSegmentInfo
29446 *        Contains Segment Information at the period level
29447 * @return {toRepresentationsCallback}
29448 *         Callback map function
29449 */
29450
29451const toRepresentations = (periodAttributes, periodBaseUrls, periodSegmentInfo) => adaptationSet => {
29452  const adaptationSetAttributes = parseAttributes(adaptationSet);
29453  const adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL'));
29454  const role = findChildren(adaptationSet, 'Role')[0];
29455  const roleAttributes = {
29456    role: parseAttributes(role)
29457  };
29458  let attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes);
29459  const accessibility = findChildren(adaptationSet, 'Accessibility')[0];
29460  const captionServices = parseCaptionServiceMetadata(parseAttributes(accessibility));
29461
29462  if (captionServices) {
29463    attrs = merge(attrs, {
29464      captionServices
29465    });
29466  }
29467
29468  const label = findChildren(adaptationSet, 'Label')[0];
29469
29470  if (label && label.childNodes.length) {
29471    const labelVal = label.childNodes[0].nodeValue.trim();
29472    attrs = merge(attrs, {
29473      label: labelVal
29474    });
29475  }
29476
29477  const contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection'));
29478
29479  if (Object.keys(contentProtection).length) {
29480    attrs = merge(attrs, {
29481      contentProtection
29482    });
29483  }
29484
29485  const segmentInfo = getSegmentInformation(adaptationSet);
29486  const representations = findChildren(adaptationSet, 'Representation');
29487  const adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo);
29488  return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo)));
29489};
29490/**
29491 * Contains all period information for mapping nodes onto adaptation sets.
29492 *
29493 * @typedef {Object} PeriodInformation
29494 * @property {Node} period.node
29495 *           Period node from the mpd
29496 * @property {Object} period.attributes
29497 *           Parsed period attributes from node plus any added
29498 */
29499
29500/**
29501 * Maps a PeriodInformation object to a list of Representation information objects for all
29502 * AdaptationSet nodes contained within the Period.
29503 *
29504 * @name toAdaptationSetsCallback
29505 * @function
29506 * @param {PeriodInformation} period
29507 *        Period object containing necessary period information
29508 * @param {number} periodStart
29509 *        Start time of the Period within the mpd
29510 * @return {RepresentationInformation[]}
29511 *         List of objects containing Representaion information
29512 */
29513
29514/**
29515 * Returns a callback for Array.prototype.map for mapping Period nodes to a list of
29516 * Representation information objects
29517 *
29518 * @param {Object} mpdAttributes
29519 *        Contains attributes inherited by the mpd
29520  * @param {Object[]} mpdBaseUrls
29521 *        Contains list of objects with resolved base urls and attributes
29522 *        inherited by the mpd
29523 * @return {toAdaptationSetsCallback}
29524 *         Callback map function
29525 */
29526
29527const toAdaptationSets = (mpdAttributes, mpdBaseUrls) => (period, index) => {
29528  const periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period.node, 'BaseURL'));
29529  const periodAttributes = merge(mpdAttributes, {
29530    periodStart: period.attributes.start
29531  });
29532
29533  if (typeof period.attributes.duration === 'number') {
29534    periodAttributes.periodDuration = period.attributes.duration;
29535  }
29536
29537  const adaptationSets = findChildren(period.node, 'AdaptationSet');
29538  const periodSegmentInfo = getSegmentInformation(period.node);
29539  return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo)));
29540};
29541/**
29542 * Tranforms an array of content steering nodes into an object
29543 * containing CDN content steering information from the MPD manifest.
29544 *
29545 * For more information on the DASH spec for Content Steering parsing, see:
29546 * https://dashif.org/docs/DASH-IF-CTS-00XX-Content-Steering-Community-Review.pdf
29547 *
29548 * @param {Node[]} contentSteeringNodes
29549 *        Content steering nodes
29550 * @param {Function} eventHandler
29551 *        The event handler passed into the parser options to handle warnings
29552 * @return {Object}
29553 *        Object containing content steering data
29554 */
29555
29556const generateContentSteeringInformation = (contentSteeringNodes, eventHandler) => {
29557  // If there are more than one ContentSteering tags, throw an error
29558  if (contentSteeringNodes.length > 1) {
29559    eventHandler({
29560      type: 'warn',
29561      message: 'The MPD manifest should contain no more than one ContentSteering tag'
29562    });
29563  } // Return a null value if there are no ContentSteering tags
29564
29565
29566  if (!contentSteeringNodes.length) {
29567    return null;
29568  }
29569
29570  const infoFromContentSteeringTag = merge({
29571    serverURL: getContent(contentSteeringNodes[0])
29572  }, parseAttributes(contentSteeringNodes[0])); // Converts `queryBeforeStart` to a boolean, as well as setting the default value
29573  // to `false` if it doesn't exist
29574
29575  infoFromContentSteeringTag.queryBeforeStart = infoFromContentSteeringTag.queryBeforeStart === 'true';
29576  return infoFromContentSteeringTag;
29577};
29578/**
29579 * Gets Period@start property for a given period.
29580 *
29581 * @param {Object} options
29582 *        Options object
29583 * @param {Object} options.attributes
29584 *        Period attributes
29585 * @param {Object} [options.priorPeriodAttributes]
29586 *        Prior period attributes (if prior period is available)
29587 * @param {string} options.mpdType
29588 *        The MPD@type these periods came from
29589 * @return {number|null}
29590 *         The period start, or null if it's an early available period or error
29591 */
29592
29593const getPeriodStart = ({
29594  attributes,
29595  priorPeriodAttributes,
29596  mpdType
29597}) => {
29598  // Summary of period start time calculation from DASH spec section 5.3.2.1
29599  //
29600  // A period's start is the first period's start + time elapsed after playing all
29601  // prior periods to this one. Periods continue one after the other in time (without
29602  // gaps) until the end of the presentation.
29603  //
29604  // The value of Period@start should be:
29605  // 1. if Period@start is present: value of Period@start
29606  // 2. if previous period exists and it has @duration: previous Period@start +
29607  //    previous Period@duration
29608  // 3. if this is first period and MPD@type is 'static': 0
29609  // 4. in all other cases, consider the period an "early available period" (note: not
29610  //    currently supported)
29611  // (1)
29612  if (typeof attributes.start === 'number') {
29613    return attributes.start;
29614  } // (2)
29615
29616
29617  if (priorPeriodAttributes && typeof priorPeriodAttributes.start === 'number' && typeof priorPeriodAttributes.duration === 'number') {
29618    return priorPeriodAttributes.start + priorPeriodAttributes.duration;
29619  } // (3)
29620
29621
29622  if (!priorPeriodAttributes && mpdType === 'static') {
29623    return 0;
29624  } // (4)
29625  // There is currently no logic for calculating the Period@start value if there is
vendor: 10,492 bytes, lines 29626-29933
29626  // no Period@start or prior Period@start and Period@duration available. This is not made
29627  // explicit by the DASH interop guidelines or the DASH spec, however, since there's
29628  // nothing about any other resolution strategies, it's implied. Thus, this case should
29629  // be considered an early available period, or error, and null should suffice for both
29630  // of those cases.
29631
29632
29633  return null;
29634};
29635/**
29636 * Traverses the mpd xml tree to generate a list of Representation information objects
29637 * that have inherited attributes from parent nodes
29638 *
29639 * @param {Node} mpd
29640 *        The root node of the mpd
29641 * @param {Object} options
29642 *        Available options for inheritAttributes
29643 * @param {string} options.manifestUri
29644 *        The uri source of the mpd
29645 * @param {number} options.NOW
29646 *        Current time per DASH IOP.  Default is current time in ms since epoch
29647 * @param {number} options.clientOffset
29648 *        Client time difference from NOW (in milliseconds)
29649 * @return {RepresentationInformation[]}
29650 *         List of objects containing Representation information
29651 */
29652
29653const inheritAttributes = (mpd, options = {}) => {
29654  const {
29655    manifestUri = '',
29656    NOW = Date.now(),
29657    clientOffset = 0,
29658    // TODO: For now, we are expecting an eventHandler callback function
29659    // to be passed into the mpd parser as an option.
29660    // In the future, we should enable stream parsing by using the Stream class from vhs-utils.
29661    // This will support new features including a standardized event handler.
29662    // See the m3u8 parser for examples of how stream parsing is currently used for HLS parsing.
29663    // https://github.com/videojs/vhs-utils/blob/88d6e10c631e57a5af02c5a62bc7376cd456b4f5/src/stream.js#L9
29664    eventHandler = function () {}
29665  } = options;
29666  const periodNodes = findChildren(mpd, 'Period');
29667
29668  if (!periodNodes.length) {
29669    throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
29670  }
29671
29672  const locations = findChildren(mpd, 'Location');
29673  const mpdAttributes = parseAttributes(mpd);
29674  const mpdBaseUrls = buildBaseUrls([{
29675    baseUrl: manifestUri
29676  }], findChildren(mpd, 'BaseURL'));
29677  const contentSteeringNodes = findChildren(mpd, 'ContentSteering'); // See DASH spec section 5.3.1.2, Semantics of MPD element. Default type to 'static'.
29678
29679  mpdAttributes.type = mpdAttributes.type || 'static';
29680  mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
29681  mpdAttributes.NOW = NOW;
29682  mpdAttributes.clientOffset = clientOffset;
29683
29684  if (locations.length) {
29685    mpdAttributes.locations = locations.map(getContent);
29686  }
29687
29688  const periods = []; // Since toAdaptationSets acts on individual periods right now, the simplest approach to
29689  // adding properties that require looking at prior periods is to parse attributes and add
29690  // missing ones before toAdaptationSets is called. If more such properties are added, it
29691  // may be better to refactor toAdaptationSets.
29692
29693  periodNodes.forEach((node, index) => {
29694    const attributes = parseAttributes(node); // Use the last modified prior period, as it may contain added information necessary
29695    // for this period.
29696
29697    const priorPeriod = periods[index - 1];
29698    attributes.start = getPeriodStart({
29699      attributes,
29700      priorPeriodAttributes: priorPeriod ? priorPeriod.attributes : null,
29701      mpdType: mpdAttributes.type
29702    });
29703    periods.push({
29704      node,
29705      attributes
29706    });
29707  });
29708  return {
29709    locations: mpdAttributes.locations,
29710    contentSteeringInfo: generateContentSteeringInformation(contentSteeringNodes, eventHandler),
29711    // TODO: There are occurences where this `representationInfo` array contains undesired
29712    // duplicates. This generally occurs when there are multiple BaseURL nodes that are
29713    // direct children of the MPD node. When we attempt to resolve URLs from a combination of the
29714    // parent BaseURL and a child BaseURL, and the value does not resolve,
29715    // we end up returning the child BaseURL multiple times.
29716    // We need to determine a way to remove these duplicates in a safe way.
29717    // See: https://github.com/videojs/mpd-parser/pull/17#discussion_r162750527
29718    representationInfo: flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls))),
29719    eventStream: flatten(periods.map(toEventStream))
29720  };
29721};
29722
29723const stringToMpdXml = manifestString => {
29724  if (manifestString === '') {
29725    throw new Error(errors.DASH_EMPTY_MANIFEST);
29726  }
29727
29728  const parser = new xmldom.DOMParser();
29729  let xml;
29730  let mpd;
29731
29732  try {
29733    xml = parser.parseFromString(manifestString, 'application/xml');
29734    mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null;
29735  } catch (e) {// ie 11 throws on invalid xml
29736  }
29737
29738  if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) {
29739    throw new Error(errors.DASH_INVALID_XML);
29740  }
29741
29742  return mpd;
29743};
29744
29745/**
29746 * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
29747 *
29748 * @param {string} mpd
29749 *        XML string of the MPD manifest
29750 * @return {Object|null}
29751 *         Attributes of UTCTiming node specified in the manifest. Null if none found
29752 */
29753
29754const parseUTCTimingScheme = mpd => {
29755  const UTCTimingNode = findChildren(mpd, 'UTCTiming')[0];
29756
29757  if (!UTCTimingNode) {
29758    return null;
29759  }
29760
29761  const attributes = parseAttributes(UTCTimingNode);
29762
29763  switch (attributes.schemeIdUri) {
29764    case 'urn:mpeg:dash:utc:http-head:2014':
29765    case 'urn:mpeg:dash:utc:http-head:2012':
29766      attributes.method = 'HEAD';
29767      break;
29768
29769    case 'urn:mpeg:dash:utc:http-xsdate:2014':
29770    case 'urn:mpeg:dash:utc:http-iso:2014':
29771    case 'urn:mpeg:dash:utc:http-xsdate:2012':
29772    case 'urn:mpeg:dash:utc:http-iso:2012':
29773      attributes.method = 'GET';
29774      break;
29775
29776    case 'urn:mpeg:dash:utc:direct:2014':
29777    case 'urn:mpeg:dash:utc:direct:2012':
29778      attributes.method = 'DIRECT';
29779      attributes.value = Date.parse(attributes.value);
29780      break;
29781
29782    case 'urn:mpeg:dash:utc:http-ntp:2014':
29783    case 'urn:mpeg:dash:utc:ntp:2014':
29784    case 'urn:mpeg:dash:utc:sntp:2014':
29785    default:
29786      throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME);
29787  }
29788
29789  return attributes;
29790};
29791
29792const VERSION = version;
29793/*
29794 * Given a DASH manifest string and options, parses the DASH manifest into an object in the
29795 * form outputed by m3u8-parser and accepted by videojs/http-streaming.
29796 *
29797 * For live DASH manifests, if `previousManifest` is provided in options, then the newly
29798 * parsed DASH manifest will have its media sequence and discontinuity sequence values
29799 * updated to reflect its position relative to the prior manifest.
29800 *
29801 * @param {string} manifestString - the DASH manifest as a string
29802 * @param {options} [options] - any options
29803 *
29804 * @return {Object} the manifest object
29805 */
29806
29807const parse = (manifestString, options = {}) => {
29808  const parsedManifestInfo = inheritAttributes(stringToMpdXml(manifestString), options);
29809  const playlists = toPlaylists(parsedManifestInfo.representationInfo);
29810  return toM3u8({
29811    dashPlaylists: playlists,
29812    locations: parsedManifestInfo.locations,
29813    contentSteering: parsedManifestInfo.contentSteeringInfo,
29814    sidxMapping: options.sidxMapping,
29815    previousManifest: options.previousManifest,
29816    eventStream: parsedManifestInfo.eventStream
29817  });
29818};
29819/**
29820 * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
29821 *
29822 * @param {string} manifestString
29823 *        XML string of the MPD manifest
29824 * @return {Object|null}
29825 *         Attributes of UTCTiming node specified in the manifest. Null if none found
29826 */
29827
29828
29829const parseUTCTiming = manifestString => parseUTCTimingScheme(stringToMpdXml(manifestString));
29830
29831exports.VERSION = VERSION;
29832exports.addSidxSegmentsToPlaylist = addSidxSegmentsToPlaylist$1;
29833exports.generateSidxKey = generateSidxKey;
29834exports.inheritAttributes = inheritAttributes;
29835exports.parse = parse;
29836exports.parseUTCTiming = parseUTCTiming;
29837exports.stringToMpdXml = stringToMpdXml;
29838exports.toM3u8 = toM3u8;
29839exports.toPlaylists = toPlaylists;
29840
29841},{"@videojs/vhs-utils/cjs/decode-b64-to-uint8-array":15,"@videojs/vhs-utils/cjs/media-groups":18,"@videojs/vhs-utils/cjs/resolve-url":23,"@xmldom/xmldom":33,"global/window":39}],46:[function(require,module,exports){
29842var getUint64 = require('../utils/numbers.js').getUint64;
29843
29844var parseSidx = function(data) {
29845  var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
29846      result = {
29847        version: data[0],
29848        flags: new Uint8Array(data.subarray(1, 4)),
29849        references: [],
29850        referenceId: view.getUint32(4),
29851        timescale: view.getUint32(8)
29852      },
29853    i = 12;
29854
29855  if (result.version === 0) {
29856    result.earliestPresentationTime = view.getUint32(i);
29857    result.firstOffset = view.getUint32(i + 4);
29858    i += 8;
29859  } else {
29860    // read 64 bits
29861    result.earliestPresentationTime = getUint64(data.subarray(i));
29862    result.firstOffset = getUint64(data.subarray(i + 8));
29863    i += 16;
29864  }
29865
29866  i += 2; // reserved
29867
29868  var referenceCount = view.getUint16(i);
29869
29870  i += 2; // start of references
29871
29872  for (; referenceCount > 0; i += 12, referenceCount--) {
29873    result.references.push({
29874      referenceType: (data[i] & 0x80) >>> 7,
29875      referencedSize: view.getUint32(i) & 0x7FFFFFFF,
29876      subsegmentDuration: view.getUint32(i + 4),
29877      startsWithSap: !!(data[i + 8] & 0x80),
29878      sapType: (data[i + 8] & 0x70) >>> 4,
29879      sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
29880    });
29881  }
29882
29883  return result;
29884};
29885
29886
29887module.exports = parseSidx;
29888
29889},{"../utils/numbers.js":48}],47:[function(require,module,exports){
29890/**
29891 * mux.js
29892 *
29893 * Copyright (c) Brightcove
29894 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
29895 */
29896var
29897  ONE_SECOND_IN_TS = 90000, // 90kHz clock
29898  secondsToVideoTs,
29899  secondsToAudioTs,
29900  videoTsToSeconds,
29901  audioTsToSeconds,
29902  audioTsToVideoTs,
29903  videoTsToAudioTs,
29904  metadataTsToSeconds;
29905
29906secondsToVideoTs = function(seconds) {
29907  return seconds * ONE_SECOND_IN_TS;
29908};
29909
29910secondsToAudioTs = function(seconds, sampleRate) {
29911  return seconds * sampleRate;
29912};
29913
29914videoTsToSeconds = function(timestamp) {
29915  return timestamp / ONE_SECOND_IN_TS;
29916};
29917
29918audioTsToSeconds = function(timestamp, sampleRate) {
29919  return timestamp / sampleRate;
29920};
29921
29922audioTsToVideoTs = function(timestamp, sampleRate) {
29923  return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
29924};
29925
29926videoTsToAudioTs = function(timestamp, sampleRate) {
29927  return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
29928};
29929
29930/**
29931 * Adjust ID3 tag or caption timing information by the timeline pts values
29932 * (if keepOriginalTimestamps is false) and convert to seconds
29933 */
29934metadataTsToSeconds = function(timestamp, timelineStartPts, keepOriginalTimestamps) {
29935  return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
29936};
29937
29938module.exports = {
29939  ONE_SECOND_IN_TS: ONE_SECOND_IN_TS,
29940  secondsToVideoTs: secondsToVideoTs,
29941  secondsToAudioTs: secondsToAudioTs,
29942  videoTsToSeconds: videoTsToSeconds,
29943  audioTsToSeconds: audioTsToSeconds,
29944  audioTsToVideoTs: audioTsToVideoTs,
29945  videoTsToAudioTs: videoTsToAudioTs,
29946  metadataTsToSeconds: metadataTsToSeconds
29947};
29948
29949},{}],48:[function(require,module,exports){
29950var MAX_UINT32 = Math.pow(2, 32);
29951
29952var getUint64 = function(uint8) {
29953  var dv = new DataView(uint8.buffer, uint8.byteOffset, uint8.byteLength);
29954  var value;
29955
29956  if (dv.getBigUint64) {
29957    value = dv.getBigUint64(0);
29958
29959    if (value < Number.MAX_SAFE_INTEGER) {
29960      return Number(value);
29961    }
29962
29963    return value;
29964  }
29965
29966  return (dv.getUint32(0) * MAX_UINT32) + dv.getUint32(4);
29967};
29968
29969module.exports = {
29970  getUint64: getUint64,
29971  MAX_UINT32: MAX_UINT32
29972};
29973
29974},{}],49:[function(require,module,exports){
29975module.exports={
29976  "Audio Player": "Lecteur audio",
29977  "Video Player": "Lecteur vidéo",
29978  "Play": "Lecture",
29979  "Pause": "Pause",
29980  "Replay": "Revoir",
29981  "Current Time": "Temps actuel",
29982  "Duration": "Durée",
29983  "Remaining Time": "Temps restant",
29984  "Stream Type": "Type de flux",
29985  "LIVE": "EN DIRECT",
29986  "Seek to live, currently behind live": "Rechercher le direct, actuellement après le direct",
29987  "Seek to live, currently playing live": "Rechercher le direct, le direct actuellement en cours de lecture",
29988  "Loaded": "Chargé",
29989  "Progress": "Progression",
29990  "Progress Bar": "Barre de progression",
29991  "progress bar timing: currentTime={1} duration={2}": "{1} de {2}",
29992  "Fullscreen": "Plein écran",
29993  "Exit Fullscreen": "Fenêtré",
29994  "Mute": "Mettre en sourdine",
29995  "Unmute": "Activer le son",
29996  "Playback Rate": "Vitesse de lecture",
29997  "Subtitles": "Sous-titres",
29998  "subtitles off": "Sous-titres désactivés",
29999  "Captions": "Sous-titres transcrits",
30000  "captions off": "Sous-titres transcrits désactivés",
30001  "Chapters": "Chapitres",
30002  "Descriptions": "Descriptions",
30003  "descriptions off": "descriptions désactivées",
30004  "Audio Track": "Piste audio",
30005  "Volume Level": "Niveau de volume",
30006  "You aborted the media playback": "Vous avez interrompu la lecture de la vidéo.",
30007  "A network error caused the media download to fail part-way.": "Une erreur de réseau a interrompu le téléchargement de la vidéo.",
30008  "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Cette vidéo n'a pas pu être chargée, soit parce que le serveur ou le réseau a échoué ou parce que le format n'est pas reconnu.",
30009  "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "La lecture de la vidéo a été interrompue à cause d'un problème de corruption ou parce que la vidéo utilise des fonctionnalités non prises en charge par votre navigateur.",
30010  "No compatible source was found for this media.": "Aucune source compatible n'a été trouvée pour cette vidéo.",
30011  "The media is encrypted and we do not have the keys to decrypt it.": "Le média est chiffré et nous n'avons pas les clés pour le déchiffrer.",
30012  "Play Video": "Lire la vidéo",
30013  "Close": "Fermer",
30014  "Close Modal Dialog": "Fermer la boîte de dialogue modale",
30015  "Modal Window": "Fenêtre modale",
30016  "This is a modal window": "Ceci est une fenêtre modale",
30017  "This modal can be closed by pressing the Escape key or activating the close button.": "Ce modal peut être fermé en appuyant sur la touche Échap ou activer le bouton de fermeture.",
30018  ", opens captions settings dialog": ", ouvrir les paramètres des sous-titres transcrits",
30019  ", opens subtitles settings dialog": ", ouvrir les paramètres des sous-titres",
30020  ", opens descriptions settings dialog": ", ouvrir les paramètres des descriptions",
30021  ", selected": ", sélectionné",
30022  "captions settings": "Paramètres des sous-titres transcrits",
30023  "subtitles settings": "Paramètres des sous-titres",
30024  "descriptions settings": "Paramètres des descriptions",
30025  "Text": "Texte",
30026  "White": "Blanc",
30027  "Black": "Noir",
30028  "Red": "Rouge",
30029  "Green": "Vert",
30030  "Blue": "Bleu",
30031  "Yellow": "Jaune",
30032  "Magenta": "Magenta",
30033  "Cyan": "Cyan",
30034  "Background": "Arrière-plan",
30035  "Window": "Fenêtre",
30036  "Transparent": "Transparent",
30037  "Semi-Transparent": "Semi-transparent",
30038  "Opaque": "Opaque",
30039  "Font Size": "Taille des caractères",
30040  "Text Edge Style": "Style des contours du texte",
30041  "None": "Aucun",
30042  "Raised": "Élevé",
30043  "Depressed": "Enfoncé",
30044  "Uniform": "Uniforme",
30045  "Drop shadow": "Ombre portée",
30046  "Font Family": "Famille de polices",
30047  "Proportional Sans-Serif": "Polices à chasse variable sans empattement (Proportional Sans-Serif)",
30048  "Monospace Sans-Serif": "Polices à chasse fixe sans empattement (Monospace Sans-Serif)",
30049  "Proportional Serif": "Polices à chasse variable avec empattement (Proportional Serif)",
30050  "Monospace Serif": "Polices à chasse fixe avec empattement (Monospace Serif)",
30051  "Casual": "Manuscrite",
30052  "Script": "Scripte",
30053  "Small Caps": "Petites capitales",
30054  "Reset": "Réinitialiser",
30055  "restore all settings to the default values": "Restaurer tous les paramètres aux valeurs par défaut",
30056  "Done": "Terminé",
30057  "Caption Settings Dialog": "Boîte de dialogue des paramètres des sous-titres transcrits",
30058  "Beginning of dialog window. Escape will cancel and close the window.": "Début de la fenêtre de dialogue. La touche d'échappement annulera et fermera la fenêtre.",
30059  "End of dialog window.": "Fin de la fenêtre de dialogue.",
30060  "Exit Picture-in-Picture": "Quitter le mode image dans l'image",
30061  "Picture-in-Picture": "Image dans l'image",
30062  "{1} is loading.": "{1} en cours de chargement.",
30063  "No content": "Aucun contenu",
30064  "Color": "Couleur",
30065  "Opacity": "Opacité",
30066  "Text Background": "Arrière-plan du texte",
30067  "Caption Area Background": "Arrière-plan de la zone de sous-titre",
30068  "Skip backward {1} seconds": "Reculer de {1} secondes",
30069  "Skip forward {1} seconds": "Avancer de {1} secondes"
30070}
30071
30072},{}],50:[function(require,module,exports){
30073(function (global){
30074/**
30075 * @license
30076 * Video.js 8.24.0 <http://videojs.com/>
30077 * Copyright 2010-present Video.js contributors
30078 * Available under Apache License Version 2.0
30079 * <https://github.com/videojs/video.js/blob/main/LICENSE>
30080 *
30081 * Includes vtt.js <https://github.com/mozilla/vtt.js>
30082 * Available under Apache License Version 2.0
30083 * <https://github.com/mozilla/vtt.js/blob/main/LICENSE>
30084 */
30085
30086'use strict';
30087
30088var window$1 = require('global/window');
30089var document$1 = require('global/document');
30090var XHR = require('@videojs/xhr');
30091var vtt = require('videojs-vtt.js');
30092var _extends = require('@babel/runtime/helpers/extends');
30093var _resolveUrl = require('@videojs/vhs-utils/cjs/resolve-url.js');
30094var m3u8Parser = require('m3u8-parser');
30095var codecs_js = require('@videojs/vhs-utils/cjs/codecs.js');
30096var mediaTypes_js = require('@videojs/vhs-utils/cjs/media-types.js');
30097var byteHelpers = require('@videojs/vhs-utils/cjs/byte-helpers');
30098var mpdParser = require('mpd-parser');
30099var parseSidx = require('mux.js/lib/tools/parse-sidx');
30100var id3Helpers = require('@videojs/vhs-utils/cjs/id3-helpers');
30101var containers = require('@videojs/vhs-utils/cjs/containers');
30102var clock = require('mux.js/lib/utils/clock');
30103
30104function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
30105
30106var window__default = /*#__PURE__*/_interopDefaultLegacy(window$1);
30107var document__default = /*#__PURE__*/_interopDefaultLegacy(document$1);
30108var XHR__default = /*#__PURE__*/_interopDefaultLegacy(XHR);
30109var vtt__default = /*#__PURE__*/_interopDefaultLegacy(vtt);
30110var _extends__default = /*#__PURE__*/_interopDefaultLegacy(_extends);
30111var _resolveUrl__default = /*#__PURE__*/_interopDefaultLegacy(_resolveUrl);
30112var parseSidx__default = /*#__PURE__*/_interopDefaultLegacy(parseSidx);
30113
30114var version$6 = "8.24.0";
30115
30116/**
30117 * An Object that contains lifecycle hooks as keys which point to an array
30118 * of functions that are run when a lifecycle is triggered
30119 *
30120 * @private
30121 */
30122const hooks_ = {};
30123
30124/**
30125 * Get a list of hooks for a specific lifecycle
30126 *
30127 * @param  {string} type
30128 *         the lifecycle to get hooks from
30129 *
30130 * @param  {Function|Function[]} [fn]
30131 *         Optionally add a hook (or hooks) to the lifecycle that your are getting.
30132 *
30133 * @return {Array}
30134 *         an array of hooks, or an empty array if there are none.
30135 */
vendor: 4,745 bytes, lines 30136-30305
30136const hooks = function (type, fn) {
30137  hooks_[type] = hooks_[type] || [];
30138  if (fn) {
30139    hooks_[type] = hooks_[type].concat(fn);
30140  }
30141  return hooks_[type];
30142};
30143
30144/**
30145 * Add a function hook to a specific videojs lifecycle.
30146 *
30147 * @param {string} type
30148 *        the lifecycle to hook the function to.
30149 *
30150 * @param {Function|Function[]}
30151 *        The function or array of functions to attach.
30152 */
30153const hook = function (type, fn) {
30154  hooks(type, fn);
30155};
30156
30157/**
30158 * Remove a hook from a specific videojs lifecycle.
30159 *
30160 * @param  {string} type
30161 *         the lifecycle that the function hooked to
30162 *
30163 * @param  {Function} fn
30164 *         The hooked function to remove
30165 *
30166 * @return {boolean}
30167 *         The function that was removed or undef
30168 */
30169const removeHook = function (type, fn) {
30170  const index = hooks(type).indexOf(fn);
30171  if (index <= -1) {
30172    return false;
30173  }
30174  hooks_[type] = hooks_[type].slice();
30175  hooks_[type].splice(index, 1);
30176  return true;
30177};
30178
30179/**
30180 * Add a function hook that will only run once to a specific videojs lifecycle.
30181 *
30182 * @param {string} type
30183 *        the lifecycle to hook the function to.
30184 *
30185 * @param {Function|Function[]}
30186 *        The function or array of functions to attach.
30187 */
30188const hookOnce = function (type, fn) {
30189  hooks(type, [].concat(fn).map(original => {
30190    const wrapper = (...args) => {
30191      removeHook(type, wrapper);
30192      return original(...args);
30193    };
30194    return wrapper;
30195  }));
30196};
30197
30198/**
30199 * @file fullscreen-api.js
30200 * @module fullscreen-api
30201 */
30202
30203/**
30204 * Store the browser-specific methods for the fullscreen API.
30205 *
30206 * @type {Object}
30207 * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
30208 * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
30209 */
30210const FullscreenApi = {
30211  prefixed: true
30212};
30213
30214// browser API methods
30215const apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'],
30216// WebKit
30217['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen']];
30218const specApi = apiMap[0];
30219let browserApi;
30220
30221// determine the supported set of functions
30222for (let i = 0; i < apiMap.length; i++) {
30223  // check for exitFullscreen function
30224  if (apiMap[i][1] in document__default["default"]) {
30225    browserApi = apiMap[i];
30226    break;
30227  }
30228}
30229
30230// map the browser API names to the spec API names
30231if (browserApi) {
30232  for (let i = 0; i < browserApi.length; i++) {
30233    FullscreenApi[specApi[i]] = browserApi[i];
30234  }
30235  FullscreenApi.prefixed = browserApi[0] !== specApi[0];
30236}
30237
30238/**
30239 * @file create-logger.js
30240 * @module create-logger
30241 */
30242
30243// This is the private tracking variable for the logging history.
30244let history = [];
30245
30246/**
30247 * Log messages to the console and history based on the type of message
30248 *
30249 * @private
30250 * @param  {string} name
30251 *         The name of the console method to use.
30252 *
30253 * @param  {Object} log
30254 *         The arguments to be passed to the matching console method.
30255 *
30256 * @param {string} [styles]
30257 *        styles for name
30258 */
30259const LogByTypeFactory = (name, log, styles) => (type, level, args) => {
30260  const lvl = log.levels[level];
30261  const lvlRegExp = new RegExp(`^(${lvl})$`);
30262  let resultName = name;
30263  if (type !== 'log') {
30264    // Add the type to the front of the message when it's not "log".
30265    args.unshift(type.toUpperCase() + ':');
30266  }
30267  if (styles) {
30268    resultName = `%c${name}`;
30269    args.unshift(styles);
30270  }
30271
30272  // Add console prefix after adding to history.
30273  args.unshift(resultName + ':');
30274
30275  // Add a clone of the args at this point to history.
30276  if (history) {
30277    history.push([].concat(args));
30278
30279    // only store 1000 history entries
30280    const splice = history.length - 1000;
30281    history.splice(0, splice > 0 ? splice : 0);
30282  }
30283
30284  // If there's no console then don't try to output messages, but they will
30285  // still be stored in history.
30286  if (!window__default["default"].console) {
30287    return;
30288  }
30289
30290  // Was setting these once outside of this function, but containing them
30291  // in the function makes it easier to test cases where console doesn't exist
30292  // when the module is executed.
30293  let fn = window__default["default"].console[type];
30294  if (!fn && type === 'debug') {
30295    // Certain browsers don't have support for console.debug. For those, we
30296    // should default to the closest comparable log.
30297    fn = window__default["default"].console.info || window__default["default"].console.log;
30298  }
30299
30300  // Bail out if there's no console or if this type is not allowed by the
30301  // current logging level.
30302  if (!fn || !lvl || !lvlRegExp.test(type)) {
30303    return;
30304  }
30305  fn[Array.isArray(args) ? 'apply' : 'call'](window__default["default"]
vendor: 11,279 bytes, lines 30305-30734
30305.console, args);
30306};
30307function createLogger$1(name, delimiter = ':', styles = '') {
30308  // This is the private tracking variable for logging level.
30309  let level = 'info';
30310
30311  // the curried logByType bound to the specific log and history
30312  let logByType;
30313
30314  /**
30315   * Logs plain debug messages. Similar to `console.log`.
30316   *
30317   * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
30318   * of our JSDoc template, we cannot properly document this as both a function
30319   * and a namespace, so its function signature is documented here.
30320   *
30321   * #### Arguments
30322   * ##### *args
30323   * *[]
30324   *
30325   * Any combination of values that could be passed to `console.log()`.
30326   *
30327   * #### Return Value
30328   *
30329   * `undefined`
30330   *
30331   * @namespace
30332   * @param    {...*} args
30333   *           One or more messages or objects that should be logged.
30334   */
30335  function log(...args) {
30336    logByType('log', level, args);
30337  }
30338
30339  // This is the logByType helper that the logging methods below use
30340  logByType = LogByTypeFactory(name, log, styles);
30341
30342  /**
30343   * Create a new subLogger which chains the old name to the new name.
30344   *
30345   * For example, doing `mylogger = videojs.log.createLogger('player')` and then using that logger will log the following:
30346   * ```js
30347   *  mylogger('foo');
30348   *  // > VIDEOJS: player: foo
30349   * ```
30350   *
30351   * @param {string} subName
30352   *        The name to add call the new logger
30353   * @param {string} [subDelimiter]
30354   *        Optional delimiter
30355   * @param {string} [subStyles]
30356   *        Optional styles
30357   * @return {Object}
30358   */
30359  log.createLogger = (subName, subDelimiter, subStyles) => {
30360    const resultDelimiter = subDelimiter !== undefined ? subDelimiter : delimiter;
30361    const resultStyles = subStyles !== undefined ? subStyles : styles;
30362    const resultName = `${name} ${resultDelimiter} ${subName}`;
30363    return createLogger$1(resultName, resultDelimiter, resultStyles);
30364  };
30365
30366  /**
30367   * Create a new logger.
30368   *
30369   * @param {string} newName
30370   *        The name for the new logger
30371   * @param {string} [newDelimiter]
30372   *        Optional delimiter
30373   * @param {string} [newStyles]
30374   *        Optional styles
30375   * @return {Object}
30376   */
30377  log.createNewLogger = (newName, newDelimiter, newStyles) => {
30378    return createLogger$1(newName, newDelimiter, newStyles);
30379  };
30380
30381  /**
30382   * Enumeration of available logging levels, where the keys are the level names
30383   * and the values are `|`-separated strings containing logging methods allowed
30384   * in that logging level. These strings are used to create a regular expression
30385   * matching the function name being called.
30386   *
30387   * Levels provided by Video.js are:
30388   *
30389   * - `off`: Matches no calls. Any value that can be cast to `false` will have
30390   *   this effect. The most restrictive.
30391   * - `all`: Matches only Video.js-provided functions (`debug`, `log`,
30392   *   `log.warn`, and `log.error`).
30393   * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
30394   * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
30395   * - `warn`: Matches `log.warn` and `log.error` calls.
30396   * - `error`: Matches only `log.error` calls.
30397   *
30398   * @type {Object}
30399   */
30400  log.levels = {
30401    all: 'debug|log|warn|error',
30402    off: '',
30403    debug: 'debug|log|warn|error',
30404    info: 'log|warn|error',
30405    warn: 'warn|error',
30406    error: 'error',
30407    DEFAULT: level
30408  };
30409
30410  /**
30411   * Get or set the current logging level.
30412   *
30413   * If a string matching a key from {@link module:log.levels} is provided, acts
30414   * as a setter.
30415   *
30416   * @param  {'all'|'debug'|'info'|'warn'|'error'|'off'} [lvl]
30417   *         Pass a valid level to set a new logging level.
30418   *
30419   * @return {string}
30420   *         The current logging level.
30421   */
30422  log.level = lvl => {
30423    if (typeof lvl === 'string') {
30424      if (!log.levels.hasOwnProperty(lvl)) {
30425        throw new Error(`"${lvl}" in not a valid log level`);
30426      }
30427      level = lvl;
30428    }
30429    return level;
30430  };
30431
30432  /**
30433   * Returns an array containing everything that has been logged to the history.
30434   *
30435   * This array is a shallow clone of the internal history record. However, its
30436   * contents are _not_ cloned; so, mutating objects inside this array will
30437   * mutate them in history.
30438   *
30439   * @return {Array}
30440   */
30441  log.history = () => history ? [].concat(history) : [];
30442
30443  /**
30444   * Allows you to filter the history by the given logger name
30445   *
30446   * @param {string} fname
30447   *        The name to filter by
30448   *
30449   * @return {Array}
30450   *         The filtered list to return
30451   */
30452  log.history.filter = fname => {
30453    return (history || []).filter(historyItem => {
30454      // if the first item in each historyItem includes `fname`, then it's a match
30455      return new RegExp(`.*${fname}.*`).test(historyItem[0]);
30456    });
30457  };
30458
30459  /**
30460   * Clears the internal history tracking, but does not prevent further history
30461   * tracking.
30462   */
30463  log.history.clear = () => {
30464    if (history) {
30465      history.length = 0;
30466    }
30467  };
30468
30469  /**
30470   * Disable history tracking if it is currently enabled.
30471   */
30472  log.history.disable = () => {
30473    if (history !== null) {
30474      history.length = 0;
30475      history = null;
30476    }
30477  };
30478
30479  /**
30480   * Enable history tracking if it is currently disabled.
30481   */
30482  log.history.enable = () => {
30483    if (history === null) {
30484      history = [];
30485    }
30486  };
30487
30488  /**
30489   * Logs error messages. Similar to `console.error`.
30490   *
30491   * @param {...*} args
30492   *        One or more messages or objects that should be logged as an error
30493   */
30494  log.error = (...args) => logByType('error', level, args);
30495
30496  /**
30497   * Logs warning messages. Similar to `console.warn`.
30498   *
30499   * @param {...*} args
30500   *        One or more messages or objects that should be logged as a warning.
30501   */
30502  log.warn = (...args) => logByType('warn', level, args);
30503
30504  /**
30505   * Logs debug messages. Similar to `console.debug`, but may also act as a comparable
30506   * log if `console.debug` is not available
30507   *
30508   * @param {...*} args
30509   *        One or more messages or objects that should be logged as debug.
30510   */
30511  log.debug = (...args) => logByType('debug', level, args);
30512  return log;
30513}
30514
30515/**
30516 * @file log.js
30517 * @module log
30518 */
30519const log$1 = createLogger$1('VIDEOJS');
30520const createLogger = log$1.createLogger;
30521
30522/**
30523 * @file obj.js
30524 * @module obj
30525 */
30526
30527/**
30528 * @callback obj:EachCallback
30529 *
30530 * @param {*} value
30531 *        The current key for the object that is being iterated over.
30532 *
30533 * @param {string} key
30534 *        The current key-value for object that is being iterated over
30535 */
30536
30537/**
30538 * @callback obj:ReduceCallback
30539 *
30540 * @param {*} accum
30541 *        The value that is accumulating over the reduce loop.
30542 *
30543 * @param {*} value
30544 *        The current key for the object that is being iterated over.
30545 *
30546 * @param {string} key
30547 *        The current key-value for object that is being iterated over
30548 *
30549 * @return {*}
30550 *         The new accumulated value.
30551 */
30552const toString = Object.prototype.toString;
30553
30554/**
30555 * Get the keys of an Object
30556 *
30557 * @param {Object}
30558 *        The Object to get the keys from
30559 *
30560 * @return {string[]}
30561 *         An array of the keys from the object. Returns an empty array if the
30562 *         object passed in was invalid or had no keys.
30563 *
30564 * @private
30565 */
30566const keys = function (object) {
30567  return isObject(object) ? Object.keys(object) : [];
30568};
30569
30570/**
30571 * Array-like iteration for objects.
30572 *
30573 * @param {Object} object
30574 *        The object to iterate over
30575 *
30576 * @param {obj:EachCallback} fn
30577 *        The callback function which is called for each key in the object.
30578 */
30579function each(object, fn) {
30580  keys(object).forEach(key => fn(object[key], key));
30581}
30582
30583/**
30584 * Array-like reduce for objects.
30585 *
30586 * @param {Object} object
30587 *        The Object that you want to reduce.
30588 *
30589 * @param {Function} fn
30590 *         A callback function which is called for each key in the object. It
30591 *         receives the accumulated value and the per-iteration value and key
30592 *         as arguments.
30593 *
30594 * @param {*} [initial = 0]
30595 *        Starting value
30596 *
30597 * @return {*}
30598 *         The final accumulated value.
30599 */
30600function reduce(object, fn, initial = 0) {
30601  return keys(object).reduce((accum, key) => fn(accum, object[key], key), initial);
30602}
30603
30604/**
30605 * Returns whether a value is an object of any kind - including DOM nodes,
30606 * arrays, regular expressions, etc. Not functions, though.
30607 *
30608 * This avoids the gotcha where using `typeof` on a `null` value
30609 * results in `'object'`.
30610 *
30611 * @param  {Object} value
30612 * @return {boolean}
30613 */
30614function isObject(value) {
30615  return !!value && typeof value === 'object';
30616}
30617
30618/**
30619 * Returns whether an object appears to be a "plain" object - that is, a
30620 * direct instance of `Object`.
30621 *
30622 * @param  {Object} value
30623 * @return {boolean}
30624 */
30625function isPlain(value) {
30626  return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object;
30627}
30628
30629/**
30630 * Merge two objects recursively.
30631 *
30632 * Performs a deep merge like
30633 * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges
30634 * plain objects (not arrays, elements, or anything else).
30635 *
30636 * Non-plain object values will be copied directly from the right-most
30637 * argument.
30638 *
30639 * @param   {Object[]} sources
30640 *          One or more objects to merge into a new object.
30641 *
30642 * @return {Object}
30643 *          A new object that is the merged result of all sources.
30644 */
30645function merge$1(...sources) {
30646  const result = {};
30647  sources.forEach(source => {
30648    if (!source) {
30649      return;
30650    }
30651    each(source, (value, key) => {
30652      if (!isPlain(value)) {
30653        result[key] = value;
30654        return;
30655      }
30656      if (!isPlain(result[key])) {
30657        result[key] = {};
30658      }
30659      result[key] = merge$1(result[key], value);
30660    });
30661  });
30662  return result;
30663}
30664
30665/**
30666 * Returns an array of values for a given object
30667 *
30668 * @param  {Object} source - target object
30669 * @return {Array<unknown>} - object values
30670 */
30671function values(source = {}) {
30672  const result = [];
30673  for (const key in source) {
30674    if (source.hasOwnProperty(key)) {
30675      const value = source[key];
30676      result.push(value);
30677    }
30678  }
30679  return result;
30680}
30681
30682/**
30683 * Object.defineProperty but "lazy", which means that the value is only set after
30684 * it is retrieved the first time, rather than being set right away.
30685 *
30686 * @param {Object} obj the object to set the property on
30687 * @param {string} key the key for the property to set
30688 * @param {Function} getValue the function used to get the value when it is needed.
30689 * @param {boolean} setter whether a setter should be allowed or not
30690 */
30691function defineLazyProperty(obj, key, getValue, setter = true) {
30692  const set = value => Object.defineProperty(obj, key, {
30693    value,
30694    enumerable: true,
30695    writable: true
30696  });
30697  const options = {
30698    configurable: true,
30699    enumerable: true,
30700    get() {
30701      const value = getValue();
30702      set(value);
30703      return value;
30704    }
30705  };
30706  if (setter) {
30707    options.set = set;
30708  }
30709  return Object.defineProperty(obj, key, options);
30710}
30711
30712var Obj = /*#__PURE__*/Object.freeze({
30713  __proto__: null,
30714  each: each,
30715  reduce: reduce,
30716  isObject: isObject,
30717  isPlain: isPlain,
30718  merge: merge$1,
30719  values: values,
30720  defineLazyProperty: defineLazyProperty
30721});
30722
30723/**
30724 * @file browser.js
30725 * @module browser
30726 */
30727
30728/**
30729 * Whether or not this device is an iPod.
30730 *
30731 * @static
30732 * @type {Boolean}
30733 */
30734let IS_IPOD = false;
vendor: 13,751 bytes, lines 30735-31241
30735
30736/**
30737 * The detected iOS version - or `null`.
30738 *
30739 * @static
30740 * @type {string|null}
30741 */
30742let IOS_VERSION = null;
30743
30744/**
30745 * Whether or not this is an Android device.
30746 *
30747 * @static
30748 * @type {Boolean}
30749 */
30750let IS_ANDROID = false;
30751
30752/**
30753 * The detected Android version - or `null` if not Android or indeterminable.
30754 *
30755 * @static
30756 * @type {number|string|null}
30757 */
30758let ANDROID_VERSION;
30759
30760/**
30761 * Whether or not this is Mozilla Firefox.
30762 *
30763 * @static
30764 * @type {Boolean}
30765 */
30766let IS_FIREFOX = false;
30767
30768/**
30769 * Whether or not this is Microsoft Edge.
30770 *
30771 * @static
30772 * @type {Boolean}
30773 */
30774let IS_EDGE = false;
30775
30776/**
30777 * Whether or not this is any Chromium Browser
30778 *
30779 * @static
30780 * @type {Boolean}
30781 */
30782let IS_CHROMIUM = false;
30783
30784/**
30785 * Whether or not this is any Chromium browser that is not Edge.
30786 *
30787 * This will also be `true` for Chrome on iOS, which will have different support
30788 * as it is actually Safari under the hood.
30789 *
30790 * Deprecated, as the behaviour to not match Edge was to prevent Legacy Edge's UA matching.
30791 * IS_CHROMIUM should be used instead.
30792 * "Chromium but not Edge" could be explicitly tested with IS_CHROMIUM && !IS_EDGE
30793 *
30794 * @static
30795 * @deprecated
30796 * @type {Boolean}
30797 */
30798let IS_CHROME = false;
30799
30800/**
30801 * The detected Chromium version - or `null`.
30802 *
30803 * @static
30804 * @type {number|null}
30805 */
30806let CHROMIUM_VERSION = null;
30807
30808/**
30809 * The detected Google Chrome version - or `null`.
30810 * This has always been the _Chromium_ version, i.e. would return on Chromium Edge.
30811 * Deprecated, use CHROMIUM_VERSION instead.
30812 *
30813 * @static
30814 * @deprecated
30815 * @type {number|null}
30816 */
30817let CHROME_VERSION = null;
30818
30819/**
30820 * Whether or not this is a Chromecast receiver application.
30821 *
30822 * @static
30823 * @type {Boolean}
30824 */
30825const IS_CHROMECAST_RECEIVER = Boolean(window__default["default"].cast && window__default["default"].cast.framework && window__default["default"].cast.framework.CastReceiverContext);
30826
30827/**
30828 * The detected Internet Explorer version - or `null`.
30829 *
30830 * @static
30831 * @deprecated
30832 * @type {number|null}
30833 */
30834let IE_VERSION = null;
30835
30836/**
30837 * Whether or not this is desktop Safari.
30838 *
30839 * @static
30840 * @type {Boolean}
30841 */
30842let IS_SAFARI = false;
30843
30844/**
30845 * Whether or not this is a Windows machine.
30846 *
30847 * @static
30848 * @type {Boolean}
30849 */
30850let IS_WINDOWS = false;
30851
30852/**
30853 * Whether or not this device is an iPad.
30854 *
30855 * @static
30856 * @type {Boolean}
30857 */
30858let IS_IPAD = false;
30859
30860/**
30861 * Whether or not this device is an iPhone.
30862 *
30863 * @static
30864 * @type {Boolean}
30865 */
30866// The Facebook app's UIWebView identifies as both an iPhone and iPad, so
30867// to identify iPhones, we need to exclude iPads.
30868// http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
30869let IS_IPHONE = false;
30870
30871/**
30872 * Whether or not this is a Tizen device.
30873 *
30874 * @static
30875 * @type {Boolean}
30876 */
30877let IS_TIZEN = false;
30878
30879/**
30880 * Whether or not this is a WebOS device.
30881 *
30882 * @static
30883 * @type {Boolean}
30884 */
30885let IS_WEBOS = false;
30886
30887/**
30888 * Whether or not this is a Smart TV (Tizen or WebOS) device.
30889 *
30890 * @static
30891 * @type {Boolean}
30892 */
30893let IS_SMART_TV = false;
30894
30895/**
30896 * Whether or not this device is touch-enabled.
30897 *
30898 * @static
30899 * @const
30900 * @type {Boolean}
30901 */
30902const TOUCH_ENABLED = Boolean(isReal() && ('ontouchstart' in window__default["default"] || window__default["default"].navigator.maxTouchPoints || window__default["default"].DocumentTouch && window__default["default"].document instanceof window__default["default"].DocumentTouch));
30903const UAD = window__default["default"].navigator && window__default["default"].navigator.userAgentData;
30904if (UAD && UAD.platform && UAD.brands) {
30905  // If userAgentData is present, use it instead of userAgent to avoid warnings
30906  // Currently only implemented on Chromium
30907  // userAgentData does not expose Android version, so ANDROID_VERSION remains `null`
30908
30909  IS_ANDROID = UAD.platform === 'Android';
30910  IS_EDGE = Boolean(UAD.brands.find(b => b.brand === 'Microsoft Edge'));
30911  IS_CHROMIUM = Boolean(UAD.brands.find(b => b.brand === 'Chromium'));
30912  IS_CHROME = !IS_EDGE && IS_CHROMIUM;
30913  CHROMIUM_VERSION = CHROME_VERSION = (UAD.brands.find(b => b.brand === 'Chromium') || {}).version || null;
30914  IS_WINDOWS = UAD.platform === 'Windows';
30915}
30916
30917// If the browser is not Chromium, either userAgentData is not present which could be an old Chromium browser,
30918//  or it's a browser that has added userAgentData since that we don't have tests for yet. In either case,
30919// the checks need to be made agiainst the regular userAgent string.
30920if (!IS_CHROMIUM) {
30921  const USER_AGENT = window__default["default"].navigator && window__default["default"].navigator.userAgent || '';
30922  IS_IPOD = /iPod/i.test(USER_AGENT);
30923  IOS_VERSION = function () {
30924    const match = USER_AGENT.match(/OS (\d+)_/i);
30925    if (match && match[1]) {
30926      return match[1];
30927    }
30928    return null;
30929  }();
30930  IS_ANDROID = /Android/i.test(USER_AGENT);
30931  ANDROID_VERSION = function () {
30932    // This matches Android Major.Minor.Patch versions
30933    // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
30934    const match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
30935    if (!match) {
30936      return null;
30937    }
30938    const major = match[1] && parseFloat(match[1]);
30939    const minor = match[2] && parseFloat(match[2]);
30940    if (major && minor) {
30941      return parseFloat(match[1] + '.' + match[2]);
30942    } else if (major) {
30943      return major;
30944    }
30945    return null;
30946  }();
30947  IS_FIREFOX = /Firefox/i.test(USER_AGENT);
30948  IS_EDGE = /Edg/i.test(USER_AGENT);
30949  IS_CHROMIUM = /Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT);
30950  IS_CHROME = !IS_EDGE && IS_CHROMIUM;
30951  CHROMIUM_VERSION = CHROME_VERSION = function () {
30952    const match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
30953    if (match && match[2]) {
30954      return parseFloat(match[2]);
30955    }
30956    return null;
30957  }();
30958  IE_VERSION = function () {
30959    const result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
30960    let version = result && parseFloat(result[1]);
30961    if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
30962      // IE 11 has a different user agent string than other IE versions
30963      version = 11.0;
30964    }
30965    return version;
30966  }();
30967  IS_TIZEN = /Tizen/i.test(USER_AGENT);
30968  IS_WEBOS = /Web0S/i.test(USER_AGENT);
30969  IS_SMART_TV = IS_TIZEN || IS_WEBOS;
30970  IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE && !IS_SMART_TV;
30971  IS_WINDOWS = /Windows/i.test(USER_AGENT);
30972  IS_IPAD = /iPad/i.test(USER_AGENT) || IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT);
30973  IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
30974}
30975
30976/**
30977 * Whether or not this is an iOS device.
30978 *
30979 * @static
30980 * @const
30981 * @type {Boolean}
30982 */
30983const IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
30984
30985/**
30986 * Whether or not this is any flavor of Safari - including iOS.
30987 *
30988 * @static
30989 * @const
30990 * @type {Boolean}
30991 */
30992const IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
30993
30994var browser = /*#__PURE__*/Object.freeze({
30995  __proto__: null,
30996  get IS_IPOD () { return IS_IPOD; },
30997  get IOS_VERSION () { return IOS_VERSION; },
30998  get IS_ANDROID () { return IS_ANDROID; },
30999  get ANDROID_VERSION () { return ANDROID_VERSION; },
31000  get IS_FIREFOX () { return IS_FIREFOX; },
31001  get IS_EDGE () { return IS_EDGE; },
31002  get IS_CHROMIUM () { return IS_CHROMIUM; },
31003  get IS_CHROME () { return IS_CHROME; },
31004  get CHROMIUM_VERSION () { return CHROMIUM_VERSION; },
31005  get CHROME_VERSION () { return CHROME_VERSION; },
31006  IS_CHROMECAST_RECEIVER: IS_CHROMECAST_RECEIVER,
31007  get IE_VERSION () { return IE_VERSION; },
31008  get IS_SAFARI () { return IS_SAFARI; },
31009  get IS_WINDOWS () { return IS_WINDOWS; },
31010  get IS_IPAD () { return IS_IPAD; },
31011  get IS_IPHONE () { return IS_IPHONE; },
31012  get IS_TIZEN () { return IS_TIZEN; },
31013  get IS_WEBOS () { return IS_WEBOS; },
31014  get IS_SMART_TV () { return IS_SMART_TV; },
31015  TOUCH_ENABLED: TOUCH_ENABLED,
31016  IS_IOS: IS_IOS,
31017  IS_ANY_SAFARI: IS_ANY_SAFARI
31018});
31019
31020/**
31021 * @file dom.js
31022 * @module dom
31023 */
31024
31025/**
31026 * Detect if a value is a string with any non-whitespace characters.
31027 *
31028 * @private
31029 * @param  {string} str
31030 *         The string to check
31031 *
31032 * @return {boolean}
31033 *         Will be `true` if the string is non-blank, `false` otherwise.
31034 *
31035 */
31036function isNonBlankString(str) {
31037  // we use str.trim as it will trim any whitespace characters
31038  // from the front or back of non-whitespace characters. aka
31039  // Any string that contains non-whitespace characters will
31040  // still contain them after `trim` but whitespace only strings
31041  // will have a length of 0, failing this check.
31042  return typeof str === 'string' && Boolean(str.trim());
31043}
31044
31045/**
31046 * Throws an error if the passed string has whitespace. This is used by
31047 * class methods to be relatively consistent with the classList API.
31048 *
31049 * @private
31050 * @param  {string} str
31051 *         The string to check for whitespace.
31052 *
31053 * @throws {Error}
31054 *         Throws an error if there is whitespace in the string.
31055 */
31056function throwIfWhitespace(str) {
31057  // str.indexOf instead of regex because str.indexOf is faster performance wise.
31058  if (str.indexOf(' ') >= 0) {
31059    throw new Error('class has illegal whitespace characters');
31060  }
31061}
31062
31063/**
31064 * Whether the current DOM interface appears to be real (i.e. not simulated).
31065 *
31066 * @return {boolean}
31067 *         Will be `true` if the DOM appears to be real, `false` otherwise.
31068 */
31069function isReal() {
31070  // Both document and window will never be undefined thanks to `global`.
31071  return document__default["default"] === window__default["default"].document;
31072}
31073
31074/**
31075 * Determines, via duck typing, whether or not a value is a DOM element.
31076 *
31077 * @param  {*} value
31078 *         The value to check.
31079 *
31080 * @return {boolean}
31081 *         Will be `true` if the value is a DOM element, `false` otherwise.
31082 */
31083function isEl(value) {
31084  return isObject(value) && value.nodeType === 1;
31085}
31086
31087/**
31088 * Determines if the current DOM is embedded in an iframe.
31089 *
31090 * @return {boolean}
31091 *         Will be `true` if the DOM is embedded in an iframe, `false`
31092 *         otherwise.
31093 */
31094function isInFrame() {
31095  // We need a try/catch here because Safari will throw errors when attempting
31096  // to get either `parent` or `self`
31097  try {
31098    return window__default["default"].parent !== window__default["default"].self;
31099  } catch (x) {
31100    return true;
31101  }
31102}
31103
31104/**
31105 * Creates functions to query the DOM using a given method.
31106 *
31107 * @private
31108 * @param   {string} method
31109 *          The method to create the query with.
31110 *
31111 * @return  {Function}
31112 *          The query method
31113 */
31114function createQuerier(method) {
31115  return function (selector, context) {
31116    if (!isNonBlankString(selector)) {
31117      return document__default["default"][method](null);
31118    }
31119    if (isNonBlankString(context)) {
31120      context = document__default["default"].querySelector(context);
31121    }
31122    const ctx = isEl(context) ? context : document__default["default"];
31123    return ctx[method] && ctx[method](selector);
31124  };
31125}
31126
31127/**
31128 * Creates an element and applies properties, attributes, and inserts content.
31129 *
31130 * @param  {string} [tagName='div']
31131 *         Name of tag to be created.
31132 *
31133 * @param  {Object} [properties={}]
31134 *         Element properties to be applied.
31135 *
31136 * @param  {Object} [attributes={}]
31137 *         Element attributes to be applied.
31138 *
31139 * @param {ContentDescriptor} [content]
31140 *        A content descriptor object.
31141 *
31142 * @return {Element}
31143 *         The element that was created.
31144 */
31145function createEl(tagName = 'div', properties = {}, attributes = {}, content) {
31146  const el = document__default["default"].createElement(tagName);
31147  Object.getOwnPropertyNames(properties).forEach(function (propName) {
31148    const val = properties[propName];
31149
31150    // Handle textContent since it's not supported everywhere and we have a
31151    // method for it.
31152    if (propName === 'textContent') {
31153      textContent(el, val);
31154    } else if (el[propName] !== val || propName === 'tabIndex') {
31155      el[propName] = val;
31156    }
31157  });
31158  Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
31159    el.setAttribute(attrName, attributes[attrName]);
31160  });
31161  if (content) {
31162    appendContent(el, content);
31163  }
31164  return el;
31165}
31166
31167/**
31168 * Injects text into an element, replacing any existing contents entirely.
31169 *
31170 * @param  {HTMLElement} el
31171 *         The element to add text content into
31172 *
31173 * @param  {string} text
31174 *         The text content to add.
31175 *
31176 * @return {Element}
31177 *         The element with added text content.
31178 */
31179function textContent(el, text) {
31180  if (typeof el.textContent === 'undefined') {
31181    el.innerText = text;
31182  } else {
31183    el.textContent = text;
31184  }
31185  return el;
31186}
31187
31188/**
31189 * Insert an element as the first child node of another
31190 *
31191 * @param {Element} child
31192 *        Element to insert
31193 *
31194 * @param {Element} parent
31195 *        Element to insert child into
31196 */
31197function prependTo(child, parent) {
31198  if (parent.firstChild) {
31199    parent.insertBefore(child, parent.firstChild);
31200  } else {
31201    parent.appendChild(child);
31202  }
31203}
31204
31205/**
31206 * Check if an element has a class name.
31207 *
31208 * @param  {Element} element
31209 *         Element to check
31210 *
31211 * @param  {string} classToCheck
31212 *         Class name to check for
31213 *
31214 * @return {boolean}
31215 *         Will be `true` if the element has a class, `false` otherwise.
31216 *
31217 * @throws {Error}
31218 *         Throws an error if `classToCheck` has white space.
31219 */
31220function hasClass(element, classToCheck) {
31221  throwIfWhitespace(classToCheck);
31222  return element.classList.contains(classToCheck);
31223}
31224
31225/**
31226 * Add a class name to an element.
31227 *
31228 * @param  {Element} element
31229 *         Element to add class name to.
31230 *
31231 * @param  {...string} classesToAdd
31232 *         One or more class name to add.
31233 *
31234 * @return {Element}
31235 *         The DOM element with the added class name.
31236 */
31237function addClass(element, ...classesToAdd) {
31238  element.classList.add(...classesToAdd.reduce((prev, current) => prev.concat(current.split(/\s+/)), []));
31239  return element;
31240}
31241
vendor: 5,204 bytes, lines 31242-31420
31242/**
31243 * Remove a class name from an element.
31244 *
31245 * @param  {Element} element
31246 *         Element to remove a class name from.
31247 *
31248 * @param  {...string} classesToRemove
31249 *         One or more class name to remove.
31250 *
31251 * @return {Element}
31252 *         The DOM element with class name removed.
31253 */
31254function removeClass(element, ...classesToRemove) {
31255  // Protect in case the player gets disposed
31256  if (!element) {
31257    log$1.warn("removeClass was called with an element that doesn't exist");
31258    return null;
31259  }
31260  element.classList.remove(...classesToRemove.reduce((prev, current) => prev.concat(current.split(/\s+/)), []));
31261  return element;
31262}
31263
31264/**
31265 * The callback definition for toggleClass.
31266 *
31267 * @callback PredicateCallback
31268 * @param    {Element} element
31269 *           The DOM element of the Component.
31270 *
31271 * @param    {string} classToToggle
31272 *           The `className` that wants to be toggled
31273 *
31274 * @return   {boolean|undefined}
31275 *           If `true` is returned, the `classToToggle` will be added to the
31276 *           `element`, but not removed. If `false`, the `classToToggle` will be removed from
31277 *           the `element`, but not added. If `undefined`, the callback will be ignored.
31278 *
31279 */
31280
31281/**
31282 * Adds or removes a class name to/from an element depending on an optional
31283 * condition or the presence/absence of the class name.
31284 *
31285 * @param  {Element} element
31286 *         The element to toggle a class name on.
31287 *
31288 * @param  {string} classToToggle
31289 *         The class that should be toggled.
31290 *
31291 * @param  {boolean|PredicateCallback} [predicate]
31292 *         See the return value for {@link module:dom~PredicateCallback}
31293 *
31294 * @return {Element}
31295 *         The element with a class that has been toggled.
31296 */
31297function toggleClass(element, classToToggle, predicate) {
31298  if (typeof predicate === 'function') {
31299    predicate = predicate(element, classToToggle);
31300  }
31301  if (typeof predicate !== 'boolean') {
31302    predicate = undefined;
31303  }
31304  classToToggle.split(/\s+/).forEach(className => element.classList.toggle(className, predicate));
31305  return element;
31306}
31307
31308/**
31309 * Apply attributes to an HTML element.
31310 *
31311 * @param {Element} el
31312 *        Element to add attributes to.
31313 *
31314 * @param {Object} [attributes]
31315 *        Attributes to be applied.
31316 */
31317function setAttributes(el, attributes) {
31318  Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
31319    const attrValue = attributes[attrName];
31320    if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
31321      el.removeAttribute(attrName);
31322    } else {
31323      el.setAttribute(attrName, attrValue === true ? '' : attrValue);
31324    }
31325  });
31326}
31327
31328/**
31329 * Get an element's attribute values, as defined on the HTML tag.
31330 *
31331 * Attributes are not the same as properties. They're defined on the tag
31332 * or with setAttribute.
31333 *
31334 * @param  {Element} tag
31335 *         Element from which to get tag attributes.
31336 *
31337 * @return {Object}
31338 *         All attributes of the element. Boolean attributes will be `true` or
31339 *         `false`, others will be strings.
31340 */
31341function getAttributes(tag) {
31342  const obj = {};
31343
31344  // known boolean attributes
31345  // we can check for matching boolean properties, but not all browsers
31346  // and not all tags know about these attributes, so, we still want to check them manually
31347  const knownBooleans = ['autoplay', 'controls', 'playsinline', 'loop', 'muted', 'default', 'defaultMuted'];
31348  if (tag && tag.attributes && tag.attributes.length > 0) {
31349    const attrs = tag.attributes;
31350    for (let i = attrs.length - 1; i >= 0; i--) {
31351      const attrName = attrs[i].name;
31352      /** @type {boolean|string} */
31353      let attrVal = attrs[i].value;
31354
31355      // check for known booleans
31356      // the matching element property will return a value for typeof
31357      if (knownBooleans.includes(attrName)) {
31358        // the value of an included boolean attribute is typically an empty
31359        // string ('') which would equal false if we just check for a false value.
31360        // we also don't want support bad code like autoplay='false'
31361        attrVal = attrVal !== null ? true : false;
31362      }
31363      obj[attrName] = attrVal;
31364    }
31365  }
31366  return obj;
31367}
31368
31369/**
31370 * Get the value of an element's attribute.
31371 *
31372 * @param {Element} el
31373 *        A DOM element.
31374 *
31375 * @param {string} attribute
31376 *        Attribute to get the value of.
31377 *
31378 * @return {string}
31379 *         The value of the attribute.
31380 */
31381function getAttribute(el, attribute) {
31382  return el.getAttribute(attribute);
31383}
31384
31385/**
31386 * Set the value of an element's attribute.
31387 *
31388 * @param {Element} el
31389 *        A DOM element.
31390 *
31391 * @param {string} attribute
31392 *        Attribute to set.
31393 *
31394 * @param {string} value
31395 *        Value to set the attribute to.
31396 */
31397function setAttribute(el, attribute, value) {
31398  el.setAttribute(attribute, value);
31399}
31400
31401/**
31402 * Remove an element's attribute.
31403 *
31404 * @param {Element} el
31405 *        A DOM element.
31406 *
31407 * @param {string} attribute
31408 *        Attribute to remove.
31409 */
31410function removeAttribute(el, attribute) {
31411  el.removeAttribute(attribute);
31412}
31413
31414/**
31415 * Attempt to block the ability to select text.
31416 */
31417function blockTextSelection() {
31418  document__default["default"].body.focus();
31419  document__default["default"].onselectstart = function () {
31420    return false;
vendor: 23,989 bytes, lines 31421-32222
31421  };
31422}
31423
31424/**
31425 * Turn off text selection blocking.
31426 */
31427function unblockTextSelection() {
31428  document__default["default"].onselectstart = function () {
31429    return true;
31430  };
31431}
31432
31433/**
31434 * Identical to the native `getBoundingClientRect` function, but ensures that
31435 * the method is supported at all (it is in all browsers we claim to support)
31436 * and that the element is in the DOM before continuing.
31437 *
31438 * This wrapper function also shims properties which are not provided by some
31439 * older browsers (namely, IE8).
31440 *
31441 * Additionally, some browsers do not support adding properties to a
31442 * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
31443 * properties (except `x` and `y` which are not widely supported). This helps
31444 * avoid implementations where keys are non-enumerable.
31445 *
31446 * @param  {Element} el
31447 *         Element whose `ClientRect` we want to calculate.
31448 *
31449 * @return {Object|undefined}
31450 *         Always returns a plain object - or `undefined` if it cannot.
31451 */
31452function getBoundingClientRect(el) {
31453  if (el && el.getBoundingClientRect && el.parentNode) {
31454    const rect = el.getBoundingClientRect();
31455    const result = {};
31456    ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(k => {
31457      if (rect[k] !== undefined) {
31458        result[k] = rect[k];
31459      }
31460    });
31461    if (!result.height) {
31462      result.height = parseFloat(computedStyle(el, 'height'));
31463    }
31464    if (!result.width) {
31465      result.width = parseFloat(computedStyle(el, 'width'));
31466    }
31467    return result;
31468  }
31469}
31470
31471/**
31472 * Represents the position of a DOM element on the page.
31473 *
31474 * @typedef  {Object} module:dom~Position
31475 *
31476 * @property {number} left
31477 *           Pixels to the left.
31478 *
31479 * @property {number} top
31480 *           Pixels from the top.
31481 */
31482
31483/**
31484 * Get the position of an element in the DOM.
31485 *
31486 * Uses `getBoundingClientRect` technique from John Resig.
31487 *
31488 * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
31489 *
31490 * @param  {Element} el
31491 *         Element from which to get offset.
31492 *
31493 * @return {module:dom~Position}
31494 *         The position of the element that was passed in.
31495 */
31496function findPosition(el) {
31497  if (!el || el && !el.offsetParent) {
31498    return {
31499      left: 0,
31500      top: 0,
31501      width: 0,
31502      height: 0
31503    };
31504  }
31505  const width = el.offsetWidth;
31506  const height = el.offsetHeight;
31507  let left = 0;
31508  let top = 0;
31509  while (el.offsetParent && el !== document__default["default"][FullscreenApi.fullscreenElement]) {
31510    left += el.offsetLeft;
31511    top += el.offsetTop;
31512    el = el.offsetParent;
31513  }
31514  return {
31515    left,
31516    top,
31517    width,
31518    height
31519  };
31520}
31521
31522/**
31523 * Represents x and y coordinates for a DOM element or mouse pointer.
31524 *
31525 * @typedef  {Object} module:dom~Coordinates
31526 *
31527 * @property {number} x
31528 *           x coordinate in pixels
31529 *
31530 * @property {number} y
31531 *           y coordinate in pixels
31532 */
31533
31534/**
31535 * Get the pointer position within an element.
31536 *
31537 * The base on the coordinates are the bottom left of the element.
31538 *
31539 * @param  {Element} el
31540 *         Element on which to get the pointer position on.
31541 *
31542 * @param  {Event} event
31543 *         Event object.
31544 *
31545 * @return {module:dom~Coordinates}
31546 *         A coordinates object corresponding to the mouse position.
31547 *
31548 */
31549function getPointerPosition(el, event) {
31550  const translated = {
31551    x: 0,
31552    y: 0
31553  };
31554  if (IS_IOS) {
31555    let item = el;
31556    while (item && item.nodeName.toLowerCase() !== 'html') {
31557      const transform = computedStyle(item, 'transform');
31558      if (/^matrix/.test(transform)) {
31559        const values = transform.slice(7, -1).split(/,\s/).map(Number);
31560        translated.x += values[4];
31561        translated.y += values[5];
31562      } else if (/^matrix3d/.test(transform)) {
31563        const values = transform.slice(9, -1).split(/,\s/).map(Number);
31564        translated.x += values[12];
31565        translated.y += values[13];
31566      }
31567      if (item.assignedSlot && item.assignedSlot.parentElement && window__default["default"].WebKitCSSMatrix) {
31568        const transformValue = window__default["default"].getComputedStyle(item.assignedSlot.parentElement).transform;
31569        const matrix = new window__default["default"].WebKitCSSMatrix(transformValue);
31570        translated.x += matrix.m41;
31571        translated.y += matrix.m42;
31572      }
31573      item = item.parentNode || item.host;
31574    }
31575  }
31576  const position = {};
31577  const boxTarget = findPosition(event.target);
31578  const box = findPosition(el);
31579  const boxW = box.width;
31580  const boxH = box.height;
31581  let offsetY = event.offsetY - (box.top - boxTarget.top);
31582  let offsetX = event.offsetX - (box.left - boxTarget.left);
31583  if (event.changedTouches) {
31584    offsetX = event.changedTouches[0].pageX - box.left;
31585    offsetY = event.changedTouches[0].pageY + box.top;
31586    if (IS_IOS) {
31587      offsetX -= translated.x;
31588      offsetY -= translated.y;
31589    }
31590  }
31591  position.y = 1 - Math.max(0, Math.min(1, offsetY / boxH));
31592  position.x = Math.max(0, Math.min(1, offsetX / boxW));
31593  return position;
31594}
31595
31596/**
31597 * Determines, via duck typing, whether or not a value is a text node.
31598 *
31599 * @param  {*} value
31600 *         Check if this value is a text node.
31601 *
31602 * @return {boolean}
31603 *         Will be `true` if the value is a text node, `false` otherwise.
31604 */
31605function isTextNode(value) {
31606  return isObject(value) && value.nodeType === 3;
31607}
31608
31609/**
31610 * Empties the contents of an element.
31611 *
31612 * @param  {Element} el
31613 *         The element to empty children from
31614 *
31615 * @return {Element}
31616 *         The element with no children
31617 */
31618function emptyEl(el) {
31619  while (el.firstChild) {
31620    el.removeChild(el.firstChild);
31621  }
31622  return el;
31623}
31624
31625/**
31626 * This is a mixed value that describes content to be injected into the DOM
31627 * via some method. It can be of the following types:
31628 *
31629 * Type       | Description
31630 * -----------|-------------
31631 * `string`   | The value will be normalized into a text node.
31632 * `Element`  | The value will be accepted as-is.
31633 * `Text`     | A TextNode. The value will be accepted as-is.
31634 * `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).
31635 * `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.
31636 *
31637 * @typedef {string|Element|Text|Array|Function} ContentDescriptor
31638 */
31639
31640/**
31641 * Normalizes content for eventual insertion into the DOM.
31642 *
31643 * This allows a wide range of content definition methods, but helps protect
31644 * from falling into the trap of simply writing to `innerHTML`, which could
31645 * be an XSS concern.
31646 *
31647 * The content for an element can be passed in multiple types and
31648 * combinations, whose behavior is as follows:
31649 *
31650 * @param {ContentDescriptor} content
31651 *        A content descriptor value.
31652 *
31653 * @return {Array}
31654 *         All of the content that was passed in, normalized to an array of
31655 *         elements or text nodes.
31656 */
31657function normalizeContent(content) {
31658  // First, invoke content if it is a function. If it produces an array,
31659  // that needs to happen before normalization.
31660  if (typeof content === 'function') {
31661    content = content();
31662  }
31663
31664  // Next up, normalize to an array, so one or many items can be normalized,
31665  // filtered, and returned.
31666  return (Array.isArray(content) ? content : [content]).map(value => {
31667    // First, invoke value if it is a function to produce a new value,
31668    // which will be subsequently normalized to a Node of some kind.
31669    if (typeof value === 'function') {
31670      value = value();
31671    }
31672    if (isEl(value) || isTextNode(value)) {
31673      return value;
31674    }
31675    if (typeof value === 'string' && /\S/.test(value)) {
31676      return document__default["default"].createTextNode(value);
31677    }
31678  }).filter(value => value);
31679}
31680
31681/**
31682 * Normalizes and appends content to an element.
31683 *
31684 * @param  {Element} el
31685 *         Element to append normalized content to.
31686 *
31687 * @param {ContentDescriptor} content
31688 *        A content descriptor value.
31689 *
31690 * @return {Element}
31691 *         The element with appended normalized content.
31692 */
31693function appendContent(el, content) {
31694  normalizeContent(content).forEach(node => el.appendChild(node));
31695  return el;
31696}
31697
31698/**
31699 * Normalizes and inserts content into an element; this is identical to
31700 * `appendContent()`, except it empties the element first.
31701 *
31702 * @param {Element} el
31703 *        Element to insert normalized content into.
31704 *
31705 * @param {ContentDescriptor} content
31706 *        A content descriptor value.
31707 *
31708 * @return {Element}
31709 *         The element with inserted normalized content.
31710 */
31711function insertContent(el, content) {
31712  return appendContent(emptyEl(el), content);
31713}
31714
31715/**
31716 * Check if an event was a single left click.
31717 *
31718 * @param  {MouseEvent} event
31719 *         Event object.
31720 *
31721 * @return {boolean}
31722 *         Will be `true` if a single left click, `false` otherwise.
31723 */
31724function isSingleLeftClick(event) {
31725  // Note: if you create something draggable, be sure to
31726  // call it on both `mousedown` and `mousemove` event,
31727  // otherwise `mousedown` should be enough for a button
31728
31729  if (event.button === undefined && event.buttons === undefined) {
31730    // Why do we need `buttons` ?
31731    // Because, middle mouse sometimes have this:
31732    // e.button === 0 and e.buttons === 4
31733    // Furthermore, we want to prevent combination click, something like
31734    // HOLD middlemouse then left click, that would be
31735    // e.button === 0, e.buttons === 5
31736    // just `button` is not gonna work
31737
31738    // Alright, then what this block does ?
31739    // this is for chrome `simulate mobile devices`
31740    // I want to support this as well
31741
31742    return true;
31743  }
31744  if (event.button === 0 && event.buttons === undefined) {
31745    // Touch screen, sometimes on some specific device, `buttons`
31746    // doesn't have anything (safari on ios, blackberry...)
31747
31748    return true;
31749  }
31750
31751  // `mouseup` event on a single left click has
31752  // `button` and `buttons` equal to 0
31753  if (event.type === 'mouseup' && event.button === 0 && event.buttons === 0) {
31754    return true;
31755  }
31756
31757  // MacOS Sonoma trackpad when "tap to click enabled"
31758  if (event.type === 'mousedown' && event.button === 0 && event.buttons === 0) {
31759    return true;
31760  }
31761  if (event.button !== 0 || event.buttons !== 1) {
31762    // This is the reason we have those if else block above
31763    // if any special case we can catch and let it slide
31764    // we do it above, when get to here, this definitely
31765    // is-not-left-click
31766
31767    return false;
31768  }
31769  return true;
31770}
31771
31772/**
31773 * Finds a single DOM element matching `selector` within the optional
31774 * `context` of another DOM element (defaulting to `document`).
31775 *
31776 * @param  {string} selector
31777 *         A valid CSS selector, which will be passed to `querySelector`.
31778 *
31779 * @param  {Element|String} [context=document]
31780 *         A DOM element within which to query. Can also be a selector
31781 *         string in which case the first matching element will be used
31782 *         as context. If missing (or no element matches selector), falls
31783 *         back to `document`.
31784 *
31785 * @return {Element|null}
31786 *         The element that was found or null.
31787 */
31788const $ = createQuerier('querySelector');
31789
31790/**
31791 * Finds a all DOM elements matching `selector` within the optional
31792 * `context` of another DOM element (defaulting to `document`).
31793 *
31794 * @param  {string} selector
31795 *         A valid CSS selector, which will be passed to `querySelectorAll`.
31796 *
31797 * @param  {Element|String} [context=document]
31798 *         A DOM element within which to query. Can also be a selector
31799 *         string in which case the first matching element will be used
31800 *         as context. If missing (or no element matches selector), falls
31801 *         back to `document`.
31802 *
31803 * @return {NodeList}
31804 *         A element list of elements that were found. Will be empty if none
31805 *         were found.
31806 *
31807 */
31808const $$ = createQuerier('querySelectorAll');
31809
31810/**
31811 * A safe getComputedStyle.
31812 *
31813 * This is needed because in Firefox, if the player is loaded in an iframe with
31814 * `display:none`, then `getComputedStyle` returns `null`, so, we do a
31815 * null-check to make sure that the player doesn't break in these cases.
31816 *
31817 * @param    {Element} el
31818 *           The element you want the computed style of
31819 *
31820 * @param    {string} prop
31821 *           The property name you want
31822 *
31823 * @see      https://bugzilla.mozilla.org/show_bug.cgi?id=548397
31824 */
31825function computedStyle(el, prop) {
31826  if (!el || !prop) {
31827    return '';
31828  }
31829  if (typeof window__default["default"].getComputedStyle === 'function') {
31830    let computedStyleValue;
31831    try {
31832      computedStyleValue = window__default["default"].getComputedStyle(el);
31833    } catch (e) {
31834      return '';
31835    }
31836    return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : '';
31837  }
31838  return '';
31839}
31840
31841/**
31842 * Copy document style sheets to another window.
31843 *
31844 * @param    {Window} win
31845 *           The window element you want to copy the document style sheets to.
31846 *
31847 */
31848function copyStyleSheetsToWindow(win) {
31849  [...document__default["default"].styleSheets].forEach(styleSheet => {
31850    try {
31851      const cssRules = [...styleSheet.cssRules].map(rule => rule.cssText).join('');
31852      const style = document__default["default"].createElement('style');
31853      style.textContent = cssRules;
31854      win.document.head.appendChild(style);
31855    } catch (e) {
31856      const link = document__default["default"].createElement('link');
31857      link.rel = 'stylesheet';
31858      link.type = styleSheet.type;
31859      // For older Safari this has to be the string; on other browsers setting the MediaList works
31860      link.media = styleSheet.media.mediaText;
31861      link.href = styleSheet.href;
31862      win.document.head.appendChild(link);
31863    }
31864  });
31865}
31866
31867var Dom = /*#__PURE__*/Object.freeze({
31868  __proto__: null,
31869  isReal: isReal,
31870  isEl: isEl,
31871  isInFrame: isInFrame,
31872  createEl: createEl,
31873  textContent: textContent,
31874  prependTo: prependTo,
31875  hasClass: hasClass,
31876  addClass: addClass,
31877  removeClass: removeClass,
31878  toggleClass: toggleClass,
31879  setAttributes: setAttributes,
31880  getAttributes: getAttributes,
31881  getAttribute: getAttribute,
31882  setAttribute: setAttribute,
31883  removeAttribute: removeAttribute,
31884  blockTextSelection: blockTextSelection,
31885  unblockTextSelection: unblockTextSelection,
31886  getBoundingClientRect: getBoundingClientRect,
31887  findPosition: findPosition,
31888  getPointerPosition: getPointerPosition,
31889  isTextNode: isTextNode,
31890  emptyEl: emptyEl,
31891  normalizeContent: normalizeContent,
31892  appendContent: appendContent,
31893  insertContent: insertContent,
31894  isSingleLeftClick: isSingleLeftClick,
31895  $: $,
31896  $$: $$,
31897  computedStyle: computedStyle,
31898  copyStyleSheetsToWindow: copyStyleSheetsToWindow
31899});
31900
31901/**
31902 * @file setup.js - Functions for setting up a player without
31903 * user interaction based on the data-setup `attribute` of the video tag.
31904 *
31905 * @module setup
31906 */
31907let _windowLoaded = false;
31908let videojs$1;
31909
31910/**
31911 * Set up any tags that have a data-setup `attribute` when the player is started.
31912 */
31913const autoSetup = function () {
31914  if (videojs$1.options.autoSetup === false) {
31915    return;
31916  }
31917  const vids = Array.prototype.slice.call(document__default["default"].getElementsByTagName('video'));
31918  const audios = Array.prototype.slice.call(document__default["default"].getElementsByTagName('audio'));
31919  const divs = Array.prototype.slice.call(document__default["default"].getElementsByTagName('video-js'));
31920  const mediaEls = vids.concat(audios, divs);
31921
31922  // Check if any media elements exist
31923  if (mediaEls && mediaEls.length > 0) {
31924    for (let i = 0, e = mediaEls.length; i < e; i++) {
31925      const mediaEl = mediaEls[i];
31926
31927      // Check if element exists, has getAttribute func.
31928      if (mediaEl && mediaEl.getAttribute) {
31929        // Make sure this player hasn't already been set up.
31930        if (mediaEl.player === undefined) {
31931          const options = mediaEl.getAttribute('data-setup');
31932
31933          // Check if data-setup attr exists.
31934          // We only auto-setup if they've added the data-setup attr.
31935          if (options !== null) {
31936            // Create new video.js instance.
31937            videojs$1(mediaEl);
31938          }
31939        }
31940
31941        // If getAttribute isn't defined, we need to wait for the DOM.
31942      } else {
31943        autoSetupTimeout(1);
31944        break;
31945      }
31946    }
31947
31948    // No videos were found, so keep looping unless page is finished loading.
31949  } else if (!_windowLoaded) {
31950    autoSetupTimeout(1);
31951  }
31952};
31953
31954/**
31955 * Wait until the page is loaded before running autoSetup. This will be called in
31956 * autoSetup if `hasLoaded` returns false.
31957 *
31958 * @param {number} wait
31959 *        How long to wait in ms
31960 *
31961 * @param {module:videojs} [vjs]
31962 *        The videojs library function
31963 */
31964function autoSetupTimeout(wait, vjs) {
31965  // Protect against breakage in non-browser environments
31966  if (!isReal()) {
31967    return;
31968  }
31969  if (vjs) {
31970    videojs$1 = vjs;
31971  }
31972  window__default["default"].setTimeout(autoSetup, wait);
31973}
31974
31975/**
31976 * Used to set the internal tracking of window loaded state to true.
31977 *
31978 * @private
31979 */
31980function setWindowLoaded() {
31981  _windowLoaded = true;
31982  window__default["default"].removeEventListener('load', setWindowLoaded);
31983}
31984if (isReal()) {
31985  if (document__default["default"].readyState === 'complete') {
31986    setWindowLoaded();
31987  } else {
31988    /**
31989     * Listen for the load event on window, and set _windowLoaded to true.
31990     *
31991     * We use a standard event listener here to avoid incrementing the GUID
31992     * before any players are created.
31993     *
31994     * @listens load
31995     */
31996    window__default["default"].addEventListener('load', setWindowLoaded);
31997  }
31998}
31999
32000/**
32001 * @file stylesheet.js
32002 * @module stylesheet
32003 */
32004
32005/**
32006 * Create a DOM style element given a className for it.
32007 *
32008 * @param {string} className
32009 *        The className to add to the created style element.
32010 *
32011 * @return {Element}
32012 *         The element that was created.
32013 */
32014const createStyleElement = function (className) {
32015  const style = document__default["default"].createElement('style');
32016  style.className = className;
32017  return style;
32018};
32019
32020/**
32021 * Add text to a DOM element.
32022 *
32023 * @param {Element} el
32024 *        The Element to add text content to.
32025 *
32026 * @param {string} content
32027 *        The text to add to the element.
32028 */
32029const setTextContent = function (el, content) {
32030  if (el.styleSheet) {
32031    el.styleSheet.cssText = content;
32032  } else {
32033    el.textContent = content;
32034  }
32035};
32036
32037/**
32038 * @file dom-data.js
32039 * @module dom-data
32040 */
32041
32042/**
32043 * Element Data Store.
32044 *
32045 * Allows for binding data to an element without putting it directly on the
32046 * element. Ex. Event listeners are stored here.
32047 * (also from jsninja.com, slightly modified and updated for closure compiler)
32048 *
32049 * @type {Object}
32050 * @private
32051 */
32052var DomData = new WeakMap();
32053
32054/**
32055 * @file guid.js
32056 * @module guid
32057 */
32058
32059// Default value for GUIDs. This allows us to reset the GUID counter in tests.
32060//
32061// The initial GUID is 3 because some users have come to rely on the first
32062// default player ID ending up as `vjs_video_3`.
32063//
32064// See: https://github.com/videojs/video.js/pull/6216
32065const _initialGuid = 3;
32066
32067/**
32068 * Unique ID for an element or function
32069 *
32070 * @type {Number}
32071 */
32072let _guid = _initialGuid;
32073
32074/**
32075 * Get a unique auto-incrementing ID by number that has not been returned before.
32076 *
32077 * @return {number}
32078 *         A new unique ID.
32079 */
32080function newGUID() {
32081  return _guid++;
32082}
32083
32084/**
32085 * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
32086 * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
32087 * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
32088 * robust as jquery's, so there's probably some differences.
32089 *
32090 * @file events.js
32091 * @module events
32092 */
32093
32094/**
32095 * Clean up the listener cache and dispatchers
32096 *
32097 * @param {Element|Object} elem
32098 *        Element to clean up
32099 *
32100 * @param {string} type
32101 *        Type of event to clean up
32102 */
32103function _cleanUpEvents(elem, type) {
32104  if (!DomData.has(elem)) {
32105    return;
32106  }
32107  const data = DomData.get(elem);
32108
32109  // Remove the events of a particular type if there are none left
32110  if (data.handlers[type].length === 0) {
32111    delete data.handlers[type];
32112    // data.handlers[type] = null;
32113    // Setting to null was causing an error with data.handlers
32114
32115    // Remove the meta-handler from the element
32116    if (elem.removeEventListener) {
32117      elem.removeEventListener(type, data.dispatcher, false);
32118    } else if (elem.detachEvent) {
32119      elem.detachEvent('on' + type, data.dispatcher);
32120    }
32121  }
32122
32123  // Remove the events object if there are no types left
32124  if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
32125    delete data.handlers;
32126    delete data.dispatcher;
32127    delete data.disabled;
32128  }
32129
32130  // Finally remove the element data if there is no data left
32131  if (Object.getOwnPropertyNames(data).length === 0) {
32132    DomData.delete(elem);
32133  }
32134}
32135
32136/**
32137 * Loops through an array of event types and calls the requested method for each type.
32138 *
32139 * @param {Function} fn
32140 *        The event method we want to use.
32141 *
32142 * @param {Element|Object} elem
32143 *        Element or object to bind listeners to
32144 *
32145 * @param {string[]} types
32146 *        Type of event to bind to.
32147 *
32148 * @param {Function} callback
32149 *        Event listener.
32150 */
32151function _handleMultipleEvents(fn, elem, types, callback) {
32152  types.forEach(function (type) {
32153    // Call the event method for each one of the types
32154    fn(elem, type, callback);
32155  });
32156}
32157
32158/**
32159 * Fix a native event to have standard property values
32160 *
32161 * @param {Object} event
32162 *        Event object to fix.
32163 *
32164 * @return {Object}
32165 *         Fixed event object.
32166 */
32167function fixEvent(event) {
32168  if (event.fixed_) {
32169    return event;
32170  }
32171  function returnTrue() {
32172    return true;
32173  }
32174  function returnFalse() {
32175    return false;
32176  }
32177
32178  // Test if fixing up is needed
32179  // Used to check if !event.stopPropagation instead of isPropagationStopped
32180  // But native events return true for stopPropagation, but don't have
32181  // other expected methods like isPropagationStopped. Seems to be a problem
32182  // with the Javascript Ninja code. So we're just overriding all events now.
32183  if (!event || !event.isPropagationStopped || !event.isImmediatePropagationStopped) {
32184    const old = event || window__default["default"].event;
32185    event = {};
32186    // Clone the old object so that we can modify the values event = {};
32187    // IE8 Doesn't like when you mess with native event properties
32188    // Firefox returns false for event.hasOwnProperty('type') and other props
32189    //  which makes copying more difficult.
32190
32191    // TODO: Probably best to create an allowlist of event props
32192    const deprecatedProps = ['layerX', 'layerY', 'keyLocation', 'path', 'webkitMovementX', 'webkitMovementY', 'mozPressure', 'mozInputSource'];
32193    for (const key in old) {
32194      // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
32195      // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
32196      // and webkitMovementX/Y
32197      // Lighthouse complains if Event.path is copied
32198      if (!deprecatedProps.includes(key)) {
32199        // Chrome 32+ warns if you try to copy deprecated returnValue, but
32200        // we still want to if preventDefault isn't supported (IE8).
32201        if (!(key === 'returnValue' && old.preventDefault)) {
32202          event[key] = old[key];
32203        }
32204      }
32205    }
32206
32207    // The event occurred on this element
32208    if (!event.target) {
32209      event.target = event.srcElement || document__default["default"];
32210    }
32211
32212    // Handle which other element the event is related to
32213    if (!event.relatedTarget) {
32214      event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
32215    }
32216
32217    // Stop the default browser action
32218    event.preventDefault = function () {
32219      if (old.preventDefault) {
32220        old.preventDefault();
32221      }
32222      event.returnValue = false;
vendor: 4,237 bytes, lines 32223-32364
32223      old.returnValue = false;
32224      event.defaultPrevented = true;
32225    };
32226    event.defaultPrevented = false;
32227
32228    // Stop the event from bubbling
32229    event.stopPropagation = function () {
32230      if (old.stopPropagation) {
32231        old.stopPropagation();
32232      }
32233      event.cancelBubble = true;
32234      old.cancelBubble = true;
32235      event.isPropagationStopped = returnTrue;
32236    };
32237    event.isPropagationStopped = returnFalse;
32238
32239    // Stop the event from bubbling and executing other handlers
32240    event.stopImmediatePropagation = function () {
32241      if (old.stopImmediatePropagation) {
32242        old.stopImmediatePropagation();
32243      }
32244      event.isImmediatePropagationStopped = returnTrue;
32245      event.stopPropagation();
32246    };
32247    event.isImmediatePropagationStopped = returnFalse;
32248
32249    // Handle mouse position
32250    if (event.clientX !== null && event.clientX !== undefined) {
32251      const doc = document__default["default"].documentElement;
32252      const body = document__default["default"].body;
32253      event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
32254      event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
32255    }
32256
32257    // Handle key presses
32258    event.which = event.charCode || event.keyCode;
32259
32260    // Fix button for mouse clicks:
32261    // 0 == left; 1 == middle; 2 == right
32262    if (event.button !== null && event.button !== undefined) {
32263      // The following is disabled because it does not pass videojs-standard
32264      // and... yikes.
32265      /* eslint-disable */
32266      event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
32267      /* eslint-enable */
32268    }
32269  }
32270  event.fixed_ = true;
32271  // Returns fixed-up instance
32272  return event;
32273}
32274
32275/**
32276 * Whether passive event listeners are supported
32277 */
32278let _supportsPassive;
32279const supportsPassive = function () {
32280  if (typeof _supportsPassive !== 'boolean') {
32281    _supportsPassive = false;
32282    try {
32283      const opts = Object.defineProperty({}, 'passive', {
32284        get() {
32285          _supportsPassive = true;
32286        }
32287      });
32288      window__default["default"].addEventListener('test', null, opts);
32289      window__default["default"].removeEventListener('test', null, opts);
32290    } catch (e) {
32291      // disregard
32292    }
32293  }
32294  return _supportsPassive;
32295};
32296
32297/**
32298 * Touch events Chrome expects to be passive
32299 */
32300const passiveEvents = ['touchstart', 'touchmove'];
32301
32302/**
32303 * Add an event listener to element
32304 * It stores the handler function in a separate cache object
32305 * and adds a generic handler to the element's event,
32306 * along with a unique id (guid) to the element.
32307 *
32308 * @param {Element|Object} elem
32309 *        Element or object to bind listeners to
32310 *
32311 * @param {string|string[]} type
32312 *        Type of event to bind to.
32313 *
32314 * @param {Function} fn
32315 *        Event listener.
32316 */
32317function on(elem, type, fn) {
32318  if (Array.isArray(type)) {
32319    return _handleMultipleEvents(on, elem, type, fn);
32320  }
32321  if (!DomData.has(elem)) {
32322    DomData.set(elem, {});
32323  }
32324  const data = DomData.get(elem);
32325
32326  // We need a place to store all our handler data
32327  if (!data.handlers) {
32328    data.handlers = {};
32329  }
32330  if (!data.handlers[type]) {
32331    data.handlers[type] = [];
32332  }
32333  if (!fn.guid) {
32334    fn.guid = newGUID();
32335  }
32336  data.handlers[type].push(fn);
32337  if (!data.dispatcher) {
32338    data.disabled = false;
32339    data.dispatcher = function (event, hash) {
32340      if (data.disabled) {
32341        return;
32342      }
32343      event = fixEvent(event);
32344      const handlers = data.handlers[event.type];
32345      if (handlers) {
32346        // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
32347        const handlersCopy = handlers.slice(0);
32348        for (let m = 0, n = handlersCopy.length; m < n; m++) {
32349          if (event.isImmediatePropagationStopped()) {
32350            break;
32351          } else {
32352            try {
32353              handlersCopy[m].call(elem, event, hash);
32354            } catch (e) {
32355              log$1.error(e);
32356            }
32357          }
32358        }
32359      }
32360    };
32361  }
32362  if (data.handlers[type].length === 1) {
32363    if (elem.addEventListener) {
32364      let options = false;
vendor: 4,196 bytes, lines 32365-32508
32365      if (supportsPassive() && passiveEvents.indexOf(type) > -1) {
32366        options = {
32367          passive: true
32368        };
32369      }
32370      elem.addEventListener(type, data.dispatcher, options);
32371    } else if (elem.attachEvent) {
32372      elem.attachEvent('on' + type, data.dispatcher);
32373    }
32374  }
32375}
32376
32377/**
32378 * Removes event listeners from an element
32379 *
32380 * @param {Element|Object} elem
32381 *        Object to remove listeners from.
32382 *
32383 * @param {string|string[]} [type]
32384 *        Type of listener to remove. Don't include to remove all events from element.
32385 *
32386 * @param {Function} [fn]
32387 *        Specific listener to remove. Don't include to remove listeners for an event
32388 *        type.
32389 */
32390function off(elem, type, fn) {
32391  // Don't want to add a cache object through getElData if not needed
32392  if (!DomData.has(elem)) {
32393    return;
32394  }
32395  const data = DomData.get(elem);
32396
32397  // If no events exist, nothing to unbind
32398  if (!data.handlers) {
32399    return;
32400  }
32401  if (Array.isArray(type)) {
32402    return _handleMultipleEvents(off, elem, type, fn);
32403  }
32404
32405  // Utility function
32406  const removeType = function (el, t) {
32407    data.handlers[t] = [];
32408    _cleanUpEvents(el, t);
32409  };
32410
32411  // Are we removing all bound events?
32412  if (type === undefined) {
32413    for (const t in data.handlers) {
32414      if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
32415        removeType(elem, t);
32416      }
32417    }
32418    return;
32419  }
32420  const handlers = data.handlers[type];
32421
32422  // If no handlers exist, nothing to unbind
32423  if (!handlers) {
32424    return;
32425  }
32426
32427  // If no listener was provided, remove all listeners for type
32428  if (!fn) {
32429    removeType(elem, type);
32430    return;
32431  }
32432
32433  // We're only removing a single handler
32434  if (fn.guid) {
32435    for (let n = 0; n < handlers.length; n++) {
32436      if (handlers[n].guid === fn.guid) {
32437        handlers.splice(n--, 1);
32438      }
32439    }
32440  }
32441  _cleanUpEvents(elem, type);
32442}
32443
32444/**
32445 * Trigger an event for an element
32446 *
32447 * @param {Element|Object} elem
32448 *        Element to trigger an event on
32449 *
32450 * @param {EventTarget~Event|string} event
32451 *        A string (the type) or an event object with a type attribute
32452 *
32453 * @param {Object} [hash]
32454 *        data hash to pass along with the event
32455 *
32456 * @return {boolean|undefined}
32457 *         Returns the opposite of `defaultPrevented` if default was
32458 *         prevented. Otherwise, returns `undefined`
32459 */
32460function trigger(elem, event, hash) {
32461  // Fetches element data and a reference to the parent (for bubbling).
32462  // Don't want to add a data object to cache for every parent,
32463  // so checking hasElData first.
32464  const elemData = DomData.has(elem) ? DomData.get(elem) : {};
32465  const parent = elem.parentNode || elem.ownerDocument;
32466  // type = event.type || event,
32467  // handler;
32468
32469  // If an event name was passed as a string, creates an event out of it
32470  if (typeof event === 'string') {
32471    event = {
32472      type: event,
32473      target: elem
32474    };
32475  } else if (!event.target) {
32476    event.target = elem;
32477  }
32478
32479  // Normalizes the event properties.
32480  event = fixEvent(event);
32481
32482  // If the passed element has a dispatcher, executes the established handlers.
32483  if (elemData.dispatcher) {
32484    elemData.dispatcher.call(elem, event, hash);
32485  }
32486
32487  // Unless explicitly stopped or the event does not bubble (e.g. media events)
32488  // recursively calls this function to bubble the event up the DOM.
32489  if (parent && !event.isPropagationStopped() && event.bubbles === true) {
32490    trigger.call(null, parent, event, hash);
32491
32492    // If at the top of the DOM, triggers the default action unless disabled.
32493  } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
32494    if (!DomData.has(event.target)) {
32495      DomData.set(event.target, {});
32496    }
32497    const targetData = DomData.get(event.target);
32498
32499    // Checks if the target has a default action for this event.
32500    if (event.target[event.type]) {
32501      // Temporarily disables event dispatching on the target as we have already executed the handler.
32502      targetData.disabled = true;
32503      // Executes the default action.
32504      if (typeof event.target[event.type] === 'function') {
32505        event.target[event.type]();
32506      }
32507      // Re-enables event dispatching.
32508      targetData.disabled = false;
vendor: 5,945 bytes, lines 32509-32728
32509    }
32510  }
32511
32512  // Inform the triggerer if the default was prevented by returning false
32513  return !event.defaultPrevented;
32514}
32515
32516/**
32517 * Trigger a listener only once for an event.
32518 *
32519 * @param {Element|Object} elem
32520 *        Element or object to bind to.
32521 *
32522 * @param {string|string[]} type
32523 *        Name/type of event
32524 *
32525 * @param {Event~EventListener} fn
32526 *        Event listener function
32527 */
32528function one(elem, type, fn) {
32529  if (Array.isArray(type)) {
32530    return _handleMultipleEvents(one, elem, type, fn);
32531  }
32532  const func = function () {
32533    off(elem, type, func);
32534    fn.apply(this, arguments);
32535  };
32536
32537  // copy the guid to the new function so it can removed using the original function's ID
32538  func.guid = fn.guid = fn.guid || newGUID();
32539  on(elem, type, func);
32540}
32541
32542/**
32543 * Trigger a listener only once and then turn if off for all
32544 * configured events
32545 *
32546 * @param {Element|Object} elem
32547 *        Element or object to bind to.
32548 *
32549 * @param {string|string[]} type
32550 *        Name/type of event
32551 *
32552 * @param {Event~EventListener} fn
32553 *        Event listener function
32554 */
32555function any(elem, type, fn) {
32556  const func = function () {
32557    off(elem, type, func);
32558    fn.apply(this, arguments);
32559  };
32560
32561  // copy the guid to the new function so it can removed using the original function's ID
32562  func.guid = fn.guid = fn.guid || newGUID();
32563
32564  // multiple ons, but one off for everything
32565  on(elem, type, func);
32566}
32567
32568var Events = /*#__PURE__*/Object.freeze({
32569  __proto__: null,
32570  fixEvent: fixEvent,
32571  on: on,
32572  off: off,
32573  trigger: trigger,
32574  one: one,
32575  any: any
32576});
32577
32578/**
32579 * @file fn.js
32580 * @module fn
32581 */
32582const UPDATE_REFRESH_INTERVAL = 30;
32583
32584/**
32585 * A private, internal-only function for changing the context of a function.
32586 *
32587 * It also stores a unique id on the function so it can be easily removed from
32588 * events.
32589 *
32590 * @private
32591 * @function
32592 * @param    {*} context
32593 *           The object to bind as scope.
32594 *
32595 * @param    {Function} fn
32596 *           The function to be bound to a scope.
32597 *
32598 * @param    {number} [uid]
32599 *           An optional unique ID for the function to be set
32600 *
32601 * @return   {Function}
32602 *           The new function that will be bound into the context given
32603 */
32604const bind_ = function (context, fn, uid) {
32605  // Make sure the function has a unique ID
32606  if (!fn.guid) {
32607    fn.guid = newGUID();
32608  }
32609
32610  // Create the new function that changes the context
32611  const bound = fn.bind(context);
32612
32613  // Allow for the ability to individualize this function
32614  // Needed in the case where multiple objects might share the same prototype
32615  // IF both items add an event listener with the same function, then you try to remove just one
32616  // it will remove both because they both have the same guid.
32617  // when using this, you need to use the bind method when you remove the listener as well.
32618  // currently used in text tracks
32619  bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
32620  return bound;
32621};
32622
32623/**
32624 * Wraps the given function, `fn`, with a new function that only invokes `fn`
32625 * at most once per every `wait` milliseconds.
32626 *
32627 * @function
32628 * @param    {Function} fn
32629 *           The function to be throttled.
32630 *
32631 * @param    {number}   wait
32632 *           The number of milliseconds by which to throttle.
32633 *
32634 * @return   {Function}
32635 */
32636const throttle = function (fn, wait) {
32637  let last = window__default["default"].performance.now();
32638  const throttled = function (...args) {
32639    const now = window__default["default"].performance.now();
32640    if (now - last >= wait) {
32641      fn(...args);
32642      last = now;
32643    }
32644  };
32645  return throttled;
32646};
32647
32648/**
32649 * Creates a debounced function that delays invoking `func` until after `wait`
32650 * milliseconds have elapsed since the last time the debounced function was
32651 * invoked.
32652 *
32653 * Inspired by lodash and underscore implementations.
32654 *
32655 * @function
32656 * @param    {Function} func
32657 *           The function to wrap with debounce behavior.
32658 *
32659 * @param    {number} wait
32660 *           The number of milliseconds to wait after the last invocation.
32661 *
32662 * @param    {boolean} [immediate]
32663 *           Whether or not to invoke the function immediately upon creation.
32664 *
32665 * @param    {Object} [context=window]
32666 *           The "context" in which the debounced function should debounce. For
32667 *           example, if this function should be tied to a Video.js player,
32668 *           the player can be passed here. Alternatively, defaults to the
32669 *           global `window` object.
32670 *
32671 * @return   {Function}
32672 *           A debounced function.
32673 */
32674const debounce$1 = function (func, wait, immediate, context = window__default["default"]) {
32675  let timeout;
32676  const cancel = () => {
32677    context.clearTimeout(timeout);
32678    timeout = null;
32679  };
32680
32681  /* eslint-disable consistent-this */
32682  const debounced = function () {
32683    const self = this;
32684    const args = arguments;
32685    let later = function () {
32686      timeout = null;
32687      later = null;
32688      if (!immediate) {
32689        func.apply(self, args);
32690      }
32691    };
32692    if (!timeout && immediate) {
32693      func.apply(self, args);
32694    }
32695    context.clearTimeout(timeout);
32696    timeout = context.setTimeout(later, wait);
32697  };
32698  /* eslint-enable consistent-this */
32699
32700  debounced.cancel = cancel;
32701  return debounced;
32702};
32703
32704var Fn = /*#__PURE__*/Object.freeze({
32705  __proto__: null,
32706  UPDATE_REFRESH_INTERVAL: UPDATE_REFRESH_INTERVAL,
32707  bind_: bind_,
32708  throttle: throttle,
32709  debounce: debounce$1
32710});
32711
32712/**
32713 * @file src/js/event-target.js
32714 */
32715let EVENT_MAP;
32716
32717/**
32718 * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
32719 * adds shorthand functions that wrap around lengthy functions. For example:
32720 * the `on` function is a wrapper around `addEventListener`.
32721 *
32722 * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
32723 * @class EventTarget
32724 */
32725class EventTarget$2 {
32726  /**
32727   * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
32728   * function that will get called when an event with a certain 
vendor: 4,752 bytes, lines 32728-32879
32728name gets triggered.
32729   *
32730   * @param {string|string[]} type
32731   *        An event name or an array of event names.
32732   *
32733   * @param {Function} fn
32734   *        The function to call with `EventTarget`s
32735   */
32736  on(type, fn) {
32737    // Remove the addEventListener alias before calling Events.on
32738    // so we don't get into an infinite type loop
32739    const ael = this.addEventListener;
32740    this.addEventListener = () => {};
32741    on(this, type, fn);
32742    this.addEventListener = ael;
32743  }
32744  /**
32745   * Removes an `event listener` for a specific event from an instance of `EventTarget`.
32746   * This makes it so that the `event listener` will no longer get called when the
32747   * named event happens.
32748   *
32749   * @param {string|string[]} type
32750   *        An event name or an array of event names.
32751   *
32752   * @param {Function} fn
32753   *        The function to remove.
32754   */
32755  off(type, fn) {
32756    off(this, type, fn);
32757  }
32758  /**
32759   * This function will add an `event listener` that gets triggered only once. After the
32760   * first trigger it will get removed. This is like adding an `event listener`
32761   * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
32762   *
32763   * @param {string|string[]} type
32764   *        An event name or an array of event names.
32765   *
32766   * @param {Function} fn
32767   *        The function to be called once for each event name.
32768   */
32769  one(type, fn) {
32770    // Remove the addEventListener aliasing Events.on
32771    // so we don't get into an infinite type loop
32772    const ael = this.addEventListener;
32773    this.addEventListener = () => {};
32774    one(this, type, fn);
32775    this.addEventListener = ael;
32776  }
32777  /**
32778   * This function will add an `event listener` that gets triggered only once and is
32779   * removed from all events. This is like adding an array of `event listener`s
32780   * with {@link EventTarget#on} that calls {@link EventTarget#off} on all events the
32781   * first time it is triggered.
32782   *
32783   * @param {string|string[]} type
32784   *        An event name or an array of event names.
32785   *
32786   * @param {Function} fn
32787   *        The function to be called once for each event name.
32788   */
32789  any(type, fn) {
32790    // Remove the addEventListener aliasing Events.on
32791    // so we don't get into an infinite type loop
32792    const ael = this.addEventListener;
32793    this.addEventListener = () => {};
32794    any(this, type, fn);
32795    this.addEventListener = ael;
32796  }
32797  /**
32798   * This function causes an event to happen. This will then cause any `event listeners`
32799   * that are waiting for that event, to get called. If there are no `event listeners`
32800   * for an event then nothing will happen.
32801   *
32802   * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
32803   * Trigger will also call the `on` + `uppercaseEventName` function.
32804   *
32805   * Example:
32806   * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
32807   * `onClick` if it exists.
32808   *
32809   * @param {string|EventTarget~Event|Object} event
32810   *        The name of the event, an `Event`, or an object with a key of type set to
32811   *        an event name.
32812   */
32813  trigger(event) {
32814    const type = event.type || event;
32815
32816    // deprecation
32817    // In a future version we should default target to `this`
32818    // similar to how we default the target to `elem` in
32819    // `Events.trigger`. Right now the default `target` will be
32820    // `document` due to the `Event.fixEvent` call.
32821    if (typeof event === 'string') {
32822      event = {
32823        type
32824      };
32825    }
32826    event = fixEvent(event);
32827    if (this.allowedEvents_[type] && this['on' + type]) {
32828      this['on' + type](event);
32829    }
32830    trigger(this, event);
32831  }
32832  queueTrigger(event) {
32833    // only set up EVENT_MAP if it'll be used
32834    if (!EVENT_MAP) {
32835      EVENT_MAP = new Map();
32836    }
32837    const type = event.type || event;
32838    let map = EVENT_MAP.get(this);
32839    if (!map) {
32840      map = new Map();
32841      EVENT_MAP.set(this, map);
32842    }
32843    const oldTimeout = map.get(type);
32844    map.delete(type);
32845    window__default["default"].clearTimeout(oldTimeout);
32846    const timeout = window__default["default"].setTimeout(() => {
32847      map.delete(type);
32848      // if we cleared out all timeouts for the current target, delete its map
32849      if (map.size === 0) {
32850        map = null;
32851        EVENT_MAP.delete(this);
32852      }
32853      this.trigger(event);
32854    }, 0);
32855    map.set(type, timeout);
32856  }
32857}
32858
32859/**
32860 * A Custom DOM event.
32861 *
32862 * @typedef {CustomEvent} Event
32863 * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
32864 */
32865
32866/**
32867 * All event listeners should follow the following format.
32868 *
32869 * @callback EventListener
32870 * @this {EventTarget}
32871 *
32872 * @param {Event} event
32873 *        the event that triggered this function
32874 *
32875 * @param {Object} [hash]
32876 *        hash of data sent during the event
32877 */
32878
32879/**
vendor: 5,063 bytes, lines 32880-33063
32880 * An object containing event names as keys and booleans as values.
32881 *
32882 * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
32883 *         will have extra functionality. See that function for more information.
32884 *
32885 * @property EventTarget.prototype.allowedEvents_
32886 * @protected
32887 */
32888EventTarget$2.prototype.allowedEvents_ = {};
32889
32890/**
32891 * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
32892 * the standard DOM API.
32893 *
32894 * @function
32895 * @see {@link EventTarget#on}
32896 */
32897EventTarget$2.prototype.addEventListener = EventTarget$2.prototype.on;
32898
32899/**
32900 * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
32901 * the standard DOM API.
32902 *
32903 * @function
32904 * @see {@link EventTarget#off}
32905 */
32906EventTarget$2.prototype.removeEventListener = EventTarget$2.prototype.off;
32907
32908/**
32909 * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
32910 * the standard DOM API.
32911 *
32912 * @function
32913 * @see {@link EventTarget#trigger}
32914 */
32915EventTarget$2.prototype.dispatchEvent = EventTarget$2.prototype.trigger;
32916
32917/**
32918 * @file mixins/evented.js
32919 * @module evented
32920 */
32921const objName = obj => {
32922  if (typeof obj.name === 'function') {
32923    return obj.name();
32924  }
32925  if (typeof obj.name === 'string') {
32926    return obj.name;
32927  }
32928  if (obj.name_) {
32929    return obj.name_;
32930  }
32931  if (obj.constructor && obj.constructor.name) {
32932    return obj.constructor.name;
32933  }
32934  return typeof obj;
32935};
32936
32937/**
32938 * Returns whether or not an object has had the evented mixin applied.
32939 *
32940 * @param  {Object} object
32941 *         An object to test.
32942 *
32943 * @return {boolean}
32944 *         Whether or not the object appears to be evented.
32945 */
32946const isEvented = object => object instanceof EventTarget$2 || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(k => typeof object[k] === 'function');
32947
32948/**
32949 * Adds a callback to run after the evented mixin applied.
32950 *
32951 * @param  {Object} target
32952 *         An object to Add
32953 * @param  {Function} callback
32954 *         The callback to run.
32955 */
32956const addEventedCallback = (target, callback) => {
32957  if (isEvented(target)) {
32958    callback();
32959  } else {
32960    if (!target.eventedCallbacks) {
32961      target.eventedCallbacks = [];
32962    }
32963    target.eventedCallbacks.push(callback);
32964  }
32965};
32966
32967/**
32968 * Whether a value is a valid event type - non-empty string or array.
32969 *
32970 * @private
32971 * @param  {string|Array} type
32972 *         The type value to test.
32973 *
32974 * @return {boolean}
32975 *         Whether or not the type is a valid event type.
32976 */
32977const isValidEventType = type =>
32978// The regex here verifies that the `type` contains at least one non-
32979// whitespace character.
32980typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length;
32981
32982/**
32983 * Validates a value to determine if it is a valid event target. Throws if not.
32984 *
32985 * @private
32986 * @throws {Error}
32987 *         If the target does not appear to be a valid event target.
32988 *
32989 * @param  {Object} target
32990 *         The object to test.
32991 *
32992 * @param  {Object} obj
32993 *         The evented object we are validating for
32994 *
32995 * @param  {string} fnName
32996 *         The name of the evented mixin function that called this.
32997 */
32998const validateTarget = (target, obj, fnName) => {
32999  if (!target || !target.nodeName && !isEvented(target)) {
33000    throw new Error(`Invalid target for ${objName(obj)}#${fnName}; must be a DOM node or evented object.`);
33001  }
33002};
33003
33004/**
33005 * Validates a value to determine if it is a valid event target. Throws if not.
33006 *
33007 * @private
33008 * @throws {Error}
33009 *         If the type does not appear to be a valid event type.
33010 *
33011 * @param  {string|Array} type
33012 *         The type to test.
33013 *
33014 * @param  {Object} obj
33015*         The evented object we are validating for
33016 *
33017 * @param  {string} fnName
33018 *         The name of the evented mixin function that called this.
33019 */
33020const validateEventType = (type, obj, fnName) => {
33021  if (!isValidEventType(type)) {
33022    throw new Error(`Invalid event type for ${objName(obj)}#${fnName}; must be a non-empty string or array.`);
33023  }
33024};
33025
33026/**
33027 * Validates a value to determine if it is a valid listener. Throws if not.
33028 *
33029 * @private
33030 * @throws {Error}
33031 *         If the listener is not a function.
33032 *
33033 * @param  {Function} listener
33034 *         The listener to test.
33035 *
33036 * @param  {Object} obj
33037 *         The evented object we are validating for
33038 *
33039 * @param  {string} fnName
33040 *         The name of the evented mixin function that called this.
33041 */
33042const validateListener = (listener, obj, fnName) => {
33043  if (typeof listener !== 'function') {
33044    throw new Error(`Invalid listener for ${objName(obj)}#${fnName}; must be a function.`);
33045  }
33046};
33047
33048/**
33049 * Takes an array of arguments given to `on()` or `one()`, validates them, and
33050 * normalizes them into an object.
33051 *
33052 * @private
33053 * @param  {Object} self
33054 *         The evented object on which `on()` or `one()` was called. This
33055 *         object will be bound as the `this` value for the listener.
33056 *
33057 * @param  {Array} args
33058 *         An array of arguments passed to `on()` or `one()`.
33059 *
33060 * @param  {string} fnName
33061 *         The name of the evented mixin function that called this.
33062 *
33063 * @return {Object}
vendor: 11,070 bytes, lines 33064-33382
33064 *         An object containing useful values for `on()` or `one()` calls.
33065 */
33066const normalizeListenArgs = (self, args, fnName) => {
33067  // If the number of arguments is less than 3, the target is always the
33068  // evented object itself.
33069  const isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
33070  let target;
33071  let type;
33072  let listener;
33073  if (isTargetingSelf) {
33074    target = self.eventBusEl_;
33075
33076    // Deal with cases where we got 3 arguments, but we are still listening to
33077    // the evented object itself.
33078    if (args.length >= 3) {
33079      args.shift();
33080    }
33081    [type, listener] = args;
33082  } else {
33083    // This was `[target, type, listener] = args;` but this block needs more than
33084    // one statement to produce minified output compatible with Chrome 53.
33085    // See https://github.com/videojs/video.js/pull/8810
33086    target = args[0];
33087    type = args[1];
33088    listener = args[2];
33089  }
33090  validateTarget(target, self, fnName);
33091  validateEventType(type, self, fnName);
33092  validateListener(listener, self, fnName);
33093  listener = bind_(self, listener);
33094  return {
33095    isTargetingSelf,
33096    target,
33097    type,
33098    listener
33099  };
33100};
33101
33102/**
33103 * Adds the listener to the event type(s) on the target, normalizing for
33104 * the type of target.
33105 *
33106 * @private
33107 * @param  {Element|Object} target
33108 *         A DOM node or evented object.
33109 *
33110 * @param  {string} method
33111 *         The event binding method to use ("on" or "one").
33112 *
33113 * @param  {string|Array} type
33114 *         One or more event type(s).
33115 *
33116 * @param  {Function} listener
33117 *         A listener function.
33118 */
33119const listen = (target, method, type, listener) => {
33120  validateTarget(target, target, method);
33121  if (target.nodeName) {
33122    Events[method](target, type, listener);
33123  } else {
33124    target[method](type, listener);
33125  }
33126};
33127
33128/**
33129 * Contains methods that provide event capabilities to an object which is passed
33130 * to {@link module:evented|evented}.
33131 *
33132 * @mixin EventedMixin
33133 */
33134const EventedMixin = {
33135  /**
33136   * Add a listener to an event (or events) on this object or another evented
33137   * object.
33138   *
33139   * @param  {string|Array|Element|Object} targetOrType
33140   *         If this is a string or array, it represents the event type(s)
33141   *         that will trigger the listener.
33142   *
33143   *         Another evented object can be passed here instead, which will
33144   *         cause the listener to listen for events on _that_ object.
33145   *
33146   *         In either case, the listener's `this` value will be bound to
33147   *         this object.
33148   *
33149   * @param  {string|Array|Function} typeOrListener
33150   *         If the first argument was a string or array, this should be the
33151   *         listener function. Otherwise, this is a string or array of event
33152   *         type(s).
33153   *
33154   * @param  {Function} [listener]
33155   *         If the first argument was another evented object, this will be
33156   *         the listener function.
33157   */
33158  on(...args) {
33159    const {
33160      isTargetingSelf,
33161      target,
33162      type,
33163      listener
33164    } = normalizeListenArgs(this, args, 'on');
33165    listen(target, 'on', type, listener);
33166
33167    // If this object is listening to another evented object.
33168    if (!isTargetingSelf) {
33169      // If this object is disposed, remove the listener.
33170      const removeListenerOnDispose = () => this.off(target, type, listener);
33171
33172      // Use the same function ID as the listener so we can remove it later it
33173      // using the ID of the original listener.
33174      removeListenerOnDispose.guid = listener.guid;
33175
33176      // Add a listener to the target's dispose event as well. This ensures
33177      // that if the target is disposed BEFORE this object, we remove the
33178      // removal listener that was just added. Otherwise, we create a memory leak.
33179      const removeRemoverOnTargetDispose = () => this.off('dispose', removeListenerOnDispose);
33180
33181      // Use the same function ID as the listener so we can remove it later
33182      // it using the ID of the original listener.
33183      removeRemoverOnTargetDispose.guid = listener.guid;
33184      listen(this, 'on', 'dispose', removeListenerOnDispose);
33185      listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
33186    }
33187  },
33188  /**
33189   * Add a listener to an event (or events) on this object or another evented
33190   * object. The listener will be called once per event and then removed.
33191   *
33192   * @param  {string|Array|Element|Object} targetOrType
33193   *         If this is a string or array, it represents the event type(s)
33194   *         that will trigger the listener.
33195   *
33196   *         Another evented object can be passed here instead, which will
33197   *         cause the listener to listen for events on _that_ object.
33198   *
33199   *         In either case, the listener's `this` value will be bound to
33200   *         this object.
33201   *
33202   * @param  {string|Array|Function} typeOrListener
33203   *         If the first argument was a string or array, this should be the
33204   *         listener function. Otherwise, this is a string or array of event
33205   *         type(s).
33206   *
33207   * @param  {Function} [listener]
33208   *         If the first argument was another evented object, this will be
33209   *         the listener function.
33210   */
33211  one(...args) {
33212    const {
33213      isTargetingSelf,
33214      target,
33215      type,
33216      listener
33217    } = normalizeListenArgs(this, args, 'one');
33218
33219    // Targeting this evented object.
33220    if (isTargetingSelf) {
33221      listen(target, 'one', type, listener);
33222
33223      // Targeting another evented object.
33224    } else {
33225      // TODO: This wrapper is incorrect! It should only
33226      //       remove the wrapper for the event type that called it.
33227      //       Instead all listeners are removed on the first trigger!
33228      //       see https://github.com/videojs/video.js/issues/5962
33229      const wrapper = (...largs) => {
33230        this.off(target, type, wrapper);
33231        listener.apply(null, largs);
33232      };
33233
33234      // Use the same function ID as the listener so we can remove it later
33235      // it using the ID of the original listener.
33236      wrapper.guid = listener.guid;
33237      listen(target, 'one', type, wrapper);
33238    }
33239  },
33240  /**
33241   * Add a listener to an event (or events) on this object or another evented
33242   * object. The listener will only be called once for the first event that is triggered
33243   * then removed.
33244   *
33245   * @param  {string|Array|Element|Object} targetOrType
33246   *         If this is a string or array, it represents the event type(s)
33247   *         that will trigger the listener.
33248   *
33249   *         Another evented object can be passed here instead, which will
33250   *         cause the listener to listen for events on _that_ object.
33251   *
33252   *         In either case, the listener's `this` value will be bound to
33253   *         this object.
33254   *
33255   * @param  {string|Array|Function} typeOrListener
33256   *         If the first argument was a string or array, this should be the
33257   *         listener function. Otherwise, this is a string or array of event
33258   *         type(s).
33259   *
33260   * @param  {Function} [listener]
33261   *         If the first argument was another evented object, this will be
33262   *         the listener function.
33263   */
33264  any(...args) {
33265    const {
33266      isTargetingSelf,
33267      target,
33268      type,
33269      listener
33270    } = normalizeListenArgs(this, args, 'any');
33271
33272    // Targeting this evented object.
33273    if (isTargetingSelf) {
33274      listen(target, 'any', type, listener);
33275
33276      // Targeting another evented object.
33277    } else {
33278      const wrapper = (...largs) => {
33279        this.off(target, type, wrapper);
33280        listener.apply(null, largs);
33281      };
33282
33283      // Use the same function ID as the listener so we can remove it later
33284      // it using the ID of the original listener.
33285      wrapper.guid = listener.guid;
33286      listen(target, 'any', type, wrapper);
33287    }
33288  },
33289  /**
33290   * Removes listener(s) from event(s) on an evented object.
33291   *
33292   * @param  {string|Array|Element|Object} [targetOrType]
33293   *         If this is a string or array, it represents the event type(s).
33294   *
33295   *         Another evented object can be passed here instead, in which case
33296   *         ALL 3 arguments are _required_.
33297   *
33298   * @param  {string|Array|Function} [typeOrListener]
33299   *         If the first argument was a string or array, this may be the
33300   *         listener function. Otherwise, this is a string or array of event
33301   *         type(s).
33302   *
33303   * @param  {Function} [listener]
33304   *         If the first argument was another evented object, this will be
33305   *         the listener function; otherwise, _all_ listeners bound to the
33306   *         event type(s) will be removed.
33307   */
33308  off(targetOrType, typeOrListener, listener) {
33309    // Targeting this evented object.
33310    if (!targetOrType || isValidEventType(targetOrType)) {
33311      off(this.eventBusEl_, targetOrType, typeOrListener);
33312
33313      // Targeting another evented object.
33314    } else {
33315      const target = targetOrType;
33316      const type = typeOrListener;
33317
33318      // Fail fast and in a meaningful way!
33319      validateTarget(target, this, 'off');
33320      validateEventType(type, this, 'off');
33321      validateListener(listener, this, 'off');
33322
33323      // Ensure there's at least a guid, even if the function hasn't been used
33324      listener = bind_(this, listener);
33325
33326      // Remove the dispose listener on this evented object, which was given
33327      // the same guid as the event listener in on().
33328      this.off('dispose', listener);
33329      if (target.nodeName) {
33330        off(target, type, listener);
33331        off(target, 'dispose', listener);
33332      } else if (isEvented(target)) {
33333        target.off(type, listener);
33334        target.off('dispose', listener);
33335      }
33336    }
33337  },
33338  /**
33339   * Fire an event on this evented object, causing its listeners to be called.
33340   *
33341   * @param   {string|Object} event
33342   *          An event type or an object with a type property.
33343   *
33344   * @param   {Object} [hash]
33345   *          An additional object to pass along to listeners.
33346   *
33347   * @return {boolean}
33348   *          Whether or not the default behavior was prevented.
33349   */
33350  trigger(event, hash) {
33351    validateTarget(this.eventBusEl_, this, 'trigger');
33352    const type = event && typeof event !== 'string' ? event.type : event;
33353    if (!isValidEventType(type)) {
33354      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.');
33355    }
33356    return trigger(this.eventBusEl_, event, hash);
33357  }
33358};
33359
33360/**
33361 * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
33362 *
33363 * @param  {Object} target
33364 *         The object to which to add event methods.
33365 *
33366 * @param  {Object} [options={}]
33367 *         Options for customizing the mixin behavior.
33368 *
33369 * @param  {string} [options.eventBusKey]
33370 *         By default, adds a `eventBusEl_` DOM element to the target object,
33371 *         which is used as an event bus. If the target object already has a
33372 *         DOM element that should be used, pass its key here.
33373 *
33374 * @return {Object}
33375 *         The target object.
33376 */
33377function evented(target, options = {}) {
33378  const {
33379    eventBusKey
33380  } = options;
33381
33382  // Set or create the eventBusEl_.
vendor: 5,747 bytes, lines 33383-33597
33383  if (eventBusKey) {
33384    if (!target[eventBusKey].nodeName) {
33385      throw new Error(`The eventBusKey "${eventBusKey}" does not refer to an element.`);
33386    }
33387    target.eventBusEl_ = target[eventBusKey];
33388  } else {
33389    target.eventBusEl_ = createEl('span', {
33390      className: 'vjs-event-bus'
33391    });
33392  }
33393  Object.assign(target, EventedMixin);
33394  if (target.eventedCallbacks) {
33395    target.eventedCallbacks.forEach(callback => {
33396      callback();
33397    });
33398  }
33399
33400  // When any evented object is disposed, it removes all its listeners.
33401  target.on('dispose', () => {
33402    target.off();
33403    [target, target.el_, target.eventBusEl_].forEach(function (val) {
33404      if (val && DomData.has(val)) {
33405        DomData.delete(val);
33406      }
33407    });
33408    window__default["default"].setTimeout(() => {
33409      target.eventBusEl_ = null;
33410    }, 0);
33411  });
33412  return target;
33413}
33414
33415/**
33416 * @file mixins/stateful.js
33417 * @module stateful
33418 */
33419
33420/**
33421 * Contains methods that provide statefulness to an object which is passed
33422 * to {@link module:stateful}.
33423 *
33424 * @mixin StatefulMixin
33425 */
33426const StatefulMixin = {
33427  /**
33428   * A hash containing arbitrary keys and values representing the state of
33429   * the object.
33430   *
33431   * @type {Object}
33432   */
33433  state: {},
33434  /**
33435   * Set the state of an object by mutating its
33436   * {@link module:stateful~StatefulMixin.state|state} object in place.
33437   *
33438   * @fires   module:stateful~StatefulMixin#statechanged
33439   * @param   {Object|Function} stateUpdates
33440   *          A new set of properties to shallow-merge into the plugin state.
33441   *          Can be a plain object or a function returning a plain object.
33442   *
33443   * @return {Object|undefined}
33444   *          An object containing changes that occurred. If no changes
33445   *          occurred, returns `undefined`.
33446   */
33447  setState(stateUpdates) {
33448    // Support providing the `stateUpdates` state as a function.
33449    if (typeof stateUpdates === 'function') {
33450      stateUpdates = stateUpdates();
33451    }
33452    let changes;
33453    each(stateUpdates, (value, key) => {
33454      // Record the change if the value is different from what's in the
33455      // current state.
33456      if (this.state[key] !== value) {
33457        changes = changes || {};
33458        changes[key] = {
33459          from: this.state[key],
33460          to: value
33461        };
33462      }
33463      this.state[key] = value;
33464    });
33465
33466    // Only trigger "statechange" if there were changes AND we have a trigger
33467    // function. This allows us to not require that the target object be an
33468    // evented object.
33469    if (changes && isEvented(this)) {
33470      /**
33471       * An event triggered on an object that is both
33472       * {@link module:stateful|stateful} and {@link module:evented|evented}
33473       * indicating that its state has changed.
33474       *
33475       * @event    module:stateful~StatefulMixin#statechanged
33476       * @type     {Object}
33477       * @property {Object} changes
33478       *           A hash containing the properties that were changed and
33479       *           the values they were changed `from` and `to`.
33480       */
33481      this.trigger({
33482        changes,
33483        type: 'statechanged'
33484      });
33485    }
33486    return changes;
33487  }
33488};
33489
33490/**
33491 * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
33492 * object.
33493 *
33494 * If the target object is {@link module:evented|evented} and has a
33495 * `handleStateChanged` method, that method will be automatically bound to the
33496 * `statechanged` event on itself.
33497 *
33498 * @param   {Object} target
33499 *          The object to be made stateful.
33500 *
33501 * @param   {Object} [defaultState]
33502 *          A default set of properties to populate the newly-stateful object's
33503 *          `state` property.
33504 *
33505 * @return {Object}
33506 *          Returns the `target`.
33507 */
33508function stateful(target, defaultState) {
33509  Object.assign(target, StatefulMixin);
33510
33511  // This happens after the mixing-in because we need to replace the `state`
33512  // added in that step.
33513  target.state = Object.assign({}, target.state, defaultState);
33514
33515  // Auto-bind the `handleStateChanged` method of the target object if it exists.
33516  if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
33517    target.on('statechanged', target.handleStateChanged);
33518  }
33519  return target;
33520}
33521
33522/**
33523 * @file str.js
33524 * @module to-lower-case
33525 */
33526
33527/**
33528 * Lowercase the first letter of a string.
33529 *
33530 * @param {string} string
33531 *        String to be lowercased
33532 *
33533 * @return {string}
33534 *         The string with a lowercased first letter
33535 */
33536const toLowerCase = function (string) {
33537  if (typeof string !== 'string') {
33538    return string;
33539  }
33540  return string.replace(/./, w => w.toLowerCase());
33541};
33542
33543/**
33544 * Uppercase the first letter of a string.
33545 *
33546 * @param {string} string
33547 *        String to be uppercased
33548 *
33549 * @return {string}
33550 *         The string with an uppercased first letter
33551 */
33552const toTitleCase$1 = function (string) {
33553  if (typeof string !== 'string') {
33554    return string;
33555  }
33556  return string.replace(/./, w => w.toUpperCase());
33557};
33558
33559/**
33560 * Compares the TitleCase versions of the two strings for equality.
33561 *
33562 * @param {string} str1
33563 *        The first string to compare
33564 *
33565 * @param {string} str2
33566 *        The second string to compare
33567 *
33568 * @return {boolean}
33569 *         Whether the TitleCase versions of the strings are equal
33570 */
33571const titleCaseEquals = function (str1, str2) {
33572  return toTitleCase$1(str1) === toTitleCase$1(str2);
33573};
33574
33575var Str = /*#__PURE__*/Object.freeze({
33576  __proto__: null,
33577  toLowerCase: toLowerCase,
33578  toTitleCase: toTitleCase$1,
33579  titleCaseEquals: titleCaseEquals
33580});
33581
33582/**
33583 * Player Component - Base class for all UI objects
33584 *
33585 * @file component.js
33586 */
33587
33588/** @import Player from './player' */
33589
33590/**
33591 * A callback to be called if and when the component is ready.
33592 * `this` will be the Component instance.
33593 *
33594 * @callback ReadyCallback
33595 * @returns  {void}
33596 */
33597
vendor: 4,585 bytes, lines 33598-33726
33598/**
33599 * Base class for all UI Components.
33600 * Components are UI objects which represent both a javascript object and an element
33601 * in the DOM. They can be children of other components, and can have
33602 * children themselves.
33603 *
33604 * Components can also use methods from {@link EventTarget}
33605 */
33606class Component$1 {
33607  /**
33608   * Creates an instance of this class.
33609   *
33610   * @param {Player} player
33611   *        The `Player` that this class should be attached to.
33612   *
33613   * @param {Object} [options]
33614   *        The key/value store of component options.
33615   *
33616   * @param {Object[]} [options.children]
33617   *        An array of children objects to initialize this component with. Children objects have
33618   *        a name property that will be used if more than one component of the same type needs to be
33619   *        added.
33620   *
33621   * @param  {string} [options.className]
33622   *         A class or space separated list of classes to add the component
33623   *
33624   * @param {ReadyCallback} [ready]
33625   *        Function that gets called when the `Component` is ready.
33626   */
33627  constructor(player, options, ready) {
33628    // The component might be the player itself and we can't pass `this` to super
33629    if (!player && this.play) {
33630      this.player_ = player = this; // eslint-disable-line
33631    } else {
33632      this.player_ = player;
33633    }
33634    this.isDisposed_ = false;
33635
33636    // Hold the reference to the parent component via `addChild` method
33637    this.parentComponent_ = null;
33638
33639    // Make a copy of prototype.options_ to protect against overriding defaults
33640    this.options_ = merge$1({}, this.options_);
33641
33642    // Updated options with supplied options
33643    options = this.options_ = merge$1(this.options_, options);
33644
33645    // Get ID from options or options element if one is supplied
33646    this.id_ = options.id || options.el && options.el.id;
33647
33648    // If there was no ID from the options, generate one
33649    if (!this.id_) {
33650      // Don't require the player ID function in the case of mock players
33651      const id = player && player.id && player.id() || 'no_player';
33652      this.id_ = `${id}_component_${newGUID()}`;
33653    }
33654    this.name_ = options.name || null;
33655
33656    // Create element if one wasn't provided in options
33657    if (options.el) {
33658      this.el_ = options.el;
33659    } else if (options.createEl !== false) {
33660      this.el_ = this.createEl();
33661    }
33662    if (options.className && this.el_) {
33663      options.className.split(' ').forEach(c => this.addClass(c));
33664    }
33665
33666    // Remove the placeholder event methods. If the component is evented, the
33667    // real methods are added next
33668    ['on', 'off', 'one', 'any', 'trigger'].forEach(fn => {
33669      this[fn] = undefined;
33670    });
33671
33672    // if evented is anything except false, we want to mixin in evented
33673    if (options.evented !== false) {
33674      // Make this an evented object and use `el_`, if available, as its event bus
33675      evented(this, {
33676        eventBusKey: this.el_ ? 'el_' : null
33677      });
33678      this.handleLanguagechange = this.handleLanguagechange.bind(this);
33679      this.on(this.player_, 'languagechange', this.handleLanguagechange);
33680    }
33681    stateful(this, this.constructor.defaultState);
33682    this.children_ = [];
33683    this.childIndex_ = {};
33684    this.childNameIndex_ = {};
33685    this.setTimeoutIds_ = new Set();
33686    this.setIntervalIds_ = new Set();
33687    this.rafIds_ = new Set();
33688    this.namedRafs_ = new Map();
33689    this.clearingTimersOnDispose_ = false;
33690
33691    // Add any child components in options
33692    if (options.initChildren !== false) {
33693      this.initChildren();
33694    }
33695
33696    // Don't want to trigger ready here or it will go before init is actually
33697    // finished for all children that run this constructor
33698    this.ready(ready);
33699    if (options.reportTouchActivity !== false) {
33700      this.enableTouchActivity();
33701    }
33702  }
33703
33704  // `on`, `off`, `one`, `any` and `trigger` are here so tsc includes them in definitions.
33705  // They are replaced or removed in the constructor
33706
33707  /**
33708   * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
33709   * function that will get called when an event with a certain name gets triggered.
33710   *
33711   * @param {string|string[]} type
33712   *        An event name or an array of event names.
33713   *
33714   * @param {Function} fn
33715   *        The function to call with `EventTarget`s
33716   */
33717
33718  /**
33719   * Removes an `event listener` for a specific event from an instance of `EventTarget`.
33720   * This makes it so that the `event listener` will no longer get called when the
33721   * named event happens.
33722   *
33723   * @param {string|string[]} type
33724   *        An event name or an array of event names.
33725   *
33726   * @param {Function}
vendor: 4,145 bytes, lines 33726-33863
33726 [fn]
33727   *        The function to remove. If not specified, all listeners managed by Video.js will be removed.
33728   */
33729
33730  /**
33731   * This function will add an `event listener` that gets triggered only once. After the
33732   * first trigger it will get removed. This is like adding an `event listener`
33733   * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
33734   *
33735   * @param {string|string[]} type
33736   *        An event name or an array of event names.
33737   *
33738   * @param {Function} fn
33739   *        The function to be called once for each event name.
33740   */
33741
33742  /**
33743   * This function will add an `event listener` that gets triggered only once and is
33744   * removed from all events. This is like adding an array of `event listener`s
33745   * with {@link EventTarget#on} that calls {@link EventTarget#off} on all events the
33746   * first time it is triggered.
33747   *
33748   * @param {string|string[]} type
33749   *        An event name or an array of event names.
33750   *
33751   * @param {Function} fn
33752   *        The function to be called once for each event name.
33753   */
33754
33755  /**
33756   * This function causes an event to happen. This will then cause any `event listeners`
33757   * that are waiting for that event, to get called. If there are no `event listeners`
33758   * for an event then nothing will happen.
33759   *
33760   * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
33761   * Trigger will also call the `on` + `uppercaseEventName` function.
33762   *
33763   * Example:
33764   * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
33765   * `onClick` if it exists.
33766   *
33767   * @param {string|Event|Object} event
33768   *        The name of the event, an `Event`, or an object with a key of type set to
33769   *        an event name.
33770   *
33771   * @param {Object} [hash]
33772   *        Optionally extra argument to pass through to an event listener
33773   */
33774
33775  /**
33776   * Dispose of the `Component` and all child components.
33777   *
33778   * @fires Component#dispose
33779   *
33780   * @param {Object} options
33781   * @param {Element} options.originalEl element with which to replace player element
33782   */
33783  dispose(options = {}) {
33784    // Bail out if the component has already been disposed.
33785    if (this.isDisposed_) {
33786      return;
33787    }
33788    if (this.readyQueue_) {
33789      this.readyQueue_.length = 0;
33790    }
33791
33792    /**
33793     * Triggered when a `Component` is disposed.
33794     *
33795     * @event Component#dispose
33796     * @type {Event}
33797     *
33798     * @property {boolean} [bubbles=false]
33799     *           set to false so that the dispose event does not
33800     *           bubble up
33801     */
33802    this.trigger({
33803      type: 'dispose',
33804      bubbles: false
33805    });
33806    this.isDisposed_ = true;
33807
33808    // Dispose all children.
33809    if (this.children_) {
33810      for (let i = this.children_.length - 1; i >= 0; i--) {
33811        if (this.children_[i].dispose) {
33812          this.children_[i].dispose();
33813        }
33814      }
33815    }
33816
33817    // Delete child references
33818    this.children_ = null;
33819    this.childIndex_ = null;
33820    this.childNameIndex_ = null;
33821    this.parentComponent_ = null;
33822    if (this.el_) {
33823      // Remove element from DOM
33824      if (this.el_.parentNode) {
33825        if (options.restoreEl) {
33826          this.el_.parentNode.replaceChild(options.restoreEl, this.el_);
33827        } else {
33828          this.el_.parentNode.removeChild(this.el_);
33829        }
33830      }
33831      this.el_ = null;
33832    }
33833
33834    // remove reference to the player after disposing of the element
33835    this.player_ = null;
33836  }
33837
33838  /**
33839   * Determine whether or not this component has been disposed.
33840   *
33841   * @return {boolean}
33842   *         If the component has been disposed, will be `true`. Otherwise, `false`.
33843   */
33844  isDisposed() {
33845    return Boolean(this.isDisposed_);
33846  }
33847
33848  /**
33849   * Return the {@link Player} that the `Component` has attached to.
33850   *
33851   * @return {Player}
33852   *         The player that this `Component` has attached to.
33853   */
33854  player() {
33855    return this.player_;
33856  }
33857
33858  /**
33859   * Deep merge of options objects with new options.
33860   * > Note: When both `obj` and `options` contain properties whose values are objects.
33861   *         The two properties get merged using {@link module:obj.merge}
33862   *
33863   * @param {Object} obj
vendor: 5,173 bytes, lines 33864-34048
33864   *        The object that contains new options.
33865   *
33866   * @return {Object}
33867   *         A new object of `this.options_` and `obj` merged together.
33868   */
33869  options(obj) {
33870    if (!obj) {
33871      return this.options_;
33872    }
33873    this.options_ = merge$1(this.options_, obj);
33874    return this.options_;
33875  }
33876
33877  /**
33878   * Get the `Component`s DOM element
33879   *
33880   * @return {Element}
33881   *         The DOM element for this `Component`.
33882   */
33883  el() {
33884    return this.el_;
33885  }
33886
33887  /**
33888   * Create the `Component`s DOM element.
33889   *
33890   * @param {string} [tagName]
33891   *        Element's DOM node type. e.g. 'div'
33892   *
33893   * @param {Object} [properties]
33894   *        An object of properties that should be set.
33895   *
33896   * @param {Object} [attributes]
33897   *        An object of attributes that should be set.
33898   *
33899   * @return {Element}
33900   *         The element that gets created.
33901   */
33902  createEl(tagName, properties, attributes) {
33903    return createEl(tagName, properties, attributes);
33904  }
33905
33906  /**
33907   * Localize a string given the string in english.
33908   *
33909   * If tokens are provided, it'll try and run a simple token replacement on the provided string.
33910   * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
33911   *
33912   * If a `defaultValue` is provided, it'll use that over `string`,
33913   * if a value isn't found in provided language files.
33914   * This is useful if you want to have a descriptive key for token replacement
33915   * but have a succinct localized string and not require `en.json` to be included.
33916   *
33917   * Currently, it is used for the progress bar timing.
33918   * ```js
33919   * {
33920   *   "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
33921   * }
33922   * ```
33923   * It is then used like so:
33924   * ```js
33925   * this.localize('progress bar timing: currentTime={1} duration{2}',
33926   *               [this.player_.currentTime(), this.player_.duration()],
33927   *               '{1} of {2}');
33928   * ```
33929   *
33930   * Which outputs something like: `01:23 of 24:56`.
33931   *
33932   *
33933   * @param {string} string
33934   *        The string to localize and the key to lookup in the language files.
33935   * @param {string[]} [tokens]
33936   *        If the current item has token replacements, provide the tokens here.
33937   * @param {string} [defaultValue]
33938   *        Defaults to `string`. Can be a default value to use for token replacement
33939   *        if the lookup key is needed to be separate.
33940   *
33941   * @return {string}
33942   *         The localized string or if no localization exists the english string.
33943   */
33944  localize(string, tokens, defaultValue = string) {
33945    const code = this.player_.language && this.player_.language();
33946    const languages = this.player_.languages && this.player_.languages();
33947    const language = languages && languages[code];
33948    const primaryCode = code && code.split('-')[0];
33949    const primaryLang = languages && languages[primaryCode];
33950    let localizedString = defaultValue;
33951    if (language && language[string]) {
33952      localizedString = language[string];
33953    } else if (primaryLang && primaryLang[string]) {
33954      localizedString = primaryLang[string];
33955    }
33956    if (tokens) {
33957      localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
33958        const value = tokens[index - 1];
33959        let ret = value;
33960        if (typeof value === 'undefined') {
33961          ret = match;
33962        }
33963        return ret;
33964      });
33965    }
33966    return localizedString;
33967  }
33968
33969  /**
33970   * Handles language change for the player in components. Should be overridden by sub-components.
33971   *
33972   * @abstract
33973   */
33974  handleLanguagechange() {}
33975
33976  /**
33977   * Return the `Component`s DOM element. This is where children get inserted.
33978   * This will usually be the the same as the element returned in {@link Component#el}.
33979   *
33980   * @return {Element}
33981   *         The content element for this `Component`.
33982   */
33983  contentEl() {
33984    return this.contentEl_ || this.el_;
33985  }
33986
33987  /**
33988   * Get this `Component`s ID
33989   *
33990   * @return {string}
33991   *         The id of this `Component`
33992   */
33993  id() {
33994    return this.id_;
33995  }
33996
33997  /**
33998   * Get the `Component`s name. The name gets used to reference the `Component`
33999   * and is set during registration.
34000   *
34001   * @return {string}
34002   *         The name of this `Component`.
34003   */
34004  name() {
34005    return this.name_;
34006  }
34007
34008  /**
34009   * Get an array of all child components
34010   *
34011   * @return {Array}
34012   *         The children
34013   */
34014  children() {
34015    return this.children_;
34016  }
34017
34018  /**
34019   * Returns the child `Component` with the given `id`.
34020   *
34021   * @param {string} id
34022   *        The id of the child `Component` to get.
34023   *
34024   * @return {Component|undefined}
34025   *         The child `Component` with the given `id` or undefined.
34026   */
34027  getChildById(id) {
34028    return this.childIndex_[id];
34029  }
34030
34031  /**
34032   * Returns the child `Component` with the given `name`.
34033   *
34034   * @param {string} name
34035   *        The name of the child `Component` to get.
34036   *
34037   * @return {Component|undefined}
34038   *         The child `Component` with the given `name` or undefined.
34039   */
34040  getChild(name) {
34041    if (!name) {
34042      return;
34043    }
34044    return this.childNameIndex_[name];
34045  }
34046
34047  /**
34048   * Returns the descendant `Component` following the givent
vendor: 3,291 bytes, lines 34049-34139
34049   * descendant `names`. For instance ['foo', 'bar', 'baz'] would
34050   * try to get 'foo' on the current component, 'bar' on the 'foo'
34051   * component and 'baz' on the 'bar' component and return undefined
34052   * if any of those don't exist.
34053   *
34054   * @param {...string[]|...string} names
34055   *        The name of the child `Component` to get.
34056   *
34057   * @return {Component|undefined}
34058   *         The descendant `Component` following the given descendant
34059   *         `names` or undefined.
34060   */
34061  getDescendant(...names) {
34062    // flatten array argument into the main array
34063    names = names.reduce((acc, n) => acc.concat(n), []);
34064    let currentChild = this;
34065    for (let i = 0; i < names.length; i++) {
34066      currentChild = currentChild.getChild(names[i]);
34067      if (!currentChild || !currentChild.getChild) {
34068        return;
34069      }
34070    }
34071    return currentChild;
34072  }
34073
34074  /**
34075   * Adds an SVG icon element to another element or component.
34076   *
34077   * @param {string} iconName
34078   *        The name of icon. A list of all the icon names can be found at 'sandbox/svg-icons.html'
34079   *
34080   * @param {Element} [el=this.el()]
34081   *        Element to set the title on. Defaults to the current Component's element.
34082   *
34083   * @return {Element}
34084   *        The newly created icon element.
34085   */
34086  setIcon(iconName, el = this.el()) {
34087    // TODO: In v9 of video.js, we will want to remove font icons entirely.
34088    // This means this check, as well as the others throughout the code, and
34089    // the unecessary CSS for font icons, will need to be removed.
34090    // See https://github.com/videojs/video.js/pull/8260 as to which components
34091    // need updating.
34092    if (!this.player_.options_.experimentalSvgIcons) {
34093      return;
34094    }
34095    const xmlnsURL = 'http://www.w3.org/2000/svg';
34096
34097    // The below creates an element in the format of:
34098    // <span><svg><use>....</use></svg></span>
34099    const iconContainer = createEl('span', {
34100      className: 'vjs-icon-placeholder vjs-svg-icon'
34101    }, {
34102      'aria-hidden': 'true'
34103    });
34104    const svgEl = document__default["default"].createElementNS(xmlnsURL, 'svg');
34105    svgEl.setAttributeNS(null, 'viewBox', '0 0 512 512');
34106    const useEl = document__default["default"].createElementNS(xmlnsURL, 'use');
34107    svgEl.appendChild(useEl);
34108    useEl.setAttributeNS(null, 'href', `#vjs-icon-${iconName}`);
34109    iconContainer.appendChild(svgEl);
34110
34111    // Replace a pre-existing icon if one exists.
34112    if (this.iconIsSet_) {
34113      el.replaceChild(iconContainer, el.querySelector('.vjs-icon-placeholder'));
34114    } else {
34115      el.appendChild(iconContainer);
34116    }
34117    this.iconIsSet_ = true;
34118    return iconContainer;
34119  }
34120
34121  /**
34122   * Add a child `Component` inside the current `Component`.
34123   *
34124   * @param {string|Component} child
34125   *        The name or instance of a child to add.
34126   *
34127   * @param {Object} [options={}]
34128   *        The key/value store of options that will get passed to children of
34129   *        the child.
34130   *
34131   * @param {number} [index=this.children_.length]
34132   *        The index to attempt to add a child into.
34133   *
34134   *
34135   * @return {Component}
34136   *         The `Component` that gets added as a child. When using a string the
34137   *         `Component` will get created by this process.
34138   */
34139  addChild(child, options = {}, index = this.children_.length) {
vendor: 6,594 bytes, lines 34140-34327
34140    let component;
34141    let componentName;
34142
34143    // If child is a string, create component with options
34144    if (typeof child === 'string') {
34145      componentName = toTitleCase$1(child);
34146      const componentClassName = options.componentClass || componentName;
34147
34148      // Set name through options
34149      options.name = componentName;
34150
34151      // Create a new object & element for this controls set
34152      // If there's no .player_, this is a player
34153      const ComponentClass = Component$1.getComponent(componentClassName);
34154      if (!ComponentClass) {
34155        throw new Error(`Component ${componentClassName} does not exist`);
34156      }
34157
34158      // data stored directly on the videojs object may be
34159      // misidentified as a component to retain
34160      // backwards-compatibility with 4.x. check to make sure the
34161      // component class can be instantiated.
34162      if (typeof ComponentClass !== 'function') {
34163        return null;
34164      }
34165      component = new ComponentClass(this.player_ || this, options);
34166
34167      // child is a component instance
34168    } else {
34169      component = child;
34170    }
34171    if (component.parentComponent_) {
34172      component.parentComponent_.removeChild(component);
34173    }
34174    this.children_.splice(index, 0, component);
34175    component.parentComponent_ = this;
34176    if (typeof component.id === 'function') {
34177      this.childIndex_[component.id()] = component;
34178    }
34179
34180    // If a name wasn't used to create the component, check if we can use the
34181    // name function of the component
34182    componentName = componentName || component.name && toTitleCase$1(component.name());
34183    if (componentName) {
34184      this.childNameIndex_[componentName] = component;
34185      this.childNameIndex_[toLowerCase(componentName)] = component;
34186    }
34187
34188    // Add the UI object's element to the container div (box)
34189    // Having an element is not required
34190    if (typeof component.el === 'function' && component.el()) {
34191      // If inserting before a component, insert before that component's element
34192      let refNode = null;
34193      if (this.children_[index + 1]) {
34194        // Most children are components, but the video tech is an HTML element
34195        if (this.children_[index + 1].el_) {
34196          refNode = this.children_[index + 1].el_;
34197        } else if (isEl(this.children_[index + 1])) {
34198          refNode = this.children_[index + 1];
34199        }
34200      }
34201      this.contentEl().insertBefore(component.el(), refNode);
34202    }
34203
34204    // Return so it can stored on parent object if desired.
34205    return component;
34206  }
34207
34208  /**
34209   * Remove a child `Component` from this `Component`s list of children. Also removes
34210   * the child `Component`s element from this `Component`s element.
34211   *
34212   * @param {string|Component} component
34213   *       The name or instance of a child to remove.
34214   */
34215  removeChild(component) {
34216    if (typeof component === 'string') {
34217      component = this.getChild(component);
34218    }
34219    if (!component || !this.children_) {
34220      return;
34221    }
34222    let childFound = false;
34223    for (let i = this.children_.length - 1; i >= 0; i--) {
34224      if (this.children_[i] === component) {
34225        childFound = true;
34226        this.children_.splice(i, 1);
34227        break;
34228      }
34229    }
34230    if (!childFound) {
34231      return;
34232    }
34233    component.parentComponent_ = null;
34234    this.childIndex_[component.id()] = null;
34235    this.childNameIndex_[toTitleCase$1(component.name())] = null;
34236    this.childNameIndex_[toLowerCase(component.name())] = null;
34237    const compEl = component.el();
34238    if (compEl && compEl.parentNode === this.contentEl()) {
34239      this.contentEl().removeChild(component.el());
34240    }
34241  }
34242
34243  /**
34244   * Add and initialize default child `Component`s based upon options.
34245   */
34246  initChildren() {
34247    const children = this.options_.children;
34248    if (children) {
34249      // `this` is `parent`
34250      const parentOptions = this.options_;
34251      const handleAdd = child => {
34252        const name = child.name;
34253        let opts = child.opts;
34254
34255        // Allow options for children to be set at the parent options
34256        // e.g. videojs(id, { controlBar: false });
34257        // instead of videojs(id, { children: { controlBar: false });
34258        if (parentOptions[name] !== undefined) {
34259          opts = parentOptions[name];
34260        }
34261
34262        // Allow for disabling default components
34263        // e.g. options['children']['posterImage'] = false
34264        if (opts === false) {
34265          return;
34266        }
34267
34268        // Allow options to be passed as a simple boolean if no configuration
34269        // is necessary.
34270        if (opts === true) {
34271          opts = {};
34272        }
34273
34274        // We also want to pass the original player options
34275        // to each component as well so they don't need to
34276        // reach back into the player for options later.
34277        opts.playerOptions = this.options_.playerOptions;
34278
34279        // Create and add the child component.
34280        // Add a direct reference to the child by name on the parent instance.
34281        // If two of the same component are used, different names should be supplied
34282        // for each
34283        const newChild = this.addChild(name, opts);
34284        if (newChild) {
34285          this[name] = newChild;
34286        }
34287      };
34288
34289      // Allow for an array of children details to passed in the options
34290      let workingChildren;
34291      const Tech = Component$1.getComponent('Tech');
34292      if (Array.isArray(children)) {
34293        workingChildren = children;
34294      } else {
34295        workingChildren = Object.keys(children);
34296      }
34297      workingChildren
34298      // children that are in this.options_ but also in workingChildren  would
34299      // give us extra children we do not want. So, we want to filter them out.
34300      .concat(Object.keys(this.options_).filter(function (child) {
34301        return !workingChildren.some(function (wchild) {
34302          if (typeof wchild === 'string') {
34303            return child === wchild;
34304          }
34305          return child === wchild.name;
34306        });
34307      })).map(child => {
34308        let name;
34309        let opts;
34310        if (typeof child === 'string') {
34311          name = child;
34312          opts = children[name] || this.options_[name] || {};
34313        } else {
34314          name = child.name;
34315          opts = child;
34316        }
34317        return {
34318          name,
34319          opts
34320        };
34321      }).filter(child => {
34322        // we have to make sure that child.name isn't in the techOrder since
34323        // techs are registered as Components but can't aren't compatible
34324        // See https://github.com/videojs/video.js/issues/2772
34325        const c = Component$1.getComponent(child.opts.componentClass || toTitleCase$1(child.name));
34326        return c && !Tech.isTech(c);
34327      }
vendor: 9,442 bytes, lines 34327-34634
34327).forEach(handleAdd);
34328    }
34329  }
34330
34331  /**
34332   * Builds the default DOM class name. Should be overridden by sub-components.
34333   *
34334   * @return {string}
34335   *         The DOM class name for this object.
34336   *
34337   * @abstract
34338   */
34339  buildCSSClass() {
34340    // Child classes can include a function that does:
34341    // return 'CLASS NAME' + this._super();
34342    return '';
34343  }
34344
34345  /**
34346   * Bind a listener to the component's ready state.
34347   * Different from event listeners in that if the ready event has already happened
34348   * it will trigger the function immediately.
34349   *
34350   * @param {ReadyCallback} fn
34351   *        Function that gets called when the `Component` is ready.
34352   */
34353  ready(fn, sync = false) {
34354    if (!fn) {
34355      return;
34356    }
34357    if (!this.isReady_) {
34358      this.readyQueue_ = this.readyQueue_ || [];
34359      this.readyQueue_.push(fn);
34360      return;
34361    }
34362    if (sync) {
34363      fn.call(this);
34364    } else {
34365      // Call the function asynchronously by default for consistency
34366      this.setTimeout(fn, 1);
34367    }
34368  }
34369
34370  /**
34371   * Trigger all the ready listeners for this `Component`.
34372   *
34373   * @fires Component#ready
34374   */
34375  triggerReady() {
34376    this.isReady_ = true;
34377
34378    // Ensure ready is triggered asynchronously
34379    this.setTimeout(function () {
34380      const readyQueue = this.readyQueue_;
34381
34382      // Reset Ready Queue
34383      this.readyQueue_ = [];
34384      if (readyQueue && readyQueue.length > 0) {
34385        readyQueue.forEach(function (fn) {
34386          fn.call(this);
34387        }, this);
34388      }
34389
34390      // Allow for using event listeners also
34391      /**
34392       * Triggered when a `Component` is ready.
34393       *
34394       * @event Component#ready
34395       * @type {Event}
34396       */
34397      this.trigger('ready');
34398    }, 1);
34399  }
34400
34401  /**
34402   * Find a single DOM element matching a `selector`. This can be within the `Component`s
34403   * `contentEl()` or another custom context.
34404   *
34405   * @param {string} selector
34406   *        A valid CSS selector, which will be passed to `querySelector`.
34407   *
34408   * @param {Element|string} [context=this.contentEl()]
34409   *        A DOM element within which to query. Can also be a selector string in
34410   *        which case the first matching element will get used as context. If
34411   *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
34412   *        nothing it falls back to `document`.
34413   *
34414   * @return {Element|null}
34415   *         the dom element that was found, or null
34416   *
34417   * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
34418   */
34419  $(selector, context) {
34420    return $(selector, context || this.contentEl());
34421  }
34422
34423  /**
34424   * Finds all DOM element matching a `selector`. This can be within the `Component`s
34425   * `contentEl()` or another custom context.
34426   *
34427   * @param {string} selector
34428   *        A valid CSS selector, which will be passed to `querySelectorAll`.
34429   *
34430   * @param {Element|string} [context=this.contentEl()]
34431   *        A DOM element within which to query. Can also be a selector string in
34432   *        which case the first matching element will get used as context. If
34433   *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
34434   *        nothing it falls back to `document`.
34435   *
34436   * @return {NodeList}
34437   *         a list of dom elements that were found
34438   *
34439   * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
34440   */
34441  $$(selector, context) {
34442    return $$(selector, context || this.contentEl());
34443  }
34444
34445  /**
34446   * Check if a component's element has a CSS class name.
34447   *
34448   * @param {string} classToCheck
34449   *        CSS class name to check.
34450   *
34451   * @return {boolean}
34452   *         - True if the `Component` has the class.
34453   *         - False if the `Component` does not have the class`
34454   */
34455  hasClass(classToCheck) {
34456    return hasClass(this.el_, classToCheck);
34457  }
34458
34459  /**
34460   * Add a CSS class name to the `Component`s element.
34461   *
34462   * @param {...string} classesToAdd
34463   *        One or more CSS class name to add.
34464   */
34465  addClass(...classesToAdd) {
34466    addClass(this.el_, ...classesToAdd);
34467  }
34468
34469  /**
34470   * Remove a CSS class name from the `Component`s element.
34471   *
34472   * @param {...string} classesToRemove
34473   *        One or more CSS class name to remove.
34474   */
34475  removeClass(...classesToRemove) {
34476    removeClass(this.el_, ...classesToRemove);
34477  }
34478
34479  /**
34480   * Add or remove a CSS class name from the component's element.
34481   * - `classToToggle` gets added when {@link Component#hasClass} would return false.
34482   * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
34483   *
34484   * @param  {string} classToToggle
34485   *         The class to add or remove. Passed to DOMTokenList's toggle()
34486   *
34487   * @param  {boolean|Dom.PredicateCallback} [predicate]
34488   *         A boolean or function that returns a boolean. Passed to DOMTokenList's toggle().
34489   */
34490  toggleClass(classToToggle, predicate) {
34491    toggleClass(this.el_, classToToggle, predicate);
34492  }
34493
34494  /**
34495   * Show the `Component`s element if it is hidden by removing the
34496   * 'vjs-hidden' class name from it.
34497   */
34498  show() {
34499    this.removeClass('vjs-hidden');
34500  }
34501
34502  /**
34503   * Hide the `Component`s element if it is currently showing by adding the
34504   * 'vjs-hidden` class name to it.
34505   */
34506  hide() {
34507    this.addClass('vjs-hidden');
34508  }
34509
34510  /**
34511   * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
34512   * class name to it. Used during fadeIn/fadeOut.
34513   *
34514   * @private
34515   */
34516  lockShowing() {
34517    this.addClass('vjs-lock-showing');
34518  }
34519
34520  /**
34521   * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
34522   * class name from it. Used during fadeIn/fadeOut.
34523   *
34524   * @private
34525   */
34526  unlockShowing() {
34527    this.removeClass('vjs-lock-showing');
34528  }
34529
34530  /**
34531   * Get the value of an attribute on the `Component`s element.
34532   *
34533   * @param {string} attribute
34534   *        Name of the attribute to get the value from.
34535   *
34536   * @return {string|null}
34537   *         - The value of the attribute that was asked for.
34538   *         - Can be an empty string on some browsers if the attribute does not exist
34539   *           or has no value
34540   *         - Most browsers will return null if the attribute does not exist or has
34541   *           no value.
34542   *
34543   * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
34544   */
34545  getAttribute(attribute) {
34546    return getAttribute(this.el_, attribute);
34547  }
34548
34549  /**
34550   * Set the value of an attribute on the `Component`'s element
34551   *
34552   * @param {string} attribute
34553   *        Name of the attribute to set.
34554   *
34555   * @param {string} value
34556   *        Value to set the attribute to.
34557   *
34558   * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
34559   */
34560  setAttribute(attribute, value) {
34561    setAttribute(this.el_, attribute, value);
34562  }
34563
34564  /**
34565   * Remove an attribute from the `Component`s element.
34566   *
34567   * @param {string} attribute
34568   *        Name of the attribute to remove.
34569   *
34570   * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
34571   */
34572  removeAttribute(attribute) {
34573    removeAttribute(this.el_, attribute);
34574  }
34575
34576  /**
34577   * Get or set the width of the component based upon the CSS styles.
34578   * See {@link Component#dimension} for more detailed information.
34579   *
34580   * @param {number|string} [num]
34581   *        The width that you want to set postfixed with '%', 'px' or nothing.
34582   *
34583   * @param {boolean} [skipListeners]
34584   *        Skip the componentresize event trigger
34585   *
34586   * @return {number|undefined}
34587   *         The width when getting, zero if there is no width
34588   */
34589  width(num, skipListeners) {
34590    return this.dimension('width', num, skipListeners);
34591  }
34592
34593  /**
34594   * Get or set the height of the component based upon the CSS styles.
34595   * See {@link Component#dimension} for more detailed information.
34596   *
34597   * @param {number|string} [num]
34598   *        The height that you want to set postfixed with '%', 'px' or nothing.
34599   *
34600   * @param {boolean} [skipListeners]
34601   *        Skip the componentresize event trigger
34602   *
34603   * @return {number|undefined}
34604   *         The height when getting, zero if there is no height
34605   */
34606  height(num, skipListeners) {
34607    return this.dimension('height', num, skipListeners);
34608  }
34609
34610  /**
34611   * Set both the width and height of the `Component` element at the same time.
34612   *
34613   * @param  {number|string} width
34614   *         Width to set the `Component`s element to.
34615   *
34616   * @param  {number|string} height
34617   *         Height to set the `Component`s element to.
34618   */
34619  dimensions(width, height) {
34620    // Skip componentresize listeners on width for optimization
34621    this.width(width, true);
34622    this.height(height);
34623  }
34624
34625  /**
34626   * Get or set width or height of the `Component` element. This is the shared code
34627   * for the {@link Component#width} and {@link Component#height}.
34628   *
34629   * Things to know:
34630   * - If the width or height in an number this will return the number postfixed with 'px'.
34631   * - If the width/height is a percent this will return the percent postfixed with '%'
34632   * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
34633   *   defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
34634   *   See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-u
vendor: 6,321 bytes, lines 34634-34847
34634sing-width/}
34635   *   for more information
34636   * - If you want the computed style of the component, use {@link Component#currentWidth}
34637   *   and {@link {Component#currentHeight}
34638   *
34639   * @fires Component#componentresize
34640   *
34641   * @param {string} widthOrHeight
34642   8        'width' or 'height'
34643   *
34644   * @param  {number|string} [num]
34645   8         New dimension
34646   *
34647   * @param  {boolean} [skipListeners]
34648   *         Skip componentresize event trigger
34649   *
34650   * @return {number|undefined}
34651   *         The dimension when getting or 0 if unset
34652   */
34653  dimension(widthOrHeight, num, skipListeners) {
34654    if (num !== undefined) {
34655      // Set to zero if null or literally NaN (NaN !== NaN)
34656      if (num === null || num !== num) {
34657        num = 0;
34658      }
34659
34660      // Check if using css width/height (% or px) and adjust
34661      if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
34662        this.el_.style[widthOrHeight] = num;
34663      } else if (num === 'auto') {
34664        this.el_.style[widthOrHeight] = '';
34665      } else {
34666        this.el_.style[widthOrHeight] = num + 'px';
34667      }
34668
34669      // skipListeners allows us to avoid triggering the resize event when setting both width and height
34670      if (!skipListeners) {
34671        /**
34672         * Triggered when a component is resized.
34673         *
34674         * @event Component#componentresize
34675         * @type {Event}
34676         */
34677        this.trigger('componentresize');
34678      }
34679      return;
34680    }
34681
34682    // Not setting a value, so getting it
34683    // Make sure element exists
34684    if (!this.el_) {
34685      return 0;
34686    }
34687
34688    // Get dimension value from style
34689    const val = this.el_.style[widthOrHeight];
34690    const pxIndex = val.indexOf('px');
34691    if (pxIndex !== -1) {
34692      // Return the pixel value with no 'px'
34693      return parseInt(val.slice(0, pxIndex), 10);
34694    }
34695
34696    // No px so using % or no style was set, so falling back to offsetWidth/height
34697    // If component has display:none, offset will return 0
34698    // TODO: handle display:none and no dimension style using px
34699    return parseInt(this.el_['offset' + toTitleCase$1(widthOrHeight)], 10);
34700  }
34701
34702  /**
34703   * Get the computed width or the height of the component's element.
34704   *
34705   * Uses `window.getComputedStyle`.
34706   *
34707   * @param {string} widthOrHeight
34708   *        A string containing 'width' or 'height'. Whichever one you want to get.
34709   *
34710   * @return {number}
34711   *         The dimension that gets asked for or 0 if nothing was set
34712   *         for that dimension.
34713   */
34714  currentDimension(widthOrHeight) {
34715    let computedWidthOrHeight = 0;
34716    if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
34717      throw new Error('currentDimension only accepts width or height value');
34718    }
34719    computedWidthOrHeight = computedStyle(this.el_, widthOrHeight);
34720
34721    // remove 'px' from variable and parse as integer
34722    computedWidthOrHeight = parseFloat(computedWidthOrHeight);
34723
34724    // if the computed value is still 0, it's possible that the browser is lying
34725    // and we want to check the offset values.
34726    // This code also runs wherever getComputedStyle doesn't exist.
34727    if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) {
34728      const rule = `offset${toTitleCase$1(widthOrHeight)}`;
34729      computedWidthOrHeight = this.el_[rule];
34730    }
34731    return computedWidthOrHeight;
34732  }
34733
34734  /**
34735   * An object that contains width and height values of the `Component`s
34736   * computed style. Uses `window.getComputedStyle`.
34737   *
34738   * @typedef {Object} Component~DimensionObject
34739   *
34740   * @property {number} width
34741   *           The width of the `Component`s computed style.
34742   *
34743   * @property {number} height
34744   *           The height of the `Component`s computed style.
34745   */
34746
34747  /**
34748   * Get an object that contains computed width and height values of the
34749   * component's element.
34750   *
34751   * Uses `window.getComputedStyle`.
34752   *
34753   * @return {Component~DimensionObject}
34754   *         The computed dimensions of the component's element.
34755   */
34756  currentDimensions() {
34757    return {
34758      width: this.currentDimension('width'),
34759      height: this.currentDimension('height')
34760    };
34761  }
34762
34763  /**
34764   * Get the computed width of the component's element.
34765   *
34766   * Uses `window.getComputedStyle`.
34767   *
34768   * @return {number}
34769   *         The computed width of the component's element.
34770   */
34771  currentWidth() {
34772    return this.currentDimension('width');
34773  }
34774
34775  /**
34776   * Get the computed height of the component's element.
34777   *
34778   * Uses `window.getComputedStyle`.
34779   *
34780   * @return {number}
34781   *         The computed height of the component's element.
34782   */
34783  currentHeight() {
34784    return this.currentDimension('height');
34785  }
34786
34787  /**
34788   * Retrieves the position and size information of the component's element.
34789   *
34790   * @return {Object} An object with `boundingClientRect` and `center` properties.
34791   *         - `boundingClientRect`: An object with properties `x`, `y`, `width`,
34792   *           `height`, `top`, `right`, `bottom`, and `left`, representing
34793   *           the bounding rectangle of the element.
34794   *         - `center`: An object with properties `x` and `y`, representing
34795   *           the center point of the element. `width` and `height` are set to 0.
34796   */
34797  getPositions() {
34798    const rect = this.el_.getBoundingClientRect();
34799
34800    // Creating objects that mirror DOMRectReadOnly for boundingClientRect and center
34801    const boundingClientRect = {
34802      x: rect.x,
34803      y: rect.y,
34804      width: rect.width,
34805      height: rect.height,
34806      top: rect.top,
34807      right: rect.right,
34808      bottom: rect.bottom,
34809      left: rect.left
34810    };
34811
34812    // Calculating the center position
34813    const center = {
34814      x: rect.left + rect.width / 2,
34815      y: rect.top + rect.height / 2,
34816      width: 0,
34817      height: 0,
34818      top: rect.top + rect.height / 2,
34819      right: rect.left + rect.width / 2,
34820      bottom: rect.top + rect.height / 2,
34821      left: rect.left + rect.width / 2
34822    };
34823    return {
34824      boundingClientRect,
34825      center
34826    };
34827  }
34828
34829  /**
34830   * Set the focus to this component
34831   */
34832  focus() {
34833    this.el_.focus();
34834  }
34835
34836  /**
34837   * Remove the focus from this component
34838   */
34839  blur() {
34840    this.el_.blur();
34841  }
34842
34843  /**
34844   * When this Component receives a `keydown` event which it does not process,
34845   *  it passes the event to the Player for handling.
34846   *
34847   * @param {KeyboardEvent} event
vendor: 20,063 bytes, lines 34848-35370
34848   *        The `keydown` event that caused this function to be called.
34849   */
34850  handleKeyDown(event) {
34851    if (this.player_) {
34852      // We only stop propagation here because we want unhandled events to fall
34853      // back to the browser. Exclude Tab for focus trapping, exclude also when spatialNavigation is enabled.
34854      if (event.key !== 'Tab' && !(this.player_.options_.playerOptions.spatialNavigation && this.player_.options_.playerOptions.spatialNavigation.enabled)) {
34855        event.stopPropagation();
34856      }
34857      this.player_.handleKeyDown(event);
34858    }
34859  }
34860
34861  /**
34862   * Many components used to have a `handleKeyPress` method, which was poorly
34863   * named because it listened to a `keydown` event. This method name now
34864   * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress`
34865   * will not see their method calls stop working.
34866   *
34867   * @param {KeyboardEvent} event
34868   *        The event that caused this function to be called.
34869   */
34870  handleKeyPress(event) {
34871    this.handleKeyDown(event);
34872  }
34873
34874  /**
34875   * Emit a 'tap' events when touch event support gets detected. This gets used to
34876   * support toggling the controls through a tap on the video. They get enabled
34877   * because every sub-component would have extra overhead otherwise.
34878   *
34879   * @protected
34880   * @fires Component#tap
34881   * @listens Component#touchstart
34882   * @listens Component#touchmove
34883   * @listens Component#touchleave
34884   * @listens Component#touchcancel
34885   * @listens Component#touchend
34886    */
34887  emitTapEvents() {
34888    // Track the start time so we can determine how long the touch lasted
34889    let touchStart = 0;
34890    let firstTouch = null;
34891
34892    // Maximum movement allowed during a touch event to still be considered a tap
34893    // Other popular libs use anywhere from 2 (hammer.js) to 15,
34894    // so 10 seems like a nice, round number.
34895    const tapMovementThreshold = 10;
34896
34897    // The maximum length a touch can be while still being considered a tap
34898    const touchTimeThreshold = 200;
34899    let couldBeTap;
34900    this.on('touchstart', function (event) {
34901      // If more than one finger, don't consider treating this as a click
34902      if (event.touches.length === 1) {
34903        // Copy pageX/pageY from the object
34904        firstTouch = {
34905          pageX: event.touches[0].pageX,
34906          pageY: event.touches[0].pageY
34907        };
34908        // Record start time so we can detect a tap vs. "touch and hold"
34909        touchStart = window__default["default"].performance.now();
34910        // Reset couldBeTap tracking
34911        couldBeTap = true;
34912      }
34913    });
34914    this.on('touchmove', function (event) {
34915      // If more than one finger, don't consider treating this as a click
34916      if (event.touches.length > 1) {
34917        couldBeTap = false;
34918      } else if (firstTouch) {
34919        // Some devices will throw touchmoves for all but the slightest of taps.
34920        // So, if we moved only a small distance, this could still be a tap
34921        const xdiff = event.touches[0].pageX - firstTouch.pageX;
34922        const ydiff = event.touches[0].pageY - firstTouch.pageY;
34923        const touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
34924        if (touchDistance > tapMovementThreshold) {
34925          couldBeTap = false;
34926        }
34927      }
34928    });
34929    const noTap = function () {
34930      couldBeTap = false;
34931    };
34932
34933    // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
34934    this.on('touchleave', noTap);
34935    this.on('touchcancel', noTap);
34936
34937    // When the touch ends, measure how long it took and trigger the appropriate
34938    // event
34939    this.on('touchend', function (event) {
34940      firstTouch = null;
34941      // Proceed only if the touchmove/leave/cancel event didn't happen
34942      if (couldBeTap === true) {
34943        // Measure how long the touch lasted
34944        const touchTime = window__default["default"].performance.now() - touchStart;
34945
34946        // Make sure the touch was less than the threshold to be considered a tap
34947        if (touchTime < touchTimeThreshold) {
34948          // Don't let browser turn this into a click
34949          event.preventDefault();
34950          /**
34951           * Triggered when a `Component` is tapped.
34952           *
34953           * @event Component#tap
34954           * @type {MouseEvent}
34955           */
34956          this.trigger('tap');
34957          // It may be good to copy the touchend event object and change the
34958          // type to tap, if the other event properties aren't exact after
34959          // Events.fixEvent runs (e.g. event.target)
34960        }
34961      }
34962    });
34963  }
34964
34965  /**
34966   * This function reports user activity whenever touch events happen. This can get
34967   * turned off by any sub-components that wants touch events to act another way.
34968   *
34969   * Report user touch activity when touch events occur. User activity gets used to
34970   * determine when controls should show/hide. It is simple when it comes to mouse
34971   * events, because any mouse event should show the controls. So we capture mouse
34972   * events that bubble up to the player and report activity when that happens.
34973   * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
34974   * controls. So touch events can't help us at the player level either.
34975   *
34976   * User activity gets checked asynchronously. So what could happen is a tap event
34977   * on the video turns the controls off. Then the `touchend` event bubbles up to
34978   * the player. Which, if it reported user activity, would turn the controls right
34979   * back on. We also don't want to completely block touch events from bubbling up.
34980   * Furthermore a `touchmove` event and anything other than a tap, should not turn
34981   * controls back on.
34982   *
34983   * @listens Component#touchstart
34984   * @listens Component#touchmove
34985   * @listens Component#touchend
34986   * @listens Component#touchcancel
34987   */
34988  enableTouchActivity() {
34989    // Don't continue if the root player doesn't support reporting user activity
34990    if (!this.player() || !this.player().reportUserActivity) {
34991      return;
34992    }
34993
34994    // listener for reporting that the user is active
34995    const report = bind_(this.player(), this.player().reportUserActivity);
34996    let touchHolding;
34997    this.on('touchstart', function () {
34998      report();
34999      // For as long as the they are touching the device or have their mouse down,
35000      // we consider them active even if they're not moving their finger or mouse.
35001      // So we want to continue to update that they are active
35002      this.clearInterval(touchHolding);
35003      // report at the same interval as activityCheck
35004      touchHolding = this.setInterval(report, 250);
35005    });
35006    const touchEnd = function (event) {
35007      report();
35008      // stop the interval that maintains activity if the touch is holding
35009      this.clearInterval(touchHolding);
35010    };
35011    this.on('touchmove', report);
35012    this.on('touchend', touchEnd);
35013    this.on('touchcancel', touchEnd);
35014  }
35015
35016  /**
35017   * A callback that has no parameters and is bound into `Component`s context.
35018   *
35019   * @callback Component~GenericCallback
35020   * @this Component
35021   */
35022
35023  /**
35024   * Creates a function that runs after an `x` millisecond timeout. This function is a
35025   * wrapper around `window.setTimeout`. There are a few reasons to use this one
35026   * instead though:
35027   * 1. It gets cleared via  {@link Component#clearTimeout} when
35028   *    {@link Component#dispose} gets called.
35029   * 2. The function callback will gets turned into a {@link Component~GenericCallback}
35030   *
35031   * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
35032   *         will cause its dispose listener not to get cleaned up! Please use
35033   *         {@link Component#clearTimeout} or {@link Component#dispose} instead.
35034   *
35035   * @param {Component~GenericCallback} fn
35036   *        The function that will be run after `timeout`.
35037   *
35038   * @param {number} timeout
35039   *        Timeout in milliseconds to delay before executing the specified function.
35040   *
35041   * @return {number}
35042   *         Returns a timeout ID that gets used to identify the timeout. It can also
35043   *         get used in {@link Component#clearTimeout} to clear the timeout that
35044   *         was set.
35045   *
35046   * @listens Component#dispose
35047   * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
35048   */
35049  setTimeout(fn, timeout) {
35050    // declare as variables so they are properly available in timeout function
35051    // eslint-disable-next-line
35052    var timeoutId;
35053    fn = bind_(this, fn);
35054    this.clearTimersOnDispose_();
35055    timeoutId = window__default["default"].setTimeout(() => {
35056      if (this.setTimeoutIds_.has(timeoutId)) {
35057        this.setTimeoutIds_.delete(timeoutId);
35058      }
35059      fn();
35060    }, timeout);
35061    this.setTimeoutIds_.add(timeoutId);
35062    return timeoutId;
35063  }
35064
35065  /**
35066   * Clears a timeout that gets created via `window.setTimeout` or
35067   * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
35068   * use this function instead of `window.clearTimout`. If you don't your dispose
35069   * listener will not get cleaned up until {@link Component#dispose}!
35070   *
35071   * @param {number} timeoutId
35072   *        The id of the timeout to clear. The return value of
35073   *        {@link Component#setTimeout} or `window.setTimeout`.
35074   *
35075   * @return {number}
35076   *         Returns the timeout id that was cleared.
35077   *
35078   * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
35079   */
35080  clearTimeout(timeoutId) {
35081    if (this.setTimeoutIds_.has(timeoutId)) {
35082      this.setTimeoutIds_.delete(timeoutId);
35083      window__default["default"].clearTimeout(timeoutId);
35084    }
35085    return timeoutId;
35086  }
35087
35088  /**
35089   * Creates a function that gets run every `x` milliseconds. This function is a wrapper
35090   * around `window.setInterval`. There are a few reasons to use this one instead though.
35091   * 1. It gets cleared via  {@link Component#clearInterval} when
35092   *    {@link Component#dispose} gets called.
35093   * 2. The function callback will be a {@link Component~GenericCallback}
35094   *
35095   * @param {Component~GenericCallback} fn
35096   *        The function to run every `x` seconds.
35097   *
35098   * @param {number} interval
35099   *        Execute the specified function every `x` milliseconds.
35100   *
35101   * @return {number}
35102   *         Returns an id that can be used to identify the interval. It can also be be used in
35103   *         {@link Component#clearInterval} to clear the interval.
35104   *
35105   * @listens Component#dispose
35106   * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
35107   */
35108  setInterval(fn, interval) {
35109    fn = bind_(this, fn);
35110    this.clearTimersOnDispose_();
35111    const intervalId = window__default["default"].setInterval(fn, interval);
35112    this.setIntervalIds_.add(intervalId);
35113    return intervalId;
35114  }
35115
35116  /**
35117   * Clears an interval that gets created via `window.setInterval` or
35118   * {@link Component#setInterval}. If you set an interval via {@link Component#setInterval}
35119   * use this function instead of `window.clearInterval`. If you don't your dispose
35120   * listener will not get cleaned up until {@link Component#dispose}!
35121   *
35122   * @param {number} intervalId
35123   *        The id of the interval to clear. The return value of
35124   *        {@link Component#setInterval} or `window.setInterval`.
35125   *
35126   * @return {number}
35127   *         Returns the interval id that was cleared.
35128   *
35129   * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
35130   */
35131  clearInterval(intervalId) {
35132    if (this.setIntervalIds_.has(intervalId)) {
35133      this.setIntervalIds_.delete(intervalId);
35134      window__default["default"].clearInterval(intervalId);
35135    }
35136    return intervalId;
35137  }
35138
35139  /**
35140   * Queues up a callback to be passed to requestAnimationFrame (rAF), but
35141   * with a few extra bonuses:
35142   *
35143   * - Supports browsers that do not support rAF by falling back to
35144   *   {@link Component#setTimeout}.
35145   *
35146   * - The callback is turned into a {@link Component~GenericCallback} (i.e.
35147   *   bound to the component).
35148   *
35149   * - Automatic cancellation of the rAF callback is handled if the component
35150   *   is disposed before it is called.
35151   *
35152   * @param  {Component~GenericCallback} fn
35153   *         A function that will be bound to this component and executed just
35154   *         before the browser's next repaint.
35155   *
35156   * @return {number}
35157   *         Returns an rAF ID that gets used to identify the timeout. It can
35158   *         also be used in {@link Component#cancelAnimationFrame} to cancel
35159   *         the animation frame callback.
35160   *
35161   * @listens Component#dispose
35162   * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
35163   */
35164  requestAnimationFrame(fn) {
35165    this.clearTimersOnDispose_();
35166
35167    // declare as variables so they are properly available in rAF function
35168    // eslint-disable-next-line
35169    var id;
35170    fn = bind_(this, fn);
35171    id = window__default["default"].requestAnimationFrame(() => {
35172      if (this.rafIds_.has(id)) {
35173        this.rafIds_.delete(id);
35174      }
35175      fn();
35176    });
35177    this.rafIds_.add(id);
35178    return id;
35179  }
35180
35181  /**
35182   * Request an animation frame, but only one named animation
35183   * frame will be queued. Another will never be added until
35184   * the previous one finishes.
35185   *
35186   * @param {string} name
35187   *        The name to give this requestAnimationFrame
35188   *
35189   * @param  {Component~GenericCallback} fn
35190   *         A function that will be bound to this component and executed just
35191   *         before the browser's next repaint.
35192   */
35193  requestNamedAnimationFrame(name, fn) {
35194    if (this.namedRafs_.has(name)) {
35195      this.cancelNamedAnimationFrame(name);
35196    }
35197    this.clearTimersOnDispose_();
35198    fn = bind_(this, fn);
35199    const id = this.requestAnimationFrame(() => {
35200      fn();
35201      if (this.namedRafs_.has(name)) {
35202        this.namedRafs_.delete(name);
35203      }
35204    });
35205    this.namedRafs_.set(name, id);
35206    return name;
35207  }
35208
35209  /**
35210   * Cancels a current named animation frame if it exists.
35211   *
35212   * @param {string} name
35213   *        The name of the requestAnimationFrame to cancel.
35214   */
35215  cancelNamedAnimationFrame(name) {
35216    if (!this.namedRafs_.has(name)) {
35217      return;
35218    }
35219    this.cancelAnimationFrame(this.namedRafs_.get(name));
35220    this.namedRafs_.delete(name);
35221  }
35222
35223  /**
35224   * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
35225   * (rAF).
35226   *
35227   * If you queue an rAF callback via {@link Component#requestAnimationFrame},
35228   * use this function instead of `window.cancelAnimationFrame`. If you don't,
35229   * your dispose listener will not get cleaned up until {@link Component#dispose}!
35230   *
35231   * @param {number} id
35232   *        The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
35233   *
35234   * @return {number}
35235   *         Returns the rAF ID that was cleared.
35236   *
35237   * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
35238   */
35239  cancelAnimationFrame(id) {
35240    if (this.rafIds_.has(id)) {
35241      this.rafIds_.delete(id);
35242      window__default["default"].cancelAnimationFrame(id);
35243    }
35244    return id;
35245  }
35246
35247  /**
35248   * A function to setup `requestAnimationFrame`, `setTimeout`,
35249   * and `setInterval`, clearing on dispose.
35250   *
35251   * > Previously each timer added and removed dispose listeners on it's own.
35252   * For better performance it was decided to batch them all, and use `Set`s
35253   * to track outstanding timer ids.
35254   *
35255   * @private
35256   */
35257  clearTimersOnDispose_() {
35258    if (this.clearingTimersOnDispose_) {
35259      return;
35260    }
35261    this.clearingTimersOnDispose_ = true;
35262    this.one('dispose', () => {
35263      [['namedRafs_', 'cancelNamedAnimationFrame'], ['rafIds_', 'cancelAnimationFrame'], ['setTimeoutIds_', 'clearTimeout'], ['setIntervalIds_', 'clearInterval']].forEach(([idName, cancelName]) => {
35264        // for a `Set` key will actually be the value again
35265        // so forEach((val, val) =>` but for maps we want to use
35266        // the key.
35267        this[idName].forEach((val, key) => this[cancelName](key));
35268      });
35269      this.clearingTimersOnDispose_ = false;
35270    });
35271  }
35272
35273  /**
35274    * Decide whether an element is actually disabled or not.
35275    *
35276    * @function isActuallyDisabled
35277    * @param element {Node}
35278    * @return {boolean}
35279    *
35280    * @see {@link https://html.spec.whatwg.org/multipage/semantics-other.html#concept-element-disabled}
35281    */
35282  getIsDisabled() {
35283    return Boolean(this.el_.disabled);
35284  }
35285
35286  /**
35287    * Decide whether the element is expressly inert or not.
35288    *
35289    * @see {@link https://html.spec.whatwg.org/multipage/interaction.html#expressly-inert}
35290    * @function isExpresslyInert
35291    * @param element {Node}
35292    * @return {boolean}
35293    */
35294  getIsExpresslyInert() {
35295    return this.el_.inert && !this.el_.ownerDocument.documentElement.inert;
35296  }
35297
35298  /**
35299   * Determine whether or not this component can be considered as focusable component.
35300   *
35301   * @param {HTMLElement} el - The HTML element representing the component.
35302   * @return {boolean}
35303   *         If the component can be focused, will be `true`. Otherwise, `false`.
35304   */
35305  getIsFocusable(el) {
35306    const element = el || this.el_;
35307    return element.tabIndex >= 0 && !(this.getIsDisabled() || this.getIsExpresslyInert());
35308  }
35309
35310  /**
35311   * Determine whether or not this component is currently visible/enabled/etc...
35312   *
35313   * @param {HTMLElement} el - The HTML element representing the component.
35314   * @return {boolean}
35315   *         If the component can is currently visible & enabled, will be `true`. Otherwise, `false`.
35316   */
35317  getIsAvailableToBeFocused(el) {
35318    /**
35319     * Decide the style property of this element is specified whether it's visible or not.
35320     *
35321     * @function isVisibleStyleProperty
35322     * @param element {CSSStyleDeclaration}
35323     * @return {boolean}
35324     */
35325    function isVisibleStyleProperty(element) {
35326      const elementStyle = window__default["default"].getComputedStyle(element, null);
35327      const thisVisibility = elementStyle.getPropertyValue('visibility');
35328      const thisDisplay = elementStyle.getPropertyValue('display');
35329      const invisibleStyle = ['hidden', 'collapse'];
35330      return thisDisplay !== 'none' && !invisibleStyle.includes(thisVisibility);
35331    }
35332
35333    /**
35334     * Decide whether the element is being rendered or not.
35335     * 1. If an element has the style as "visibility: hidden | collapse" or "display: none", it is not being rendered.
35336     * 2. If an element has the style as "opacity: 0", it is not being rendered.(that is, invisible).
35337     * 3. If width and height of an element are explicitly set to 0, it is not being rendered.
35338     * 4. If a parent element is hidden, an element itself is not being rendered.
35339     * (CSS visibility property and display property are inherited.)
35340     *
35341     * @see {@link https://html.spec.whatwg.org/multipage/rendering.html#being-rendered}
35342     * @function isBeingRendered
35343     * @param element {Node}
35344     * @return {boolean}
35345     */
35346    function isBeingRendered(element) {
35347      if (!isVisibleStyleProperty(element.parentElement)) {
35348        return false;
35349      }
35350      if (!isVisibleStyleProperty(element) || element.style.opacity === '0' || window__default["default"].getComputedStyle(element).height === '0px' || window__default["default"].getComputedStyle(element).width === '0px') {
35351        return false;
35352      }
35353      return true;
35354    }
35355
35356    /**
35357     * Determine if the element is visible for the user or not.
35358     * 1. If an element sum of its offsetWidth, offsetHeight, height and width is less than 1 is not visible.
35359     * 2. If elementCenter.x is less than is not visible.
35360     * 3. If elementCenter.x is more than the document's width is not visible.
35361     * 4. If elementCenter.y is less than 0 is not visible.
35362     * 5. If elementCenter.y is the document's height is not visible.
35363     *
35364     * @function isVisible
35365     * @param element {Node}
35366     * @return {boolean}
35367     */
35368    function isVisible(element) {
35369      if (element.offsetWidth + element.offsetHeight + element.getBoundingClientRect().height + element.getBoundingClientRect().width === 0) {
35370        return false;
vendor: 9,896 bytes, lines 35371-35675
35371      }
35372
35373      // Define elementCenter object with props of x and y
35374      // x: Left position relative to the viewport plus element's width (no margin) divided between 2.
35375      // y: Top position relative to the viewport plus element's height (no margin) divided between 2.
35376      const elementCenter = {
35377        x: element.getBoundingClientRect().left + element.offsetWidth / 2,
35378        y: element.getBoundingClientRect().top + element.offsetHeight / 2
35379      };
35380      if (elementCenter.x < 0) {
35381        return false;
35382      }
35383      if (elementCenter.x > (document__default["default"].documentElement.clientWidth || window__default["default"].innerWidth)) {
35384        return false;
35385      }
35386      if (elementCenter.y < 0) {
35387        return false;
35388      }
35389      if (elementCenter.y > (document__default["default"].documentElement.clientHeight || window__default["default"].innerHeight)) {
35390        return false;
35391      }
35392      let pointContainer = document__default["default"].elementFromPoint(elementCenter.x, elementCenter.y);
35393      while (pointContainer) {
35394        if (pointContainer === element) {
35395          return true;
35396        }
35397        if (pointContainer.parentNode) {
35398          pointContainer = pointContainer.parentNode;
35399        } else {
35400          return false;
35401        }
35402      }
35403    }
35404
35405    // If no DOM element was passed as argument use this component's element.
35406    if (!el) {
35407      el = this.el();
35408    }
35409
35410    // If element is visible, is being rendered & either does not have a parent element or its tabIndex is not negative.
35411    if (isVisible(el) && isBeingRendered(el) && (!el.parentElement || el.tabIndex >= 0)) {
35412      return true;
35413    }
35414    return false;
35415  }
35416
35417  /**
35418   * Register a `Component` with `videojs` given the name and the component.
35419   *
35420   * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
35421   *         should be registered using {@link Tech.registerTech} or
35422   *         {@link videojs:videojs.registerTech}.
35423   *
35424   * > NOTE: This function can also be seen on videojs as
35425   *         {@link videojs:videojs.registerComponent}.
35426   *
35427   * @param {string} name
35428   *        The name of the `Component` to register.
35429   *
35430   * @param {Component} ComponentToRegister
35431   *        The `Component` class to register.
35432   *
35433   * @return {Component}
35434   *         The `Component` that was registered.
35435   */
35436  static registerComponent(name, ComponentToRegister) {
35437    if (typeof name !== 'string' || !name) {
35438      throw new Error(`Illegal component name, "${name}"; must be a non-empty string.`);
35439    }
35440    const Tech = Component$1.getComponent('Tech');
35441
35442    // We need to make sure this check is only done if Tech has been registered.
35443    const isTech = Tech && Tech.isTech(ComponentToRegister);
35444    const isComp = Component$1 === ComponentToRegister || Component$1.prototype.isPrototypeOf(ComponentToRegister.prototype);
35445    if (isTech || !isComp) {
35446      let reason;
35447      if (isTech) {
35448        reason = 'techs must be registered using Tech.registerTech()';
35449      } else {
35450        reason = 'must be a Component subclass';
35451      }
35452      throw new Error(`Illegal component, "${name}"; ${reason}.`);
35453    }
35454    name = toTitleCase$1(name);
35455    if (!Component$1.components_) {
35456      Component$1.components_ = {};
35457    }
35458    const Player = Component$1.getComponent('Player');
35459    if (name === 'Player' && Player && Player.players) {
35460      const players = Player.players;
35461      const playerNames = Object.keys(players);
35462
35463      // If we have players that were disposed, then their name will still be
35464      // in Players.players. So, we must loop through and verify that the value
35465      // for each item is null. This allows registration of the Player component
35466      // after all players have been disposed or before any were created.
35467      if (players && playerNames.length > 0) {
35468        for (let i = 0; i < playerNames.length; i++) {
35469          if (players[playerNames[i]] !== null) {
35470            throw new Error('Can not register Player component after player has been created.');
35471          }
35472        }
35473      }
35474    }
35475    Component$1.components_[name] = ComponentToRegister;
35476    Component$1.components_[toLowerCase(name)] = ComponentToRegister;
35477    return ComponentToRegister;
35478  }
35479
35480  /**
35481   * Get a `Component` based on the name it was registered with.
35482   *
35483   * @param {string} name
35484   *        The Name of the component to get.
35485   *
35486   * @return {typeof Component}
35487   *         The `Component` that got registered under the given name.
35488   */
35489  static getComponent(name) {
35490    if (!name || !Component$1.components_) {
35491      return;
35492    }
35493    return Component$1.components_[name];
35494  }
35495}
35496Component$1.registerComponent('Component', Component$1);
35497
35498/**
35499 * @file time.js
35500 * @module time
35501 */
35502
35503/**
35504 * Returns the time for the specified index at the start or end
35505 * of a TimeRange object.
35506 *
35507 * @typedef    {Function} TimeRangeIndex
35508 *
35509 * @param      {number} [index=0]
35510 *             The range number to return the time for.
35511 *
35512 * @return     {number}
35513 *             The time offset at the specified index.
35514 *
35515 * @deprecated The index argument must be provided.
35516 *             In the future, leaving it out will throw an error.
35517 */
35518
35519/**
35520 * An object that contains ranges of time, which mimics {@link TimeRanges}.
35521 *
35522 * @typedef  {Object} TimeRange
35523 *
35524 * @property {number} length
35525 *           The number of time ranges represented by this object.
35526 *
35527 * @property {module:time~TimeRangeIndex} start
35528 *           Returns the time offset at which a specified time range begins.
35529 *
35530 * @property {module:time~TimeRangeIndex} end
35531 *           Returns the time offset at which a specified time range ends.
35532 *
35533 * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
35534 */
35535
35536/**
35537 * Check if any of the time ranges are over the maximum index.
35538 *
35539 * @private
35540 * @param   {string} fnName
35541 *          The function name to use for logging
35542 *
35543 * @param   {number} index
35544 *          The index to check
35545 *
35546 * @param   {number} maxIndex
35547 *          The maximum possible index
35548 *
35549 * @throws  {Error} if the timeRanges provided are over the maxIndex
35550 */
35551function rangeCheck(fnName, index, maxIndex) {
35552  if (typeof index !== 'number' || index < 0 || index > maxIndex) {
35553    throw new Error(`Failed to execute '${fnName}' on 'TimeRanges': The index provided (${index}) is non-numeric or out of bounds (0-${maxIndex}).`);
35554  }
35555}
35556
35557/**
35558 * Get the time for the specified index at the start or end
35559 * of a TimeRange object.
35560 *
35561 * @private
35562 * @param      {string} fnName
35563 *             The function name to use for logging
35564 *
35565 * @param      {string} valueIndex
35566 *             The property that should be used to get the time. should be
35567 *             'start' or 'end'
35568 *
35569 * @param      {Array} ranges
35570 *             An array of time ranges
35571 *
35572 * @param      {Array} [rangeIndex=0]
35573 *             The index to start the search at
35574 *
35575 * @return     {number}
35576 *             The time that offset at the specified index.
35577 *
35578 * @deprecated rangeIndex must be set to a value, in the future this will throw an error.
35579 * @throws     {Error} if rangeIndex is more than the length of ranges
35580 */
35581function getRange(fnName, valueIndex, ranges, rangeIndex) {
35582  rangeCheck(fnName, rangeIndex, ranges.length - 1);
35583  return ranges[rangeIndex][valueIndex];
35584}
35585
35586/**
35587 * Create a time range object given ranges of time.
35588 *
35589 * @private
35590 * @param   {Array} [ranges]
35591 *          An array of time ranges.
35592 *
35593 * @return  {TimeRange}
35594 */
35595function createTimeRangesObj(ranges) {
35596  let timeRangesObj;
35597  if (ranges === undefined || ranges.length === 0) {
35598    timeRangesObj = {
35599      length: 0,
35600      start() {
35601        throw new Error('This TimeRanges object is empty');
35602      },
35603      end() {
35604        throw new Error('This TimeRanges object is empty');
35605      }
35606    };
35607  } else {
35608    timeRangesObj = {
35609      length: ranges.length,
35610      start: getRange.bind(null, 'start', 0, ranges),
35611      end: getRange.bind(null, 'end', 1, ranges)
35612    };
35613  }
35614  if (window__default["default"].Symbol && window__default["default"].Symbol.iterator) {
35615    timeRangesObj[window__default["default"].Symbol.iterator] = () => (ranges || []).values();
35616  }
35617  return timeRangesObj;
35618}
35619
35620/**
35621 * Create a `TimeRange` object which mimics an
35622 * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}.
35623 *
35624 * @param {number|Array[]} start
35625 *        The start of a single range (a number) or an array of ranges (an
35626 *        array of arrays of two numbers each).
35627 *
35628 * @param {number} end
35629 *        The end of a single range. Cannot be used with the array form of
35630 *        the `start` argument.
35631 *
35632 * @return {TimeRange}
35633 */
35634function createTimeRanges$1(start, end) {
35635  if (Array.isArray(start)) {
35636    return createTimeRangesObj(start);
35637  } else if (start === undefined || end === undefined) {
35638    return createTimeRangesObj();
35639  }
35640  return createTimeRangesObj([[start, end]]);
35641}
35642
35643/**
35644 * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
35645 * seconds) will force a number of leading zeros to cover the length of the
35646 * guide.
35647 *
35648 * @private
35649 * @param  {number} seconds
35650 *         Number of seconds to be turned into a string
35651 *
35652 * @param  {number} guide
35653 *         Number (in seconds) to model the string after
35654 *
35655 * @return {string}
35656 *         Time formatted as H:MM:SS or M:SS
35657 */
35658const defaultImplementation = function (seconds, guide) {
35659  seconds = seconds < 0 ? 0 : seconds;
35660  let s = Math.floor(seconds % 60);
35661  let m = Math.floor(seconds / 60 % 60);
35662  let h = Math.floor(seconds / 3600);
35663  const gm = Math.floor(guide / 60 % 60);
35664  const gh = Math.floor(guide / 3600);
35665
35666  // handle invalid times
35667  if (isNaN(seconds) || seconds === Infinity) {
35668    // '-' is false for all relational operators (e.g. <, >=) so this setting
35669    // will add the minimum number of fields specified by the guide
35670    h = m = s = '-';
35671  }
35672
35673  // Check if we need to show hours
35674  h = h > 0 || gh > 0 ? h + ':' : '';
35675
vendor: 3,896 bytes, lines 35676-35809
35676  // If hours are showing, we may need to add a leading zero.
35677  // Always show at least one digit of minutes.
35678  m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':';
35679
35680  // Check if leading zero is need for seconds
35681  s = s < 10 ? '0' + s : s;
35682  return h + m + s;
35683};
35684
35685// Internal pointer to the current implementation.
35686let implementation = defaultImplementation;
35687
35688/**
35689 * Replaces the default formatTime implementation with a custom implementation.
35690 *
35691 * @param {Function} customImplementation
35692 *        A function which will be used in place of the default formatTime
35693 *        implementation. Will receive the current time in seconds and the
35694 *        guide (in seconds) as arguments.
35695 */
35696function setFormatTime(customImplementation) {
35697  implementation = customImplementation;
35698}
35699
35700/**
35701 * Resets formatTime to the default implementation.
35702 */
35703function resetFormatTime() {
35704  implementation = defaultImplementation;
35705}
35706
35707/**
35708 * Delegates to either the default time formatting function or a custom
35709 * function supplied via `setFormatTime`.
35710 *
35711 * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
35712 * guide (in seconds) will force a number of leading zeros to cover the
35713 * length of the guide.
35714 *
35715 * @example  formatTime(125, 600) === "02:05"
35716 * @param    {number} seconds
35717 *           Number of seconds to be turned into a string
35718 *
35719 * @param    {number} guide
35720 *           Number (in seconds) to model the string after
35721 *
35722 * @return   {string}
35723 *           Time formatted as H:MM:SS or M:SS
35724 */
35725function formatTime(seconds, guide = seconds) {
35726  return implementation(seconds, guide);
35727}
35728
35729var Time = /*#__PURE__*/Object.freeze({
35730  __proto__: null,
35731  createTimeRanges: createTimeRanges$1,
35732  createTimeRange: createTimeRanges$1,
35733  setFormatTime: setFormatTime,
35734  resetFormatTime: resetFormatTime,
35735  formatTime: formatTime
35736});
35737
35738/**
35739 * @file buffer.js
35740 * @module buffer
35741 */
35742
35743/** @import { TimeRange } from './time' */
35744
35745/**
35746 * Compute the percentage of the media that has been buffered.
35747 *
35748 * @param {TimeRange} buffered
35749 *        The current `TimeRanges` object representing buffered time ranges
35750 *
35751 * @param {number} duration
35752 *        Total duration of the media
35753 *
35754 * @return {number}
35755 *         Percent buffered of the total duration in decimal form.
35756 */
35757function bufferedPercent(buffered, duration) {
35758  let bufferedDuration = 0;
35759  let start;
35760  let end;
35761  if (!duration) {
35762    return 0;
35763  }
35764  if (!buffered || !buffered.length) {
35765    buffered = createTimeRanges$1(0, 0);
35766  }
35767  for (let i = 0; i < buffered.length; i++) {
35768    start = buffered.start(i);
35769    end = buffered.end(i);
35770
35771    // buffered end can be bigger than duration by a very small fraction
35772    if (end > duration) {
35773      end = duration;
35774    }
35775    bufferedDuration += end - start;
35776  }
35777  return bufferedDuration / duration;
35778}
35779
35780/**
35781 * @file media-error.js
35782 */
35783
35784/**
35785 * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
35786 *
35787 * @param {number|string|Object|MediaError} value
35788 *        This can be of multiple types:
35789 *        - number: should be a standard error code
35790 *        - string: an error message (the code will be 0)
35791 *        - Object: arbitrary properties
35792 *        - `MediaError` (native): used to populate a video.js `MediaError` object
35793 *        - `MediaError` (video.js): will return itself if it's already a
35794 *          video.js `MediaError` object.
35795 *
35796 * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
35797 * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
35798 *
35799 * @class MediaError
35800 */
35801function MediaError(value) {
35802  // Allow redundant calls to this constructor to avoid having `instanceof`
35803  // checks peppered around the code.
35804  if (value instanceof MediaError) {
35805    return value;
35806  }
35807  if (typeof value === 'number') {
35808    this.code = value;
35809  } else if (typeof value === 'string') {
vendor: 5,786 bytes, lines 35810-36025
35810    // default code is zero, so this is a custom error
35811    this.message = value;
35812  } else if (isObject(value)) {
35813    // We assign the `code` property manually because native `MediaError` objects
35814    // do not expose it as an own/enumerable property of the object.
35815    if (typeof value.code === 'number') {
35816      this.code = value.code;
35817    }
35818    Object.assign(this, value);
35819  }
35820  if (!this.message) {
35821    this.message = MediaError.defaultMessages[this.code] || '';
35822  }
35823}
35824
35825/**
35826 * The error code that refers two one of the defined `MediaError` types
35827 *
35828 * @type {Number}
35829 */
35830MediaError.prototype.code = 0;
35831
35832/**
35833 * An optional message that to show with the error. Message is not part of the HTML5
35834 * video spec but allows for more informative custom errors.
35835 *
35836 * @type {String}
35837 */
35838MediaError.prototype.message = '';
35839
35840/**
35841 * An optional status code that can be set by plugins to allow even more detail about
35842 * the error. For example a plugin might provide a specific HTTP status code and an
35843 * error message for that code. Then when the plugin gets that error this class will
35844 * know how to display an error message for it. This allows a custom message to show
35845 * up on the `Player` error overlay.
35846 *
35847 * @type {Array}
35848 */
35849MediaError.prototype.status = null;
35850
35851/**
35852 * An object containing an error type, as well as other information regarding the error.
35853 *
35854 * @typedef {{errorType: string, [key: string]: any}} ErrorMetadata
35855 */
35856
35857/**
35858 * An optional object to give more detail about the error. This can be used to give
35859 * a higher level of specificity to an error versus the more generic MediaError codes.
35860 * `metadata` expects an `errorType` string that should align with the values from videojs.Error.
35861 *
35862 * @type {ErrorMetadata}
35863 */
35864MediaError.prototype.metadata = null;
35865
35866/**
35867 * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
35868 * specification listed under {@link MediaError} for more information.
35869 *
35870 * @enum {array}
35871 * @readonly
35872 * @property {string} 0 - MEDIA_ERR_CUSTOM
35873 * @property {string} 1 - MEDIA_ERR_ABORTED
35874 * @property {string} 2 - MEDIA_ERR_NETWORK
35875 * @property {string} 3 - MEDIA_ERR_DECODE
35876 * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
35877 * @property {string} 5 - MEDIA_ERR_ENCRYPTED
35878 */
35879MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
35880
35881/**
35882 * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
35883 *
35884 * @type {Array}
35885 * @constant
35886 */
35887MediaError.defaultMessages = {
35888  1: 'You aborted the media playback',
35889  2: 'A network error caused the media download to fail part-way.',
35890  3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
35891  4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
35892  5: 'The media is encrypted and we do not have the keys to decrypt it.'
35893};
35894
35895/**
35896 * W3C error code for any custom error.
35897 *
35898 * @member MediaError#MEDIA_ERR_CUSTOM
35899 * @constant {number}
35900 * @default 0
35901 */
35902MediaError.MEDIA_ERR_CUSTOM = 0;
35903
35904/**
35905 * W3C error code for any custom error.
35906 *
35907 * @member MediaError.MEDIA_ERR_CUSTOM
35908 * @constant {number}
35909 * @default 0
35910 */
35911MediaError.prototype.MEDIA_ERR_CUSTOM = 0;
35912
35913/**
35914 * W3C error code for media error aborted.
35915 *
35916 * @member MediaError#MEDIA_ERR_ABORTED
35917 * @constant {number}
35918 * @default 1
35919 */
35920MediaError.MEDIA_ERR_ABORTED = 1;
35921
35922/**
35923 * W3C error code for media error aborted.
35924 *
35925 * @member MediaError.MEDIA_ERR_ABORTED
35926 * @constant {number}
35927 * @default 1
35928 */
35929MediaError.prototype.MEDIA_ERR_ABORTED = 1;
35930
35931/**
35932 * W3C error code for any network error.
35933 *
35934 * @member MediaError#MEDIA_ERR_NETWORK
35935 * @constant {number}
35936 * @default 2
35937 */
35938MediaError.MEDIA_ERR_NETWORK = 2;
35939
35940/**
35941 * W3C error code for any network error.
35942 *
35943 * @member MediaError.MEDIA_ERR_NETWORK
35944 * @constant {number}
35945 * @default 2
35946 */
35947MediaError.prototype.MEDIA_ERR_NETWORK = 2;
35948
35949/**
35950 * W3C error code for any decoding error.
35951 *
35952 * @member MediaError#MEDIA_ERR_DECODE
35953 * @constant {number}
35954 * @default 3
35955 */
35956MediaError.MEDIA_ERR_DECODE = 3;
35957
35958/**
35959 * W3C error code for any decoding error.
35960 *
35961 * @member MediaError.MEDIA_ERR_DECODE
35962 * @constant {number}
35963 * @default 3
35964 */
35965MediaError.prototype.MEDIA_ERR_DECODE = 3;
35966
35967/**
35968 * W3C error code for any time that a source is not supported.
35969 *
35970 * @member MediaError#MEDIA_ERR_SRC_NOT_SUPPORTED
35971 * @constant {number}
35972 * @default 4
35973 */
35974MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
35975
35976/**
35977 * W3C error code for any time that a source is not supported.
35978 *
35979 * @member MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED
35980 * @constant {number}
35981 * @default 4
35982 */
35983MediaError.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
35984
35985/**
35986 * W3C error code for any time that a source is encrypted.
35987 *
35988 * @member MediaError#MEDIA_ERR_ENCRYPTED
35989 * @constant {number}
35990 * @default 5
35991 */
35992MediaError.MEDIA_ERR_ENCRYPTED = 5;
35993
35994/**
35995 * W3C error code for any time that a source is encrypted.
35996 *
35997 * @member MediaError.MEDIA_ERR_ENCRYPTED
35998 * @constant {number}
35999 * @default 5
36000 */
36001MediaError.prototype.MEDIA_ERR_ENCRYPTED = 5;
36002
36003/**
36004 * Returns whether an object is `Promise`-like (i.e. has a `then` method).
36005 *
36006 * @param  {Object}  value
36007 *         An object that may or may not be `Promise`-like.
36008 *
36009 * @return {boolean}
36010 *         Whether or not the object is `Promise`-like.
36011 */
36012function isPromise(value) {
36013  return value !== undefined && value !== null && typeof value.then === 'function';
36014}
36015
36016/**
36017 * Silence a Promise-like object.
36018 *
36019 * This is useful for avoiding non-harmful, but potentially confusing "uncaught
36020 * play promise" rejection error messages.
36021 *
36022 * @param  {Object} value
36023 *         An object that may or may not be `Promise`-like.
36024 */
36025function silencePromise(value) {
vendor: 4,112 bytes, lines 36026-36165
36026  if (isPromise(value)) {
36027    value.then(null, e => {});
36028  }
36029}
36030
36031/**
36032 * @file text-track-list-converter.js Utilities for capturing text track state and
36033 * re-creating tracks based on a capture.
36034 *
36035 * @module text-track-list-converter
36036 */
36037
36038/** @import Tech from '../tech/tech' */
36039
36040/**
36041 * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
36042 * represents the {@link TextTrack}'s state.
36043 *
36044 * @param {TextTrack} track
36045 *        The text track to query.
36046 *
36047 * @return {Object}
36048 *         A serializable javascript representation of the TextTrack.
36049 * @private
36050 */
36051const trackToJson = function (track) {
36052  const ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce((acc, prop, i) => {
36053    if (track[prop]) {
36054      acc[prop] = track[prop];
36055    }
36056    return acc;
36057  }, {
36058    cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
36059      return {
36060        startTime: cue.startTime,
36061        endTime: cue.endTime,
36062        text: cue.text,
36063        id: cue.id
36064      };
36065    })
36066  });
36067  return ret;
36068};
36069
36070/**
36071 * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
36072 * state of all {@link TextTrack}s currently configured. The return array is compatible with
36073 * {@link text-track-list-converter:jsonToTextTracks}.
36074 *
36075 * @param {Tech} tech
36076 *        The tech object to query
36077 *
36078 * @return {Array}
36079 *         A serializable javascript representation of the {@link Tech}s
36080 *         {@link TextTrackList}.
36081 */
36082const textTracksToJson = function (tech) {
36083  const trackEls = tech.$$('track');
36084  const trackObjs = Array.prototype.map.call(trackEls, t => t.track);
36085  const tracks = Array.prototype.map.call(trackEls, function (trackEl) {
36086    const json = trackToJson(trackEl.track);
36087    if (trackEl.src) {
36088      json.src = trackEl.src;
36089    }
36090    return json;
36091  });
36092  return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
36093    return trackObjs.indexOf(track) === -1;
36094  }).map(trackToJson));
36095};
36096
36097/**
36098 * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
36099 * object {@link TextTrack} representations.
36100 *
36101 * @param {Array} json
36102 *        An array of `TextTrack` representation objects, like those that would be
36103 *        produced by `textTracksToJson`.
36104 *
36105 * @param {Tech} tech
36106 *        The `Tech` to create the `TextTrack`s on.
36107 */
36108const jsonToTextTracks = function (json, tech) {
36109  json.forEach(function (track) {
36110    const addedTrack = tech.addRemoteTextTrack(track).track;
36111    if (!track.src && track.cues) {
36112      track.cues.forEach(cue => addedTrack.addCue(cue));
36113    }
36114  });
36115  return tech.textTracks();
36116};
36117var textTrackConverter = {
36118  textTracksToJson,
36119  jsonToTextTracks,
36120  trackToJson
36121};
36122
36123/**
36124 * @file modal-dialog.js
36125 */
36126
36127/** @import Player from './player' */
36128/** @import { ContentDescriptor } from './utils/dom' */
36129
36130const MODAL_CLASS_NAME = 'vjs-modal-dialog';
36131
36132/**
36133 * The `ModalDialog` displays over the video and its controls, which blocks
36134 * interaction with the player until it is closed.
36135 *
36136 * Modal dialogs include a "Close" button and will close when that button
36137 * is activated - or when ESC is pressed anywhere.
36138 *
36139 * @extends Component
36140 */
36141class ModalDialog extends Component$1 {
36142  /**
36143   * Creates an instance of this class.
36144   *
36145   * @param {Player} player
36146   *        The `Player` that this class should be attached to.
36147   *
36148   * @param {Object} [options]
36149   *        The key/value store of player options.
36150   *
36151   * @param {ContentDescriptor} [options.content=undefined]
36152   *        Provide customized content for this modal.
36153   *
36154   * @param {string} [options.description]
36155   *        A text description for the modal, primarily for accessibility.
36156   *
36157   * @param {boolean} [options.fillAlways=false]
36158   *        Normally, modals are automatically filled only the first time
36159   *        they open. This tells the modal to refresh its content
36160   *        every time it opens.
36161   *
36162   * @param {string} [options.label]
36163   *        A text label for the modal, primarily for accessibility.
36164   *
36165   * @param {boolean}
vendor: 7,144 bytes, lines 36165-36422
36165 [options.pauseOnOpen=true]
36166   *        If `true`, playback will will be paused if playing when
36167   *        the modal opens, and resumed when it closes.
36168   *
36169   * @param {boolean} [options.temporary=true]
36170   *        If `true`, the modal can only be opened once; it will be
36171   *        disposed as soon as it's closed.
36172   *
36173   * @param {boolean} [options.uncloseable=false]
36174   *        If `true`, the user will not be able to close the modal
36175   *        through the UI in the normal ways. Programmatic closing is
36176   *        still possible.
36177   */
36178  constructor(player, options) {
36179    super(player, options);
36180    this.handleKeyDown_ = e => this.handleKeyDown(e);
36181    this.close_ = e => this.close(e);
36182    this.opened_ = this.hasBeenOpened_ = this.hasBeenFilled_ = false;
36183    this.closeable(!this.options_.uncloseable);
36184    this.content(this.options_.content);
36185
36186    // Make sure the contentEl is defined AFTER any children are initialized
36187    // because we only want the contents of the modal in the contentEl
36188    // (not the UI elements like the close button).
36189    this.contentEl_ = createEl('div', {
36190      className: `${MODAL_CLASS_NAME}-content`
36191    }, {
36192      role: 'document'
36193    });
36194    this.descEl_ = createEl('p', {
36195      className: `${MODAL_CLASS_NAME}-description vjs-control-text`,
36196      id: this.el().getAttribute('aria-describedby')
36197    });
36198    textContent(this.descEl_, this.description());
36199    this.el_.appendChild(this.descEl_);
36200    this.el_.appendChild(this.contentEl_);
36201  }
36202
36203  /**
36204   * Create the `ModalDialog`'s DOM element
36205   *
36206   * @return {Element}
36207   *         The DOM element that gets created.
36208   */
36209  createEl() {
36210    return super.createEl('div', {
36211      className: this.buildCSSClass(),
36212      tabIndex: -1
36213    }, {
36214      'aria-describedby': `${this.id()}_description`,
36215      'aria-hidden': 'true',
36216      'aria-label': this.label(),
36217      'role': 'dialog',
36218      'aria-live': 'polite'
36219    });
36220  }
36221  dispose() {
36222    this.contentEl_ = null;
36223    this.descEl_ = null;
36224    this.previouslyActiveEl_ = null;
36225    super.dispose();
36226  }
36227
36228  /**
36229   * Builds the default DOM `className`.
36230   *
36231   * @return {string}
36232   *         The DOM `className` for this object.
36233   */
36234  buildCSSClass() {
36235    return `${MODAL_CLASS_NAME} vjs-hidden ${super.buildCSSClass()}`;
36236  }
36237
36238  /**
36239   * Returns the label string for this modal. Primarily used for accessibility.
36240   *
36241   * @return {string}
36242   *         the localized or raw label of this modal.
36243   */
36244  label() {
36245    return this.localize(this.options_.label || 'Modal Window');
36246  }
36247
36248  /**
36249   * Returns the description string for this modal. Primarily used for
36250   * accessibility.
36251   *
36252   * @return {string}
36253   *         The localized or raw description of this modal.
36254   */
36255  description() {
36256    let desc = this.options_.description || this.localize('This is a modal window.');
36257
36258    // Append a universal closeability message if the modal is closeable.
36259    if (this.closeable()) {
36260      desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
36261    }
36262    return desc;
36263  }
36264
36265  /**
36266   * Opens the modal.
36267   *
36268   * @fires ModalDialog#beforemodalopen
36269   * @fires ModalDialog#modalopen
36270   */
36271  open() {
36272    if (this.opened_) {
36273      if (this.options_.fillAlways) {
36274        this.fill();
36275      }
36276      return;
36277    }
36278    const player = this.player();
36279
36280    /**
36281      * Fired just before a `ModalDialog` is opened.
36282      *
36283      * @event ModalDialog#beforemodalopen
36284      * @type {Event}
36285      */
36286    this.trigger('beforemodalopen');
36287    this.opened_ = true;
36288
36289    // Fill content if the modal has never opened before and
36290    // never been filled.
36291    if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
36292      this.fill();
36293    }
36294
36295    // If the player was playing, pause it and take note of its previously
36296    // playing state.
36297    this.wasPlaying_ = !player.paused();
36298    if (this.options_.pauseOnOpen && this.wasPlaying_) {
36299      player.pause();
36300    }
36301    this.on('keydown', this.handleKeyDown_);
36302
36303    // Hide controls and note if they were enabled.
36304    this.hadControls_ = player.controls();
36305    player.controls(false);
36306    this.show();
36307    this.conditionalFocus_();
36308    this.el().setAttribute('aria-hidden', 'false');
36309
36310    /**
36311      * Fired just after a `ModalDialog` is opened.
36312      *
36313      * @event ModalDialog#modalopen
36314      * @type {Event}
36315      */
36316    this.trigger('modalopen');
36317    this.hasBeenOpened_ = true;
36318  }
36319
36320  /**
36321   * If the `ModalDialog` is currently open or closed.
36322   *
36323   * @param  {boolean} [value]
36324   *         If given, it will open (`true`) or close (`false`) the modal.
36325   *
36326   * @return {boolean}
36327   *         the current open state of the modaldialog
36328   */
36329  opened(value) {
36330    if (typeof value === 'boolean') {
36331      this[value ? 'open' : 'close']();
36332    }
36333    return this.opened_;
36334  }
36335
36336  /**
36337   * Closes the modal, does nothing if the `ModalDialog` is
36338   * not open.
36339   *
36340   * @fires ModalDialog#beforemodalclose
36341   * @fires ModalDialog#modalclose
36342   */
36343  close() {
36344    if (!this.opened_) {
36345      return;
36346    }
36347    const player = this.player();
36348
36349    /**
36350      * Fired just before a `ModalDialog` is closed.
36351      *
36352      * @event ModalDialog#beforemodalclose
36353      * @type {Event}
36354      */
36355    this.trigger('beforemodalclose');
36356    this.opened_ = false;
36357    if (this.wasPlaying_ && this.options_.pauseOnOpen) {
36358      player.play();
36359    }
36360    this.off('keydown', this.handleKeyDown_);
36361    if (this.hadControls_) {
36362      player.controls(true);
36363    }
36364    this.hide();
36365    this.el().setAttribute('aria-hidden', 'true');
36366
36367    /**
36368      * Fired just after a `ModalDialog` is closed.
36369      *
36370      * @event ModalDialog#modalclose
36371      * @type {Event}
36372      *
36373      * @property {boolean} [bubbles=true]
36374      */
36375    this.trigger({
36376      type: 'modalclose',
36377      bubbles: true
36378    });
36379    this.conditionalBlur_();
36380    if (this.options_.temporary) {
36381      this.dispose();
36382    }
36383  }
36384
36385  /**
36386   * Check to see if the `ModalDialog` is closeable via the UI.
36387   *
36388   * @param  {boolean} [value]
36389   *         If given as a boolean, it will set the `closeable` option.
36390   *
36391   * @return {boolean}
36392   *         Returns the final value of the closable option.
36393   */
36394  closeable(value) {
36395    if (typeof value === 'boolean') {
36396      const closeable = this.closeable_ = !!value;
36397      let close = this.getChild('closeButton');
36398
36399      // If this is being made closeable and has no close button, add one.
36400      if (closeable && !close) {
36401        // The close button should be a child of the modal - not its
36402        // content element, so temporarily change the content element.
36403        const temp = this.contentEl_;
36404        this.contentEl_ = this.el_;
36405        close = this.addChild('closeButton', {
36406          controlText: 'Close Modal Dialog'
36407        });
36408        this.contentEl_ = temp;
36409        this.on(close, 'close', this.close_);
36410      }
36411
36412      // If this is being made uncloseable and has a close button, remove it.
36413      if (!closeable && close) {
36414        this.off(close, 'close', this.close_);
36415        this.removeChild(close);
36416        close.dispose();
36417      }
36418    }
36419    return this.closeable_;
36420  }
36421
36422  /**
vendor: 4,222 bytes, lines 36423-36580
36423   * Fill the modal's content element with the modal's "content" option.
36424   * The content element will be emptied before this change takes place.
36425   */
36426  fill() {
36427    this.fillWith(this.content());
36428  }
36429
36430  /**
36431   * Fill the modal's content element with arbitrary content.
36432   * The content element will be emptied before this change takes place.
36433   *
36434   * @fires ModalDialog#beforemodalfill
36435   * @fires ModalDialog#modalfill
36436   *
36437   * @param {ContentDescriptor} [content]
36438   *        The same rules apply to this as apply to the `content` option.
36439   */
36440  fillWith(content) {
36441    const contentEl = this.contentEl();
36442    const parentEl = contentEl.parentNode;
36443    const nextSiblingEl = contentEl.nextSibling;
36444
36445    /**
36446      * Fired just before a `ModalDialog` is filled with content.
36447      *
36448      * @event ModalDialog#beforemodalfill
36449      * @type {Event}
36450      */
36451    this.trigger('beforemodalfill');
36452    this.hasBeenFilled_ = true;
36453
36454    // Detach the content element from the DOM before performing
36455    // manipulation to avoid modifying the live DOM multiple times.
36456    parentEl.removeChild(contentEl);
36457    this.empty();
36458    insertContent(contentEl, content);
36459    /**
36460     * Fired just after a `ModalDialog` is filled with content.
36461     *
36462     * @event ModalDialog#modalfill
36463     * @type {Event}
36464     */
36465    this.trigger('modalfill');
36466
36467    // Re-inject the re-filled content element.
36468    if (nextSiblingEl) {
36469      parentEl.insertBefore(contentEl, nextSiblingEl);
36470    } else {
36471      parentEl.appendChild(contentEl);
36472    }
36473
36474    // make sure that the close button is last in the dialog DOM
36475    const closeButton = this.getChild('closeButton');
36476    if (closeButton) {
36477      parentEl.appendChild(closeButton.el_);
36478    }
36479
36480    /**
36481     * Fired after `ModalDialog` is re-filled with content & close button is appended.
36482     *
36483     * @event ModalDialog#aftermodalfill
36484     * @type {Event}
36485     */
36486    this.trigger('aftermodalfill');
36487  }
36488
36489  /**
36490   * Empties the content element. This happens anytime the modal is filled.
36491   *
36492   * @fires ModalDialog#beforemodalempty
36493   * @fires ModalDialog#modalempty
36494   */
36495  empty() {
36496    /**
36497    * Fired just before a `ModalDialog` is emptied.
36498    *
36499    * @event ModalDialog#beforemodalempty
36500    * @type {Event}
36501    */
36502    this.trigger('beforemodalempty');
36503    emptyEl(this.contentEl());
36504
36505    /**
36506    * Fired just after a `ModalDialog` is emptied.
36507    *
36508    * @event ModalDialog#modalempty
36509    * @type {Event}
36510    */
36511    this.trigger('modalempty');
36512  }
36513
36514  /**
36515   * Gets or sets the modal content, which gets normalized before being
36516   * rendered into the DOM.
36517   *
36518   * This does not update the DOM or fill the modal, but it is called during
36519   * that process.
36520   *
36521   * @param  {ContentDescriptor} [value]
36522   *         If defined, sets the internal content value to be used on the
36523   *         next call(s) to `fill`. This value is normalized before being
36524   *         inserted. To "clear" the internal content value, pass `null`.
36525   *
36526   * @return {ContentDescriptor}
36527   *         The current content of the modal dialog
36528   */
36529  content(value) {
36530    if (typeof value !== 'undefined') {
36531      this.content_ = value;
36532    }
36533    return this.content_;
36534  }
36535
36536  /**
36537   * conditionally focus the modal dialog if focus was previously on the player.
36538   *
36539   * @private
36540   */
36541  conditionalFocus_() {
36542    const activeEl = document__default["default"].activeElement;
36543    const playerEl = this.player_.el_;
36544    this.previouslyActiveEl_ = null;
36545    if (playerEl.contains(activeEl) || playerEl === activeEl) {
36546      this.previouslyActiveEl_ = activeEl;
36547      this.focus();
36548    }
36549  }
36550
36551  /**
36552   * conditionally blur the element and refocus the last focused element
36553   *
36554   * @private
36555   */
36556  conditionalBlur_() {
36557    if (this.previouslyActiveEl_) {
36558      this.previouslyActiveEl_.focus();
36559      this.previouslyActiveEl_ = null;
36560    }
36561  }
36562
36563  /**
36564   * Keydown handler. Attached when modal is focused.
36565   *
36566   * @listens keydown
36567   */
36568  handleKeyDown(event) {
36569    /**
36570     * Fired a custom keyDown event that bubbles.
36571     *
36572     * @event ModalDialog#modalKeydown
36573     * @type {Event}
36574     */
36575    this.trigger({
36576      type: 'modalKeydown',
36577      originalEvent: event,
36578      target: this,
36579      bubbles: true
36580    });
vendor: 6,632 bytes, lines 36581-36842
36581    // Do not allow keydowns to reach out of the modal dialog.
36582    event.stopPropagation();
36583    if (event.key === 'Escape' && this.closeable()) {
36584      event.preventDefault();
36585      this.close();
36586      return;
36587    }
36588
36589    // exit early if it isn't a tab key
36590    if (event.key !== 'Tab') {
36591      return;
36592    }
36593    const focusableEls = this.focusableEls_();
36594    const activeEl = this.el_.querySelector(':focus');
36595    let focusIndex;
36596    for (let i = 0; i < focusableEls.length; i++) {
36597      if (activeEl === focusableEls[i]) {
36598        focusIndex = i;
36599        break;
36600      }
36601    }
36602    if (document__default["default"].activeElement === this.el_) {
36603      focusIndex = 0;
36604    }
36605    if (event.shiftKey && focusIndex === 0) {
36606      focusableEls[focusableEls.length - 1].focus();
36607      event.preventDefault();
36608    } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
36609      focusableEls[0].focus();
36610      event.preventDefault();
36611    }
36612  }
36613
36614  /**
36615   * get all focusable elements
36616   *
36617   * @private
36618   */
36619  focusableEls_() {
36620    const allChildren = this.el_.querySelectorAll('*');
36621    return Array.prototype.filter.call(allChildren, child => {
36622      return (child instanceof window__default["default"].HTMLAnchorElement || child instanceof window__default["default"].HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window__default["default"].HTMLInputElement || child instanceof window__default["default"].HTMLSelectElement || child instanceof window__default["default"].HTMLTextAreaElement || child instanceof window__default["default"].HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window__default["default"].HTMLIFrameElement || child instanceof window__default["default"].HTMLObjectElement || child instanceof window__default["default"].HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable');
36623    });
36624  }
36625}
36626
36627/**
36628 * Default options for `ModalDialog` default options.
36629 *
36630 * @type {Object}
36631 * @private
36632 */
36633ModalDialog.prototype.options_ = {
36634  pauseOnOpen: true,
36635  temporary: true
36636};
36637Component$1.registerComponent('ModalDialog', ModalDialog);
36638
36639/**
36640 * @file track-list.js
36641 */
36642
36643/** @import Track from './track' */
36644
36645/**
36646 * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
36647 * {@link VideoTrackList}
36648 *
36649 * @extends EventTarget
36650 */
36651class TrackList extends EventTarget$2 {
36652  /**
36653   * Create an instance of this class
36654   *
36655   * @param { Track[] } tracks
36656   *        A list of tracks to initialize the list with.
36657   *
36658   * @abstract
36659   */
36660  constructor(tracks = []) {
36661    super();
36662    this.tracks_ = [];
36663    for (let i = 0; i < tracks.length; i++) {
36664      this.addTrack(tracks[i]);
36665    }
36666  }
36667
36668  /**
36669   * The current number of `Track`s in this TrackList.
36670   *
36671   * @type {number}
36672   */
36673  get length() {
36674    return this.tracks_.length;
36675  }
36676
36677  /**
36678   * Add a {@link Track} to the `TrackList`
36679   *
36680   * @param {Track} track
36681   *        The audio, video, or text track to add to the list.
36682   *
36683   * @fires TrackList#addtrack
36684   */
36685  addTrack(track) {
36686    const index = this.tracks_.length;
36687    if (!('' + index in this)) {
36688      Object.defineProperty(this, index, {
36689        get() {
36690          return this.tracks_[index];
36691        }
36692      });
36693    }
36694
36695    // Do not add duplicate tracks
36696    if (this.tracks_.indexOf(track) === -1) {
36697      this.tracks_.push(track);
36698      /**
36699       * Triggered when a track is added to a track list.
36700       *
36701       * @event TrackList#addtrack
36702       * @type {Event}
36703       * @property {Track} track
36704       *           A reference to track that was added.
36705       */
36706      this.trigger({
36707        track,
36708        type: 'addtrack',
36709        target: this
36710      });
36711    }
36712
36713    /**
36714     * Triggered when a track label is changed.
36715     *
36716     * @event TrackList#labelchange
36717     * @type {Event}
36718     * @property {Track} track
36719     *           A reference to track whose label was changed.
36720     */
36721    track.labelchange_ = () => {
36722      this.trigger({
36723        track,
36724        type: 'labelchange',
36725        target: this
36726      });
36727    };
36728    if (isEvented(track)) {
36729      track.addEventListener('labelchange', track.labelchange_);
36730    }
36731  }
36732
36733  /**
36734   * Remove a {@link Track} from the `TrackList`
36735   *
36736   * @param {Track} rtrack
36737   *        The audio, video, or text track to remove from the list.
36738   *
36739   * @fires TrackList#removetrack
36740   */
36741  removeTrack(rtrack) {
36742    let track;
36743    for (let i = 0, l = this.length; i < l; i++) {
36744      if (this[i] === rtrack) {
36745        track = this[i];
36746        if (track.off) {
36747          track.off();
36748        }
36749        this.tracks_.splice(i, 1);
36750        break;
36751      }
36752    }
36753    if (!track) {
36754      return;
36755    }
36756
36757    /**
36758     * Triggered when a track is removed from track list.
36759     *
36760     * @event TrackList#removetrack
36761     * @type {Event}
36762     * @property {Track} track
36763     *           A reference to track that was removed.
36764     */
36765    this.trigger({
36766      track,
36767      type: 'removetrack',
36768      target: this
36769    });
36770  }
36771
36772  /**
36773   * Get a Track from the TrackList by a tracks id
36774   *
36775   * @param {string} id - the id of the track to get
36776   * @method getTrackById
36777   * @return {Track}
36778   * @private
36779   */
36780  getTrackById(id) {
36781    let result = null;
36782    for (let i = 0, l = this.length; i < l; i++) {
36783      const track = this[i];
36784      if (track.id === id) {
36785        result = track;
36786        break;
36787      }
36788    }
36789    return result;
36790  }
36791}
36792
36793/**
36794 * Triggered when a different track is selected/enabled.
36795 *
36796 * @event TrackList#change
36797 * @type {Event}
36798 */
36799
36800/**
36801 * Events that can be called with on + eventName. See {@link EventHandler}.
36802 *
36803 * @property {Object} TrackList#allowedEvents_
36804 * @protected
36805 */
36806TrackList.prototype.allowedEvents_ = {
36807  change: 'change',
36808  addtrack: 'addtrack',
36809  removetrack: 'removetrack',
36810  labelchange: 'labelchange'
36811};
36812
36813// emulate attribute EventHandler support to allow for feature detection
36814for (const event in TrackList.prototype.allowedEvents_) {
36815  TrackList.prototype['on' + event] = null;
36816}
36817
36818/**
36819 * @file audio-track-list.js
36820 */
36821
36822/** @import AudioTrack from './audio-track' */
36823
36824/**
36825 * Anywhere we call this function we diverge from the spec
36826 * as we only support one enabled audiotrack at a time
36827 *
36828 * @param {AudioTrackList} list
36829 *        list to work on
36830 *
36831 * @param {AudioTrack} track
36832 *        The track to skip
36833 *
36834 * @private
36835 */
36836const disableOthers$1 = function (list, track) {
36837  for (let i = 0; i < list.length; i++) {
36838    if (!Object.keys(list[i]).length || track.id === list[i].id) {
36839      continue;
36840    }
36841    // another audio track is enabled, disable it
36842    list[i].enabled = false;
vendor: 13,175 bytes, lines 36843-37376
36843  }
36844};
36845
36846/**
36847 * The current list of {@link AudioTrack} for a media file.
36848 *
36849 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
36850 * @extends TrackList
36851 */
36852class AudioTrackList extends TrackList {
36853  /**
36854   * Create an instance of this class.
36855   *
36856   * @param {AudioTrack[]} [tracks=[]]
36857   *        A list of `AudioTrack` to instantiate the list with.
36858   */
36859  constructor(tracks = []) {
36860    // make sure only 1 track is enabled
36861    // sorted from last index to first index
36862    for (let i = tracks.length - 1; i >= 0; i--) {
36863      if (tracks[i].enabled) {
36864        disableOthers$1(tracks, tracks[i]);
36865        break;
36866      }
36867    }
36868    super(tracks);
36869    this.changing_ = false;
36870  }
36871
36872  /**
36873   * Add an {@link AudioTrack} to the `AudioTrackList`.
36874   *
36875   * @param {AudioTrack} track
36876   *        The AudioTrack to add to the list
36877   *
36878   * @fires TrackList#addtrack
36879   */
36880  addTrack(track) {
36881    if (track.enabled) {
36882      disableOthers$1(this, track);
36883    }
36884    super.addTrack(track);
36885    // native tracks don't have this
36886    if (!track.addEventListener) {
36887      return;
36888    }
36889    track.enabledChange_ = () => {
36890      // when we are disabling other tracks (since we don't support
36891      // more than one track at a time) we will set changing_
36892      // to true so that we don't trigger additional change events
36893      if (this.changing_) {
36894        return;
36895      }
36896      this.changing_ = true;
36897      disableOthers$1(this, track);
36898      this.changing_ = false;
36899      this.trigger('change');
36900    };
36901
36902    /**
36903     * @listens AudioTrack#enabledchange
36904     * @fires TrackList#change
36905     */
36906    track.addEventListener('enabledchange', track.enabledChange_);
36907  }
36908  removeTrack(rtrack) {
36909    super.removeTrack(rtrack);
36910    if (rtrack.removeEventListener && rtrack.enabledChange_) {
36911      rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
36912      rtrack.enabledChange_ = null;
36913    }
36914  }
36915}
36916
36917/**
36918 * @file video-track-list.js
36919 */
36920
36921/** @import VideoTrack from './video-track' */
36922
36923/**
36924 * Un-select all other {@link VideoTrack}s that are selected.
36925 *
36926 * @param {VideoTrackList} list
36927 *        list to work on
36928 *
36929 * @param {VideoTrack} track
36930 *        The track to skip
36931 *
36932 * @private
36933 */
36934const disableOthers = function (list, track) {
36935  for (let i = 0; i < list.length; i++) {
36936    if (!Object.keys(list[i]).length || track.id === list[i].id) {
36937      continue;
36938    }
36939    // another video track is enabled, disable it
36940    list[i].selected = false;
36941  }
36942};
36943
36944/**
36945 * The current list of {@link VideoTrack} for a video.
36946 *
36947 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
36948 * @extends TrackList
36949 */
36950class VideoTrackList extends TrackList {
36951  /**
36952   * Create an instance of this class.
36953   *
36954   * @param {VideoTrack[]} [tracks=[]]
36955   *        A list of `VideoTrack` to instantiate the list with.
36956   */
36957  constructor(tracks = []) {
36958    // make sure only 1 track is enabled
36959    // sorted from last index to first index
36960    for (let i = tracks.length - 1; i >= 0; i--) {
36961      if (tracks[i].selected) {
36962        disableOthers(tracks, tracks[i]);
36963        break;
36964      }
36965    }
36966    super(tracks);
36967    this.changing_ = false;
36968
36969    /**
36970     * @member {number} VideoTrackList#selectedIndex
36971     *         The current index of the selected {@link VideoTrack`}.
36972     */
36973    Object.defineProperty(this, 'selectedIndex', {
36974      get() {
36975        for (let i = 0; i < this.length; i++) {
36976          if (this[i].selected) {
36977            return i;
36978          }
36979        }
36980        return -1;
36981      },
36982      set() {}
36983    });
36984  }
36985
36986  /**
36987   * Add a {@link VideoTrack} to the `VideoTrackList`.
36988   *
36989   * @param {VideoTrack} track
36990   *        The VideoTrack to add to the list
36991   *
36992   * @fires TrackList#addtrack
36993   */
36994  addTrack(track) {
36995    if (track.selected) {
36996      disableOthers(this, track);
36997    }
36998    super.addTrack(track);
36999    // native tracks don't have this
37000    if (!track.addEventListener) {
37001      return;
37002    }
37003    track.selectedChange_ = () => {
37004      if (this.changing_) {
37005        return;
37006      }
37007      this.changing_ = true;
37008      disableOthers(this, track);
37009      this.changing_ = false;
37010      this.trigger('change');
37011    };
37012
37013    /**
37014     * @listens VideoTrack#selectedchange
37015     * @fires TrackList#change
37016     */
37017    track.addEventListener('selectedchange', track.selectedChange_);
37018  }
37019  removeTrack(rtrack) {
37020    super.removeTrack(rtrack);
37021    if (rtrack.removeEventListener && rtrack.selectedChange_) {
37022      rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
37023      rtrack.selectedChange_ = null;
37024    }
37025  }
37026}
37027
37028/**
37029 * @file text-track-list.js
37030 */
37031
37032/** @import TextTrack from './text-track' */
37033
37034/**
37035 * The current list of {@link TextTrack} for a media file.
37036 *
37037 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
37038 * @extends TrackList
37039 */
37040class TextTrackList extends TrackList {
37041  /**
37042   * Add a {@link TextTrack} to the `TextTrackList`
37043   *
37044   * @param {TextTrack} track
37045   *        The text track to add to the list.
37046   *
37047   * @fires TrackList#addtrack
37048   */
37049  addTrack(track) {
37050    super.addTrack(track);
37051    if (!this.queueChange_) {
37052      this.queueChange_ = () => this.queueTrigger('change');
37053    }
37054    if (!this.triggerSelectedlanguagechange) {
37055      this.triggerSelectedlanguagechange_ = () => this.trigger('selectedlanguagechange');
37056    }
37057
37058    /**
37059     * @listens TextTrack#modechange
37060     * @fires TrackList#change
37061     */
37062    track.addEventListener('modechange', this.queueChange_);
37063    const nonLanguageTextTrackKind = ['metadata', 'chapters'];
37064    if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
37065      track.addEventListener('modechange', this.triggerSelectedlanguagechange_);
37066    }
37067  }
37068  removeTrack(rtrack) {
37069    super.removeTrack(rtrack);
37070
37071    // manually remove the event handlers we added
37072    if (rtrack.removeEventListener) {
37073      if (this.queueChange_) {
37074        rtrack.removeEventListener('modechange', this.queueChange_);
37075      }
37076      if (this.selectedlanguagechange_) {
37077        rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_);
37078      }
37079    }
37080  }
37081
37082  /**
37083   * Creates a serializable array of objects that contains serialized copies
37084   * of each text track.
37085   *
37086   * @return {Object[]} A serializable list of objects for the text track list
37087   */
37088  toJSON() {
37089    return this.tracks_.map(track => track.toJSON());
37090  }
37091}
37092
37093/**
37094 * @file html-track-element-list.js
37095 */
37096
37097/**
37098 * The current list of {@link HtmlTrackElement}s.
37099 */
37100class HtmlTrackElementList {
37101  /**
37102   * Create an instance of this class.
37103   *
37104   * @param {HtmlTrackElement[]} [tracks=[]]
37105   *        A list of `HtmlTrackElement` to instantiate the list with.
37106   */
37107  constructor(trackElements = []) {
37108    this.trackElements_ = [];
37109
37110    /**
37111     * @memberof HtmlTrackElementList
37112     * @member {number} length
37113     *         The current number of `Track`s in the this Trackist.
37114     * @instance
37115     */
37116    Object.defineProperty(this, 'length', {
37117      get() {
37118        return this.trackElements_.length;
37119      }
37120    });
37121    for (let i = 0, length = trackElements.length; i < length; i++) {
37122      this.addTrackElement_(trackElements[i]);
37123    }
37124  }
37125
37126  /**
37127   * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
37128   *
37129   * @param {HtmlTrackElement} trackElement
37130   *        The track element to add to the list.
37131   *
37132   * @private
37133   */
37134  addTrackElement_(trackElement) {
37135    const index = this.trackElements_.length;
37136    if (!('' + index in this)) {
37137      Object.defineProperty(this, index, {
37138        get() {
37139          return this.trackElements_[index];
37140        }
37141      });
37142    }
37143
37144    // Do not add duplicate elements
37145    if (this.trackElements_.indexOf(trackElement) === -1) {
37146      this.trackElements_.push(trackElement);
37147    }
37148  }
37149
37150  /**
37151   * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
37152   * {@link TextTrack}.
37153   *
37154   * @param {TextTrack} track
37155   *        The track associated with a track element.
37156   *
37157   * @return {HtmlTrackElement|undefined}
37158   *         The track element that was found or undefined.
37159   *
37160   * @private
37161   */
37162  getTrackElementByTrack_(track) {
37163    let trackElement_;
37164    for (let i = 0, length = this.trackElements_.length; i < length; i++) {
37165      if (track === this.trackElements_[i].track) {
37166        trackElement_ = this.trackElements_[i];
37167        break;
37168      }
37169    }
37170    return trackElement_;
37171  }
37172
37173  /**
37174   * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
37175   *
37176   * @param {HtmlTrackElement} trackElement
37177   *        The track element to remove from the list.
37178   *
37179   * @private
37180   */
37181  removeTrackElement_(trackElement) {
37182    for (let i = 0, length = this.trackElements_.length; i < length; i++) {
37183      if (trackElement === this.trackElements_[i]) {
37184        if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') {
37185          this.trackElements_[i].track.off();
37186        }
37187        if (typeof this.trackElements_[i].off === 'function') {
37188          this.trackElements_[i].off();
37189        }
37190        this.trackElements_.splice(i, 1);
37191        break;
37192      }
37193    }
37194  }
37195}
37196
37197/**
37198 * @file text-track-cue-list.js
37199 */
37200
37201/**
37202 * @typedef {Object} TextTrackCueList~TextTrackCue
37203 *
37204 * @property {string} id
37205 *           The unique id for this text track cue
37206 *
37207 * @property {number} startTime
37208 *           The start time for this text track cue
37209 *
37210 * @property {number} endTime
37211 *           The end time for this text track cue
37212 *
37213 * @property {boolean} pauseOnExit
37214 *           Pause when the end time is reached if true.
37215 *
37216 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
37217 */
37218
37219/**
37220 * A List of TextTrackCues.
37221 *
37222 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
37223 */
37224class TextTrackCueList {
37225  /**
37226   * Create an instance of this class..
37227   *
37228   * @param {Array} cues
37229   *        A list of cues to be initialized with
37230   */
37231  constructor(cues) {
37232    TextTrackCueList.prototype.setCues_.call(this, cues);
37233
37234    /**
37235     * @memberof TextTrackCueList
37236     * @member {number} length
37237     *         The current number of `TextTrackCue`s in the TextTrackCueList.
37238     * @instance
37239     */
37240    Object.defineProperty(this, 'length', {
37241      get() {
37242        return this.length_;
37243      }
37244    });
37245  }
37246
37247  /**
37248   * A setter for cues in this list. Creates getters
37249   * an an index for the cues.
37250   *
37251   * @param {Array} cues
37252   *        An array of cues to set
37253   *
37254   * @private
37255   */
37256  setCues_(cues) {
37257    const oldLength = this.length || 0;
37258    let i = 0;
37259    const l = cues.length;
37260    this.cues_ = cues;
37261    this.length_ = cues.length;
37262    const defineProp = function (index) {
37263      if (!('' + index in this)) {
37264        Object.defineProperty(this, '' + index, {
37265          get() {
37266            return this.cues_[index];
37267          }
37268        });
37269      }
37270    };
37271    if (oldLength < l) {
37272      i = oldLength;
37273      for (; i < l; i++) {
37274        defineProp.call(this, i);
37275      }
37276    }
37277  }
37278
37279  /**
37280   * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
37281   *
37282   * @param {string} id
37283   *        The id of the cue that should be searched for.
37284   *
37285   * @return {TextTrackCueList~TextTrackCue|null}
37286   *         A single cue or null if none was found.
37287   */
37288  getCueById(id) {
37289    let result = null;
37290    for (let i = 0, l = this.length; i < l; i++) {
37291      const cue = this[i];
37292      if (cue.id === id) {
37293        result = cue;
37294        break;
37295      }
37296    }
37297    return result;
37298  }
37299}
37300
37301/**
37302 * @file track-kinds.js
37303 */
37304
37305/**
37306 * All possible `VideoTrackKind`s
37307 *
37308 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
37309 * @typedef VideoTrack~Kind
37310 * @enum
37311 */
37312const VideoTrackKind = {
37313  alternative: 'alternative',
37314  captions: 'captions',
37315  main: 'main',
37316  sign: 'sign',
37317  subtitles: 'subtitles',
37318  commentary: 'commentary'
37319};
37320
37321/**
37322 * All possible `AudioTrackKind`s
37323 *
37324 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
37325 * @typedef AudioTrack~Kind
37326 * @enum
37327 */
37328const AudioTrackKind = {
37329  'alternative': 'alternative',
37330  'descriptions': 'descriptions',
37331  'main': 'main',
37332  'main-desc': 'main-desc',
37333  'translation': 'translation',
37334  'commentary': 'commentary'
37335};
37336
37337/**
37338 * All possible `TextTrackKind`s
37339 *
37340 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
37341 * @typedef TextTrack~Kind
37342 * @enum
37343 */
37344const TextTrackKind = {
37345  subtitles: 'subtitles',
37346  captions: 'captions',
37347  descriptions: 'descriptions',
37348  chapters: 'chapters',
37349  metadata: 'metadata'
37350};
37351
37352/**
37353 * All possible `TextTrackMode`s
37354 *
37355 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
37356 * @typedef TextTrack~Mode
37357 * @enum
37358 */
37359const TextTrackMode = {
37360  disabled: 'disabled',
37361  hidden: 'hidden',
37362  showing: 'showing'
37363};
37364
37365/**
37366 * @file track.js
37367 */
37368
37369/**
37370 * A Track class that contains all of the common functionality for {@link AudioTrack},
37371 * {@link VideoTrack}, and {@link TextTrack}.
37372 *
37373 * > Note: This class should not be used directly
37374 *
37375 * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
37376 * @extends 
vendor: 9,468 bytes, lines 37376-37725
37376EventTarget
37377 * @abstract
37378 */
37379class Track extends EventTarget$2 {
37380  /**
37381   * Create an instance of this class.
37382   *
37383   * @param {Object} [options={}]
37384   *        Object of option names and values
37385   *
37386   * @param {string} [options.kind='']
37387   *        A valid kind for the track type you are creating.
37388   *
37389   * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
37390   *        A unique id for this AudioTrack.
37391   *
37392   * @param {string} [options.label='']
37393   *        The menu label for this track.
37394   *
37395   * @param {string} [options.language='']
37396   *        A valid two character language code.
37397   *
37398   * @abstract
37399   */
37400  constructor(options = {}) {
37401    super();
37402    const trackProps = {
37403      id: options.id || 'vjs_track_' + newGUID(),
37404      kind: options.kind || '',
37405      language: options.language || ''
37406    };
37407    let label = options.label || '';
37408
37409    /**
37410     * @memberof Track
37411     * @member {string} id
37412     *         The id of this track. Cannot be changed after creation.
37413     * @instance
37414     *
37415     * @readonly
37416     */
37417
37418    /**
37419     * @memberof Track
37420     * @member {string} kind
37421     *         The kind of track that this is. Cannot be changed after creation.
37422     * @instance
37423     *
37424     * @readonly
37425     */
37426
37427    /**
37428     * @memberof Track
37429     * @member {string} language
37430     *         The two letter language code for this track. Cannot be changed after
37431     *         creation.
37432     * @instance
37433     *
37434     * @readonly
37435     */
37436
37437    for (const key in trackProps) {
37438      Object.defineProperty(this, key, {
37439        get() {
37440          return trackProps[key];
37441        },
37442        set() {}
37443      });
37444    }
37445
37446    /**
37447     * @memberof Track
37448     * @member {string} label
37449     *         The label of this track. Cannot be changed after creation.
37450     * @instance
37451     *
37452     * @fires Track#labelchange
37453     */
37454    Object.defineProperty(this, 'label', {
37455      get() {
37456        return label;
37457      },
37458      set(newLabel) {
37459        if (newLabel !== label) {
37460          label = newLabel;
37461
37462          /**
37463           * An event that fires when label changes on this track.
37464           *
37465           * > Note: This is not part of the spec!
37466           *
37467           * @event Track#labelchange
37468           * @type {Event}
37469           */
37470          this.trigger('labelchange');
37471        }
37472      }
37473    });
37474  }
37475}
37476
37477/**
37478 * @file url.js
37479 * @module url
37480 */
37481
37482/**
37483 * Resolve and parse the elements of a URL.
37484 *
37485 * @function
37486 * @param    {string} url
37487 *           The url to parse
37488 *
37489 * @return   {URL}
37490 *           An object of url details
37491 */
37492const parseUrl = function (url) {
37493  return new URL(url, document__default["default"].baseURI);
37494};
37495
37496/**
37497 * Get absolute version of relative URL.
37498 *
37499 * @function
37500 * @param    {string} url
37501 *           URL to make absolute
37502 *
37503 * @return   {string}
37504 *           Absolute URL
37505 */
37506const getAbsoluteURL = function (url) {
37507  return new URL(url, document__default["default"].baseURI).href;
37508};
37509
37510/**
37511 * Returns the extension of the passed file name. It will return an empty string
37512 * if passed an invalid path.
37513 *
37514 * @function
37515 * @param    {string} path
37516 *           The fileName path like '/path/to/file.mp4'
37517 *
37518 * @return  {string}
37519 *           The extension in lower case or an empty string if no
37520 *           extension could be found.
37521 */
37522const getFileExtension = function (path) {
37523  if (typeof path === 'string') {
37524    const cleanPath = path.split('?')[0].replace(/\/+$/, '');
37525    const match = cleanPath.match(/\.([^.\/]+)$/);
37526    return match ? match[1].toLowerCase() : '';
37527  }
37528  return '';
37529};
37530
37531/**
37532 * Returns whether the url passed is a cross domain request or not.
37533 *
37534 * @function
37535 * @param    {string} url
37536 *           The url to check.
37537 *
37538 * @param    {URL} [winLoc]
37539 *           the domain to check the url against, defaults to window.location
37540 *
37541 * @return   {boolean}
37542 *           Whether it is a cross domain request or not.
37543 */
37544const isCrossOrigin = function (url, winLoc = window__default["default"].location) {
37545  return parseUrl(url).origin !== winLoc.origin;
37546};
37547
37548var Url = /*#__PURE__*/Object.freeze({
37549  __proto__: null,
37550  parseUrl: parseUrl,
37551  getAbsoluteURL: getAbsoluteURL,
37552  getFileExtension: getFileExtension,
37553  isCrossOrigin: isCrossOrigin
37554});
37555
37556/**
37557 * @file text-track.js
37558 */
37559
37560/** @import Tech from '../tech/tech' */
37561
37562/**
37563 * Takes a webvtt file contents and parses it into cues
37564 *
37565 * @param {string} srcContent
37566 *        webVTT file contents
37567 *
37568 * @param {TextTrack} track
37569 *        TextTrack to add cues to. Cues come from the srcContent.
37570 *
37571 * @private
37572 */
37573const parseCues = function (srcContent, track) {
37574  const parser = new window__default["default"].WebVTT.Parser(window__default["default"], window__default["default"].vttjs, window__default["default"].WebVTT.StringDecoder());
37575  const errors = [];
37576  parser.oncue = function (cue) {
37577    track.addCue(cue);
37578  };
37579  parser.onparsingerror = function (error) {
37580    errors.push(error);
37581  };
37582  parser.onflush = function () {
37583    track.trigger({
37584      type: 'loadeddata',
37585      target: track
37586    });
37587  };
37588  parser.parse(srcContent);
37589  if (errors.length > 0) {
37590    if (window__default["default"].console && window__default["default"].console.groupCollapsed) {
37591      window__default["default"].console.groupCollapsed(`Text Track parsing errors for ${track.src}`);
37592    }
37593    errors.forEach(error => log$1.error(error));
37594    if (window__default["default"].console && window__default["default"].console.groupEnd) {
37595      window__default["default"].console.groupEnd();
37596    }
37597  }
37598  parser.flush();
37599};
37600
37601/**
37602 * Load a `TextTrack` from a specified url.
37603 *
37604 * @param {string} src
37605 *        Url to load track from.
37606 *
37607 * @param {TextTrack} track
37608 *        Track to add cues to. Comes from the content at the end of `url`.
37609 *
37610 * @private
37611 */
37612const loadTrack = function (src, track) {
37613  const opts = {
37614    uri: src
37615  };
37616  const crossOrigin = isCrossOrigin(src);
37617  if (crossOrigin) {
37618    opts.cors = crossOrigin;
37619  }
37620  const withCredentials = track.tech_.crossOrigin() === 'use-credentials';
37621  if (withCredentials) {
37622    opts.withCredentials = withCredentials;
37623  }
37624  XHR__default["default"](opts, bind_(this, function (err, response, responseBody) {
37625    if (err) {
37626      return log$1.error(err, response);
37627    }
37628    track.loaded_ = true;
37629
37630    // Make sure that vttjs has loaded, otherwise, wait till it finished loading
37631    // NOTE: this is only used for the alt/video.novtt.js build
37632    if (typeof window__default["default"].WebVTT !== 'function') {
37633      if (track.tech_) {
37634        // to prevent use before define eslint error, we define loadHandler
37635        // as a let here
37636        track.tech_.any(['vttjsloaded', 'vttjserror'], event => {
37637          if (event.type === 'vttjserror') {
37638            log$1.error(`vttjs failed to load, stopping trying to process ${track.src}`);
37639            return;
37640          }
37641          return parseCues(responseBody, track);
37642        });
37643      }
37644    } else {
37645      parseCues(responseBody, track);
37646    }
37647  }));
37648};
37649
37650/**
37651 * A representation of a single `TextTrack`.
37652 *
37653 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
37654 * @extends Track
37655 */
37656class TextTrack extends Track {
37657  /**
37658   * Create an instance of this class.
37659   *
37660   * @param {Object} options={}
37661   *        Object of option names and values
37662   *
37663   * @param {Tech} options.tech
37664   *        A reference to the tech that owns this TextTrack.
37665   *
37666   * @param {TextTrack~Kind} [options.kind='subtitles']
37667   *        A valid text track kind.
37668   *
37669   * @param {TextTrack~Mode} [options.mode='disabled']
37670   *        A valid text track mode.
37671   *
37672   * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
37673   *        A unique id for this TextTrack.
37674   *
37675   * @param {string} [options.label='']
37676   *        The menu label for this track.
37677   *
37678   * @param {string} [options.language='']
37679   *        A valid two character language code.
37680   *
37681   * @param {string} [options.srclang='']
37682   *        A valid two character language code. An alternative, but deprioritized
37683   *        version of `options.language`
37684   *
37685   * @param {string} [options.src]
37686   *        A url to TextTrack cues.
37687   *
37688   * @param {boolean} [options.default]
37689   *        If this track should default to on or off.
37690   */
37691  constructor(options = {}) {
37692    if (!options.tech) {
37693      throw new Error('A tech was not provided.');
37694    }
37695    const settings = merge$1(options, {
37696      kind: TextTrackKind[options.kind] || 'subtitles',
37697      language: options.language || options.srclang || ''
37698    });
37699    let mode = TextTrackMode[settings.mode] || 'disabled';
37700    const default_ = settings.default;
37701    if (settings.kind === 'metadata' || settings.kind === 'chapters') {
37702      mode = 'hidden';
37703    }
37704    super(settings);
37705    this.tech_ = settings.tech;
37706    this.cues_ = [];
37707    this.activeCues_ = [];
37708    this.preload_ = this.tech_.preloadTextTracks !== false;
37709    const cues = new TextTrackCueList(this.cues_);
37710    const activeCues = new TextTrackCueList(this.activeCues_);
37711    let changed = false;
37712    this.timeupdateHandler = bind_(this, function (event = {}) {
37713      if (this.tech_.isDisposed()) {
37714        return;
37715      }
37716      if (!this.tech_.isReady_) {
37717        if (event.type !== 'timeupdate') {
37718          this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
37719        }
37720        return;
37721      }
37722
37723      // Accessing this.activeCues for the side-effects of updating itself
37724      // due to its nature as a getter function. Do not remove or cues will
37725      // stop updating!
vendor: 4,816 bytes, lines 37726-37891
37726      // Use the setter to prevent deletion from uglify (pure_getters rule)
37727      this.activeCues = this.activeCues;
37728      if (changed) {
37729        this.trigger('cuechange');
37730        changed = false;
37731      }
37732      if (event.type !== 'timeupdate') {
37733        this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
37734      }
37735    });
37736    const disposeHandler = () => {
37737      this.stopTracking();
37738    };
37739    this.tech_.one('dispose', disposeHandler);
37740    if (mode !== 'disabled') {
37741      this.startTracking();
37742    }
37743    Object.defineProperties(this, {
37744      /**
37745       * @memberof TextTrack
37746       * @member {boolean} default
37747       *         If this track was set to be on or off by default. Cannot be changed after
37748       *         creation.
37749       * @instance
37750       *
37751       * @readonly
37752       */
37753      default: {
37754        get() {
37755          return default_;
37756        },
37757        set() {}
37758      },
37759      /**
37760       * @memberof TextTrack
37761       * @member {string} mode
37762       *         Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
37763       *         not be set if setting to an invalid mode.
37764       * @instance
37765       *
37766       * @fires TextTrack#modechange
37767       */
37768      mode: {
37769        get() {
37770          return mode;
37771        },
37772        set(newMode) {
37773          if (!TextTrackMode[newMode]) {
37774            return;
37775          }
37776          if (mode === newMode) {
37777            return;
37778          }
37779          mode = newMode;
37780          if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) {
37781            // On-demand load.
37782            loadTrack(this.src, this);
37783          }
37784          this.stopTracking();
37785          if (mode !== 'disabled') {
37786            this.startTracking();
37787          }
37788          /**
37789           * An event that fires when mode changes on this track. This allows
37790           * the TextTrackList that holds this track to act accordingly.
37791           *
37792           * > Note: This is not part of the spec!
37793           *
37794           * @event TextTrack#modechange
37795           * @type {Event}
37796           */
37797          this.trigger('modechange');
37798        }
37799      },
37800      /**
37801       * @memberof TextTrack
37802       * @member {TextTrackCueList} cues
37803       *         The text track cue list for this TextTrack.
37804       * @instance
37805       */
37806      cues: {
37807        get() {
37808          if (!this.loaded_) {
37809            return null;
37810          }
37811          return cues;
37812        },
37813        set() {}
37814      },
37815      /**
37816       * @memberof TextTrack
37817       * @member {TextTrackCueList} activeCues
37818       *         The list text track cues that are currently active for this TextTrack.
37819       * @instance
37820       */
37821      activeCues: {
37822        get() {
37823          if (!this.loaded_) {
37824            return null;
37825          }
37826
37827          // nothing to do
37828          if (this.cues.length === 0) {
37829            return activeCues;
37830          }
37831          const ct = this.tech_.currentTime();
37832          const active = [];
37833          for (let i = 0, l = this.cues.length; i < l; i++) {
37834            const cue = this.cues[i];
37835            if (cue.startTime <= ct && cue.endTime >= ct) {
37836              active.push(cue);
37837            }
37838          }
37839          changed = false;
37840          if (active.length !== this.activeCues_.length) {
37841            changed = true;
37842          } else {
37843            for (let i = 0; i < active.length; i++) {
37844              if (this.activeCues_.indexOf(active[i]) === -1) {
37845                changed = true;
37846              }
37847            }
37848          }
37849          this.activeCues_ = active;
37850          activeCues.setCues_(this.activeCues_);
37851          return activeCues;
37852        },
37853        // /!\ Keep this setter empty (see the timeupdate handler above)
37854        set() {}
37855      }
37856    });
37857    if (settings.src) {
37858      this.src = settings.src;
37859      if (!this.preload_) {
37860        // Tracks will load on-demand.
37861        // Act like we're loaded for other purposes.
37862        this.loaded_ = true;
37863      }
37864      if (this.preload_ || settings.kind !== 'subtitles' && settings.kind !== 'captions') {
37865        loadTrack(this.src, this);
37866      }
37867    } else {
37868      this.loaded_ = true;
37869    }
37870  }
37871  startTracking() {
37872    // More precise cues based on requestVideoFrameCallback with a requestAnimationFram fallback
37873    this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
37874    // Also listen to timeupdate in case rVFC/rAF stops (window in background, audio in video el)
37875    this.tech_.on('timeupdate', this.timeupdateHandler);
37876  }
37877  stopTracking() {
37878    if (this.rvf_) {
37879      this.tech_.cancelVideoFrameCallback(this.rvf_);
37880      this.rvf_ = undefined;
37881    }
37882    this.tech_.off('timeupdate', this.timeupdateHandler);
37883  }
37884
37885  /**
37886   * Add a cue to the internal list of cues.
37887   *
37888   * @param {TextTrack~Cue} cue
37889   *        The cue to add to our internal list
37890   */
37891  addCue(originalCue) {
vendor: 5,135 bytes, lines 37892-38069
37892    let cue = originalCue;
37893
37894    // Testing if the cue is a VTTCue in a way that survives minification
37895    if (!('getCueAsHTML' in cue)) {
37896      cue = new window__default["default"].vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
37897      for (const prop in originalCue) {
37898        if (!(prop in cue)) {
37899          cue[prop] = originalCue[prop];
37900        }
37901      }
37902
37903      // make sure that `id` is copied over
37904      cue.id = originalCue.id;
37905      cue.originalCue_ = originalCue;
37906    }
37907    const tracks = this.tech_.textTracks();
37908    for (let i = 0; i < tracks.length; i++) {
37909      if (tracks[i] !== this) {
37910        tracks[i].removeCue(cue);
37911      }
37912    }
37913    this.cues_.push(cue);
37914    this.cues.setCues_(this.cues_);
37915  }
37916
37917  /**
37918   * Creates a copy of the text track and makes it serializable
37919   * by removing circular dependencies.
37920   *
37921   * @return {Object} The track information as a serializable object
37922   */
37923  toJSON() {
37924    return textTrackConverter.trackToJson(this);
37925  }
37926
37927  /**
37928   * Remove a cue from our internal list
37929   *
37930   * @param {TextTrack~Cue} removeCue
37931   *        The cue to remove from our internal list
37932   */
37933  removeCue(removeCue) {
37934    let i = this.cues_.length;
37935    while (i--) {
37936      const cue = this.cues_[i];
37937      if (cue === removeCue || cue.originalCue_ && cue.originalCue_ === removeCue) {
37938        this.cues_.splice(i, 1);
37939        this.cues.setCues_(this.cues_);
37940        break;
37941      }
37942    }
37943  }
37944}
37945
37946/**
37947 * cuechange - One or more cues in the track have become active or stopped being active.
37948 *
37949 * @protected
37950 */
37951TextTrack.prototype.allowedEvents_ = {
37952  cuechange: 'cuechange'
37953};
37954
37955/**
37956 * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
37957 * only one `AudioTrack` in the list will be enabled at a time.
37958 *
37959 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
37960 * @extends Track
37961 */
37962class AudioTrack extends Track {
37963  /**
37964   * Create an instance of this class.
37965   *
37966   * @param {Object} [options={}]
37967   *        Object of option names and values
37968   *
37969   * @param {AudioTrack~Kind} [options.kind='']
37970   *        A valid audio track kind
37971   *
37972   * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
37973   *        A unique id for this AudioTrack.
37974   *
37975   * @param {string} [options.label='']
37976   *        The menu label for this track.
37977   *
37978   * @param {string} [options.language='']
37979   *        A valid two character language code.
37980   *
37981   * @param {boolean} [options.enabled]
37982   *        If this track is the one that is currently playing. If this track is part of
37983   *        an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
37984   */
37985  constructor(options = {}) {
37986    const settings = merge$1(options, {
37987      kind: AudioTrackKind[options.kind] || ''
37988    });
37989    super(settings);
37990    let enabled = false;
37991
37992    /**
37993     * @memberof AudioTrack
37994     * @member {boolean} enabled
37995     *         If this `AudioTrack` is enabled or not. When setting this will
37996     *         fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
37997     * @instance
37998     *
37999     * @fires VideoTrack#selectedchange
38000     */
38001    Object.defineProperty(this, 'enabled', {
38002      get() {
38003        return enabled;
38004      },
38005      set(newEnabled) {
38006        // an invalid or unchanged value
38007        if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
38008          return;
38009        }
38010        enabled = newEnabled;
38011
38012        /**
38013         * An event that fires when enabled changes on this track. This allows
38014         * the AudioTrackList that holds this track to act accordingly.
38015         *
38016         * > Note: This is not part of the spec! Native tracks will do
38017         *         this internally without an event.
38018         *
38019         * @event AudioTrack#enabledchange
38020         * @type {Event}
38021         */
38022        this.trigger('enabledchange');
38023      }
38024    });
38025
38026    // if the user sets this track to selected then
38027    // set selected to that true value otherwise
38028    // we keep it false
38029    if (settings.enabled) {
38030      this.enabled = settings.enabled;
38031    }
38032    this.loaded_ = true;
38033  }
38034}
38035
38036/**
38037 * A representation of a single `VideoTrack`.
38038 *
38039 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
38040 * @extends Track
38041 */
38042class VideoTrack extends Track {
38043  /**
38044   * Create an instance of this class.
38045   *
38046   * @param {Object} [options={}]
38047   *        Object of option names and values
38048   *
38049   * @param {string} [options.kind='']
38050   *        A valid {@link VideoTrack~Kind}
38051   *
38052   * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
38053   *        A unique id for this AudioTrack.
38054   *
38055   * @param {string} [options.label='']
38056   *        The menu label for this track.
38057   *
38058   * @param {string} [options.language='']
38059   *        A valid two character language code.
38060   *
38061   * @param {boolean} [options.selected]
38062   *        If this track is the one that is currently playing.
38063   */
38064  constructor(options = {}) {
38065    const settings = merge$1(options, {
38066      kind: VideoTrackKind[options.kind] || ''
38067    });
38068    super(settings);
38069    let selected = false;
vendor: 20,353 bytes, lines 38070-38841
38070
38071    /**
38072     * @memberof VideoTrack
38073     * @member {boolean} selected
38074     *         If this `VideoTrack` is selected or not. When setting this will
38075     *         fire {@link VideoTrack#selectedchange} if the state of selected changed.
38076     * @instance
38077     *
38078     * @fires VideoTrack#selectedchange
38079     */
38080    Object.defineProperty(this, 'selected', {
38081      get() {
38082        return selected;
38083      },
38084      set(newSelected) {
38085        // an invalid or unchanged value
38086        if (typeof newSelected !== 'boolean' || newSelected === selected) {
38087          return;
38088        }
38089        selected = newSelected;
38090
38091        /**
38092         * An event that fires when selected changes on this track. This allows
38093         * the VideoTrackList that holds this track to act accordingly.
38094         *
38095         * > Note: This is not part of the spec! Native tracks will do
38096         *         this internally without an event.
38097         *
38098         * @event VideoTrack#selectedchange
38099         * @type {Event}
38100         */
38101        this.trigger('selectedchange');
38102      }
38103    });
38104
38105    // if the user sets this track to selected then
38106    // set selected to that true value otherwise
38107    // we keep it false
38108    if (settings.selected) {
38109      this.selected = settings.selected;
38110    }
38111  }
38112}
38113
38114/**
38115 * @file html-track-element.js
38116 */
38117
38118/** @import Tech from '../tech/tech' */
38119
38120/**
38121 * A single track represented in the DOM.
38122 *
38123 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
38124 * @extends EventTarget
38125 */
38126class HTMLTrackElement extends EventTarget$2 {
38127  /**
38128   * Create an instance of this class.
38129   *
38130   * @param {Object} options={}
38131   *        Object of option names and values
38132   *
38133   * @param {Tech} options.tech
38134   *        A reference to the tech that owns this HTMLTrackElement.
38135   *
38136   * @param {TextTrack~Kind} [options.kind='subtitles']
38137   *        A valid text track kind.
38138   *
38139   * @param {TextTrack~Mode} [options.mode='disabled']
38140   *        A valid text track mode.
38141   *
38142   * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
38143   *        A unique id for this TextTrack.
38144   *
38145   * @param {string} [options.label='']
38146   *        The menu label for this track.
38147   *
38148   * @param {string} [options.language='']
38149   *        A valid two character language code.
38150   *
38151   * @param {string} [options.srclang='']
38152   *        A valid two character language code. An alternative, but deprioritized
38153   *        version of `options.language`
38154   *
38155   * @param {string} [options.src]
38156   *        A url to TextTrack cues.
38157   *
38158   * @param {boolean} [options.default]
38159   *        If this track should default to on or off.
38160   */
38161  constructor(options = {}) {
38162    super();
38163    let readyState;
38164    const track = new TextTrack(options);
38165    this.kind = track.kind;
38166    this.src = track.src;
38167    this.srclang = track.language;
38168    this.label = track.label;
38169    this.default = track.default;
38170    Object.defineProperties(this, {
38171      /**
38172       * @memberof HTMLTrackElement
38173       * @member {HTMLTrackElement~ReadyState} readyState
38174       *         The current ready state of the track element.
38175       * @instance
38176       */
38177      readyState: {
38178        get() {
38179          return readyState;
38180        }
38181      },
38182      /**
38183       * @memberof HTMLTrackElement
38184       * @member {TextTrack} track
38185       *         The underlying TextTrack object.
38186       * @instance
38187       *
38188       */
38189      track: {
38190        get() {
38191          return track;
38192        }
38193      }
38194    });
38195    readyState = HTMLTrackElement.NONE;
38196
38197    /**
38198     * @listens TextTrack#loadeddata
38199     * @fires HTMLTrackElement#load
38200     */
38201    track.addEventListener('loadeddata', () => {
38202      readyState = HTMLTrackElement.LOADED;
38203      this.trigger({
38204        type: 'load',
38205        target: this
38206      });
38207    });
38208  }
38209}
38210
38211/**
38212 * @protected
38213 */
38214HTMLTrackElement.prototype.allowedEvents_ = {
38215  load: 'load'
38216};
38217
38218/**
38219 * The text track not loaded state.
38220 *
38221 * @type {number}
38222 * @static
38223 */
38224HTMLTrackElement.NONE = 0;
38225
38226/**
38227 * The text track loading state.
38228 *
38229 * @type {number}
38230 * @static
38231 */
38232HTMLTrackElement.LOADING = 1;
38233
38234/**
38235 * The text track loaded state.
38236 *
38237 * @type {number}
38238 * @static
38239 */
38240HTMLTrackElement.LOADED = 2;
38241
38242/**
38243 * The text track failed to load state.
38244 *
38245 * @type {number}
38246 * @static
38247 */
38248HTMLTrackElement.ERROR = 3;
38249
38250/*
38251 * This file contains all track properties that are used in
38252 * player.js, tech.js, html5.js and possibly other techs in the future.
38253 */
38254
38255const NORMAL = {
38256  audio: {
38257    ListClass: AudioTrackList,
38258    TrackClass: AudioTrack,
38259    capitalName: 'Audio'
38260  },
38261  video: {
38262    ListClass: VideoTrackList,
38263    TrackClass: VideoTrack,
38264    capitalName: 'Video'
38265  },
38266  text: {
38267    ListClass: TextTrackList,
38268    TrackClass: TextTrack,
38269    capitalName: 'Text'
38270  }
38271};
38272Object.keys(NORMAL).forEach(function (type) {
38273  NORMAL[type].getterName = `${type}Tracks`;
38274  NORMAL[type].privateName = `${type}Tracks_`;
38275});
38276const REMOTE = {
38277  remoteText: {
38278    ListClass: TextTrackList,
38279    TrackClass: TextTrack,
38280    capitalName: 'RemoteText',
38281    getterName: 'remoteTextTracks',
38282    privateName: 'remoteTextTracks_'
38283  },
38284  remoteTextEl: {
38285    ListClass: HtmlTrackElementList,
38286    TrackClass: HTMLTrackElement,
38287    capitalName: 'RemoteTextTrackEls',
38288    getterName: 'remoteTextTrackEls',
38289    privateName: 'remoteTextTrackEls_'
38290  }
38291};
38292const ALL = Object.assign({}, NORMAL, REMOTE);
38293REMOTE.names = Object.keys(REMOTE);
38294NORMAL.names = Object.keys(NORMAL);
38295ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
38296
38297/**
38298 * @file tech.js
38299 */
38300
38301/** @import { TimeRange } from '../utils/time' */
38302
38303/**
38304 * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
38305 * that just contains the src url alone.
38306 * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
38307   * `var SourceString = 'http://example.com/some-video.mp4';`
38308 *
38309 * @typedef {Object|string} SourceObject
38310 *
38311 * @property {string} src
38312 *           The url to the source
38313 *
38314 * @property {string} type
38315 *           The mime type of the source
38316 */
38317
38318/**
38319 * A function used by {@link Tech} to create a new {@link TextTrack}.
38320 *
38321 * @private
38322 *
38323 * @param {Tech} self
38324 *        An instance of the Tech class.
38325 *
38326 * @param {string} kind
38327 *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
38328 *
38329 * @param {string} [label]
38330 *        Label to identify the text track
38331 *
38332 * @param {string} [language]
38333 *        Two letter language abbreviation
38334 *
38335 * @param {Object} [options={}]
38336 *        An object with additional text track options
38337 *
38338 * @return {TextTrack}
38339 *          The text track that was created.
38340 */
38341function createTrackHelper(self, kind, label, language, options = {}) {
38342  const tracks = self.textTracks();
38343  options.kind = kind;
38344  if (label) {
38345    options.label = label;
38346  }
38347  if (language) {
38348    options.language = language;
38349  }
38350  options.tech = self;
38351  const track = new ALL.text.TrackClass(options);
38352  tracks.addTrack(track);
38353  return track;
38354}
38355
38356/**
38357 * This is the base class for media playback technology controllers, such as
38358 * {@link HTML5}
38359 *
38360 * @extends Component
38361 */
38362class Tech extends Component$1 {
38363  /**
38364  * Create an instance of this Tech.
38365  *
38366  * @param {Object} [options]
38367  *        The key/value store of player options.
38368  *
38369  * @param {Function} [ready]
38370  *        Callback function to call when the `HTML5` Tech is ready.
38371  */
38372  constructor(options = {}, ready = function () {}) {
38373    // we don't want the tech to report user activity automatically.
38374    // This is done manually in addControlsListeners
38375    options.reportTouchActivity = false;
38376    super(null, options, ready);
38377    this.onDurationChange_ = e => this.onDurationChange(e);
38378    this.trackProgress_ = e => this.trackProgress(e);
38379    this.trackCurrentTime_ = e => this.trackCurrentTime(e);
38380    this.stopTrackingCurrentTime_ = e => this.stopTrackingCurrentTime(e);
38381    this.disposeSourceHandler_ = e => this.disposeSourceHandler(e);
38382    this.queuedHanders_ = new Set();
38383
38384    // keep track of whether the current source has played at all to
38385    // implement a very limited played()
38386    this.hasStarted_ = false;
38387    this.on('playing', function () {
38388      this.hasStarted_ = true;
38389    });
38390    this.on('loadstart', function () {
38391      this.hasStarted_ = false;
38392    });
38393    ALL.names.forEach(name => {
38394      const props = ALL[name];
38395      if (options && options[props.getterName]) {
38396        this[props.privateName] = options[props.getterName];
38397      }
38398    });
38399
38400    // Manually track progress in cases where the browser/tech doesn't report it.
38401    if (!this.featuresProgressEvents) {
38402      this.manualProgressOn();
38403    }
38404
38405    // Manually track timeupdates in cases where the browser/tech doesn't report it.
38406    if (!this.featuresTimeupdateEvents) {
38407      this.manualTimeUpdatesOn();
38408    }
38409    ['Text', 'Audio', 'Video'].forEach(track => {
38410      if (options[`native${track}Tracks`] === false) {
38411        this[`featuresNative${track}Tracks`] = false;
38412      }
38413    });
38414    if (options.nativeCaptions === false || options.nativeTextTracks === false) {
38415      this.featuresNativeTextTracks = false;
38416    } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
38417      this.featuresNativeTextTracks = true;
38418    }
38419    if (!this.featuresNativeTextTracks) {
38420      this.emulateTextTracks();
38421    }
38422    this.preloadTextTracks = options.preloadTextTracks !== false;
38423    this.autoRemoteTextTracks_ = new ALL.text.ListClass();
38424    this.initTrackListeners();
38425
38426    // Turn on component tap events only if not using native controls
38427    if (!options.nativeControlsForTouch) {
38428      this.emitTapEvents();
38429    }
38430    if (this.constructor) {
38431      this.name_ = this.constructor.name || 'Unknown Tech';
38432    }
38433  }
38434
38435  /**
38436   * A special function to trigger source set in a way that will allow player
38437   * to re-trigger if the player or tech are not ready yet.
38438   *
38439   * @fires Tech#sourceset
38440   * @param {string} src The source string at the time of the source changing.
38441   */
38442  triggerSourceset(src) {
38443    if (!this.isReady_) {
38444      // on initial ready we have to trigger source set
38445      // 1ms after ready so that player can watch for it.
38446      this.one('ready', () => this.setTimeout(() => this.triggerSourceset(src), 1));
38447    }
38448
38449    /**
38450     * Fired when the source is set on the tech causing the media element
38451     * to reload.
38452     *
38453     * @see {@link Player#event:sourceset}
38454     * @event Tech#sourceset
38455     * @type {Event}
38456     */
38457    this.trigger({
38458      src,
38459      type: 'sourceset'
38460    });
38461  }
38462
38463  /* Fallbacks for unsupported event types
38464  ================================================================================ */
38465
38466  /**
38467   * Polyfill the `progress` event for browsers that don't support it natively.
38468   *
38469   * @see {@link Tech#trackProgress}
38470   */
38471  manualProgressOn() {
38472    this.on('durationchange', this.onDurationChange_);
38473    this.manualProgress = true;
38474
38475    // Trigger progress watching when a source begins loading
38476    this.one('ready', this.trackProgress_);
38477  }
38478
38479  /**
38480   * Turn off the polyfill for `progress` events that was created in
38481   * {@link Tech#manualProgressOn}
38482   */
38483  manualProgressOff() {
38484    this.manualProgress = false;
38485    this.stopTrackingProgress();
38486    this.off('durationchange', this.onDurationChange_);
38487  }
38488
38489  /**
38490   * This is used to trigger a `progress` event when the buffered percent changes. It
38491   * sets an interval function that will be called every 500 milliseconds to check if the
38492   * buffer end percent has changed.
38493   *
38494   * > This function is called by {@link Tech#manualProgressOn}
38495   *
38496   * @param {Event} event
38497   *        The `ready` event that caused this to run.
38498   *
38499   * @listens Tech#ready
38500   * @fires Tech#progress
38501   */
38502  trackProgress(event) {
38503    this.stopTrackingProgress();
38504    this.progressInterval = this.setInterval(bind_(this, function () {
38505      // Don't trigger unless buffered amount is greater than last time
38506
38507      const numBufferedPercent = this.bufferedPercent();
38508      if (this.bufferedPercent_ !== numBufferedPercent) {
38509        /**
38510         * See {@link Player#progress}
38511         *
38512         * @event Tech#progress
38513         * @type {Event}
38514         */
38515        this.trigger('progress');
38516      }
38517      this.bufferedPercent_ = numBufferedPercent;
38518      if (numBufferedPercent === 1) {
38519        this.stopTrackingProgress();
38520      }
38521    }), 500);
38522  }
38523
38524  /**
38525   * Update our internal duration on a `durationchange` event by calling
38526   * {@link Tech#duration}.
38527   *
38528   * @param {Event} event
38529   *        The `durationchange` event that caused this to run.
38530   *
38531   * @listens Tech#durationchange
38532   */
38533  onDurationChange(event) {
38534    this.duration_ = this.duration();
38535  }
38536
38537  /**
38538   * Get and create a `TimeRange` object for buffering.
38539   *
38540   * @return {TimeRange}
38541   *         The time range object that was created.
38542   */
38543  buffered() {
38544    return createTimeRanges$1(0, 0);
38545  }
38546
38547  /**
38548   * Get the percentage of the current video that is currently buffered.
38549   *
38550   * @return {number}
38551   *         A number from 0 to 1 that represents the decimal percentage of the
38552   *         video that is buffered.
38553   *
38554   */
38555  bufferedPercent() {
38556    return bufferedPercent(this.buffered(), this.duration_);
38557  }
38558
38559  /**
38560   * Turn off the polyfill for `progress` events that was created in
38561   * {@link Tech#manualProgressOn}
38562   * Stop manually tracking progress events by clearing the interval that was set in
38563   * {@link Tech#trackProgress}.
38564   */
38565  stopTrackingProgress() {
38566    this.clearInterval(this.progressInterval);
38567  }
38568
38569  /**
38570   * Polyfill the `timeupdate` event for browsers that don't support it.
38571   *
38572   * @see {@link Tech#trackCurrentTime}
38573   */
38574  manualTimeUpdatesOn() {
38575    this.manualTimeUpdates = true;
38576    this.on('play', this.trackCurrentTime_);
38577    this.on('pause', this.stopTrackingCurrentTime_);
38578  }
38579
38580  /**
38581   * Turn off the polyfill for `timeupdate` events that was created in
38582   * {@link Tech#manualTimeUpdatesOn}
38583   */
38584  manualTimeUpdatesOff() {
38585    this.manualTimeUpdates = false;
38586    this.stopTrackingCurrentTime();
38587    this.off('play', this.trackCurrentTime_);
38588    this.off('pause', this.stopTrackingCurrentTime_);
38589  }
38590
38591  /**
38592   * Sets up an interval function to track current time and trigger `timeupdate` every
38593   * 250 milliseconds.
38594   *
38595   * @listens Tech#play
38596   * @triggers Tech#timeupdate
38597   */
38598  trackCurrentTime() {
38599    if (this.currentTimeInterval) {
38600      this.stopTrackingCurrentTime();
38601    }
38602    this.currentTimeInterval = this.setInterval(function () {
38603      /**
38604       * Triggered at an interval of 250ms to indicated that time is passing in the video.
38605       *
38606       * @event Tech#timeupdate
38607       * @type {Event}
38608       */
38609      this.trigger({
38610        type: 'timeupdate',
38611        target: this,
38612        manuallyTriggered: true
38613      });
38614
38615      // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
38616    }, 250);
38617  }
38618
38619  /**
38620   * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
38621   * `timeupdate` event is no longer triggered.
38622   *
38623   * @listens {Tech#pause}
38624   */
38625  stopTrackingCurrentTime() {
38626    this.clearInterval(this.currentTimeInterval);
38627
38628    // #1002 - if the video ends right before the next timeupdate would happen,
38629    // the progress bar won't make it all the way to the end
38630    this.trigger({
38631      type: 'timeupdate',
38632      target: this,
38633      manuallyTriggered: true
38634    });
38635  }
38636
38637  /**
38638   * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
38639   * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
38640   *
38641   * @fires Component#dispose
38642   */
38643  dispose() {
38644    // clear out all tracks because we can't reuse them between techs
38645    this.clearTracks(NORMAL.names);
38646
38647    // Turn off any manual progress or timeupdate tracking
38648    if (this.manualProgress) {
38649      this.manualProgressOff();
38650    }
38651    if (this.manualTimeUpdates) {
38652      this.manualTimeUpdatesOff();
38653    }
38654    super.dispose();
38655  }
38656
38657  /**
38658   * Clear out a single `TrackList` or an array of `TrackLists` given their names.
38659   *
38660   * > Note: Techs without source handlers should call this between sources for `video`
38661   *         & `audio` tracks. You don't want to use them between tracks!
38662   *
38663   * @param {string[]|string} types
38664   *        TrackList names to clear, valid names are `video`, `audio`, and
38665   *        `text`.
38666   */
38667  clearTracks(types) {
38668    types = [].concat(types);
38669    // clear out all tracks because we can't reuse them between techs
38670    types.forEach(type => {
38671      const list = this[`${type}Tracks`]() || [];
38672      let i = list.length;
38673      while (i--) {
38674        const track = list[i];
38675        if (type === 'text') {
38676          this.removeRemoteTextTrack(track);
38677        }
38678        list.removeTrack(track);
38679      }
38680    });
38681  }
38682
38683  /**
38684   * Remove any TextTracks added via addRemoteTextTrack that are
38685   * flagged for automatic garbage collection
38686   */
38687  cleanupAutoTextTracks() {
38688    const list = this.autoRemoteTextTracks_ || [];
38689    let i = list.length;
38690    while (i--) {
38691      const track = list[i];
38692      this.removeRemoteTextTrack(track);
38693    }
38694  }
38695
38696  /**
38697   * Reset the tech, which will removes all sources and reset the internal readyState.
38698   *
38699   * @abstract
38700   */
38701  reset() {}
38702
38703  /**
38704   * Get the value of `crossOrigin` from the tech.
38705   *
38706   * @abstract
38707   *
38708   * @see {Html5#crossOrigin}
38709   */
38710  crossOrigin() {}
38711
38712  /**
38713   * Set the value of `crossOrigin` on the tech.
38714   *
38715   * @abstract
38716   *
38717   * @param {string} crossOrigin the crossOrigin value
38718   * @see {Html5#setCrossOrigin}
38719   */
38720  setCrossOrigin() {}
38721
38722  /**
38723   * Get or set an error on the Tech.
38724   *
38725   * @param {MediaError} [err]
38726   *        Error to set on the Tech
38727   *
38728   * @return {MediaError|null}
38729   *         The current error object on the tech, or null if there isn't one.
38730   */
38731  error(err) {
38732    if (err !== undefined) {
38733      this.error_ = new MediaError(err);
38734      this.trigger('error');
38735    }
38736    return this.error_;
38737  }
38738
38739  /**
38740   * Returns the `TimeRange`s that have been played through for the current source.
38741   *
38742   * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
38743   *         It only checks whether the source has played at all or not.
38744   *
38745   * @return {TimeRange}
38746   *         - A single time range if this video has played
38747   *         - An empty set of ranges if not.
38748   */
38749  played() {
38750    if (this.hasStarted_) {
38751      return createTimeRanges$1(0, 0);
38752    }
38753    return createTimeRanges$1();
38754  }
38755
38756  /**
38757   * Start playback
38758   *
38759   * @abstract
38760   *
38761   * @see {Html5#play}
38762   */
38763  play() {}
38764
38765  /**
38766   * Set whether we are scrubbing or not
38767   *
38768   * @abstract
38769   * @param {boolean} _isScrubbing
38770   *                  - true for we are currently scrubbing
38771   *                  - false for we are no longer scrubbing
38772   *
38773   * @see {Html5#setScrubbing}
38774   */
38775  setScrubbing(_isScrubbing) {}
38776
38777  /**
38778   * Get whether we are scrubbing or not
38779   *
38780   * @abstract
38781   *
38782   * @see {Html5#scrubbing}
38783   */
38784  scrubbing() {}
38785
38786  /**
38787   * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
38788   * previously called.
38789   *
38790   * @param {number} _seconds
38791   *        Set the current time of the media to this.
38792   * @fires Tech#timeupdate
38793   */
38794  setCurrentTime(_seconds) {
38795    // improve the accuracy of manual timeupdates
38796    if (this.manualTimeUpdates) {
38797      /**
38798       * A manual `timeupdate` event.
38799       *
38800       * @event Tech#timeupdate
38801       * @type {Event}
38802       */
38803      this.trigger({
38804        type: 'timeupdate',
38805        target: this,
38806        manuallyTriggered: true
38807      });
38808    }
38809  }
38810
38811  /**
38812   * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
38813   * {@link TextTrackList} events.
38814   *
38815   * This adds {@link EventTarget~EventListeners} for `addtrack`, and  `removetrack`.
38816   *
38817   * @fires Tech#audiotrackchange
38818   * @fires Tech#videotrackchange
38819   * @fires Tech#texttrackchange
38820   */
38821  initTrackListeners() {
38822    /**
38823      * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
38824      *
38825      * @event Tech#audiotrackchange
38826      * @type {Event}
38827      */
38828
38829    /**
38830      * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
38831      *
38832      * @event Tech#videotrackchange
38833      * @type {Event}
38834      */
38835
38836    /**
38837      * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
38838      *
38839      * @event Tech#texttrackchange
38840      * @type {Event}
38841      */
vendor: 6,405 bytes, lines 38842-39025
38842    NORMAL.names.forEach(name => {
38843      const props = NORMAL[name];
38844      const trackListChanges = () => {
38845        this.trigger(`${name}trackchange`);
38846      };
38847      const tracks = this[props.getterName]();
38848      tracks.addEventListener('removetrack', trackListChanges);
38849      tracks.addEventListener('addtrack', trackListChanges);
38850      this.on('dispose', () => {
38851        tracks.removeEventListener('removetrack', trackListChanges);
38852        tracks.removeEventListener('addtrack', trackListChanges);
38853      });
38854    });
38855  }
38856
38857  /**
38858   * Emulate TextTracks using vtt.js if necessary
38859   *
38860   * @fires Tech#vttjsloaded
38861   * @fires Tech#vttjserror
38862   */
38863  addWebVttScript_() {
38864    if (window__default["default"].WebVTT) {
38865      return;
38866    }
38867
38868    // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
38869    // signals that the Tech is ready at which point Tech.el_ is part of the DOM
38870    // before inserting the WebVTT script
38871    if (document__default["default"].body.contains(this.el())) {
38872      // load via require if available and vtt.js script location was not passed in
38873      // as an option. novtt builds will turn the above require call into an empty object
38874      // which will cause this if check to always fail.
38875      if (!this.options_['vtt.js'] && isPlain(vtt__default["default"]) && Object.keys(vtt__default["default"]).length > 0) {
38876        this.trigger('vttjsloaded');
38877        return;
38878      }
38879
38880      // load vtt.js via the script location option or the cdn of no location was
38881      // passed in
38882      const script = document__default["default"].createElement('script');
38883      script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
38884      script.onload = () => {
38885        /**
38886         * Fired when vtt.js is loaded.
38887         *
38888         * @event Tech#vttjsloaded
38889         * @type {Event}
38890         */
38891        this.trigger('vttjsloaded');
38892      };
38893      script.onerror = () => {
38894        /**
38895         * Fired when vtt.js was not loaded due to an error
38896         *
38897         * @event Tech#vttjsloaded
38898         * @type {Event}
38899         */
38900        this.trigger('vttjserror');
38901      };
38902      this.on('dispose', () => {
38903        script.onload = null;
38904        script.onerror = null;
38905      });
38906      // but have not loaded yet and we set it to true before the inject so that
38907      // we don't overwrite the injected window.WebVTT if it loads right away
38908      window__default["default"].WebVTT = true;
38909      this.el().parentNode.appendChild(script);
38910    } else {
38911      this.ready(this.addWebVttScript_);
38912    }
38913  }
38914
38915  /**
38916   * Emulate texttracks
38917   *
38918   */
38919  emulateTextTracks() {
38920    const tracks = this.textTracks();
38921    const remoteTracks = this.remoteTextTracks();
38922    const handleAddTrack = e => tracks.addTrack(e.track);
38923    const handleRemoveTrack = e => tracks.removeTrack(e.track);
38924    remoteTracks.on('addtrack', handleAddTrack);
38925    remoteTracks.on('removetrack', handleRemoveTrack);
38926    this.addWebVttScript_();
38927    const updateDisplay = () => this.trigger('texttrackchange');
38928    const textTracksChanges = () => {
38929      updateDisplay();
38930      for (let i = 0; i < tracks.length; i++) {
38931        const track = tracks[i];
38932        track.removeEventListener('cuechange', updateDisplay);
38933        if (track.mode === 'showing') {
38934          track.addEventListener('cuechange', updateDisplay);
38935        }
38936      }
38937    };
38938    textTracksChanges();
38939    tracks.addEventListener('change', textTracksChanges);
38940    tracks.addEventListener('addtrack', textTracksChanges);
38941    tracks.addEventListener('removetrack', textTracksChanges);
38942    this.on('dispose', function () {
38943      remoteTracks.off('addtrack', handleAddTrack);
38944      remoteTracks.off('removetrack', handleRemoveTrack);
38945      tracks.removeEventListener('change', textTracksChanges);
38946      tracks.removeEventListener('addtrack', textTracksChanges);
38947      tracks.removeEventListener('removetrack', textTracksChanges);
38948      for (let i = 0; i < tracks.length; i++) {
38949        const track = tracks[i];
38950        track.removeEventListener('cuechange', updateDisplay);
38951      }
38952    });
38953  }
38954
38955  /**
38956   * Create and returns a remote {@link TextTrack} object.
38957   *
38958   * @param {string} kind
38959   *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
38960   *
38961   * @param {string} [label]
38962   *        Label to identify the text track
38963   *
38964   * @param {string} [language]
38965   *        Two letter language abbreviation
38966   *
38967   * @return {TextTrack}
38968   *         The TextTrack that gets created.
38969   */
38970  addTextTrack(kind, label, language) {
38971    if (!kind) {
38972      throw new Error('TextTrack kind is required but was not provided');
38973    }
38974    return createTrackHelper(this, kind, label, language);
38975  }
38976
38977  /**
38978   * Create an emulated TextTrack for use by addRemoteTextTrack
38979   *
38980   * This is intended to be overridden by classes that inherit from
38981   * Tech in order to create native or custom TextTracks.
38982   *
38983   * @param {Object} options
38984   *        The object should contain the options to initialize the TextTrack with.
38985   *
38986   * @param {string} [options.kind]
38987   *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
38988   *
38989   * @param {string} [options.label].
38990   *        Label to identify the text track
38991   *
38992   * @param {string} [options.language]
38993   *        Two letter language abbreviation.
38994   *
38995   * @return {HTMLTrackElement}
38996   *         The track element that gets created.
38997   */
38998  createRemoteTextTrack(options) {
38999    const track = merge$1(options, {
39000      tech: this
39001    });
39002    return new REMOTE.remoteTextEl.TrackClass(track);
39003  }
39004
39005  /**
39006   * Creates a remote text track object and returns an html track element.
39007   *
39008   * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
39009   *
39010   * @param {Object} options
39011   *        See {@link Tech#createRemoteTextTrack} for more detailed properties.
39012   *
39013   * @param {boolean} [manualCleanup=false]
39014   *        - When false: the TextTrack will be automatically removed from the video
39015   *          element whenever the source changes
39016   *        - When True: The TextTrack will have to be cleaned up manually
39017   *
39018   * @return {HTMLTrackElement}
39019   *         An Html Track Element.
39020   *
39021   */
39022  addRemoteTextTrack(options = {}, manualCleanup) {
39023    const htmlTrackElement = this.createRemoteTextTrack(options);
39024    if (typeof manualCleanup !== 'boolean') {
39025      manualCleanup = false;
vendor: 6,920 bytes, lines 39026-39286
39026    }
39027
39028    // store HTMLTrackElement and TextTrack to remote list
39029    this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
39030    this.remoteTextTracks().addTrack(htmlTrackElement.track);
39031    if (manualCleanup === false) {
39032      // create the TextTrackList if it doesn't exist
39033      this.ready(() => this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track));
39034    }
39035    return htmlTrackElement;
39036  }
39037
39038  /**
39039   * Remove a remote text track from the remote `TextTrackList`.
39040   *
39041   * @param {TextTrack} track
39042   *        `TextTrack` to remove from the `TextTrackList`
39043   */
39044  removeRemoteTextTrack(track) {
39045    const trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
39046
39047    // remove HTMLTrackElement and TextTrack from remote list
39048    this.remoteTextTrackEls().removeTrackElement_(trackElement);
39049    this.remoteTextTracks().removeTrack(track);
39050    this.autoRemoteTextTracks_.removeTrack(track);
39051  }
39052
39053  /**
39054   * Gets available media playback quality metrics as specified by the W3C's Media
39055   * Playback Quality API.
39056   *
39057   * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
39058   *
39059   * @return {Object}
39060   *         An object with supported media playback quality metrics
39061   *
39062   * @abstract
39063   */
39064  getVideoPlaybackQuality() {
39065    return {};
39066  }
39067
39068  /**
39069   * Attempt to create a floating video window always on top of other windows
39070   * so that users may continue consuming media while they interact with other
39071   * content sites, or applications on their device.
39072   *
39073   * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
39074   *
39075   * @return {Promise|undefined}
39076   *         A promise with a Picture-in-Picture window if the browser supports
39077   *         Promises (or one was passed in as an option). It returns undefined
39078   *         otherwise.
39079   *
39080   * @abstract
39081   */
39082  requestPictureInPicture() {
39083    return Promise.reject();
39084  }
39085
39086  /**
39087   * A method to check for the value of the 'disablePictureInPicture' <video> property.
39088   * Defaults to true, as it should be considered disabled if the tech does not support pip
39089   *
39090   * @abstract
39091   */
39092  disablePictureInPicture() {
39093    return true;
39094  }
39095
39096  /**
39097   * A method to set or unset the 'disablePictureInPicture' <video> property.
39098   *
39099   * @abstract
39100   */
39101  setDisablePictureInPicture() {}
39102
39103  /**
39104   * A fallback implementation of requestVideoFrameCallback using requestAnimationFrame
39105   *
39106   * @param {function} cb
39107   * @return {number} request id
39108   */
39109  requestVideoFrameCallback(cb) {
39110    const id = newGUID();
39111    if (!this.isReady_ || this.paused()) {
39112      this.queuedHanders_.add(id);
39113      this.one('playing', () => {
39114        if (this.queuedHanders_.has(id)) {
39115          this.queuedHanders_.delete(id);
39116          cb();
39117        }
39118      });
39119    } else {
39120      this.requestNamedAnimationFrame(id, cb);
39121    }
39122    return id;
39123  }
39124
39125  /**
39126   * A fallback implementation of cancelVideoFrameCallback
39127   *
39128   * @param {number} id id of callback to be cancelled
39129   */
39130  cancelVideoFrameCallback(id) {
39131    if (this.queuedHanders_.has(id)) {
39132      this.queuedHanders_.delete(id);
39133    } else {
39134      this.cancelNamedAnimationFrame(id);
39135    }
39136  }
39137
39138  /**
39139   * A method to set a poster from a `Tech`.
39140   *
39141   * @abstract
39142   */
39143  setPoster() {}
39144
39145  /**
39146   * A method to check for the presence of the 'playsinline' <video> attribute.
39147   *
39148   * @abstract
39149   */
39150  playsinline() {}
39151
39152  /**
39153   * A method to set or unset the 'playsinline' <video> attribute.
39154   *
39155   * @abstract
39156   */
39157  setPlaysinline() {}
39158
39159  /**
39160   * Attempt to force override of native audio tracks.
39161   *
39162   * @param {boolean} override - If set to true native audio will be overridden,
39163   * otherwise native audio will potentially be used.
39164   *
39165   * @abstract
39166   */
39167  overrideNativeAudioTracks(override) {}
39168
39169  /**
39170   * Attempt to force override of native video tracks.
39171   *
39172   * @param {boolean} override - If set to true native video will be overridden,
39173   * otherwise native video will potentially be used.
39174   *
39175   * @abstract
39176   */
39177  overrideNativeVideoTracks(override) {}
39178
39179  /**
39180   * Check if the tech can support the given mime-type.
39181   *
39182   * The base tech does not support any type, but source handlers might
39183   * overwrite this.
39184   *
39185   * @param  {string} _type
39186   *         The mimetype to check for support
39187   *
39188   * @return {string}
39189   *         'probably', 'maybe', or empty string
39190   *
39191   * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
39192   *
39193   * @abstract
39194   */
39195  canPlayType(_type) {
39196    return '';
39197  }
39198
39199  /**
39200   * Check if the type is supported by this tech.
39201   *
39202   * The base tech does not support any type, but source handlers might
39203   * overwrite this.
39204   *
39205   * @param {string} _type
39206   *        The media type to check
39207   * @return {string} Returns the native video element's response
39208   */
39209  static canPlayType(_type) {
39210    return '';
39211  }
39212
39213  /**
39214   * Check if the tech can support the given source
39215   *
39216   * @param {Object} srcObj
39217   *        The source object
39218   * @param {Object} options
39219   *        The options passed to the tech
39220   * @return {string} 'probably', 'maybe', or '' (empty string)
39221   */
39222  static canPlaySource(srcObj, options) {
39223    return Tech.canPlayType(srcObj.type);
39224  }
39225
39226  /*
39227   * Return whether the argument is a Tech or not.
39228   * Can be passed either a Class like `Html5` or a instance like `player.tech_`
39229   *
39230   * @param {Object} component
39231   *        The item to check
39232   *
39233   * @return {boolean}
39234   *         Whether it is a tech or not
39235   *         - True if it is a tech
39236   *         - False if it is not
39237   */
39238  static isTech(component) {
39239    return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
39240  }
39241
39242  /**
39243   * Registers a `Tech` into a shared list for videojs.
39244   *
39245   * @param {string} name
39246   *        Name of the `Tech` to register.
39247   *
39248   * @param {Object} tech
39249   *        The `Tech` class to register.
39250   */
39251  static registerTech(name, tech) {
39252    if (!Tech.techs_) {
39253      Tech.techs_ = {};
39254    }
39255    if (!Tech.isTech(tech)) {
39256      throw new Error(`Tech ${name} must be a Tech`);
39257    }
39258    if (!Tech.canPlayType) {
39259      throw new Error('Techs must have a static canPlayType method on them');
39260    }
39261    if (!Tech.canPlaySource) {
39262      throw new Error('Techs must have a static canPlaySource method on them');
39263    }
39264    name = toTitleCase$1(name);
39265    Tech.techs_[name] = tech;
39266    Tech.techs_[toLowerCase(name)] = tech;
39267    if (name !== 'Tech') {
39268      // camel case the techName for use in techOrder
39269      Tech.defaultTechOrder_.push(name);
39270    }
39271    return tech;
39272  }
39273
39274  /**
39275   * Get a `Tech` from the shared list by name.
39276   *
39277   * @param {string} name
39278   *        `camelCase` or `TitleCase` name of the Tech to get
39279   *
39280   * @return {Tech|undefined}
39281   *         The `Tech` or undefined if there was no tech with the name requested.
39282   */
39283  static getTech(name) {
39284    if (!name) {
39285      return;
39286    }
vendor: 5,120 bytes, lines 39287-39493
39287    if (Tech.techs_ && Tech.techs_[name]) {
39288      return Tech.techs_[name];
39289    }
39290    name = toTitleCase$1(name);
39291    if (window__default["default"] && window__default["default"].videojs && window__default["default"].videojs[name]) {
39292      log$1.warn(`The ${name} tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)`);
39293      return window__default["default"].videojs[name];
39294    }
39295  }
39296}
39297
39298/**
39299 * Get the {@link VideoTrackList}
39300 *
39301 * @returns {VideoTrackList}
39302 * @method Tech.prototype.videoTracks
39303 */
39304
39305/**
39306 * Get the {@link AudioTrackList}
39307 *
39308 * @returns {AudioTrackList}
39309 * @method Tech.prototype.audioTracks
39310 */
39311
39312/**
39313 * Get the {@link TextTrackList}
39314 *
39315 * @returns {TextTrackList}
39316 * @method Tech.prototype.textTracks
39317 */
39318
39319/**
39320 * Get the remote element {@link TextTrackList}
39321 *
39322 * @returns {TextTrackList}
39323 * @method Tech.prototype.remoteTextTracks
39324 */
39325
39326/**
39327 * Get the remote element {@link HtmlTrackElementList}
39328 *
39329 * @returns {HtmlTrackElementList}
39330 * @method Tech.prototype.remoteTextTrackEls
39331 */
39332
39333ALL.names.forEach(function (name) {
39334  const props = ALL[name];
39335  Tech.prototype[props.getterName] = function () {
39336    this[props.privateName] = this[props.privateName] || new props.ListClass();
39337    return this[props.privateName];
39338  };
39339});
39340
39341/**
39342 * List of associated text tracks
39343 *
39344 * @type {TextTrackList}
39345 * @private
39346 * @property Tech#textTracks_
39347 */
39348
39349/**
39350 * List of associated audio tracks.
39351 *
39352 * @type {AudioTrackList}
39353 * @private
39354 * @property Tech#audioTracks_
39355 */
39356
39357/**
39358 * List of associated video tracks.
39359 *
39360 * @type {VideoTrackList}
39361 * @private
39362 * @property Tech#videoTracks_
39363 */
39364
39365/**
39366 * Boolean indicating whether the `Tech` supports volume control.
39367 *
39368 * @type {boolean}
39369 * @default
39370 */
39371Tech.prototype.featuresVolumeControl = true;
39372
39373/**
39374 * Boolean indicating whether the `Tech` supports muting volume.
39375 *
39376 * @type {boolean}
39377 * @default
39378 */
39379Tech.prototype.featuresMuteControl = true;
39380
39381/**
39382 * Boolean indicating whether the `Tech` supports fullscreen resize control.
39383 * Resizing plugins using request fullscreen reloads the plugin
39384 *
39385 * @type {boolean}
39386 * @default
39387 */
39388Tech.prototype.featuresFullscreenResize = false;
39389
39390/**
39391 * Boolean indicating whether the `Tech` supports changing the speed at which the video
39392 * plays. Examples:
39393 *   - Set player to play 2x (twice) as fast
39394 *   - Set player to play 0.5x (half) as fast
39395 *
39396 * @type {boolean}
39397 * @default
39398 */
39399Tech.prototype.featuresPlaybackRate = false;
39400
39401/**
39402 * Boolean indicating whether the `Tech` supports the `progress` event.
39403 * This will be used to determine if {@link Tech#manualProgressOn} should be called.
39404 *
39405 * @type {boolean}
39406 * @default
39407 */
39408Tech.prototype.featuresProgressEvents = false;
39409
39410/**
39411 * Boolean indicating whether the `Tech` supports the `sourceset` event.
39412 *
39413 * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
39414 * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
39415 * a new source.
39416 *
39417 * @type {boolean}
39418 * @default
39419 */
39420Tech.prototype.featuresSourceset = false;
39421
39422/**
39423 * Boolean indicating whether the `Tech` supports the `timeupdate` event.
39424 * This will be used to determine if {@link Tech#manualTimeUpdates} should be called.
39425 *
39426 * @type {boolean}
39427 * @default
39428 */
39429Tech.prototype.featuresTimeupdateEvents = false;
39430
39431/**
39432 * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
39433 * This will help us integrate with native `TextTrack`s if the browser supports them.
39434 *
39435 * @type {boolean}
39436 * @default
39437 */
39438Tech.prototype.featuresNativeTextTracks = false;
39439
39440/**
39441 * Boolean indicating whether the `Tech` supports `requestVideoFrameCallback`.
39442 *
39443 * @type {boolean}
39444 * @default
39445 */
39446Tech.prototype.featuresVideoFrameCallback = false;
39447
39448/**
39449 * A functional mixin for techs that want to use the Source Handler pattern.
39450 * Source handlers are scripts for handling specific formats.
39451 * The source handler pattern is used for adaptive formats (HLS, DASH) that
39452 * manually load video data and feed it into a Source Buffer (Media Source Extensions)
39453 * Example: `Tech.withSourceHandlers.call(MyTech);`
39454 *
39455 * @param {Tech} _Tech
39456 *        The tech to add source handler functions to.
39457 *
39458 * @mixes Tech~SourceHandlerAdditions
39459 */
39460Tech.withSourceHandlers = function (_Tech) {
39461  /**
39462   * Register a source handler
39463   *
39464   * @param {Function} handler
39465   *        The source handler class
39466   *
39467   * @param {number} [index]
39468   *        Register it at the following index
39469   */
39470  _Tech.registerSourceHandler = function (handler, index) {
39471    let handlers = _Tech.sourceHandlers;
39472    if (!handlers) {
39473      handlers = _Tech.sourceHandlers = [];
39474    }
39475    if (index === undefined) {
39476      // add to the end of the list
39477      index = handlers.length;
39478    }
39479    handlers.splice(index, 0, handler);
39480  };
39481
39482  /**
39483   * Check if the tech can support the given type. Also checks the
39484   * Techs sourceHandlers.
39485   *
39486   * @param {string} type
39487   *         The mimetype to check.
39488   *
39489   * @return {string}
39490   *         'probably', 'maybe', or '' (empty string)
39491   */
39492  _Tech.canPlayType = function (type) {
39493    const handlers = _Tech.sourceHandlers || [];
vendor: 12,943 bytes, lines 39494-39970
39494    let can;
39495    for (let i = 0; i < handlers.length; i++) {
39496      can = handlers[i].canPlayType(type);
39497      if (can) {
39498        return can;
39499      }
39500    }
39501    return '';
39502  };
39503
39504  /**
39505   * Returns the first source handler that supports the source.
39506   *
39507   * TODO: Answer question: should 'probably' be prioritized over 'maybe'
39508   *
39509   * @param {SourceObject} source
39510   *        The source object
39511   *
39512   * @param {Object} options
39513   *        The options passed to the tech
39514   *
39515   * @return {SourceHandler|null}
39516   *          The first source handler that supports the source or null if
39517   *          no SourceHandler supports the source
39518   */
39519  _Tech.selectSourceHandler = function (source, options) {
39520    const handlers = _Tech.sourceHandlers || [];
39521    let can;
39522    for (let i = 0; i < handlers.length; i++) {
39523      can = handlers[i].canHandleSource(source, options);
39524      if (can) {
39525        return handlers[i];
39526      }
39527    }
39528    return null;
39529  };
39530
39531  /**
39532   * Check if the tech can support the given source.
39533   *
39534   * @param {SourceObject} srcObj
39535   *        The source object
39536   *
39537   * @param {Object} options
39538   *        The options passed to the tech
39539   *
39540   * @return {string}
39541   *         'probably', 'maybe', or '' (empty string)
39542   */
39543  _Tech.canPlaySource = function (srcObj, options) {
39544    const sh = _Tech.selectSourceHandler(srcObj, options);
39545    if (sh) {
39546      return sh.canHandleSource(srcObj, options);
39547    }
39548    return '';
39549  };
39550
39551  /**
39552   * When using a source handler, prefer its implementation of
39553   * any function normally provided by the tech.
39554   */
39555  const deferrable = ['seekable', 'seeking', 'duration'];
39556
39557  /**
39558   * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
39559   * function if it exists, with a fallback to the Techs seekable function.
39560   *
39561   * @method _Tech.seekable
39562   */
39563
39564  /**
39565   * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
39566   * function if it exists, otherwise it will fallback to the techs duration function.
39567   *
39568   * @method _Tech.duration
39569   */
39570
39571  deferrable.forEach(function (fnName) {
39572    const originalFn = this[fnName];
39573    if (typeof originalFn !== 'function') {
39574      return;
39575    }
39576    this[fnName] = function () {
39577      if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
39578        return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
39579      }
39580      return originalFn.apply(this, arguments);
39581    };
39582  }, _Tech.prototype);
39583
39584  /**
39585   * Create a function for setting the source using a source object
39586   * and source handlers.
39587   * Should never be called unless a source handler was found.
39588   *
39589   * @param {SourceObject} source
39590   *        A source object with src and type keys
39591   */
39592  _Tech.prototype.setSource = function (source) {
39593    let sh = _Tech.selectSourceHandler(source, this.options_);
39594    if (!sh) {
39595      // Fall back to a native source handler when unsupported sources are
39596      // deliberately set
39597      if (_Tech.nativeSourceHandler) {
39598        sh = _Tech.nativeSourceHandler;
39599      } else {
39600        log$1.error('No source handler found for the current source.');
39601      }
39602    }
39603
39604    // Dispose any existing source handler
39605    this.disposeSourceHandler();
39606    this.off('dispose', this.disposeSourceHandler_);
39607    if (sh !== _Tech.nativeSourceHandler) {
39608      this.currentSource_ = source;
39609    }
39610    this.sourceHandler_ = sh.handleSource(source, this, this.options_);
39611    this.one('dispose', this.disposeSourceHandler_);
39612  };
39613
39614  /**
39615   * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
39616   *
39617   * @listens Tech#dispose
39618   */
39619  _Tech.prototype.disposeSourceHandler = function () {
39620    // if we have a source and get another one
39621    // then we are loading something new
39622    // than clear all of our current tracks
39623    if (this.currentSource_) {
39624      this.clearTracks(['audio', 'video']);
39625      this.currentSource_ = null;
39626    }
39627
39628    // always clean up auto-text tracks
39629    this.cleanupAutoTextTracks();
39630    if (this.sourceHandler_) {
39631      if (this.sourceHandler_.dispose) {
39632        this.sourceHandler_.dispose();
39633      }
39634      this.sourceHandler_ = null;
39635    }
39636  };
39637};
39638
39639// The base Tech class needs to be registered as a Component. It is the only
39640// Tech that can be registered as a Component.
39641Component$1.registerComponent('Tech', Tech);
39642Tech.registerTech('Tech', Tech);
39643
39644/**
39645 * A list of techs that should be added to techOrder on Players
39646 *
39647 * @private
39648 */
39649Tech.defaultTechOrder_ = [];
39650
39651/**
39652 * @file middleware.js
39653 * @module middleware
39654 */
39655
39656/** @import Player from '../player' */
39657/** @import Tech from '../tech/tech' */
39658
39659const middlewares = {};
39660const middlewareInstances = {};
39661const TERMINATOR = {};
39662
39663/**
39664 * A middleware object is a plain JavaScript object that has methods that
39665 * match the {@link Tech} methods found in the lists of allowed
39666 * {@link module:middleware.allowedGetters|getters},
39667 * {@link module:middleware.allowedSetters|setters}, and
39668 * {@link module:middleware.allowedMediators|mediators}.
39669 *
39670 * @typedef {Object} MiddlewareObject
39671 */
39672
39673/**
39674 * A middleware factory function that should return a
39675 * {@link module:middleware~MiddlewareObject|MiddlewareObject}.
39676 *
39677 * This factory will be called for each player when needed, with the player
39678 * passed in as an argument.
39679 *
39680 * @callback MiddlewareFactory
39681 * @param {Player} player
39682 *        A Video.js player.
39683 */
39684
39685/**
39686 * Define a middleware that the player should use by way of a factory function
39687 * that returns a middleware object.
39688 *
39689 * @param  {string} type
39690 *         The MIME type to match or `"*"` for all MIME types.
39691 *
39692 * @param  {MiddlewareFactory} middleware
39693 *         A middleware factory function that will be executed for
39694 *         matching types.
39695 */
39696function use(type, middleware) {
39697  middlewares[type] = middlewares[type] || [];
39698  middlewares[type].push(middleware);
39699}
39700
39701/**
39702 * Asynchronously sets a source using middleware by recursing through any
39703 * matching middlewares and calling `setSource` on each, passing along the
39704 * previous returned value each time.
39705 *
39706 * @param  {Player} player
39707 *         A {@link Player} instance.
39708 *
39709 * @param  {Tech~SourceObject} src
39710 *         A source object.
39711 *
39712 * @param  {Function}
39713 *         The next middleware to run.
39714 */
39715function setSource(player, src, next) {
39716  player.setTimeout(() => setSourceHelper(src, middlewares[src.type], next, player), 1);
39717}
39718
39719/**
39720 * When the tech is set, passes the tech to each middleware's `setTech` method.
39721 *
39722 * @param {Object[]} middleware
39723 *        An array of middleware instances.
39724 *
39725 * @param {Tech} tech
39726 *        A Video.js tech.
39727 */
39728function setTech(middleware, tech) {
39729  middleware.forEach(mw => mw.setTech && mw.setTech(tech));
39730}
39731
39732/**
39733 * Calls a getter on the tech first, through each middleware
39734 * from right to left to the player.
39735 *
39736 * @param  {Object[]} middleware
39737 *         An array of middleware instances.
39738 *
39739 * @param  {Tech} tech
39740 *         The current tech.
39741 *
39742 * @param  {string} method
39743 *         A method name.
39744 *
39745 * @return {*}
39746 *         The final value from the tech after middleware has intercepted it.
39747 */
39748function get(middleware, tech, method) {
39749  return middleware.reduceRight(middlewareIterator(method), tech[method]());
39750}
39751
39752/**
39753 * Takes the argument given to the player and calls the setter method on each
39754 * middleware from left to right to the tech.
39755 *
39756 * @param  {Object[]} middleware
39757 *         An array of middleware instances.
39758 *
39759 * @param  {Tech} tech
39760 *         The current tech.
39761 *
39762 * @param  {string} method
39763 *         A method name.
39764 *
39765 * @param  {*} arg
39766 *         The value to set on the tech.
39767 *
39768 * @return {*}
39769 *         The return value of the `method` of the `tech`.
39770 */
39771function set(middleware, tech, method, arg) {
39772  return tech[method](middleware.reduce(middlewareIterator(method), arg));
39773}
39774
39775/**
39776 * Takes the argument given to the player and calls the `call` version of the
39777 * method on each middleware from left to right.
39778 *
39779 * Then, call the passed in method on the tech and return the result unchanged
39780 * back to the player, through middleware, this time from right to left.
39781 *
39782 * @param  {Object[]} middleware
39783 *         An array of middleware instances.
39784 *
39785 * @param  {Tech} tech
39786 *         The current tech.
39787 *
39788 * @param  {string} method
39789 *         A method name.
39790 *
39791 * @param  {*} arg
39792 *         The value to set on the tech.
39793 *
39794 * @return {*}
39795 *         The return value of the `method` of the `tech`, regardless of the
39796 *         return values of middlewares.
39797 */
39798function mediate(middleware, tech, method, arg = null) {
39799  const callMethod = 'call' + toTitleCase$1(method);
39800  const middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
39801  const terminated = middlewareValue === TERMINATOR;
39802  // deprecated. The `null` return value should instead return TERMINATOR to
39803  // prevent confusion if a techs method actually returns null.
39804  const returnValue = terminated ? null : tech[method](middlewareValue);
39805  executeRight(middleware, method, returnValue, terminated);
39806  return returnValue;
39807}
39808
39809/**
39810 * Enumeration of allowed getters where the keys are method names.
39811 *
39812 * @type {Object}
39813 */
39814const allowedGetters = {
39815  buffered: 1,
39816  currentTime: 1,
39817  duration: 1,
39818  muted: 1,
39819  played: 1,
39820  paused: 1,
39821  seekable: 1,
39822  volume: 1,
39823  ended: 1
39824};
39825
39826/**
39827 * Enumeration of allowed setters where the keys are method names.
39828 *
39829 * @type {Object}
39830 */
39831const allowedSetters = {
39832  setCurrentTime: 1,
39833  setMuted: 1,
39834  setVolume: 1
39835};
39836
39837/**
39838 * Enumeration of allowed mediators where the keys are method names.
39839 *
39840 * @type {Object}
39841 */
39842const allowedMediators = {
39843  play: 1,
39844  pause: 1
39845};
39846function middlewareIterator(method) {
39847  return (value, mw) => {
39848    // if the previous middleware terminated, pass along the termination
39849    if (value === TERMINATOR) {
39850      return TERMINATOR;
39851    }
39852    if (mw[method]) {
39853      return mw[method](value);
39854    }
39855    return value;
39856  };
39857}
39858function executeRight(mws, method, value, terminated) {
39859  for (let i = mws.length - 1; i >= 0; i--) {
39860    const mw = mws[i];
39861    if (mw[method]) {
39862      mw[method](terminated, value);
39863    }
39864  }
39865}
39866
39867/**
39868 * Clear the middleware cache for a player.
39869 *
39870 * @param  {Player} player
39871 *         A {@link Player} instance.
39872 */
39873function clearCacheForPlayer(player) {
39874  if (middlewareInstances.hasOwnProperty(player.id())) {
39875    delete middlewareInstances[player.id()];
39876  }
39877}
39878
39879/**
39880 * {
39881 *  [playerId]: [[mwFactory, mwInstance], ...]
39882 * }
39883 *
39884 * @private
39885 */
39886function getOrCreateFactory(player, mwFactory) {
39887  const mws = middlewareInstances[player.id()];
39888  let mw = null;
39889  if (mws === undefined || mws === null) {
39890    mw = mwFactory(player);
39891    middlewareInstances[player.id()] = [[mwFactory, mw]];
39892    return mw;
39893  }
39894  for (let i = 0; i < mws.length; i++) {
39895    const [mwf, mwi] = mws[i];
39896    if (mwf !== mwFactory) {
39897      continue;
39898    }
39899    mw = mwi;
39900  }
39901  if (mw === null) {
39902    mw = mwFactory(player);
39903    mws.push([mwFactory, mw]);
39904  }
39905  return mw;
39906}
39907function setSourceHelper(src = {}, middleware = [], next, player, acc = [], lastRun = false) {
39908  const [mwFactory, ...mwrest] = middleware;
39909
39910  // if mwFactory is a string, then we're at a fork in the road
39911  if (typeof mwFactory === 'string') {
39912    setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun);
39913
39914    // if we have an mwFactory, call it with the player to get the mw,
39915    // then call the mw's setSource method
39916  } else if (mwFactory) {
39917    const mw = getOrCreateFactory(player, mwFactory);
39918
39919    // if setSource isn't present, implicitly select this middleware
39920    if (!mw.setSource) {
39921      acc.push(mw);
39922      return setSourceHelper(src, mwrest, next, player, acc, lastRun);
39923    }
39924    mw.setSource(Object.assign({}, src), function (err, _src) {
39925      // something happened, try the next middleware on the current level
39926      // make sure to use the old src
39927      if (err) {
39928        return setSourceHelper(src, mwrest, next, player, acc, lastRun);
39929      }
39930
39931      // we've succeeded, now we need to go deeper
39932      acc.push(mw);
39933
39934      // if it's the same type, continue down the current chain
39935      // otherwise, we want to go down the new chain
39936      setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
39937    });
39938  } else if (mwrest.length) {
39939    setSourceHelper(src, mwrest, next, player, acc, lastRun);
39940  } else if (lastRun) {
39941    next(src, acc);
39942  } else {
39943    setSourceHelper(src, middlewares['*'], next, player, acc, true);
39944  }
39945}
39946
39947/** @import Player from '../player' */
39948
39949/**
39950 * Mimetypes
39951 *
39952 * @see https://www.iana.org/assignments/media-types/media-types.xhtml
39953 * @typedef Mimetypes~Kind
39954 * @enum
39955 */
39956const MimetypesKind = {
39957  opus: 'video/ogg',
39958  ogv: 'video/ogg',
39959  mp4: 'video/mp4',
39960  mov: 'video/mp4',
39961  m4v: 'video/mp4',
39962  mkv: 'video/x-matroska',
39963  m4a: 'audio/mp4',
39964  mp3: 'audio/mpeg',
39965  aac: 'audio/aac',
39966  caf: 'audio/x-caf',
39967  flac: 'audio/flac',
39968  oga: 'audio/ogg',
39969  wav: 'audio/wav',
39970  m3u8: 'application/x-mpegURL',
vendor: 22,512 bytes, lines 39971-40153
39971  mpd: 'application/dash+xml',
39972  jpg: 'image/jpeg',
39973  jpeg: 'image/jpeg',
39974  gif: 'image/gif',
39975  png: 'image/png',
39976  svg: 'image/svg+xml',
39977  webp: 'image/webp'
39978};
39979
39980/**
39981 * Get the mimetype of a given src url if possible
39982 *
39983 * @param {string} src
39984 *        The url to the src
39985 *
39986 * @return {string}
39987 *         return the mimetype if it was known or empty string otherwise
39988 */
39989const getMimetype = function (src = '') {
39990  const ext = getFileExtension(src);
39991  const mimetype = MimetypesKind[ext.toLowerCase()];
39992  return mimetype || '';
39993};
39994
39995/**
39996 * Find the mime type of a given source string if possible. Uses the player
39997 * source cache.
39998 *
39999 * @param {Player} player
40000 *        The player object
40001 *
40002 * @param {string} src
40003 *        The source string
40004 *
40005 * @return {string}
40006 *         The type that was found
40007 */
40008const findMimetype = (player, src) => {
40009  if (!src) {
40010    return '';
40011  }
40012
40013  // 1. check for the type in the `source` cache
40014  if (player.cache_.source.src === src && player.cache_.source.type) {
40015    return player.cache_.source.type;
40016  }
40017
40018  // 2. see if we have this source in our `currentSources` cache
40019  const matchingSources = player.cache_.sources.filter(s => s.src === src);
40020  if (matchingSources.length) {
40021    return matchingSources[0].type;
40022  }
40023
40024  // 3. look for the src url in source elements and use the type there
40025  const sources = player.$$('source');
40026  for (let i = 0; i < sources.length; i++) {
40027    const s = sources[i];
40028    if (s.type && s.src && s.src === src) {
40029      return s.type;
40030    }
40031  }
40032
40033  // 4. finally fallback to our list of mime types based on src url extension
40034  return getMimetype(src);
40035};
40036
40037/**
40038 * @module filter-source
40039 */
40040
40041/**
40042 * Filter out single bad source objects or multiple source objects in an
40043 * array. Also flattens nested source object arrays into a 1 dimensional
40044 * array of source objects.
40045 *
40046 * @param {Tech~SourceObject|Tech~SourceObject[]} src
40047 *        The src object to filter
40048 *
40049 * @return {Tech~SourceObject[]}
40050 *         An array of sourceobjects containing only valid sources
40051 *
40052 * @private
40053 */
40054const filterSource = function (src) {
40055  // traverse array
40056  if (Array.isArray(src)) {
40057    let newsrc = [];
40058    src.forEach(function (srcobj) {
40059      srcobj = filterSource(srcobj);
40060      if (Array.isArray(srcobj)) {
40061        newsrc = newsrc.concat(srcobj);
40062      } else if (isObject(srcobj)) {
40063        newsrc.push(srcobj);
40064      }
40065    });
40066    src = newsrc;
40067  } else if (typeof src === 'string' && src.trim()) {
40068    // convert string into object
40069    src = [fixSource({
40070      src
40071    })];
40072  } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
40073    // src is already valid
40074    src = [fixSource(src)];
40075  } else {
40076    // invalid source, turn it into an empty array
40077    src = [];
40078  }
40079  return src;
40080};
40081
40082/**
40083 * Checks src mimetype, adding it when possible
40084 *
40085 * @param {Tech~SourceObject} src
40086 *        The src object to check
40087 * @return {Tech~SourceObject}
40088 *        src Object with known type
40089 */
40090function fixSource(src) {
40091  if (!src.type) {
40092    const mimetype = getMimetype(src.src);
40093    if (mimetype) {
40094      src.type = mimetype;
40095    }
40096  }
40097  return src;
40098}
40099
40100var 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 1200 1227\" id=\"vjs-icon-twitter\">\n      <path d=\"M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z\"/>\n    </symbol>\n    <symbol viewBox=\"0 0 1792 1792\" id=\"vjs-icon-tumblr\">\n      <path d=\"M1328 1329l80 237q-23 35-111 66t-177 32q-104 2-190.5-26T787 1564t-95-106-55.5-120-16.5-118V676H452V461q72-26 129-69.5t91-90 58-102 34-99T779 12q1-5 4.5-8.5T791 0h244v424h333v252h-334v518q0 30 6.5 56t22.5 52.5 49.5 41.5 81.5 14q78-2 134-29z\"></path>\n    </symbol>\n    <symbol viewBox=\"0 0 1792 1792\" id=\"vjs-icon-pinterest\">\n      <path d=\"M1664 896q0 209-103 385.5T1281.5 1561 896 1664q-111 0-218-32 59-93 78-164 9-34 54-211 20 39 73 67.5t114 28.5q121 0 216-68.5t147-188.5 52-270q0-114-59.5-214T1180 449t-255-63q-105 0-196 29t-154.5 77-109 110.5-67 129.5T377 866q0 104 40 183t117 111q30 12 38-20 2-7 8-31t8-30q6-23-11-43-51-61-51-151 0-151 104.5-259.5T904 517q151 0 235.5 82t84.5 213q0 170-68.5 289T980 1220q-61 0-98-43.5T859 1072q8-35 26.5-93.5t30-103T927 800q0-50-27-83t-77-33q-62 0-105 57t-43 142q0 73 25 122l-99 418q-17 70-13 177-206-91-333-281T128 896q0-209 103-385.5T510.5 231 896 128t385.5 103T1561 510.5 1664 896z\"></path>\n    </symbol>\n  </defs>\n</svg>";
40101
40102// /**
40103
40104// Determine the keycode for the 'back' key based on the platform
40105const backKeyCode = IS_TIZEN ? 10009 : IS_WEBOS ? 461 : 8;
40106const SpatialNavKeyCodes = {
40107  codes: {
40108    play: 415,
40109    pause: 19,
40110    ff: 417,
40111    rw: 412,
40112    back: backKeyCode
40113  },
40114  names: {
40115    415: 'play',
40116    19: 'pause',
40117    417: 'ff',
40118    412: 'rw',
40119    [backKeyCode]: 'back'
40120  },
40121  isEventKey(event, keyName) {
40122    keyName = keyName.toLowerCase();
40123    if (this.names[event.keyCode] && this.names[event.keyCode] === keyName) {
40124      return true;
40125    }
40126    return false;
40127  },
40128  getEventName(event) {
40129    if (this.names[event.keyCode]) {
40130      return this.names[event.keyCode];
40131    } else if (this.codes[event.code]) {
40132      const code = this.codes[event.code];
40133      return this.names[code];
40134    }
40135    return null;
40136  }
40137};
40138
40139/**
40140 * @file spatial-navigation.js
40141 */
40142
40143/** @import Component from './component' */
40144/** @import Player from './player' */
40145
40146// The number of seconds the `step*` functions move the timeline.
40147const STEP_SECONDS = 5;
40148
40149/**
40150 * Spatial Navigation in Video.js enhances user experience and accessibility on smartTV devices,
40151 * enabling seamless navigation through interactive elements within the player using remote control arrow keys.
40152 * This functionality allows users to effortlessly navigate through focusable components.
40153 *
vendor: 5,422 bytes, lines 40154-40317
40154 * @extends EventTarget
40155 */
40156class SpatialNavigation extends EventTarget$2 {
40157  /**
40158   * Constructs a SpatialNavigation instance with initial settings.
40159   * Sets up the player instance, and prepares the spatial navigation system.
40160   *
40161   * @class
40162   * @param {Player} player - The Video.js player instance to which the spatial navigation is attached.
40163   */
40164  constructor(player) {
40165    super();
40166    this.player_ = player;
40167    this.focusableComponents = [];
40168    this.isListening_ = false;
40169    this.isPaused_ = false;
40170    this.onKeyDown_ = this.onKeyDown_.bind(this);
40171    this.lastFocusedComponent_ = null;
40172  }
40173
40174  /**
40175   * Starts the spatial navigation by adding a keydown event listener to the video container.
40176   * This method ensures that the event listener is added only once.
40177   */
40178  start() {
40179    // If the listener is already active, exit early.
40180    if (this.isListening_) {
40181      return;
40182    }
40183
40184    // Add the event listener since the listener is not yet active.
40185    this.player_.on('keydown', this.onKeyDown_);
40186    this.player_.on('modalKeydown', this.onKeyDown_);
40187    // Listen for source change events
40188    this.player_.on('loadedmetadata', () => {
40189      this.focus(this.updateFocusableComponents()[0]);
40190    });
40191    this.player_.on('modalclose', () => {
40192      this.refocusComponent();
40193    });
40194    this.player_.on('focusin', this.handlePlayerFocus_.bind(this));
40195    this.player_.on('focusout', this.handlePlayerBlur_.bind(this));
40196    this.isListening_ = true;
40197    if (this.player_.errorDisplay) {
40198      this.player_.errorDisplay.on('aftermodalfill', () => {
40199        this.updateFocusableComponents();
40200        if (this.focusableComponents.length) {
40201          // The modal has focusable components:
40202
40203          if (this.focusableComponents.length > 1) {
40204            // The modal has close button + some additional buttons.
40205            // Focusing first additional button:
40206
40207            this.focusableComponents[1].focus();
40208          } else {
40209            // The modal has only close button,
40210            // Focusing it:
40211
40212            this.focusableComponents[0].focus();
40213          }
40214        }
40215      });
40216    }
40217  }
40218
40219  /**
40220   * Stops the spatial navigation by removing the keydown event listener from the video container.
40221   * Also sets the `isListening_` flag to false.
40222   */
40223  stop() {
40224    this.player_.off('keydown', this.onKeyDown_);
40225    this.isListening_ = false;
40226  }
40227
40228  /**
40229   * Responds to keydown events for spatial navigation and media control.
40230   *
40231   * Determines if spatial navigation or media control is active and handles key inputs accordingly.
40232   *
40233   * @param {KeyboardEvent} event - The keydown event to be handled.
40234   */
40235  onKeyDown_(event) {
40236    // Determine if the event is a custom modalKeydown event
40237    const actualEvent = event.originalEvent ? event.originalEvent : event;
40238    if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(actualEvent.key)) {
40239      // Handle directional navigation
40240      if (this.isPaused_) {
40241        return;
40242      }
40243      actualEvent.preventDefault();
40244
40245      // "ArrowLeft" => "left" etc
40246      const direction = actualEvent.key.substring(5).toLowerCase();
40247      this.move(direction);
40248    } else if (SpatialNavKeyCodes.isEventKey(actualEvent, 'play') || SpatialNavKeyCodes.isEventKey(actualEvent, 'pause') || SpatialNavKeyCodes.isEventKey(actualEvent, 'ff') || SpatialNavKeyCodes.isEventKey(actualEvent, 'rw')) {
40249      // Handle media actions
40250      actualEvent.preventDefault();
40251      const action = SpatialNavKeyCodes.getEventName(actualEvent);
40252      this.performMediaAction_(action);
40253    } else if (SpatialNavKeyCodes.isEventKey(actualEvent, 'Back') && event.target && typeof event.target.closeable === 'function' && event.target.closeable()) {
40254      actualEvent.preventDefault();
40255      event.target.close();
40256    }
40257  }
40258
40259  /**
40260   * Performs media control actions based on the given key input.
40261   *
40262   * Controls the playback and seeking functionalities of the media player.
40263   *
40264   * @param {string} key - The key representing the media action to be performed.
40265   *   Accepted keys: 'play', 'pause', 'ff' (fast-forward), 'rw' (rewind).
40266   */
40267  performMediaAction_(key) {
40268    if (this.player_) {
40269      switch (key) {
40270        case 'play':
40271          if (this.player_.paused()) {
40272            this.player_.play();
40273          }
40274          break;
40275        case 'pause':
40276          if (!this.player_.paused()) {
40277            this.player_.pause();
40278          }
40279          break;
40280        case 'ff':
40281          this.userSeek_(this.player_.currentTime() + STEP_SECONDS);
40282          break;
40283        case 'rw':
40284          this.userSeek_(this.player_.currentTime() - STEP_SECONDS);
40285          break;
40286      }
40287    }
40288  }
40289
40290  /**
40291   * Prevent liveThreshold from causing seeks to seem like they
40292   * are not happening from a user perspective.
40293   *
40294   * @param {number} ct
40295   *        current time to seek to
40296   */
40297  userSeek_(ct) {
40298    if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
40299      this.player_.liveTracker.nextSeekedFromUser();
40300    }
40301    this.player_.currentTime(ct);
40302  }
40303
40304  /**
40305   * Pauses the spatial navigation functionality.
40306   * This method sets a flag that can be used to temporarily disable the navigation logic.
40307   */
40308  pause() {
40309    this.isPaused_ = true;
40310  }
40311
40312  /**
40313   * Resumes the spatial navigation functionality if it has been paused.
40314   * This method resets the pause flag, re-enabling the navigation logic.
40315   */
40316  resume() {
40317    this.isPaused_ = false;
vendor: 12,359 bytes, lines 40318-40642
40318  }
40319
40320  /**
40321   * Handles Player Blur.
40322   *
40323   * @param {string|Event|Object} event
40324   *        The name of the event, an `Event`, or an object with a key of type set to
40325   *        an event name.
40326   *
40327   * Calls for handling of the Player Blur if:
40328   * *The next focused element is not a child of current focused element &
40329   * The next focused element is not a child of the Player.
40330   * *There is no next focused element
40331   */
40332  handlePlayerBlur_(event) {
40333    const nextFocusedElement = event.relatedTarget;
40334    let isChildrenOfPlayer = null;
40335    const currentComponent = this.getCurrentComponent(event.target);
40336    if (nextFocusedElement) {
40337      isChildrenOfPlayer = Boolean(nextFocusedElement.closest('.video-js'));
40338
40339      // If nextFocusedElement is the 'TextTrackSettings' component
40340      if (nextFocusedElement.classList.contains('vjs-text-track-settings') && !this.isPaused_) {
40341        this.searchForTrackSelect_();
40342      }
40343    }
40344    if (!event.currentTarget.contains(event.relatedTarget) && !isChildrenOfPlayer || !nextFocusedElement) {
40345      if (currentComponent && currentComponent.name() === 'CloseButton') {
40346        this.refocusComponent();
40347      } else {
40348        this.pause();
40349        if (currentComponent && currentComponent.el()) {
40350          // Store last focused component
40351          this.lastFocusedComponent_ = currentComponent;
40352        }
40353      }
40354    }
40355  }
40356
40357  /**
40358   * Handles the Player focus event.
40359   *
40360   * Calls for handling of the Player Focus if current element is focusable.
40361   */
40362  handlePlayerFocus_() {
40363    if (this.getCurrentComponent() && this.getCurrentComponent().getIsFocusable()) {
40364      this.resume();
40365    }
40366  }
40367
40368  /**
40369   * Gets a set of focusable components.
40370   *
40371   * @return {Array}
40372   *         Returns an array of focusable components.
40373   */
40374  updateFocusableComponents() {
40375    const player = this.player_;
40376    const focusableComponents = [];
40377
40378    /**
40379     * Searches for children candidates.
40380     *
40381     * Pushes Components to array of 'focusableComponents'.
40382     * Calls itself if there is children elements inside iterated component.
40383     *
40384     * @param {Array} componentsArray - The array of components to search for focusable children.
40385     */
40386    function searchForChildrenCandidates(componentsArray) {
40387      for (const i of componentsArray) {
40388        if (i.hasOwnProperty('el_') && i.getIsFocusable() && i.getIsAvailableToBeFocused(i.el())) {
40389          focusableComponents.push(i);
40390        }
40391        if (i.hasOwnProperty('children_') && i.children_.length > 0) {
40392          searchForChildrenCandidates(i.children_);
40393        }
40394      }
40395    }
40396
40397    // Iterate inside all children components of the player.
40398    player.children_.forEach(value => {
40399      if (value.hasOwnProperty('el_')) {
40400        // If component has required functions 'getIsFocusable' & 'getIsAvailableToBeFocused', is focusable & available to be focused.
40401        if (value.getIsFocusable && value.getIsAvailableToBeFocused && value.getIsFocusable() && value.getIsAvailableToBeFocused(value.el())) {
40402          focusableComponents.push(value);
40403          return;
40404          // If component has posible children components as candidates.
40405        } else if (value.hasOwnProperty('children_') && value.children_.length > 0) {
40406          searchForChildrenCandidates(value.children_);
40407          // If component has posible item components as candidates.
40408        } else if (value.hasOwnProperty('items') && value.items.length > 0) {
40409          searchForChildrenCandidates(value.items);
40410          // If there is a suitable child element within the component's DOM element.
40411        } else if (this.findSuitableDOMChild(value)) {
40412          focusableComponents.push(value);
40413        }
40414      }
40415
40416      // TODO - Refactor the following logic after refactor of videojs-errors elements to be components is done.
40417      if (value.name_ === 'ErrorDisplay' && value.opened_) {
40418        const buttonContainer = value.el_.querySelector('.vjs-errors-ok-button-container');
40419        if (buttonContainer) {
40420          const modalButtons = buttonContainer.querySelectorAll('button');
40421          modalButtons.forEach((element, index) => {
40422            // Add elements as objects to be handled by the spatial navigation
40423            focusableComponents.push({
40424              name: () => {
40425                return 'ModalButton' + (index + 1);
40426              },
40427              el: () => element,
40428              getPositions: () => {
40429                const rect = element.getBoundingClientRect();
40430
40431                // Creating objects that mirror DOMRectReadOnly for boundingClientRect and center
40432                const boundingClientRect = {
40433                  x: rect.x,
40434                  y: rect.y,
40435                  width: rect.width,
40436                  height: rect.height,
40437                  top: rect.top,
40438                  right: rect.right,
40439                  bottom: rect.bottom,
40440                  left: rect.left
40441                };
40442
40443                // Calculating the center position
40444                const center = {
40445                  x: rect.left + rect.width / 2,
40446                  y: rect.top + rect.height / 2,
40447                  width: 0,
40448                  height: 0,
40449                  top: rect.top + rect.height / 2,
40450                  right: rect.left + rect.width / 2,
40451                  bottom: rect.top + rect.height / 2,
40452                  left: rect.left + rect.width / 2
40453                };
40454                return {
40455                  boundingClientRect,
40456                  center
40457                };
40458              },
40459              // Asume that the following are always focusable
40460              getIsAvailableToBeFocused: () => true,
40461              getIsFocusable: el => true,
40462              focus: () => element.focus()
40463            });
40464          });
40465        }
40466      }
40467    });
40468    this.focusableComponents = focusableComponents;
40469    return this.focusableComponents;
40470  }
40471
40472  /**
40473   * Finds a suitable child element within the provided component's DOM element.
40474   *
40475   * @param {Object} component - The component containing the DOM element to search within.
40476   * @return {HTMLElement|null} Returns the suitable child element if found, or null if not found.
40477   */
40478  findSuitableDOMChild(component) {
40479    /**
40480     * Recursively searches for a suitable child node that can be focused within a given component.
40481     * It first checks if the provided node itself can be focused according to the component's
40482     * `getIsFocusable` and `getIsAvailableToBeFocused` methods. If not, it recursively searches
40483     * through the node's children to find a suitable child node that meets the focusability criteria.
40484     *
40485     * @param {HTMLElement} node - The DOM node to start the search from.
40486     * @return {HTMLElement|null} The first child node that is focusable and available to be focused,
40487     * or `null` if no suitable child is found.
40488     */
40489    function searchForSuitableChild(node) {
40490      if (component.getIsFocusable(node) && component.getIsAvailableToBeFocused(node)) {
40491        return node;
40492      }
40493      for (let i = 0; i < node.children.length; i++) {
40494        const child = node.children[i];
40495        const suitableChild = searchForSuitableChild(child);
40496        if (suitableChild) {
40497          return suitableChild;
40498        }
40499      }
40500      return null;
40501    }
40502    if (component.el()) {
40503      return searchForSuitableChild(component.el());
40504    }
40505    return null;
40506  }
40507
40508  /**
40509   * Gets the currently focused component from the list of focusable components.
40510   * If a target element is provided, it uses that element to find the corresponding
40511   * component. If no target is provided, it defaults to using the document's currently
40512   * active element.
40513   *
40514   * @param {HTMLElement} [target] - The DOM element to check against the focusable components.
40515   *                                 If not provided, `document.activeElement` is used.
40516   * @return {Component|null} - Returns the focused component if found among the focusable components,
40517   *                            otherwise returns null if no matching component is found.
40518   */
40519  getCurrentComponent(target) {
40520    this.updateFocusableComponents();
40521    // eslint-disable-next-line
40522    const curComp = target || document.activeElement;
40523    if (this.focusableComponents.length) {
40524      for (const i of this.focusableComponents) {
40525        // If component Node is equal to the current active element.
40526        if (i.el() === curComp) {
40527          return i;
40528        }
40529      }
40530    }
40531  }
40532
40533  /**
40534   * Adds a component to the array of focusable components.
40535   *
40536   * @param {Component} component
40537   *        The `Component` to be added.
40538   */
40539  add(component) {
40540    const focusableComponents = [...this.focusableComponents];
40541    if (component.hasOwnProperty('el_') && component.getIsFocusable() && component.getIsAvailableToBeFocused(component.el())) {
40542      focusableComponents.push(component);
40543    }
40544    this.focusableComponents = focusableComponents;
40545    // Trigger the notification manually
40546    this.trigger({
40547      type: 'focusableComponentsChanged',
40548      focusableComponents: this.focusableComponents
40549    });
40550  }
40551
40552  /**
40553   * Removes component from the array of focusable components.
40554   *
40555   * @param {Component} component - The component to be removed from the focusable components array.
40556   */
40557  remove(component) {
40558    for (let i = 0; i < this.focusableComponents.length; i++) {
40559      if (this.focusableComponents[i].name() === component.name()) {
40560        this.focusableComponents.splice(i, 1);
40561        // Trigger the notification manually
40562        this.trigger({
40563          type: 'focusableComponentsChanged',
40564          focusableComponents: this.focusableComponents
40565        });
40566        return;
40567      }
40568    }
40569  }
40570
40571  /**
40572   * Clears array of focusable components.
40573   */
40574  clear() {
40575    // Check if the array is already empty to avoid unnecessary event triggering
40576    if (this.focusableComponents.length > 0) {
40577      // Clear the array
40578      this.focusableComponents = [];
40579
40580      // Trigger the notification manually
40581      this.trigger({
40582        type: 'focusableComponentsChanged',
40583        focusableComponents: this.focusableComponents
40584      });
40585    }
40586  }
40587
40588  /**
40589   * Navigates to the next focusable component based on the specified direction.
40590   *
40591   * @param {string} direction 'up', 'down', 'left', 'right'
40592   */
40593  move(direction) {
40594    const currentFocusedComponent = this.getCurrentComponent();
40595    if (!currentFocusedComponent) {
40596      return;
40597    }
40598    const currentPositions = currentFocusedComponent.getPositions();
40599    const candidates = this.focusableComponents.filter(component => component !== currentFocusedComponent && this.isInDirection_(currentPositions.boundingClientRect, component.getPositions().boundingClientRect, direction));
40600    const bestCandidate = this.findBestCandidate_(currentPositions.center, candidates, direction);
40601    if (bestCandidate) {
40602      this.focus(bestCandidate);
40603    } else {
40604      this.trigger({
40605        type: 'endOfFocusableComponents',
40606        direction,
40607        focusedComponent: currentFocusedComponent
40608      });
40609    }
40610  }
40611
40612  /**
40613   * Finds the best candidate on the current center position,
40614   * the list of candidates, and the specified navigation direction.
40615   *
40616   * @param {Object} currentCenter The center position of the current focused component element.
40617   * @param {Array} candidates An array of candidate components to receive focus.
40618   * @param {string} direction The direction of navigation ('up', 'down', 'left', 'right').
40619   * @return {Object|null} The component that is the best candidate for receiving focus.
40620   */
40621  findBestCandidate_(currentCenter, candidates, direction) {
40622    let minDistance = Infinity;
40623    let bestCandidate = null;
40624    for (const candidate of candidates) {
40625      const candidateCenter = candidate.getPositions().center;
40626      const distance = this.calculateDistance_(currentCenter, candidateCenter, direction);
40627      if (distance < minDistance) {
40628        minDistance = distance;
40629        bestCandidate = candidate;
40630      }
40631    }
40632    return bestCandidate;
40633  }
40634
40635  /**
40636   * Determines if a target rectangle is in the specified navigation direction
40637   * relative to a source rectangle.
40638   *
40639   * @param {Object} srcRect The bounding rectangle of the source element.
40640   * @param {Object} targetRect The bounding rectangle of the target element.
40641   * @param {string} direction The navigation direction ('up', 'down', 'left', 'right').
40642   * @return {boolean}
vendor: 19,926 bytes, lines 40642-41315
40642 True if the target is in the specified direction relative to the source.
40643   */
40644  isInDirection_(srcRect, targetRect, direction) {
40645    switch (direction) {
40646      case 'right':
40647        return targetRect.left >= srcRect.right;
40648      case 'left':
40649        return targetRect.right <= srcRect.left;
40650      case 'down':
40651        return targetRect.top >= srcRect.bottom;
40652      case 'up':
40653        return targetRect.bottom <= srcRect.top;
40654      default:
40655        return false;
40656    }
40657  }
40658
40659  /**
40660   * Focus the last focused component saved before blur on player.
40661   */
40662  refocusComponent() {
40663    if (this.lastFocusedComponent_) {
40664      // If user is not active, set it to active.
40665      if (!this.player_.userActive()) {
40666        this.player_.userActive(true);
40667      }
40668      this.updateFocusableComponents();
40669
40670      // Search inside array of 'focusableComponents' for a match of name of
40671      // the last focused component.
40672      for (let i = 0; i < this.focusableComponents.length; i++) {
40673        if (this.focusableComponents[i].name() === this.lastFocusedComponent_.name()) {
40674          this.focus(this.focusableComponents[i]);
40675          return;
40676        }
40677      }
40678    } else {
40679      this.focus(this.updateFocusableComponents()[0]);
40680    }
40681  }
40682
40683  /**
40684   * Focuses on a given component.
40685   * If the component is available to be focused, it focuses on the component.
40686   * If not, it attempts to find a suitable DOM child within the component and focuses on it.
40687   *
40688   * @param {Component} component - The component to be focused.
40689   */
40690  focus(component) {
40691    if (typeof component !== 'object') {
40692      return;
40693    }
40694    if (component.getIsAvailableToBeFocused(component.el())) {
40695      component.focus();
40696    } else if (this.findSuitableDOMChild(component)) {
40697      this.findSuitableDOMChild(component).focus();
40698    }
40699  }
40700
40701  /**
40702   * Calculates the distance between two points, adjusting the calculation based on
40703   * the specified navigation direction.
40704   *
40705   * @param {Object} center1 The center point of the first element.
40706   * @param {Object} center2 The center point of the second element.
40707   * @param {string} direction The direction of navigation ('up', 'down', 'left', 'right').
40708   * @return {number} The calculated distance between the two centers.
40709   */
40710  calculateDistance_(center1, center2, direction) {
40711    const dx = Math.abs(center1.x - center2.x);
40712    const dy = Math.abs(center1.y - center2.y);
40713    let distance;
40714    switch (direction) {
40715      case 'right':
40716      case 'left':
40717        // Higher weight for vertical distance in horizontal navigation.
40718        distance = dx + dy * 100;
40719        break;
40720      case 'up':
40721        // Strongly prioritize vertical proximity for UP navigation.
40722        // Adjust the weight to ensure that elements directly above are favored.
40723        distance = dy * 2 + dx * 0.5;
40724        break;
40725      case 'down':
40726        // More balanced weight for vertical and horizontal distances.
40727        // Adjust the weights here to find the best balance.
40728        distance = dy * 5 + dx;
40729        break;
40730      default:
40731        distance = dx + dy;
40732    }
40733    return distance;
40734  }
40735
40736  /**
40737   * This gets called by 'handlePlayerBlur_' if 'spatialNavigation' is enabled.
40738   * Searches for the first 'TextTrackSelect' inside of modal to focus.
40739   *
40740   * @private
40741   */
40742  searchForTrackSelect_() {
40743    const spatialNavigation = this;
40744    for (const component of spatialNavigation.updateFocusableComponents()) {
40745      if (component.constructor.name === 'TextTrackSelect') {
40746        spatialNavigation.focus(component);
40747        break;
40748      }
40749    }
40750  }
40751}
40752
40753/**
40754 * @file loader.js
40755 */
40756
40757/** @import Player from '../player' */
40758
40759/**
40760 * The `MediaLoader` is the `Component` that decides which playback technology to load
40761 * when a player is initialized.
40762 *
40763 * @extends Component
40764 */
40765class MediaLoader extends Component$1 {
40766  /**
40767   * Create an instance of this class.
40768   *
40769   * @param {Player} player
40770   *        The `Player` that this class should attach to.
40771   *
40772   * @param {Object} [options]
40773   *        The key/value store of player options.
40774   *
40775   * @param {Function} [ready]
40776   *        The function that is run when this component is ready.
40777   */
40778  constructor(player, options, ready) {
40779    // MediaLoader has no element
40780    const options_ = merge$1({
40781      createEl: false
40782    }, options);
40783    super(player, options_, ready);
40784
40785    // If there are no sources when the player is initialized,
40786    // load the first supported playback technology.
40787
40788    if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
40789      for (let i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
40790        const techName = toTitleCase$1(j[i]);
40791        let tech = Tech.getTech(techName);
40792
40793        // Support old behavior of techs being registered as components.
40794        // Remove once that deprecated behavior is removed.
40795        if (!techName) {
40796          tech = Component$1.getComponent(techName);
40797        }
40798
40799        // Check if the browser supports this technology
40800        if (tech && tech.isSupported()) {
40801          player.loadTech_(techName);
40802          break;
40803        }
40804      }
40805    } else {
40806      // Loop through playback technologies (e.g. HTML5) and check for support.
40807      // Then load the best source.
40808      // A few assumptions here:
40809      //   All playback technologies respect preload false.
40810      player.src(options.playerOptions.sources);
40811    }
40812  }
40813}
40814Component$1.registerComponent('MediaLoader', MediaLoader);
40815
40816/**
40817 * @file clickable-component.js
40818 */
40819
40820/** @import Player from './player' */
40821
40822/**
40823 * Component which is clickable or keyboard actionable, but is not a
40824 * native HTML button.
40825 *
40826 * @extends Component
40827 */
40828class ClickableComponent extends Component$1 {
40829  /**
40830   * Creates an instance of this class.
40831   *
40832   * @param  {Player} player
40833   *         The `Player` that this class should be attached to.
40834   *
40835   * @param  {Object} [options]
40836   *         The key/value store of component options.
40837   *
40838   * @param  {function} [options.clickHandler]
40839   *         The function to call when the button is clicked / activated
40840   *
40841   * @param  {string} [options.controlText]
40842   *         The text to set on the button
40843   *
40844   * @param  {string} [options.className]
40845   *         A class or space separated list of classes to add the component
40846   *
40847   */
40848  constructor(player, options) {
40849    super(player, options);
40850    if (this.options_.controlText) {
40851      this.controlText(this.options_.controlText);
40852    }
40853    this.handleMouseOver_ = e => this.handleMouseOver(e);
40854    this.handleMouseOut_ = e => this.handleMouseOut(e);
40855    this.handleClick_ = e => this.handleClick(e);
40856    this.handleKeyDown_ = e => this.handleKeyDown(e);
40857    this.emitTapEvents();
40858    this.enable();
40859  }
40860
40861  /**
40862   * Create the `ClickableComponent`s DOM element.
40863   *
40864   * @param {string} [tag=div]
40865   *        The element's node type.
40866   *
40867   * @param {Object} [props={}]
40868   *        An object of properties that should be set on the element.
40869   *
40870   * @param {Object} [attributes={}]
40871   *        An object of attributes that should be set on the element.
40872   *
40873   * @return {Element}
40874   *         The element that gets created.
40875   */
40876  createEl(tag = 'div', props = {}, attributes = {}) {
40877    props = Object.assign({
40878      className: this.buildCSSClass(),
40879      tabIndex: 0
40880    }, props);
40881    if (tag === 'button') {
40882      log$1.error(`Creating a ClickableComponent with an HTML element of ${tag} is not supported; use a Button instead.`);
40883    }
40884
40885    // Add ARIA attributes for clickable element which is not a native HTML button
40886    attributes = Object.assign({
40887      role: 'button'
40888    }, attributes);
40889    this.tabIndex_ = props.tabIndex;
40890    const el = createEl(tag, props, attributes);
40891    if (!this.player_.options_.experimentalSvgIcons) {
40892      el.appendChild(createEl('span', {
40893        className: 'vjs-icon-placeholder'
40894      }, {
40895        'aria-hidden': true
40896      }));
40897    }
40898    this.createControlTextEl(el);
40899    return el;
40900  }
40901  dispose() {
40902    // remove controlTextEl_ on dispose
40903    this.controlTextEl_ = null;
40904    super.dispose();
40905  }
40906
40907  /**
40908   * Create a control text element on this `ClickableComponent`
40909   *
40910   * @param {Element} [el]
40911   *        Parent element for the control text.
40912   *
40913   * @return {Element}
40914   *         The control text element that gets created.
40915   */
40916  createControlTextEl(el) {
40917    this.controlTextEl_ = createEl('span', {
40918      className: 'vjs-control-text'
40919    }, {
40920      // let the screen reader user know that the text of the element may change
40921      'aria-live': 'polite'
40922    });
40923    if (el) {
40924      el.appendChild(this.controlTextEl_);
40925    }
40926    this.controlText(this.controlText_, el);
40927    return this.controlTextEl_;
40928  }
40929
40930  /**
40931   * Get or set the localize text to use for the controls on the `ClickableComponent`.
40932   *
40933   * @param {string} [text]
40934   *        Control text for element.
40935   *
40936   * @param {Element} [el=this.el()]
40937   *        Element to set the title on.
40938   *
40939   * @return {string}
40940   *         - The control text when getting
40941   */
40942  controlText(text, el = this.el()) {
40943    if (text === undefined) {
40944      return this.controlText_ || 'Need Text';
40945    }
40946    const localizedText = this.localize(text);
40947
40948    /** @protected */
40949    this.controlText_ = text;
40950    textContent(this.controlTextEl_, localizedText);
40951    if (!this.nonIconControl && !this.player_.options_.noUITitleAttributes) {
40952      // Set title attribute if only an icon is shown
40953      el.setAttribute('title', localizedText);
40954    }
40955  }
40956
40957  /**
40958   * Builds the default DOM `className`.
40959   *
40960   * @return {string}
40961   *         The DOM `className` for this object.
40962   */
40963  buildCSSClass() {
40964    return `vjs-control vjs-button ${super.buildCSSClass()}`;
40965  }
40966
40967  /**
40968   * Enable this `ClickableComponent`
40969   */
40970  enable() {
40971    if (!this.enabled_) {
40972      this.enabled_ = true;
40973      this.removeClass('vjs-disabled');
40974      this.el_.setAttribute('aria-disabled', 'false');
40975      if (typeof this.tabIndex_ !== 'undefined') {
40976        this.el_.setAttribute('tabIndex', this.tabIndex_);
40977      }
40978      this.on(['tap', 'click'], this.handleClick_);
40979      this.on('keydown', this.handleKeyDown_);
40980    }
40981  }
40982
40983  /**
40984   * Disable this `ClickableComponent`
40985   */
40986  disable() {
40987    this.enabled_ = false;
40988    this.addClass('vjs-disabled');
40989    this.el_.setAttribute('aria-disabled', 'true');
40990    if (typeof this.tabIndex_ !== 'undefined') {
40991      this.el_.removeAttribute('tabIndex');
40992    }
40993    this.off('mouseover', this.handleMouseOver_);
40994    this.off('mouseout', this.handleMouseOut_);
40995    this.off(['tap', 'click'], this.handleClick_);
40996    this.off('keydown', this.handleKeyDown_);
40997  }
40998
40999  /**
41000   * Handles language change in ClickableComponent for the player in components
41001   *
41002   *
41003   */
41004  handleLanguagechange() {
41005    this.controlText(this.controlText_);
41006  }
41007
41008  /**
41009   * Event handler that is called when a `ClickableComponent` receives a
41010   * `click` or `tap` event.
41011   *
41012   * @param {Event} event
41013   *        The `tap` or `click` event that caused this function to be called.
41014   *
41015   * @listens tap
41016   * @listens click
41017   * @abstract
41018   */
41019  handleClick(event) {
41020    if (this.options_.clickHandler) {
41021      this.options_.clickHandler.call(this, arguments);
41022    }
41023  }
41024
41025  /**
41026   * Event handler that is called when a `ClickableComponent` receives a
41027   * `keydown` event.
41028   *
41029   * By default, if the key is Space or Enter, it will trigger a `click` event.
41030   *
41031   * @param {KeyboardEvent} event
41032   *        The `keydown` event that caused this function to be called.
41033   *
41034   * @listens keydown
41035   */
41036  handleKeyDown(event) {
41037    // Support Space or Enter key operation to fire a click event. Also,
41038    // prevent the event from propagating through the DOM and triggering
41039    // Player hotkeys.
41040    if (event.key === ' ' || event.key === 'Enter') {
41041      event.preventDefault();
41042      event.stopPropagation();
41043      this.trigger('click');
41044    } else {
41045      // Pass keypress handling up for unsupported keys
41046      super.handleKeyDown(event);
41047    }
41048  }
41049}
41050Component$1.registerComponent('ClickableComponent', ClickableComponent);
41051
41052/**
41053 * @file poster-image.js
41054 */
41055
41056/** @import Player from './player' */
41057
41058/**
41059 * A `ClickableComponent` that handles showing the poster image for the player.
41060 *
41061 * @extends ClickableComponent
41062 */
41063class PosterImage extends ClickableComponent {
41064  /**
41065   * Create an instance of this class.
41066   *
41067   * @param {Player} player
41068   *        The `Player` that this class should attach to.
41069   *
41070   * @param {Object} [options]
41071   *        The key/value store of player options.
41072   */
41073  constructor(player, options) {
41074    super(player, options);
41075    const {
41076      mainContent
41077    } = options && options.playerOptions || {};
41078    this.isMainContent = mainContent;
41079    this.update();
41080    this.update_ = e => this.update(e);
41081    player.on('posterchange', this.update_);
41082  }
41083
41084  /**
41085   * Clean up and dispose of the `PosterImage`.
41086   */
41087  dispose() {
41088    this.player().off('posterchange', this.update_);
41089    super.dispose();
41090  }
41091
41092  /**
41093   * Create the `PosterImage`s DOM element.
41094   *
41095   * @return {Element}
41096   *         The element that gets created.
41097   */
41098  createEl() {
41099    // The el is an empty div to keep position in the DOM
41100    // A picture and img el will be inserted when a source is set
41101    return createEl('div', {
41102      className: 'vjs-poster'
41103    });
41104  }
41105
41106  /**
41107   * Get or set the `PosterImage`'s crossOrigin option.
41108   *
41109   * @param {string|null} [value]
41110   *        The value to set the crossOrigin to. If an argument is
41111   *        given, must be one of `'anonymous'` or `'use-credentials'`, or 'null'.
41112   *
41113   * @return {string|null}
41114   *         - The current crossOrigin value of the `Player` when getting.
41115   *         - undefined when setting
41116   */
41117  crossOrigin(value) {
41118    // `null` can be set to unset a value
41119    if (typeof value === 'undefined') {
41120      if (this.$('img')) {
41121        // If the poster's element exists, give its value
41122        return this.$('img').crossOrigin;
41123      } else if (this.player_.tech_ && this.player_.tech_.isReady_) {
41124        // If not but the tech is ready, query the tech
41125        return this.player_.crossOrigin();
41126      }
41127      // Otherwise check options as the  poster is usually set before the state of crossorigin
41128      // can be retrieved by the getter
41129      return this.player_.options_.crossOrigin || this.player_.options_.crossorigin || null;
41130    }
41131    if (value !== null && value !== 'anonymous' && value !== 'use-credentials') {
41132      this.player_.log.warn(`crossOrigin must be null,  "anonymous" or "use-credentials", given "${value}"`);
41133      return;
41134    }
41135    if (this.$('img')) {
41136      this.$('img').crossOrigin = value;
41137    }
41138    return;
41139  }
41140
41141  /**
41142   * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
41143   *
41144   * @listens Player#posterchange
41145   *
41146   * @param {Event} [event]
41147   *        The `Player#posterchange` event that triggered this function.
41148   */
41149  update(event) {
41150    const url = this.player().poster();
41151    this.setSrc(url);
41152
41153    // If there's no poster source we should display:none on this component
41154    // so it's not still clickable or right-clickable
41155    if (url) {
41156      this.show();
41157    } else {
41158      this.hide();
41159    }
41160  }
41161
41162  /**
41163   * Set the source of the `PosterImage` depending on the display method. (Re)creates
41164   * the inner picture and img elementss when needed.
41165   *
41166   * @param {string} [url]
41167   *        The URL to the source for the `PosterImage`. If not specified or falsy,
41168   *        any source and ant inner picture/img are removed.
41169   */
41170  setSrc(url) {
41171    if (!url) {
41172      this.el_.textContent = '';
41173      return;
41174    }
41175    if (!this.$('img')) {
41176      this.el_.appendChild(createEl('picture', {
41177        className: 'vjs-poster',
41178        // Don't want poster to be tabbable.
41179        tabIndex: -1
41180      }, {}, createEl('img', {
41181        loading: this.isMainContent ? 'eager' : 'lazy',
41182        fetchPriority: this.isMainContent ? 'high' : 'auto',
41183        crossOrigin: this.crossOrigin()
41184      }, {
41185        alt: ''
41186      })));
41187    }
41188    this.$('img').src = url;
41189  }
41190
41191  /**
41192   * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
41193   * {@link ClickableComponent#handleClick} for instances where this will be triggered.
41194   *
41195   * @listens tap
41196   * @listens click
41197   * @listens keydown
41198   *
41199   * @param {Event} event
41200   +        The `click`, `tap` or `keydown` event that caused this function to be called.
41201   */
41202  handleClick(event) {
41203    // We don't want a click to trigger playback when controls are disabled
41204    if (!this.player_.controls()) {
41205      return;
41206    }
41207
41208    // On Microsoft Edge, moving focus to the <video> (tech) element as playback
41209    // starts prevents a protected (DRM/EME) video surface from being presented:
41210    // audio plays but the frame stays black until a later repaint or focus change.
41211    // Focus the control-bar play toggle instead of the tech on Edge. See
41212    // https://github.com/videojs/video.js/issues/6270.
41213    const cb = this.player_.getChild('controlBar');
41214    const playToggle = cb && cb.getChild('playToggle');
41215    if (IS_EDGE) {
41216      (playToggle || this.player_).focus();
41217    } else if (this.player_.tech(true)) {
41218      this.player_.tech(true).focus();
41219    }
41220    if (this.player_.paused()) {
41221      silencePromise(this.player_.play());
41222    } else {
41223      this.player_.pause();
41224    }
41225  }
41226}
41227
41228/**
41229 * Get or set the `PosterImage`'s crossorigin option. For the HTML5 player, this
41230 * sets the `crossOrigin` property on the `<img>` tag to control the CORS
41231 * behavior.
41232 *
41233 * @param {string|null} [value]
41234 *        The value to set the `PosterImages`'s crossorigin to. If an argument is
41235 *        given, must be one of `anonymous` or `use-credentials`.
41236 *
41237 * @return {string|null|undefined}
41238 *         - The current crossorigin value of the `Player` when getting.
41239 *         - undefined when setting
41240 */
41241PosterImage.prototype.crossorigin = PosterImage.prototype.crossOrigin;
41242Component$1.registerComponent('PosterImage', PosterImage);
41243
41244/**
41245 * @file text-track-display.js
41246 */
41247
41248/** @import Player from '../player' */
41249
41250const darkGray = '#222';
41251const lightGray = '#ccc';
41252const fontMap = {
41253  monospace: 'monospace',
41254  sansSerif: 'sans-serif',
41255  serif: 'serif',
41256  monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
41257  monospaceSerif: '"Courier New", monospace',
41258  proportionalSansSerif: 'sans-serif',
41259  proportionalSerif: 'serif',
41260  casual: '"Comic Sans MS", Impact, fantasy',
41261  script: '"Monotype Corsiva", cursive',
41262  smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
41263};
41264
41265/**
41266 * Construct an rgba color from a given hex color code.
41267 *
41268 * @param {number} color
41269 *        Hex number for color, like #f0e or #f604e2.
41270 *
41271 * @param {number} opacity
41272 *        Value for opacity, 0.0 - 1.0.
41273 *
41274 * @return {string}
41275 *         The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
41276 */
41277function constructColor(color, opacity) {
41278  let hex;
41279  if (color.length === 4) {
41280    // color looks like "#f0e"
41281    hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
41282  } else if (color.length === 7) {
41283    // color looks like "#f604e2"
41284    hex = color.slice(1);
41285  } else {
41286    throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
41287  }
41288  return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
41289}
41290
41291/**
41292 * Try to update the style of a DOM element. Some style changes will throw an error,
41293 * particularly in IE8. Those should be noops.
41294 *
41295 * @param {Element} el
41296 *        The DOM element to be styled.
41297 *
41298 * @param {string} style
41299 *        The CSS property on the element that should be styled.
41300 *
41301 * @param {string} rule
41302 *        The style rule that should be applied to the property.
41303 *
41304 * @private
41305 */
41306function tryUpdateStyle(el, style, rule) {
41307  try {
41308    el.style[style] = rule;
41309  } catch (e) {
41310    // Satisfies linter.
41311    return;
41312  }
41313}
41314
41315/**
vendor: 5,193 bytes, lines 41316-41469
41316 * Converts the CSS top/right/bottom/left property numeric value to string in pixels.
41317 *
41318 * @param {number} position
41319 *        The CSS top/right/bottom/left property value.
41320 *
41321 * @return {string}
41322 *          The CSS property value that was created, like '10px'.
41323 *
41324 * @private
41325 */
41326function getCSSPositionValue(position) {
41327  return position ? `${position}px` : '';
41328}
41329
41330/**
41331 * The component for displaying text track cues.
41332 *
41333 * @extends Component
41334 */
41335class TextTrackDisplay extends Component$1 {
41336  /**
41337   * Creates an instance of this class.
41338   *
41339   * @param {Player} player
41340   *        The `Player` that this class should be attached to.
41341   *
41342   * @param {Object} [options]
41343   *        The key/value store of player options.
41344   *
41345   * @param {Function} [ready]
41346   *        The function to call when `TextTrackDisplay` is ready.
41347   */
41348  constructor(player, options, ready) {
41349    super(player, options, ready);
41350    const updateDisplayTextHandler = e => this.updateDisplay(e);
41351    const updateDisplayHandler = e => {
41352      this.updateDisplayOverlay();
41353      this.updateDisplay(e);
41354    };
41355    player.on('loadstart', e => this.toggleDisplay(e));
41356    player.on('useractive', updateDisplayTextHandler);
41357    player.on('userinactive', updateDisplayTextHandler);
41358    player.on('texttrackchange', updateDisplayTextHandler);
41359    player.on('loadedmetadata', e => {
41360      this.updateDisplayOverlay();
41361      this.preselectTrack(e);
41362    });
41363
41364    // This used to be called during player init, but was causing an error
41365    // if a track should show by default and the display hadn't loaded yet.
41366    // Should probably be moved to an external track loader when we support
41367    // tracks that don't need a display.
41368    player.ready(bind_(this, function () {
41369      if (player.tech_ && player.tech_.featuresNativeTextTracks) {
41370        this.hide();
41371        return;
41372      }
41373      player.on('fullscreenchange', updateDisplayHandler);
41374      player.on('playerresize', updateDisplayHandler);
41375      const screenOrientation = window__default["default"].screen.orientation || window__default["default"];
41376      const changeOrientationEvent = window__default["default"].screen.orientation ? 'change' : 'orientationchange';
41377      screenOrientation.addEventListener(changeOrientationEvent, updateDisplayHandler);
41378      player.on('dispose', () => screenOrientation.removeEventListener(changeOrientationEvent, updateDisplayHandler));
41379      const tracks = this.options_.playerOptions.tracks || [];
41380      for (let i = 0; i < tracks.length; i++) {
41381        this.player_.addRemoteTextTrack(tracks[i], true);
41382      }
41383      this.preselectTrack();
41384    }));
41385  }
41386
41387  /**
41388  * Preselect a track following this precedence:
41389  * - matches the previously selected {@link TextTrack}'s language and kind
41390  * - matches the previously selected {@link TextTrack}'s language only
41391  * - is the first default captions track
41392  * - is the first default descriptions track
41393  *
41394  * @listens Player#loadstart
41395  */
41396  preselectTrack() {
41397    const modes = {
41398      captions: 1,
41399      subtitles: 1
41400    };
41401    const trackList = this.player_.textTracks();
41402    const userPref = this.player_.cache_.selectedLanguage;
41403    let firstDesc;
41404    let firstCaptions;
41405    let preferredTrack;
41406    for (let i = 0; i < trackList.length; i++) {
41407      const track = trackList[i];
41408      if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) {
41409        // Always choose the track that matches both language and kind
41410        if (track.kind === userPref.kind) {
41411          preferredTrack = track;
41412          // or choose the first track that matches language
41413        } else if (!preferredTrack) {
41414          preferredTrack = track;
41415        }
41416
41417        // clear everything if offTextTrackMenuItem was clicked
41418      } else if (userPref && !userPref.enabled) {
41419        preferredTrack = null;
41420        firstDesc = null;
41421        firstCaptions = null;
41422      } else if (track.default) {
41423        if (track.kind === 'descriptions' && !firstDesc) {
41424          firstDesc = track;
41425        } else if (track.kind in modes && !firstCaptions) {
41426          firstCaptions = track;
41427        }
41428      }
41429    }
41430
41431    // The preferredTrack matches the user preference and takes
41432    // precedence over all the other tracks.
41433    // So, display the preferredTrack before the first default track
41434    // and the subtitles/captions track before the descriptions track
41435    if (preferredTrack) {
41436      preferredTrack.mode = 'showing';
41437    } else if (firstCaptions) {
41438      firstCaptions.mode = 'showing';
41439    } else if (firstDesc) {
41440      firstDesc.mode = 'showing';
41441    }
41442  }
41443
41444  /**
41445   * Turn display of {@link TextTrack}'s from the current state into the other state.
41446   * There are only two states:
41447   * - 'shown'
41448   * - 'hidden'
41449   *
41450   * @listens Player#loadstart
41451   */
41452  toggleDisplay() {
41453    if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
41454      this.hide();
41455    } else {
41456      this.show();
41457    }
41458  }
41459
41460  /**
41461   * Create the {@link Component}'s DOM element.
41462   *
41463   * @return {Element}
41464   *         The element that was created.
41465   */
41466  createEl() {
41467    return super.createEl('div', {
41468      className: 'vjs-text-track-display'
41469    }, {
vendor: 7,065 bytes, lines 41470-41649
41470      'translate': 'yes',
41471      'aria-live': 'off',
41472      'aria-atomic': 'true'
41473    });
41474  }
41475
41476  /**
41477   * Clear all displayed {@link TextTrack}s.
41478   */
41479  clearDisplay() {
41480    if (typeof window__default["default"].WebVTT === 'function') {
41481      window__default["default"].WebVTT.processCues(window__default["default"], [], this.el_);
41482    }
41483  }
41484
41485  /**
41486   * Update the displayed {@link TextTrack} when either a {@link Player#texttrackchange},
41487   * a {@link Player#fullscreenchange}, a {@link Player#useractive}, or a
41488   * {@link Player#userinactive} is fired.
41489   *
41490   * @listens Player#texttrackchange
41491   * @listens Player#fullscreenchange
41492   * @listens Player#useractive
41493   * @listens Player#userinactive
41494   */
41495  updateDisplay() {
41496    const tracks = this.player_.textTracks();
41497    const allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks;
41498    this.clearDisplay();
41499    if (allowMultipleShowingTracks) {
41500      const showingTracks = [];
41501      for (let i = 0; i < tracks.length; ++i) {
41502        const track = tracks[i];
41503        if (track.mode !== 'showing') {
41504          continue;
41505        }
41506        showingTracks.push(track);
41507      }
41508      this.updateForTrack(showingTracks);
41509      return;
41510    }
41511
41512    //  Track display prioritization model: if multiple tracks are 'showing',
41513    //  display the first 'subtitles' or 'captions' track which is 'showing',
41514    //  otherwise display the first 'descriptions' track which is 'showing'
41515
41516    let descriptionsTrack = null;
41517    let captionsSubtitlesTrack = null;
41518    let i = tracks.length;
41519    while (i--) {
41520      const track = tracks[i];
41521      if (track.mode === 'showing') {
41522        if (track.kind === 'descriptions') {
41523          descriptionsTrack = track;
41524        } else {
41525          captionsSubtitlesTrack = track;
41526        }
41527      }
41528    }
41529    if (captionsSubtitlesTrack) {
41530      if (this.getAttribute('aria-live') !== 'off') {
41531        this.setAttribute('aria-live', 'off');
41532      }
41533      this.updateForTrack(captionsSubtitlesTrack);
41534    } else if (descriptionsTrack) {
41535      if (this.getAttribute('aria-live') !== 'assertive') {
41536        this.setAttribute('aria-live', 'assertive');
41537      }
41538      this.updateForTrack(descriptionsTrack);
41539    }
41540    if (!(window__default["default"].CSS !== undefined && window__default["default"].CSS.supports('inset', '10px'))) {
41541      const textTrackDisplay = this.el_;
41542      const vjsTextTrackCues = textTrackDisplay.querySelectorAll('.vjs-text-track-cue');
41543      const controlBarHeight = this.player_.controlBar.el_.getBoundingClientRect().height;
41544      const playerHeight = this.player_.el_.getBoundingClientRect().height;
41545
41546      // Clear inline style before getting actual height of textTrackDisplay
41547      textTrackDisplay.style = '';
41548
41549      // textrack style updates, this styles are required to be inline
41550      tryUpdateStyle(textTrackDisplay, 'position', 'relative');
41551      tryUpdateStyle(textTrackDisplay, 'height', playerHeight - controlBarHeight + 'px');
41552      tryUpdateStyle(textTrackDisplay, 'top', 'unset');
41553      if (IS_SMART_TV) {
41554        tryUpdateStyle(textTrackDisplay, 'bottom', playerHeight + 'px');
41555      } else {
41556        tryUpdateStyle(textTrackDisplay, 'bottom', '0px');
41557      }
41558
41559      // vjsTextTrackCue style updates
41560      if (vjsTextTrackCues.length > 0) {
41561        vjsTextTrackCues.forEach(vjsTextTrackCue => {
41562          // verify if inset styles are inline
41563          if (vjsTextTrackCue.style.inset) {
41564            const insetStyles = vjsTextTrackCue.style.inset.split(' ');
41565
41566            // expected value is always 3
41567            if (insetStyles.length === 3) {
41568              Object.assign(vjsTextTrackCue.style, {
41569                top: insetStyles[0],
41570                right: insetStyles[1],
41571                bottom: insetStyles[2],
41572                left: 'unset'
41573              });
41574            }
41575          }
41576        });
41577      }
41578    }
41579  }
41580
41581  /**
41582   * Updates the displayed TextTrack to be sure it overlays the video when a either
41583   * a {@link Player#texttrackchange} or a {@link Player#fullscreenchange} is fired.
41584   */
41585  updateDisplayOverlay() {
41586    // inset-inline and inset-block are not supprted on old chrome, but these are
41587    // only likely to be used on TV devices
41588    if (!this.player_.videoHeight() || !(window__default["default"].CSS !== undefined && window__default["default"].CSS.supports('inset-inline: 10px'))) {
41589      return;
41590    }
41591    const playerWidth = this.player_.currentWidth();
41592    const playerHeight = this.player_.currentHeight();
41593    const playerAspectRatio = playerWidth / playerHeight;
41594    const videoAspectRatio = this.player_.videoWidth() / this.player_.videoHeight();
41595    let insetInlineMatch = 0;
41596    let insetBlockMatch = 0;
41597    if (Math.abs(playerAspectRatio - videoAspectRatio) > 0.1) {
41598      if (playerAspectRatio > videoAspectRatio) {
41599        insetInlineMatch = Math.round((playerWidth - playerHeight * videoAspectRatio) / 2);
41600      } else {
41601        insetBlockMatch = Math.round((playerHeight - playerWidth / videoAspectRatio) / 2);
41602      }
41603    }
41604    tryUpdateStyle(this.el_, 'insetInline', getCSSPositionValue(insetInlineMatch));
41605    tryUpdateStyle(this.el_, 'insetBlock', getCSSPositionValue(insetBlockMatch));
41606  }
41607
41608  /**
41609   * Style {@Link TextTrack} activeCues according to {@Link TextTrackSettings}.
41610   *
41611   * @param {TextTrack} track
41612   *        Text track object containing active cues to style.
41613   */
41614  updateDisplayState(track) {
41615    const overrides = this.player_.textTrackSettings.getValues();
41616    const cues = track.activeCues;
41617    let i = cues.length;
41618    while (i--) {
41619      const cue = cues[i];
41620      if (!cue) {
41621        continue;
41622      }
41623      const cueDiv = cue.displayState;
41624      if (overrides.color) {
41625        cueDiv.firstChild.style.color = overrides.color;
41626      }
41627      if (overrides.textOpacity) {
41628        tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
41629      }
41630      if (overrides.backgroundColor) {
41631        cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
41632      }
41633      if (overrides.backgroundOpacity) {
41634        tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
41635      }
41636      if (overrides.windowColor) {
41637        if (overrides.windowOpacity) {
41638          tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
41639        } else {
41640          cueDiv.style.backgroundColor = overrides.windowColor;
41641        }
41642      }
41643      if (overrides.edgeStyle) {
41644        if (overrides.edgeStyle === 'dropshadow') {
41645          cueDiv.firstChild.style.textShadow = `2px 2px 3px ${darkGray}, 2px 2px 4px ${darkGray}, 2px 2px 5px ${darkGray}`;
41646        } else if (overrides.edgeStyle === 'raised') {
41647          cueDiv.firstChild.style.textShadow = `1px 1px ${darkGray}, 2px 2px ${darkGray}, 3px 3px ${darkGray}`;
41648        } else if (overrides.edgeStyle === 'depressed') {
41649          cueDiv.firstChild.style.textShadow = `1px 1px ${lightGray}, 0 1px ${lightGray}, -1px -1px ${darkGray}, 0 -1px ${darkGray}`;
vendor: 7,188 bytes, lines 41650-41886
41650        } else if (overrides.edgeStyle === 'uniform') {
41651          cueDiv.firstChild.style.textShadow = `0 0 4px ${darkGray}, 0 0 4px ${darkGray}, 0 0 4px ${darkGray}, 0 0 4px ${darkGray}`;
41652        }
41653      }
41654      if (overrides.fontPercent && overrides.fontPercent !== 1) {
41655        const fontSize = window__default["default"].parseFloat(cueDiv.style.fontSize);
41656        cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
41657        cueDiv.style.height = 'auto';
41658        cueDiv.style.top = 'auto';
41659      }
41660      if (overrides.fontFamily && overrides.fontFamily !== 'default') {
41661        if (overrides.fontFamily === 'small-caps') {
41662          cueDiv.firstChild.style.fontVariant = 'small-caps';
41663        } else {
41664          cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
41665        }
41666      }
41667    }
41668  }
41669
41670  /**
41671   * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
41672   *
41673   * @param {TextTrack|TextTrack[]} tracks
41674   *        Text track object or text track array to be added to the list.
41675   */
41676  updateForTrack(tracks) {
41677    if (!Array.isArray(tracks)) {
41678      tracks = [tracks];
41679    }
41680    if (typeof window__default["default"].WebVTT !== 'function' || tracks.every(track => {
41681      return !track.activeCues;
41682    })) {
41683      return;
41684    }
41685    const cues = [];
41686
41687    // push all active track cues
41688    for (let i = 0; i < tracks.length; ++i) {
41689      const track = tracks[i];
41690      for (let j = 0; j < track.activeCues.length; ++j) {
41691        cues.push(track.activeCues[j]);
41692      }
41693    }
41694
41695    // removes all cues before it processes new ones
41696    window__default["default"].WebVTT.processCues(window__default["default"], cues, this.el_);
41697
41698    // add unique class to each language text track & add settings styling if necessary
41699    for (let i = 0; i < tracks.length; ++i) {
41700      const track = tracks[i];
41701      for (let j = 0; j < track.activeCues.length; ++j) {
41702        const cueEl = track.activeCues[j].displayState;
41703        addClass(cueEl, 'vjs-text-track-cue', 'vjs-text-track-cue-' + (track.language ? track.language : i));
41704        if (track.language) {
41705          setAttribute(cueEl, 'lang', track.language);
41706        }
41707      }
41708      if (this.player_.textTrackSettings) {
41709        this.updateDisplayState(track);
41710      }
41711    }
41712  }
41713}
41714Component$1.registerComponent('TextTrackDisplay', TextTrackDisplay);
41715
41716/**
41717 * @file loading-spinner.js
41718 */
41719
41720/**
41721 * A loading spinner for use during waiting/loading events.
41722 *
41723 * @extends Component
41724 */
41725class LoadingSpinner extends Component$1 {
41726  /**
41727   * Create the `LoadingSpinner`s DOM element.
41728   *
41729   * @return {Element}
41730   *         The dom element that gets created.
41731   */
41732  createEl() {
41733    const isAudio = this.player_.isAudio();
41734    const playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
41735    const controlText = createEl('span', {
41736      className: 'vjs-control-text',
41737      textContent: this.localize('{1} is loading.', [playerType])
41738    });
41739    const el = super.createEl('div', {
41740      className: 'vjs-loading-spinner',
41741      dir: 'ltr'
41742    });
41743    el.appendChild(controlText);
41744    return el;
41745  }
41746
41747  /**
41748   * Update control text on languagechange
41749   */
41750  handleLanguagechange() {
41751    this.$('.vjs-control-text').textContent = this.localize('{1} is loading.', [this.player_.isAudio() ? 'Audio Player' : 'Video Player']);
41752  }
41753}
41754Component$1.registerComponent('LoadingSpinner', LoadingSpinner);
41755
41756/**
41757 * @file button.js
41758 */
41759
41760/**
41761 * Base class for all buttons.
41762 *
41763 * @extends ClickableComponent
41764 */
41765class Button extends ClickableComponent {
41766  /**
41767   * Create the `Button`s DOM element.
41768   *
41769   * @param {string} [tag="button"]
41770   *        The element's node type. This argument is IGNORED: no matter what
41771   *        is passed, it will always create a `button` element.
41772   *
41773   * @param {Object} [props={}]
41774   *        An object of properties that should be set on the element.
41775   *
41776   * @param {Object} [attributes={}]
41777   *        An object of attributes that should be set on the element.
41778   *
41779   * @return {Element}
41780   *         The element that gets created.
41781   */
41782  createEl(tag, props = {}, attributes = {}) {
41783    tag = 'button';
41784    props = Object.assign({
41785      className: this.buildCSSClass()
41786    }, props);
41787
41788    // Add attributes for button element
41789    attributes = Object.assign({
41790      // Necessary since the default button type is "submit"
41791      type: 'button'
41792    }, attributes);
41793    const el = createEl(tag, props, attributes);
41794    if (!this.player_.options_.experimentalSvgIcons) {
41795      el.appendChild(createEl('span', {
41796        className: 'vjs-icon-placeholder'
41797      }, {
41798        'aria-hidden': true
41799      }));
41800    }
41801    this.createControlTextEl(el);
41802    return el;
41803  }
41804
41805  /**
41806   * Add a child `Component` inside of this `Button`.
41807   *
41808   * @param {string|Component} child
41809   *        The name or instance of a child to add.
41810   *
41811   * @param {Object} [options={}]
41812   *        The key/value store of options that will get passed to children of
41813   *        the child.
41814   *
41815   * @return {Component}
41816   *         The `Component` that gets added as a child. When using a string the
41817   *         `Component` will get created by this process.
41818   *
41819   * @deprecated since version 5
41820   */
41821  addChild(child, options = {}) {
41822    const className = this.constructor.name;
41823    log$1.warn(`Adding an actionable (user controllable) child to a Button (${className}) is not supported; use a ClickableComponent instead.`);
41824
41825    // Avoid the error message generated by ClickableComponent's addChild method
41826    return Component$1.prototype.addChild.call(this, child, options);
41827  }
41828
41829  /**
41830   * Enable the `Button` element so that it can be activated or clicked. Use this with
41831   * {@link Button#disable}.
41832   */
41833  enable() {
41834    super.enable();
41835    this.el_.removeAttribute('disabled');
41836  }
41837
41838  /**
41839   * Disable the `Button` element so that it cannot be activated or clicked. Use this with
41840   * {@link Button#enable}.
41841   */
41842  disable() {
41843    super.disable();
41844    this.el_.setAttribute('disabled', 'disabled');
41845  }
41846
41847  /**
41848   * This gets called when a `Button` has focus and `keydown` is triggered via a key
41849   * press.
41850   *
41851   * @param {KeyboardEvent} event
41852   *        The event that caused this function to get called.
41853   *
41854   * @listens keydown
41855   */
41856  handleKeyDown(event) {
41857    // Ignore Space or Enter key operation, which is handled by the browser for
41858    // a button - though not for its super class, ClickableComponent. Also,
41859    // prevent the event from propagating through the DOM and triggering Player
41860    // hotkeys. We do not preventDefault here because we _want_ the browser to
41861    // handle it.
41862    if (event.key === ' ' || event.key === 'Enter') {
41863      event.stopPropagation();
41864      return;
41865    }
41866
41867    // Pass keypress handling up for unsupported keys
41868    super.handleKeyDown(event);
41869  }
41870}
41871Component$1.registerComponent('Button', Button);
41872
41873/**
41874 * @file big-play-button.js
41875 */
41876
41877/**
41878 * The initial play button that shows before the video has played. The hiding of the
41879 * `BigPlayButton` get done via CSS and `Player` states.
41880 *
41881 * @extends Button
41882 */
41883class BigPlayButton extends Button {
41884  constructor(player, options) {
41885    super(player, options);
41886    this.mouseused_ = false;
vendor: 9,881 bytes, lines 41887-42259
41887    this.setIcon('play');
41888    this.on('mousedown', e => this.handleMouseDown(e));
41889  }
41890
41891  /**
41892   * Builds the default DOM `className`.
41893   *
41894   * @return {string}
41895   *         The DOM `className` for this object. Always returns 'vjs-big-play-button'.
41896   */
41897  buildCSSClass() {
41898    return 'vjs-big-play-button';
41899  }
41900
41901  /**
41902   * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
41903   * for more detailed information on what a click can be.
41904   *
41905   * @param {KeyboardEvent|MouseEvent|TouchEvent} event
41906   *        The `keydown`, `tap`, or `click` event that caused this function to be
41907   *        called.
41908   *
41909   * @listens tap
41910   * @listens click
41911   */
41912  handleClick(event) {
41913    const playPromise = this.player_.play();
41914    const cb = this.player_.getChild('controlBar');
41915    const playToggle = cb && cb.getChild('playToggle');
41916
41917    // exit early if tapped or clicked via the mouse
41918    if (event.type === 'tap' || this.mouseused_ && 'clientX' in event && 'clientY' in event) {
41919      silencePromise(playPromise);
41920
41921      // On Microsoft Edge, moving focus to the <video> (tech) element as playback
41922      // starts prevents a protected (DRM/EME) video surface from being presented:
41923      // audio plays but the frame stays black until a later repaint or focus change.
41924      // Focus the control-bar play toggle instead of the tech on Edge, mirroring the
41925      // keyboard branch below. This is the pointer-path counterpart to the fix for
41926      // https://github.com/videojs/video.js/issues/6270 (#6318/#6508 redirected only
41927      // the keyboard path; the mouse/tap tech.focus() survived and regressed on
41928      // Chromium Edge with hardware-accelerated DRM).
41929      if (IS_EDGE) {
41930        (playToggle || this.player_).focus();
41931      } else if (this.player_.tech(true)) {
41932        this.player_.tech(true).focus();
41933      }
41934      return;
41935    }
41936    if (!playToggle) {
41937      this.player_.tech(true).focus();
41938      return;
41939    }
41940    const playFocus = () => playToggle.focus();
41941    if (isPromise(playPromise)) {
41942      playPromise.then(playFocus, () => {});
41943    } else {
41944      this.setTimeout(playFocus, 1);
41945    }
41946  }
41947
41948  /**
41949   * Event handler that is called when a `BigPlayButton` receives a
41950   * `keydown` event.
41951   *
41952   * @param {KeyboardEvent} event
41953   *        The `keydown` event that caused this function to be called.
41954   *
41955   * @listens keydown
41956   */
41957  handleKeyDown(event) {
41958    this.mouseused_ = false;
41959    super.handleKeyDown(event);
41960  }
41961
41962  /**
41963   * Handle `mousedown` events on the `BigPlayButton`.
41964   *
41965   * @param {MouseEvent} event
41966   *        `mousedown` or `touchstart` event that triggered this function
41967   *
41968   * @listens mousedown
41969   */
41970  handleMouseDown(event) {
41971    this.mouseused_ = true;
41972  }
41973}
41974
41975/**
41976 * The text that should display over the `BigPlayButton`s controls. Added to for localization.
41977 *
41978 * @type {string}
41979 * @protected
41980 */
41981BigPlayButton.prototype.controlText_ = 'Play Video';
41982Component$1.registerComponent('BigPlayButton', BigPlayButton);
41983
41984/**
41985 * @file close-button.js
41986 */
41987
41988/** @import Player from './player' */
41989
41990/**
41991 * The `CloseButton` is a `{@link Button}` that fires a `close` event when
41992 * it gets clicked.
41993 *
41994 * @extends Button
41995 */
41996class CloseButton extends Button {
41997  /**
41998  * Creates an instance of the this class.
41999  *
42000  * @param  {Player} player
42001  *         The `Player` that this class should be attached to.
42002  *
42003  * @param  {Object} [options]
42004  *         The key/value store of player options.
42005  */
42006  constructor(player, options) {
42007    super(player, options);
42008    this.setIcon('cancel');
42009    this.controlText(options && options.controlText || this.localize('Close'));
42010  }
42011
42012  /**
42013  * Builds the default DOM `className`.
42014  *
42015  * @return {string}
42016  *         The DOM `className` for this object.
42017  */
42018  buildCSSClass() {
42019    return `vjs-close-button ${super.buildCSSClass()}`;
42020  }
42021
42022  /**
42023   * This gets called when a `CloseButton` gets clicked. See
42024   * {@link ClickableComponent#handleClick} for more information on when
42025   * this will be triggered
42026   *
42027   * @param {Event} event
42028   *        The `keydown`, `tap`, or `click` event that caused this function to be
42029   *        called.
42030   *
42031   * @listens tap
42032   * @listens click
42033   * @fires CloseButton#close
42034   */
42035  handleClick(event) {
42036    /**
42037     * Triggered when the a `CloseButton` is clicked.
42038     *
42039     * @event CloseButton#close
42040     * @type {Event}
42041     *
42042     * @property {boolean} [bubbles=false]
42043     *           set to false so that the close event does not
42044     *           bubble up to parents if there is no listener
42045     */
42046    this.trigger({
42047      type: 'close',
42048      bubbles: false
42049    });
42050  }
42051  /**
42052   * Event handler that is called when a `CloseButton` receives a
42053   * `keydown` event.
42054   *
42055   * By default, if the key is Esc, it will trigger a `click` event.
42056   *
42057   * @param {KeyboardEvent} event
42058   *        The `keydown` event that caused this function to be called.
42059   *
42060   * @listens keydown
42061   */
42062  handleKeyDown(event) {
42063    // Esc button will trigger `click` event
42064    if (event.key === 'Escape') {
42065      event.preventDefault();
42066      event.stopPropagation();
42067      this.trigger('click');
42068    } else {
42069      // Pass keypress handling up for unsupported keys
42070      super.handleKeyDown(event);
42071    }
42072  }
42073}
42074Component$1.registerComponent('CloseButton', CloseButton);
42075
42076/**
42077 * @file play-toggle.js
42078 */
42079
42080/** @import Player from '../player' */
42081
42082/**
42083 * Button to toggle between play and pause.
42084 *
42085 * @extends Button
42086 */
42087class PlayToggle extends Button {
42088  /**
42089   * Creates an instance of this class.
42090   *
42091   * @param {Player} player
42092   *        The `Player` that this class should be attached to.
42093   *
42094   * @param {Object} [options={}]
42095   *        The key/value store of player options.
42096   */
42097  constructor(player, options = {}) {
42098    super(player, options);
42099
42100    // show or hide replay icon
42101    options.replay = options.replay === undefined || options.replay;
42102    this.setIcon('play');
42103    this.on(player, 'play', e => this.handlePlay(e));
42104    this.on(player, 'pause', e => this.handlePause(e));
42105    if (options.replay) {
42106      this.on(player, 'ended', e => this.handleEnded(e));
42107    }
42108  }
42109
42110  /**
42111   * Builds the default DOM `className`.
42112   *
42113   * @return {string}
42114   *         The DOM `className` for this object.
42115   */
42116  buildCSSClass() {
42117    return `vjs-play-control ${super.buildCSSClass()}`;
42118  }
42119
42120  /**
42121   * This gets called when an `PlayToggle` is "clicked". See
42122   * {@link ClickableComponent} for more detailed information on what a click can be.
42123   *
42124   * @param {Event} [event]
42125   *        The `keydown`, `tap`, or `click` event that caused this function to be
42126   *        called.
42127   *
42128   * @listens tap
42129   * @listens click
42130   */
42131  handleClick(event) {
42132    if (this.player_.paused()) {
42133      silencePromise(this.player_.play());
42134    } else {
42135      this.player_.pause();
42136    }
42137  }
42138
42139  /**
42140   * This gets called once after the video has ended and the user seeks so that
42141   * we can change the replay button back to a play button.
42142   *
42143   * @param {Event} [event]
42144   *        The event that caused this function to run.
42145   *
42146   * @listens Player#seeked
42147   */
42148  handleSeeked(event) {
42149    this.removeClass('vjs-ended');
42150    if (this.player_.paused()) {
42151      this.handlePause(event);
42152    } else {
42153      this.handlePlay(event);
42154    }
42155  }
42156
42157  /**
42158   * Add the vjs-playing class to the element so it can change appearance.
42159   *
42160   * @param {Event} [event]
42161   *        The event that caused this function to run.
42162   *
42163   * @listens Player#play
42164   */
42165  handlePlay(event) {
42166    this.removeClass('vjs-ended', 'vjs-paused');
42167    this.addClass('vjs-playing');
42168    // change the button text to "Pause"
42169    this.setIcon('pause');
42170    this.controlText('Pause');
42171  }
42172
42173  /**
42174   * Add the vjs-paused class to the element so it can change appearance.
42175   *
42176   * @param {Event} [event]
42177   *        The event that caused this function to run.
42178   *
42179   * @listens Player#pause
42180   */
42181  handlePause(event) {
42182    this.removeClass('vjs-playing');
42183    this.addClass('vjs-paused');
42184    // change the button text to "Play"
42185    this.setIcon('play');
42186    this.controlText('Play');
42187  }
42188
42189  /**
42190   * Add the vjs-ended class to the element so it can change appearance
42191   *
42192   * @param {Event} [event]
42193   *        The event that caused this function to run.
42194   *
42195   * @listens Player#ended
42196   */
42197  handleEnded(event) {
42198    this.removeClass('vjs-playing');
42199    this.addClass('vjs-ended');
42200    // change the button text to "Replay"
42201    this.setIcon('replay');
42202    this.controlText('Replay');
42203
42204    // on the next seek remove the replay button
42205    this.one(this.player_, 'seeked', e => this.handleSeeked(e));
42206  }
42207}
42208
42209/**
42210 * The text that should display over the `PlayToggle`s controls. Added for localization.
42211 *
42212 * @type {string}
42213 * @protected
42214 */
42215PlayToggle.prototype.controlText_ = 'Play';
42216Component$1.registerComponent('PlayToggle', PlayToggle);
42217
42218/**
42219 * @file time-display.js
42220 */
42221
42222/** @import Player from '../../player' */
42223
42224/**
42225 * Displays time information about the video
42226 *
42227 * @extends Component
42228 */
42229class TimeDisplay extends Component$1 {
42230  /**
42231   * Creates an instance of this class.
42232   *
42233   * @param {Player} player
42234   *        The `Player` that this class should be attached to.
42235   *
42236   * @param {Object} [options]
42237   *        The key/value store of player options.
42238   */
42239  constructor(player, options) {
42240    super(player, options);
42241    this.on(player, ['timeupdate', 'ended', 'seeking'], e => this.update(e));
42242    this.updateTextNode_();
42243  }
42244
42245  /**
42246   * Create the `Component`'s DOM element
42247   *
42248   * @return {Element}
42249   *         The element that was created.
42250   */
42251  createEl() {
42252    const className = this.buildCSSClass();
42253    const el = super.createEl('div', {
42254      className: `${className} vjs-time-control vjs-control`
42255    });
42256    const span = createEl('span', {
42257      className: 'vjs-control-text',
42258      textContent: `${this.localize(this.labelText_)}\u00a0`
42259    }, {
vendor: 4,302 bytes, lines 42260-42414
42260      role: 'presentation'
42261    });
42262    el.appendChild(span);
42263    this.contentEl_ = createEl('span', {
42264      className: `${className}-display`
42265    }, {
42266      // span elements have no implicit role, but some screen readers (notably VoiceOver)
42267      // treat them as a break between items in the DOM when using arrow keys
42268      // (or left-to-right swipes on iOS) to read contents of a page. Using
42269      // role='presentation' causes VoiceOver to NOT treat this span as a break.
42270      role: 'presentation'
42271    });
42272    el.appendChild(this.contentEl_);
42273    return el;
42274  }
42275  dispose() {
42276    this.contentEl_ = null;
42277    this.textNode_ = null;
42278    super.dispose();
42279  }
42280
42281  /**
42282   * Updates the displayed time according to the `updateContent` function which is defined in the child class.
42283   *
42284   * @param {Event} [event]
42285   *          The `timeupdate`, `ended` or `seeking` (if enableSmoothSeeking is true) event that caused this function to be called.
42286   */
42287  update(event) {
42288    if (!this.player_.options_.enableSmoothSeeking && event.type === 'seeking') {
42289      return;
42290    }
42291    this.updateContent(event);
42292  }
42293
42294  /**
42295   * Updates the time display text node with a new time
42296   *
42297   * @param {number} [time=0] the time to update to
42298   *
42299   * @private
42300   */
42301  updateTextNode_(time = 0) {
42302    time = formatTime(time);
42303    if (this.formattedTime_ === time) {
42304      return;
42305    }
42306    this.formattedTime_ = time;
42307    this.requestNamedAnimationFrame('TimeDisplay#updateTextNode_', () => {
42308      if (!this.contentEl_) {
42309        return;
42310      }
42311      let oldNode = this.textNode_;
42312      if (oldNode && this.contentEl_.firstChild !== oldNode) {
42313        oldNode = null;
42314        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.');
42315      }
42316      this.textNode_ = document__default["default"].createTextNode(this.formattedTime_);
42317      if (!this.textNode_) {
42318        return;
42319      }
42320      if (oldNode) {
42321        this.contentEl_.replaceChild(this.textNode_, oldNode);
42322      } else {
42323        this.contentEl_.appendChild(this.textNode_);
42324      }
42325    });
42326  }
42327
42328  /**
42329   * To be filled out in the child class, should update the displayed time
42330   * in accordance with the fact that the current time has changed.
42331   *
42332   * @param {Event} [event]
42333   *        The `timeupdate`  event that caused this to run.
42334   *
42335   * @listens Player#timeupdate
42336   */
42337  updateContent(event) {}
42338}
42339
42340/**
42341 * The text that is added to the `TimeDisplay` for screen reader users.
42342 *
42343 * @type {string}
42344 * @private
42345 */
42346TimeDisplay.prototype.labelText_ = 'Time';
42347
42348/**
42349 * The text that should display over the `TimeDisplay`s controls. Added to for localization.
42350 *
42351 * @type {string}
42352 * @protected
42353 *
42354 * @deprecated in v7; controlText_ is not used in non-active display Components
42355 */
42356TimeDisplay.prototype.controlText_ = 'Time';
42357Component$1.registerComponent('TimeDisplay', TimeDisplay);
42358
42359/**
42360 * @file current-time-display.js
42361 */
42362
42363/**
42364 * Displays the current time
42365 *
42366 * @extends Component
42367 */
42368class CurrentTimeDisplay extends TimeDisplay {
42369  /**
42370   * Builds the default DOM `className`.
42371   *
42372   * @return {string}
42373   *         The DOM `className` for this object.
42374   */
42375  buildCSSClass() {
42376    return 'vjs-current-time';
42377  }
42378
42379  /**
42380   * Update current time display
42381   *
42382   * @param {Event} [event]
42383   *        The `timeupdate` event that caused this function to run.
42384   *
42385   * @listens Player#timeupdate
42386   */
42387  updateContent(event) {
42388    // Allows for smooth scrubbing, when player can't keep up.
42389    let time;
42390    if (this.player_.ended()) {
42391      time = this.player_.duration();
42392    } else if (event && event.target && typeof event.target.pendingSeekTime === 'function' && event.target.pendingSeekTime() !== null) {
42393      time = event.target.pendingSeekTime();
42394    } else {
42395      time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
42396    }
42397    this.updateTextNode_(time);
42398  }
42399}
42400
42401/**
42402 * The text that is added to the `CurrentTimeDisplay` for screen reader users.
42403 *
42404 * @type {string}
42405 * @private
42406 */
42407CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
42408
42409/**
42410 * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
42411 *
42412 * @type {string}
42413 * @protected
42414 *
vendor: 4,369 bytes, lines 42415-42580
42415 * @deprecated in v7; controlText_ is not used in non-active display Components
42416 */
42417CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
42418Component$1.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
42419
42420/**
42421 * @file duration-display.js
42422 */
42423
42424/** @import Player from '../../player' */
42425
42426/**
42427 * Displays the duration
42428 *
42429 * @extends Component
42430 */
42431class DurationDisplay extends TimeDisplay {
42432  /**
42433   * Creates an instance of this class.
42434   *
42435   * @param {Player} player
42436   *        The `Player` that this class should be attached to.
42437   *
42438   * @param {Object} [options]
42439   *        The key/value store of player options.
42440   */
42441  constructor(player, options) {
42442    super(player, options);
42443    const updateContent = e => this.updateContent(e);
42444
42445    // we do not want to/need to throttle duration changes,
42446    // as they should always display the changed duration as
42447    // it has changed
42448    this.on(player, 'durationchange', updateContent);
42449
42450    // Listen to loadstart because the player duration is reset when a new media element is loaded,
42451    // but the durationchange on the user agent will not fire.
42452    // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
42453    this.on(player, 'loadstart', updateContent);
42454
42455    // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
42456    // listeners could have broken dependent applications/libraries. These
42457    // can likely be removed for 7.0.
42458    this.on(player, 'loadedmetadata', updateContent);
42459  }
42460
42461  /**
42462   * Builds the default DOM `className`.
42463   *
42464   * @return {string}
42465   *         The DOM `className` for this object.
42466   */
42467  buildCSSClass() {
42468    return 'vjs-duration';
42469  }
42470
42471  /**
42472   * Update duration time display.
42473   *
42474   * @param {Event} [event]
42475   *        The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
42476   *        this function to be called.
42477   *
42478   * @listens Player#durationchange
42479   * @listens Player#timeupdate
42480   * @listens Player#loadedmetadata
42481   */
42482  updateContent(event) {
42483    const duration = this.player_.duration();
42484    this.updateTextNode_(duration);
42485  }
42486}
42487
42488/**
42489 * The text that is added to the `DurationDisplay` for screen reader users.
42490 *
42491 * @type {string}
42492 * @private
42493 */
42494DurationDisplay.prototype.labelText_ = 'Duration';
42495
42496/**
42497 * The text that should display over the `DurationDisplay`s controls. Added to for localization.
42498 *
42499 * @type {string}
42500 * @protected
42501 *
42502 * @deprecated in v7; controlText_ is not used in non-active display Components
42503 */
42504DurationDisplay.prototype.controlText_ = 'Duration';
42505Component$1.registerComponent('DurationDisplay', DurationDisplay);
42506
42507/**
42508 * @file time-divider.js
42509 */
42510
42511/**
42512 * The separator between the current time and duration.
42513 * Can be hidden if it's not needed in the design.
42514 *
42515 * @extends Component
42516 */
42517class TimeDivider extends Component$1 {
42518  /**
42519   * Create the component's DOM element
42520   *
42521   * @return {Element}
42522   *         The element that was created.
42523   */
42524  createEl() {
42525    const el = super.createEl('div', {
42526      className: 'vjs-time-control vjs-time-divider'
42527    }, {
42528      // this element and its contents can be hidden from assistive techs since
42529      // it is made extraneous by the announcement of the control text
42530      // for the current time and duration displays
42531      'aria-hidden': true
42532    });
42533    const div = super.createEl('div');
42534    const span = super.createEl('span', {
42535      textContent: '/'
42536    });
42537    div.appendChild(span);
42538    el.appendChild(div);
42539    return el;
42540  }
42541}
42542Component$1.registerComponent('TimeDivider', TimeDivider);
42543
42544/**
42545 * @file remaining-time-display.js
42546 */
42547
42548/** @import Player from '../../player' */
42549
42550/**
42551 * Displays the time left in the video
42552 *
42553 * @extends Component
42554 */
42555class RemainingTimeDisplay extends TimeDisplay {
42556  /**
42557   * Creates an instance of this class.
42558   *
42559   * @param {Player} player
42560   *        The `Player` that this class should be attached to.
42561   *
42562   * @param {Object} [options]
42563   *        The key/value store of player options.
42564   */
42565  constructor(player, options) {
42566    super(player, options);
42567    this.on(player, 'durationchange', e => this.updateContent(e));
42568  }
42569
42570  /**
42571   * Builds the default DOM `className`.
42572   *
42573   * @return {string}
42574   *         The DOM `className` for this object.
42575   */
42576  buildCSSClass() {
42577    return 'vjs-remaining-time';
42578  }
42579
42580  /**
vendor: 4,659 bytes, lines 42581-42763
42581   * Create the `Component`'s DOM element with the "minus" character prepend to the time
42582   *
42583   * @return {Element}
42584   *         The element that was created.
42585   */
42586  createEl() {
42587    const el = super.createEl();
42588    if (this.options_.displayNegative !== false) {
42589      el.insertBefore(createEl('span', {}, {
42590        'aria-hidden': true
42591      }, '-'), this.contentEl_);
42592    }
42593    return el;
42594  }
42595
42596  /**
42597   * Update remaining time display.
42598   *
42599   * @param {Event} [event]
42600   *        The `timeupdate` or `durationchange` event that caused this to run.
42601   *
42602   * @listens Player#timeupdate
42603   * @listens Player#durationchange
42604   */
42605  updateContent(event) {
42606    if (typeof this.player_.duration() !== 'number') {
42607      return;
42608    }
42609    let time;
42610
42611    // @deprecated We should only use remainingTimeDisplay
42612    // as of video.js 7
42613    if (this.player_.ended()) {
42614      time = 0;
42615    } else if (this.player_.remainingTimeDisplay) {
42616      time = this.player_.remainingTimeDisplay();
42617    } else {
42618      time = this.player_.remainingTime();
42619    }
42620    this.updateTextNode_(time);
42621  }
42622}
42623
42624/**
42625 * The text that is added to the `RemainingTimeDisplay` for screen reader users.
42626 *
42627 * @type {string}
42628 * @private
42629 */
42630RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
42631
42632/**
42633 * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
42634 *
42635 * @type {string}
42636 * @protected
42637 *
42638 * @deprecated in v7; controlText_ is not used in non-active display Components
42639 */
42640RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
42641Component$1.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
42642
42643/**
42644 * @file live-display.js
42645 */
42646
42647/** @import Player from '../player' */
42648
42649// TODO - Future make it click to snap to live
42650
42651/**
42652 * Displays the live indicator when duration is Infinity.
42653 *
42654 * @extends Component
42655 */
42656class LiveDisplay extends Component$1 {
42657  /**
42658   * Creates an instance of this class.
42659   *
42660   * @param {Player} player
42661   *        The `Player` that this class should be attached to.
42662   *
42663   * @param {Object} [options]
42664   *        The key/value store of player options.
42665   */
42666  constructor(player, options) {
42667    super(player, options);
42668    this.updateShowing();
42669    this.on(this.player(), 'durationchange', e => this.updateShowing(e));
42670  }
42671
42672  /**
42673   * Create the `Component`'s DOM element
42674   *
42675   * @return {Element}
42676   *         The element that was created.
42677   */
42678  createEl() {
42679    const el = super.createEl('div', {
42680      className: 'vjs-live-control vjs-control'
42681    });
42682    this.contentEl_ = createEl('div', {
42683      className: 'vjs-live-display'
42684    }, {
42685      'aria-live': 'off'
42686    });
42687    this.contentEl_.appendChild(createEl('span', {
42688      className: 'vjs-control-text',
42689      textContent: `${this.localize('Stream Type')}\u00a0`
42690    }));
42691    this.contentEl_.appendChild(document__default["default"].createTextNode(this.localize('LIVE')));
42692    el.appendChild(this.contentEl_);
42693    return el;
42694  }
42695  dispose() {
42696    this.contentEl_ = null;
42697    super.dispose();
42698  }
42699
42700  /**
42701   * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
42702   * it accordingly
42703   *
42704   * @param {Event} [event]
42705   *        The {@link Player#durationchange} event that caused this function to run.
42706   *
42707   * @listens Player#durationchange
42708   */
42709  updateShowing(event) {
42710    if (this.player().duration() === Infinity) {
42711      this.show();
42712    } else {
42713      this.hide();
42714    }
42715  }
42716}
42717Component$1.registerComponent('LiveDisplay', LiveDisplay);
42718
42719/**
42720 * @file seek-to-live.js
42721 */
42722
42723/** @import Player from '../player' */
42724
42725/**
42726 * Displays the live indicator when duration is Infinity.
42727 *
42728 * @extends Component
42729 */
42730class SeekToLive extends Button {
42731  /**
42732   * Creates an instance of this class.
42733   *
42734   * @param {Player} player
42735   *        The `Player` that this class should be attached to.
42736   *
42737   * @param {Object} [options]
42738   *        The key/value store of player options.
42739   */
42740  constructor(player, options) {
42741    super(player, options);
42742    this.updateLiveEdgeStatus();
42743    if (this.player_.liveTracker) {
42744      this.updateLiveEdgeStatusHandler_ = e => this.updateLiveEdgeStatus(e);
42745      this.on(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatusHandler_);
42746    }
42747  }
42748
42749  /**
42750   * Create the `Component`'s DOM element
42751   *
42752   * @return {Element}
42753   *         The element that was created.
42754   */
42755  createEl() {
42756    const el = super.createEl('button', {
42757      className: 'vjs-seek-to-live-control vjs-control'
42758    });
42759    this.setIcon('circle', el);
42760    this.textEl_ = createEl('span', {
42761      className: 'vjs-seek-to-live-text',
42762      textContent: this.localize('LIVE')
42763    }, {
vendor: 3,144 bytes, lines 42764-42884
42764      'aria-hidden': 'true'
42765    });
42766    el.appendChild(this.textEl_);
42767    return el;
42768  }
42769
42770  /**
42771   * Update the state of this button if we are at the live edge
42772   * or not
42773   */
42774  updateLiveEdgeStatus() {
42775    // default to live edge
42776    if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) {
42777      this.setAttribute('aria-disabled', true);
42778      this.addClass('vjs-at-live-edge');
42779      this.controlText('Seek to live, currently playing live');
42780    } else {
42781      this.setAttribute('aria-disabled', false);
42782      this.removeClass('vjs-at-live-edge');
42783      this.controlText('Seek to live, currently behind live');
42784    }
42785  }
42786
42787  /**
42788   * On click bring us as near to the live point as possible.
42789   * This requires that we wait for the next `live-seekable-change`
42790   * event which will happen every segment length seconds.
42791   */
42792  handleClick() {
42793    this.player_.liveTracker.seekToLiveEdge();
42794  }
42795
42796  /**
42797   * Dispose of the element and stop tracking
42798   */
42799  dispose() {
42800    if (this.player_.liveTracker) {
42801      this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatusHandler_);
42802    }
42803    this.textEl_ = null;
42804    super.dispose();
42805  }
42806}
42807/**
42808 * The text that should display over the `SeekToLive`s control. Added for localization.
42809 *
42810 * @type {string}
42811 * @protected
42812 */
42813SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live';
42814Component$1.registerComponent('SeekToLive', SeekToLive);
42815
42816/**
42817 * @file num.js
42818 * @module num
42819 */
42820
42821/**
42822 * Keep a number between a min and a max value
42823 *
42824 * @param {number} number
42825 *        The number to clamp
42826 *
42827 * @param {number} min
42828 *        The minimum value
42829 * @param {number} max
42830 *        The maximum value
42831 *
42832 * @return {number}
42833 *         the clamped number
42834 */
42835function clamp(number, min, max) {
42836  number = Number(number);
42837  return Math.min(max, Math.max(min, isNaN(number) ? min : number));
42838}
42839
42840var Num = /*#__PURE__*/Object.freeze({
42841  __proto__: null,
42842  clamp: clamp
42843});
42844
42845/**
42846 * @file slider.js
42847 */
42848
42849/** @import Player from '../player' */
42850
42851/**
42852 * The base functionality for a slider. Can be vertical or horizontal.
42853 * For instance the volume bar or the seek bar on a video is a slider.
42854 *
42855 * @extends Component
42856 */
42857class Slider extends Component$1 {
42858  /**
42859  * Create an instance of this class
42860  *
42861  * @param {Player} player
42862  *        The `Player` that this class should be attached to.
42863  *
42864  * @param {Object} [options]
42865  *        The key/value store of player options.
42866  */
42867  constructor(player, options) {
42868    super(player, options);
42869    this.handleMouseDown_ = e => this.handleMouseDown(e);
42870    this.handleMouseUp_ = e => this.handleMouseUp(e);
42871    this.handleKeyDown_ = e => this.handleKeyDown(e);
42872    this.handleClick_ = e => this.handleClick(e);
42873    this.handleMouseMove_ = e => this.handleMouseMove(e);
42874    this.update_ = e => this.update(e);
42875
42876    // Set property names to bar to match with the child Slider class is looking for
42877    this.bar = this.getChild(this.options_.barName);
42878
42879    // Set a horizontal or vertical class on the slider depending on the slider type
42880    this.vertical(!!this.options_.vertical);
42881    this.enable();
42882  }
42883
42884  /**
vendor: 4,336 bytes, lines 42885-43018
42885   * Are controls are currently enabled for this slider or not.
42886   *
42887   * @return {boolean}
42888   *         true if controls are enabled, false otherwise
42889   */
42890  enabled() {
42891    return this.enabled_;
42892  }
42893
42894  /**
42895   * Enable controls for this slider if they are disabled
42896   */
42897  enable() {
42898    if (this.enabled()) {
42899      return;
42900    }
42901    this.on('mousedown', this.handleMouseDown_);
42902    this.on('touchstart', this.handleMouseDown_);
42903    this.on('keydown', this.handleKeyDown_);
42904    this.on('click', this.handleClick_);
42905
42906    // TODO: deprecated, controlsvisible does not seem to be fired
42907    this.on(this.player_, 'controlsvisible', this.update);
42908    if (this.playerEvent) {
42909      this.on(this.player_, this.playerEvent, this.update);
42910    }
42911    this.removeClass('disabled');
42912    this.setAttribute('tabindex', 0);
42913    this.enabled_ = true;
42914  }
42915
42916  /**
42917   * Disable controls for this slider if they are enabled
42918   */
42919  disable() {
42920    if (!this.enabled()) {
42921      return;
42922    }
42923    const doc = this.bar.el_.ownerDocument;
42924    this.off('mousedown', this.handleMouseDown_);
42925    this.off('touchstart', this.handleMouseDown_);
42926    this.off('keydown', this.handleKeyDown_);
42927    this.off('click', this.handleClick_);
42928    this.off(this.player_, 'controlsvisible', this.update_);
42929    this.off(doc, 'mousemove', this.handleMouseMove_);
42930    this.off(doc, 'mouseup', this.handleMouseUp_);
42931    this.off(doc, 'touchmove', this.handleMouseMove_);
42932    this.off(doc, 'touchend', this.handleMouseUp_);
42933    this.removeAttribute('tabindex');
42934    this.addClass('disabled');
42935    if (this.playerEvent) {
42936      this.off(this.player_, this.playerEvent, this.update);
42937    }
42938    this.enabled_ = false;
42939  }
42940
42941  /**
42942   * Create the `Slider`s DOM element.
42943   *
42944   * @param {string} type
42945   *        Type of element to create.
42946   *
42947   * @param {Object} [props={}]
42948   *        List of properties in Object form.
42949   *
42950   * @param {Object} [attributes={}]
42951   *        list of attributes in Object form.
42952   *
42953   * @return {Element}
42954   *         The element that gets created.
42955   */
42956  createEl(type, props = {}, attributes = {}) {
42957    // Add the slider element class to all sub classes
42958    props.className = props.className + ' vjs-slider';
42959    props = Object.assign({
42960      tabIndex: 0
42961    }, props);
42962    attributes = Object.assign({
42963      'role': 'slider',
42964      'aria-valuenow': 0,
42965      'aria-valuemin': 0,
42966      'aria-valuemax': 100
42967    }, attributes);
42968    return super.createEl(type, props, attributes);
42969  }
42970
42971  /**
42972   * Handle `mousedown` or `touchstart` events on the `Slider`.
42973   *
42974   * @param {MouseEvent} event
42975   *        `mousedown` or `touchstart` event that triggered this function
42976   *
42977   * @listens mousedown
42978   * @listens touchstart
42979   * @fires Slider#slideractive
42980   */
42981  handleMouseDown(event) {
42982    const doc = this.bar.el_.ownerDocument;
42983    if (event.type === 'mousedown') {
42984      event.preventDefault();
42985    }
42986    // Do not call preventDefault() on touchstart in Chrome
42987    // to avoid console warnings. Use a 'touch-action: none' style
42988    // instead to prevent unintended scrolling.
42989    // https://developers.google.com/web/updates/2017/01/scrolling-intervention
42990    if (event.type === 'touchstart' && !IS_CHROME) {
42991      event.preventDefault();
42992    }
42993    blockTextSelection();
42994    this.addClass('vjs-sliding');
42995    /**
42996     * Triggered when the slider is in an active state
42997     *
42998     * @event Slider#slideractive
42999     * @type {MouseEvent}
43000     */
43001    this.trigger('slideractive');
43002    this.on(doc, 'mousemove', this.handleMouseMove_);
43003    this.on(doc, 'mouseup', this.handleMouseUp_);
43004    this.on(doc, 'touchmove', this.handleMouseMove_);
43005    this.on(doc, 'touchend', this.handleMouseUp_);
43006    this.handleMouseMove(event, true);
43007  }
43008
43009  /**
43010   * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
43011   * The `mousemove` and `touchmove` events will only only trigger this function during
43012   * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
43013   * {@link Slider#handleMouseUp}.
43014   *
43015   * @param {MouseEvent} event
43016   *        `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
43017   *        this function
43018   * @param {boolean} mouseDown this is a flag that should be set to true if `handleMouseMove` is called directly. It allows us to skip things that should not happen if c
vendor: 8,265 bytes, lines 43018-43295
43018oming from mouse down but should happen on regular mouse move handler. Defaults to false.
43019   *
43020   * @listens mousemove
43021   * @listens touchmove
43022   */
43023  handleMouseMove(event) {}
43024
43025  /**
43026   * Handle `mouseup` or `touchend` events on the `Slider`.
43027   *
43028   * @param {MouseEvent} event
43029   *        `mouseup` or `touchend` event that triggered this function.
43030   *
43031   * @listens touchend
43032   * @listens mouseup
43033   * @fires Slider#sliderinactive
43034   */
43035  handleMouseUp(event) {
43036    const doc = this.bar.el_.ownerDocument;
43037    unblockTextSelection();
43038    this.removeClass('vjs-sliding');
43039    /**
43040     * Triggered when the slider is no longer in an active state.
43041     *
43042     * @event Slider#sliderinactive
43043     * @type {Event}
43044     */
43045    this.trigger('sliderinactive');
43046    this.off(doc, 'mousemove', this.handleMouseMove_);
43047    this.off(doc, 'mouseup', this.handleMouseUp_);
43048    this.off(doc, 'touchmove', this.handleMouseMove_);
43049    this.off(doc, 'touchend', this.handleMouseUp_);
43050    this.update();
43051  }
43052
43053  /**
43054   * Update the progress bar of the `Slider`.
43055   *
43056   * @return {number}
43057   *          The percentage of progress the progress bar represents as a
43058   *          number from 0 to 1.
43059   */
43060  update() {
43061    // In VolumeBar init we have a setTimeout for update that pops and update
43062    // to the end of the execution stack. The player is destroyed before then
43063    // update will cause an error
43064    // If there's no bar...
43065    if (!this.el_ || !this.bar) {
43066      return;
43067    }
43068
43069    // clamp progress between 0 and 1
43070    // and only round to four decimal places, as we round to two below
43071    const progress = this.getProgress();
43072    if (progress === this.progress_) {
43073      return progress;
43074    }
43075    this.progress_ = progress;
43076    this.requestNamedAnimationFrame('Slider#update', () => {
43077      // Set the new bar width or height
43078      const sizeKey = this.vertical() ? 'height' : 'width';
43079
43080      // Convert to a percentage for css value
43081      this.bar.el().style[sizeKey] = (progress * 100).toFixed(2) + '%';
43082    });
43083    return progress;
43084  }
43085
43086  /**
43087   * Get the percentage of the bar that should be filled
43088   * but clamped and rounded.
43089   *
43090   * @return {number}
43091   *         percentage filled that the slider is
43092   */
43093  getProgress() {
43094    return Number(clamp(this.getPercent(), 0, 1).toFixed(4));
43095  }
43096
43097  /**
43098   * Calculate distance for slider
43099   *
43100   * @param {Event} event
43101   *        The event that caused this function to run.
43102   *
43103   * @return {number}
43104   *         The current position of the Slider.
43105   *         - position.x for vertical `Slider`s
43106   *         - position.y for horizontal `Slider`s
43107   */
43108  calculateDistance(event) {
43109    const position = getPointerPosition(this.el_, event);
43110    if (this.vertical()) {
43111      return position.y;
43112    }
43113    return position.x;
43114  }
43115
43116  /**
43117   * Handle a `keydown` event on the `Slider`. Watches for left, right, up, and down
43118   * arrow keys. This function will only be called when the slider has focus. See
43119   * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
43120   *
43121   * @param {KeyboardEvent} event
43122   *        the `keydown` event that caused this function to run.
43123   *
43124   * @listens keydown
43125   */
43126  handleKeyDown(event) {
43127    const spatialNavOptions = this.options_.playerOptions.spatialNavigation;
43128    const spatialNavEnabled = spatialNavOptions && spatialNavOptions.enabled;
43129    const horizontalSeek = spatialNavOptions && spatialNavOptions.horizontalSeek;
43130    if (spatialNavEnabled) {
43131      if (horizontalSeek && event.key === 'ArrowLeft' || !horizontalSeek && event.key === 'ArrowDown') {
43132        event.preventDefault();
43133        event.stopPropagation();
43134        this.stepBack();
43135      } else if (horizontalSeek && event.key === 'ArrowRight' || !horizontalSeek && event.key === 'ArrowUp') {
43136        event.preventDefault();
43137        event.stopPropagation();
43138        this.stepForward();
43139      } else {
43140        if (this.pendingSeekTime()) {
43141          this.pendingSeekTime(null);
43142          this.userSeek_(this.player_.currentTime());
43143        }
43144        super.handleKeyDown(event);
43145      }
43146
43147      // Left and Down Arrows
43148    } else if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {
43149      event.preventDefault();
43150      event.stopPropagation();
43151      this.stepBack();
43152
43153      // Up and Right Arrows
43154    } else if (event.key === 'ArrowUp' || event.key === 'ArrowRight') {
43155      event.preventDefault();
43156      event.stopPropagation();
43157      this.stepForward();
43158    } else {
43159      // Pass keydown handling up for unsupported keys
43160      super.handleKeyDown(event);
43161    }
43162  }
43163
43164  /**
43165   * Listener for click events on slider, used to prevent clicks
43166   *   from bubbling up to parent elements like button menus.
43167   *
43168   * @param {Object} event
43169   *        Event that caused this object to run
43170   */
43171  handleClick(event) {
43172    event.stopPropagation();
43173    event.preventDefault();
43174  }
43175
43176  /**
43177   * Get/set if slider is horizontal for vertical
43178   *
43179   * @param {boolean} [bool]
43180   *        - true if slider is vertical,
43181   *        - false is horizontal
43182   *
43183   * @return {boolean}
43184   *         - true if slider is vertical, and getting
43185   *         - false if the slider is horizontal, and getting
43186   */
43187  vertical(bool) {
43188    if (bool === undefined) {
43189      return this.vertical_ || false;
43190    }
43191    this.vertical_ = !!bool;
43192    if (this.vertical_) {
43193      this.addClass('vjs-slider-vertical');
43194    } else {
43195      this.addClass('vjs-slider-horizontal');
43196    }
43197  }
43198}
43199Component$1.registerComponent('Slider', Slider);
43200
43201/**
43202 * @file load-progress-bar.js
43203 */
43204
43205/** @import Player from '../../player' */
43206
43207// get the percent width of a time compared to the total end
43208const percentify = (time, end) => clamp(time / end * 100, 0, 100).toFixed(2) + '%';
43209
43210/**
43211 * Shows loading progress
43212 *
43213 * @extends Component
43214 */
43215class LoadProgressBar extends Component$1 {
43216  /**
43217   * Creates an instance of this class.
43218   *
43219   * @param {Player} player
43220   *        The `Player` that this class should be attached to.
43221   *
43222   * @param {Object} [options]
43223   *        The key/value store of player options.
43224   */
43225  constructor(player, options) {
43226    super(player, options);
43227    this.partEls_ = [];
43228    this.on(player, 'progress', e => this.update(e));
43229  }
43230
43231  /**
43232   * Create the `Component`'s DOM element
43233   *
43234   * @return {Element}
43235   *         The element that was created.
43236   */
43237  createEl() {
43238    const el = super.createEl('div', {
43239      className: 'vjs-load-progress'
43240    });
43241    const wrapper = createEl('span', {
43242      className: 'vjs-control-text'
43243    });
43244    const loadedText = createEl('span', {
43245      textContent: this.localize('Loaded')
43246    });
43247    const separator = document__default["default"].createTextNode(': ');
43248    this.percentageEl_ = createEl('span', {
43249      className: 'vjs-control-text-loaded-percentage',
43250      textContent: '0%'
43251    });
43252    el.appendChild(wrapper);
43253    wrapper.appendChild(loadedText);
43254    wrapper.appendChild(separator);
43255    wrapper.appendChild(this.percentageEl_);
43256    return el;
43257  }
43258  dispose() {
43259    this.partEls_ = null;
43260    this.percentageEl_ = null;
43261    super.dispose();
43262  }
43263
43264  /**
43265   * Update progress bar
43266   *
43267   * @param {Event} [event]
43268   *        The `progress` event that caused this function to run.
43269   *
43270   * @listens Player#progress
43271   */
43272  update(event) {
43273    this.requestNamedAnimationFrame('LoadProgressBar#update', () => {
43274      const liveTracker = this.player_.liveTracker;
43275      const buffered = this.player_.buffered();
43276      const duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
43277      const bufferedEnd = this.player_.bufferedEnd();
43278      const children = this.partEls_;
43279      const percent = percentify(bufferedEnd, duration);
43280      if (this.percent_ !== percent) {
43281        // update the width of the progress bar
43282        this.el_.style.width = percent;
43283        // update the control-text
43284        textContent(this.percentageEl_, percent);
43285        this.percent_ = percent;
43286      }
43287
43288      // add child elements to represent the individual buffered time ranges
43289      for (let i = 0; i < buffered.length; i++) {
43290        const start = buffered.start(i);
43291        const end = buffered.end(i);
43292        let part = children[i];
43293        if (!part) {
43294          part = this.el_.appendChild(createEl());
43295          children[i] = part;
vendor: 11,638 bytes, lines 43296-43660
43296        }
43297
43298        //  only update if changed
43299        if (part.dataset.start === start && part.dataset.end === end) {
43300          continue;
43301        }
43302        part.dataset.start = start;
43303        part.dataset.end = end;
43304
43305        // set the percent based on the width of the progress bar (bufferedEnd)
43306        part.style.left = percentify(start, bufferedEnd);
43307        part.style.width = percentify(end - start, bufferedEnd);
43308      }
43309
43310      // remove unused buffered range elements
43311      for (let i = children.length; i > buffered.length; i--) {
43312        this.el_.removeChild(children[i - 1]);
43313      }
43314      children.length = buffered.length;
43315    });
43316  }
43317}
43318Component$1.registerComponent('LoadProgressBar', LoadProgressBar);
43319
43320/**
43321 * @file time-tooltip.js
43322 */
43323
43324/** @import Player from '../../player' */
43325
43326/**
43327 * Time tooltips display a time above the progress bar.
43328 *
43329 * @extends Component
43330 */
43331class TimeTooltip extends Component$1 {
43332  /**
43333   * Creates an instance of this class.
43334   *
43335   * @param {Player} player
43336   *        The {@link Player} that this class should be attached to.
43337   *
43338   * @param {Object} [options]
43339   *        The key/value store of player options.
43340   */
43341  constructor(player, options) {
43342    super(player, options);
43343    this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
43344  }
43345
43346  /**
43347   * Create the time tooltip DOM element
43348   *
43349   * @return {Element}
43350   *         The element that was created.
43351   */
43352  createEl() {
43353    return super.createEl('div', {
43354      className: 'vjs-time-tooltip'
43355    }, {
43356      'aria-hidden': 'true'
43357    });
43358  }
43359
43360  /**
43361   * Updates the position of the time tooltip relative to the `SeekBar`.
43362   *
43363   * @param {Object} seekBarRect
43364   *        The `ClientRect` for the {@link SeekBar} element.
43365   *
43366   * @param {number} seekBarPoint
43367   *        A number from 0 to 1, representing a horizontal reference point
43368   *        from the left edge of the {@link SeekBar}
43369   */
43370  update(seekBarRect, seekBarPoint, content) {
43371    const tooltipRect = findPosition(this.el_);
43372    const playerRect = getBoundingClientRect(this.player_.el());
43373    const seekBarPointPx = seekBarRect.width * seekBarPoint;
43374
43375    // do nothing if either rect isn't available
43376    // for example, if the player isn't in the DOM for testing
43377    if (!playerRect || !tooltipRect) {
43378      return;
43379    }
43380
43381    // This is the space left of the `seekBarPoint` available within the bounds
43382    // of the player. We calculate any gap between the left edge of the player
43383    // and the left edge of the `SeekBar` and add the number of pixels in the
43384    // `SeekBar` before hitting the `seekBarPoint`
43385    let spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx;
43386
43387    // This is the space right of the `seekBarPoint` available within the bounds
43388    // of the player. We calculate the number of pixels from the `seekBarPoint`
43389    // to the right edge of the `SeekBar` and add to that any gap between the
43390    // right edge of the `SeekBar` and the player.
43391    let spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right);
43392
43393    // spaceRightOfPoint is always NaN for mouse time display
43394    // because the seekbarRect does not have a right property. This causes
43395    // the mouse tool tip to be truncated when it's close to the right edge of the player.
43396    // In such cases, we ignore the `playerRect.right - seekBarRect.right` value when calculating.
43397    // For the sake of consistency, we ignore seekBarRect.left - playerRect.left for the left edge.
43398    if (!spaceRightOfPoint) {
43399      spaceRightOfPoint = seekBarRect.width - seekBarPointPx;
43400      spaceLeftOfPoint = seekBarPointPx;
43401    }
43402    // This is the number of pixels by which the tooltip will need to be pulled
43403    // further to the right to center it over the `seekBarPoint`.
43404    let pullTooltipBy = tooltipRect.width / 2;
43405
43406    // Adjust the `pullTooltipBy` distance to the left or right depending on
43407    // the results of the space calculations above.
43408    if (spaceLeftOfPoint < pullTooltipBy) {
43409      pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
43410    } else if (spaceRightOfPoint < pullTooltipBy) {
43411      pullTooltipBy = spaceRightOfPoint;
43412    }
43413
43414    // Due to the imprecision of decimal/ratio based calculations and varying
43415    // rounding behaviors, there are cases where the spacing adjustment is off
43416    // by a pixel or two. This adds insurance to these calculations.
43417    if (pullTooltipBy < 0) {
43418      pullTooltipBy = 0;
43419    } else if (pullTooltipBy > tooltipRect.width) {
43420      pullTooltipBy = tooltipRect.width;
43421    }
43422
43423    // prevent small width fluctuations within 0.4px from
43424    // changing the value below.
43425    // This really helps for live to prevent the play
43426    // progress time tooltip from jittering
43427    pullTooltipBy = Math.round(pullTooltipBy);
43428    this.el_.style.right = `-${pullTooltipBy}px`;
43429    this.write(content);
43430  }
43431
43432  /**
43433   * Write the time to the tooltip DOM element.
43434   *
43435   * @param {string} content
43436   *        The formatted time for the tooltip.
43437   */
43438  write(content) {
43439    textContent(this.el_, content);
43440  }
43441
43442  /**
43443   * Updates the position of the time tooltip relative to the `SeekBar`.
43444   *
43445   * @param {Object} seekBarRect
43446   *        The `ClientRect` for the {@link SeekBar} element.
43447   *
43448   * @param {number} seekBarPoint
43449   *        A number from 0 to 1, representing a horizontal reference point
43450   *        from the left edge of the {@link SeekBar}
43451   *
43452   * @param {number} time
43453   *        The time to update the tooltip to, not used during live playback
43454   *
43455   * @param {Function} cb
43456   *        A function that will be called during the request animation frame
43457   *        for tooltips that need to do additional animations from the default
43458   */
43459  updateTime(seekBarRect, seekBarPoint, time, cb) {
43460    this.requestNamedAnimationFrame('TimeTooltip#updateTime', () => {
43461      let content;
43462      const duration = this.player_.duration();
43463      if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
43464        const liveWindow = this.player_.liveTracker.liveWindow();
43465        const secondsBehind = liveWindow - seekBarPoint * liveWindow;
43466        content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
43467      } else {
43468        content = formatTime(time, duration);
43469      }
43470      this.update(seekBarRect, seekBarPoint, content);
43471      if (cb) {
43472        cb();
43473      }
43474    });
43475  }
43476}
43477Component$1.registerComponent('TimeTooltip', TimeTooltip);
43478
43479/**
43480 * @file play-progress-bar.js
43481 */
43482
43483/**
43484 * Used by {@link SeekBar} to display media playback progress as part of the
43485 * {@link ProgressControl}.
43486 *
43487 * @extends Component
43488 */
43489class PlayProgressBar extends Component$1 {
43490  /**
43491   * Creates an instance of this class.
43492   *
43493   * @param {Player} player
43494   *        The {@link Player} that this class should be attached to.
43495   *
43496   * @param {Object} [options]
43497   *        The key/value store of player options.
43498   */
43499  constructor(player, options) {
43500    super(player, options);
43501    this.setIcon('circle');
43502    this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
43503  }
43504
43505  /**
43506   * Create the the DOM element for this class.
43507   *
43508   * @return {Element}
43509   *         The element that was created.
43510   */
43511  createEl() {
43512    return super.createEl('div', {
43513      className: 'vjs-play-progress vjs-slider-bar'
43514    }, {
43515      'aria-hidden': 'true'
43516    });
43517  }
43518
43519  /**
43520   * Enqueues updates to its own DOM as well as the DOM of its
43521   * {@link TimeTooltip} child.
43522   *
43523   * @param {Object} seekBarRect
43524   *        The `ClientRect` for the {@link SeekBar} element.
43525   *
43526   * @param {number} seekBarPoint
43527   *        A number from 0 to 1, representing a horizontal reference point
43528   *        from the left edge of the {@link SeekBar}
43529   *
43530   * @param {Event} [event]
43531   *        The `timeupdate` event that caused this function to run.
43532   */
43533  update(seekBarRect, seekBarPoint, event) {
43534    const timeTooltip = this.getChild('timeTooltip');
43535    if (!timeTooltip) {
43536      return;
43537    }
43538
43539    // Combined logic: if an event with a valid pendingSeekTime getter exists, use it.
43540    const time = event && event.target && typeof event.target.pendingSeekTime === 'function' ? event.target.pendingSeekTime() : this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
43541    timeTooltip.updateTime(seekBarRect, seekBarPoint, time);
43542  }
43543}
43544
43545/**
43546 * Default options for {@link PlayProgressBar}.
43547 *
43548 * @type {Object}
43549 * @private
43550 */
43551PlayProgressBar.prototype.options_ = {
43552  children: []
43553};
43554
43555// Time tooltips should not be added to a player on mobile devices
43556if (!IS_IOS && !IS_ANDROID) {
43557  PlayProgressBar.prototype.options_.children.push('timeTooltip');
43558}
43559Component$1.registerComponent('PlayProgressBar', PlayProgressBar);
43560
43561/**
43562 * @file mouse-time-display.js
43563 */
43564
43565/**
43566 * The {@link MouseTimeDisplay} component tracks mouse movement over the
43567 * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
43568 * indicating the time which is represented by a given point in the
43569 * {@link ProgressControl}.
43570 *
43571 * @extends Component
43572 */
43573class MouseTimeDisplay extends Component$1 {
43574  /**
43575   * Creates an instance of this class.
43576   *
43577   * @param {Player} player
43578   *        The {@link Player} that this class should be attached to.
43579   *
43580   * @param {Object} [options]
43581   *        The key/value store of player options.
43582   */
43583  constructor(player, options) {
43584    super(player, options);
43585    this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
43586  }
43587
43588  /**
43589   * Create the DOM element for this class.
43590   *
43591   * @return {Element}
43592   *         The element that was created.
43593   */
43594  createEl() {
43595    return super.createEl('div', {
43596      className: 'vjs-mouse-display'
43597    });
43598  }
43599
43600  /**
43601   * Enqueues updates to its own DOM as well as the DOM of its
43602   * {@link TimeTooltip} child.
43603   *
43604   * @param {Object} seekBarRect
43605   *        The `ClientRect` for the {@link SeekBar} element.
43606   *
43607   * @param {number} seekBarPoint
43608   *        A number from 0 to 1, representing a horizontal reference point
43609   *        from the left edge of the {@link SeekBar}
43610   */
43611  update(seekBarRect, seekBarPoint) {
43612    const time = seekBarPoint * this.player_.duration();
43613    this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, () => {
43614      this.el_.style.left = `${seekBarRect.width * seekBarPoint}px`;
43615    });
43616  }
43617}
43618
43619/**
43620 * Default options for `MouseTimeDisplay`
43621 *
43622 * @type {Object}
43623 * @private
43624 */
43625MouseTimeDisplay.prototype.options_ = {
43626  children: ['timeTooltip']
43627};
43628Component$1.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
43629
43630/**
43631 * @file seek-bar.js
43632 */
43633
43634/**
43635 * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
43636 * as its `bar`.
43637 *
43638 * @extends Slider
43639 */
43640class SeekBar extends Slider {
43641  /**
43642   * Creates an instance of this class.
43643   *
43644   * @param {Player} player
43645   *        The `Player` that this class should be attached to.
43646   *
43647   * @param {Object} [options]
43648   *        The key/value store of player options.
43649   * @param {number} [options.stepSeconds=5]
43650   *        The number of seconds to increment on keyboard control
43651   * @param {number} [options.pageMultiplier=12]
43652   *        The multiplier of stepSeconds that PgUp/PgDown move the timeline.
43653   */
43654  constructor(player, options) {
43655    options = merge$1(SeekBar.prototype.options_, options);
43656
43657    // Avoid mutating the prototype's `children` array by creating a copy
43658    options.children = [...options.children];
43659    const shouldDisableSeekWhileScrubbing = player.options_.disableSeekWhileScrubbingOnMobile && (IS_IOS || IS_ANDROID) || player.options_.disableSeekWhileScrubbingOnSTV;
43660
vendor: 4,229 bytes, lines 43661-43776
43661    // Add the TimeTooltip as a child if we are on desktop, or on mobile with `disableSeekWhileScrubbingOnMobile: true`
43662    if (!IS_IOS && !IS_ANDROID || shouldDisableSeekWhileScrubbing) {
43663      options.children.splice(1, 0, 'mouseTimeDisplay');
43664    }
43665    super(player, options);
43666    this.shouldDisableSeekWhileScrubbing_ = shouldDisableSeekWhileScrubbing;
43667    this.pendingSeekTime_ = null;
43668    this.setEventHandlers_();
43669  }
43670
43671  /**
43672   * Sets the event handlers
43673   *
43674   * @private
43675   */
43676  setEventHandlers_() {
43677    this.update_ = bind_(this, this.update);
43678    this.update = throttle(this.update_, UPDATE_REFRESH_INTERVAL);
43679    this.on(this.player_, ['durationchange', 'timeupdate'], this.update);
43680    this.on(this.player_, ['ended'], this.update_);
43681    if (this.player_.liveTracker) {
43682      this.on(this.player_.liveTracker, 'liveedgechange', this.update);
43683    }
43684
43685    // when playing, let's ensure we smoothly update the play progress bar
43686    // via an interval
43687    this.updateInterval = null;
43688    this.enableIntervalHandler_ = e => this.enableInterval_(e);
43689    this.disableIntervalHandler_ = e => this.disableInterval_(e);
43690    this.on(this.player_, ['playing'], this.enableIntervalHandler_);
43691    this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_);
43692
43693    // we don't need to update the play progress if the document is hidden,
43694    // also, this causes the CPU to spike and eventually crash the page on IE11.
43695    if ('hidden' in document__default["default"] && 'visibilityState' in document__default["default"]) {
43696      this.on(document__default["default"], 'visibilitychange', this.toggleVisibility_);
43697    }
43698  }
43699  toggleVisibility_(e) {
43700    if (document__default["default"].visibilityState === 'hidden') {
43701      this.cancelNamedAnimationFrame('SeekBar#update');
43702      this.cancelNamedAnimationFrame('Slider#update');
43703      this.disableInterval_(e);
43704    } else {
43705      if (!this.player_.ended() && !this.player_.paused()) {
43706        this.enableInterval_();
43707      }
43708
43709      // we just switched back to the page and someone may be looking, so, update ASAP
43710      this.update();
43711    }
43712  }
43713  enableInterval_() {
43714    if (this.updateInterval) {
43715      return;
43716    }
43717    this.updateInterval = this.setInterval(this.update, UPDATE_REFRESH_INTERVAL);
43718  }
43719  disableInterval_(e) {
43720    if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e && e.type !== 'ended') {
43721      return;
43722    }
43723    if (!this.updateInterval) {
43724      return;
43725    }
43726    this.clearInterval(this.updateInterval);
43727    this.updateInterval = null;
43728  }
43729
43730  /**
43731   * Create the `Component`'s DOM element
43732   *
43733   * @return {Element}
43734   *         The element that was created.
43735   */
43736  createEl() {
43737    return super.createEl('div', {
43738      className: 'vjs-progress-holder'
43739    }, {
43740      'aria-label': this.localize('Progress Bar')
43741    });
43742  }
43743
43744  /**
43745   * This function updates the play progress bar and accessibility
43746   * attributes to whatever is passed in.
43747   *
43748   * @param {Event} [event]
43749   *        The `timeupdate` or `ended` event that caused this to run.
43750   *
43751   * @listens Player#timeupdate
43752   *
43753   * @return {number}
43754   *          The current percent at a number from 0-1
43755   */
43756  update(event) {
43757    // ignore updates while the tab is hidden
43758    if (document__default["default"].visibilityState === 'hidden') {
43759      return;
43760    }
43761    const percent = super.update();
43762    this.requestNamedAnimationFrame('SeekBar#update', () => {
43763      const currentTime = this.player_.ended() ? this.player_.duration() : this.getCurrentTime_();
43764      const liveTracker = this.player_.liveTracker;
43765      let duration = this.player_.duration();
43766      if (liveTracker && liveTracker.isLive()) {
43767        duration = this.player_.liveTracker.liveCurrentTime();
43768      }
43769      if (this.percent_ !== percent) {
43770        // machine readable value of progress bar (percentage complete)
43771        this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2));
43772        this.percent_ = percent;
43773      }
43774      if (this.currentTime_ !== currentTime || this.duration_ !== duration) {
43775        // human readable value of progress bar (time complete)
43776        this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}
vendor: 8,692 bytes, lines 43776-44059
43776', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}'));
43777        this.currentTime_ = currentTime;
43778        this.duration_ = duration;
43779      }
43780
43781      // update the progress bar time tooltip with the current time
43782      if (this.bar) {
43783        this.bar.update(getBoundingClientRect(this.el()), this.getProgress(), event);
43784      }
43785    });
43786    return percent;
43787  }
43788
43789  /**
43790   * Prevent liveThreshold from causing seeks to seem like they
43791   * are not happening from a user perspective.
43792   *
43793   * @param {number} ct
43794   *        current time to seek to
43795   */
43796  userSeek_(ct) {
43797    if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
43798      this.player_.liveTracker.nextSeekedFromUser();
43799    }
43800    this.player_.currentTime(ct);
43801  }
43802
43803  /**
43804   * Get the value of current time but allows for smooth scrubbing,
43805   * when player can't keep up.
43806   *
43807   * @return {number}
43808   *         The current time value to display
43809   *
43810   * @private
43811   */
43812  getCurrentTime_() {
43813    return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
43814  }
43815
43816  /**
43817   * Getter and setter for pendingSeekTime.
43818   * Ensures the value is clamped between 0 and duration.
43819   *
43820   * @param {number|null} [time] - Optional. The new pending seek time, can be a number or null.
43821   * @return {number|null} - The current pending seek time.
43822   */
43823  pendingSeekTime(time) {
43824    if (time !== undefined) {
43825      if (time !== null) {
43826        const duration = this.player_.duration();
43827        this.pendingSeekTime_ = Math.max(0, Math.min(time, duration));
43828      } else {
43829        this.pendingSeekTime_ = null;
43830      }
43831    }
43832    return this.pendingSeekTime_;
43833  }
43834
43835  /**
43836   * Get the percentage of media played so far.
43837   *
43838   * @return {number}
43839   *         The percentage of media played so far (0 to 1).
43840   */
43841  getPercent() {
43842    // If we have a pending seek time, we are scrubbing on mobile and should set the slider percent
43843    // to reflect the current scrub location.
43844    if (this.pendingSeekTime() !== null) {
43845      return this.pendingSeekTime() / this.player_.duration();
43846    }
43847    const currentTime = this.getCurrentTime_();
43848    let percent;
43849    const liveTracker = this.player_.liveTracker;
43850    if (liveTracker && liveTracker.isLive()) {
43851      percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow();
43852
43853      // prevent the percent from changing at the live edge
43854      if (liveTracker.atLiveEdge()) {
43855        percent = 1;
43856      }
43857    } else {
43858      percent = currentTime / this.player_.duration();
43859    }
43860    return percent;
43861  }
43862
43863  /**
43864   * Handle mouse down on seek bar
43865   *
43866   * @param {MouseEvent} event
43867   *        The `mousedown` event that caused this to run.
43868   *
43869   * @listens mousedown
43870   */
43871  handleMouseDown(event) {
43872    if (!isSingleLeftClick(event)) {
43873      return;
43874    }
43875
43876    // Stop event propagation to prevent double fire in progress-control.js
43877    event.stopPropagation();
43878    this.videoWasPlaying = !this.player_.paused();
43879
43880    // Don't pause if we are on mobile and `disableSeekWhileScrubbingOnMobile: true`.
43881    // In that case, playback should continue while the player scrubs to a new location.
43882    if (!this.shouldDisableSeekWhileScrubbing_) {
43883      this.player_.pause();
43884    }
43885    super.handleMouseDown(event);
43886  }
43887
43888  /**
43889   * Handle mouse move on seek bar
43890   *
43891   * @param {MouseEvent} event
43892   *        The `mousemove` event that caused this to run.
43893   * @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
43894   *
43895   * @listens mousemove
43896   */
43897  handleMouseMove(event, mouseDown = false) {
43898    if (!isSingleLeftClick(event) || isNaN(this.player_.duration())) {
43899      return;
43900    }
43901    if (!mouseDown && !this.player_.scrubbing()) {
43902      this.player_.scrubbing(true);
43903    }
43904    let newTime;
43905    const distance = this.calculateDistance(event);
43906    const liveTracker = this.player_.liveTracker;
43907    if (!liveTracker || !liveTracker.isLive()) {
43908      newTime = distance * this.player_.duration();
43909
43910      // Don't let video end while scrubbing.
43911      if (newTime === this.player_.duration()) {
43912        newTime = newTime - 0.1;
43913      }
43914    } else {
43915      if (distance >= 0.99) {
43916        liveTracker.seekToLiveEdge();
43917        return;
43918      }
43919      const seekableStart = liveTracker.seekableStart();
43920      const seekableEnd = liveTracker.liveCurrentTime();
43921      newTime = seekableStart + distance * liveTracker.liveWindow();
43922
43923      // Don't let video end while scrubbing.
43924      if (newTime >= seekableEnd) {
43925        newTime = seekableEnd;
43926      }
43927
43928      // Compensate for precision differences so that currentTime is not less
43929      // than seekable start
43930      if (newTime <= seekableStart) {
43931        newTime = seekableStart + 0.1;
43932      }
43933
43934      // On android seekableEnd can be Infinity sometimes,
43935      // this will cause newTime to be Infinity, which is
43936      // not a valid currentTime.
43937      if (newTime === Infinity) {
43938        return;
43939      }
43940    }
43941
43942    // if on mobile and `disableSeekWhileScrubbingOnMobile: true`, keep track of the desired seek point but we won't initiate the seek until 'touchend'
43943    if (this.shouldDisableSeekWhileScrubbing_) {
43944      this.pendingSeekTime(newTime);
43945    } else {
43946      this.userSeek_(newTime);
43947    }
43948    if (this.player_.options_.enableSmoothSeeking) {
43949      this.update();
43950    }
43951  }
43952  enable() {
43953    super.enable();
43954    const mouseTimeDisplay = this.getChild('mouseTimeDisplay');
43955    if (!mouseTimeDisplay) {
43956      return;
43957    }
43958    mouseTimeDisplay.show();
43959  }
43960  disable() {
43961    super.disable();
43962    const mouseTimeDisplay = this.getChild('mouseTimeDisplay');
43963    if (!mouseTimeDisplay) {
43964      return;
43965    }
43966    mouseTimeDisplay.hide();
43967  }
43968
43969  /**
43970   * Handle mouse up on seek bar
43971   *
43972   * @param {MouseEvent} event
43973   *        The `mouseup` event that caused this to run.
43974   *
43975   * @listens mouseup
43976   */
43977  handleMouseUp(event) {
43978    super.handleMouseUp(event);
43979
43980    // Stop event propagation to prevent double fire in progress-control.js
43981    if (event) {
43982      event.stopPropagation();
43983    }
43984    this.player_.scrubbing(false);
43985
43986    // If we have a pending seek time, then we have finished scrubbing on mobile and should initiate a seek.
43987    if (this.pendingSeekTime() !== null) {
43988      this.userSeek_(this.pendingSeekTime());
43989      this.pendingSeekTime(null);
43990    }
43991
43992    /**
43993     * Trigger timeupdate because we're done seeking and the time has changed.
43994     * This is particularly useful for if the player is paused to time the time displays.
43995     *
43996     * @event Tech#timeupdate
43997     * @type {Event}
43998     */
43999    this.player_.trigger({
44000      type: 'timeupdate',
44001      target: this,
44002      manuallyTriggered: true
44003    });
44004    if (this.videoWasPlaying) {
44005      silencePromise(this.player_.play());
44006    } else {
44007      // We're done seeking and the time has changed.
44008      // If the player is paused, make sure we display the correct time on the seek bar.
44009      this.update_();
44010    }
44011  }
44012
44013  /**
44014   * Handles pending seek time when `disableSeekWhileScrubbingOnSTV` is enabled.
44015   *
44016   * @param {number} stepAmount - The number of seconds to step (positive for forward, negative for backward).
44017   */
44018  handlePendingSeek_(stepAmount) {
44019    if (!this.player_.paused()) {
44020      this.player_.pause();
44021    }
44022    const currentPos = this.pendingSeekTime() !== null ? this.pendingSeekTime() : this.player_.currentTime();
44023    this.pendingSeekTime(currentPos + stepAmount);
44024    this.player_.trigger({
44025      type: 'timeupdate',
44026      target: this,
44027      manuallyTriggered: true
44028    });
44029  }
44030
44031  /**
44032   * Move more quickly fast forward for keyboard-only users
44033   */
44034  stepForward() {
44035    // if `disableSeekWhileScrubbingOnSTV: true`, keep track of the desired seek point but we won't initiate the seek
44036    if (this.shouldDisableSeekWhileScrubbing_) {
44037      this.handlePendingSeek_(this.options().stepSeconds);
44038    } else {
44039      this.userSeek_(this.player_.currentTime() + this.options().stepSeconds);
44040    }
44041  }
44042
44043  /**
44044   * Move more quickly rewind for keyboard-only users
44045   */
44046  stepBack() {
44047    // if `disableSeekWhileScrubbingOnSTV: true`, keep track of the desired seek point but we won't initiate the seek
44048    if (this.shouldDisableSeekWhileScrubbing_) {
44049      this.handlePendingSeek_(-this.options().stepSeconds);
44050    } else {
44051      this.userSeek_(this.player_.currentTime() - this.options().stepSeconds);
44052    }
44053  }
44054
44055  /**
44056   * Toggles the playback state of the player
44057   * This gets called when enter or space is used on the seekbar
44058   *
44059   * @param {KeyboardEvent} event
vendor: 6,586 bytes, lines 44060-44267
44060   *        The `keydown` event that caused this function to be called
44061   *
44062   */
44063  handleAction(event) {
44064    if (this.pendingSeekTime() !== null) {
44065      this.userSeek_(this.pendingSeekTime());
44066      this.pendingSeekTime(null);
44067    }
44068    if (this.player_.paused()) {
44069      this.player_.play();
44070    } else {
44071      this.player_.pause();
44072    }
44073  }
44074
44075  /**
44076   * Called when this SeekBar has focus and a key gets pressed down.
44077   * Supports the following keys:
44078   *
44079   *   Space or Enter key fire a click event
44080   *   Home key moves to start of the timeline
44081   *   End key moves to end of the timeline
44082   *   Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline
44083   *   PageDown key moves back a larger step than ArrowDown
44084   *   PageUp key moves forward a large step
44085   *
44086   * @param {KeyboardEvent} event
44087   *        The `keydown` event that caused this function to be called.
44088   *
44089   * @listens keydown
44090   */
44091  handleKeyDown(event) {
44092    const liveTracker = this.player_.liveTracker;
44093    if (event.key === ' ' || event.key === 'Enter') {
44094      event.preventDefault();
44095      event.stopPropagation();
44096      this.handleAction(event);
44097    } else if (event.key === 'Home') {
44098      event.preventDefault();
44099      event.stopPropagation();
44100      this.userSeek_(0);
44101    } else if (event.key === 'End') {
44102      event.preventDefault();
44103      event.stopPropagation();
44104      if (liveTracker && liveTracker.isLive()) {
44105        this.userSeek_(liveTracker.liveCurrentTime());
44106      } else {
44107        this.userSeek_(this.player_.duration());
44108      }
44109    } else if (/^[0-9]$/.test(event.key)) {
44110      event.preventDefault();
44111      event.stopPropagation();
44112      const gotoFraction = parseInt(event.key, 10) * 0.1;
44113      if (liveTracker && liveTracker.isLive()) {
44114        this.userSeek_(liveTracker.seekableStart() + liveTracker.liveWindow() * gotoFraction);
44115      } else {
44116        this.userSeek_(this.player_.duration() * gotoFraction);
44117      }
44118    } else if (event.key === 'PageDown') {
44119      event.preventDefault();
44120      event.stopPropagation();
44121      this.userSeek_(this.player_.currentTime() - this.options().stepSeconds * this.options().pageMultiplier);
44122    } else if (event.key === 'PageUp') {
44123      event.preventDefault();
44124      event.stopPropagation();
44125      this.userSeek_(this.player_.currentTime() + this.options().stepSeconds * this.options().pageMultiplier);
44126    } else {
44127      // Pass keydown handling up for unsupported keys
44128      super.handleKeyDown(event);
44129    }
44130  }
44131  dispose() {
44132    this.disableInterval_();
44133    this.off(this.player_, ['durationchange', 'timeupdate'], this.update);
44134    this.off(this.player_, ['ended'], this.update_);
44135    if (this.player_.liveTracker) {
44136      this.off(this.player_.liveTracker, 'liveedgechange', this.update);
44137    }
44138    this.off(this.player_, ['playing'], this.enableIntervalHandler_);
44139    this.off(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_);
44140
44141    // we don't need to update the play progress if the document is hidden,
44142    // also, this causes the CPU to spike and eventually crash the page on IE11.
44143    if ('hidden' in document__default["default"] && 'visibilityState' in document__default["default"]) {
44144      this.off(document__default["default"], 'visibilitychange', this.toggleVisibility_);
44145    }
44146    super.dispose();
44147  }
44148}
44149
44150/**
44151 * Default options for the `SeekBar`
44152 *
44153 * @type {Object}
44154 * @private
44155 */
44156SeekBar.prototype.options_ = {
44157  children: ['loadProgressBar', 'playProgressBar'],
44158  barName: 'playProgressBar',
44159  stepSeconds: 5,
44160  pageMultiplier: 12
44161};
44162Component$1.registerComponent('SeekBar', SeekBar);
44163
44164/**
44165 * @file progress-control.js
44166 */
44167
44168/**
44169 * The Progress Control component contains the seek bar, load progress,
44170 * and play progress.
44171 *
44172 * @extends Component
44173 */
44174class ProgressControl extends Component$1 {
44175  /**
44176   * Creates an instance of this class.
44177   *
44178   * @param {Player} player
44179   *        The `Player` that this class should be attached to.
44180   *
44181   * @param {Object} [options]
44182   *        The key/value store of player options.
44183   */
44184  constructor(player, options) {
44185    super(player, options);
44186    this.handleMouseMove = throttle(bind_(this, this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
44187    this.throttledHandleMouseSeek = throttle(bind_(this, this.handleMouseSeek), UPDATE_REFRESH_INTERVAL);
44188    this.handleMouseUpHandler_ = e => this.handleMouseUp(e);
44189    this.handleMouseDownHandler_ = e => this.handleMouseDown(e);
44190    this.enable();
44191  }
44192
44193  /**
44194   * Create the `Component`'s DOM element
44195   *
44196   * @return {Element}
44197   *         The element that was created.
44198   */
44199  createEl() {
44200    return super.createEl('div', {
44201      className: 'vjs-progress-control vjs-control'
44202    });
44203  }
44204
44205  /**
44206   * When the mouse moves over the `ProgressControl`, the pointer position
44207   * gets passed down to the `MouseTimeDisplay` component.
44208   *
44209   * @param {Event} event
44210   *        The `mousemove` event that caused this function to run.
44211   *
44212   * @listen mousemove
44213   */
44214  handleMouseMove(event) {
44215    const seekBar = this.getChild('seekBar');
44216    if (!seekBar) {
44217      return;
44218    }
44219    const playProgressBar = seekBar.getChild('playProgressBar');
44220    const mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
44221    if (!playProgressBar && !mouseTimeDisplay) {
44222      return;
44223    }
44224    const seekBarEl = seekBar.el();
44225    const seekBarRect = findPosition(seekBarEl);
44226    let seekBarPoint = getPointerPosition(seekBarEl, event).x;
44227
44228    // The default skin has a gap on either side of the `SeekBar`. This means
44229    // that it's possible to trigger this behavior outside the boundaries of
44230    // the `SeekBar`. This ensures we stay within it at all times.
44231    seekBarPoint = clamp(seekBarPoint, 0, 1);
44232    if (mouseTimeDisplay) {
44233      mouseTimeDisplay.update(seekBarRect, seekBarPoint);
44234    }
44235    if (playProgressBar) {
44236      playProgressBar.update(seekBarRect, seekBar.getProgress());
44237    }
44238  }
44239
44240  /**
44241   * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
44242   *
44243   * @method ProgressControl#throttledHandleMouseSeek
44244   * @param {Event} event
44245   *        The `mousemove` event that caused this function to run.
44246   *
44247   * @listen mousemove
44248   * @listen touchmove
44249   */
44250
44251  /**
44252   * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
44253   *
44254   * @param {Event} event
44255   *        `mousedown` or `touchstart` event that triggered this function
44256   *
44257   * @listens mousemove
44258   * @listens touchmove
44259   */
44260  handleMouseSeek(event) {
44261    const seekBar = this.getChild('seekBar');
44262    if (seekBar) {
44263      seekBar.handleMouseMove(event);
44264    }
44265  }
44266
44267  /**
vendor: 1,071 bytes, lines 44268-44304
44268   * Are controls are currently enabled for this progress control.
44269   *
44270   * @return {boolean}
44271   *         true if controls are enabled, false otherwise
44272   */
44273  enabled() {
44274    return this.enabled_;
44275  }
44276
44277  /**
44278   * Disable all controls on the progress control and its children
44279   */
44280  disable() {
44281    this.children().forEach(child => child.disable && child.disable());
44282    if (!this.enabled()) {
44283      return;
44284    }
44285    this.off(['mousedown', 'touchstart'], this.handleMouseDownHandler_);
44286    this.off(this.el_, ['mousemove', 'touchmove'], this.handleMouseMove);
44287    this.removeListenersAddedOnMousedownAndTouchstart();
44288    this.addClass('disabled');
44289    this.enabled_ = false;
44290
44291    // Restore normal playback state if controls are disabled while scrubbing
44292    if (this.player_.scrubbing()) {
44293      const seekBar = this.getChild('seekBar');
44294      this.player_.scrubbing(false);
44295      if (seekBar.videoWasPlaying) {
44296        silencePromise(this.player_.play());
44297      }
44298    }
44299  }
44300
44301  /**
44302   * Enable all controls on the progress control and its children
44303   */
44304  enable() {
vendor: 11,294 bytes, lines 44305-44699
44305    this.children().forEach(child => child.enable && child.enable());
44306    if (this.enabled()) {
44307      return;
44308    }
44309    this.on(['mousedown', 'touchstart'], this.handleMouseDownHandler_);
44310    this.on(this.el_, ['mousemove', 'touchmove'], this.handleMouseMove);
44311    this.removeClass('disabled');
44312    this.enabled_ = true;
44313  }
44314
44315  /**
44316   * Cleanup listeners after the user finishes interacting with the progress controls
44317   */
44318  removeListenersAddedOnMousedownAndTouchstart() {
44319    const doc = this.el_.ownerDocument;
44320    this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
44321    this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
44322    this.off(doc, 'mouseup', this.handleMouseUpHandler_);
44323    this.off(doc, 'touchend', this.handleMouseUpHandler_);
44324  }
44325
44326  /**
44327   * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
44328   *
44329   * @param {Event} event
44330   *        `mousedown` or `touchstart` event that triggered this function
44331   *
44332   * @listens mousedown
44333   * @listens touchstart
44334   */
44335  handleMouseDown(event) {
44336    const doc = this.el_.ownerDocument;
44337    const seekBar = this.getChild('seekBar');
44338    if (seekBar) {
44339      seekBar.handleMouseDown(event);
44340    }
44341    this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
44342    this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
44343    this.on(doc, 'mouseup', this.handleMouseUpHandler_);
44344    this.on(doc, 'touchend', this.handleMouseUpHandler_);
44345  }
44346
44347  /**
44348   * Handle `mouseup` or `touchend` events on the `ProgressControl`.
44349   *
44350   * @param {Event} event
44351   *        `mouseup` or `touchend` event that triggered this function.
44352   *
44353   * @listens touchend
44354   * @listens mouseup
44355   */
44356  handleMouseUp(event) {
44357    const seekBar = this.getChild('seekBar');
44358    if (seekBar) {
44359      seekBar.handleMouseUp(event);
44360    }
44361    this.removeListenersAddedOnMousedownAndTouchstart();
44362  }
44363}
44364
44365/**
44366 * Default options for `ProgressControl`
44367 *
44368 * @type {Object}
44369 * @private
44370 */
44371ProgressControl.prototype.options_ = {
44372  children: ['seekBar']
44373};
44374Component$1.registerComponent('ProgressControl', ProgressControl);
44375
44376/**
44377 * @file picture-in-picture-toggle.js
44378 */
44379
44380/** @import Player from '../player' */
44381
44382/**
44383 * Toggle Picture-in-Picture mode
44384 *
44385 * @extends Button
44386 */
44387class PictureInPictureToggle extends Button {
44388  /**
44389   * Creates an instance of this class.
44390   *
44391   * @param {Player} player
44392   *        The `Player` that this class should be attached to.
44393   *
44394   * @param {Object} [options]
44395   *        The key/value store of player options.
44396   *
44397   * @listens Player#enterpictureinpicture
44398   * @listens Player#leavepictureinpicture
44399   */
44400  constructor(player, options) {
44401    super(player, options);
44402    this.setIcon('picture-in-picture-enter');
44403    this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], e => this.handlePictureInPictureChange(e));
44404    this.on(player, ['disablepictureinpicturechanged', 'loadedmetadata'], e => this.handlePictureInPictureEnabledChange(e));
44405    this.on(player, ['loadedmetadata', 'audioonlymodechange', 'audiopostermodechange'], () => this.handlePictureInPictureAudioModeChange());
44406
44407    // TODO: Deactivate button on player emptied event.
44408    this.disable();
44409  }
44410
44411  /**
44412   * Builds the default DOM `className`.
44413   *
44414   * @return {string}
44415   *         The DOM `className` for this object.
44416   */
44417  buildCSSClass() {
44418    return `vjs-picture-in-picture-control vjs-hidden ${super.buildCSSClass()}`;
44419  }
44420
44421  /**
44422   * Displays or hides the button depending on the audio mode detection.
44423   * Exits picture-in-picture if it is enabled when switching to audio mode.
44424   */
44425  handlePictureInPictureAudioModeChange() {
44426    // This audio detection will not detect HLS or DASH audio-only streams because there was no reliable way to detect them at the time
44427    const isSourceAudio = this.player_.currentType().substring(0, 5) === 'audio';
44428    const isAudioMode = isSourceAudio || this.player_.audioPosterMode() || this.player_.audioOnlyMode();
44429    if (!isAudioMode) {
44430      this.show();
44431      return;
44432    }
44433    if (this.player_.isInPictureInPicture()) {
44434      this.player_.exitPictureInPicture();
44435    }
44436    this.hide();
44437  }
44438
44439  /**
44440   * Enables or disables button based on availability of a Picture-In-Picture mode.
44441   *
44442   * Enabled if
44443   * - `player.options().enableDocumentPictureInPicture` is true and
44444   *   window.documentPictureInPicture is available; or
44445   * - `player.disablePictureInPicture()` is false and
44446   *   element.requestPictureInPicture is available
44447   */
44448  handlePictureInPictureEnabledChange() {
44449    if (document__default["default"].pictureInPictureEnabled && this.player_.disablePictureInPicture() === false || this.player_.options_.enableDocumentPictureInPicture && 'documentPictureInPicture' in window__default["default"]) {
44450      this.enable();
44451    } else {
44452      this.disable();
44453    }
44454  }
44455
44456  /**
44457   * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly.
44458   *
44459   * @param {Event} [event]
44460   *        The {@link Player#enterpictureinpicture} or {@link Player#leavepictureinpicture} event that caused this function to be
44461   *        called.
44462   *
44463   * @listens Player#enterpictureinpicture
44464   * @listens Player#leavepictureinpicture
44465   */
44466  handlePictureInPictureChange(event) {
44467    if (this.player_.isInPictureInPicture()) {
44468      this.setIcon('picture-in-picture-exit');
44469      this.controlText('Exit Picture-in-Picture');
44470    } else {
44471      this.setIcon('picture-in-picture-enter');
44472      this.controlText('Picture-in-Picture');
44473    }
44474    this.handlePictureInPictureEnabledChange();
44475  }
44476
44477  /**
44478   * This gets called when an `PictureInPictureToggle` is "clicked". See
44479   * {@link ClickableComponent} for more detailed information on what a click can be.
44480   *
44481   * @param {Event} [event]
44482   *        The `keydown`, `tap`, or `click` event that caused this function to be
44483   *        called.
44484   *
44485   * @listens tap
44486   * @listens click
44487   */
44488  handleClick(event) {
44489    if (!this.player_.isInPictureInPicture()) {
44490      this.player_.requestPictureInPicture();
44491    } else {
44492      this.player_.exitPictureInPicture();
44493    }
44494  }
44495
44496  /**
44497   * Show the `Component`s element if it is hidden by removing the
44498   * 'vjs-hidden' class name from it only in browsers that support the Picture-in-Picture API.
44499   */
44500  show() {
44501    // Does not allow to display the pictureInPictureToggle in browsers that do not support the Picture-in-Picture API, e.g. Firefox.
44502    if (typeof document__default["default"].exitPictureInPicture !== 'function') {
44503      return;
44504    }
44505    super.show();
44506  }
44507}
44508
44509/**
44510 * The text that should display over the `PictureInPictureToggle`s controls. Added for localization.
44511 *
44512 * @type {string}
44513 * @protected
44514 */
44515PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture';
44516Component$1.registerComponent('PictureInPictureToggle', PictureInPictureToggle);
44517
44518/**
44519 * @file fullscreen-toggle.js
44520 */
44521
44522/** @import Player from '../player' */
44523
44524/**
44525 * Toggle fullscreen video
44526 *
44527 * @extends Button
44528 */
44529class FullscreenToggle extends Button {
44530  /**
44531   * Creates an instance of this class.
44532   *
44533   * @param {Player} player
44534   *        The `Player` that this class should be attached to.
44535   *
44536   * @param {Object} [options]
44537   *        The key/value store of player options.
44538   */
44539  constructor(player, options) {
44540    super(player, options);
44541    this.setIcon('fullscreen-enter');
44542    this.on(player, 'fullscreenchange', e => this.handleFullscreenChange(e));
44543    if (document__default["default"][player.fsApi_.fullscreenEnabled] === false) {
44544      this.disable();
44545    }
44546  }
44547
44548  /**
44549   * Builds the default DOM `className`.
44550   *
44551   * @return {string}
44552   *         The DOM `className` for this object.
44553   */
44554  buildCSSClass() {
44555    return `vjs-fullscreen-control ${super.buildCSSClass()}`;
44556  }
44557
44558  /**
44559   * Handles fullscreenchange on the player and change control text accordingly.
44560   *
44561   * @param {Event} [event]
44562   *        The {@link Player#fullscreenchange} event that caused this function to be
44563   *        called.
44564   *
44565   * @listens Player#fullscreenchange
44566   */
44567  handleFullscreenChange(event) {
44568    if (this.player_.isFullscreen()) {
44569      this.controlText('Exit Fullscreen');
44570      this.setIcon('fullscreen-exit');
44571    } else {
44572      this.controlText('Fullscreen');
44573      this.setIcon('fullscreen-enter');
44574    }
44575  }
44576
44577  /**
44578   * This gets called when an `FullscreenToggle` is "clicked". See
44579   * {@link ClickableComponent} for more detailed information on what a click can be.
44580   *
44581   * @param {Event} [event]
44582   *        The `keydown`, `tap`, or `click` event that caused this function to be
44583   *        called.
44584   *
44585   * @listens tap
44586   * @listens click
44587   */
44588  handleClick(event) {
44589    if (!this.player_.isFullscreen()) {
44590      this.player_.requestFullscreen();
44591    } else {
44592      this.player_.exitFullscreen();
44593    }
44594  }
44595}
44596
44597/**
44598 * The text that should display over the `FullscreenToggle`s controls. Added for localization.
44599 *
44600 * @type {string}
44601 * @protected
44602 */
44603FullscreenToggle.prototype.controlText_ = 'Fullscreen';
44604Component$1.registerComponent('FullscreenToggle', FullscreenToggle);
44605
44606/** @import Component from '../../component' */
44607/** @import Player from '../../player' */
44608
44609/**
44610 * Check if volume control is supported and if it isn't hide the
44611 * `Component` that was passed  using the `vjs-hidden` class.
44612 *
44613 * @param {Component} self
44614 *        The component that should be hidden if volume is unsupported
44615 *
44616 * @param {Player} player
44617 *        A reference to the player
44618 *
44619 * @private
44620 */
44621const checkVolumeSupport = function (self, player) {
44622  // hide volume controls when they're not supported by the current tech
44623  if (player.tech_ && !player.tech_.featuresVolumeControl) {
44624    self.addClass('vjs-hidden');
44625  }
44626  self.on(player, 'loadstart', function () {
44627    if (!player.tech_.featuresVolumeControl) {
44628      self.addClass('vjs-hidden');
44629    } else {
44630      self.removeClass('vjs-hidden');
44631    }
44632  });
44633};
44634
44635/**
44636 * @file volume-level.js
44637 */
44638
44639/**
44640 * Shows volume level
44641 *
44642 * @extends Component
44643 */
44644class VolumeLevel extends Component$1 {
44645  /**
44646   * Create the `Component`'s DOM element
44647   *
44648   * @return {Element}
44649   *         The element that was created.
44650   */
44651  createEl() {
44652    const el = super.createEl('div', {
44653      className: 'vjs-volume-level'
44654    });
44655    this.setIcon('circle', el);
44656    el.appendChild(super.createEl('span', {
44657      className: 'vjs-control-text'
44658    }));
44659    return el;
44660  }
44661}
44662Component$1.registerComponent('VolumeLevel', VolumeLevel);
44663
44664/**
44665 * @file volume-level-tooltip.js
44666 */
44667
44668/** @import Player from '../../player' */
44669
44670/**
44671 * Volume level tooltips display a volume above or side by side the volume bar.
44672 *
44673 * @extends Component
44674 */
44675class VolumeLevelTooltip extends Component$1 {
44676  /**
44677   * Creates an instance of this class.
44678   *
44679   * @param {Player} player
44680   *        The {@link Player} that this class should be attached to.
44681   *
44682   * @param {Object} [options]
44683   *        The key/value store of player options.
44684   */
44685  constructor(player, options) {
44686    super(player, options);
44687    this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
44688  }
44689
44690  /**
44691   * Create the volume tooltip DOM element
44692   *
44693   * @return {Element}
44694   *         The element that was created.
44695   */
44696  createEl() {
44697    return super.createEl('div', {
44698      className: 'vjs-volume-tooltip'
44699    }, {
vendor: 20,287 bytes, lines 44700-45414
44700      'aria-hidden': 'true'
44701    });
44702  }
44703
44704  /**
44705   * Updates the position of the tooltip relative to the `VolumeBar` and
44706   * its content text.
44707   *
44708   * @param {Object} rangeBarRect
44709   *        The `ClientRect` for the {@link VolumeBar} element.
44710   *
44711   * @param {number} rangeBarPoint
44712   *        A number from 0 to 1, representing a horizontal/vertical reference point
44713   *        from the left edge of the {@link VolumeBar}
44714   *
44715   * @param {boolean} vertical
44716   *        Referees to the Volume control position
44717   *        in the control bar{@link VolumeControl}
44718   *
44719   */
44720  update(rangeBarRect, rangeBarPoint, vertical, content) {
44721    if (!vertical) {
44722      const tooltipRect = getBoundingClientRect(this.el_);
44723      const playerRect = getBoundingClientRect(this.player_.el());
44724      const volumeBarPointPx = rangeBarRect.width * rangeBarPoint;
44725      if (!playerRect || !tooltipRect) {
44726        return;
44727      }
44728      const spaceLeftOfPoint = rangeBarRect.left - playerRect.left + volumeBarPointPx;
44729      const spaceRightOfPoint = rangeBarRect.width - volumeBarPointPx + (playerRect.right - rangeBarRect.right);
44730      let pullTooltipBy = tooltipRect.width / 2;
44731      if (spaceLeftOfPoint < pullTooltipBy) {
44732        pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
44733      } else if (spaceRightOfPoint < pullTooltipBy) {
44734        pullTooltipBy = spaceRightOfPoint;
44735      }
44736      if (pullTooltipBy < 0) {
44737        pullTooltipBy = 0;
44738      } else if (pullTooltipBy > tooltipRect.width) {
44739        pullTooltipBy = tooltipRect.width;
44740      }
44741      this.el_.style.right = `-${pullTooltipBy}px`;
44742    }
44743    this.write(`${content}%`);
44744  }
44745
44746  /**
44747   * Write the volume to the tooltip DOM element.
44748   *
44749   * @param {string} content
44750   *        The formatted volume for the tooltip.
44751   */
44752  write(content) {
44753    textContent(this.el_, content);
44754  }
44755
44756  /**
44757   * Updates the position of the volume tooltip relative to the `VolumeBar`.
44758   *
44759   * @param {Object} rangeBarRect
44760   *        The `ClientRect` for the {@link VolumeBar} element.
44761   *
44762   * @param {number} rangeBarPoint
44763   *        A number from 0 to 1, representing a horizontal/vertical reference point
44764   *        from the left edge of the {@link VolumeBar}
44765   *
44766   * @param {boolean} vertical
44767   *        Referees to the Volume control position
44768   *        in the control bar{@link VolumeControl}
44769   *
44770   * @param {number} volume
44771   *        The volume level to update the tooltip to
44772   *
44773   * @param {Function} cb
44774   *        A function that will be called during the request animation frame
44775   *        for tooltips that need to do additional animations from the default
44776   */
44777  updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, cb) {
44778    this.requestNamedAnimationFrame('VolumeLevelTooltip#updateVolume', () => {
44779      this.update(rangeBarRect, rangeBarPoint, vertical, volume.toFixed(0));
44780      if (cb) {
44781        cb();
44782      }
44783    });
44784  }
44785}
44786Component$1.registerComponent('VolumeLevelTooltip', VolumeLevelTooltip);
44787
44788/**
44789 * @file mouse-volume-level-display.js
44790 */
44791
44792/**
44793 * The {@link MouseVolumeLevelDisplay} component tracks mouse movement over the
44794 * {@link VolumeControl}. It displays an indicator and a {@link VolumeLevelTooltip}
44795 * indicating the volume level which is represented by a given point in the
44796 * {@link VolumeBar}.
44797 *
44798 * @extends Component
44799 */
44800class MouseVolumeLevelDisplay extends Component$1 {
44801  /**
44802   * Creates an instance of this class.
44803   *
44804   * @param {Player} player
44805   *        The {@link Player} that this class should be attached to.
44806   *
44807   * @param {Object} [options]
44808   *        The key/value store of player options.
44809   */
44810  constructor(player, options) {
44811    super(player, options);
44812    this.update = throttle(bind_(this, this.update), UPDATE_REFRESH_INTERVAL);
44813  }
44814
44815  /**
44816   * Create the DOM element for this class.
44817   *
44818   * @return {Element}
44819   *         The element that was created.
44820   */
44821  createEl() {
44822    return super.createEl('div', {
44823      className: 'vjs-mouse-display'
44824    });
44825  }
44826
44827  /**
44828   * Enquires updates to its own DOM as well as the DOM of its
44829   * {@link VolumeLevelTooltip} child.
44830   *
44831   * @param {Object} rangeBarRect
44832   *        The `ClientRect` for the {@link VolumeBar} element.
44833   *
44834   * @param {number} rangeBarPoint
44835   *        A number from 0 to 1, representing a horizontal/vertical reference point
44836   *        from the left edge of the {@link VolumeBar}
44837   *
44838   * @param {boolean} vertical
44839   *        Referees to the Volume control position
44840   *        in the control bar{@link VolumeControl}
44841   *
44842   */
44843  update(rangeBarRect, rangeBarPoint, vertical) {
44844    const volume = 100 * rangeBarPoint;
44845    this.getChild('volumeLevelTooltip').updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, () => {
44846      if (vertical) {
44847        this.el_.style.bottom = `${rangeBarRect.height * rangeBarPoint}px`;
44848      } else {
44849        this.el_.style.left = `${rangeBarRect.width * rangeBarPoint}px`;
44850      }
44851    });
44852  }
44853}
44854
44855/**
44856 * Default options for `MouseVolumeLevelDisplay`
44857 *
44858 * @type {Object}
44859 * @private
44860 */
44861MouseVolumeLevelDisplay.prototype.options_ = {
44862  children: ['volumeLevelTooltip']
44863};
44864Component$1.registerComponent('MouseVolumeLevelDisplay', MouseVolumeLevelDisplay);
44865
44866/**
44867 * @file volume-bar.js
44868 */
44869
44870/**
44871 * The bar that contains the volume level and can be clicked on to adjust the level
44872 *
44873 * @extends Slider
44874 */
44875class VolumeBar extends Slider {
44876  /**
44877   * Creates an instance of this class.
44878   *
44879   * @param {Player} player
44880   *        The `Player` that this class should be attached to.
44881   *
44882   * @param {Object} [options]
44883   *        The key/value store of player options.
44884   */
44885  constructor(player, options) {
44886    super(player, options);
44887    this.on('slideractive', e => this.updateLastVolume_(e));
44888    this.on(player, 'volumechange', e => this.updateARIAAttributes(e));
44889    player.ready(() => this.updateARIAAttributes());
44890  }
44891
44892  /**
44893   * Create the `Component`'s DOM element
44894   *
44895   * @return {Element}
44896   *         The element that was created.
44897   */
44898  createEl() {
44899    return super.createEl('div', {
44900      className: 'vjs-volume-bar vjs-slider-bar'
44901    }, {
44902      'aria-label': this.localize('Volume Level'),
44903      'aria-live': 'polite'
44904    });
44905  }
44906
44907  /**
44908   * Handle mouse down on volume bar
44909   *
44910   * @param {Event} event
44911   *        The `mousedown` event that caused this to run.
44912   *
44913   * @listens mousedown
44914   */
44915  handleMouseDown(event) {
44916    if (!isSingleLeftClick(event)) {
44917      return;
44918    }
44919    super.handleMouseDown(event);
44920  }
44921
44922  /**
44923   * Handle movement events on the {@link VolumeMenuButton}.
44924   *
44925   * @param {Event} event
44926   *        The event that caused this function to run.
44927   *
44928   * @listens mousemove
44929   */
44930  handleMouseMove(event) {
44931    const mouseVolumeLevelDisplay = this.getChild('mouseVolumeLevelDisplay');
44932    if (mouseVolumeLevelDisplay) {
44933      const volumeBarEl = this.el();
44934      const volumeBarRect = getBoundingClientRect(volumeBarEl);
44935      const vertical = this.vertical();
44936      let volumeBarPoint = getPointerPosition(volumeBarEl, event);
44937      volumeBarPoint = vertical ? volumeBarPoint.y : volumeBarPoint.x;
44938      // The default skin has a gap on either side of the `VolumeBar`. This means
44939      // that it's possible to trigger this behavior outside the boundaries of
44940      // the `VolumeBar`. This ensures we stay within it at all times.
44941      volumeBarPoint = clamp(volumeBarPoint, 0, 1);
44942      mouseVolumeLevelDisplay.update(volumeBarRect, volumeBarPoint, vertical);
44943    }
44944    if (!isSingleLeftClick(event)) {
44945      return;
44946    }
44947    this.checkMuted();
44948    this.player_.volume(this.calculateDistance(event));
44949  }
44950
44951  /**
44952   * If the player is muted unmute it.
44953   */
44954  checkMuted() {
44955    if (this.player_.muted()) {
44956      this.player_.muted(false);
44957    }
44958  }
44959
44960  /**
44961   * Get percent of volume level
44962   *
44963   * @return {number}
44964   *         Volume level percent as a decimal number.
44965   */
44966  getPercent() {
44967    if (this.player_.muted()) {
44968      return 0;
44969    }
44970    return this.player_.volume();
44971  }
44972
44973  /**
44974   * Increase volume level for keyboard users
44975   */
44976  stepForward() {
44977    this.checkMuted();
44978    this.player_.volume(this.player_.volume() + 0.1);
44979  }
44980
44981  /**
44982   * Decrease volume level for keyboard users
44983   */
44984  stepBack() {
44985    this.checkMuted();
44986    this.player_.volume(this.player_.volume() - 0.1);
44987  }
44988
44989  /**
44990   * Update ARIA accessibility attributes
44991   *
44992   * @param {Event} [event]
44993   *        The `volumechange` event that caused this function to run.
44994   *
44995   * @listens Player#volumechange
44996   */
44997  updateARIAAttributes(event) {
44998    const ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
44999    this.el_.setAttribute('aria-valuenow', ariaValue);
45000    this.el_.setAttribute('aria-valuetext', ariaValue + '%');
45001  }
45002
45003  /**
45004   * Returns the current value of the player volume as a percentage
45005   *
45006   * @private
45007   */
45008  volumeAsPercentage_() {
45009    return Math.round(this.player_.volume() * 100);
45010  }
45011
45012  /**
45013   * When user starts dragging the VolumeBar, store the volume and listen for
45014   * the end of the drag. When the drag ends, if the volume was set to zero,
45015   * set lastVolume to the stored volume.
45016   *
45017   * @listens slideractive
45018   * @private
45019   */
45020  updateLastVolume_() {
45021    const volumeBeforeDrag = this.player_.volume();
45022    this.one('sliderinactive', () => {
45023      if (this.player_.volume() === 0) {
45024        this.player_.lastVolume_(volumeBeforeDrag);
45025      }
45026    });
45027  }
45028}
45029
45030/**
45031 * Default options for the `VolumeBar`
45032 *
45033 * @type {Object}
45034 * @private
45035 */
45036VolumeBar.prototype.options_ = {
45037  children: ['volumeLevel'],
45038  barName: 'volumeLevel'
45039};
45040
45041// MouseVolumeLevelDisplay tooltip should not be added to a player on mobile devices
45042if (!IS_IOS && !IS_ANDROID) {
45043  VolumeBar.prototype.options_.children.splice(0, 0, 'mouseVolumeLevelDisplay');
45044}
45045
45046/**
45047 * Call the update event for this Slider when this event happens on the player.
45048 *
45049 * @type {string}
45050 */
45051VolumeBar.prototype.playerEvent = 'volumechange';
45052Component$1.registerComponent('VolumeBar', VolumeBar);
45053
45054/**
45055 * @file volume-control.js
45056 */
45057
45058/**
45059 * The component for controlling the volume level
45060 *
45061 * @extends Component
45062 */
45063class VolumeControl extends Component$1 {
45064  /**
45065   * Creates an instance of this class.
45066   *
45067   * @param {Player} player
45068   *        The `Player` that this class should be attached to.
45069   *
45070   * @param {Object} [options={}]
45071   *        The key/value store of player options.
45072   */
45073  constructor(player, options = {}) {
45074    options.vertical = options.vertical || false;
45075
45076    // Pass the vertical option down to the VolumeBar if
45077    // the VolumeBar is turned on.
45078    if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
45079      options.volumeBar = options.volumeBar || {};
45080      options.volumeBar.vertical = options.vertical;
45081    }
45082    super(player, options);
45083
45084    // hide this control if volume support is missing
45085    checkVolumeSupport(this, player);
45086    this.throttledHandleMouseMove = throttle(bind_(this, this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
45087    this.handleMouseUpHandler_ = e => this.handleMouseUp(e);
45088    this.on('mousedown', e => this.handleMouseDown(e));
45089    this.on('touchstart', e => this.handleMouseDown(e));
45090    this.on('mousemove', e => this.handleMouseMove(e));
45091
45092    // while the slider is active (the mouse has been pressed down and
45093    // is dragging) or in focus we do not want to hide the VolumeBar
45094    this.on(this.volumeBar, ['focus', 'slideractive'], () => {
45095      this.volumeBar.addClass('vjs-slider-active');
45096      this.addClass('vjs-slider-active');
45097      this.trigger('slideractive');
45098    });
45099    this.on(this.volumeBar, ['blur', 'sliderinactive'], () => {
45100      this.volumeBar.removeClass('vjs-slider-active');
45101      this.removeClass('vjs-slider-active');
45102      this.trigger('sliderinactive');
45103    });
45104  }
45105
45106  /**
45107   * Create the `Component`'s DOM element
45108   *
45109   * @return {Element}
45110   *         The element that was created.
45111   */
45112  createEl() {
45113    let orientationClass = 'vjs-volume-horizontal';
45114    if (this.options_.vertical) {
45115      orientationClass = 'vjs-volume-vertical';
45116    }
45117    return super.createEl('div', {
45118      className: `vjs-volume-control vjs-control ${orientationClass}`
45119    });
45120  }
45121
45122  /**
45123   * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
45124   *
45125   * @param {Event} event
45126   *        `mousedown` or `touchstart` event that triggered this function
45127   *
45128   * @listens mousedown
45129   * @listens touchstart
45130   */
45131  handleMouseDown(event) {
45132    const doc = this.el_.ownerDocument;
45133    this.on(doc, 'mousemove', this.throttledHandleMouseMove);
45134    this.on(doc, 'touchmove', this.throttledHandleMouseMove);
45135    this.on(doc, 'mouseup', this.handleMouseUpHandler_);
45136    this.on(doc, 'touchend', this.handleMouseUpHandler_);
45137  }
45138
45139  /**
45140   * Handle `mouseup` or `touchend` events on the `VolumeControl`.
45141   *
45142   * @param {Event} event
45143   *        `mouseup` or `touchend` event that triggered this function.
45144   *
45145   * @listens touchend
45146   * @listens mouseup
45147   */
45148  handleMouseUp(event) {
45149    const doc = this.el_.ownerDocument;
45150    this.off(doc, 'mousemove', this.throttledHandleMouseMove);
45151    this.off(doc, 'touchmove', this.throttledHandleMouseMove);
45152    this.off(doc, 'mouseup', this.handleMouseUpHandler_);
45153    this.off(doc, 'touchend', this.handleMouseUpHandler_);
45154  }
45155
45156  /**
45157   * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
45158   *
45159   * @param {Event} event
45160   *        `mousedown` or `touchstart` event that triggered this function
45161   *
45162   * @listens mousedown
45163   * @listens touchstart
45164   */
45165  handleMouseMove(event) {
45166    this.volumeBar.handleMouseMove(event);
45167  }
45168}
45169
45170/**
45171 * Default options for the `VolumeControl`
45172 *
45173 * @type {Object}
45174 * @private
45175 */
45176VolumeControl.prototype.options_ = {
45177  children: ['volumeBar']
45178};
45179Component$1.registerComponent('VolumeControl', VolumeControl);
45180
45181/** @import Component from '../../component' */
45182/** @import Player from '../../player' */
45183
45184/**
45185 * Check if muting volume is supported and if it isn't hide the mute toggle
45186 * button.
45187 *
45188 * @param {Component} self
45189 *        A reference to the mute toggle button
45190 *
45191 * @param {Player} player
45192 *        A reference to the player
45193 *
45194 * @private
45195 */
45196const checkMuteSupport = function (self, player) {
45197  // hide mute toggle button if it's not supported by the current tech
45198  if (player.tech_ && !player.tech_.featuresMuteControl) {
45199    self.addClass('vjs-hidden');
45200  }
45201  self.on(player, 'loadstart', function () {
45202    if (!player.tech_.featuresMuteControl) {
45203      self.addClass('vjs-hidden');
45204    } else {
45205      self.removeClass('vjs-hidden');
45206    }
45207  });
45208};
45209
45210/**
45211 * @file mute-toggle.js
45212 */
45213
45214/** @import Player from '../player' */
45215
45216/**
45217 * A button component for muting the audio.
45218 *
45219 * @extends Button
45220 */
45221class MuteToggle extends Button {
45222  /**
45223   * Creates an instance of this class.
45224   *
45225   * @param {Player} player
45226   *        The `Player` that this class should be attached to.
45227   *
45228   * @param {Object} [options]
45229   *        The key/value store of player options.
45230   */
45231  constructor(player, options) {
45232    super(player, options);
45233
45234    // hide this control if volume support is missing
45235    checkMuteSupport(this, player);
45236    this.on(player, ['loadstart', 'volumechange'], e => this.update(e));
45237  }
45238
45239  /**
45240   * Builds the default DOM `className`.
45241   *
45242   * @return {string}
45243   *         The DOM `className` for this object.
45244   */
45245  buildCSSClass() {
45246    return `vjs-mute-control ${super.buildCSSClass()}`;
45247  }
45248
45249  /**
45250   * This gets called when an `MuteToggle` is "clicked". See
45251   * {@link ClickableComponent} for more detailed information on what a click can be.
45252   *
45253   * @param {Event} [event]
45254   *        The `keydown`, `tap`, or `click` event that caused this function to be
45255   *        called.
45256   *
45257   * @listens tap
45258   * @listens click
45259   */
45260  handleClick(event) {
45261    const vol = this.player_.volume();
45262    const lastVolume = this.player_.lastVolume_();
45263    if (vol === 0) {
45264      const volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
45265      this.player_.volume(volumeToSet);
45266      this.player_.muted(false);
45267    } else {
45268      this.player_.muted(this.player_.muted() ? false : true);
45269    }
45270  }
45271
45272  /**
45273   * Update the `MuteToggle` button based on the state of `volume` and `muted`
45274   * on the player.
45275   *
45276   * @param {Event} [event]
45277   *        The {@link Player#loadstart} event if this function was called
45278   *        through an event.
45279   *
45280   * @listens Player#loadstart
45281   * @listens Player#volumechange
45282   */
45283  update(event) {
45284    this.updateIcon_();
45285    this.updateControlText_();
45286  }
45287
45288  /**
45289   * Update the appearance of the `MuteToggle` icon.
45290   *
45291   * Possible states (given `level` variable below):
45292   * - 0: crossed out
45293   * - 1: zero bars of volume
45294   * - 2: one bar of volume
45295   * - 3: two bars of volume
45296   *
45297   * @private
45298   */
45299  updateIcon_() {
45300    const vol = this.player_.volume();
45301    let level = 3;
45302    this.setIcon('volume-high');
45303
45304    // in iOS when a player is loaded with muted attribute
45305    // and volume is changed with a native mute button
45306    // we want to make sure muted state is updated
45307    if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) {
45308      this.player_.muted(this.player_.tech_.el_.muted);
45309    }
45310    if (vol === 0 || this.player_.muted()) {
45311      this.setIcon('volume-mute');
45312      level = 0;
45313    } else if (vol < 0.33) {
45314      this.setIcon('volume-low');
45315      level = 1;
45316    } else if (vol < 0.67) {
45317      this.setIcon('volume-medium');
45318      level = 2;
45319    }
45320    removeClass(this.el_, [0, 1, 2, 3].reduce((str, i) => str + `${i ? ' ' : ''}vjs-vol-${i}`, ''));
45321    addClass(this.el_, `vjs-vol-${level}`);
45322  }
45323
45324  /**
45325   * If `muted` has changed on the player, update the control text
45326   * (`title` attribute on `vjs-mute-control` element and content of
45327   * `vjs-control-text` element).
45328   *
45329   * @private
45330   */
45331  updateControlText_() {
45332    const soundOff = this.player_.muted() || this.player_.volume() === 0;
45333    const text = soundOff ? 'Unmute' : 'Mute';
45334    if (this.controlText() !== text) {
45335      this.controlText(text);
45336    }
45337  }
45338}
45339
45340/**
45341 * The text that should display over the `MuteToggle`s controls. Added for localization.
45342 *
45343 * @type {string}
45344 * @protected
45345 */
45346MuteToggle.prototype.controlText_ = 'Mute';
45347Component$1.registerComponent('MuteToggle', MuteToggle);
45348
45349/**
45350 * @file volume-control.js
45351 */
45352
45353/**
45354 * A Component to contain the MuteToggle and VolumeControl so that
45355 * they can work together.
45356 *
45357 * @extends Component
45358 */
45359class VolumePanel extends Component$1 {
45360  /**
45361   * Creates an instance of this class.
45362   *
45363   * @param {Player} player
45364   *        The `Player` that this class should be attached to.
45365   *
45366   * @param {Object} [options={}]
45367   *        The key/value store of player options.
45368   */
45369  constructor(player, options = {}) {
45370    if (typeof options.inline !== 'undefined') {
45371      options.inline = options.inline;
45372    } else {
45373      options.inline = true;
45374    }
45375
45376    // pass the inline option down to the VolumeControl as vertical if
45377    // the VolumeControl is on.
45378    if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
45379      options.volumeControl = options.volumeControl || {};
45380      options.volumeControl.vertical = !options.inline;
45381    }
45382    super(player, options);
45383
45384    // this handler is used by mouse handler methods below
45385    this.handleKeyPressHandler_ = e => this.handleKeyPress(e);
45386    this.on(player, ['loadstart'], e => this.volumePanelState_(e));
45387    this.on(this.muteToggle, 'keyup', e => this.handleKeyPress(e));
45388    this.on(this.volumeControl, 'keyup', e => this.handleVolumeControlKeyUp(e));
45389    this.on('keydown', e => this.handleKeyPress(e));
45390    this.on('mouseover', e => this.handleMouseOver(e));
45391    this.on('mouseout', e => this.handleMouseOut(e));
45392
45393    // while the slider is active (the mouse has been pressed down and
45394    // is dragging) we do not want to hide the VolumeBar
45395    this.on(this.volumeControl, ['slideractive'], this.sliderActive_);
45396    this.on(this.volumeControl, ['sliderinactive'], this.sliderInactive_);
45397  }
45398
45399  /**
45400   * Add vjs-slider-active class to the VolumePanel
45401   *
45402   * @listens VolumeControl#slideractive
45403   * @private
45404   */
45405  sliderActive_() {
45406    this.addClass('vjs-slider-active');
45407  }
45408
45409  /**
45410   * Removes vjs-slider-active class to the VolumePanel
45411   *
45412   * @listens VolumeControl#sliderinactive
45413   * @private
45414   */
vendor: 20,472 bytes, lines 45415-46140
45415  sliderInactive_() {
45416    this.removeClass('vjs-slider-active');
45417  }
45418
45419  /**
45420   * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
45421   * depending on MuteToggle and VolumeControl state
45422   *
45423   * @listens Player#loadstart
45424   * @private
45425   */
45426  volumePanelState_() {
45427    // hide volume panel if neither volume control or mute toggle
45428    // are displayed
45429    if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
45430      this.addClass('vjs-hidden');
45431    }
45432
45433    // if only mute toggle is visible we don't want
45434    // volume panel expanding when hovered or active
45435    if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
45436      this.addClass('vjs-mute-toggle-only');
45437    }
45438  }
45439
45440  /**
45441   * Create the `Component`'s DOM element
45442   *
45443   * @return {Element}
45444   *         The element that was created.
45445   */
45446  createEl() {
45447    let orientationClass = 'vjs-volume-panel-horizontal';
45448    if (!this.options_.inline) {
45449      orientationClass = 'vjs-volume-panel-vertical';
45450    }
45451    return super.createEl('div', {
45452      className: `vjs-volume-panel vjs-control ${orientationClass}`
45453    });
45454  }
45455
45456  /**
45457   * Dispose of the `volume-panel` and all child components.
45458   */
45459  dispose() {
45460    this.handleMouseOut();
45461    super.dispose();
45462  }
45463
45464  /**
45465   * Handles `keyup` events on the `VolumeControl`, looking for ESC, which closes
45466   * the volume panel and sets focus on `MuteToggle`.
45467   *
45468   * @param {Event} event
45469   *        The `keyup` event that caused this function to be called.
45470   *
45471   * @listens keyup
45472   */
45473  handleVolumeControlKeyUp(event) {
45474    if (event.key === 'Escape') {
45475      this.muteToggle.focus();
45476    }
45477  }
45478
45479  /**
45480   * This gets called when a `VolumePanel` gains hover via a `mouseover` event.
45481   * Turns on listening for `mouseover` event. When they happen it
45482   * calls `this.handleMouseOver`.
45483   *
45484   * @param {Event} event
45485   *        The `mouseover` event that caused this function to be called.
45486   *
45487   * @listens mouseover
45488   */
45489  handleMouseOver(event) {
45490    this.addClass('vjs-hover');
45491    on(document__default["default"], 'keyup', this.handleKeyPressHandler_);
45492  }
45493
45494  /**
45495   * This gets called when a `VolumePanel` gains hover via a `mouseout` event.
45496   * Turns on listening for `mouseout` event. When they happen it
45497   * calls `this.handleMouseOut`.
45498   *
45499   * @param {Event} event
45500   *        The `mouseout` event that caused this function to be called.
45501   *
45502   * @listens mouseout
45503   */
45504  handleMouseOut(event) {
45505    this.removeClass('vjs-hover');
45506    off(document__default["default"], 'keyup', this.handleKeyPressHandler_);
45507  }
45508
45509  /**
45510   * Handles `keyup` event on the document or `keydown` event on the `VolumePanel`,
45511   * looking for ESC, which hides the `VolumeControl`.
45512   *
45513   * @param {Event} event
45514   *        The keypress that triggered this event.
45515   *
45516   * @listens keydown | keyup
45517   */
45518  handleKeyPress(event) {
45519    if (event.key === 'Escape') {
45520      this.handleMouseOut();
45521    }
45522  }
45523}
45524
45525/**
45526 * Default options for the `VolumeControl`
45527 *
45528 * @type {Object}
45529 * @private
45530 */
45531VolumePanel.prototype.options_ = {
45532  children: ['muteToggle', 'volumeControl']
45533};
45534Component$1.registerComponent('VolumePanel', VolumePanel);
45535
45536/**
45537 * Button to skip forward a configurable amount of time
45538 * through a video. Renders in the control bar.
45539 *
45540 * e.g. options: {controlBar: {skipButtons: forward: 5}}
45541 *
45542 * @extends Button
45543 */
45544class SkipForward extends Button {
45545  constructor(player, options) {
45546    super(player, options);
45547    this.validOptions = [5, 10, 30];
45548    this.skipTime = this.getSkipForwardTime();
45549    if (this.skipTime && this.validOptions.includes(this.skipTime)) {
45550      this.setIcon(`forward-${this.skipTime}`);
45551      this.controlText(this.localize('Skip forward {1} seconds', [this.skipTime.toLocaleString(player.language())]));
45552      this.show();
45553    } else {
45554      this.hide();
45555    }
45556  }
45557  getSkipForwardTime() {
45558    const playerOptions = this.options_.playerOptions;
45559    return playerOptions.controlBar && playerOptions.controlBar.skipButtons && playerOptions.controlBar.skipButtons.forward;
45560  }
45561  buildCSSClass() {
45562    return `vjs-skip-forward-${this.getSkipForwardTime()} ${super.buildCSSClass()}`;
45563  }
45564
45565  /**
45566   * On click, skips forward in the duration/seekable range by a configurable amount of seconds.
45567   * If the time left in the duration/seekable range is less than the configured 'skip forward' time,
45568   * skips to end of duration/seekable range.
45569   *
45570   * Handle a click on a `SkipForward` button
45571   *
45572   * @param {EventTarget~Event} event
45573   *        The `click` event that caused this function
45574   *        to be called
45575   */
45576  handleClick(event) {
45577    if (isNaN(this.player_.duration())) {
45578      return;
45579    }
45580    const currentVideoTime = this.player_.currentTime();
45581    const liveTracker = this.player_.liveTracker;
45582    const duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
45583    let newTime;
45584    if (currentVideoTime + this.skipTime <= duration) {
45585      newTime = currentVideoTime + this.skipTime;
45586    } else {
45587      newTime = duration;
45588    }
45589    this.player_.currentTime(newTime);
45590  }
45591
45592  /**
45593   * Update control text on languagechange
45594   */
45595  handleLanguagechange() {
45596    this.controlText(this.localize('Skip forward {1} seconds', [this.skipTime]));
45597  }
45598}
45599SkipForward.prototype.controlText_ = 'Skip Forward';
45600Component$1.registerComponent('SkipForward', SkipForward);
45601
45602/**
45603 * Button to skip backward a configurable amount of time
45604 * through a video. Renders in the control bar.
45605 *
45606 *  * e.g. options: {controlBar: {skipButtons: backward: 5}}
45607 *
45608 * @extends Button
45609 */
45610class SkipBackward extends Button {
45611  constructor(player, options) {
45612    super(player, options);
45613    this.validOptions = [5, 10, 30];
45614    this.skipTime = this.getSkipBackwardTime();
45615    if (this.skipTime && this.validOptions.includes(this.skipTime)) {
45616      this.setIcon(`replay-${this.skipTime}`);
45617      this.controlText(this.localize('Skip backward {1} seconds', [this.skipTime.toLocaleString(player.language())]));
45618      this.show();
45619    } else {
45620      this.hide();
45621    }
45622  }
45623  getSkipBackwardTime() {
45624    const playerOptions = this.options_.playerOptions;
45625    return playerOptions.controlBar && playerOptions.controlBar.skipButtons && playerOptions.controlBar.skipButtons.backward;
45626  }
45627  buildCSSClass() {
45628    return `vjs-skip-backward-${this.getSkipBackwardTime()} ${super.buildCSSClass()}`;
45629  }
45630
45631  /**
45632   * On click, skips backward in the video by a configurable amount of seconds.
45633   * If the current time in the video is less than the configured 'skip backward' time,
45634   * skips to beginning of video or seekable range.
45635   *
45636   * Handle a click on a `SkipBackward` button
45637   *
45638   * @param {EventTarget~Event} event
45639   *        The `click` event that caused this function
45640   *        to be called
45641   */
45642  handleClick(event) {
45643    const currentVideoTime = this.player_.currentTime();
45644    const liveTracker = this.player_.liveTracker;
45645    const seekableStart = liveTracker && liveTracker.isLive() && liveTracker.seekableStart();
45646    let newTime;
45647    if (seekableStart && currentVideoTime - this.skipTime <= seekableStart) {
45648      newTime = seekableStart;
45649    } else if (currentVideoTime >= this.skipTime) {
45650      newTime = currentVideoTime - this.skipTime;
45651    } else {
45652      newTime = 0;
45653    }
45654    this.player_.currentTime(newTime);
45655  }
45656
45657  /**
45658   * Update control text on languagechange
45659   */
45660  handleLanguagechange() {
45661    this.controlText(this.localize('Skip backward {1} seconds', [this.skipTime]));
45662  }
45663}
45664SkipBackward.prototype.controlText_ = 'Skip Backward';
45665Component$1.registerComponent('SkipBackward', SkipBackward);
45666
45667/**
45668 * @file menu.js
45669 */
45670
45671/** @import Player from '../player' */
45672
45673/**
45674 * The Menu component is used to build popup menus, including subtitle and
45675 * captions selection menus.
45676 *
45677 * @extends Component
45678 */
45679class Menu extends Component$1 {
45680  /**
45681   * Create an instance of this class.
45682   *
45683   * @param {Player} player
45684   *        the player that this component should attach to
45685   *
45686   * @param {Object} [options]
45687   *        Object of option names and values
45688   *
45689   */
45690  constructor(player, options) {
45691    super(player, options);
45692    if (options) {
45693      this.menuButton_ = options.menuButton;
45694    }
45695    this.focusedChild_ = -1;
45696    this.on('keydown', e => this.handleKeyDown(e));
45697
45698    // All the menu item instances share the same blur handler provided by the menu container.
45699    this.boundHandleBlur_ = e => this.handleBlur(e);
45700    this.boundHandleTapClick_ = e => this.handleTapClick(e);
45701  }
45702
45703  /**
45704   * Add event listeners to the {@link MenuItem}.
45705   *
45706   * @param {Object} component
45707   *        The instance of the `MenuItem` to add listeners to.
45708   *
45709   */
45710  addEventListenerForItem(component) {
45711    if (!(component instanceof Component$1)) {
45712      return;
45713    }
45714    this.on(component, 'blur', this.boundHandleBlur_);
45715    this.on(component, ['tap', 'click'], this.boundHandleTapClick_);
45716  }
45717
45718  /**
45719   * Remove event listeners from the {@link MenuItem}.
45720   *
45721   * @param {Object} component
45722   *        The instance of the `MenuItem` to remove listeners.
45723   *
45724   */
45725  removeEventListenerForItem(component) {
45726    if (!(component instanceof Component$1)) {
45727      return;
45728    }
45729    this.off(component, 'blur', this.boundHandleBlur_);
45730    this.off(component, ['tap', 'click'], this.boundHandleTapClick_);
45731  }
45732
45733  /**
45734   * This method will be called indirectly when the component has been added
45735   * before the component adds to the new menu instance by `addItem`.
45736   * In this case, the original menu instance will remove the component
45737   * by calling `removeChild`.
45738   *
45739   * @param {Object} component
45740   *        The instance of the `MenuItem`
45741   */
45742  removeChild(component) {
45743    if (typeof component === 'string') {
45744      component = this.getChild(component);
45745    }
45746    this.removeEventListenerForItem(component);
45747    super.removeChild(component);
45748  }
45749
45750  /**
45751   * Add a {@link MenuItem} to the menu.
45752   *
45753   * @param {Object|string} component
45754   *        The name or instance of the `MenuItem` to add.
45755   *
45756   */
45757  addItem(component) {
45758    const childComponent = this.addChild(component);
45759    if (childComponent) {
45760      this.addEventListenerForItem(childComponent);
45761    }
45762  }
45763
45764  /**
45765   * Create the `Menu`s DOM element.
45766   *
45767   * @return {Element}
45768   *         the element that was created
45769   */
45770  createEl() {
45771    const contentElType = this.options_.contentElType || 'ul';
45772    this.contentEl_ = createEl(contentElType, {
45773      className: 'vjs-menu-content'
45774    });
45775    this.contentEl_.setAttribute('role', 'menu');
45776    const el = super.createEl('div', {
45777      append: this.contentEl_,
45778      className: 'vjs-menu'
45779    });
45780    el.appendChild(this.contentEl_);
45781
45782    // Prevent clicks from bubbling up. Needed for Menu Buttons,
45783    // where a click on the parent is significant
45784    on(el, 'click', function (event) {
45785      event.preventDefault();
45786      event.stopImmediatePropagation();
45787    });
45788    return el;
45789  }
45790  dispose() {
45791    this.contentEl_ = null;
45792    this.boundHandleBlur_ = null;
45793    this.boundHandleTapClick_ = null;
45794    super.dispose();
45795  }
45796
45797  /**
45798   * Called when a `MenuItem` loses focus.
45799   *
45800   * @param {Event} event
45801   *        The `blur` event that caused this function to be called.
45802   *
45803   * @listens blur
45804   */
45805  handleBlur(event) {
45806    const relatedTarget = event.relatedTarget || document__default["default"].activeElement;
45807
45808    // Close menu popup when a user clicks outside the menu
45809    if (!this.children().some(element => {
45810      return element.el() === relatedTarget;
45811    })) {
45812      const btn = this.menuButton_;
45813      if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) {
45814        btn.unpressButton();
45815      }
45816    }
45817  }
45818
45819  /**
45820   * Called when a `MenuItem` gets clicked or tapped.
45821   *
45822   * @param {Event} event
45823   *        The `click` or `tap` event that caused this function to be called.
45824   *
45825   * @listens click,tap
45826   */
45827  handleTapClick(event) {
45828    // Unpress the associated MenuButton, and move focus back to it
45829    if (this.menuButton_) {
45830      this.menuButton_.unpressButton();
45831      const childComponents = this.children();
45832      if (!Array.isArray(childComponents)) {
45833        return;
45834      }
45835      const foundComponent = childComponents.filter(component => component.el() === event.target)[0];
45836      if (!foundComponent) {
45837        return;
45838      }
45839
45840      // don't focus menu button if item is a caption settings item
45841      // because focus will move elsewhere
45842      if (foundComponent.name() !== 'CaptionSettingsMenuItem') {
45843        this.menuButton_.focus();
45844      }
45845    }
45846  }
45847
45848  /**
45849   * Handle a `keydown` event on this menu. This listener is added in the constructor.
45850   *
45851   * @param {KeyboardEvent} event
45852   *        A `keydown` event that happened on the menu.
45853   *
45854   * @listens keydown
45855   */
45856  handleKeyDown(event) {
45857    // Left and Down Arrows
45858    if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {
45859      event.preventDefault();
45860      event.stopPropagation();
45861      this.stepForward();
45862
45863      // Up and Right Arrows
45864    } else if (event.key === 'ArrowRight' || event.key === 'ArrowUp') {
45865      event.preventDefault();
45866      event.stopPropagation();
45867      this.stepBack();
45868    }
45869  }
45870
45871  /**
45872   * Move to next (lower) menu item for keyboard users.
45873   */
45874  stepForward() {
45875    let stepChild = 0;
45876    if (this.focusedChild_ !== undefined) {
45877      stepChild = this.focusedChild_ + 1;
45878    }
45879    this.focus(stepChild);
45880  }
45881
45882  /**
45883   * Move to previous (higher) menu item for keyboard users.
45884   */
45885  stepBack() {
45886    let stepChild = 0;
45887    if (this.focusedChild_ !== undefined) {
45888      stepChild = this.focusedChild_ - 1;
45889    }
45890    this.focus(stepChild);
45891  }
45892
45893  /**
45894   * Set focus on a {@link MenuItem} in the `Menu`.
45895   *
45896   * @param {Object|string} [item=0]
45897   *        Index of child item set focus on.
45898   */
45899  focus(item = 0) {
45900    const children = this.children().slice();
45901    const haveTitle = children.length && children[0].hasClass('vjs-menu-title');
45902    if (haveTitle) {
45903      children.shift();
45904    }
45905    if (children.length > 0) {
45906      if (item < 0) {
45907        item = 0;
45908      } else if (item >= children.length) {
45909        item = children.length - 1;
45910      }
45911      this.focusedChild_ = item;
45912      children[item].el_.focus();
45913    }
45914  }
45915}
45916Component$1.registerComponent('Menu', Menu);
45917
45918/**
45919 * @file menu-button.js
45920 */
45921
45922/** @import Player from '../player' */
45923
45924/**
45925 * A `MenuButton` class for any popup {@link Menu}.
45926 *
45927 * @extends Component
45928 */
45929class MenuButton extends Component$1 {
45930  /**
45931   * Creates an instance of this class.
45932   *
45933   * @param {Player} player
45934   *        The `Player` that this class should be attached to.
45935   *
45936   * @param {Object} [options={}]
45937   *        The key/value store of player options.
45938   */
45939  constructor(player, options = {}) {
45940    super(player, options);
45941    this.menuButton_ = new Button(player, options);
45942    this.menuButton_.controlText(this.controlText_);
45943    this.menuButton_.el_.setAttribute('aria-haspopup', 'true');
45944
45945    // Add buildCSSClass values to the button, not the wrapper
45946    const buttonClass = Button.prototype.buildCSSClass();
45947    this.menuButton_.el_.className = this.buildCSSClass() + ' ' + buttonClass;
45948    this.menuButton_.removeClass('vjs-control');
45949    this.addChild(this.menuButton_);
45950    this.update();
45951    this.enabled_ = true;
45952    const handleClick = e => this.handleClick(e);
45953    this.handleMenuKeyUp_ = e => this.handleMenuKeyUp(e);
45954    this.on(this.menuButton_, 'tap', handleClick);
45955    this.on(this.menuButton_, 'click', handleClick);
45956    this.on(this.menuButton_, 'keydown', e => this.handleKeyDown(e));
45957    this.on(this.menuButton_, 'mouseenter', () => {
45958      this.addClass('vjs-hover');
45959      this.menu.show();
45960      on(document__default["default"], 'keyup', this.handleMenuKeyUp_);
45961    });
45962    this.on('mouseleave', e => this.handleMouseLeave(e));
45963    this.on('keydown', e => this.handleSubmenuKeyDown(e));
45964  }
45965
45966  /**
45967   * Update the menu based on the current state of its items.
45968   */
45969  update() {
45970    const menu = this.createMenu();
45971    if (this.menu) {
45972      this.menu.dispose();
45973      this.removeChild(this.menu);
45974    }
45975    this.menu = menu;
45976    this.addChild(menu);
45977
45978    /**
45979     * Track the state of the menu button
45980     *
45981     * @type {Boolean}
45982     * @private
45983     */
45984    this.buttonPressed_ = false;
45985    this.menuButton_.el_.setAttribute('aria-expanded', 'false');
45986    if (this.items && this.items.length <= this.hideThreshold_) {
45987      this.hide();
45988      this.menu.contentEl_.removeAttribute('role');
45989    } else {
45990      this.show();
45991      this.menu.contentEl_.setAttribute('role', 'menu');
45992    }
45993  }
45994
45995  /**
45996   * Create the menu and add all items to it.
45997   *
45998   * @return {Menu}
45999   *         The constructed menu
46000   */
46001  createMenu() {
46002    const menu = new Menu(this.player_, {
46003      menuButton: this
46004    });
46005
46006    /**
46007     * Hide the menu if the number of items is less than or equal to this threshold. This defaults
46008     * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
46009     * it here because every time we run `createMenu` we need to reset the value.
46010     *
46011     * @protected
46012     * @type {Number}
46013     */
46014    this.hideThreshold_ = 0;
46015
46016    // Add a title list item to the top
46017    if (this.options_.title) {
46018      const titleEl = createEl('li', {
46019        className: 'vjs-menu-title',
46020        textContent: toTitleCase$1(this.options_.title),
46021        tabIndex: -1
46022      });
46023      const titleComponent = new Component$1(this.player_, {
46024        el: titleEl
46025      });
46026      menu.addItem(titleComponent);
46027    }
46028    this.items = this.createItems();
46029    if (this.items) {
46030      // Add menu items to the menu
46031      for (let i = 0; i < this.items.length; i++) {
46032        menu.addItem(this.items[i]);
46033      }
46034    }
46035    return menu;
46036  }
46037
46038  /**
46039   * Create the list of menu items. Specific to each subclass.
46040   *
46041   * @abstract
46042   */
46043  createItems() {}
46044
46045  /**
46046   * Create the `MenuButtons`s DOM element.
46047   *
46048   * @return {Element}
46049   *         The element that gets created.
46050   */
46051  createEl() {
46052    return super.createEl('div', {
46053      className: this.buildWrapperCSSClass()
46054    }, {});
46055  }
46056
46057  /**
46058   * Overwrites the `setIcon` method from `Component`.
46059   * In this case, we want the icon to be appended to the menuButton.
46060   *
46061   * @param {string} name
46062   *         The icon name to be added.
46063   *
46064   * @return {Element}
46065   *         The element that gets created.
46066   */
46067  setIcon(name) {
46068    return super.setIcon(name, this.menuButton_.el_);
46069  }
46070
46071  /**
46072   * Allow sub components to stack CSS class names for the wrapper element
46073   *
46074   * @return {string}
46075   *         The constructed wrapper DOM `className`
46076   */
46077  buildWrapperCSSClass() {
46078    let menuButtonClass = 'vjs-menu-button';
46079
46080    // If the inline option is passed, we want to use different styles altogether.
46081    if (this.options_.inline === true) {
46082      menuButtonClass += '-inline';
46083    } else {
46084      menuButtonClass += '-popup';
46085    }
46086
46087    // TODO: Fix the CSS so that this isn't necessary
46088    const buttonClass = Button.prototype.buildCSSClass();
46089    return `vjs-menu-button ${menuButtonClass} ${buttonClass} ${super.buildCSSClass()}`;
46090  }
46091
46092  /**
46093   * Builds the default DOM `className`.
46094   *
46095   * @return {string}
46096   *         The DOM `className` for this object.
46097   */
46098  buildCSSClass() {
46099    let menuButtonClass = 'vjs-menu-button';
46100
46101    // If the inline option is passed, we want to use different styles altogether.
46102    if (this.options_.inline === true) {
46103      menuButtonClass += '-inline';
46104    } else {
46105      menuButtonClass += '-popup';
46106    }
46107    return `vjs-menu-button ${menuButtonClass} ${super.buildCSSClass()}`;
46108  }
46109
46110  /**
46111   * Get or set the localized control text that will be used for accessibility.
46112   *
46113   * > NOTE: This will come from the internal `menuButton_` element.
46114   *
46115   * @param {string} [text]
46116   *        Control text for element.
46117   *
46118   * @param {Element} [el=this.menuButton_.el()]
46119   *        Element to set the title on.
46120   *
46121   * @return {string}
46122   *         - The control text when getting
46123   */
46124  controlText(text, el = this.menuButton_.el()) {
46125    return this.menuButton_.controlText(text, el);
46126  }
46127
46128  /**
46129   * Dispose of the `menu-button` and all child components.
46130   */
46131  dispose() {
46132    this.handleMouseLeave();
46133    super.dispose();
46134  }
46135
46136  /**
46137   * Handle a click on a `MenuButton`.
46138   * See {@link ClickableComponent#handleClick} for instances where this is called.
46139   *
46140   * @param {Event} event
vendor: 4,308 bytes, lines 46141-46302
46141   *        The `keydown`, `tap`, or `click` event that caused this function to be
46142   *        called.
46143   *
46144   * @listens tap
46145   * @listens click
46146   */
46147  handleClick(event) {
46148    if (this.buttonPressed_) {
46149      this.unpressButton();
46150    } else {
46151      this.pressButton();
46152    }
46153  }
46154
46155  /**
46156   * Handle `mouseleave` for `MenuButton`.
46157   *
46158   * @param {Event} event
46159   *        The `mouseleave` event that caused this function to be called.
46160   *
46161   * @listens mouseleave
46162   */
46163  handleMouseLeave(event) {
46164    this.removeClass('vjs-hover');
46165    off(document__default["default"], 'keyup', this.handleMenuKeyUp_);
46166  }
46167
46168  /**
46169   * Set the focus to the actual button, not to this element
46170   */
46171  focus() {
46172    this.menuButton_.focus();
46173  }
46174
46175  /**
46176   * Remove the focus from the actual button, not this element
46177   */
46178  blur() {
46179    this.menuButton_.blur();
46180  }
46181
46182  /**
46183   * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
46184   * {@link ClickableComponent#handleKeyDown} for instances where this is called.
46185   *
46186   * @param {Event} event
46187   *        The `keydown` event that caused this function to be called.
46188   *
46189   * @listens keydown
46190   */
46191  handleKeyDown(event) {
46192    // Escape or Tab unpress the 'button'
46193    if (event.key === 'Escape' || event.key === 'Tab') {
46194      if (this.buttonPressed_) {
46195        this.unpressButton();
46196      }
46197
46198      // Don't preventDefault for Tab key - we still want to lose focus
46199      if (!event.key === 'Tab') {
46200        event.preventDefault();
46201        // Set focus back to the menu button's button
46202        this.menuButton_.focus();
46203      }
46204      // Up Arrow or Down Arrow also 'press' the button to open the menu
46205    } else if (event.key === 'Up' || event.key === 'Down' && !(this.player_.options_.playerOptions.spatialNavigation && this.player_.options_.playerOptions.spatialNavigation.enabled)) {
46206      if (!this.buttonPressed_) {
46207        event.preventDefault();
46208        this.pressButton();
46209      }
46210    }
46211  }
46212
46213  /**
46214   * Handle a `keyup` event on a `MenuButton`. The listener for this is added in
46215   * the constructor.
46216   *
46217   * @param {Event} event
46218   *        Key press event
46219   *
46220   * @listens keyup
46221   */
46222  handleMenuKeyUp(event) {
46223    // Escape hides popup menu
46224    if (event.key === 'Escape' || event.key === 'Tab') {
46225      this.removeClass('vjs-hover');
46226    }
46227  }
46228
46229  /**
46230   * This method name now delegates to `handleSubmenuKeyDown`. This means
46231   * anyone calling `handleSubmenuKeyPress` will not see their method calls
46232   * stop working.
46233   *
46234   * @param {Event} event
46235   *        The event that caused this function to be called.
46236   */
46237  handleSubmenuKeyPress(event) {
46238    this.handleSubmenuKeyDown(event);
46239  }
46240
46241  /**
46242   * Handle a `keydown` event on a sub-menu. The listener for this is added in
46243   * the constructor.
46244   *
46245   * @param {Event} event
46246   *        Key press event
46247   *
46248   * @listens keydown
46249   */
46250  handleSubmenuKeyDown(event) {
46251    // Escape or Tab unpress the 'button'
46252    if (event.key === 'Escape' || event.key === 'Tab') {
46253      if (this.buttonPressed_) {
46254        this.unpressButton();
46255      }
46256      // Don't preventDefault for Tab key - we still want to lose focus
46257      if (!event.key === 'Tab') {
46258        event.preventDefault();
46259        // Set focus back to the menu button's button
46260        this.menuButton_.focus();
46261      }
46262    }
46263  }
46264
46265  /**
46266   * Put the current `MenuButton` into a pressed state.
46267   */
46268  pressButton() {
46269    if (this.enabled_) {
46270      this.buttonPressed_ = true;
46271      this.menu.show();
46272      this.menu.lockShowing();
46273      this.menuButton_.el_.setAttribute('aria-expanded', 'true');
46274
46275      // set the focus into the submenu, except on iOS where it is resulting in
46276      // undesired scrolling behavior when the player is in an iframe
46277      if (IS_IOS && isInFrame()) {
46278        // Return early so that the menu isn't focused
46279        return;
46280      }
46281      this.menu.focus();
46282    }
46283  }
46284
46285  /**
46286   * Take the current `MenuButton` out of a pressed state.
46287   */
46288  unpressButton() {
46289    if (this.enabled_) {
46290      this.buttonPressed_ = false;
46291      this.menu.unlockShowing();
46292      this.menu.hide();
46293      this.menuButton_.el_.setAttribute('aria-expanded', 'false');
46294    }
46295  }
46296
46297  /**
46298   * Disable the `MenuButton`. Don't allow it to be clicked.
46299   */
46300  disable() {
46301    this.unpressButton();
46302    this.enabled_ = false;
vendor: 4,705 bytes, lines 46303-46474
46303    this.addClass('vjs-disabled');
46304    this.menuButton_.disable();
46305  }
46306
46307  /**
46308   * Enable the `MenuButton`. Allow it to be clicked.
46309   */
46310  enable() {
46311    this.enabled_ = true;
46312    this.removeClass('vjs-disabled');
46313    this.menuButton_.enable();
46314  }
46315}
46316Component$1.registerComponent('MenuButton', MenuButton);
46317
46318/**
46319 * @file track-button.js
46320 */
46321
46322/** @import Player from '../player' */
46323
46324/**
46325 * The base class for buttons that toggle specific  track types (e.g. subtitles).
46326 *
46327 * @extends MenuButton
46328 */
46329class TrackButton extends MenuButton {
46330  /**
46331   * Creates an instance of this class.
46332   *
46333   * @param {Player} player
46334   *        The `Player` that this class should be attached to.
46335   *
46336   * @param {Object} [options]
46337   *        The key/value store of player options.
46338   */
46339  constructor(player, options) {
46340    const tracks = options.tracks;
46341    super(player, options);
46342    if (this.items.length <= 1) {
46343      this.hide();
46344    }
46345    if (!tracks) {
46346      return;
46347    }
46348    this.updateHandler_ = bind_(this, this.update);
46349    this.cleanupHandler_ = bind_(this, this.cleanupTrackListeners_);
46350    tracks.addEventListener('removetrack', this.updateHandler_);
46351    tracks.addEventListener('addtrack', this.updateHandler_);
46352    tracks.addEventListener('labelchange', this.updateHandler_);
46353    this.player_.on('ready', this.updateHandler_);
46354    this.player_.on('dispose', this.cleanupHandler_);
46355  }
46356
46357  /**
46358   * Remove all track list event listeners without triggering a full dispose.
46359   * Used as the player 'dispose' handler and called by dispose() before super.
46360   *
46361   * @private
46362   */
46363  cleanupTrackListeners_() {
46364    if (!this.updateHandler_) {
46365      return;
46366    }
46367    const tracks = this.options_.tracks;
46368    if (tracks) {
46369      tracks.removeEventListener('removetrack', this.updateHandler_);
46370      tracks.removeEventListener('addtrack', this.updateHandler_);
46371      tracks.removeEventListener('labelchange', this.updateHandler_);
46372    }
46373    if (this.player_) {
46374      this.player_.off('ready', this.updateHandler_);
46375      this.player_.off('dispose', this.cleanupHandler_);
46376    }
46377    this.updateHandler_ = null;
46378    this.cleanupHandler_ = null;
46379  }
46380
46381  /**
46382   * Dispose of the Component and remove all event listeners.
46383   *
46384   * @override
46385   */
46386  dispose() {
46387    this.cleanupTrackListeners_();
46388    super.dispose();
46389  }
46390}
46391Component$1.registerComponent('TrackButton', TrackButton);
46392
46393/**
46394 * @file menu-item.js
46395 */
46396
46397/** @import Player from '../player' */
46398
46399/**
46400 * The component for a menu item. `<li>`
46401 *
46402 * @extends ClickableComponent
46403 */
46404class MenuItem extends ClickableComponent {
46405  /**
46406   * Creates an instance of the this class.
46407   *
46408   * @param {Player} player
46409   *        The `Player` that this class should be attached to.
46410   *
46411   * @param {Object} [options={}]
46412   *        The key/value store of player options.
46413   *
46414   */
46415  constructor(player, options) {
46416    super(player, options);
46417    this.selectable = options.selectable;
46418    this.isSelected_ = options.selected || false;
46419    this.multiSelectable = options.multiSelectable;
46420    this.selected(this.isSelected_);
46421    if (this.selectable) {
46422      if (this.multiSelectable) {
46423        this.el_.setAttribute('role', 'menuitemcheckbox');
46424      } else {
46425        this.el_.setAttribute('role', 'menuitemradio');
46426      }
46427    } else {
46428      this.el_.setAttribute('role', 'menuitem');
46429    }
46430  }
46431
46432  /**
46433   * Create the `MenuItem's DOM element
46434   *
46435   * @param {string} [type=li]
46436   *        Element's node type, not actually used, always set to `li`.
46437   *
46438   * @param {Object} [props={}]
46439   *        An object of properties that should be set on the element
46440   *
46441   * @param {Object} [attrs={}]
46442   *        An object of attributes that should be set on the element
46443   *
46444   * @return {Element}
46445   *         The element that gets created.
46446   */
46447  createEl(type, props, attrs) {
46448    // The control is textual, not just an icon
46449    this.nonIconControl = true;
46450    const el = super.createEl('li', Object.assign({
46451      className: 'vjs-menu-item',
46452      tabIndex: -1
46453    }, props), attrs);
46454
46455    // swap icon with menu item text.
46456    const menuItemEl = createEl('span', {
46457      className: 'vjs-menu-item-text',
46458      textContent: this.localize(this.options_.label)
46459    });
46460
46461    // If using SVG icons, the element with vjs-icon-placeholder will be added separately.
46462    if (this.player_.options_.experimentalSvgIcons) {
46463      el.appendChild(menuItemEl);
46464    } else {
46465      el.replaceChild(menuItemEl, el.querySelector('.vjs-icon-placeholder'));
46466    }
46467    return el;
46468  }
46469
46470  /**
46471   * Ignore keys which are used by the menu, but pass any other ones up. See
46472   * {@link ClickableComponent#handleKeyDown} for instances where this is called.
46473   *
46474   * @param {KeyboardEvent} event
vendor: 4,546 bytes, lines 46475-46609
46475   *        The `keydown` event that caused this function to be called.
46476   *
46477   * @listens keydown
46478   */
46479  handleKeyDown(event) {
46480    if (!['Tab', 'Escape', 'ArrowUp', 'ArrowLeft', 'ArrowRight', 'ArrowDown'].includes(event.key)) {
46481      // Pass keydown handling up for unused keys
46482      super.handleKeyDown(event);
46483    }
46484  }
46485
46486  /**
46487   * Any click on a `MenuItem` puts it into the selected state.
46488   * See {@link ClickableComponent#handleClick} for instances where this is called.
46489   *
46490   * @param {Event} event
46491   *        The `keydown`, `tap`, or `click` event that caused this function to be
46492   *        called.
46493   *
46494   * @listens tap
46495   * @listens click
46496   */
46497  handleClick(event) {
46498    this.selected(true);
46499  }
46500
46501  /**
46502   * Set the state for this menu item as selected or not.
46503   *
46504   * @param {boolean} selected
46505   *        if the menu item is selected or not
46506   */
46507  selected(selected) {
46508    if (this.selectable) {
46509      if (selected) {
46510        this.addClass('vjs-selected');
46511        this.el_.setAttribute('aria-checked', 'true');
46512        // aria-checked isn't fully supported by browsers/screen readers,
46513        // so indicate selected state to screen reader in the control text.
46514        this.controlText(', selected');
46515        this.isSelected_ = true;
46516      } else {
46517        this.removeClass('vjs-selected');
46518        this.el_.setAttribute('aria-checked', 'false');
46519        // Indicate un-selected state to screen reader
46520        this.controlText('');
46521        this.isSelected_ = false;
46522      }
46523    }
46524  }
46525}
46526Component$1.registerComponent('MenuItem', MenuItem);
46527
46528/**
46529 * @file text-track-menu-item.js
46530 */
46531
46532/** @import Player from '../../player' */
46533
46534/**
46535 * The specific menu item type for selecting a language within a text track kind
46536 *
46537 * @extends MenuItem
46538 */
46539class TextTrackMenuItem extends MenuItem {
46540  /**
46541   * Creates an instance of this class.
46542   *
46543   * @param {Player} player
46544   *        The `Player` that this class should be attached to.
46545   *
46546   * @param {Object} [options]
46547   *        The key/value store of player options.
46548   */
46549  constructor(player, options) {
46550    const track = options.track;
46551    const tracks = player.textTracks();
46552
46553    // Modify options for parent MenuItem class's init.
46554    options.label = track.label || track.language || 'Unknown';
46555    options.selected = track.mode === 'showing';
46556    super(player, options);
46557    this.track = track;
46558    // Determine the relevant kind(s) of tracks for this component and filter
46559    // out empty kinds.
46560    this.kinds = (options.kinds || [options.kind || this.track.kind]).filter(Boolean);
46561    const changeHandler = (...args) => {
46562      this.handleTracksChange.apply(this, args);
46563    };
46564    const selectedLanguageChangeHandler = (...args) => {
46565      this.handleSelectedLanguageChange.apply(this, args);
46566    };
46567    player.on(['loadstart', 'texttrackchange'], changeHandler);
46568    tracks.addEventListener('change', changeHandler);
46569    tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
46570    this.on('dispose', function () {
46571      player.off(['loadstart', 'texttrackchange'], changeHandler);
46572      tracks.removeEventListener('change', changeHandler);
46573      tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
46574    });
46575
46576    // iOS7 doesn't dispatch change events to TextTrackLists when an
46577    // associated track's mode changes. Without something like
46578    // Object.observe() (also not present on iOS7), it's not
46579    // possible to detect changes to the mode attribute and polyfill
46580    // the change event. As a poor substitute, we manually dispatch
46581    // change events whenever the controls modify the mode.
46582    if (tracks.onchange === undefined) {
46583      let event;
46584      this.on(['tap', 'click'], function () {
46585        if (typeof window__default["default"].Event !== 'object') {
46586          // Android 2.3 throws an Illegal Constructor error for window.Event
46587          try {
46588            event = new window__default["default"].Event('change');
46589          } catch (err) {
46590            // continue regardless of error
46591          }
46592        }
46593        if (!event) {
46594          event = document__default["default"].createEvent('Event');
46595          event.initEvent('change', true, true);
46596        }
46597        tracks.dispatchEvent(event);
46598      });
46599    }
46600
46601    // set the default state based on current tracks
46602    this.handleTracksChange();
46603  }
46604
46605  /**
46606   * This gets called when an `TextTrackMenuItem` is "clicked". See
46607   * {@link ClickableComponent} for more detailed information on what a click can be.
46608   *
46609   * @param {Event} event
vendor: 55,376 bytes, lines 46610-48577
46610   *        The `keydown`, `tap`, or `click` event that caused this function to be
46611   *        called.
46612   *
46613   * @listens tap
46614   * @listens click
46615   */
46616  handleClick(event) {
46617    const referenceTrack = this.track;
46618    const tracks = this.player_.textTracks();
46619    super.handleClick(event);
46620    if (!tracks) {
46621      return;
46622    }
46623    for (let i = 0; i < tracks.length; i++) {
46624      const track = tracks[i];
46625
46626      // If the track from the text tracks list is not of the right kind,
46627      // skip it. We do not want to affect tracks of incompatible kind(s).
46628      if (this.kinds.indexOf(track.kind) === -1) {
46629        continue;
46630      }
46631
46632      // If this text track is the component's track and it is not showing,
46633      // set it to showing.
46634      if (track === referenceTrack) {
46635        if (track.mode !== 'showing') {
46636          track.mode = 'showing';
46637        }
46638
46639        // If this text track is not the component's track and it is not
46640        // disabled, set it to disabled.
46641      } else if (track.mode !== 'disabled') {
46642        track.mode = 'disabled';
46643      }
46644    }
46645  }
46646
46647  /**
46648   * Handle text track list change
46649   *
46650   * @param {Event} event
46651   *        The `change` event that caused this function to be called.
46652   *
46653   * @listens TextTrackList#change
46654   */
46655  handleTracksChange(event) {
46656    const shouldBeSelected = this.track.mode === 'showing';
46657
46658    // Prevent redundant selected() calls because they may cause
46659    // screen readers to read the appended control text unnecessarily
46660    if (shouldBeSelected !== this.isSelected_) {
46661      this.selected(shouldBeSelected);
46662    }
46663  }
46664  handleSelectedLanguageChange(event) {
46665    if (this.track.mode === 'showing') {
46666      const selectedLanguage = this.player_.cache_.selectedLanguage;
46667
46668      // Don't replace the kind of track across the same language
46669      if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
46670        return;
46671      }
46672      this.player_.cache_.selectedLanguage = {
46673        enabled: true,
46674        language: this.track.language,
46675        kind: this.track.kind
46676      };
46677    }
46678  }
46679  dispose() {
46680    // remove reference to track object on dispose
46681    this.track = null;
46682    super.dispose();
46683  }
46684}
46685Component$1.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
46686
46687/**
46688 * @file off-text-track-menu-item.js
46689 */
46690
46691/** @import Player from '../../player' */
46692
46693/**
46694 * A special menu item for turning off a specific type of text track
46695 *
46696 * @extends TextTrackMenuItem
46697 */
46698class OffTextTrackMenuItem extends TextTrackMenuItem {
46699  /**
46700   * Creates an instance of this class.
46701   *
46702   * @param {Player} player
46703   *        The `Player` that this class should be attached to.
46704   *
46705   * @param {Object} [options]
46706   *        The key/value store of player options.
46707   */
46708  constructor(player, options) {
46709    // Create pseudo track info
46710    // Requires options['kind']
46711    options.track = {
46712      player,
46713      // it is no longer necessary to store `kind` or `kinds` on the track itself
46714      // since they are now stored in the `kinds` property of all instances of
46715      // TextTrackMenuItem, but this will remain for backwards compatibility
46716      kind: options.kind,
46717      kinds: options.kinds,
46718      default: false,
46719      mode: 'disabled'
46720    };
46721    if (!options.kinds) {
46722      options.kinds = [options.kind];
46723    }
46724    if (options.label) {
46725      options.track.label = options.label;
46726    } else {
46727      options.track.label = options.kinds.join(' and ') + ' off';
46728    }
46729
46730    // MenuItem is selectable
46731    options.selectable = true;
46732    // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
46733    options.multiSelectable = false;
46734    super(player, options);
46735  }
46736
46737  /**
46738   * Handle text track change
46739   *
46740   * @param {Event} event
46741   *        The event that caused this function to run
46742   */
46743  handleTracksChange(event) {
46744    const tracks = this.player().textTracks();
46745    let shouldBeSelected = true;
46746    for (let i = 0, l = tracks.length; i < l; i++) {
46747      const track = tracks[i];
46748      if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
46749        shouldBeSelected = false;
46750        break;
46751      }
46752    }
46753
46754    // Prevent redundant selected() calls because they may cause
46755    // screen readers to read the appended control text unnecessarily
46756    if (shouldBeSelected !== this.isSelected_) {
46757      this.selected(shouldBeSelected);
46758    }
46759  }
46760  handleSelectedLanguageChange(event) {
46761    const tracks = this.player().textTracks();
46762    let allHidden = true;
46763    for (let i = 0, l = tracks.length; i < l; i++) {
46764      const track = tracks[i];
46765      if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
46766        allHidden = false;
46767        break;
46768      }
46769    }
46770    if (allHidden) {
46771      this.player_.cache_.selectedLanguage = {
46772        enabled: false
46773      };
46774    }
46775  }
46776
46777  /**
46778   * Update control text and label on languagechange
46779   */
46780  handleLanguagechange() {
46781    this.$('.vjs-menu-item-text').textContent = this.player_.localize(this.options_.label);
46782    super.handleLanguagechange();
46783  }
46784}
46785Component$1.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
46786
46787/**
46788 * @file text-track-button.js
46789 */
46790
46791/** @import Player from '../../player' */
46792
46793/**
46794 * The base class for buttons that toggle specific text track types (e.g. subtitles)
46795 *
46796 * @extends MenuButton
46797 */
46798class TextTrackButton extends TrackButton {
46799  /**
46800   * Creates an instance of this class.
46801   *
46802   * @param {Player} player
46803   *        The `Player` that this class should be attached to.
46804   *
46805   * @param {Object} [options={}]
46806   *        The key/value store of player options.
46807   */
46808  constructor(player, options = {}) {
46809    options.tracks = player.textTracks();
46810    super(player, options);
46811  }
46812
46813  /**
46814   * Create a menu item for each text track
46815   *
46816   * @param {TextTrackMenuItem[]} [items=[]]
46817   *        Existing array of items to use during creation
46818   *
46819   * @return {TextTrackMenuItem[]}
46820   *         Array of menu items that were created
46821   */
46822  createItems(items = [], TrackMenuItem = TextTrackMenuItem) {
46823    // Label is an override for the [track] off label
46824    // USed to localise captions/subtitles
46825    let label;
46826    if (this.label_) {
46827      label = `${this.label_} off`;
46828    }
46829    // Add an OFF menu item to turn all tracks off
46830    items.push(new OffTextTrackMenuItem(this.player_, {
46831      kinds: this.kinds_,
46832      kind: this.kind_,
46833      label
46834    }));
46835    this.hideThreshold_ += 1;
46836    const tracks = this.player_.textTracks();
46837    if (!Array.isArray(this.kinds_)) {
46838      this.kinds_ = [this.kind_];
46839    }
46840    for (let i = 0; i < tracks.length; i++) {
46841      const track = tracks[i];
46842
46843      // only add tracks that are of an appropriate kind and have a label
46844      if (this.kinds_.indexOf(track.kind) > -1) {
46845        const item = new TrackMenuItem(this.player_, {
46846          track,
46847          kinds: this.kinds_,
46848          kind: this.kind_,
46849          // MenuItem is selectable
46850          selectable: true,
46851          // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
46852          multiSelectable: false
46853        });
46854        item.addClass(`vjs-${track.kind}-menu-item`);
46855        items.push(item);
46856      }
46857    }
46858    return items;
46859  }
46860}
46861Component$1.registerComponent('TextTrackButton', TextTrackButton);
46862
46863/**
46864 * @file chapters-track-menu-item.js
46865 */
46866
46867/** @import Player from '../../player' */
46868
46869/**
46870 * The chapter track menu item
46871 *
46872 * @extends MenuItem
46873 */
46874class ChaptersTrackMenuItem extends MenuItem {
46875  /**
46876   * Creates an instance of this class.
46877   *
46878   * @param {Player} player
46879   *        The `Player` that this class should be attached to.
46880   *
46881   * @param {Object} [options]
46882   *        The key/value store of player options.
46883   */
46884  constructor(player, options) {
46885    const track = options.track;
46886    const cue = options.cue;
46887    const currentTime = player.currentTime();
46888
46889    // Modify options for parent MenuItem class's init.
46890    options.selectable = true;
46891    options.multiSelectable = false;
46892    options.label = cue.text;
46893    options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
46894    super(player, options);
46895    this.track = track;
46896    this.cue = cue;
46897  }
46898
46899  /**
46900   * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
46901   * {@link ClickableComponent} for more detailed information on what a click can be.
46902   *
46903   * @param {Event} [event]
46904   *        The `keydown`, `tap`, or `click` event that caused this function to be
46905   *        called.
46906   *
46907   * @listens tap
46908   * @listens click
46909   */
46910  handleClick(event) {
46911    super.handleClick();
46912    this.player_.currentTime(this.cue.startTime);
46913  }
46914}
46915Component$1.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
46916
46917/**
46918 * @file chapters-button.js
46919 */
46920
46921/** @import Player from '../../player' */
46922/** @import Menu from '../../menu/menu' */
46923/** @import TextTrack from '../../tracks/text-track' */
46924/** @import TextTrackMenuItem from '../text-track-controls/text-track-menu-item' */
46925
46926/**
46927 * The button component for toggling and selecting chapters
46928 * Chapters act much differently than other text tracks
46929 * Cues are navigation vs. other tracks of alternative languages
46930 *
46931 * @extends TextTrackButton
46932 */
46933class ChaptersButton extends TextTrackButton {
46934  /**
46935   * Creates an instance of this class.
46936   *
46937   * @param {Player} player
46938   *        The `Player` that this class should be attached to.
46939   *
46940   * @param {Object} [options]
46941   *        The key/value store of player options.
46942   *
46943   * @param {Function} [ready]
46944   *        The function to call when this function is ready.
46945   */
46946  constructor(player, options, ready) {
46947    super(player, options, ready);
46948    this.setIcon('chapters');
46949    this.selectCurrentItem_ = () => {
46950      this.items.forEach(item => {
46951        item.selected(this.track_.activeCues[0] === item.cue);
46952      });
46953    };
46954  }
46955
46956  /**
46957   * Builds the default DOM `className`.
46958   *
46959   * @return {string}
46960   *         The DOM `className` for this object.
46961   */
46962  buildCSSClass() {
46963    return `vjs-chapters-button ${super.buildCSSClass()}`;
46964  }
46965  buildWrapperCSSClass() {
46966    return `vjs-chapters-button ${super.buildWrapperCSSClass()}`;
46967  }
46968
46969  /**
46970   * Update the menu based on the current state of its items.
46971   *
46972   * @param {Event} [event]
46973   *        An event that triggered this function to run.
46974   *
46975   * @listens TextTrackList#addtrack
46976   * @listens TextTrackList#removetrack
46977   * @listens TextTrackList#change
46978   */
46979  update(event) {
46980    if (event && event.track && event.track.kind !== 'chapters') {
46981      return;
46982    }
46983    const track = this.findChaptersTrack();
46984    if (track !== this.track_) {
46985      this.setTrack(track);
46986      super.update();
46987    } else if (!this.items || track && track.cues && track.cues.length !== this.items.length) {
46988      // Update the menu initially or if the number of cues has changed since set
46989      super.update();
46990    }
46991  }
46992
46993  /**
46994   * Set the currently selected track for the chapters button.
46995   *
46996   * @param {TextTrack} track
46997   *        The new track to select. Nothing will change if this is the currently selected
46998   *        track.
46999   */
47000  setTrack(track) {
47001    if (this.track_ === track) {
47002      return;
47003    }
47004    if (!this.updateHandler_) {
47005      this.updateHandler_ = this.update.bind(this);
47006    }
47007
47008    // here this.track_ refers to the old track instance
47009    if (this.track_) {
47010      const remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
47011      if (remoteTextTrackEl) {
47012        remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
47013      }
47014      this.track_.removeEventListener('cuechange', this.selectCurrentItem_);
47015      this.track_ = null;
47016    }
47017    this.track_ = track;
47018
47019    // here this.track_ refers to the new track instance
47020    if (this.track_) {
47021      this.track_.mode = 'hidden';
47022      const remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
47023      if (remoteTextTrackEl) {
47024        remoteTextTrackEl.addEventListener('load', this.updateHandler_);
47025      }
47026      this.track_.addEventListener('cuechange', this.selectCurrentItem_);
47027    }
47028  }
47029
47030  /**
47031   * Find the track object that is currently in use by this ChaptersButton
47032   *
47033   * @return {TextTrack|undefined}
47034   *         The current track or undefined if none was found.
47035   */
47036  findChaptersTrack() {
47037    const tracks = this.player_.textTracks() || [];
47038    for (let i = tracks.length - 1; i >= 0; i--) {
47039      // We will always choose the last track as our chaptersTrack
47040      const track = tracks[i];
47041      if (track.kind === this.kind_) {
47042        return track;
47043      }
47044    }
47045  }
47046
47047  /**
47048   * Get the caption for the ChaptersButton based on the track label. This will also
47049   * use the current tracks localized kind as a fallback if a label does not exist.
47050   *
47051   * @return {string}
47052   *         The tracks current label or the localized track kind.
47053   */
47054  getMenuCaption() {
47055    if (this.track_ && this.track_.label) {
47056      return this.track_.label;
47057    }
47058    return this.localize(toTitleCase$1(this.kind_));
47059  }
47060
47061  /**
47062   * Create menu from chapter track
47063   *
47064   * @return {Menu}
47065   *         New menu for the chapter buttons
47066   */
47067  createMenu() {
47068    this.options_.title = this.getMenuCaption();
47069    return super.createMenu();
47070  }
47071
47072  /**
47073   * Create a menu item for each text track
47074   *
47075   * @return  {TextTrackMenuItem[]}
47076   *         Array of menu items
47077   */
47078  createItems() {
47079    const items = [];
47080    if (!this.track_) {
47081      return items;
47082    }
47083    const cues = this.track_.cues;
47084    if (!cues) {
47085      return items;
47086    }
47087    for (let i = 0, l = cues.length; i < l; i++) {
47088      const cue = cues[i];
47089      const mi = new ChaptersTrackMenuItem(this.player_, {
47090        track: this.track_,
47091        cue
47092      });
47093      items.push(mi);
47094    }
47095    return items;
47096  }
47097}
47098
47099/**
47100 * `kind` of TextTrack to look for to associate it with this menu.
47101 *
47102 * @type {string}
47103 * @private
47104 */
47105ChaptersButton.prototype.kind_ = 'chapters';
47106
47107/**
47108 * The text that should display over the `ChaptersButton`s controls. Added for localization.
47109 *
47110 * @type {string}
47111 * @protected
47112 */
47113ChaptersButton.prototype.controlText_ = 'Chapters';
47114Component$1.registerComponent('ChaptersButton', ChaptersButton);
47115
47116/**
47117 * @file descriptions-button.js
47118 */
47119
47120/** @import Player from '../../player' */
47121
47122/**
47123 * The button component for toggling and selecting descriptions
47124 *
47125 * @extends TextTrackButton
47126 */
47127class DescriptionsButton extends TextTrackButton {
47128  /**
47129   * Creates an instance of this class.
47130   *
47131   * @param {Player} player
47132   *        The `Player` that this class should be attached to.
47133   *
47134   * @param {Object} [options]
47135   *        The key/value store of player options.
47136   *
47137   * @param {Function} [ready]
47138   *        The function to call when this component is ready.
47139   */
47140  constructor(player, options, ready) {
47141    super(player, options, ready);
47142    this.setIcon('audio-description');
47143    const tracks = player.textTracks();
47144    const changeHandler = bind_(this, this.handleTracksChange);
47145    tracks.addEventListener('change', changeHandler);
47146    this.on('dispose', function () {
47147      tracks.removeEventListener('change', changeHandler);
47148    });
47149  }
47150
47151  /**
47152   * Handle text track change
47153   *
47154   * @param {Event} event
47155   *        The event that caused this function to run
47156   *
47157   * @listens TextTrackList#change
47158   */
47159  handleTracksChange(event) {
47160    const tracks = this.player().textTracks();
47161    let disabled = false;
47162
47163    // Check whether a track of a different kind is showing
47164    for (let i = 0, l = tracks.length; i < l; i++) {
47165      const track = tracks[i];
47166      if (track.kind !== this.kind_ && track.mode === 'showing') {
47167        disabled = true;
47168        break;
47169      }
47170    }
47171
47172    // If another track is showing, disable this menu button
47173    if (disabled) {
47174      this.disable();
47175    } else {
47176      this.enable();
47177    }
47178  }
47179
47180  /**
47181   * Builds the default DOM `className`.
47182   *
47183   * @return {string}
47184   *         The DOM `className` for this object.
47185   */
47186  buildCSSClass() {
47187    return `vjs-descriptions-button ${super.buildCSSClass()}`;
47188  }
47189  buildWrapperCSSClass() {
47190    return `vjs-descriptions-button ${super.buildWrapperCSSClass()}`;
47191  }
47192}
47193
47194/**
47195 * `kind` of TextTrack to look for to associate it with this menu.
47196 *
47197 * @type {string}
47198 * @private
47199 */
47200DescriptionsButton.prototype.kind_ = 'descriptions';
47201
47202/**
47203 * The text that should display over the `DescriptionsButton`s controls. Added for localization.
47204 *
47205 * @type {string}
47206 * @protected
47207 */
47208DescriptionsButton.prototype.controlText_ = 'Descriptions';
47209Component$1.registerComponent('DescriptionsButton', DescriptionsButton);
47210
47211/**
47212 * @file subtitles-button.js
47213 */
47214
47215/** @import Player from '../../player' */
47216
47217/**
47218 * The button component for toggling and selecting subtitles
47219 *
47220 * @extends TextTrackButton
47221 */
47222class SubtitlesButton extends TextTrackButton {
47223  /**
47224   * Creates an instance of this class.
47225   *
47226   * @param {Player} player
47227   *        The `Player` that this class should be attached to.
47228   *
47229   * @param {Object} [options]
47230   *        The key/value store of player options.
47231   *
47232   * @param {Function} [ready]
47233   *        The function to call when this component is ready.
47234   */
47235  constructor(player, options, ready) {
47236    super(player, options, ready);
47237    this.setIcon('subtitles');
47238  }
47239
47240  /**
47241   * Builds the default DOM `className`.
47242   *
47243   * @return {string}
47244   *         The DOM `className` for this object.
47245   */
47246  buildCSSClass() {
47247    return `vjs-subtitles-button ${super.buildCSSClass()}`;
47248  }
47249  buildWrapperCSSClass() {
47250    return `vjs-subtitles-button ${super.buildWrapperCSSClass()}`;
47251  }
47252}
47253
47254/**
47255 * `kind` of TextTrack to look for to associate it with this menu.
47256 *
47257 * @type {string}
47258 * @private
47259 */
47260SubtitlesButton.prototype.kind_ = 'subtitles';
47261
47262/**
47263 * The text that should display over the `SubtitlesButton`s controls. Added for localization.
47264 *
47265 * @type {string}
47266 * @protected
47267 */
47268SubtitlesButton.prototype.controlText_ = 'Subtitles';
47269Component$1.registerComponent('SubtitlesButton', SubtitlesButton);
47270
47271/**
47272 * @file caption-settings-menu-item.js
47273 */
47274
47275/** @import Player from '../../player' */
47276
47277/**
47278 * The menu item for caption track settings menu
47279 *
47280 * @extends TextTrackMenuItem
47281 */
47282class CaptionSettingsMenuItem extends TextTrackMenuItem {
47283  /**
47284   * Creates an instance of this class.
47285   *
47286   * @param {Player} player
47287   *        The `Player` that this class should be attached to.
47288   *
47289   * @param {Object} [options]
47290   *        The key/value store of player options.
47291   */
47292  constructor(player, options) {
47293    options.track = {
47294      player,
47295      kind: options.kind,
47296      label: options.kind + ' settings',
47297      selectable: false,
47298      default: false,
47299      mode: 'disabled'
47300    };
47301
47302    // CaptionSettingsMenuItem has no concept of 'selected'
47303    options.selectable = false;
47304    options.name = 'CaptionSettingsMenuItem';
47305    super(player, options);
47306    this.addClass('vjs-texttrack-settings');
47307    this.controlText(', opens ' + options.kind + ' settings dialog');
47308  }
47309
47310  /**
47311   * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
47312   * {@link ClickableComponent} for more detailed information on what a click can be.
47313   *
47314   * @param {Event} [event]
47315   *        The `keydown`, `tap`, or `click` event that caused this function to be
47316   *        called.
47317   *
47318   * @listens tap
47319   * @listens click
47320   */
47321  handleClick(event) {
47322    this.player().getChild('textTrackSettings').open();
47323  }
47324
47325  /**
47326   * Update control text and label on languagechange
47327   */
47328  handleLanguagechange() {
47329    this.$('.vjs-menu-item-text').textContent = this.player_.localize(this.options_.kind + ' settings');
47330    super.handleLanguagechange();
47331  }
47332}
47333Component$1.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
47334
47335/**
47336 * @file captions-button.js
47337 */
47338
47339/** @import Player from '../../player' */
47340
47341/**
47342 * The button component for toggling and selecting captions
47343 *
47344 * @extends TextTrackButton
47345 */
47346class CaptionsButton extends TextTrackButton {
47347  /**
47348   * Creates an instance of this class.
47349   *
47350   * @param {Player} player
47351   *        The `Player` that this class should be attached to.
47352   *
47353   * @param {Object} [options]
47354   *        The key/value store of player options.
47355   *
47356   * @param {Function} [ready]
47357   *        The function to call when this component is ready.
47358   */
47359  constructor(player, options, ready) {
47360    super(player, options, ready);
47361    this.setIcon('captions');
47362  }
47363
47364  /**
47365   * Builds the default DOM `className`.
47366   *
47367   * @return {string}
47368   *         The DOM `className` for this object.
47369   */
47370  buildCSSClass() {
47371    return `vjs-captions-button ${super.buildCSSClass()}`;
47372  }
47373  buildWrapperCSSClass() {
47374    return `vjs-captions-button ${super.buildWrapperCSSClass()}`;
47375  }
47376
47377  /**
47378   * Create caption menu items
47379   *
47380   * @return {CaptionSettingsMenuItem[]}
47381   *         The array of current menu items.
47382   */
47383  createItems() {
47384    const items = [];
47385    if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
47386      items.push(new CaptionSettingsMenuItem(this.player_, {
47387        kind: this.kind_
47388      }));
47389      this.hideThreshold_ += 1;
47390    }
47391    return super.createItems(items);
47392  }
47393}
47394
47395/**
47396 * `kind` of TextTrack to look for to associate it with this menu.
47397 *
47398 * @type {string}
47399 * @private
47400 */
47401CaptionsButton.prototype.kind_ = 'captions';
47402
47403/**
47404 * The text that should display over the `CaptionsButton`s controls. Added for localization.
47405 *
47406 * @type {string}
47407 * @protected
47408 */
47409CaptionsButton.prototype.controlText_ = 'Captions';
47410Component$1.registerComponent('CaptionsButton', CaptionsButton);
47411
47412/**
47413 * @file subs-caps-menu-item.js
47414 */
47415
47416/**
47417 * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
47418 * in the SubsCapsMenu.
47419 *
47420 * @extends TextTrackMenuItem
47421 */
47422class SubsCapsMenuItem extends TextTrackMenuItem {
47423  createEl(type, props, attrs) {
47424    const el = super.createEl(type, props, attrs);
47425    const parentSpan = el.querySelector('.vjs-menu-item-text');
47426    if (this.options_.track.kind === 'captions') {
47427      if (this.player_.options_.experimentalSvgIcons) {
47428        this.setIcon('captions', el);
47429      } else {
47430        parentSpan.appendChild(createEl('span', {
47431          className: 'vjs-icon-placeholder'
47432        }, {
47433          'aria-hidden': true
47434        }));
47435      }
47436      parentSpan.appendChild(createEl('span', {
47437        className: 'vjs-control-text',
47438        // space added as the text will visually flow with the
47439        // label
47440        textContent: ` ${this.localize('Captions')}`
47441      }));
47442    }
47443    return el;
47444  }
47445}
47446Component$1.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
47447
47448/**
47449 * @file sub-caps-button.js
47450 */
47451
47452/** @import Player from '../../player' */
47453
47454/**
47455 * The button component for toggling and selecting captions and/or subtitles
47456 *
47457 * @extends TextTrackButton
47458 */
47459class SubsCapsButton extends TextTrackButton {
47460  /**
47461   * Creates an instance of this class.
47462   *
47463   * @param {Player} player
47464   *        The `Player` that this class should be attached to.
47465   *
47466   * @param {Object} [options]
47467   *        The key/value store of player options.
47468   *
47469   * @param {Function} [ready]
47470   *        The function to call when this component is ready.
47471   */
47472  constructor(player, options = {}) {
47473    super(player, options);
47474
47475    // Although North America uses "captions" in most cases for
47476    // "captions and subtitles" other locales use "subtitles"
47477    this.label_ = 'subtitles';
47478    this.setIcon('subtitles');
47479    if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(this.player_.language_) > -1) {
47480      this.label_ = 'captions';
47481      this.setIcon('captions');
47482    }
47483    this.menuButton_.controlText(toTitleCase$1(this.label_));
47484  }
47485
47486  /**
47487   * Builds the default DOM `className`.
47488   *
47489   * @return {string}
47490   *         The DOM `className` for this object.
47491   */
47492  buildCSSClass() {
47493    return `vjs-subs-caps-button ${super.buildCSSClass()}`;
47494  }
47495  buildWrapperCSSClass() {
47496    return `vjs-subs-caps-button ${super.buildWrapperCSSClass()}`;
47497  }
47498
47499  /**
47500   * Create caption/subtitles menu items
47501   *
47502   * @return {CaptionSettingsMenuItem[]}
47503   *         The array of current menu items.
47504   */
47505  createItems() {
47506    let items = [];
47507    if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
47508      items.push(new CaptionSettingsMenuItem(this.player_, {
47509        kind: this.label_
47510      }));
47511      this.hideThreshold_ += 1;
47512    }
47513    items = super.createItems(items, SubsCapsMenuItem);
47514    return items;
47515  }
47516}
47517
47518/**
47519 * `kind`s of TextTrack to look for to associate it with this menu.
47520 *
47521 * @type {array}
47522 * @private
47523 */
47524SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
47525
47526/**
47527 * The text that should display over the `SubsCapsButton`s controls.
47528 *
47529 *
47530 * @type {string}
47531 * @protected
47532 */
47533SubsCapsButton.prototype.controlText_ = 'Subtitles';
47534Component$1.registerComponent('SubsCapsButton', SubsCapsButton);
47535
47536/**
47537 * @file audio-track-menu-item.js
47538 */
47539
47540/** @import Player from '../../player' */
47541
47542/**
47543 * An {@link AudioTrack} {@link MenuItem}
47544 *
47545 * @extends MenuItem
47546 */
47547class AudioTrackMenuItem extends MenuItem {
47548  /**
47549   * Creates an instance of this class.
47550   *
47551   * @param {Player} player
47552   *        The `Player` that this class should be attached to.
47553   *
47554   * @param {Object} [options]
47555   *        The key/value store of player options.
47556   */
47557  constructor(player, options) {
47558    const track = options.track;
47559    const tracks = player.audioTracks();
47560
47561    // Modify options for parent MenuItem class's init.
47562    options.label = track.label || track.language || 'Unknown';
47563    options.selected = track.enabled;
47564    super(player, options);
47565    this.track = track;
47566    this.addClass(`vjs-${track.kind}-menu-item`);
47567    const changeHandler = (...args) => {
47568      this.handleTracksChange.apply(this, args);
47569    };
47570    tracks.addEventListener('change', changeHandler);
47571    this.on('dispose', () => {
47572      tracks.removeEventListener('change', changeHandler);
47573    });
47574  }
47575  createEl(type, props, attrs) {
47576    const el = super.createEl(type, props, attrs);
47577    const parentSpan = el.querySelector('.vjs-menu-item-text');
47578    if (['main-desc', 'descriptions', 'description'].indexOf(this.options_.track.kind) >= 0) {
47579      parentSpan.appendChild(createEl('span', {
47580        className: 'vjs-icon-placeholder'
47581      }, {
47582        'aria-hidden': true
47583      }));
47584      parentSpan.appendChild(createEl('span', {
47585        className: 'vjs-control-text',
47586        textContent: ' ' + this.localize('Descriptions')
47587      }));
47588    }
47589    return el;
47590  }
47591
47592  /**
47593   * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
47594   * for more detailed information on what a click can be.
47595   *
47596   * @param {Event} [event]
47597   *        The `keydown`, `tap`, or `click` event that caused this function to be
47598   *        called.
47599   *
47600   * @listens tap
47601   * @listens click
47602   */
47603  handleClick(event) {
47604    super.handleClick(event);
47605
47606    // the audio track list will automatically toggle other tracks
47607    // off for us.
47608    this.track.enabled = true;
47609
47610    // when native audio tracks are used, we want to make sure that other tracks are turned off
47611    if (this.player_.tech_.featuresNativeAudioTracks) {
47612      const tracks = this.player_.audioTracks();
47613      for (let i = 0; i < tracks.length; i++) {
47614        const track = tracks[i];
47615
47616        // skip the current track since we enabled it above
47617        if (track === this.track) {
47618          continue;
47619        }
47620        track.enabled = track === this.track;
47621      }
47622    }
47623  }
47624
47625  /**
47626   * Handle any {@link AudioTrack} change.
47627   *
47628   * @param {Event} [event]
47629   *        The {@link AudioTrackList#change} event that caused this to run.
47630   *
47631   * @listens AudioTrackList#change
47632   */
47633  handleTracksChange(event) {
47634    this.selected(this.track.enabled);
47635  }
47636}
47637Component$1.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
47638
47639/**
47640 * @file audio-track-button.js
47641 */
47642
47643/**
47644 * The base class for buttons that toggle specific {@link AudioTrack} types.
47645 *
47646 * @extends TrackButton
47647 */
47648class AudioTrackButton extends TrackButton {
47649  /**
47650   * Creates an instance of this class.
47651   *
47652   * @param {Player} player
47653   *        The `Player` that this class should be attached to.
47654   *
47655   * @param {Object} [options={}]
47656   *        The key/value store of player options.
47657   */
47658  constructor(player, options = {}) {
47659    options.tracks = player.audioTracks();
47660    super(player, options);
47661    this.setIcon('audio');
47662  }
47663
47664  /**
47665   * Builds the default DOM `className`.
47666   *
47667   * @return {string}
47668   *         The DOM `className` for this object.
47669   */
47670  buildCSSClass() {
47671    return `vjs-audio-button ${super.buildCSSClass()}`;
47672  }
47673  buildWrapperCSSClass() {
47674    return `vjs-audio-button ${super.buildWrapperCSSClass()}`;
47675  }
47676
47677  /**
47678   * Create a menu item for each audio track
47679   *
47680   * @param {AudioTrackMenuItem[]} [items=[]]
47681   *        An array of existing menu items to use.
47682   *
47683   * @return {AudioTrackMenuItem[]}
47684   *         An array of menu items
47685   */
47686  createItems(items = []) {
47687    // if there's only one audio track, there no point in showing it
47688    this.hideThreshold_ = 1;
47689    const tracks = this.player_.audioTracks();
47690    for (let i = 0; i < tracks.length; i++) {
47691      const track = tracks[i];
47692      items.push(new AudioTrackMenuItem(this.player_, {
47693        track,
47694        // MenuItem is selectable
47695        selectable: true,
47696        // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
47697        multiSelectable: false
47698      }));
47699    }
47700    return items;
47701  }
47702}
47703
47704/**
47705 * The text that should display over the `AudioTrackButton`s controls. Added for localization.
47706 *
47707 * @type {string}
47708 * @protected
47709 */
47710AudioTrackButton.prototype.controlText_ = 'Audio Track';
47711Component$1.registerComponent('AudioTrackButton', AudioTrackButton);
47712
47713/**
47714 * @file playback-rate-menu-item.js
47715 */
47716
47717/** @import Player from '../../player' */
47718
47719/**
47720 * The specific menu item type for selecting a playback rate.
47721 *
47722 * @extends MenuItem
47723 */
47724class PlaybackRateMenuItem extends MenuItem {
47725  /**
47726   * Creates an instance of this class.
47727   *
47728   * @param {Player} player
47729   *        The `Player` that this class should be attached to.
47730   *
47731   * @param {Object} [options]
47732   *        The key/value store of player options.
47733   */
47734  constructor(player, options) {
47735    const label = options.rate;
47736    const rate = parseFloat(label, 10);
47737
47738    // Modify options for parent MenuItem class's init.
47739    options.label = label;
47740    options.selected = rate === player.playbackRate();
47741    options.selectable = true;
47742    options.multiSelectable = false;
47743    super(player, options);
47744    this.label = label;
47745    this.rate = rate;
47746    this.on(player, 'ratechange', e => this.update(e));
47747  }
47748
47749  /**
47750   * This gets called when an `PlaybackRateMenuItem` is "clicked". See
47751   * {@link ClickableComponent} for more detailed information on what a click can be.
47752   *
47753   * @param {Event} [event]
47754   *        The `keydown`, `tap`, or `click` event that caused this function to be
47755   *        called.
47756   *
47757   * @listens tap
47758   * @listens click
47759   */
47760  handleClick(event) {
47761    super.handleClick();
47762    this.player().playbackRate(this.rate);
47763  }
47764
47765  /**
47766   * Update the PlaybackRateMenuItem when the playbackrate changes.
47767   *
47768   * @param {Event} [event]
47769   *        The `ratechange` event that caused this function to run.
47770   *
47771   * @listens Player#ratechange
47772   */
47773  update(event) {
47774    this.selected(this.player().playbackRate() === this.rate);
47775  }
47776}
47777
47778/**
47779 * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
47780 *
47781 * @type {string}
47782 * @private
47783 */
47784PlaybackRateMenuItem.prototype.contentElType = 'button';
47785Component$1.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
47786
47787/**
47788 * @file playback-rate-menu-button.js
47789 */
47790
47791/** @import Player from '../../player' */
47792
47793/**
47794 * The component for controlling the playback rate.
47795 *
47796 * @extends MenuButton
47797 */
47798class PlaybackRateMenuButton extends MenuButton {
47799  /**
47800   * Creates an instance of this class.
47801   *
47802   * @param {Player} player
47803   *        The `Player` that this class should be attached to.
47804   *
47805   * @param {Object} [options]
47806   *        The key/value store of player options.
47807   */
47808  constructor(player, options) {
47809    super(player, options);
47810    this.menuButton_.el_.setAttribute('aria-describedby', this.labelElId_);
47811    this.updateVisibility();
47812    this.updateLabel();
47813    this.on(player, 'loadstart', e => this.updateVisibility(e));
47814    this.on(player, 'ratechange', e => this.updateLabel(e));
47815    this.on(player, 'playbackrateschange', e => this.handlePlaybackRateschange(e));
47816  }
47817
47818  /**
47819   * Create the `Component`'s DOM element
47820   *
47821   * @return {Element}
47822   *         The element that was created.
47823   */
47824  createEl() {
47825    const el = super.createEl();
47826    this.labelElId_ = 'vjs-playback-rate-value-label-' + this.id_;
47827    this.labelEl_ = createEl('div', {
47828      className: 'vjs-playback-rate-value',
47829      id: this.labelElId_,
47830      textContent: '1x'
47831    });
47832    el.appendChild(this.labelEl_);
47833    return el;
47834  }
47835  dispose() {
47836    this.labelEl_ = null;
47837    super.dispose();
47838  }
47839
47840  /**
47841   * Builds the default DOM `className`.
47842   *
47843   * @return {string}
47844   *         The DOM `className` for this object.
47845   */
47846  buildCSSClass() {
47847    return `vjs-playback-rate ${super.buildCSSClass()}`;
47848  }
47849  buildWrapperCSSClass() {
47850    return `vjs-playback-rate ${super.buildWrapperCSSClass()}`;
47851  }
47852
47853  /**
47854   * Create the list of menu items. Specific to each subclass.
47855   *
47856   */
47857  createItems() {
47858    const rates = this.playbackRates();
47859    const items = [];
47860    for (let i = rates.length - 1; i >= 0; i--) {
47861      items.push(new PlaybackRateMenuItem(this.player(), {
47862        rate: rates[i] + 'x'
47863      }));
47864    }
47865    return items;
47866  }
47867
47868  /**
47869   * On playbackrateschange, update the menu to account for the new items.
47870   *
47871   * @listens Player#playbackrateschange
47872   */
47873  handlePlaybackRateschange(event) {
47874    this.update();
47875  }
47876
47877  /**
47878   * Get possible playback rates
47879   *
47880   * @return {Array}
47881   *         All possible playback rates
47882   */
47883  playbackRates() {
47884    const player = this.player();
47885    return player.playbackRates && player.playbackRates() || [];
47886  }
47887
47888  /**
47889   * Get whether playback rates is supported by the tech
47890   * and an array of playback rates exists
47891   *
47892   * @return {boolean}
47893   *         Whether changing playback rate is supported
47894   */
47895  playbackRateSupported() {
47896    return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
47897  }
47898
47899  /**
47900   * Hide playback rate controls when they're no playback rate options to select
47901   *
47902   * @param {Event} [event]
47903   *        The event that caused this function to run.
47904   *
47905   * @listens Player#loadstart
47906   */
47907  updateVisibility(event) {
47908    if (this.playbackRateSupported()) {
47909      this.removeClass('vjs-hidden');
47910    } else {
47911      this.addClass('vjs-hidden');
47912    }
47913  }
47914
47915  /**
47916   * Update button label when rate changed
47917   *
47918   * @param {Event} [event]
47919   *        The event that caused this function to run.
47920   *
47921   * @listens Player#ratechange
47922   */
47923  updateLabel(event) {
47924    if (this.playbackRateSupported()) {
47925      this.labelEl_.textContent = this.player().playbackRate() + 'x';
47926    }
47927  }
47928}
47929
47930/**
47931 * The text that should display over the `PlaybackRateMenuButton`s controls.
47932 *
47933 * Added for localization.
47934 *
47935 * @type {string}
47936 * @protected
47937 */
47938PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
47939Component$1.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
47940
47941/**
47942 * @file spacer.js
47943 */
47944
47945/**
47946 * Just an empty spacer element that can be used as an append point for plugins, etc.
47947 * Also can be used to create space between elements when necessary.
47948 *
47949 * @extends Component
47950 */
47951class Spacer extends Component$1 {
47952  /**
47953  * Builds the default DOM `className`.
47954  *
47955  * @return {string}
47956  *         The DOM `className` for this object.
47957  */
47958  buildCSSClass() {
47959    return `vjs-spacer ${super.buildCSSClass()}`;
47960  }
47961
47962  /**
47963   * Create the `Component`'s DOM element
47964   *
47965   * @return {Element}
47966   *         The element that was created.
47967   */
47968  createEl(tag = 'div', props = {}, attributes = {}) {
47969    if (!props.className) {
47970      props.className = this.buildCSSClass();
47971    }
47972    return super.createEl(tag, props, attributes);
47973  }
47974}
47975Component$1.registerComponent('Spacer', Spacer);
47976
47977/**
47978 * @file custom-control-spacer.js
47979 */
47980
47981/**
47982 * Spacer specifically meant to be used as an insertion point for new plugins, etc.
47983 *
47984 * @extends Spacer
47985 */
47986class CustomControlSpacer extends Spacer {
47987  /**
47988   * Builds the default DOM `className`.
47989   *
47990   * @return {string}
47991   *         The DOM `className` for this object.
47992   */
47993  buildCSSClass() {
47994    return `vjs-custom-control-spacer ${super.buildCSSClass()}`;
47995  }
47996
47997  /**
47998   * Create the `Component`'s DOM element
47999   *
48000   * @return {Element}
48001   *         The element that was created.
48002   */
48003  createEl() {
48004    return super.createEl('div', {
48005      className: this.buildCSSClass(),
48006      // No-flex/table-cell mode requires there be some content
48007      // in the cell to fill the remaining space of the table.
48008      textContent: '\u00a0'
48009    });
48010  }
48011}
48012Component$1.registerComponent('CustomControlSpacer', CustomControlSpacer);
48013
48014/**
48015 * @file control-bar.js
48016 */
48017
48018/**
48019 * Container of main controls.
48020 *
48021 * @extends Component
48022 */
48023class ControlBar extends Component$1 {
48024  /**
48025   * Create the `Component`'s DOM element
48026   *
48027   * @return {Element}
48028   *         The element that was created.
48029   */
48030  createEl() {
48031    return super.createEl('div', {
48032      className: 'vjs-control-bar',
48033      dir: 'ltr'
48034    });
48035  }
48036}
48037
48038/**
48039 * Default options for `ControlBar`
48040 *
48041 * @type {Object}
48042 * @private
48043 */
48044ControlBar.prototype.options_ = {
48045  children: ['playToggle', 'skipBackward', 'skipForward', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'pictureInPictureToggle', 'fullscreenToggle']
48046};
48047Component$1.registerComponent('ControlBar', ControlBar);
48048
48049/**
48050 * @file error-display.js
48051 */
48052
48053/** @import Player from './player' */
48054
48055/**
48056 * A display that indicates an error has occurred. This means that the video
48057 * is unplayable.
48058 *
48059 * @extends ModalDialog
48060 */
48061class ErrorDisplay extends ModalDialog {
48062  /**
48063   * Creates an instance of this class.
48064   *
48065   * @param  {Player} player
48066   *         The `Player` that this class should be attached to.
48067   *
48068   * @param  {Object} [options]
48069   *         The key/value store of player options.
48070   */
48071  constructor(player, options) {
48072    super(player, options);
48073    this.on(player, 'error', e => {
48074      this.open(e);
48075    });
48076  }
48077
48078  /**
48079   * Builds the default DOM `className`.
48080   *
48081   * @return {string}
48082   *         The DOM `className` for this object.
48083   *
48084   * @deprecated Since version 5.
48085   */
48086  buildCSSClass() {
48087    return `vjs-error-display ${super.buildCSSClass()}`;
48088  }
48089
48090  /**
48091   * Gets the localized error message based on the `Player`s error.
48092   *
48093   * @return {string}
48094   *         The `Player`s error message localized or an empty string.
48095   */
48096  content() {
48097    const error = this.player().error();
48098    return error ? this.localize(error.message) : '';
48099  }
48100}
48101
48102/**
48103 * The default options for an `ErrorDisplay`.
48104 *
48105 * @private
48106 */
48107ErrorDisplay.prototype.options_ = Object.assign({}, ModalDialog.prototype.options_, {
48108  pauseOnOpen: false,
48109  fillAlways: true,
48110  temporary: false,
48111  uncloseable: true
48112});
48113Component$1.registerComponent('ErrorDisplay', ErrorDisplay);
48114
48115/** @import Player from '../player' */
48116/** @import { ContentDescriptor } from  '../utils/dom' */
48117
48118/**
48119 * Creates DOM element of 'select' & its options.
48120 *
48121 * @extends Component
48122 */
48123class TextTrackSelect extends Component$1 {
48124  /**
48125   * Creates an instance of this class.
48126   *
48127   * @param {Player} player
48128   *        The `Player` that this class should be attached to.
48129   *
48130   * @param {Object} [options]
48131   *        The key/value store of player options.
48132   *
48133   * @param {ContentDescriptor} [options.content=undefined]
48134   *        Provide customized content for this modal.
48135   *
48136   * @param {string} [options.legendId]
48137   *        A text with part of an string to create atribute of aria-labelledby.
48138   *
48139   * @param {string} [options.id]
48140   *        A text with part of an string to create atribute of aria-labelledby.
48141   *
48142   * @param {Array} [options.SelectOptions]
48143   *        Array that contains the value & textContent of for each of the
48144   *        options elements.
48145   */
48146  constructor(player, options = {}) {
48147    super(player, options);
48148    this.el_.setAttribute('aria-labelledby', this.selectLabelledbyIds);
48149  }
48150
48151  /**
48152   * Create the `TextTrackSelect`'s DOM element
48153   *
48154   * @return {Element}
48155   *         The DOM element that gets created.
48156   */
48157  createEl() {
48158    this.selectLabelledbyIds = [this.options_.legendId, this.options_.labelId].join(' ').trim();
48159
48160    // Create select & inner options
48161    const selectoptions = createEl('select', {
48162      id: this.options_.id
48163    }, {}, this.options_.SelectOptions.map(optionText => {
48164      // Constructs an id for the <option>.
48165      // For the colour settings that have two <selects> with a <label> each, generates an id based off the label value
48166      // For font size/family and edge style with one <select> and no <label>, generates an id with a guid
48167      const optionId = (this.options_.labelId ? this.options_.labelId : `vjs-track-option-${newGUID()}`) + '-' + optionText[1].replace(/\W+/g, '');
48168      const option = createEl('option', {
48169        id: optionId,
48170        value: this.localize(optionText[0]),
48171        textContent: this.localize(optionText[1])
48172      });
48173      option.setAttribute('aria-labelledby', `${this.selectLabelledbyIds} ${optionId}`);
48174      return option;
48175    }));
48176    return selectoptions;
48177  }
48178}
48179Component$1.registerComponent('TextTrackSelect', TextTrackSelect);
48180
48181/** @import Player from '../player' */
48182/** @import { ContentDescriptor } from '../utils/dom' */
48183
48184/**
48185 * Creates fieldset section of 'TextTrackSettings'.
48186 * Manganes two versions of fieldsets, one for type of 'colors'
48187 * & the other for 'font', Component adds diferent DOM elements
48188 * to that fieldset  depending on the type.
48189 *
48190 * @extends Component
48191 */
48192class TextTrackFieldset extends Component$1 {
48193  /**
48194   * Creates an instance of this class.
48195   *
48196   * @param {Player} player
48197   *        The `Player` that this class should be attached to.
48198   *
48199   * @param {Object} [options]
48200   *        The key/value store of player options.
48201   *
48202   * @param {ContentDescriptor} [options.content=undefined]
48203   *        Provide customized content for this modal.
48204   *
48205   * @param {string} [options.legendId]
48206   *        A text with part of an string to create atribute of aria-labelledby.
48207   *        It passes to 'TextTrackSelect'.
48208   *
48209   * @param {string} [options.id]
48210   *        A text with part of an string to create atribute of aria-labelledby.
48211   *        It passes to 'TextTrackSelect'.
48212   *
48213   * @param {string} [options.legendText]
48214   *        A text to use as the text content of the legend element.
48215   *
48216   * @param {Array} [options.selects]
48217   *        Array that contains the selects that are use to create 'selects'
48218   *        components.
48219   *
48220   * @param {Array} [options.SelectOptions]
48221   *        Array that contains the value & textContent of for each of the
48222   *        options elements, it passes to 'TextTrackSelect'.
48223   *
48224   * @param {string} [options.type]
48225   *        Conditions if some DOM elements will be added to the fieldset
48226   *        component.
48227   *
48228   * @param {Object} [options.selectConfigs]
48229   *        Object with the following properties that are the selects configurations:
48230   *        backgroundColor, backgroundOpacity, color, edgeStyle, fontFamily,
48231   *        fontPercent, textOpacity, windowColor, windowOpacity.
48232   *        These properties are use to configure the 'TextTrackSelect' Component.
48233   */
48234  constructor(player, options = {}) {
48235    super(player, options);
48236
48237    // Add Components & DOM Elements
48238    const legendElement = createEl('legend', {
48239      textContent: this.localize(this.options_.legendText),
48240      id: this.options_.legendId
48241    });
48242    this.el().appendChild(legendElement);
48243    const selects = this.options_.selects;
48244
48245    // Iterate array of selects to create 'selects' components
48246    for (const i of selects) {
48247      const selectConfig = this.options_.selectConfigs[i];
48248      const selectClassName = selectConfig.className;
48249      const id = selectConfig.id.replace('%s', this.options_.id_);
48250      let span = null;
48251      const guid = `vjs_select_${newGUID()}`;
48252
48253      // Conditionally create span to add on the component
48254      if (this.options_.type === 'colors') {
48255        span = createEl('span', {
48256          className: selectClassName
48257        });
48258        const label = createEl('label', {
48259          id,
48260          className: 'vjs-label',
48261          textContent: this.localize(selectConfig.label)
48262        });
48263        label.setAttribute('for', guid);
48264        span.appendChild(label);
48265      }
48266      const textTrackSelect = new TextTrackSelect(player, {
48267        SelectOptions: selectConfig.options,
48268        legendId: this.options_.legendId,
48269        id: guid,
48270        labelId: id
48271      });
48272      this.addChild(textTrackSelect);
48273
48274      // Conditionally append to 'select' component to conditionally created span
48275      if (this.options_.type === 'colors') {
48276        span.appendChild(textTrackSelect.el());
48277        this.el().appendChild(span);
48278      }
48279    }
48280  }
48281
48282  /**
48283   * Create the `TextTrackFieldset`'s DOM element
48284   *
48285   * @return {Element}
48286   *         The DOM element that gets created.
48287   */
48288  createEl() {
48289    const el = createEl('fieldset', {
48290      // Prefixing classes of elements within a player with "vjs-"
48291      // is a convention used in Video.js.
48292      className: this.options_.className
48293    });
48294    return el;
48295  }
48296}
48297Component$1.registerComponent('TextTrackFieldset', TextTrackFieldset);
48298
48299/** @import Player from '../player' */
48300/** @import { ContentDescriptor } from  '../utils/dom' */
48301
48302/**
48303 * The component 'TextTrackSettingsColors' displays a set of 'fieldsets'
48304 * using the component 'TextTrackFieldset'.
48305 *
48306 * @extends Component
48307 */
48308class TextTrackSettingsColors extends Component$1 {
48309  /**
48310   * Creates an instance of this class.
48311   *
48312   * @param {Player} player
48313   *        The `Player` that this class should be attached to.
48314   *
48315   * @param {Object} [options]
48316   *        The key/value store of player options.
48317   *
48318   * @param {ContentDescriptor} [options.content=undefined]
48319   *        Provide customized content for this modal.
48320   *
48321   * @param {Array} [options.fieldSets]
48322   *        Array that contains the configurations for the selects.
48323   *
48324   * @param {Object} [options.selectConfigs]
48325   *        Object with the following properties that are the select confugations:
48326   *        backgroundColor, backgroundOpacity, color, edgeStyle, fontFamily,
48327   *        fontPercent, textOpacity, windowColor, windowOpacity.
48328   *        it passes to 'TextTrackFieldset'.
48329   */
48330  constructor(player, options = {}) {
48331    super(player, options);
48332    const id_ = this.options_.textTrackComponentid;
48333
48334    // createElFgColor_
48335    const ElFgColorFieldset = new TextTrackFieldset(player, {
48336      id_,
48337      legendId: `captions-text-legend-${id_}`,
48338      legendText: this.localize('Text'),
48339      className: 'vjs-fg vjs-track-setting',
48340      selects: this.options_.fieldSets[0],
48341      selectConfigs: this.options_.selectConfigs,
48342      type: 'colors'
48343    });
48344    this.addChild(ElFgColorFieldset);
48345
48346    // createElBgColor_
48347    const ElBgColorFieldset = new TextTrackFieldset(player, {
48348      id_,
48349      legendId: `captions-background-${id_}`,
48350      legendText: this.localize('Text Background'),
48351      className: 'vjs-bg vjs-track-setting',
48352      selects: this.options_.fieldSets[1],
48353      selectConfigs: this.options_.selectConfigs,
48354      type: 'colors'
48355    });
48356    this.addChild(ElBgColorFieldset);
48357
48358    // createElWinColor_
48359    const ElWinColorFieldset = new TextTrackFieldset(player, {
48360      id_,
48361      legendId: `captions-window-${id_}`,
48362      legendText: this.localize('Caption Area Background'),
48363      className: 'vjs-window vjs-track-setting',
48364      selects: this.options_.fieldSets[2],
48365      selectConfigs: this.options_.selectConfigs,
48366      type: 'colors'
48367    });
48368    this.addChild(ElWinColorFieldset);
48369  }
48370
48371  /**
48372   * Create the `TextTrackSettingsColors`'s DOM element
48373   *
48374   * @return {Element}
48375   *         The DOM element that gets created.
48376   */
48377  createEl() {
48378    const el = createEl('div', {
48379      className: 'vjs-track-settings-colors'
48380    });
48381    return el;
48382  }
48383}
48384Component$1.registerComponent('TextTrackSettingsColors', TextTrackSettingsColors);
48385
48386/** @import Player from '../player' */
48387/** @import { ContentDescriptor } from  '../utils/dom' */
48388
48389/**
48390 * The component 'TextTrackSettingsFont' displays a set of 'fieldsets'
48391 * using the component 'TextTrackFieldset'.
48392 *
48393 * @extends Component
48394 */
48395class TextTrackSettingsFont extends Component$1 {
48396  /**
48397   * Creates an instance of this class.
48398   *
48399   * @param {Player} player
48400   *        The `Player` that this class should be attached to.
48401   *
48402   * @param {Object} [options]
48403   *        The key/value store of player options.
48404   *
48405   * @param {ContentDescriptor} [options.content=undefined]
48406   *        Provide customized content for this modal.
48407   *
48408   * @param {Array} [options.fieldSets]
48409   *        Array that contains the configurations for the selects.
48410   *
48411   * @param {Object} [options.selectConfigs]
48412   *        Object with the following properties that are the select confugations:
48413   *        backgroundColor, backgroundOpacity, color, edgeStyle, fontFamily,
48414   *        fontPercent, textOpacity, windowColor, windowOpacity.
48415   *        it passes to 'TextTrackFieldset'.
48416   */
48417  constructor(player, options = {}) {
48418    super(player, options);
48419    const id_ = this.options_.textTrackComponentid;
48420    const ElFgColorFieldset = new TextTrackFieldset(player, {
48421      id_,
48422      legendId: `captions-font-size-${id_}`,
48423      legendText: 'Font Size',
48424      className: 'vjs-font-percent vjs-track-setting',
48425      selects: this.options_.fieldSets[0],
48426      selectConfigs: this.options_.selectConfigs,
48427      type: 'font'
48428    });
48429    this.addChild(ElFgColorFieldset);
48430    const ElBgColorFieldset = new TextTrackFieldset(player, {
48431      id_,
48432      legendId: `captions-edge-style-${id_}`,
48433      legendText: this.localize('Text Edge Style'),
48434      className: 'vjs-edge-style vjs-track-setting',
48435      selects: this.options_.fieldSets[1],
48436      selectConfigs: this.options_.selectConfigs,
48437      type: 'font'
48438    });
48439    this.addChild(ElBgColorFieldset);
48440    const ElWinColorFieldset = new TextTrackFieldset(player, {
48441      id_,
48442      legendId: `captions-font-family-${id_}`,
48443      legendText: this.localize('Font Family'),
48444      className: 'vjs-font-family vjs-track-setting',
48445      selects: this.options_.fieldSets[2],
48446      selectConfigs: this.options_.selectConfigs,
48447      type: 'font'
48448    });
48449    this.addChild(ElWinColorFieldset);
48450  }
48451
48452  /**
48453   * Create the `TextTrackSettingsFont`'s DOM element
48454   *
48455   * @return {Element}
48456   *         The DOM element that gets created.
48457   */
48458  createEl() {
48459    const el = createEl('div', {
48460      className: 'vjs-track-settings-font'
48461    });
48462    return el;
48463  }
48464}
48465Component$1.registerComponent('TextTrackSettingsFont', TextTrackSettingsFont);
48466
48467/**
48468 * Buttons of reset & done that modal 'TextTrackSettings'
48469 * uses as part of its content.
48470 *
48471 * 'Reset': Resets all settings on 'TextTrackSettings'.
48472 * 'Done': Closes 'TextTrackSettings' modal.
48473 *
48474 * @extends Component
48475 */
48476class TrackSettingsControls extends Component$1 {
48477  constructor(player, options = {}) {
48478    super(player, options);
48479
48480    // Create DOM elements
48481    const resetButton = new Button(player, {
48482      controlText: this.localize('restore all settings to the default values'),
48483      className: 'vjs-default-button'
48484    });
48485    resetButton.el().classList.remove('vjs-control', 'vjs-button');
48486    resetButton.el().textContent = this.localize('Reset');
48487    this.addChild(resetButton);
48488    const doneText = this.localize('Done');
48489    const doneButton = new Button(player, {
48490      controlText: doneText,
48491      className: 'vjs-done-button'
48492    });
48493
48494    // Remove unrequired style classes
48495    doneButton.el().classList.remove('vjs-control', 'vjs-button');
48496    doneButton.el().textContent = doneText;
48497    this.addChild(doneButton);
48498  }
48499
48500  /**
48501   * Create the `TrackSettingsControls`'s DOM element
48502   *
48503   * @return {Element}
48504   *         The DOM element that gets created.
48505   */
48506  createEl() {
48507    const el = createEl('div', {
48508      className: 'vjs-track-settings-controls'
48509    });
48510    return el;
48511  }
48512}
48513Component$1.registerComponent('TrackSettingsControls', TrackSettingsControls);
48514
48515/**
48516 * @file text-track-settings.js
48517 */
48518
48519/** @import Player from '../player' */
48520
48521const LOCAL_STORAGE_KEY$1 = 'vjs-text-track-settings';
48522const COLOR_BLACK = ['#000', 'Black'];
48523const COLOR_BLUE = ['#00F', 'Blue'];
48524const COLOR_CYAN = ['#0FF', 'Cyan'];
48525const COLOR_GREEN = ['#0F0', 'Green'];
48526const COLOR_MAGENTA = ['#F0F', 'Magenta'];
48527const COLOR_RED = ['#F00', 'Red'];
48528const COLOR_WHITE = ['#FFF', 'White'];
48529const COLOR_YELLOW = ['#FF0', 'Yellow'];
48530const OPACITY_OPAQUE = ['1', 'Opaque'];
48531const OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
48532const OPACITY_TRANS = ['0', 'Transparent'];
48533
48534// Configuration for the various <select> elements in the DOM of this component.
48535//
48536// Possible keys include:
48537//
48538// `default`:
48539//   The default option index. Only needs to be provided if not zero.
48540// `parser`:
48541//   A function which is used to parse the value from the selected option in
48542//   a customized way.
48543// `selector`:
48544//   The selector used to find the associated <select> element.
48545const selectConfigs = {
48546  backgroundColor: {
48547    selector: '.vjs-bg-color > select',
48548    id: 'captions-background-color-%s',
48549    label: 'Color',
48550    options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN],
48551    className: 'vjs-bg-color'
48552  },
48553  backgroundOpacity: {
48554    selector: '.vjs-bg-opacity > select',
48555    id: 'captions-background-opacity-%s',
48556    label: 'Opacity',
48557    options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS],
48558    className: 'vjs-bg-opacity vjs-opacity'
48559  },
48560  color: {
48561    selector: '.vjs-text-color > select',
48562    id: 'captions-foreground-color-%s',
48563    label: 'Color',
48564    options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN],
48565    className: 'vjs-text-color'
48566  },
48567  edgeStyle: {
48568    selector: '.vjs-edge-style > select',
48569    id: '',
48570    label: 'Text Edge Style',
48571    options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Drop shadow']]
48572  },
48573  fontFamily: {
48574    selector: '.vjs-font-family > select',
48575    id: '',
48576    label: 'Font Family',
48577    options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', '
vendor: 4,701 bytes, lines 48577-48737
48577Small Caps']]
48578  },
48579  fontPercent: {
48580    selector: '.vjs-font-percent > select',
48581    id: '',
48582    label: 'Font Size',
48583    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%']],
48584    default: 2,
48585    parser: v => v === '1.00' ? null : Number(v)
48586  },
48587  textOpacity: {
48588    selector: '.vjs-text-opacity > select',
48589    id: 'captions-foreground-opacity-%s',
48590    label: 'Opacity',
48591    options: [OPACITY_OPAQUE, OPACITY_SEMI],
48592    className: 'vjs-text-opacity vjs-opacity'
48593  },
48594  // Options for this object are defined below.
48595  windowColor: {
48596    selector: '.vjs-window-color > select',
48597    id: 'captions-window-color-%s',
48598    label: 'Color',
48599    className: 'vjs-window-color'
48600  },
48601  // Options for this object are defined below.
48602  windowOpacity: {
48603    selector: '.vjs-window-opacity > select',
48604    id: 'captions-window-opacity-%s',
48605    label: 'Opacity',
48606    options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE],
48607    className: 'vjs-window-opacity vjs-opacity'
48608  }
48609};
48610selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
48611
48612/**
48613 * Get the actual value of an option.
48614 *
48615 * @param  {string} value
48616 *         The value to get
48617 *
48618 * @param  {Function} [parser]
48619 *         Optional function to adjust the value.
48620 *
48621 * @return {*}
48622 *         - Will be `undefined` if no value exists
48623 *         - Will be `undefined` if the given value is "none".
48624 *         - Will be the actual value otherwise.
48625 *
48626 * @private
48627 */
48628function parseOptionValue(value, parser) {
48629  if (parser) {
48630    value = parser(value);
48631  }
48632  if (value && value !== 'none') {
48633    return value;
48634  }
48635}
48636
48637/**
48638 * Gets the value of the selected <option> element within a <select> element.
48639 *
48640 * @param  {Element} el
48641 *         the element to look in
48642 *
48643 * @param  {Function} [parser]
48644 *         Optional function to adjust the value.
48645 *
48646 * @return {*}
48647 *         - Will be `undefined` if no value exists
48648 *         - Will be `undefined` if the given value is "none".
48649 *         - Will be the actual value otherwise.
48650 *
48651 * @private
48652 */
48653function getSelectedOptionValue(el, parser) {
48654  const value = el.options[el.options.selectedIndex].value;
48655  return parseOptionValue(value, parser);
48656}
48657
48658/**
48659 * Sets the selected <option> element within a <select> element based on a
48660 * given value.
48661 *
48662 * @param {Element} el
48663 *        The element to look in.
48664 *
48665 * @param {string} value
48666 *        the property to look on.
48667 *
48668 * @param {Function} [parser]
48669 *        Optional function to adjust the value before comparing.
48670 *
48671 * @private
48672 */
48673function setSelectedOption(el, value, parser) {
48674  if (!value) {
48675    return;
48676  }
48677  for (let i = 0; i < el.options.length; i++) {
48678    if (parseOptionValue(el.options[i].value, parser) === value) {
48679      el.selectedIndex = i;
48680      break;
48681    }
48682  }
48683}
48684
48685/**
48686 * Manipulate Text Tracks settings.
48687 *
48688 * @extends ModalDialog
48689 */
48690class TextTrackSettings extends ModalDialog {
48691  /**
48692   * Creates an instance of this class.
48693   *
48694   * @param {Player} player
48695   *         The `Player` that this class should be attached to.
48696   *
48697   * @param {Object} [options]
48698   *         The key/value store of player options.
48699   */
48700  constructor(player, options) {
48701    options.temporary = false;
48702    super(player, options);
48703    this.updateDisplay = this.updateDisplay.bind(this);
48704
48705    // fill the modal and pretend we have opened it
48706    this.fill();
48707    this.hasBeenOpened_ = this.hasBeenFilled_ = true;
48708    this.renderModalComponents(player);
48709    this.endDialog = createEl('p', {
48710      className: 'vjs-control-text',
48711      textContent: this.localize('End of dialog window.')
48712    });
48713    this.el().appendChild(this.endDialog);
48714    this.setDefaults();
48715
48716    // Grab `persistTextTrackSettings` from the player options if not passed in child options
48717    if (options.persistTextTrackSettings === undefined) {
48718      this.options_.persistTextTrackSettings = this.options_.playerOptions.persistTextTrackSettings;
48719    }
48720    this.bindFunctionsToSelectsAndButtons();
48721    if (this.options_.persistTextTrackSettings) {
48722      this.restoreSettings();
48723    }
48724  }
48725  renderModalComponents(player) {
48726    const textTrackSettingsColors = new TextTrackSettingsColors(player, {
48727      textTrackComponentid: this.id_,
48728      selectConfigs,
48729      fieldSets: [['color', 'textOpacity'], ['backgroundColor', 'backgroundOpacity'], ['windowColor', 'windowOpacity']]
48730    });
48731    this.addChild(textTrackSettingsColors);
48732    const textTrackSettingsFont = new TextTrackSettingsFont(player, {
48733      textTrackComponentid: this.id_,
48734      selectConfigs,
48735      fieldSets: [['fontPercent'], ['edgeStyle'], ['fontFamily']]
48736    });
48737    this.addChild(textTrackSettingsFont);
vendor: 9,913 bytes, lines 48738-49056
48738    const trackSettingsControls = new TrackSettingsControls(player);
48739    this.addChild(trackSettingsControls);
48740  }
48741  bindFunctionsToSelectsAndButtons() {
48742    this.on(this.$('.vjs-done-button'), ['click', 'tap'], () => {
48743      this.saveSettings();
48744      this.close();
48745    });
48746    this.on(this.$('.vjs-default-button'), ['click', 'tap'], () => {
48747      this.setDefaults();
48748      this.updateDisplay();
48749    });
48750    each(selectConfigs, config => {
48751      this.on(this.$(config.selector), 'change', this.updateDisplay);
48752    });
48753  }
48754  dispose() {
48755    this.endDialog = null;
48756    super.dispose();
48757  }
48758  label() {
48759    return this.localize('Caption Settings Dialog');
48760  }
48761  description() {
48762    return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
48763  }
48764  buildCSSClass() {
48765    return super.buildCSSClass() + ' vjs-text-track-settings';
48766  }
48767
48768  /**
48769   * Gets an object of text track settings (or null).
48770   *
48771   * @return {Object}
48772   *         An object with config values parsed from the DOM or localStorage.
48773   */
48774  getValues() {
48775    return reduce(selectConfigs, (accum, config, key) => {
48776      const value = getSelectedOptionValue(this.$(config.selector), config.parser);
48777      if (value !== undefined) {
48778        accum[key] = value;
48779      }
48780      return accum;
48781    }, {});
48782  }
48783
48784  /**
48785   * Sets text track settings from an object of values.
48786   *
48787   * @param {Object} values
48788   *        An object with config values parsed from the DOM or localStorage.
48789   */
48790  setValues(values) {
48791    each(selectConfigs, (config, key) => {
48792      setSelectedOption(this.$(config.selector), values[key], config.parser);
48793    });
48794  }
48795
48796  /**
48797   * Sets all `<select>` elements to their default values.
48798   */
48799  setDefaults() {
48800    each(selectConfigs, config => {
48801      const index = config.hasOwnProperty('default') ? config.default : 0;
48802      this.$(config.selector).selectedIndex = index;
48803    });
48804  }
48805
48806  /**
48807   * Restore texttrack settings from localStorage
48808   */
48809  restoreSettings() {
48810    let values;
48811    try {
48812      values = JSON.parse(window__default["default"].localStorage.getItem(LOCAL_STORAGE_KEY$1));
48813    } catch (err) {
48814      log$1.warn(err);
48815    }
48816    if (values) {
48817      this.setValues(values);
48818    }
48819  }
48820
48821  /**
48822   * Save text track settings to localStorage
48823   */
48824  saveSettings() {
48825    if (!this.options_.persistTextTrackSettings) {
48826      return;
48827    }
48828    const values = this.getValues();
48829    try {
48830      if (Object.keys(values).length) {
48831        window__default["default"].localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(values));
48832      } else {
48833        window__default["default"].localStorage.removeItem(LOCAL_STORAGE_KEY$1);
48834      }
48835    } catch (err) {
48836      log$1.warn(err);
48837    }
48838  }
48839
48840  /**
48841   * Update display of text track settings
48842   */
48843  updateDisplay() {
48844    const ttDisplay = this.player_.getChild('textTrackDisplay');
48845    if (ttDisplay) {
48846      ttDisplay.updateDisplay();
48847    }
48848  }
48849
48850  /**
48851   * Repopulate dialog with new localizations on languagechange
48852   */
48853  handleLanguagechange() {
48854    this.fill();
48855    this.renderModalComponents(this.player_);
48856    this.bindFunctionsToSelectsAndButtons();
48857  }
48858}
48859Component$1.registerComponent('TextTrackSettings', TextTrackSettings);
48860
48861/**
48862 * @file resize-manager.js
48863 */
48864
48865/**
48866 * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
48867 *
48868 * 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}.
48869 *
48870 * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
48871 * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
48872 *
48873 * @example <caption>How to disable the resize manager</caption>
48874 * const player = videojs('#vid', {
48875 *   resizeManager: false
48876 * });
48877 *
48878 * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
48879 *
48880 * @extends Component
48881 */
48882class ResizeManager extends Component$1 {
48883  /**
48884   * Create the ResizeManager.
48885   *
48886   * @param {Object} player
48887   *        The `Player` that this class should be attached to.
48888   *
48889   * @param {Object} [options]
48890   *        The key/value store of ResizeManager options.
48891   *
48892   * @param {Object} [options.ResizeObserver]
48893   *        A polyfill for ResizeObserver can be passed in here.
48894   *        If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
48895   */
48896  constructor(player, options) {
48897    let RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window__default["default"].ResizeObserver;
48898
48899    // if `null` was passed, we want to disable the ResizeObserver
48900    if (options.ResizeObserver === null) {
48901      RESIZE_OBSERVER_AVAILABLE = false;
48902    }
48903
48904    // Only create an element when ResizeObserver isn't available
48905    const options_ = merge$1({
48906      createEl: !RESIZE_OBSERVER_AVAILABLE,
48907      reportTouchActivity: false
48908    }, options);
48909    super(player, options_);
48910    this.ResizeObserver = options.ResizeObserver || window__default["default"].ResizeObserver;
48911    this.loadListener_ = null;
48912    this.resizeObserver_ = null;
48913    this.debouncedHandler_ = debounce$1(() => {
48914      this.resizeHandler();
48915    }, 100, false, this);
48916    if (RESIZE_OBSERVER_AVAILABLE) {
48917      this.resizeObserver_ = new this.ResizeObserver(this.debouncedHandler_);
48918      this.resizeObserver_.observe(player.el());
48919    } else {
48920      this.loadListener_ = () => {
48921        if (!this.el_ || !this.el_.contentWindow) {
48922          return;
48923        }
48924        const debouncedHandler_ = this.debouncedHandler_;
48925        let unloadListener_ = this.unloadListener_ = function () {
48926          off(this, 'resize', debouncedHandler_);
48927          off(this, 'unload', unloadListener_);
48928          unloadListener_ = null;
48929        };
48930
48931        // safari and edge can unload the iframe before resizemanager dispose
48932        // we have to dispose of event handlers correctly before that happens
48933        on(this.el_.contentWindow, 'unload', unloadListener_);
48934        on(this.el_.contentWindow, 'resize', debouncedHandler_);
48935      };
48936      this.one('load', this.loadListener_);
48937    }
48938  }
48939  createEl() {
48940    return super.createEl('iframe', {
48941      className: 'vjs-resize-manager',
48942      tabIndex: -1,
48943      title: this.localize('No content')
48944    }, {
48945      'aria-hidden': 'true'
48946    });
48947  }
48948
48949  /**
48950   * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
48951   *
48952   * @fires Player#playerresize
48953   */
48954  resizeHandler() {
48955    /**
48956     * Called when the player size has changed
48957     *
48958     * @event Player#playerresize
48959     * @type {Event}
48960     */
48961    // make sure player is still around to trigger
48962    // prevents this from causing an error after dispose
48963    if (!this.player_ || !this.player_.trigger) {
48964      return;
48965    }
48966    this.player_.trigger('playerresize');
48967  }
48968  dispose() {
48969    if (this.debouncedHandler_) {
48970      this.debouncedHandler_.cancel();
48971    }
48972    if (this.resizeObserver_) {
48973      if (this.player_.el()) {
48974        this.resizeObserver_.unobserve(this.player_.el());
48975      }
48976      this.resizeObserver_.disconnect();
48977    }
48978    if (this.loadListener_) {
48979      this.off('load', this.loadListener_);
48980    }
48981    if (this.el_ && this.el_.contentWindow && this.unloadListener_) {
48982      this.unloadListener_.call(this.el_.contentWindow);
48983    }
48984    this.ResizeObserver = null;
48985    this.resizeObserver = null;
48986    this.debouncedHandler_ = null;
48987    this.loadListener_ = null;
48988    super.dispose();
48989  }
48990}
48991Component$1.registerComponent('ResizeManager', ResizeManager);
48992
48993/** @import Player from './player' */
48994
48995const defaults$1 = {
48996  trackingThreshold: 20,
48997  liveTolerance: 15
48998};
48999
49000/*
49001  track when we are at the live edge, and other helpers for live playback */
49002
49003/**
49004 * A class for checking live current time and determining when the player
49005 * is at or behind the live edge.
49006 */
49007class LiveTracker extends Component$1 {
49008  /**
49009   * Creates an instance of this class.
49010   *
49011   * @param {Player} player
49012   *        The `Player` that this class should be attached to.
49013   *
49014   * @param {Object} [options]
49015   *        The key/value store of player options.
49016   *
49017   * @param {number} [options.trackingThreshold=20]
49018   *        Number of seconds of live window (seekableEnd - seekableStart) that
49019   *        media needs to have before the liveui will be shown.
49020   *
49021   * @param {number} [options.liveTolerance=15]
49022   *        Number of seconds behind live that we have to be
49023   *        before we will be considered non-live. Note that this will only
49024   *        be used when playing at the live edge. This allows large seekable end
49025   *        changes to not effect whether we are live or not.
49026   */
49027  constructor(player, options) {
49028    // LiveTracker does not need an element
49029    const options_ = merge$1(defaults$1, options, {
49030      createEl: false
49031    });
49032    super(player, options_);
49033    this.trackLiveHandler_ = () => this.trackLive_();
49034    this.handlePlay_ = e => this.handlePlay(e);
49035    this.handleFirstTimeupdate_ = e => this.handleFirstTimeupdate(e);
49036    this.handleSeeked_ = e => this.handleSeeked(e);
49037    this.seekToLiveEdge_ = e => this.seekToLiveEdge(e);
49038    this.reset_();
49039    this.on(this.player_, 'durationchange', e => this.handleDurationchange(e));
49040    // we should try to toggle tracking on canplay as native playback engines, like Safari
49041    // may not have the proper values for things like seekableEnd until then
49042    this.on(this.player_, 'canplay', () => this.toggleTracking());
49043  }
49044
49045  /**
49046   * all the functionality for tracking when seek end changes
49047   * and for tracking how far past seek end we should be
49048   */
49049  trackLive_() {
49050    const seekable = this.player_.seekable();
49051
49052    // skip undefined seekable
49053    if (!seekable || !seekable.length) {
49054      return;
49055    }
49056    const newTime = Number(window__default["default"].performance.now().toFixed(4));
vendor: 7,712 bytes, lines 49057-49328
49057    const deltaTime = this.lastTime_ === -1 ? 0 : (newTime - this.lastTime_) / 1000;
49058    this.lastTime_ = newTime;
49059    this.pastSeekEnd_ = this.pastSeekEnd() + deltaTime;
49060    const liveCurrentTime = this.liveCurrentTime();
49061    const currentTime = this.player_.currentTime();
49062
49063    // we are behind live if any are true
49064    // 1. the player is paused
49065    // 2. the user seeked to a location 2 seconds away from live
49066    // 3. the difference between live and current time is greater
49067    //    liveTolerance which defaults to 15s
49068    let isBehind = this.player_.paused() || this.seekedBehindLive_ || Math.abs(liveCurrentTime - currentTime) > this.options_.liveTolerance;
49069
49070    // we cannot be behind if
49071    // 1. until we have not seen a timeupdate yet
49072    // 2. liveCurrentTime is Infinity, which happens on Android and Native Safari
49073    if (!this.timeupdateSeen_ || liveCurrentTime === Infinity) {
49074      isBehind = false;
49075    }
49076    if (isBehind !== this.behindLiveEdge_) {
49077      this.behindLiveEdge_ = isBehind;
49078      this.trigger('liveedgechange');
49079    }
49080  }
49081
49082  /**
49083   * handle a durationchange event on the player
49084   * and start/stop tracking accordingly.
49085   */
49086  handleDurationchange() {
49087    this.toggleTracking();
49088  }
49089
49090  /**
49091   * start/stop tracking
49092   */
49093  toggleTracking() {
49094    if (this.player_.duration() === Infinity && this.liveWindow() >= this.options_.trackingThreshold) {
49095      if (this.player_.options_.liveui) {
49096        this.player_.addClass('vjs-liveui');
49097      }
49098      this.startTracking();
49099    } else {
49100      this.player_.removeClass('vjs-liveui');
49101      this.stopTracking();
49102    }
49103  }
49104
49105  /**
49106   * start tracking live playback
49107   */
49108  startTracking() {
49109    if (this.isTracking()) {
49110      return;
49111    }
49112
49113    // If we haven't seen a timeupdate, we need to check whether playback
49114    // began before this component started tracking. This can happen commonly
49115    // when using autoplay.
49116    if (!this.timeupdateSeen_) {
49117      this.timeupdateSeen_ = this.player_.hasStarted();
49118    }
49119    this.trackingInterval_ = this.setInterval(this.trackLiveHandler_, UPDATE_REFRESH_INTERVAL);
49120    this.trackLive_();
49121    this.on(this.player_, ['play', 'pause'], this.trackLiveHandler_);
49122    if (!this.timeupdateSeen_) {
49123      this.one(this.player_, 'play', this.handlePlay_);
49124      this.one(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
49125    } else {
49126      this.on(this.player_, 'seeked', this.handleSeeked_);
49127    }
49128  }
49129
49130  /**
49131   * handle the first timeupdate on the player if it wasn't already playing
49132   * when live tracker started tracking.
49133   */
49134  handleFirstTimeupdate() {
49135    this.timeupdateSeen_ = true;
49136    this.on(this.player_, 'seeked', this.handleSeeked_);
49137  }
49138
49139  /**
49140   * Keep track of what time a seek starts, and listen for seeked
49141   * to find where a seek ends.
49142   */
49143  handleSeeked() {
49144    const timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime());
49145    this.seekedBehindLive_ = this.nextSeekedFromUser_ && timeDiff > 2;
49146    this.nextSeekedFromUser_ = false;
49147    this.trackLive_();
49148  }
49149
49150  /**
49151   * handle the first play on the player, and make sure that we seek
49152   * right to the live edge.
49153   */
49154  handlePlay() {
49155    this.one(this.player_, 'timeupdate', this.seekToLiveEdge_);
49156  }
49157
49158  /**
49159   * Stop tracking, and set all internal variables to
49160   * their initial value.
49161   */
49162  reset_() {
49163    this.lastTime_ = -1;
49164    this.pastSeekEnd_ = 0;
49165    this.lastSeekEnd_ = -1;
49166    this.behindLiveEdge_ = true;
49167    this.timeupdateSeen_ = false;
49168    this.seekedBehindLive_ = false;
49169    this.nextSeekedFromUser_ = false;
49170    this.clearInterval(this.trackingInterval_);
49171    this.trackingInterval_ = null;
49172    this.off(this.player_, ['play', 'pause'], this.trackLiveHandler_);
49173    this.off(this.player_, 'seeked', this.handleSeeked_);
49174    this.off(this.player_, 'play', this.handlePlay_);
49175    this.off(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
49176    this.off(this.player_, 'timeupdate', this.seekToLiveEdge_);
49177  }
49178
49179  /**
49180   * The next seeked event is from the user. Meaning that any seek
49181   * > 2s behind live will be considered behind live for real and
49182   * liveTolerance will be ignored.
49183   */
49184  nextSeekedFromUser() {
49185    this.nextSeekedFromUser_ = true;
49186  }
49187
49188  /**
49189   * stop tracking live playback
49190   */
49191  stopTracking() {
49192    if (!this.isTracking()) {
49193      return;
49194    }
49195    this.reset_();
49196    this.trigger('liveedgechange');
49197  }
49198
49199  /**
49200   * A helper to get the player seekable end
49201   * so that we don't have to null check everywhere
49202   *
49203   * @return {number}
49204   *         The furthest seekable end or Infinity.
49205   */
49206  seekableEnd() {
49207    const seekable = this.player_.seekable();
49208    const seekableEnds = [];
49209    let i = seekable ? seekable.length : 0;
49210    while (i--) {
49211      seekableEnds.push(seekable.end(i));
49212    }
49213
49214    // grab the furthest seekable end after sorting, or if there are none
49215    // default to Infinity
49216    return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity;
49217  }
49218
49219  /**
49220   * A helper to get the player seekable start
49221   * so that we don't have to null check everywhere
49222   *
49223   * @return {number}
49224   *         The earliest seekable start or 0.
49225   */
49226  seekableStart() {
49227    const seekable = this.player_.seekable();
49228    const seekableStarts = [];
49229    let i = seekable ? seekable.length : 0;
49230    while (i--) {
49231      seekableStarts.push(seekable.start(i));
49232    }
49233
49234    // grab the first seekable start after sorting, or if there are none
49235    // default to 0
49236    return seekableStarts.length ? seekableStarts.sort()[0] : 0;
49237  }
49238
49239  /**
49240   * Get the live time window aka
49241   * the amount of time between seekable start and
49242   * live current time.
49243   *
49244   * @return {number}
49245   *         The amount of seconds that are seekable in
49246   *         the live video.
49247   */
49248  liveWindow() {
49249    const liveCurrentTime = this.liveCurrentTime();
49250
49251    // if liveCurrenTime is Infinity then we don't have a liveWindow at all
49252    if (liveCurrentTime === Infinity) {
49253      return 0;
49254    }
49255    return liveCurrentTime - this.seekableStart();
49256  }
49257
49258  /**
49259   * Determines if the player is live, only checks if this component
49260   * is tracking live playback or not
49261   *
49262   * @return {boolean}
49263   *         Whether liveTracker is tracking
49264   */
49265  isLive() {
49266    return this.isTracking();
49267  }
49268
49269  /**
49270   * Determines if currentTime is at the live edge and won't fall behind
49271   * on each seekableendchange
49272   *
49273   * @return {boolean}
49274   *         Whether playback is at the live edge
49275   */
49276  atLiveEdge() {
49277    return !this.behindLiveEdge();
49278  }
49279
49280  /**
49281   * get what we expect the live current time to be
49282   *
49283   * @return {number}
49284   *         The expected live current time
49285   */
49286  liveCurrentTime() {
49287    return this.pastSeekEnd() + this.seekableEnd();
49288  }
49289
49290  /**
49291   * The number of seconds that have occurred after seekable end
49292   * changed. This will be reset to 0 once seekable end changes.
49293   *
49294   * @return {number}
49295   *         Seconds past the current seekable end
49296   */
49297  pastSeekEnd() {
49298    const seekableEnd = this.seekableEnd();
49299    if (this.lastSeekEnd_ !== -1 && seekableEnd !== this.lastSeekEnd_) {
49300      this.pastSeekEnd_ = 0;
49301    }
49302    this.lastSeekEnd_ = seekableEnd;
49303    return this.pastSeekEnd_;
49304  }
49305
49306  /**
49307   * If we are currently behind the live edge, aka currentTime will be
49308   * behind on a seekableendchange
49309   *
49310   * @return {boolean}
49311   *         If we are behind the live edge
49312   */
49313  behindLiveEdge() {
49314    return this.behindLiveEdge_;
49315  }
49316
49317  /**
49318   * Whether live tracker is currently tracking or not.
49319   */
49320  isTracking() {
49321    return typeof this.trackingInterval_ === 'number';
49322  }
49323
49324  /**
49325   * Seek to the live edge if we are behind the live edge
49326   */
49327  seekToLiveEdge() {
49328    this.seekedBehindLive_ = false;
vendor: 5,646 bytes, lines 49329-49529
49329    if (this.atLiveEdge()) {
49330      return;
49331    }
49332    this.nextSeekedFromUser_ = false;
49333    this.player_.currentTime(this.liveCurrentTime());
49334  }
49335
49336  /**
49337   * Dispose of liveTracker
49338   */
49339  dispose() {
49340    this.stopTracking();
49341    super.dispose();
49342  }
49343}
49344Component$1.registerComponent('LiveTracker', LiveTracker);
49345
49346/**
49347 * Displays an element over the player which contains an optional title and
49348 * description for the current content.
49349 *
49350 * Much of the code for this component originated in the now obsolete
49351 * videojs-dock plugin: https://github.com/brightcove/videojs-dock/
49352 *
49353 * @extends Component
49354 */
49355class TitleBar extends Component$1 {
49356  constructor(player, options) {
49357    super(player, options);
49358    this.on('statechanged', e => this.updateDom_());
49359    this.updateDom_();
49360  }
49361
49362  /**
49363   * Create the `TitleBar`'s DOM element
49364   *
49365   * @return {Element}
49366   *         The element that was created.
49367   */
49368  createEl() {
49369    this.els = {
49370      title: createEl('div', {
49371        className: 'vjs-title-bar-title',
49372        id: `vjs-title-bar-title-${newGUID()}`
49373      }),
49374      description: createEl('div', {
49375        className: 'vjs-title-bar-description',
49376        id: `vjs-title-bar-description-${newGUID()}`
49377      })
49378    };
49379    return createEl('div', {
49380      className: 'vjs-title-bar'
49381    }, {}, values(this.els));
49382  }
49383
49384  /**
49385   * Updates the DOM based on the component's state object.
49386   */
49387  updateDom_() {
49388    const tech = this.player_.tech_;
49389    const techEl = tech && tech.el_;
49390    const techAriaAttrs = {
49391      title: 'aria-labelledby',
49392      description: 'aria-describedby'
49393    };
49394    ['title', 'description'].forEach(k => {
49395      const value = this.state[k];
49396      const el = this.els[k];
49397      const techAriaAttr = techAriaAttrs[k];
49398      emptyEl(el);
49399      if (value) {
49400        textContent(el, value);
49401      }
49402
49403      // If there is a tech element available, update its ARIA attributes
49404      // according to whether a title and/or description have been provided.
49405      if (techEl) {
49406        techEl.removeAttribute(techAriaAttr);
49407        if (value) {
49408          techEl.setAttribute(techAriaAttr, el.id);
49409        }
49410      }
49411    });
49412    if (this.state.title || this.state.description) {
49413      this.show();
49414    } else {
49415      this.hide();
49416    }
49417  }
49418
49419  /**
49420   * Update the contents of the title bar component with new title and
49421   * description text.
49422   *
49423   * If both title and description are missing, the title bar will be hidden.
49424   *
49425   * If either title or description are present, the title bar will be visible.
49426   *
49427   * NOTE: Any previously set value will be preserved. To unset a previously
49428   * set value, you must pass an empty string or null.
49429   *
49430   * For example:
49431   *
49432   * ```
49433   * update({title: 'foo', description: 'bar'}) // title: 'foo', description: 'bar'
49434   * update({description: 'bar2'}) // title: 'foo', description: 'bar2'
49435   * update({title: ''}) // title: '', description: 'bar2'
49436   * update({title: 'foo', description: null}) // title: 'foo', description: null
49437   * ```
49438   *
49439   * @param  {Object} [options={}]
49440   *         An options object. When empty, the title bar will be hidden.
49441   *
49442   * @param  {string} [options.title]
49443   *         A title to display in the title bar.
49444   *
49445   * @param  {string} [options.description]
49446   *         A description to display in the title bar.
49447   */
49448  update(options) {
49449    this.setState(options);
49450  }
49451
49452  /**
49453   * Dispose the component.
49454   */
49455  dispose() {
49456    const tech = this.player_.tech_;
49457    const techEl = tech && tech.el_;
49458    if (techEl) {
49459      techEl.removeAttribute('aria-labelledby');
49460      techEl.removeAttribute('aria-describedby');
49461    }
49462    super.dispose();
49463    this.els = null;
49464  }
49465}
49466Component$1.registerComponent('TitleBar', TitleBar);
49467
49468/** @import Player from './player' */
49469
49470/**
49471 * @typedef {object} TransientButtonOptions
49472 * @property {string} [controlText] Control text, usually visible for these buttons
49473 * @property {number} [initialDisplay=4000] Time in ms that button should initially remain visible
49474 * @property {Array<'top'|'neartop'|'bottom'|'left'|'right'>} [position] Array of position strings to add basic styles for positioning
49475 * @property {string} [className] Class(es) to add
49476 * @property {boolean} [takeFocus=false] Whether element sohuld take focus when shown
49477 * @property {Function} [clickHandler] Function called on button activation
49478 */
49479
49480/** @type {TransientButtonOptions} */
49481const defaults = {
49482  initialDisplay: 4000,
49483  position: [],
49484  takeFocus: false
49485};
49486
49487/**
49488 * A floating transient button.
49489 * It's recommended to insert these buttons _before_ the control bar with the this argument to `addChild`
49490 * for a logical tab order.
49491 *
49492 * @example
49493 * ```
49494 * player.addChild(
49495 *   'TransientButton',
49496 *   options,
49497 *   player.children().indexOf(player.getChild("ControlBar"))
49498 * )
49499 * ```
49500 *
49501 * @extends Button
49502 */
49503class TransientButton extends Button {
49504  /**
49505   * TransientButton constructor
49506   *
49507   * @param {Player} player The button's player
49508   * @param {TransientButtonOptions} options Options for the transient button
49509   */
49510  constructor(player, options) {
49511    options = merge$1(defaults, options);
49512    super(player, options);
49513    this.controlText(options.controlText);
49514    this.hide();
49515
49516    // When shown, the float button will be visible even if the user is inactive.
49517    // Clear this if there is any interaction.
49518    this.on(this.player_, ['useractive', 'userinactive'], e => {
49519      this.removeClass('force-display');
49520    });
49521  }
49522
49523  /**
49524   * Return CSS class including position classes
49525   *
49526   * @return {string} CSS class list
49527   */
49528  buildCSSClass() {
49529    return `vjs-transient-button focus-visible ${this.options_.position.map(c =>
vendor: 10,815 bytes, lines 49529-49883
49529 `vjs-${c}`).join(' ')}`;
49530  }
49531
49532  /**
49533   * Create the button element
49534   *
49535   * @return {HTMLButtonElement} The button element
49536   */
49537  createEl() {
49538    /** @type HTMLButtonElement */
49539    const el = createEl('button', {}, {
49540      type: 'button',
49541      class: this.buildCSSClass()
49542    }, createEl('span'));
49543    this.controlTextEl_ = el.querySelector('span');
49544    return el;
49545  }
49546
49547  /**
49548   * Show the button. The button will remain visible for the `initialDisplay` time, default 4s,
49549   * and when there is user activity.
49550   */
49551  show() {
49552    super.show();
49553    this.addClass('force-display');
49554    if (this.options_.takeFocus) {
49555      this.el().focus({
49556        preventScroll: true
49557      });
49558    }
49559    this.forceDisplayTimeout = this.player_.setTimeout(() => {
49560      this.removeClass('force-display');
49561    }, this.options_.initialDisplay);
49562  }
49563
49564  /**
49565   * Hide the display, even if during the `initialDisplay` time.
49566   */
49567  hide() {
49568    this.removeClass('force-display');
49569    super.hide();
49570  }
49571
49572  /**
49573   * Dispose the component
49574   */
49575  dispose() {
49576    this.player_.clearTimeout(this.forceDisplayTimeout);
49577    super.dispose();
49578  }
49579}
49580Component$1.registerComponent('TransientButton', TransientButton);
49581
49582/** @import Html5 from './html5' */
49583
49584/**
49585 * This function is used to fire a sourceset when there is something
49586 * similar to `mediaEl.load()` being called. It will try to find the source via
49587 * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
49588 * with the source that was found or empty string if we cannot know. If it cannot
49589 * find a source then `sourceset` will not be fired.
49590 *
49591 * @param {Html5} tech
49592 *        The tech object that sourceset was setup on
49593 *
49594 * @return {boolean}
49595 *         returns false if the sourceset was not fired and true otherwise.
49596 */
49597const sourcesetLoad = tech => {
49598  const el = tech.el();
49599
49600  // if `el.src` is set, that source will be loaded.
49601  if (el.hasAttribute('src')) {
49602    tech.triggerSourceset(el.src);
49603    return true;
49604  }
49605
49606  /**
49607   * Since there isn't a src property on the media element, source elements will be used for
49608   * implementing the source selection algorithm. This happens asynchronously and
49609   * for most cases were there is more than one source we cannot tell what source will
49610   * be loaded, without re-implementing the source selection algorithm. At this time we are not
49611   * going to do that. There are three special cases that we do handle here though:
49612   *
49613   * 1. If there are no sources, do not fire `sourceset`.
49614   * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
49615   * 3. If there is more than one `<source>` but all of them have the same `src` url.
49616   *    That will be our src.
49617   */
49618  const sources = tech.$$('source');
49619  const srcUrls = [];
49620  let src = '';
49621
49622  // if there are no sources, do not fire sourceset
49623  if (!sources.length) {
49624    return false;
49625  }
49626
49627  // only count valid/non-duplicate source elements
49628  for (let i = 0; i < sources.length; i++) {
49629    const url = sources[i].src;
49630    if (url && srcUrls.indexOf(url) === -1) {
49631      srcUrls.push(url);
49632    }
49633  }
49634
49635  // there were no valid sources
49636  if (!srcUrls.length) {
49637    return false;
49638  }
49639
49640  // there is only one valid source element url
49641  // use that
49642  if (srcUrls.length === 1) {
49643    src = srcUrls[0];
49644  }
49645  tech.triggerSourceset(src);
49646  return true;
49647};
49648
49649/**
49650 * our implementation of an `innerHTML` descriptor for browsers
49651 * that do not have one.
49652 */
49653const innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
49654  get() {
49655    return this.cloneNode(true).innerHTML;
49656  },
49657  set(v) {
49658    // make a dummy node to use innerHTML on
49659    const dummy = document__default["default"].createElement(this.nodeName.toLowerCase());
49660
49661    // set innerHTML to the value provided
49662    dummy.innerHTML = v;
49663
49664    // make a document fragment to hold the nodes from dummy
49665    const docFrag = document__default["default"].createDocumentFragment();
49666
49667    // copy all of the nodes created by the innerHTML on dummy
49668    // to the document fragment
49669    while (dummy.childNodes.length) {
49670      docFrag.appendChild(dummy.childNodes[0]);
49671    }
49672
49673    // remove content
49674    this.innerText = '';
49675
49676    // now we add all of that html in one by appending the
49677    // document fragment. This is how innerHTML does it.
49678    window__default["default"].Element.prototype.appendChild.call(this, docFrag);
49679
49680    // then return the result that innerHTML's setter would
49681    return this.innerHTML;
49682  }
49683});
49684
49685/**
49686 * Get a property descriptor given a list of priorities and the
49687 * property to get.
49688 */
49689const getDescriptor = (priority, prop) => {
49690  let descriptor = {};
49691  for (let i = 0; i < priority.length; i++) {
49692    descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
49693    if (descriptor && descriptor.set && descriptor.get) {
49694      break;
49695    }
49696  }
49697  descriptor.enumerable = true;
49698  descriptor.configurable = true;
49699  return descriptor;
49700};
49701const getInnerHTMLDescriptor = tech => getDescriptor([tech.el(), window__default["default"].HTMLMediaElement.prototype, window__default["default"].Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
49702
49703/**
49704 * Patches browser internal functions so that we can tell synchronously
49705 * if a `<source>` was appended to the media element. For some reason this
49706 * causes a `sourceset` if the the media element is ready and has no source.
49707 * This happens when:
49708 * - The page has just loaded and the media element does not have a source.
49709 * - The media element was emptied of all sources, then `load()` was called.
49710 *
49711 * It does this by patching the following functions/properties when they are supported:
49712 *
49713 * - `append()` - can be used to add a `<source>` element to the media element
49714 * - `appendChild()` - can be used to add a `<source>` element to the media element
49715 * - `insertAdjacentHTML()` -  can be used to add a `<source>` element to the media element
49716 * - `innerHTML` -  can be used to add a `<source>` element to the media element
49717 *
49718 * @param {Html5} tech
49719 *        The tech object that sourceset is being setup on.
49720 */
49721const firstSourceWatch = function (tech) {
49722  const el = tech.el();
49723
49724  // make sure firstSourceWatch isn't setup twice.
49725  if (el.resetSourceWatch_) {
49726    return;
49727  }
49728  const old = {};
49729  const innerDescriptor = getInnerHTMLDescriptor(tech);
49730  const appendWrapper = appendFn => (...args) => {
49731    const retval = appendFn.apply(el, args);
49732    sourcesetLoad(tech);
49733    return retval;
49734  };
49735  ['append', 'appendChild', 'insertAdjacentHTML'].forEach(k => {
49736    if (!el[k]) {
49737      return;
49738    }
49739
49740    // store the old function
49741    old[k] = el[k];
49742
49743    // call the old function with a sourceset if a source
49744    // was loaded
49745    el[k] = appendWrapper(old[k]);
49746  });
49747  Object.defineProperty(el, 'innerHTML', merge$1(innerDescriptor, {
49748    set: appendWrapper(innerDescriptor.set)
49749  }));
49750  el.resetSourceWatch_ = () => {
49751    el.resetSourceWatch_ = null;
49752    Object.keys(old).forEach(k => {
49753      el[k] = old[k];
49754    });
49755    Object.defineProperty(el, 'innerHTML', innerDescriptor);
49756  };
49757
49758  // on the first sourceset, we need to revert our changes
49759  tech.one('sourceset', el.resetSourceWatch_);
49760};
49761
49762/**
49763 * our implementation of a `src` descriptor for browsers
49764 * that do not have one
49765 */
49766const srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
49767  get() {
49768    if (this.hasAttribute('src')) {
49769      return getAbsoluteURL(window__default["default"].Element.prototype.getAttribute.call(this, 'src'));
49770    }
49771    return '';
49772  },
49773  set(v) {
49774    window__default["default"].Element.prototype.setAttribute.call(this, 'src', v);
49775    return v;
49776  }
49777});
49778const getSrcDescriptor = tech => getDescriptor([tech.el(), window__default["default"].HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
49779
49780/**
49781 * setup `sourceset` handling on the `Html5` tech. This function
49782 * patches the following element properties/functions:
49783 *
49784 * - `src` - to determine when `src` is set
49785 * - `setAttribute()` - to determine when `src` is set
49786 * - `load()` - this re-triggers the source selection algorithm, and can
49787 *              cause a sourceset.
49788 *
49789 * If there is no source when we are adding `sourceset` support or during a `load()`
49790 * we also patch the functions listed in `firstSourceWatch`.
49791 *
49792 * @param {Html5} tech
49793 *        The tech to patch
49794 */
49795const setupSourceset = function (tech) {
49796  if (!tech.featuresSourceset) {
49797    return;
49798  }
49799  const el = tech.el();
49800
49801  // make sure sourceset isn't setup twice.
49802  if (el.resetSourceset_) {
49803    return;
49804  }
49805  const srcDescriptor = getSrcDescriptor(tech);
49806  const oldSetAttribute = el.setAttribute;
49807  const oldLoad = el.load;
49808  Object.defineProperty(el, 'src', merge$1(srcDescriptor, {
49809    set: v => {
49810      const retval = srcDescriptor.set.call(el, v);
49811
49812      // we use the getter here to get the actual value set on src
49813      tech.triggerSourceset(el.src);
49814      return retval;
49815    }
49816  }));
49817  el.setAttribute = (n, v) => {
49818    const retval = oldSetAttribute.call(el, n, v);
49819    if (/src/i.test(n)) {
49820      tech.triggerSourceset(el.src);
49821    }
49822    return retval;
49823  };
49824  el.load = () => {
49825    const retval = oldLoad.call(el);
49826
49827    // if load was called, but there was no source to fire
49828    // sourceset on. We have to watch for a source append
49829    // as that can trigger a `sourceset` when the media element
49830    // has no source
49831    if (!sourcesetLoad(tech)) {
49832      tech.triggerSourceset('');
49833      firstSourceWatch(tech);
49834    }
49835    return retval;
49836  };
49837  if (el.currentSrc) {
49838    tech.triggerSourceset(el.currentSrc);
49839  } else if (!sourcesetLoad(tech)) {
49840    firstSourceWatch(tech);
49841  }
49842  el.resetSourceset_ = () => {
49843    el.resetSourceset_ = null;
49844    el.load = oldLoad;
49845    el.setAttribute = oldSetAttribute;
49846    Object.defineProperty(el, 'src', srcDescriptor);
49847    if (el.resetSourceWatch_) {
49848      el.resetSourceWatch_();
49849    }
49850  };
49851};
49852
49853/**
49854 * @file html5.js
49855 */
49856
49857/**
49858 * HTML5 Media Controller - Wrapper for HTML5 Media API
49859 *
49860 * @mixes Tech~SourceHandlerAdditions
49861 * @extends Tech
49862 */
49863class Html5 extends Tech {
49864  /**
49865  * Create an instance of this Tech.
49866  *
49867  * @param {Object} [options]
49868  *        The key/value store of player options.
49869  *
49870  * @param {Function} [ready]
49871  *        Callback function to call when the `HTML5` Tech is ready.
49872  */
49873  constructor(options, ready) {
49874    super(options, ready);
49875    const source = options.source;
49876    let crossoriginTracks = false;
49877    this.featuresVideoFrameCallback = this.featuresVideoFrameCallback && this.el_.tagName === 'VIDEO';
49878
49879    // Set the source if one is provided
49880    // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
49881    // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
49882    // anyway so the error gets fired.
49883    if (source && (this.el_.currentSrc !== source.src || options.tag && options.tag.
vendor: 6,484 bytes, lines 49883-50046
49883initNetworkState_ === 3)) {
49884      this.setSource(source);
49885    } else {
49886      this.handleLateInit_(this.el_);
49887    }
49888
49889    // setup sourceset after late sourceset/init
49890    if (options.enableSourceset) {
49891      this.setupSourcesetHandling_();
49892    }
49893    this.isScrubbing_ = false;
49894    if (this.el_.hasChildNodes()) {
49895      const nodes = this.el_.childNodes;
49896      let nodesLength = nodes.length;
49897      const removeNodes = [];
49898      while (nodesLength--) {
49899        const node = nodes[nodesLength];
49900        const nodeName = node.nodeName.toLowerCase();
49901        if (nodeName === 'track') {
49902          if (!this.featuresNativeTextTracks) {
49903            // Empty video tag tracks so the built-in player doesn't use them also.
49904            // This may not be fast enough to stop HTML5 browsers from reading the tags
49905            // so we'll need to turn off any default tracks if we're manually doing
49906            // captions and subtitles. videoElement.textTracks
49907            removeNodes.push(node);
49908          } else {
49909            // store HTMLTrackElement and TextTrack to remote list
49910            this.remoteTextTrackEls().addTrackElement_(node);
49911            this.remoteTextTracks().addTrack(node.track);
49912            this.textTracks().addTrack(node.track);
49913            if (!crossoriginTracks && !this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
49914              crossoriginTracks = true;
49915            }
49916          }
49917        }
49918      }
49919      for (let i = 0; i < removeNodes.length; i++) {
49920        this.el_.removeChild(removeNodes[i]);
49921      }
49922    }
49923    this.proxyNativeTracks_();
49924    if (this.featuresNativeTextTracks && crossoriginTracks) {
49925      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.');
49926    }
49927
49928    // prevent iOS Safari from disabling metadata text tracks during native playback
49929    this.restoreMetadataTracksInIOSNativePlayer_();
49930
49931    // Determine if native controls should be used
49932    // Our goal should be to get the custom controls on mobile solid everywhere
49933    // so we can remove this all together. Right now this will block custom
49934    // controls on touch enabled laptops like the Chrome Pixel
49935    if ((TOUCH_ENABLED || IS_IPHONE) && options.nativeControlsForTouch === true) {
49936      this.setControls(true);
49937    }
49938
49939    // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
49940    // into a `fullscreenchange` event
49941    this.proxyWebkitFullscreen_();
49942    this.triggerReady();
49943  }
49944
49945  /**
49946   * Dispose of `HTML5` media element and remove all tracks.
49947   */
49948  dispose() {
49949    if (this.el_ && this.el_.resetSourceset_) {
49950      this.el_.resetSourceset_();
49951    }
49952    Html5.disposeMediaElement(this.el_);
49953    this.options_ = null;
49954
49955    // tech will handle clearing of the emulated track list
49956    super.dispose();
49957  }
49958
49959  /**
49960   * Modify the media element so that we can detect when
49961   * the source is changed. Fires `sourceset` just after the source has changed
49962   */
49963  setupSourcesetHandling_() {
49964    setupSourceset(this);
49965  }
49966
49967  /**
49968   * When a captions track is enabled in the iOS Safari native player, all other
49969   * tracks are disabled (including metadata tracks), which nulls all of their
49970   * associated cue points. This will restore metadata tracks to their pre-fullscreen
49971   * state in those cases so that cue points are not needlessly lost.
49972   *
49973   * @private
49974   */
49975  restoreMetadataTracksInIOSNativePlayer_() {
49976    const textTracks = this.textTracks();
49977    let metadataTracksPreFullscreenState;
49978
49979    // captures a snapshot of every metadata track's current state
49980    const takeMetadataTrackSnapshot = () => {
49981      metadataTracksPreFullscreenState = [];
49982      for (let i = 0; i < textTracks.length; i++) {
49983        const track = textTracks[i];
49984        if (track.kind === 'metadata') {
49985          metadataTracksPreFullscreenState.push({
49986            track,
49987            storedMode: track.mode
49988          });
49989        }
49990      }
49991    };
49992
49993    // snapshot each metadata track's initial state, and update the snapshot
49994    // each time there is a track 'change' event
49995    takeMetadataTrackSnapshot();
49996    textTracks.addEventListener('change', takeMetadataTrackSnapshot);
49997    this.on('dispose', () => textTracks.removeEventListener('change', takeMetadataTrackSnapshot));
49998    const restoreTrackMode = () => {
49999      for (let i = 0; i < metadataTracksPreFullscreenState.length; i++) {
50000        const storedTrack = metadataTracksPreFullscreenState[i];
50001        if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
50002          storedTrack.track.mode = storedTrack.storedMode;
50003        }
50004      }
50005      // we only want this handler to be executed on the first 'change' event
50006      textTracks.removeEventListener('change', restoreTrackMode);
50007    };
50008
50009    // when we enter fullscreen playback, stop updating the snapshot and
50010    // restore all track modes to their pre-fullscreen state
50011    this.on('webkitbeginfullscreen', () => {
50012      textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
50013
50014      // remove the listener before adding it just in case it wasn't previously removed
50015      textTracks.removeEventListener('change', restoreTrackMode);
50016      textTracks.addEventListener('change', restoreTrackMode);
50017    });
50018
50019    // start updating the snapshot again after leaving fullscreen
50020    this.on('webkitendfullscreen', () => {
50021      // remove the listener before adding it just in case it wasn't previously removed
50022      textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
50023      textTracks.addEventListener('change', takeMetadataTrackSnapshot);
50024
50025      // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
50026      textTracks.removeEventListener('change', restoreTrackMode);
50027    });
50028  }
50029
50030  /**
50031   * Attempt to force override of tracks for the given type
50032   *
50033   * @param {string} type - Track type to override, possible values include 'Audio',
50034   * 'Video', and 'Text'.
50035   * @param {boolean} override - If set to true native audio/video will be overridden,
50036   * otherwise native audio/video will potentially be used.
50037   * @private
50038   */
50039  overrideNative_(type, override) {
50040    // If there is no behavioral change don't add/remove listeners
50041    if (override !== this[`featuresNative${type}Tracks`]) {
50042      return;
50043    }
50044    const lowerCaseType = type.toLowerCase();
50045    if (this[`${lowerCaseType}TracksListeners_`]) {
50046      Object.keys(this[`${lowerCaseType}
vendor: 3,091 bytes, lines 50046-50136
50046TracksListeners_`]).forEach(eventName => {
50047        const elTracks = this.el()[`${lowerCaseType}Tracks`];
50048        elTracks.removeEventListener(eventName, this[`${lowerCaseType}TracksListeners_`][eventName]);
50049      });
50050    }
50051    this[`featuresNative${type}Tracks`] = !override;
50052    this[`${lowerCaseType}TracksListeners_`] = null;
50053    this.proxyNativeTracksForType_(lowerCaseType);
50054  }
50055
50056  /**
50057   * Attempt to force override of native audio tracks.
50058   *
50059   * @param {boolean} override - If set to true native audio will be overridden,
50060   * otherwise native audio will potentially be used.
50061   */
50062  overrideNativeAudioTracks(override) {
50063    this.overrideNative_('Audio', override);
50064  }
50065
50066  /**
50067   * Attempt to force override of native video tracks.
50068   *
50069   * @param {boolean} override - If set to true native video will be overridden,
50070   * otherwise native video will potentially be used.
50071   */
50072  overrideNativeVideoTracks(override) {
50073    this.overrideNative_('Video', override);
50074  }
50075
50076  /**
50077   * Proxy native track list events for the given type to our track
50078   * lists if the browser we are playing in supports that type of track list.
50079   *
50080   * @param {string} name - Track type; values include 'audio', 'video', and 'text'
50081   * @private
50082   */
50083  proxyNativeTracksForType_(name) {
50084    const props = NORMAL[name];
50085    const elTracks = this.el()[props.getterName];
50086    const techTracks = this[props.getterName]();
50087    if (!this[`featuresNative${props.capitalName}Tracks`] || !elTracks || !elTracks.addEventListener) {
50088      return;
50089    }
50090    const listeners = {
50091      change: e => {
50092        const event = {
50093          type: 'change',
50094          target: techTracks,
50095          currentTarget: techTracks,
50096          srcElement: techTracks
50097        };
50098        techTracks.trigger(event);
50099
50100        // if we are a text track change event, we should also notify the
50101        // remote text track list. This can potentially cause a false positive
50102        // if we were to get a change event on a non-remote track and
50103        // we triggered the event on the remote text track list which doesn't
50104        // contain that track. However, best practices mean looping through the
50105        // list of tracks and searching for the appropriate mode value, so,
50106        // this shouldn't pose an issue
50107        if (name === 'text') {
50108          this[REMOTE.remoteText.getterName]().trigger(event);
50109        }
50110      },
50111      addtrack(e) {
50112        techTracks.addTrack(e.track);
50113      },
50114      removetrack(e) {
50115        techTracks.removeTrack(e.track);
50116      }
50117    };
50118    const removeOldTracks = function () {
50119      const removeTracks = [];
50120      for (let i = 0; i < techTracks.length; i++) {
50121        let found = false;
50122        for (let j = 0; j < elTracks.length; j++) {
50123          if (elTracks[j] === techTracks[i]) {
50124            found = true;
50125            break;
50126          }
50127        }
50128        if (!found) {
50129          removeTracks.push(techTracks[i]);
50130        }
50131      }
50132      while (removeTracks.length) {
50133        techTracks.removeTrack(removeTracks.shift());
50134      }
50135    };
50136    this[props.getterName + 'Listeners_'] = listeners;
vendor: 4,380 bytes, lines 50137-50254
50137    Object.keys(listeners).forEach(eventName => {
50138      const listener = listeners[eventName];
50139      elTracks.addEventListener(eventName, listener);
50140      this.on('dispose', e => elTracks.removeEventListener(eventName, listener));
50141    });
50142
50143    // Remove (native) tracks that are not used anymore
50144    this.on('loadstart', removeOldTracks);
50145    this.on('dispose', e => this.off('loadstart', removeOldTracks));
50146  }
50147
50148  /**
50149   * Proxy all native track list events to our track lists if the browser we are playing
50150   * in supports that type of track list.
50151   *
50152   * @private
50153   */
50154  proxyNativeTracks_() {
50155    NORMAL.names.forEach(name => {
50156      this.proxyNativeTracksForType_(name);
50157    });
50158  }
50159
50160  /**
50161   * Create the `Html5` Tech's DOM element.
50162   *
50163   * @return {Element}
50164   *         The element that gets created.
50165   */
50166  createEl() {
50167    let el = this.options_.tag;
50168
50169    // Check if this browser supports moving the element into the box.
50170    // On the iPhone video will break if you move the element,
50171    // So we have to create a brand new element.
50172    // If we ingested the player div, we do not need to move the media element.
50173    if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
50174      // If the original tag is still there, clone and remove it.
50175      if (el) {
50176        const clone = el.cloneNode(true);
50177        if (el.parentNode) {
50178          el.parentNode.insertBefore(clone, el);
50179        }
50180        Html5.disposeMediaElement(el);
50181        el = clone;
50182      } else {
50183        el = document__default["default"].createElement('video');
50184
50185        // determine if native controls should be used
50186        const tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
50187        const attributes = merge$1({}, tagAttributes);
50188        if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
50189          delete attributes.controls;
50190        }
50191        setAttributes(el, Object.assign(attributes, {
50192          id: this.options_.techId,
50193          class: 'vjs-tech'
50194        }));
50195      }
50196      el.playerId = this.options_.playerId;
50197    }
50198    if (typeof this.options_.preload !== 'undefined') {
50199      setAttribute(el, 'preload', this.options_.preload);
50200    }
50201    if (this.options_.disablePictureInPicture !== undefined) {
50202      el.disablePictureInPicture = this.options_.disablePictureInPicture;
50203    }
50204
50205    // Update specific tag settings, in case they were overridden
50206    // `autoplay` has to be *last* so that `muted` and `playsinline` are present
50207    // when iOS/Safari or other browsers attempt to autoplay.
50208    const settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
50209    for (let i = 0; i < settingsAttrs.length; i++) {
50210      const attr = settingsAttrs[i];
50211      const value = this.options_[attr];
50212      if (typeof value !== 'undefined') {
50213        if (value) {
50214          setAttribute(el, attr, attr);
50215        } else {
50216          removeAttribute(el, attr);
50217        }
50218        el[attr] = value;
50219      }
50220    }
50221    return el;
50222  }
50223
50224  /**
50225   * This will be triggered if the loadstart event has already fired, before videojs was
50226   * ready. Two known examples of when this can happen are:
50227   * 1. If we're loading the playback object after it has started loading
50228   * 2. The media is already playing the (often with autoplay on) then
50229   *
50230   * This function will fire another loadstart so that videojs can catchup.
50231   *
50232   * @fires Tech#loadstart
50233   *
50234   * @return {undefined}
50235   *         returns nothing.
50236   */
50237  handleLateInit_(el) {
50238    if (el.networkState === 0 || el.networkState === 3) {
50239      // The video element hasn't started loading the source yet
50240      // or didn't find a source
50241      return;
50242    }
50243    if (el.readyState === 0) {
50244      // NetworkState is set synchronously BUT loadstart is fired at the
50245      // end of the current stack, usually before setInterval(fn, 0).
50246      // So at this point we know loadstart may have already fired or is
50247      // about to fire, and either way the player hasn't seen it yet.
50248      // We don't want to fire loadstart prematurely here and cause a
50249      // double loadstart so we'll wait and see if it happens between now
50250      // and the next loop, and fire it if not.
50251      // HOWEVER, we also want to make sure it fires before loadedmetadata
50252      // which could also happen between now and the next loop, so we'll
50253      // watch for that also.
50254      let loadstartFired = false;
vendor: 5,142 bytes, lines 50255-50422
50255      const setLoadstartFired = function () {
50256        loadstartFired = true;
50257      };
50258      this.on('loadstart', setLoadstartFired);
50259      const triggerLoadstart = function () {
50260        // We did miss the original loadstart. Make sure the player
50261        // sees loadstart before loadedmetadata
50262        if (!loadstartFired) {
50263          this.trigger('loadstart');
50264        }
50265      };
50266      this.on('loadedmetadata', triggerLoadstart);
50267      this.ready(function () {
50268        this.off('loadstart', setLoadstartFired);
50269        this.off('loadedmetadata', triggerLoadstart);
50270        if (!loadstartFired) {
50271          // We did miss the original native loadstart. Fire it now.
50272          this.trigger('loadstart');
50273        }
50274      });
50275      return;
50276    }
50277
50278    // From here on we know that loadstart already fired and we missed it.
50279    // The other readyState events aren't as much of a problem if we double
50280    // them, so not going to go to as much trouble as loadstart to prevent
50281    // that unless we find reason to.
50282    const eventsToTrigger = ['loadstart'];
50283
50284    // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
50285    eventsToTrigger.push('loadedmetadata');
50286
50287    // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
50288    if (el.readyState >= 2) {
50289      eventsToTrigger.push('loadeddata');
50290    }
50291
50292    // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
50293    if (el.readyState >= 3) {
50294      eventsToTrigger.push('canplay');
50295    }
50296
50297    // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
50298    if (el.readyState >= 4) {
50299      eventsToTrigger.push('canplaythrough');
50300    }
50301
50302    // We still need to give the player time to add event listeners
50303    this.ready(function () {
50304      eventsToTrigger.forEach(function (type) {
50305        this.trigger(type);
50306      }, this);
50307    });
50308  }
50309
50310  /**
50311   * Set whether we are scrubbing or not.
50312   * This is used to decide whether we should use `fastSeek` or not.
50313   * `fastSeek` is used to provide trick play on Safari browsers.
50314   *
50315   * @param {boolean} isScrubbing
50316   *                  - true for we are currently scrubbing
50317   *                  - false for we are no longer scrubbing
50318   */
50319  setScrubbing(isScrubbing) {
50320    this.isScrubbing_ = isScrubbing;
50321  }
50322
50323  /**
50324   * Get whether we are scrubbing or not.
50325   *
50326   * @return {boolean} isScrubbing
50327   *                  - true for we are currently scrubbing
50328   *                  - false for we are no longer scrubbing
50329   */
50330  scrubbing() {
50331    return this.isScrubbing_;
50332  }
50333
50334  /**
50335   * Set current time for the `HTML5` tech.
50336   *
50337   * @param {number} seconds
50338   *        Set the current time of the media to this.
50339   */
50340  setCurrentTime(seconds) {
50341    try {
50342      if (this.isScrubbing_ && this.el_.fastSeek && IS_ANY_SAFARI) {
50343        this.el_.fastSeek(seconds);
50344      } else {
50345        this.el_.currentTime = seconds;
50346      }
50347    } catch (e) {
50348      log$1(e, 'Video is not ready. (Video.js)');
50349      // this.warning(VideoJS.warnings.videoNotReady);
50350    }
50351  }
50352
50353  /**
50354   * Get the current duration of the HTML5 media element.
50355   *
50356   * @return {number}
50357   *         The duration of the media or 0 if there is no duration.
50358   */
50359  duration() {
50360    // Android Chrome will report duration as Infinity for VOD HLS until after
50361    // playback has started, which triggers the live display erroneously.
50362    // Return NaN if playback has not started and trigger a durationupdate once
50363    // the duration can be reliably known.
50364    if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
50365      // Wait for the first `timeupdate` with currentTime > 0 - there may be
50366      // several with 0
50367      const checkProgress = () => {
50368        if (this.el_.currentTime > 0) {
50369          // Trigger durationchange for genuinely live video
50370          if (this.el_.duration === Infinity) {
50371            this.trigger('durationchange');
50372          }
50373          this.off('timeupdate', checkProgress);
50374        }
50375      };
50376      this.on('timeupdate', checkProgress);
50377      return NaN;
50378    }
50379    return this.el_.duration || NaN;
50380  }
50381
50382  /**
50383   * Get the current width of the HTML5 media element.
50384   *
50385   * @return {number}
50386   *         The width of the HTML5 media element.
50387   */
50388  width() {
50389    return this.el_.offsetWidth;
50390  }
50391
50392  /**
50393   * Get the current height of the HTML5 media element.
50394   *
50395   * @return {number}
50396   *         The height of the HTML5 media element.
50397   */
50398  height() {
50399    return this.el_.offsetHeight;
50400  }
50401
50402  /**
50403   * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
50404   * `fullscreenchange` event.
50405   *
50406   * @private
50407   * @fires fullscreenchange
50408   * @listens webkitendfullscreen
50409   * @listens webkitbeginfullscreen
50410   * @listens webkitbeginfullscreen
50411   */
50412  proxyWebkitFullscreen_() {
50413    if (!('webkitDisplayingFullscreen' in this.el_)) {
50414      return;
50415    }
50416    const endFn = function () {
50417      this.trigger('fullscreenchange', {
50418        isFullscreen: false
50419      });
50420      // Safari will sometimes set controls on the videoelement when existing fullscreen.
50421      if (this.el_.controls && !this.options_.nativeControlsForTouch && this.controls()) {
50422        this.el_.controls = false;
vendor: 4,232 bytes, lines 50423-50565
50423      }
50424    };
50425    const beginFn = function () {
50426      if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
50427        this.one('webkitendfullscreen', endFn);
50428        this.trigger('fullscreenchange', {
50429          isFullscreen: true,
50430          // set a flag in case another tech triggers fullscreenchange
50431          nativeIOSFullscreen: true
50432        });
50433      }
50434    };
50435    this.on('webkitbeginfullscreen', beginFn);
50436    this.on('dispose', () => {
50437      this.off('webkitbeginfullscreen', beginFn);
50438      this.off('webkitendfullscreen', endFn);
50439    });
50440  }
50441
50442  /**
50443   * Check if fullscreen is supported on the video el.
50444   *
50445   * @return {boolean}
50446   *         - True if fullscreen is supported.
50447   *         - False if fullscreen is not supported.
50448   */
50449  supportsFullScreen() {
50450    return typeof this.el_.webkitEnterFullScreen === 'function';
50451  }
50452
50453  /**
50454   * Request that the `HTML5` Tech enter fullscreen.
50455   */
50456  enterFullScreen() {
50457    const video = this.el_;
50458    if (video.paused && video.networkState <= video.HAVE_METADATA) {
50459      // attempt to prime the video element for programmatic access
50460      // this isn't necessary on the desktop but shouldn't hurt
50461      silencePromise(this.el_.play());
50462
50463      // playing and pausing synchronously during the transition to fullscreen
50464      // can get iOS ~6.1 devices into a play/pause loop
50465      this.setTimeout(function () {
50466        video.pause();
50467        try {
50468          video.webkitEnterFullScreen();
50469        } catch (e) {
50470          this.trigger('fullscreenerror', e);
50471        }
50472      }, 0);
50473    } else {
50474      try {
50475        video.webkitEnterFullScreen();
50476      } catch (e) {
50477        this.trigger('fullscreenerror', e);
50478      }
50479    }
50480  }
50481
50482  /**
50483   * Request that the `HTML5` Tech exit fullscreen.
50484   */
50485  exitFullScreen() {
50486    if (!this.el_.webkitDisplayingFullscreen) {
50487      this.trigger('fullscreenerror', new Error('The video is not fullscreen'));
50488      return;
50489    }
50490    this.el_.webkitExitFullScreen();
50491  }
50492
50493  /**
50494   * Create a floating video window always on top of other windows so that users may
50495   * continue consuming media while they interact with other content sites, or
50496   * applications on their device.
50497   *
50498   * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
50499   *
50500   * @return {Promise}
50501   *         A promise with a Picture-in-Picture window.
50502   */
50503  requestPictureInPicture() {
50504    return this.el_.requestPictureInPicture();
50505  }
50506
50507  /**
50508   * Native requestVideoFrameCallback if supported by browser/tech, or fallback
50509   * Don't use rVCF on Safari when DRM is playing, as it doesn't fire
50510   * Needs to be checked later than the constructor
50511   * This will be a false positive for clear sources loaded after a Fairplay source
50512   *
50513   * @param {function} cb function to call
50514   * @return {number} id of request
50515   */
50516  requestVideoFrameCallback(cb) {
50517    if (this.featuresVideoFrameCallback && !this.el_.webkitKeys) {
50518      return this.el_.requestVideoFrameCallback(cb);
50519    }
50520    return super.requestVideoFrameCallback(cb);
50521  }
50522
50523  /**
50524   * Native or fallback requestVideoFrameCallback
50525   *
50526   * @param {number} id request id to cancel
50527   */
50528  cancelVideoFrameCallback(id) {
50529    if (this.featuresVideoFrameCallback && !this.el_.webkitKeys) {
50530      this.el_.cancelVideoFrameCallback(id);
50531    } else {
50532      super.cancelVideoFrameCallback(id);
50533    }
50534  }
50535
50536  /**
50537   * A getter/setter for the `Html5` Tech's source object.
50538   * > Note: Please use {@link Html5#setSource}
50539   *
50540   * @param {Tech~SourceObject} [src]
50541   *        The source object you want to set on the `HTML5` techs element.
50542   *
50543   * @return {Tech~SourceObject|undefined}
50544   *         - The current source object when a source is not passed in.
50545   *         - undefined when setting
50546   *
50547   * @deprecated Since version 5.
50548   */
50549  src(src) {
50550    if (src === undefined) {
50551      return this.el_.src;
50552    }
50553
50554    // Setting src through `src` instead of `setSrc` will be deprecated
50555    this.setSrc(src);
50556  }
50557
50558  /**
50559   * Add a <source> element to the <video> element.
50560   *
50561   * @param {string} srcUrl
50562   *        The URL of the video source.
50563   *
50564   * @param {string} [mimeType]
50565   *        The MIME type of the video source. Optional but recommended.
vendor: 6,639 bytes, lines 50566-50785
50566   *
50567   * @return {boolean}
50568   *         Returns true if the source element was successfully added, false otherwise.
50569   */
50570  addSourceElement(srcUrl, mimeType) {
50571    if (!srcUrl) {
50572      log$1.error('Invalid source URL.');
50573      return false;
50574    }
50575    const sourceAttributes = {
50576      src: srcUrl
50577    };
50578    if (mimeType) {
50579      sourceAttributes.type = mimeType;
50580    }
50581    const sourceElement = createEl('source', {}, sourceAttributes);
50582    this.el_.appendChild(sourceElement);
50583    return true;
50584  }
50585
50586  /**
50587   * Remove a <source> element from the <video> element by its URL.
50588   *
50589   * @param {string} srcUrl
50590   *        The URL of the source to remove.
50591   *
50592   * @return {boolean}
50593   *         Returns true if the source element was successfully removed, false otherwise.
50594   */
50595  removeSourceElement(srcUrl) {
50596    if (!srcUrl) {
50597      log$1.error('Source URL is required to remove the source element.');
50598      return false;
50599    }
50600    const sourceElements = this.el_.querySelectorAll('source');
50601    for (const sourceElement of sourceElements) {
50602      if (sourceElement.src === srcUrl) {
50603        this.el_.removeChild(sourceElement);
50604        return true;
50605      }
50606    }
50607    log$1.warn(`No matching source element found with src: ${srcUrl}`);
50608    return false;
50609  }
50610
50611  /**
50612   * Reset the tech by removing all sources and then calling
50613   * {@link Html5.resetMediaElement}.
50614   */
50615  reset() {
50616    Html5.resetMediaElement(this.el_);
50617  }
50618
50619  /**
50620   * Get the current source on the `HTML5` Tech. Falls back to returning the source from
50621   * the HTML5 media element.
50622   *
50623   * @return {Tech~SourceObject}
50624   *         The current source object from the HTML5 tech. With a fallback to the
50625   *         elements source.
50626   */
50627  currentSrc() {
50628    if (this.currentSource_) {
50629      return this.currentSource_.src;
50630    }
50631    return this.el_.currentSrc;
50632  }
50633
50634  /**
50635   * Set controls attribute for the HTML5 media Element.
50636   *
50637   * @param {string} val
50638   *        Value to set the controls attribute to
50639   */
50640  setControls(val) {
50641    this.el_.controls = !!val;
50642  }
50643
50644  /**
50645   * Create and returns a remote {@link TextTrack} object.
50646   *
50647   * @param {string} kind
50648   *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
50649   *
50650   * @param {string} [label]
50651   *        Label to identify the text track
50652   *
50653   * @param {string} [language]
50654   *        Two letter language abbreviation
50655   *
50656   * @return {TextTrack}
50657   *         The TextTrack that gets created.
50658   */
50659  addTextTrack(kind, label, language) {
50660    if (!this.featuresNativeTextTracks) {
50661      return super.addTextTrack(kind, label, language);
50662    }
50663    return this.el_.addTextTrack(kind, label, language);
50664  }
50665
50666  /**
50667   * Creates either native TextTrack or an emulated TextTrack depending
50668   * on the value of `featuresNativeTextTracks`
50669   *
50670   * @param {Object} options
50671   *        The object should contain the options to initialize the TextTrack with.
50672   *
50673   * @param {string} [options.kind]
50674   *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
50675   *
50676   * @param {string} [options.label]
50677   *        Label to identify the text track
50678   *
50679   * @param {string} [options.language]
50680   *        Two letter language abbreviation.
50681   *
50682   * @param {boolean} [options.default]
50683   *        Default this track to on.
50684   *
50685   * @param {string} [options.id]
50686   *        The internal id to assign this track.
50687   *
50688   * @param {string} [options.src]
50689   *        A source url for the track.
50690   *
50691   * @return {HTMLTrackElement}
50692   *         The track element that gets created.
50693   */
50694  createRemoteTextTrack(options) {
50695    if (!this.featuresNativeTextTracks) {
50696      return super.createRemoteTextTrack(options);
50697    }
50698    const htmlTrackElement = document__default["default"].createElement('track');
50699    if (options.kind) {
50700      htmlTrackElement.kind = options.kind;
50701    }
50702    if (options.label) {
50703      htmlTrackElement.label = options.label;
50704    }
50705    if (options.language || options.srclang) {
50706      htmlTrackElement.srclang = options.language || options.srclang;
50707    }
50708    if (options.default) {
50709      htmlTrackElement.default = options.default;
50710    }
50711    if (options.id) {
50712      htmlTrackElement.id = options.id;
50713    }
50714    if (options.src) {
50715      htmlTrackElement.src = options.src;
50716    }
50717    return htmlTrackElement;
50718  }
50719
50720  /**
50721   * Creates a remote text track object and returns an html track element.
50722   *
50723   * @param {Object} options The object should contain values for
50724   * kind, language, label, and src (location of the WebVTT file)
50725   * @param {boolean} [manualCleanup=false] if set to true, the TextTrack
50726   * will not be removed from the TextTrackList and HtmlTrackElementList
50727   * after a source change
50728   * @return {HTMLTrackElement} An Html Track Element.
50729   * This can be an emulated {@link HTMLTrackElement} or a native one.
50730   *
50731   */
50732  addRemoteTextTrack(options, manualCleanup) {
50733    const htmlTrackElement = super.addRemoteTextTrack(options, manualCleanup);
50734    if (this.featuresNativeTextTracks) {
50735      this.el().appendChild(htmlTrackElement);
50736    }
50737    return htmlTrackElement;
50738  }
50739
50740  /**
50741   * Remove remote `TextTrack` from `TextTrackList` object
50742   *
50743   * @param {TextTrack} track
50744   *        `TextTrack` object to remove
50745   */
50746  removeRemoteTextTrack(track) {
50747    super.removeRemoteTextTrack(track);
50748    if (this.featuresNativeTextTracks) {
50749      const tracks = this.$$('track');
50750      let i = tracks.length;
50751      while (i--) {
50752        if (track === tracks[i] || track === tracks[i].track) {
50753          this.el().removeChild(tracks[i]);
50754        }
50755      }
50756    }
50757  }
50758
50759  /**
50760   * Gets available media playback quality metrics as specified by the W3C's Media
50761   * Playback Quality API.
50762   *
50763   * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
50764   *
50765   * @return {Object}
50766   *         An object with supported media playback quality metrics
50767   */
50768  getVideoPlaybackQuality() {
50769    if (typeof this.el().getVideoPlaybackQuality === 'function') {
50770      return this.el().getVideoPlaybackQuality();
50771    }
50772    const videoPlaybackQuality = {};
50773    if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
50774      videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
50775      videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
50776    }
50777    if (window__default["default"].performance) {
50778      videoPlaybackQuality.creationTime = window__default["default"].performance.now();
50779    }
50780    return videoPlaybackQuality;
50781  }
50782}
50783
50784/* HTML5 Support Testing ---------------------------------------------------- */
50785
vendor: 9,414 bytes, lines 50786-51104
50786/**
50787 * Element for testing browser HTML5 media capabilities
50788 *
50789 * @type {Element}
50790 * @constant
50791 * @private
50792 */
50793defineLazyProperty(Html5, 'TEST_VID', function () {
50794  if (!isReal()) {
50795    return;
50796  }
50797  const video = document__default["default"].createElement('video');
50798  const track = document__default["default"].createElement('track');
50799  track.kind = 'captions';
50800  track.srclang = 'en';
50801  track.label = 'English';
50802  video.appendChild(track);
50803  return video;
50804});
50805
50806/**
50807 * Check if HTML5 media is supported by this browser/device.
50808 *
50809 * @return {boolean}
50810 *         - True if HTML5 media is supported.
50811 *         - False if HTML5 media is not supported.
50812 */
50813Html5.isSupported = function () {
50814  // IE with no Media Player is a LIAR! (#984)
50815  try {
50816    Html5.TEST_VID.volume = 0.5;
50817  } catch (e) {
50818    return false;
50819  }
50820  return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
50821};
50822
50823/**
50824 * Check if the tech can support the given type
50825 *
50826 * @param {string} type
50827 *        The mimetype to check
50828 * @return {string} 'probably', 'maybe', or '' (empty string)
50829 */
50830Html5.canPlayType = function (type) {
50831  return Html5.TEST_VID.canPlayType(type);
50832};
50833
50834/**
50835 * Check if the tech can support the given source
50836 *
50837 * @param {Object} srcObj
50838 *        The source object
50839 * @param {Object} options
50840 *        The options passed to the tech
50841 * @return {string} 'probably', 'maybe', or '' (empty string)
50842 */
50843Html5.canPlaySource = function (srcObj, options) {
50844  return Html5.canPlayType(srcObj.type);
50845};
50846
50847/**
50848 * Check if the volume can be changed in this browser/device.
50849 * Volume cannot be changed in a lot of mobile devices.
50850 * Specifically, it can't be changed from 1 on iOS.
50851 *
50852 * @return {boolean}
50853 *         - True if volume can be controlled
50854 *         - False otherwise
50855 */
50856Html5.canControlVolume = function () {
50857  // IE will error if Windows Media Player not installed #3315
50858  try {
50859    const volume = Html5.TEST_VID.volume;
50860    Html5.TEST_VID.volume = volume / 2 + 0.1;
50861    const canControl = volume !== Html5.TEST_VID.volume;
50862
50863    // With the introduction of iOS 15, there are cases where the volume is read as
50864    // changed but reverts back to its original state at the start of the next tick.
50865    // To determine whether volume can be controlled on iOS,
50866    // a timeout is set and the volume is checked asynchronously.
50867    // Since `features` doesn't currently work asynchronously, the value is manually set.
50868    if (canControl && IS_IOS) {
50869      window__default["default"].setTimeout(() => {
50870        if (Html5 && Html5.prototype) {
50871          Html5.prototype.featuresVolumeControl = volume !== Html5.TEST_VID.volume;
50872        }
50873      });
50874
50875      // default iOS to false, which will be updated in the timeout above.
50876      return false;
50877    }
50878    return canControl;
50879  } catch (e) {
50880    return false;
50881  }
50882};
50883
50884/**
50885 * Check if the volume can be muted in this browser/device.
50886 * Some devices, e.g. iOS, don't allow changing volume
50887 * but permits muting/unmuting.
50888 *
50889 * @return {boolean}
50890 *      - True if volume can be muted
50891 *      - False otherwise
50892 */
50893Html5.canMuteVolume = function () {
50894  try {
50895    const muted = Html5.TEST_VID.muted;
50896
50897    // in some versions of iOS muted property doesn't always
50898    // work, so we want to set both property and attribute
50899    Html5.TEST_VID.muted = !muted;
50900    if (Html5.TEST_VID.muted) {
50901      setAttribute(Html5.TEST_VID, 'muted', 'muted');
50902    } else {
50903      removeAttribute(Html5.TEST_VID, 'muted', 'muted');
50904    }
50905    return muted !== Html5.TEST_VID.muted;
50906  } catch (e) {
50907    return false;
50908  }
50909};
50910
50911/**
50912 * Check if the playback rate can be changed in this browser/device.
50913 *
50914 * @return {boolean}
50915 *         - True if playback rate can be controlled
50916 *         - False otherwise
50917 */
50918Html5.canControlPlaybackRate = function () {
50919  // Playback rate API is implemented in Android Chrome, but doesn't do anything
50920  // https://github.com/videojs/video.js/issues/3180
50921  if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
50922    return false;
50923  }
50924  // IE will error if Windows Media Player not installed #3315
50925  try {
50926    const playbackRate = Html5.TEST_VID.playbackRate;
50927    Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
50928    return playbackRate !== Html5.TEST_VID.playbackRate;
50929  } catch (e) {
50930    return false;
50931  }
50932};
50933
50934/**
50935 * Check if we can override a video/audio elements attributes, with
50936 * Object.defineProperty.
50937 *
50938 * @return {boolean}
50939 *         - True if builtin attributes can be overridden
50940 *         - False otherwise
50941 */
50942Html5.canOverrideAttributes = function () {
50943  // if we cannot overwrite the src/innerHTML property, there is no support
50944  // iOS 7 safari for instance cannot do this.
50945  try {
50946    const noop = () => {};
50947    Object.defineProperty(document__default["default"].createElement('video'), 'src', {
50948      get: noop,
50949      set: noop
50950    });
50951    Object.defineProperty(document__default["default"].createElement('audio'), 'src', {
50952      get: noop,
50953      set: noop
50954    });
50955    Object.defineProperty(document__default["default"].createElement('video'), 'innerHTML', {
50956      get: noop,
50957      set: noop
50958    });
50959    Object.defineProperty(document__default["default"].createElement('audio'), 'innerHTML', {
50960      get: noop,
50961      set: noop
50962    });
50963  } catch (e) {
50964    return false;
50965  }
50966  return true;
50967};
50968
50969/**
50970 * Check to see if native `TextTrack`s are supported by this browser/device.
50971 *
50972 * @return {boolean}
50973 *         - True if native `TextTrack`s are supported.
50974 *         - False otherwise
50975 */
50976Html5.supportsNativeTextTracks = function () {
50977  return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
50978};
50979
50980/**
50981 * Check to see if native `VideoTrack`s are supported by this browser/device
50982 *
50983 * @return {boolean}
50984 *        - True if native `VideoTrack`s are supported.
50985 *        - False otherwise
50986 */
50987Html5.supportsNativeVideoTracks = function () {
50988  return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
50989};
50990
50991/**
50992 * Check to see if native `AudioTrack`s are supported by this browser/device
50993 *
50994 * @return {boolean}
50995 *        - True if native `AudioTrack`s are supported.
50996 *        - False otherwise
50997 */
50998Html5.supportsNativeAudioTracks = function () {
50999  return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
51000};
51001
51002/**
51003 * An array of events available on the Html5 tech.
51004 *
51005 * @private
51006 * @type {Array}
51007 */
51008Html5.Events = ['loadstart', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'seeking', 'seeked', 'ended', 'durationchange', 'timeupdate', 'progress', 'play', 'pause', 'ratechange', 'resize', 'volumechange'];
51009
51010/**
51011 * Boolean indicating whether the `Tech` supports volume control.
51012 *
51013 * @type {boolean}
51014 * @default {@link Html5.canControlVolume}
51015 */
51016/**
51017 * Boolean indicating whether the `Tech` supports muting volume.
51018 *
51019 * @type {boolean}
51020 * @default {@link Html5.canMuteVolume}
51021 */
51022
51023/**
51024 * Boolean indicating whether the `Tech` supports changing the speed at which the media
51025 * plays. Examples:
51026 *   - Set player to play 2x (twice) as fast
51027 *   - Set player to play 0.5x (half) as fast
51028 *
51029 * @type {boolean}
51030 * @default {@link Html5.canControlPlaybackRate}
51031 */
51032
51033/**
51034 * Boolean indicating whether the `Tech` supports the `sourceset` event.
51035 *
51036 * @type {boolean}
51037 * @default
51038 */
51039/**
51040 * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
51041 *
51042 * @type {boolean}
51043 * @default {@link Html5.supportsNativeTextTracks}
51044 */
51045/**
51046 * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
51047 *
51048 * @type {boolean}
51049 * @default {@link Html5.supportsNativeVideoTracks}
51050 */
51051/**
51052 * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
51053 *
51054 * @type {boolean}
51055 * @default {@link Html5.supportsNativeAudioTracks}
51056 */
51057[['featuresMuteControl', 'canMuteVolume'], ['featuresPlaybackRate', 'canControlPlaybackRate'], ['featuresSourceset', 'canOverrideAttributes'], ['featuresNativeTextTracks', 'supportsNativeTextTracks'], ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'], ['featuresNativeAudioTracks', 'supportsNativeAudioTracks']].forEach(function ([key, fn]) {
51058  defineLazyProperty(Html5.prototype, key, () => Html5[fn](), true);
51059});
51060Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
51061
51062/**
51063 * Boolean indicating whether the `HTML5` tech currently supports the media element
51064 * moving in the DOM. iOS breaks if you move the media element, so this is set this to
51065 * false there. Everywhere else this should be true.
51066 *
51067 * @type {boolean}
51068 * @default
51069 */
51070Html5.prototype.movingMediaElementInDOM = !IS_IOS;
51071
51072// TODO: Previous comment: No longer appears to be used. Can probably be removed.
51073//       Is this true?
51074/**
51075 * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
51076 * when going into fullscreen.
51077 *
51078 * @type {boolean}
51079 * @default
51080 */
51081Html5.prototype.featuresFullscreenResize = true;
51082
51083/**
51084 * Boolean indicating whether the `HTML5` tech currently supports the progress event.
51085 * If this is false, manual `progress` events will be triggered instead.
51086 *
51087 * @type {boolean}
51088 * @default
51089 */
51090Html5.prototype.featuresProgressEvents = true;
51091
51092/**
51093 * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
51094 * If this is false, manual `timeupdate` events will be triggered instead.
51095 *
51096 * @default
51097 */
51098Html5.prototype.featuresTimeupdateEvents = true;
51099
51100/**
51101 * Whether the HTML5 el supports `requestVideoFrameCallback`
51102 *
51103 * @type {boolean}
51104 */
vendor: 23,656 bytes, lines 51105-51748
51105Html5.prototype.featuresVideoFrameCallback = !!(Html5.TEST_VID && Html5.TEST_VID.requestVideoFrameCallback);
51106Html5.disposeMediaElement = function (el) {
51107  if (!el) {
51108    return;
51109  }
51110  if (el.parentNode) {
51111    el.parentNode.removeChild(el);
51112  }
51113
51114  // remove any child track or source nodes to prevent their loading
51115  while (el.hasChildNodes()) {
51116    el.removeChild(el.firstChild);
51117  }
51118
51119  // remove any src reference. not setting `src=''` because that causes a warning
51120  // in firefox
51121  el.removeAttribute('src');
51122
51123  // force the media element to update its loading state by calling load()
51124  // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
51125  if (typeof el.load === 'function') {
51126    // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
51127    (function () {
51128      try {
51129        el.load();
51130      } catch (e) {
51131        // not supported
51132      }
51133    })();
51134  }
51135};
51136Html5.resetMediaElement = function (el) {
51137  if (!el) {
51138    return;
51139  }
51140  const sources = el.querySelectorAll('source');
51141  let i = sources.length;
51142  while (i--) {
51143    el.removeChild(sources[i]);
51144  }
51145
51146  // remove any src reference.
51147  // not setting `src=''` because that throws an error
51148  el.removeAttribute('src');
51149  if (typeof el.load === 'function') {
51150    // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
51151    (function () {
51152      try {
51153        el.load();
51154      } catch (e) {
51155        // satisfy linter
51156      }
51157    })();
51158  }
51159};
51160
51161/* Native HTML5 element property wrapping ----------------------------------- */
51162// Wrap native boolean attributes with getters that check both property and attribute
51163// The list is as followed:
51164// muted, defaultMuted, autoplay, controls, loop, playsinline
51165[
51166/**
51167 * Get the value of `muted` from the media element. `muted` indicates
51168 * that the volume for the media should be set to silent. This does not actually change
51169 * the `volume` attribute.
51170 *
51171 * @method Html5#muted
51172 * @return {boolean}
51173 *         - True if the value of `volume` should be ignored and the audio set to silent.
51174 *         - False if the value of `volume` should be used.
51175 *
51176 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
51177 */
51178'muted',
51179/**
51180 * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
51181 * whether the media should start muted or not. Only changes the default state of the
51182 * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
51183 * current state.
51184 *
51185 * @method Html5#defaultMuted
51186 * @return {boolean}
51187 *         - The value of `defaultMuted` from the media element.
51188 *         - True indicates that the media should start muted.
51189 *         - False indicates that the media should not start muted
51190 *
51191 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
51192 */
51193'defaultMuted',
51194/**
51195 * Get the value of `autoplay` from the media element. `autoplay` indicates
51196 * that the media should start to play as soon as the page is ready.
51197 *
51198 * @method Html5#autoplay
51199 * @return {boolean}
51200 *         - The value of `autoplay` from the media element.
51201 *         - True indicates that the media should start as soon as the page loads.
51202 *         - False indicates that the media should not start as soon as the page loads.
51203 *
51204 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
51205 */
51206'autoplay',
51207/**
51208 * Get the value of `controls` from the media element. `controls` indicates
51209 * whether the native media controls should be shown or hidden.
51210 *
51211 * @method Html5#controls
51212 * @return {boolean}
51213 *         - The value of `controls` from the media element.
51214 *         - True indicates that native controls should be showing.
51215 *         - False indicates that native controls should be hidden.
51216 *
51217 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
51218 */
51219'controls',
51220/**
51221 * Get the value of `loop` from the media element. `loop` indicates
51222 * that the media should return to the start of the media and continue playing once
51223 * it reaches the end.
51224 *
51225 * @method Html5#loop
51226 * @return {boolean}
51227 *         - The value of `loop` from the media element.
51228 *         - True indicates that playback should seek back to start once
51229 *           the end of a media is reached.
51230 *         - False indicates that playback should not loop back to the start when the
51231 *           end of the media is reached.
51232 *
51233 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
51234 */
51235'loop',
51236/**
51237 * Get the value of `playsinline` from the media element. `playsinline` indicates
51238 * to the browser that non-fullscreen playback is preferred when fullscreen
51239 * playback is the native default, such as in iOS Safari.
51240 *
51241 * @method Html5#playsinline
51242 * @return {boolean}
51243 *         - The value of `playsinline` from the media element.
51244 *         - True indicates that the media should play inline.
51245 *         - False indicates that the media should not play inline.
51246 *
51247 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
51248 */
51249'playsinline'].forEach(function (prop) {
51250  Html5.prototype[prop] = function () {
51251    return this.el_[prop] || this.el_.hasAttribute(prop);
51252  };
51253});
51254
51255// Wrap native boolean attributes with setters that set both property and attribute
51256// The list is as followed:
51257// setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
51258// setControls is special-cased above
51259[
51260/**
51261 * Set the value of `muted` on the media element. `muted` indicates that the current
51262 * audio level should be silent.
51263 *
51264 * @method Html5#setMuted
51265 * @param {boolean} muted
51266 *        - True if the audio should be set to silent
51267 *        - False otherwise
51268 *
51269 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
51270 */
51271'muted',
51272/**
51273 * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
51274 * audio level should be silent, but will only effect the muted level on initial playback..
51275 *
51276 * @method Html5.prototype.setDefaultMuted
51277 * @param {boolean} defaultMuted
51278 *        - True if the audio should be set to silent
51279 *        - False otherwise
51280 *
51281 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
51282 */
51283'defaultMuted',
51284/**
51285 * Set the value of `autoplay` on the media element. `autoplay` indicates
51286 * that the media should start to play as soon as the page is ready.
51287 *
51288 * @method Html5#setAutoplay
51289 * @param {boolean} autoplay
51290 *         - True indicates that the media should start as soon as the page loads.
51291 *         - False indicates that the media should not start as soon as the page loads.
51292 *
51293 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
51294 */
51295'autoplay',
51296/**
51297 * Set the value of `loop` on the media element. `loop` indicates
51298 * that the media should return to the start of the media and continue playing once
51299 * it reaches the end.
51300 *
51301 * @method Html5#setLoop
51302 * @param {boolean} loop
51303 *         - True indicates that playback should seek back to start once
51304 *           the end of a media is reached.
51305 *         - False indicates that playback should not loop back to the start when the
51306 *           end of the media is reached.
51307 *
51308 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
51309 */
51310'loop',
51311/**
51312 * Set the value of `playsinline` from the media element. `playsinline` indicates
51313 * to the browser that non-fullscreen playback is preferred when fullscreen
51314 * playback is the native default, such as in iOS Safari.
51315 *
51316 * @method Html5#setPlaysinline
51317 * @param {boolean} playsinline
51318 *         - True indicates that the media should play inline.
51319 *         - False indicates that the media should not play inline.
51320 *
51321 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
51322 */
51323'playsinline'].forEach(function (prop) {
51324  Html5.prototype['set' + toTitleCase$1(prop)] = function (v) {
51325    this.el_[prop] = v;
51326    if (v) {
51327      this.el_.setAttribute(prop, prop);
51328    } else {
51329      this.el_.removeAttribute(prop);
51330    }
51331  };
51332});
51333
51334// Wrap native properties with a getter
51335// The list is as followed
51336// paused, currentTime, buffered, volume, poster, preload, error, seeking
51337// seekable, ended, playbackRate, defaultPlaybackRate, disablePictureInPicture
51338// played, networkState, readyState, videoWidth, videoHeight, crossOrigin
51339[
51340/**
51341 * Get the value of `paused` from the media element. `paused` indicates whether the media element
51342 * is currently paused or not.
51343 *
51344 * @method Html5#paused
51345 * @return {boolean}
51346 *         The value of `paused` from the media element.
51347 *
51348 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
51349 */
51350'paused',
51351/**
51352 * Get the value of `currentTime` from the media element. `currentTime` indicates
51353 * the current second that the media is at in playback.
51354 *
51355 * @method Html5#currentTime
51356 * @return {number}
51357 *         The value of `currentTime` from the media element.
51358 *
51359 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
51360 */
51361'currentTime',
51362/**
51363 * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
51364 * object that represents the parts of the media that are already downloaded and
51365 * available for playback.
51366 *
51367 * @method Html5#buffered
51368 * @return {TimeRange}
51369 *         The value of `buffered` from the media element.
51370 *
51371 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
51372 */
51373'buffered',
51374/**
51375 * Get the value of `volume` from the media element. `volume` indicates
51376 * the current playback volume of audio for a media. `volume` will be a value from 0
51377 * (silent) to 1 (loudest and default).
51378 *
51379 * @method Html5#volume
51380 * @return {number}
51381 *         The value of `volume` from the media element. Value will be between 0-1.
51382 *
51383 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
51384 */
51385'volume',
51386/**
51387 * Get the value of `poster` from the media element. `poster` indicates
51388 * that the url of an image file that can/will be shown when no media data is available.
51389 *
51390 * @method Html5#poster
51391 * @return {string}
51392 *         The value of `poster` from the media element. Value will be a url to an
51393 *         image.
51394 *
51395 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
51396 */
51397'poster',
51398/**
51399 * Get the value of `preload` from the media element. `preload` indicates
51400 * what should download before the media is interacted with. It can have the following
51401 * values:
51402 * - none: nothing should be downloaded
51403 * - metadata: poster and the first few frames of the media may be downloaded to get
51404 *   media dimensions and other metadata
51405 * - auto: allow the media and metadata for the media to be downloaded before
51406 *    interaction
51407 *
51408 * @method Html5#preload
51409 * @return {string}
51410 *         The value of `preload` from the media element. Will be 'none', 'metadata',
51411 *         or 'auto'.
51412 *
51413 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
51414 */
51415'preload',
51416/**
51417 * Get the value of the `error` from the media element. `error` indicates any
51418 * MediaError that may have occurred during playback. If error returns null there is no
51419 * current error.
51420 *
51421 * @method Html5#error
51422 * @return {MediaError|null}
51423 *         The value of `error` from the media element. Will be `MediaError` if there
51424 *         is a current error and null otherwise.
51425 *
51426 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
51427 */
51428'error',
51429/**
51430 * Get the value of `seeking` from the media element. `seeking` indicates whether the
51431 * media is currently seeking to a new position or not.
51432 *
51433 * @method Html5#seeking
51434 * @return {boolean}
51435 *         - The value of `seeking` from the media element.
51436 *         - True indicates that the media is currently seeking to a new position.
51437 *         - False indicates that the media is not seeking to a new position at this time.
51438 *
51439 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
51440 */
51441'seeking',
51442/**
51443 * Get the value of `seekable` from the media element. `seekable` returns a
51444 * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
51445 *
51446 * @method Html5#seekable
51447 * @return {TimeRange}
51448 *         The value of `seekable` from the media element. A `TimeRange` object
51449 *         indicating the current ranges of time that can be seeked to.
51450 *
51451 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
51452 */
51453'seekable',
51454/**
51455 * Get the value of `ended` from the media element. `ended` indicates whether
51456 * the media has reached the end or not.
51457 *
51458 * @method Html5#ended
51459 * @return {boolean}
51460 *         - The value of `ended` from the media element.
51461 *         - True indicates that the media has ended.
51462 *         - False indicates that the media has not ended.
51463 *
51464 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
51465 */
51466'ended',
51467/**
51468 * Get the value of `playbackRate` from the media element. `playbackRate` indicates
51469 * the rate at which the media is currently playing back. Examples:
51470 *   - if playbackRate is set to 2, media will play twice as fast.
51471 *   - if playbackRate is set to 0.5, media will play half as fast.
51472 *
51473 * @method Html5#playbackRate
51474 * @return {number}
51475 *         The value of `playbackRate` from the media element. A number indicating
51476 *         the current playback speed of the media, where 1 is normal speed.
51477 *
51478 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
51479 */
51480'playbackRate',
51481/**
51482 * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
51483 * the rate at which the media is currently playing back. This value will not indicate the current
51484 * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
51485 *
51486 * Examples:
51487 *   - if defaultPlaybackRate is set to 2, media will play twice as fast.
51488 *   - if defaultPlaybackRate is set to 0.5, media will play half as fast.
51489 *
51490 * @method Html5.prototype.defaultPlaybackRate
51491 * @return {number}
51492 *         The value of `defaultPlaybackRate` from the media element. A number indicating
51493 *         the current playback speed of the media, where 1 is normal speed.
51494 *
51495 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
51496 */
51497'defaultPlaybackRate',
51498/**
51499 * Get the value of 'disablePictureInPicture' from the video element.
51500 *
51501 * @method Html5#disablePictureInPicture
51502 * @return {boolean} value
51503 *         - The value of `disablePictureInPicture` from the video element.
51504 *         - True indicates that the video can't be played in Picture-In-Picture mode
51505 *         - False indicates that the video can be played in Picture-In-Picture mode
51506 *
51507 * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
51508 */
51509'disablePictureInPicture',
51510/**
51511 * Get the value of `played` from the media element. `played` returns a `TimeRange`
51512 * object representing points in the media timeline that have been played.
51513 *
51514 * @method Html5#played
51515 * @return {TimeRange}
51516 *         The value of `played` from the media element. A `TimeRange` object indicating
51517 *         the ranges of time that have been played.
51518 *
51519 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
51520 */
51521'played',
51522/**
51523 * Get the value of `networkState` from the media element. `networkState` indicates
51524 * the current network state. It returns an enumeration from the following list:
51525 * - 0: NETWORK_EMPTY
51526 * - 1: NETWORK_IDLE
51527 * - 2: NETWORK_LOADING
51528 * - 3: NETWORK_NO_SOURCE
51529 *
51530 * @method Html5#networkState
51531 * @return {number}
51532 *         The value of `networkState` from the media element. This will be a number
51533 *         from the list in the description.
51534 *
51535 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
51536 */
51537'networkState',
51538/**
51539 * Get the value of `readyState` from the media element. `readyState` indicates
51540 * the current state of the media element. It returns an enumeration from the
51541 * following list:
51542 * - 0: HAVE_NOTHING
51543 * - 1: HAVE_METADATA
51544 * - 2: HAVE_CURRENT_DATA
51545 * - 3: HAVE_FUTURE_DATA
51546 * - 4: HAVE_ENOUGH_DATA
51547 *
51548 * @method Html5#readyState
51549 * @return {number}
51550 *         The value of `readyState` from the media element. This will be a number
51551 *         from the list in the description.
51552 *
51553 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
51554 */
51555'readyState',
51556/**
51557 * Get the value of `videoWidth` from the video element. `videoWidth` indicates
51558 * the current width of the video in css pixels.
51559 *
51560 * @method Html5#videoWidth
51561 * @return {number}
51562 *         The value of `videoWidth` from the video element. This will be a number
51563 *         in css pixels.
51564 *
51565 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
51566 */
51567'videoWidth',
51568/**
51569 * Get the value of `videoHeight` from the video element. `videoHeight` indicates
51570 * the current height of the video in css pixels.
51571 *
51572 * @method Html5#videoHeight
51573 * @return {number}
51574 *         The value of `videoHeight` from the video element. This will be a number
51575 *         in css pixels.
51576 *
51577 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
51578 */
51579'videoHeight',
51580/**
51581 * Get the value of `crossOrigin` from the media element. `crossOrigin` indicates
51582 * to the browser that should sent the cookies along with the requests for the
51583 * different assets/playlists
51584 *
51585 * @method Html5#crossOrigin
51586 * @return {string}
51587 *         - anonymous indicates that the media should not sent cookies.
51588 *         - use-credentials indicates that the media should sent cookies along the requests.
51589 *
51590 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
51591 */
51592'crossOrigin'].forEach(function (prop) {
51593  Html5.prototype[prop] = function () {
51594    return this.el_[prop];
51595  };
51596});
51597
51598// Wrap native properties with a setter in this format:
51599// set + toTitleCase(name)
51600// The list is as follows:
51601// setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate,
51602// setDisablePictureInPicture, setCrossOrigin
51603[
51604/**
51605 * Set the value of `volume` on the media element. `volume` indicates the current
51606 * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
51607 * so on.
51608 *
51609 * @method Html5#setVolume
51610 * @param {number} percentAsDecimal
51611 *        The volume percent as a decimal. Valid range is from 0-1.
51612 *
51613 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
51614 */
51615'volume',
51616/**
51617 * Set the value of `src` on the media element. `src` indicates the current
51618 * {@link Tech~SourceObject} for the media.
51619 *
51620 * @method Html5#setSrc
51621 * @param {Tech~SourceObject} src
51622 *        The source object to set as the current source.
51623 *
51624 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
51625 */
51626'src',
51627/**
51628 * Set the value of `poster` on the media element. `poster` is the url to
51629 * an image file that can/will be shown when no media data is available.
51630 *
51631 * @method Html5#setPoster
51632 * @param {string} poster
51633 *        The url to an image that should be used as the `poster` for the media
51634 *        element.
51635 *
51636 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
51637 */
51638'poster',
51639/**
51640 * Set the value of `preload` on the media element. `preload` indicates
51641 * what should download before the media is interacted with. It can have the following
51642 * values:
51643 * - none: nothing should be downloaded
51644 * - metadata: poster and the first few frames of the media may be downloaded to get
51645 *   media dimensions and other metadata
51646 * - auto: allow the media and metadata for the media to be downloaded before
51647 *    interaction
51648 *
51649 * @method Html5#setPreload
51650 * @param {string} preload
51651 *         The value of `preload` to set on the media element. Must be 'none', 'metadata',
51652 *         or 'auto'.
51653 *
51654 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
51655 */
51656'preload',
51657/**
51658 * Set the value of `playbackRate` on the media element. `playbackRate` indicates
51659 * the rate at which the media should play back. Examples:
51660 *   - if playbackRate is set to 2, media will play twice as fast.
51661 *   - if playbackRate is set to 0.5, media will play half as fast.
51662 *
51663 * @method Html5#setPlaybackRate
51664 * @return {number}
51665 *         The value of `playbackRate` from the media element. A number indicating
51666 *         the current playback speed of the media, where 1 is normal speed.
51667 *
51668 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
51669 */
51670'playbackRate',
51671/**
51672 * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
51673 * the rate at which the media should play back upon initial startup. Changing this value
51674 * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
51675 *
51676 * Example Values:
51677 *   - if playbackRate is set to 2, media will play twice as fast.
51678 *   - if playbackRate is set to 0.5, media will play half as fast.
51679 *
51680 * @method Html5.prototype.setDefaultPlaybackRate
51681 * @return {number}
51682 *         The value of `defaultPlaybackRate` from the media element. A number indicating
51683 *         the current playback speed of the media, where 1 is normal speed.
51684 *
51685 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
51686 */
51687'defaultPlaybackRate',
51688/**
51689 * Prevents the browser from suggesting a Picture-in-Picture context menu
51690 * or to request Picture-in-Picture automatically in some cases.
51691 *
51692 * @method Html5#setDisablePictureInPicture
51693 * @param {boolean} value
51694 *         The true value will disable Picture-in-Picture mode.
51695 *
51696 * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
51697 */
51698'disablePictureInPicture',
51699/**
51700 * Set the value of `crossOrigin` from the media element. `crossOrigin` indicates
51701 * to the browser that should sent the cookies along with the requests for the
51702 * different assets/playlists
51703 *
51704 * @method Html5#setCrossOrigin
51705 * @param {string} crossOrigin
51706 *         - anonymous indicates that the media should not sent cookies.
51707 *         - use-credentials indicates that the media should sent cookies along the requests.
51708 *
51709 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
51710 */
51711'crossOrigin'].forEach(function (prop) {
51712  Html5.prototype['set' + toTitleCase$1(prop)] = function (v) {
51713    this.el_[prop] = v;
51714  };
51715});
51716
51717// wrap native functions with a function
51718// The list is as follows:
51719// pause, load, play
51720[
51721/**
51722 * A wrapper around the media elements `pause` function. This will call the `HTML5`
51723 * media elements `pause` function.
51724 *
51725 * @method Html5#pause
51726 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
51727 */
51728'pause',
51729/**
51730 * A wrapper around the media elements `load` function. This will call the `HTML5`s
51731 * media element `load` function.
51732 *
51733 * @method Html5#load
51734 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
51735 */
51736'load',
51737/**
51738 * A wrapper around the media elements `play` function. This will call the `HTML5`s
51739 * media element `play` function.
51740 *
51741 * @method Html5#play
51742 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
51743 */
51744'play'].forEach(function (prop) {
51745  Html5.prototype[prop] = function () {
51746    return this.el_[prop]();
51747  };
51748});
vendor: 6,777 bytes, lines 51749-52034
51749Tech.withSourceHandlers(Html5);
51750
51751/**
51752 * Native source handler for Html5, simply passes the source to the media element.
51753 *
51754 * @property {Tech~SourceObject} source
51755 *        The source object
51756 *
51757 * @property {Html5} tech
51758 *        The instance of the HTML5 tech.
51759 */
51760Html5.nativeSourceHandler = {};
51761
51762/**
51763 * Check if the media element can play the given mime type.
51764 *
51765 * @param {string} type
51766 *        The mimetype to check
51767 *
51768 * @return {string}
51769 *         'probably', 'maybe', or '' (empty string)
51770 */
51771Html5.nativeSourceHandler.canPlayType = function (type) {
51772  // IE without MediaPlayer throws an error (#519)
51773  try {
51774    return Html5.TEST_VID.canPlayType(type);
51775  } catch (e) {
51776    return '';
51777  }
51778};
51779
51780/**
51781 * Check if the media element can handle a source natively.
51782 *
51783 * @param {Tech~SourceObject} source
51784 *         The source object
51785 *
51786 * @param {Object} [options]
51787 *         Options to be passed to the tech.
51788 *
51789 * @return {string}
51790 *         'probably', 'maybe', or '' (empty string).
51791 */
51792Html5.nativeSourceHandler.canHandleSource = function (source, options) {
51793  // If a type was provided we should rely on that
51794  if (source.type) {
51795    return Html5.nativeSourceHandler.canPlayType(source.type);
51796
51797    // If no type, fall back to checking 'video/[EXTENSION]'
51798  } else if (source.src) {
51799    const ext = getFileExtension(source.src);
51800    return Html5.nativeSourceHandler.canPlayType(`video/${ext}`);
51801  }
51802  return '';
51803};
51804
51805/**
51806 * Pass the source to the native media element.
51807 *
51808 * @param {Tech~SourceObject} source
51809 *        The source object
51810 *
51811 * @param {Html5} tech
51812 *        The instance of the Html5 tech
51813 *
51814 * @param {Object} [options]
51815 *        The options to pass to the source
51816 */
51817Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
51818  tech.setSrc(source.src);
51819};
51820
51821/**
51822 * A noop for the native dispose function, as cleanup is not needed.
51823 */
51824Html5.nativeSourceHandler.dispose = function () {};
51825
51826// Register the native source handler
51827Html5.registerSourceHandler(Html5.nativeSourceHandler);
51828Tech.registerTech('Html5', Html5);
51829
51830/**
51831 * @file player.js
51832 */
51833
51834/** @import AudioTrackList from './tracks/audio-track-list' */
51835/** @import HtmlTrackElement from './tracks/html-track-element' */
51836/** @import HtmlTrackElementList from './tracks/html-track-element-list' */
51837/** @import TextTrackList from './tracks/text-track-list' */
51838/** @import { TimeRange } from './utils/time' */
51839/** @import VideoTrackList from './tracks/video-track-list' */
51840
51841/**
51842 * @callback PlayerReadyCallback
51843 * @this     {Player}
51844 * @returns  {void}
51845 */
51846
51847// The following tech events are simply re-triggered
51848// on the player when they happen
51849const TECH_EVENTS_RETRIGGER = [
51850/**
51851 * Fired while the user agent is downloading media data.
51852 *
51853 * @event Player#progress
51854 * @type {Event}
51855 */
51856/**
51857 * Retrigger the `progress` event that was triggered by the {@link Tech}.
51858 *
51859 * @private
51860 * @method Player#handleTechProgress_
51861 * @fires Player#progress
51862 * @listens Tech#progress
51863 */
51864'progress',
51865/**
51866 * Fires when the loading of an audio/video is aborted.
51867 *
51868 * @event Player#abort
51869 * @type {Event}
51870 */
51871/**
51872 * Retrigger the `abort` event that was triggered by the {@link Tech}.
51873 *
51874 * @private
51875 * @method Player#handleTechAbort_
51876 * @fires Player#abort
51877 * @listens Tech#abort
51878 */
51879'abort',
51880/**
51881 * Fires when the browser is intentionally not getting media data.
51882 *
51883 * @event Player#suspend
51884 * @type {Event}
51885 */
51886/**
51887 * Retrigger the `suspend` event that was triggered by the {@link Tech}.
51888 *
51889 * @private
51890 * @method Player#handleTechSuspend_
51891 * @fires Player#suspend
51892 * @listens Tech#suspend
51893 */
51894'suspend',
51895/**
51896 * Fires when the current playlist is empty.
51897 *
51898 * @event Player#emptied
51899 * @type {Event}
51900 */
51901/**
51902 * Retrigger the `emptied` event that was triggered by the {@link Tech}.
51903 *
51904 * @private
51905 * @method Player#handleTechEmptied_
51906 * @fires Player#emptied
51907 * @listens Tech#emptied
51908 */
51909'emptied',
51910/**
51911 * Fires when the browser is trying to get media data, but data is not available.
51912 *
51913 * @event Player#stalled
51914 * @type {Event}
51915 */
51916/**
51917 * Retrigger the `stalled` event that was triggered by the {@link Tech}.
51918 *
51919 * @private
51920 * @method Player#handleTechStalled_
51921 * @fires Player#stalled
51922 * @listens Tech#stalled
51923 */
51924'stalled',
51925/**
51926 * Fires when the browser has loaded meta data for the audio/video.
51927 *
51928 * @event Player#loadedmetadata
51929 * @type {Event}
51930 */
51931/**
51932 * Retrigger the `loadedmetadata` event that was triggered by the {@link Tech}.
51933 *
51934 * @private
51935 * @method Player#handleTechLoadedmetadata_
51936 * @fires Player#loadedmetadata
51937 * @listens Tech#loadedmetadata
51938 */
51939'loadedmetadata',
51940/**
51941 * Fires when the browser has loaded the current frame of the audio/video.
51942 *
51943 * @event Player#loadeddata
51944 * @type {event}
51945 */
51946/**
51947 * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
51948 *
51949 * @private
51950 * @method Player#handleTechLoaddeddata_
51951 * @fires Player#loadeddata
51952 * @listens Tech#loadeddata
51953 */
51954'loadeddata',
51955/**
51956 * Fires when the current playback position has changed.
51957 *
51958 * @event Player#timeupdate
51959 * @type {event}
51960 */
51961/**
51962 * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
51963 *
51964 * @private
51965 * @method Player#handleTechTimeUpdate_
51966 * @fires Player#timeupdate
51967 * @listens Tech#timeupdate
51968 */
51969'timeupdate',
51970/**
51971 * Fires when the video's intrinsic dimensions change
51972 *
51973 * @event Player#resize
51974 * @type {event}
51975 */
51976/**
51977 * Retrigger the `resize` event that was triggered by the {@link Tech}.
51978 *
51979 * @private
51980 * @method Player#handleTechResize_
51981 * @fires Player#resize
51982 * @listens Tech#resize
51983 */
51984'resize',
51985/**
51986 * Fires when the volume has been changed
51987 *
51988 * @event Player#volumechange
51989 * @type {event}
51990 */
51991/**
51992 * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
51993 *
51994 * @private
51995 * @method Player#handleTechVolumechange_
51996 * @fires Player#volumechange
51997 * @listens Tech#volumechange
51998 */
51999'volumechange',
52000/**
52001 * Fires when the text track has been changed
52002 *
52003 * @event Player#texttrackchange
52004 * @type {event}
52005 */
52006/**
52007 * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
52008 *
52009 * @private
52010 * @method Player#handleTechTexttrackchange_
52011 * @fires Player#texttrackchange
52012 * @listens Tech#texttrackchange
52013 */
52014'texttrackchange'];
52015
52016// events to queue when playback rate is zero
52017// this is a hash for the sole purpose of mapping non-camel-cased event names
52018// to camel-cased function names
52019const TECH_EVENTS_QUEUE = {
52020  canplay: 'CanPlay',
52021  canplaythrough: 'CanPlayThrough',
52022  playing: 'Playing',
52023  seeked: 'Seeked'
52024};
52025const BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge'];
52026const BREAKPOINT_CLASSES = {};
52027
52028// grep: vjs-layout-tiny
52029// grep: vjs-layout-x-small
52030// grep: vjs-layout-small
52031// grep: vjs-layout-medium
52032// grep: vjs-layout-large
52033// grep: vjs-layout-x-large
52034// grep: vjs-layout-huge
vendor: 1,776 bytes, lines 52035-52091
52035BREAKPOINT_ORDER.forEach(k => {
52036  const v = k.charAt(0) === 'x' ? `x-${k.substring(1)}` : k;
52037  BREAKPOINT_CLASSES[k] = `vjs-layout-${v}`;
52038});
52039const DEFAULT_BREAKPOINTS = {
52040  tiny: 210,
52041  xsmall: 320,
52042  small: 425,
52043  medium: 768,
52044  large: 1440,
52045  xlarge: 2560,
52046  huge: Infinity
52047};
52048
52049/**
52050 * An instance of the `Player` class is created when any of the Video.js setup methods
52051 * are used to initialize a video.
52052 *
52053 * After an instance has been created it can be accessed globally in three ways:
52054 * 1. By calling `videojs.getPlayer('example_video_1');`
52055 * 2. By calling `videojs('example_video_1');` (not recommended)
52056 * 2. By using it directly via `videojs.players.example_video_1;`
52057 *
52058 * @extends Component
52059 * @global
52060 */
52061class Player extends Component$1 {
52062  /**
52063   * Create an instance of this class.
52064   *
52065   * @param {Element} tag
52066   *        The original video DOM element used for configuring options.
52067   *
52068   * @param {Object} [options]
52069   *        Object of option names and values.
52070   *
52071   * @param {PlayerReadyCallback} [ready]
52072   *        Ready callback function.
52073   */
52074  constructor(tag, options, ready) {
52075    // Make sure tag ID exists
52076    // also here.. probably better
52077    tag.id = tag.id || options.id || `vjs_video_${newGUID()}`;
52078
52079    // Set Options
52080    // The options argument overrides options set in the video tag
52081    // which overrides globally set options.
52082    // This latter part coincides with the load order
52083    // (tag must exist before Player)
52084    options = Object.assign(Player.getTagSettings(tag), options);
52085
52086    // Delay the initialization of children because we need to set up
52087    // player properties first, and can't use `this` before `super()`
52088    options.initChildren = false;
52089
52090    // Same with creating the element
52091    options.createEl = false;
vendor: 1,535 bytes, lines 52092-52126
52092
52093    // don't auto mixin the evented mixin
52094    options.evented = false;
52095
52096    // we don't want the player to report touch activity on itself
52097    // see enableTouchActivity in Component
52098    options.reportTouchActivity = false;
52099
52100    // If language is not set, get the closest lang attribute
52101    if (!options.language) {
52102      const closest = tag.closest('[lang]');
52103      if (closest) {
52104        options.language = closest.getAttribute('lang');
52105      }
52106    }
52107
52108    // Run base component initializing with new options
52109    super(null, options, ready);
52110
52111    // Create bound methods for document listeners.
52112    this.boundDocumentFullscreenChange_ = e => this.documentFullscreenChange_(e);
52113    this.boundFullWindowOnEscKey_ = e => this.fullWindowOnEscKey(e);
52114    this.boundUpdateStyleEl_ = e => this.updateStyleEl_(e);
52115    this.boundApplyInitTime_ = e => this.applyInitTime_(e);
52116    this.boundUpdateCurrentBreakpoint_ = e => this.updateCurrentBreakpoint_(e);
52117    this.boundHandleTechClick_ = e => this.handleTechClick_(e);
52118    this.boundHandleTechDoubleClick_ = e => this.handleTechDoubleClick_(e);
52119    this.boundHandleTechTouchStart_ = e => this.handleTechTouchStart_(e);
52120    this.boundHandleTechTouchMove_ = e => this.handleTechTouchMove_(e);
52121    this.boundHandleTechTouchEnd_ = e => this.handleTechTouchEnd_(e);
52122    this.boundHandleTechTap_ = e => this.handleTechTap_(e);
52123    this.boundUpdatePlayerHeightOnAudioOnlyMode_ = e => this.updatePlayerHeightOnAudioOnlyMode_(e);
52124
52125    // default isFullscreen_ to false
52126    this.isFullscreen_ = false;
vendor: 9,363 bytes, lines 52127-52397
52127
52128    // create logger
52129    this.log = createLogger(this.id_);
52130
52131    // Hold our own reference to fullscreen api so it can be mocked in tests
52132    this.fsApi_ = FullscreenApi;
52133
52134    // Tracks when a tech changes the poster
52135    this.isPosterFromTech_ = false;
52136
52137    // Holds callback info that gets queued when playback rate is zero
52138    // and a seek is happening
52139    this.queuedCallbacks_ = [];
52140
52141    // Turn off API access because we're loading a new tech that might load asynchronously
52142    this.isReady_ = false;
52143
52144    // Init state hasStarted_
52145    this.hasStarted_ = false;
52146
52147    // Init state userActive_
52148    this.userActive_ = false;
52149
52150    // Init debugEnabled_
52151    this.debugEnabled_ = false;
52152
52153    // Init state audioOnlyMode_
52154    this.audioOnlyMode_ = false;
52155
52156    // Init state audioPosterMode_
52157    this.audioPosterMode_ = false;
52158
52159    // Init state audioOnlyCache_
52160    this.audioOnlyCache_ = {
52161      controlBarHeight: null,
52162      playerHeight: null,
52163      hiddenChildren: []
52164    };
52165
52166    // if the global option object was accidentally blown away by
52167    // someone, bail early with an informative error
52168    if (!this.options_ || !this.options_.techOrder || !this.options_.techOrder.length) {
52169      throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
52170    }
52171
52172    // Store the original tag used to set options
52173    this.tag = tag;
52174
52175    // Store the tag attributes used to restore html5 element
52176    this.tagAttributes = tag && getAttributes(tag);
52177
52178    // Update current language
52179    this.language(this.options_.language);
52180
52181    // Update Supported Languages
52182    if (options.languages) {
52183      // Normalise player option languages to lowercase
52184      const languagesToLower = {};
52185      Object.getOwnPropertyNames(options.languages).forEach(function (name) {
52186        languagesToLower[name.toLowerCase()] = options.languages[name];
52187      });
52188      this.languages_ = languagesToLower;
52189    } else {
52190      this.languages_ = Player.prototype.options_.languages;
52191    }
52192    this.resetCache_();
52193
52194    // Set poster
52195    /** @type string */
52196    this.poster_ = options.poster || '';
52197
52198    // Set controls
52199    /** @type {boolean} */
52200    this.controls_ = !!options.controls;
52201
52202    // Original tag settings stored in options
52203    // now remove immediately so native controls don't flash.
52204    // May be turned back on by HTML5 tech if nativeControlsForTouch is true
52205    tag.controls = false;
52206    tag.removeAttribute('controls');
52207    this.changingSrc_ = false;
52208    this.playCallbacks_ = [];
52209    this.playTerminatedQueue_ = [];
52210
52211    // the attribute overrides the option
52212    if (tag.hasAttribute('autoplay')) {
52213      this.autoplay(true);
52214    } else {
52215      // otherwise use the setter to validate and
52216      // set the correct value.
52217      this.autoplay(this.options_.autoplay);
52218    }
52219
52220    // check plugins
52221    if (options.plugins) {
52222      Object.keys(options.plugins).forEach(name => {
52223        if (typeof this[name] !== 'function') {
52224          throw new Error(`plugin "${name}" does not exist`);
52225        }
52226      });
52227    }
52228
52229    /*
52230     * Store the internal state of scrubbing
52231     *
52232     * @private
52233     * @return {Boolean} True if the user is scrubbing
52234     */
52235    this.scrubbing_ = false;
52236    this.el_ = this.createEl();
52237
52238    // Make this an evented object and use `el_` as its event bus.
52239    evented(this, {
52240      eventBusKey: 'el_'
52241    });
52242
52243    // listen to document and player fullscreenchange handlers so we receive those events
52244    // before a user can receive them so we can update isFullscreen appropriately.
52245    // make sure that we listen to fullscreenchange events before everything else to make sure that
52246    // our isFullscreen method is updated properly for internal components as well as external.
52247    if (this.fsApi_.requestFullscreen) {
52248      on(document__default["default"], this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
52249      this.on(this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
52250    }
52251    if (this.fluid_) {
52252      this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
52253    }
52254    // We also want to pass the original player options to each component and plugin
52255    // as well so they don't need to reach back into the player for options later.
52256    // We also need to do another copy of this.options_ so we don't end up with
52257    // an infinite loop.
52258    const playerOptionsCopy = merge$1(this.options_);
52259
52260    // Load plugins
52261    if (options.plugins) {
52262      Object.keys(options.plugins).forEach(name => {
52263        this[name](options.plugins[name]);
52264      });
52265    }
52266
52267    // Enable debug mode to fire debugon event for all plugins.
52268    if (options.debug) {
52269      this.debug(true);
52270    }
52271    this.options_.playerOptions = playerOptionsCopy;
52272    this.middleware_ = [];
52273    this.playbackRates(options.playbackRates);
52274    if (options.experimentalSvgIcons) {
52275      // Add SVG Sprite to the DOM
52276      const parser = new window__default["default"].DOMParser();
52277      const parsedSVG = parser.parseFromString(icons, 'image/svg+xml');
52278      const errorNode = parsedSVG.querySelector('parsererror');
52279      if (errorNode) {
52280        log$1.warn('Failed to load SVG Icons. Falling back to Font Icons.');
52281        this.options_.experimentalSvgIcons = null;
52282      } else {
52283        const sprite = parsedSVG.documentElement;
52284        sprite.style.display = 'none';
52285        this.el_.appendChild(sprite);
52286        this.addClass('vjs-svg-icons-enabled');
52287      }
52288    }
52289    this.initChildren();
52290
52291    // Set isAudio based on whether or not an audio tag was used
52292    this.isAudio(tag.nodeName.toLowerCase() === 'audio');
52293
52294    // Update controls className. Can't do this when the controls are initially
52295    // set because the element doesn't exist yet.
52296    if (this.controls()) {
52297      this.addClass('vjs-controls-enabled');
52298    } else {
52299      this.addClass('vjs-controls-disabled');
52300    }
52301
52302    // Set ARIA label and region role depending on player type
52303    this.el_.setAttribute('role', 'region');
52304    if (this.isAudio()) {
52305      this.el_.setAttribute('aria-label', this.localize('Audio Player'));
52306    } else {
52307      this.el_.setAttribute('aria-label', this.localize('Video Player'));
52308    }
52309    if (this.isAudio()) {
52310      this.addClass('vjs-audio');
52311    }
52312
52313    // Check if spatial navigation is enabled in the options.
52314    // If enabled, instantiate the SpatialNavigation class.
52315    if (options.spatialNavigation && options.spatialNavigation.enabled) {
52316      this.spatialNavigation = new SpatialNavigation(this);
52317      this.addClass('vjs-spatial-navigation-enabled');
52318    }
52319
52320    // TODO: Make this smarter. Toggle user state between touching/mousing
52321    // using events, since devices can have both touch and mouse events.
52322    // TODO: Make this check be performed again when the window switches between monitors
52323    // (See https://github.com/videojs/video.js/issues/5683)
52324    if (TOUCH_ENABLED) {
52325      this.addClass('vjs-touch-enabled');
52326    }
52327
52328    // iOS Safari has broken hover handling
52329    if (!IS_IOS) {
52330      this.addClass('vjs-workinghover');
52331    }
52332
52333    // Make player easily findable by ID
52334    Player.players[this.id_] = this;
52335
52336    // Add a major version class to aid css in plugins
52337    const majorVersion = version$6.split('.')[0];
52338    this.addClass(`vjs-v${majorVersion}`);
52339
52340    // When the player is first initialized, trigger activity so components
52341    // like the control bar show themselves if needed
52342    this.userActive(true);
52343    this.reportUserActivity();
52344    this.one('play', e => this.listenForUserActivity_(e));
52345    this.on('keydown', e => this.handleKeyDown(e));
52346    this.on('languagechange', e => this.handleLanguagechange(e));
52347    this.breakpoints(this.options_.breakpoints);
52348    this.responsive(this.options_.responsive);
52349
52350    // Calling both the audio mode methods after the player is fully
52351    // setup to be able to listen to the events triggered by them
52352    this.on('ready', () => {
52353      // Calling the audioPosterMode method first so that
52354      // the audioOnlyMode can take precedence when both options are set to true
52355      this.audioPosterMode(this.options_.audioPosterMode);
52356      this.audioOnlyMode(this.options_.audioOnlyMode);
52357    });
52358  }
52359
52360  /**
52361   * Destroys the video player and does any necessary cleanup.
52362   *
52363   * This is especially helpful if you are dynamically adding and removing videos
52364   * to/from the DOM.
52365   *
52366   * @fires Player#dispose
52367   */
52368  dispose() {
52369    /**
52370     * Called when the player is being disposed of.
52371     *
52372     * @event Player#dispose
52373     * @type {Event}
52374     */
52375    this.trigger('dispose');
52376    // prevent dispose from being called twice
52377    this.off('dispose');
52378
52379    // Make sure all player-specific document listeners are unbound. This is
52380    off(document__default["default"], this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
52381    off(document__default["default"], 'keydown', this.boundFullWindowOnEscKey_);
52382    if (this.styleEl_ && this.styleEl_.parentNode) {
52383      this.styleEl_.parentNode.removeChild(this.styleEl_);
52384      this.styleEl_ = null;
52385    }
52386
52387    // Kill reference to this player
52388    Player.players[this.id_] = null;
52389    if (this.tag && this.tag.player) {
52390      this.tag.player = null;
52391    }
52392    if (this.el_ && this.el_.player) {
52393      this.el_.player = null;
52394    }
52395    if (this.tech_) {
52396      this.tech_.dispose();
52397      this.isPosterFromTech_ = false;
vendor: 4,821 bytes, lines 52398-52531
52398      this.poster_ = '';
52399    }
52400    if (this.playerElIngest_) {
52401      this.playerElIngest_ = null;
52402    }
52403    if (this.tag) {
52404      this.tag = null;
52405    }
52406    clearCacheForPlayer(this);
52407
52408    // remove all event handlers for track lists
52409    // all tracks and track listeners are removed on
52410    // tech dispose
52411    ALL.names.forEach(name => {
52412      const props = ALL[name];
52413      const list = this[props.getterName]();
52414
52415      // if it is not a native list
52416      // we have to manually remove event listeners
52417      if (list && list.off) {
52418        list.off();
52419      }
52420    });
52421
52422    // the actual .el_ is removed here, or replaced if
52423    super.dispose({
52424      restoreEl: this.options_.restoreEl
52425    });
52426  }
52427
52428  /**
52429   * Create the `Player`'s DOM element.
52430   *
52431   * @return {Element}
52432   *         The DOM element that gets created.
52433   */
52434  createEl() {
52435    let tag = this.tag;
52436    let el;
52437    let playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
52438    const divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
52439    if (playerElIngest) {
52440      el = this.el_ = tag.parentNode;
52441    } else if (!divEmbed) {
52442      el = this.el_ = super.createEl('div');
52443    }
52444
52445    // Copy over all the attributes from the tag, including ID and class
52446    // ID will now reference player box, not the video tag
52447    const attrs = getAttributes(tag);
52448    if (divEmbed) {
52449      el = this.el_ = tag;
52450      tag = this.tag = document__default["default"].createElement('video');
52451      while (el.children.length) {
52452        tag.appendChild(el.firstChild);
52453      }
52454      if (!hasClass(el, 'video-js')) {
52455        addClass(el, 'video-js');
52456      }
52457      el.appendChild(tag);
52458      playerElIngest = this.playerElIngest_ = el;
52459      // move properties over from our custom `video-js` element
52460      // to our new `video` element. This will move things like
52461      // `src` or `controls` that were set via js before the player
52462      // was initialized.
52463      Object.keys(el).forEach(k => {
52464        try {
52465          tag[k] = el[k];
52466        } catch (e) {
52467          // we got a a property like outerHTML which we can't actually copy, ignore it
52468        }
52469      });
52470    }
52471
52472    // set tabindex to -1 to remove the video element from the focus order
52473    tag.setAttribute('tabindex', '-1');
52474    attrs.tabindex = '-1';
52475
52476    // Workaround for #4583 on Chrome (on Windows) with JAWS.
52477    // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
52478    // Note that we can't detect if JAWS is being used, but this ARIA attribute
52479    // doesn't change behavior of Chrome if JAWS is not being used
52480    if (IS_CHROME && IS_WINDOWS) {
52481      tag.setAttribute('role', 'application');
52482      attrs.role = 'application';
52483    }
52484
52485    // Remove width/height attrs from tag so CSS can make it 100% width/height
52486    tag.removeAttribute('width');
52487    tag.removeAttribute('height');
52488    if ('width' in attrs) {
52489      delete attrs.width;
52490    }
52491    if ('height' in attrs) {
52492      delete attrs.height;
52493    }
52494    Object.getOwnPropertyNames(attrs).forEach(function (attr) {
52495      // don't copy over the class attribute to the player element when we're in a div embed
52496      // the class is already set up properly in the divEmbed case
52497      // and we want to make sure that the `video-js` class doesn't get lost
52498      if (!(divEmbed && attr === 'class')) {
52499        el.setAttribute(attr, attrs[attr]);
52500      }
52501      if (divEmbed) {
52502        tag.setAttribute(attr, attrs[attr]);
52503      }
52504    });
52505
52506    // Update tag id/class for use as HTML5 playback tech
52507    // Might think we should do this after embedding in container so .vjs-tech class
52508    // doesn't flash 100% width/height, but class only applies with .video-js parent
52509    tag.playerId = tag.id;
52510    tag.id += '_html5_api';
52511    tag.className = 'vjs-tech';
52512
52513    // Make player findable on elements
52514    tag.player = el.player = this;
52515    // Default state of video is paused
52516    this.addClass('vjs-paused');
52517    const deviceClassNames = ['IS_SMART_TV', 'IS_TIZEN', 'IS_WEBOS', 'IS_ANDROID', 'IS_IPAD', 'IS_IPHONE', 'IS_CHROMECAST_RECEIVER'].filter(key => browser[key]).map(key => {
52518      return 'vjs-device-' + key.substring(3).toLowerCase().replace(/\_/g, '-');
52519    });
52520    this.addClass(...deviceClassNames);
52521
52522    // Add a style element in the player that we'll use to set the width/height
52523    // of the player in a way that's still overridable by CSS, just like the
52524    // video element
52525    if (window__default["default"].VIDEOJS_NO_DYNAMIC_STYLE !== true) {
52526      this.styleEl_ = createStyleElement('vjs-styles-dimensions');
52527      const defaultsStyleEl = $('.vjs-styles-defaults');
52528      const head = $('head');
52529      head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
52530    }
52531    this.fill_ = false;
vendor: 8,011 bytes, lines 52532-52785
52532    this.fluid_ = false;
52533
52534    // Pass in the width/height/aspectRatio options which will update the style el
52535    this.width(this.options_.width);
52536    this.height(this.options_.height);
52537    this.fill(this.options_.fill);
52538    this.fluid(this.options_.fluid);
52539    this.aspectRatio(this.options_.aspectRatio);
52540    // support both crossOrigin and crossorigin to reduce confusion and issues around the name
52541    this.crossOrigin(this.options_.crossOrigin || this.options_.crossorigin);
52542
52543    // Hide any links within the video/audio tag,
52544    // because IE doesn't hide them completely from screen readers.
52545    const links = tag.getElementsByTagName('a');
52546    for (let i = 0; i < links.length; i++) {
52547      const linkEl = links.item(i);
52548      addClass(linkEl, 'vjs-hidden');
52549      linkEl.setAttribute('hidden', 'hidden');
52550    }
52551
52552    // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
52553    // keep track of the original for later so we can know if the source originally failed
52554    tag.initNetworkState_ = tag.networkState;
52555
52556    // Wrap video tag in div (el/box) container
52557    if (tag.parentNode && !playerElIngest) {
52558      tag.parentNode.insertBefore(el, tag);
52559    }
52560
52561    // insert the tag as the first child of the player element
52562    // then manually add it to the children array so that this.addChild
52563    // will work properly for other components
52564    //
52565    // Breaks iPhone, fixed in HTML5 setup.
52566    prependTo(tag, el);
52567    this.children_.unshift(tag);
52568
52569    // Set lang attr on player to ensure CSS :lang() in consistent with player
52570    // if it's been set to something different to the doc
52571    this.el_.setAttribute('lang', this.language_);
52572    this.el_.setAttribute('translate', 'no');
52573    this.el_ = el;
52574    return el;
52575  }
52576
52577  /**
52578   * Get or set the `Player`'s crossOrigin option. For the HTML5 player, this
52579   * sets the `crossOrigin` property on the `<video>` tag to control the CORS
52580   * behavior.
52581   *
52582   * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
52583   *
52584   * @param {string|null} [value]
52585   *        The value to set the `Player`'s crossOrigin to. If an argument is
52586   *        given, must be one of `'anonymous'` or `'use-credentials'`, or 'null'.
52587   *
52588   * @return {string|null|undefined}
52589   *         - The current crossOrigin value of the `Player` when getting.
52590   *         - undefined when setting
52591   */
52592  crossOrigin(value) {
52593    // `null` can be set to unset a value
52594    if (typeof value === 'undefined') {
52595      return this.techGet_('crossOrigin');
52596    }
52597    if (value !== null && value !== 'anonymous' && value !== 'use-credentials') {
52598      log$1.warn(`crossOrigin must be null,  "anonymous" or "use-credentials", given "${value}"`);
52599      return;
52600    }
52601    this.techCall_('setCrossOrigin', value);
52602    if (this.posterImage) {
52603      this.posterImage.crossOrigin(value);
52604    }
52605    return;
52606  }
52607
52608  /**
52609   * A getter/setter for the `Player`'s width. Returns the player's configured value.
52610   * To get the current width use `currentWidth()`.
52611   *
52612   * @param {number|string} [value]
52613   *        CSS value to set the `Player`'s width to.
52614   *
52615   * @return {number|undefined}
52616   *         - The current width of the `Player` when getting.
52617   *         - Nothing when setting
52618   */
52619  width(value) {
52620    return this.dimension('width', value);
52621  }
52622
52623  /**
52624   * A getter/setter for the `Player`'s height. Returns the player's configured value.
52625   * To get the current height use `currentheight()`.
52626   *
52627   * @param {number|string} [value]
52628   *        CSS value to set the `Player`'s height to.
52629   *
52630   * @return {number|undefined}
52631   *         - The current height of the `Player` when getting.
52632   *         - Nothing when setting
52633   */
52634  height(value) {
52635    return this.dimension('height', value);
52636  }
52637
52638  /**
52639   * A getter/setter for the `Player`'s width & height.
52640   *
52641   * @param {string} dimension
52642   *        This string can be:
52643   *        - 'width'
52644   *        - 'height'
52645   *
52646   * @param {number|string} [value]
52647   *        Value for dimension specified in the first argument.
52648   *
52649   * @return {number}
52650   *         The dimension arguments value when getting (width/height).
52651   */
52652  dimension(dimension, value) {
52653    const privDimension = dimension + '_';
52654    if (value === undefined) {
52655      return this[privDimension] || 0;
52656    }
52657    if (value === '' || value === 'auto') {
52658      // If an empty string is given, reset the dimension to be automatic
52659      this[privDimension] = undefined;
52660      this.updateStyleEl_();
52661      return;
52662    }
52663    const parsedVal = parseFloat(value);
52664    if (isNaN(parsedVal)) {
52665      log$1.error(`Improper value "${value}" supplied for for ${dimension}`);
52666      return;
52667    }
52668    this[privDimension] = parsedVal;
52669    this.updateStyleEl_();
52670  }
52671
52672  /**
52673   * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
52674   *
52675   * Turning this on will turn off fill mode.
52676   *
52677   * @param {boolean} [bool]
52678   *        - A value of true adds the class.
52679   *        - A value of false removes the class.
52680   *        - No value will be a getter.
52681   *
52682   * @return {boolean|undefined}
52683   *         - The value of fluid when getting.
52684   *         - `undefined` when setting.
52685   */
52686  fluid(bool) {
52687    if (bool === undefined) {
52688      return !!this.fluid_;
52689    }
52690    this.fluid_ = !!bool;
52691    if (isEvented(this)) {
52692      this.off(['playerreset', 'resize'], this.boundUpdateStyleEl_);
52693    }
52694    if (bool) {
52695      this.addClass('vjs-fluid');
52696      this.fill(false);
52697      addEventedCallback(this, () => {
52698        this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
52699      });
52700    } else {
52701      this.removeClass('vjs-fluid');
52702    }
52703    this.updateStyleEl_();
52704  }
52705
52706  /**
52707   * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
52708   *
52709   * Turning this on will turn off fluid mode.
52710   *
52711   * @param {boolean} [bool]
52712   *        - A value of true adds the class.
52713   *        - A value of false removes the class.
52714   *        - No value will be a getter.
52715   *
52716   * @return {boolean|undefined}
52717   *         - The value of fluid when getting.
52718   *         - `undefined` when setting.
52719   */
52720  fill(bool) {
52721    if (bool === undefined) {
52722      return !!this.fill_;
52723    }
52724    this.fill_ = !!bool;
52725    if (bool) {
52726      this.addClass('vjs-fill');
52727      this.fluid(false);
52728    } else {
52729      this.removeClass('vjs-fill');
52730    }
52731  }
52732
52733  /**
52734   * Get/Set the aspect ratio
52735   *
52736   * @param {string} [ratio]
52737   *        Aspect ratio for player
52738   *
52739   * @return {string|undefined}
52740   *         returns the current aspect ratio when getting
52741   */
52742
52743  /**
52744   * A getter/setter for the `Player`'s aspect ratio.
52745   *
52746   * @param {string} [ratio]
52747   *        The value to set the `Player`'s aspect ratio to.
52748   *
52749   * @return {string|undefined}
52750   *         - The current aspect ratio of the `Player` when getting.
52751   *         - undefined when setting
52752   */
52753  aspectRatio(ratio) {
52754    if (ratio === undefined) {
52755      return this.aspectRatio_;
52756    }
52757
52758    // Check for width:height format
52759    if (!/^\d+\:\d+$/.test(ratio)) {
52760      throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
52761    }
52762    this.aspectRatio_ = ratio;
52763
52764    // We're assuming if you set an aspect ratio you want fluid mode,
52765    // because in fixed mode you could calculate width and height yourself.
52766    this.fluid(true);
52767    this.updateStyleEl_();
52768  }
52769
52770  /**
52771   * Update styles of the `Player` element (height, width and aspect ratio).
52772   *
52773   * @private
52774   * @listens Tech#loadedmetadata
52775   */
52776  updateStyleEl_() {
52777    if (window__default["default"].VIDEOJS_NO_DYNAMIC_STYLE === true) {
52778      const width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
52779      const height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
52780      const techEl = this.tech_ && this.tech_.el();
52781      if (techEl) {
52782        if (width >= 0) {
52783          techEl.width = width;
52784        }
52785        if (height >= 0) {
vendor: 28,386 bytes, lines 52786-53654
52786          techEl.height = height;
52787        }
52788      }
52789      return;
52790    }
52791    let width;
52792    let height;
52793    let aspectRatio;
52794    let idClass;
52795
52796    // The aspect ratio is either used directly or to calculate width and height.
52797    if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
52798      // Use any aspectRatio that's been specifically set
52799      aspectRatio = this.aspectRatio_;
52800    } else if (this.videoWidth() > 0) {
52801      // Otherwise try to get the aspect ratio from the video metadata
52802      aspectRatio = this.videoWidth() + ':' + this.videoHeight();
52803    } else {
52804      // Or use a default. The video element's is 2:1, but 16:9 is more common.
52805      aspectRatio = '16:9';
52806    }
52807
52808    // Get the ratio as a decimal we can use to calculate dimensions
52809    const ratioParts = aspectRatio.split(':');
52810    const ratioMultiplier = ratioParts[1] / ratioParts[0];
52811    if (this.width_ !== undefined) {
52812      // Use any width that's been specifically set
52813      width = this.width_;
52814    } else if (this.height_ !== undefined) {
52815      // Or calculate the width from the aspect ratio if a height has been set
52816      width = this.height_ / ratioMultiplier;
52817    } else {
52818      // Or use the video's metadata, or use the video el's default of 300
52819      width = this.videoWidth() || 300;
52820    }
52821    if (this.height_ !== undefined) {
52822      // Use any height that's been specifically set
52823      height = this.height_;
52824    } else {
52825      // Otherwise calculate the height from the ratio and the width
52826      height = width * ratioMultiplier;
52827    }
52828
52829    // Ensure the CSS class is valid by starting with an alpha character
52830    if (/^[^a-zA-Z]/.test(this.id())) {
52831      idClass = 'dimensions-' + this.id();
52832    } else {
52833      idClass = this.id() + '-dimensions';
52834    }
52835
52836    // Ensure the right class is still on the player for the style element
52837    this.addClass(idClass);
52838    setTextContent(this.styleEl_, `
52839      .${idClass} {
52840        width: ${width}px;
52841        height: ${height}px;
52842      }
52843
52844      .${idClass}.vjs-fluid:not(.vjs-audio-only-mode) {
52845        padding-top: ${ratioMultiplier * 100}%;
52846      }
52847    `);
52848  }
52849
52850  /**
52851   * Load/Create an instance of playback {@link Tech} including element
52852   * and API methods. Then append the `Tech` element in `Player` as a child.
52853   *
52854   * @param {string} techName
52855   *        name of the playback technology
52856   *
52857   * @param {string} source
52858   *        video source
52859   *
52860   * @private
52861   */
52862  loadTech_(techName, source) {
52863    // Pause and remove current playback technology
52864    if (this.tech_) {
52865      this.unloadTech_();
52866    }
52867    const titleTechName = toTitleCase$1(techName);
52868    const camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1);
52869
52870    // get rid of the HTML5 video tag as soon as we are using another tech
52871    if (titleTechName !== 'Html5' && this.tag) {
52872      Tech.getTech('Html5').disposeMediaElement(this.tag);
52873      this.tag.player = null;
52874      this.tag = null;
52875    }
52876    this.techName_ = titleTechName;
52877
52878    // Turn off API access because we're loading a new tech that might load asynchronously
52879    this.isReady_ = false;
52880    let autoplay = this.autoplay();
52881
52882    // if autoplay is a string (or `true` with normalizeAutoplay: true) we pass false to the tech
52883    // because the player is going to handle autoplay on `loadstart`
52884    if (typeof this.autoplay() === 'string' || this.autoplay() === true && this.options_.normalizeAutoplay) {
52885      autoplay = false;
52886    }
52887
52888    // Grab tech-specific options from player options and add source and parent element to use.
52889    const techOptions = {
52890      source,
52891      autoplay,
52892      'nativeControlsForTouch': this.options_.nativeControlsForTouch,
52893      'playerId': this.id(),
52894      'techId': `${this.id()}_${camelTechName}_api`,
52895      'playsinline': this.options_.playsinline,
52896      'preload': this.options_.preload,
52897      'loop': this.options_.loop,
52898      'disablePictureInPicture': this.options_.disablePictureInPicture,
52899      'muted': this.options_.muted,
52900      'poster': this.poster(),
52901      'language': this.language(),
52902      'playerElIngest': this.playerElIngest_ || false,
52903      'vtt.js': this.options_['vtt.js'],
52904      'canOverridePoster': !!this.options_.techCanOverridePoster,
52905      'enableSourceset': this.options_.enableSourceset
52906    };
52907    ALL.names.forEach(name => {
52908      const props = ALL[name];
52909      techOptions[props.getterName] = this[props.privateName];
52910    });
52911    Object.assign(techOptions, this.options_[titleTechName]);
52912    Object.assign(techOptions, this.options_[camelTechName]);
52913    Object.assign(techOptions, this.options_[techName.toLowerCase()]);
52914    if (this.tag) {
52915      techOptions.tag = this.tag;
52916    }
52917    if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
52918      techOptions.startTime = this.cache_.currentTime;
52919    }
52920
52921    // Initialize tech instance
52922    const TechClass = Tech.getTech(techName);
52923    if (!TechClass) {
52924      throw new Error(`No Tech named '${titleTechName}' exists! '${titleTechName}' should be registered using videojs.registerTech()'`);
52925    }
52926    this.tech_ = new TechClass(techOptions);
52927
52928    // player.triggerReady is always async, so don't need this to be async
52929    this.tech_.ready(bind_(this, this.handleTechReady_), true);
52930    textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_);
52931
52932    // Listen to all HTML5-defined events and trigger them on the player
52933    TECH_EVENTS_RETRIGGER.forEach(event => {
52934      this.on(this.tech_, event, e => this[`handleTech${toTitleCase$1(event)}_`](e));
52935    });
52936    Object.keys(TECH_EVENTS_QUEUE).forEach(event => {
52937      this.on(this.tech_, event, eventObj => {
52938        if (this.tech_.playbackRate() === 0 && this.tech_.seeking()) {
52939          this.queuedCallbacks_.push({
52940            callback: this[`handleTech${TECH_EVENTS_QUEUE[event]}_`].bind(this),
52941            event: eventObj
52942          });
52943          return;
52944        }
52945        this[`handleTech${TECH_EVENTS_QUEUE[event]}_`](eventObj);
52946      });
52947    });
52948    this.on(this.tech_, 'loadstart', e => this.handleTechLoadStart_(e));
52949    this.on(this.tech_, 'sourceset', e => this.handleTechSourceset_(e));
52950    this.on(this.tech_, 'waiting', e => this.handleTechWaiting_(e));
52951    this.on(this.tech_, 'ended', e => this.handleTechEnded_(e));
52952    this.on(this.tech_, 'seeking', e => this.handleTechSeeking_(e));
52953    this.on(this.tech_, 'play', e => this.handleTechPlay_(e));
52954    this.on(this.tech_, 'pause', e => this.handleTechPause_(e));
52955    this.on(this.tech_, 'durationchange', e => this.handleTechDurationChange_(e));
52956    this.on(this.tech_, 'fullscreenchange', (e, data) => this.handleTechFullscreenChange_(e, data));
52957    this.on(this.tech_, 'fullscreenerror', (e, err) => this.handleTechFullscreenError_(e, err));
52958    this.on(this.tech_, 'enterpictureinpicture', e => this.handleTechEnterPictureInPicture_(e));
52959    this.on(this.tech_, 'leavepictureinpicture', e => this.handleTechLeavePictureInPicture_(e));
52960    this.on(this.tech_, 'error', e => this.handleTechError_(e));
52961    this.on(this.tech_, 'posterchange', e => this.handleTechPosterChange_(e));
52962    this.on(this.tech_, 'textdata', e => this.handleTechTextData_(e));
52963    this.on(this.tech_, 'ratechange', e => this.handleTechRateChange_(e));
52964    this.on(this.tech_, 'loadedmetadata', this.boundUpdateStyleEl_);
52965    this.usingNativeControls(this.techGet_('controls'));
52966    if (this.controls() && !this.usingNativeControls()) {
52967      this.addTechControlsListeners_();
52968    }
52969
52970    // Add the tech element in the DOM if it was not already there
52971    // Make sure to not insert the original video element if using Html5
52972    if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
52973      prependTo(this.tech_.el(), this.el());
52974    }
52975
52976    // Get rid of the original video tag reference after the first tech is loaded
52977    if (this.tag) {
52978      this.tag.player = null;
52979      this.tag = null;
52980    }
52981  }
52982
52983  /**
52984   * Unload and dispose of the current playback {@link Tech}.
52985   *
52986   * @private
52987   */
52988  unloadTech_() {
52989    // Save the current text tracks so that we can reuse the same text tracks with the next tech
52990    ALL.names.forEach(name => {
52991      const props = ALL[name];
52992      this[props.privateName] = this[props.getterName]();
52993    });
52994    this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
52995    this.isReady_ = false;
52996    this.tech_.dispose();
52997    this.tech_ = false;
52998    if (this.isPosterFromTech_) {
52999      this.poster_ = '';
53000      this.trigger('posterchange');
53001    }
53002    this.isPosterFromTech_ = false;
53003  }
53004
53005  /**
53006   * Return a reference to the current {@link Tech}.
53007   * It will print a warning by default about the danger of using the tech directly
53008   * but any argument that is passed in will silence the warning.
53009   *
53010   * @param {*} [safety]
53011   *        Anything passed in to silence the warning
53012   *
53013   * @return {Tech}
53014   *         The Tech
53015   */
53016  tech(safety) {
53017    if (safety === undefined) {
53018      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');
53019    }
53020    return this.tech_;
53021  }
53022
53023  /**
53024   * An object that contains Video.js version.
53025   *
53026   * @typedef {Object} PlayerVersion
53027   *
53028   * @property {string} 'video.js' - Video.js version
53029   */
53030
53031  /**
53032   * Returns an object with Video.js version.
53033   *
53034   * @return {PlayerVersion}
53035   *          An object with Video.js version.
53036   */
53037  version() {
53038    return {
53039      'video.js': version$6
53040    };
53041  }
53042
53043  /**
53044   * Set up click and touch listeners for the playback element
53045   *
53046   * - On desktops: a click on the video itself will toggle playback
53047   * - On mobile devices: a click on the video toggles controls
53048   *   which is done by toggling the user state between active and
53049   *   inactive
53050   * - A tap can signal that a user has become active or has become inactive
53051   *   e.g. a quick tap on an iPhone movie should reveal the controls. Another
53052   *   quick tap should hide them again (signaling the user is in an inactive
53053   *   viewing state)
53054   * - In addition to this, we still want the user to be considered inactive after
53055   *   a few seconds of inactivity.
53056   *
53057   * > Note: the only part of iOS interaction we can't mimic with this setup
53058   * is a touch and hold on the video element counting as activity in order to
53059   * keep the controls showing, but that shouldn't be an issue. A touch and hold
53060   * on any controls will still keep the user active
53061   *
53062   * @private
53063   */
53064  addTechControlsListeners_() {
53065    // Make sure to remove all the previous listeners in case we are called multiple times.
53066    this.removeTechControlsListeners_();
53067    this.on(this.tech_, 'click', this.boundHandleTechClick_);
53068    this.on(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);
53069
53070    // If the controls were hidden we don't want that to change without a tap event
53071    // so we'll check if the controls were already showing before reporting user
53072    // activity
53073    this.on(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
53074    this.on(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
53075    this.on(this.tech_, 'touchend', this.boundHandleTechTouchEnd_);
53076
53077    // The tap listener needs to come after the touchend listener because the tap
53078    // listener cancels out any reportedUserActivity when setting userActive(false)
53079    this.on(this.tech_, 'tap', this.boundHandleTechTap_);
53080  }
53081
53082  /**
53083   * Remove the listeners used for click and tap controls. This is needed for
53084   * toggling to controls disabled, where a tap/touch should do nothing.
53085   *
53086   * @private
53087   */
53088  removeTechControlsListeners_() {
53089    // We don't want to just use `this.off()` because there might be other needed
53090    // listeners added by techs that extend this.
53091    this.off(this.tech_, 'tap', this.boundHandleTechTap_);
53092    this.off(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
53093    this.off(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
53094    this.off(this.tech_, 'touchend', this.boundHandleTechTouchEnd_);
53095    this.off(this.tech_, 'click', this.boundHandleTechClick_);
53096    this.off(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);
53097  }
53098
53099  /**
53100   * Player waits for the tech to be ready
53101   *
53102   * @private
53103   */
53104  handleTechReady_() {
53105    this.triggerReady();
53106
53107    // Keep the same volume as before
53108    if (this.cache_.volume) {
53109      this.techCall_('setVolume', this.cache_.volume);
53110    }
53111
53112    // Look if the tech found a higher resolution poster while loading
53113    this.handleTechPosterChange_();
53114
53115    // Update the duration if available
53116    this.handleTechDurationChange_();
53117  }
53118
53119  /**
53120   * Retrigger the `loadstart` event that was triggered by the {@link Tech}.
53121   *
53122   * @fires Player#loadstart
53123   * @listens Tech#loadstart
53124   * @private
53125   */
53126  handleTechLoadStart_() {
53127    // TODO: Update to use `emptied` event instead. See #1277.
53128
53129    this.removeClass('vjs-ended', 'vjs-seeking');
53130
53131    // reset the error state
53132    this.error(null);
53133
53134    // Update the duration
53135    this.handleTechDurationChange_();
53136    if (!this.paused()) {
53137      /**
53138       * Fired when the user agent begins looking for media data
53139       *
53140       * @event Player#loadstart
53141       * @type {Event}
53142       */
53143      this.trigger('loadstart');
53144    } else {
53145      // reset the hasStarted state
53146      this.hasStarted(false);
53147      this.trigger('loadstart');
53148    }
53149
53150    // autoplay happens after loadstart for the browser,
53151    // so we mimic that behavior
53152    this.manualAutoplay_(this.autoplay() === true && this.options_.normalizeAutoplay ? 'play' : this.autoplay());
53153  }
53154
53155  /**
53156   * Handle autoplay string values, rather than the typical boolean
53157   * values that should be handled by the tech. Note that this is not
53158   * part of any specification. Valid values and what they do can be
53159   * found on the autoplay getter at Player#autoplay()
53160   */
53161  manualAutoplay_(type) {
53162    if (!this.tech_ || typeof type !== 'string') {
53163      return;
53164    }
53165
53166    // Save original muted() value, set muted to true, and attempt to play().
53167    // On promise rejection, restore muted from saved value
53168    const resolveMuted = () => {
53169      const previouslyMuted = this.muted();
53170      this.muted(true);
53171      const restoreMuted = () => {
53172        this.muted(previouslyMuted);
53173      };
53174
53175      // restore muted on play terminatation
53176      this.playTerminatedQueue_.push(restoreMuted);
53177      const mutedPromise = this.play();
53178      if (!isPromise(mutedPromise)) {
53179        return;
53180      }
53181      return mutedPromise.catch(err => {
53182        restoreMuted();
53183        throw new Error(`Rejection at manualAutoplay. Restoring muted value. ${err ? err : ''}`);
53184      });
53185    };
53186    let promise;
53187
53188    // if muted defaults to true
53189    // the only thing we can do is call play
53190    if (type === 'any' && !this.muted()) {
53191      promise = this.play();
53192      if (isPromise(promise)) {
53193        promise = promise.catch(resolveMuted);
53194      }
53195    } else if (type === 'muted' && !this.muted()) {
53196      promise = resolveMuted();
53197    } else {
53198      promise = this.play();
53199    }
53200    if (!isPromise(promise)) {
53201      return;
53202    }
53203    return promise.then(() => {
53204      this.trigger({
53205        type: 'autoplay-success',
53206        autoplay: type
53207      });
53208    }).catch(() => {
53209      this.trigger({
53210        type: 'autoplay-failure',
53211        autoplay: type
53212      });
53213    });
53214  }
53215
53216  /**
53217   * Update the internal source caches so that we return the correct source from
53218   * `src()`, `currentSource()`, and `currentSources()`.
53219   *
53220   * > Note: `currentSources` will not be updated if the source that is passed in exists
53221   *         in the current `currentSources` cache.
53222   *
53223   *
53224   * @param {Tech~SourceObject} srcObj
53225   *        A string or object source to update our caches to.
53226   */
53227  updateSourceCaches_(srcObj = '') {
53228    let src = srcObj;
53229    let type = '';
53230    if (typeof src !== 'string') {
53231      src = srcObj.src;
53232      type = srcObj.type;
53233    }
53234
53235    // make sure all the caches are set to default values
53236    // to prevent null checking
53237    this.cache_.source = this.cache_.source || {};
53238    this.cache_.sources = this.cache_.sources || [];
53239
53240    // try to get the type of the src that was passed in
53241    if (src && !type) {
53242      type = findMimetype(this, src);
53243    }
53244
53245    // update `currentSource` cache always
53246    this.cache_.source = merge$1({}, srcObj, {
53247      src,
53248      type
53249    });
53250    const matchingSources = this.cache_.sources.filter(s => s.src && s.src === src);
53251    const sourceElSources = [];
53252    const sourceEls = this.$$('source');
53253    const matchingSourceEls = [];
53254    for (let i = 0; i < sourceEls.length; i++) {
53255      const sourceObj = getAttributes(sourceEls[i]);
53256      sourceElSources.push(sourceObj);
53257      if (sourceObj.src && sourceObj.src === src) {
53258        matchingSourceEls.push(sourceObj.src);
53259      }
53260    }
53261
53262    // if we have matching source els but not matching sources
53263    // the current source cache is not up to date
53264    if (matchingSourceEls.length && !matchingSources.length) {
53265      this.cache_.sources = sourceElSources;
53266      // if we don't have matching source or source els set the
53267      // sources cache to the `currentSource` cache
53268    } else if (!matchingSources.length) {
53269      this.cache_.sources = [this.cache_.source];
53270    }
53271
53272    // update the tech `src` cache
53273    this.cache_.src = src;
53274  }
53275
53276  /**
53277   * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
53278   * causing the media element to reload.
53279   *
53280   * It will fire for the initial source and each subsequent source.
53281   * This event is a custom event from Video.js and is triggered by the {@link Tech}.
53282   *
53283   * The event object for this event contains a `src` property that will contain the source
53284   * that was available when the event was triggered. This is generally only necessary if Video.js
53285   * is switching techs while the source was being changed.
53286   *
53287   * It is also fired when `load` is called on the player (or media element)
53288   * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
53289   * says that the resource selection algorithm needs to be aborted and restarted.
53290   * In this case, it is very likely that the `src` property will be set to the
53291   * empty string `""` to indicate we do not know what the source will be but
53292   * that it is changing.
53293   *
53294   * *This event is currently still experimental and may change in minor releases.*
53295   * __To use this, pass `enableSourceset` option to the player.__
53296   *
53297   * @event Player#sourceset
53298   * @type {Event}
53299   * @prop {string} src
53300   *                The source url available when the `sourceset` was triggered.
53301   *                It will be an empty string if we cannot know what the source is
53302   *                but know that the source will change.
53303   */
53304  /**
53305   * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
53306   *
53307   * @fires Player#sourceset
53308   * @listens Tech#sourceset
53309   * @private
53310   */
53311  handleTechSourceset_(event) {
53312    // only update the source cache when the source
53313    // was not updated using the player api
53314    if (!this.changingSrc_) {
53315      let updateSourceCaches = src => this.updateSourceCaches_(src);
53316      const playerSrc = this.currentSource().src;
53317      const eventSrc = event.src;
53318
53319      // if we have a playerSrc that is not a blob, and a tech src that is a blob
53320      if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) {
53321        // if both the tech source and the player source were updated we assume
53322        // something like @videojs/http-streaming did the sourceset and skip updating the source cache.
53323        if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) {
53324          updateSourceCaches = () => {};
53325        }
53326      }
53327
53328      // update the source to the initial source right away
53329      // in some cases this will be empty string
53330      updateSourceCaches(eventSrc);
53331
53332      // if the `sourceset` `src` was an empty string
53333      // wait for a `loadstart` to update the cache to `currentSrc`.
53334      // If a sourceset happens before a `loadstart`, we reset the state
53335      if (!event.src) {
53336        this.tech_.any(['sourceset', 'loadstart'], e => {
53337          // if a sourceset happens before a `loadstart` there
53338          // is nothing to do as this `handleTechSourceset_`
53339          // will be called again and this will be handled there.
53340          if (e.type === 'sourceset') {
53341            return;
53342          }
53343          const techSrc = this.techGet_('currentSrc');
53344          this.lastSource_.tech = techSrc;
53345          this.updateSourceCaches_(techSrc);
53346        });
53347      }
53348    }
53349    this.lastSource_ = {
53350      player: this.currentSource().src,
53351      tech: event.src
53352    };
53353    this.trigger({
53354      src: event.src,
53355      type: 'sourceset'
53356    });
53357  }
53358
53359  /**
53360   * Add/remove the vjs-has-started class
53361   *
53362   *
53363   * @param {boolean} request
53364   *        - true: adds the class
53365   *        - false: remove the class
53366   *
53367   * @return {boolean}
53368   *         the boolean value of hasStarted_
53369   */
53370  hasStarted(request) {
53371    if (request === undefined) {
53372      // act as getter, if we have no request to change
53373      return this.hasStarted_;
53374    }
53375    if (request === this.hasStarted_) {
53376      return;
53377    }
53378    this.hasStarted_ = request;
53379    if (this.hasStarted_) {
53380      this.addClass('vjs-has-started');
53381    } else {
53382      this.removeClass('vjs-has-started');
53383    }
53384  }
53385
53386  /**
53387   * Fired whenever the media begins or resumes playback
53388   *
53389   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
53390   * @fires Player#play
53391   * @listens Tech#play
53392   * @private
53393   */
53394  handleTechPlay_() {
53395    this.removeClass('vjs-ended', 'vjs-paused');
53396    this.addClass('vjs-playing');
53397
53398    // hide the poster when the user hits play
53399    this.hasStarted(true);
53400    /**
53401     * Triggered whenever an {@link Tech#play} event happens. Indicates that
53402     * playback has started or resumed.
53403     *
53404     * @event Player#play
53405     * @type {Event}
53406     */
53407    this.trigger('play');
53408  }
53409
53410  /**
53411   * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
53412   *
53413   * If there were any events queued while the playback rate was zero, fire
53414   * those events now.
53415   *
53416   * @private
53417   * @method Player#handleTechRateChange_
53418   * @fires Player#ratechange
53419   * @listens Tech#ratechange
53420   */
53421  handleTechRateChange_() {
53422    if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
53423      this.queuedCallbacks_.forEach(queued => queued.callback(queued.event));
53424      this.queuedCallbacks_ = [];
53425    }
53426    this.cache_.lastPlaybackRate = this.tech_.playbackRate();
53427    /**
53428     * Fires when the playing speed of the audio/video is changed
53429     *
53430     * @event Player#ratechange
53431     * @type {event}
53432     */
53433    this.trigger('ratechange');
53434  }
53435
53436  /**
53437   * Retrigger the `waiting` event that was triggered by the {@link Tech}.
53438   *
53439   * @fires Player#waiting
53440   * @listens Tech#waiting
53441   * @private
53442   */
53443  handleTechWaiting_() {
53444    this.addClass('vjs-waiting');
53445    /**
53446     * A readyState change on the DOM element has caused playback to stop.
53447     *
53448     * @event Player#waiting
53449     * @type {Event}
53450     */
53451    this.trigger('waiting');
53452
53453    // Browsers may emit a timeupdate event after a waiting event. In order to prevent
53454    // premature removal of the waiting class, wait for the time to change.
53455    const timeWhenWaiting = this.currentTime();
53456    const timeUpdateListener = () => {
53457      if (timeWhenWaiting !== this.currentTime()) {
53458        this.removeClass('vjs-waiting');
53459        this.off('timeupdate', timeUpdateListener);
53460      }
53461    };
53462    this.on('timeupdate', timeUpdateListener);
53463  }
53464
53465  /**
53466   * Retrigger the `canplay` event that was triggered by the {@link Tech}.
53467   * > Note: This is not consistent between browsers. See #1351
53468   *
53469   * @fires Player#canplay
53470   * @listens Tech#canplay
53471   * @private
53472   */
53473  handleTechCanPlay_() {
53474    this.removeClass('vjs-waiting');
53475    /**
53476     * The media has a readyState of HAVE_FUTURE_DATA or greater.
53477     *
53478     * @event Player#canplay
53479     * @type {Event}
53480     */
53481    this.trigger('canplay');
53482  }
53483
53484  /**
53485   * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
53486   *
53487   * @fires Player#canplaythrough
53488   * @listens Tech#canplaythrough
53489   * @private
53490   */
53491  handleTechCanPlayThrough_() {
53492    this.removeClass('vjs-waiting');
53493    /**
53494     * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
53495     * entire media file can be played without buffering.
53496     *
53497     * @event Player#canplaythrough
53498     * @type {Event}
53499     */
53500    this.trigger('canplaythrough');
53501  }
53502
53503  /**
53504   * Retrigger the `playing` event that was triggered by the {@link Tech}.
53505   *
53506   * @fires Player#playing
53507   * @listens Tech#playing
53508   * @private
53509   */
53510  handleTechPlaying_() {
53511    this.removeClass('vjs-waiting');
53512    /**
53513     * The media is no longer blocked from playback, and has started playing.
53514     *
53515     * @event Player#playing
53516     * @type {Event}
53517     */
53518    this.trigger('playing');
53519  }
53520
53521  /**
53522   * Retrigger the `seeking` event that was triggered by the {@link Tech}.
53523   *
53524   * @fires Player#seeking
53525   * @listens Tech#seeking
53526   * @private
53527   */
53528  handleTechSeeking_() {
53529    this.addClass('vjs-seeking');
53530    /**
53531     * Fired whenever the player is jumping to a new time
53532     *
53533     * @event Player#seeking
53534     * @type {Event}
53535     */
53536    this.trigger('seeking');
53537  }
53538
53539  /**
53540   * Retrigger the `seeked` event that was triggered by the {@link Tech}.
53541   *
53542   * @fires Player#seeked
53543   * @listens Tech#seeked
53544   * @private
53545   */
53546  handleTechSeeked_() {
53547    this.removeClass('vjs-seeking', 'vjs-ended');
53548    /**
53549     * Fired when the player has finished jumping to a new time
53550     *
53551     * @event Player#seeked
53552     * @type {Event}
53553     */
53554    this.trigger('seeked');
53555  }
53556
53557  /**
53558   * Retrigger the `pause` event that was triggered by the {@link Tech}.
53559   *
53560   * @fires Player#pause
53561   * @listens Tech#pause
53562   * @private
53563   */
53564  handleTechPause_() {
53565    this.removeClass('vjs-playing');
53566    this.addClass('vjs-paused');
53567    /**
53568     * Fired whenever the media has been paused
53569     *
53570     * @event Player#pause
53571     * @type {Event}
53572     */
53573    this.trigger('pause');
53574  }
53575
53576  /**
53577   * Retrigger the `ended` event that was triggered by the {@link Tech}.
53578   *
53579   * @fires Player#ended
53580   * @listens Tech#ended
53581   * @private
53582   */
53583  handleTechEnded_() {
53584    this.addClass('vjs-ended');
53585    this.removeClass('vjs-waiting');
53586    if (this.options_.loop) {
53587      this.currentTime(0);
53588      this.play();
53589    } else if (!this.paused()) {
53590      this.pause();
53591    }
53592
53593    /**
53594     * Fired when the end of the media resource is reached (currentTime == duration)
53595     *
53596     * @event Player#ended
53597     * @type {Event}
53598     */
53599    this.trigger('ended');
53600  }
53601
53602  /**
53603   * Fired when the duration of the media resource is first known or changed
53604   *
53605   * @listens Tech#durationchange
53606   * @private
53607   */
53608  handleTechDurationChange_() {
53609    this.duration(this.techGet_('duration'));
53610  }
53611
53612  /**
53613   * Handle a click on the media element to play/pause
53614   *
53615   * @param {Event} event
53616   *        the event that caused this function to trigger
53617   *
53618   * @listens Tech#click
53619   * @private
53620   */
53621  handleTechClick_(event) {
53622    // When controls are disabled a click should not toggle playback because
53623    // the click is considered a control
53624    if (!this.controls_) {
53625      return;
53626    }
53627    if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.click === undefined || this.options_.userActions.click !== false) {
53628      if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.click === 'function') {
53629        this.options_.userActions.click.call(this, event);
53630      } else if (this.paused()) {
53631        silencePromise(this.play());
53632      } else {
53633        this.pause();
53634      }
53635    }
53636  }
53637
53638  /**
53639   * Handle a double-click on the media element to enter/exit fullscreen,
53640   * or exit documentPictureInPicture mode
53641   *
53642   * @param {Event} event
53643   *        the event that caused this function to trigger
53644   *
53645   * @listens Tech#dblclick
53646   * @private
53647   */
53648  handleTechDoubleClick_(event) {
53649    if (!this.controls_) {
53650      return;
53651    }
53652
53653    // we do not want to toggle fullscreen state
53654    // when double-clicking inside a control bar or a modal
vendor: 5,603 bytes, lines 53655-53872
53655    const inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), el => el.contains(event.target));
53656    if (!inAllowedEls) {
53657      /*
53658       * options.userActions.doubleClick
53659       *
53660       * If `undefined` or `true`, double-click toggles fullscreen if controls are present
53661       * Set to `false` to disable double-click handling
53662       * Set to a function to substitute an external double-click handler
53663       */
53664      if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) {
53665        if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') {
53666          this.options_.userActions.doubleClick.call(this, event);
53667        } else if (this.isInPictureInPicture() && !document__default["default"].pictureInPictureElement) {
53668          // Checking the presence of `window.documentPictureInPicture.window` complicates
53669          // tests, checking `document.pictureInPictureElement` also works. It wouldn't
53670          // be null in regular picture in picture.
53671          // Exit picture in picture mode. This gesture can't trigger pip on the main window.
53672          this.exitPictureInPicture();
53673        } else if (this.isFullscreen()) {
53674          this.exitFullscreen();
53675        } else {
53676          this.requestFullscreen();
53677        }
53678      }
53679    }
53680  }
53681
53682  /**
53683   * Handle a tap on the media element. It will toggle the user
53684   * activity state, which hides and shows the controls.
53685   *
53686   * @listens Tech#tap
53687   * @private
53688   */
53689  handleTechTap_() {
53690    this.userActive(!this.userActive());
53691  }
53692
53693  /**
53694   * Handle touch to start
53695   *
53696   * @listens Tech#touchstart
53697   * @private
53698   */
53699  handleTechTouchStart_() {
53700    this.userWasActive = this.userActive();
53701  }
53702
53703  /**
53704   * Handle touch to move
53705   *
53706   * @listens Tech#touchmove
53707   * @private
53708   */
53709  handleTechTouchMove_() {
53710    if (this.userWasActive) {
53711      this.reportUserActivity();
53712    }
53713  }
53714
53715  /**
53716   * Handle touch to end
53717   *
53718   * @param {Event} event
53719   *        the touchend event that triggered
53720   *        this function
53721   *
53722   * @listens Tech#touchend
53723   * @private
53724   */
53725  handleTechTouchEnd_(event) {
53726    // Stop the mouse events from also happening
53727    if (event.cancelable) {
53728      event.preventDefault();
53729    }
53730  }
53731
53732  /**
53733   * @private
53734   */
53735  toggleFullscreenClass_() {
53736    if (this.isFullscreen()) {
53737      this.addClass('vjs-fullscreen');
53738    } else {
53739      this.removeClass('vjs-fullscreen');
53740    }
53741  }
53742
53743  /**
53744   * when the document fschange event triggers it calls this
53745   */
53746  documentFullscreenChange_(e) {
53747    const targetPlayer = e.target.player;
53748
53749    // if another player was fullscreen
53750    // do a null check for targetPlayer because older firefox's would put document as e.target
53751    if (targetPlayer && targetPlayer !== this) {
53752      return;
53753    }
53754    const el = this.el();
53755    let isFs = document__default["default"][this.fsApi_.fullscreenElement] === el;
53756    if (!isFs && el.matches) {
53757      isFs = el.matches(':' + this.fsApi_.fullscreen);
53758    }
53759    this.isFullscreen(isFs);
53760  }
53761
53762  /**
53763   * Handle Tech Fullscreen Change
53764   *
53765   * @param {Event} event
53766   *        the fullscreenchange event that triggered this function
53767   *
53768   * @param {Object} data
53769   *        the data that was sent with the event
53770   *
53771   * @private
53772   * @listens Tech#fullscreenchange
53773   * @fires Player#fullscreenchange
53774   */
53775  handleTechFullscreenChange_(event, data) {
53776    if (data) {
53777      if (data.nativeIOSFullscreen) {
53778        this.addClass('vjs-ios-native-fs');
53779        this.tech_.one('webkitendfullscreen', () => {
53780          this.removeClass('vjs-ios-native-fs');
53781        });
53782      }
53783      this.isFullscreen(data.isFullscreen);
53784    }
53785  }
53786  handleTechFullscreenError_(event, err) {
53787    this.trigger('fullscreenerror', err);
53788  }
53789
53790  /**
53791   * @private
53792   */
53793  togglePictureInPictureClass_() {
53794    if (this.isInPictureInPicture()) {
53795      this.addClass('vjs-picture-in-picture');
53796    } else {
53797      this.removeClass('vjs-picture-in-picture');
53798    }
53799  }
53800
53801  /**
53802   * Handle Tech Enter Picture-in-Picture.
53803   *
53804   * @param {Event} event
53805   *        the enterpictureinpicture event that triggered this function
53806   *
53807   * @private
53808   * @listens Tech#enterpictureinpicture
53809   */
53810  handleTechEnterPictureInPicture_(event) {
53811    this.isInPictureInPicture(true);
53812  }
53813
53814  /**
53815   * Handle Tech Leave Picture-in-Picture.
53816   *
53817   * @param {Event} event
53818   *        the leavepictureinpicture event that triggered this function
53819   *
53820   * @private
53821   * @listens Tech#leavepictureinpicture
53822   */
53823  handleTechLeavePictureInPicture_(event) {
53824    this.isInPictureInPicture(false);
53825  }
53826
53827  /**
53828   * Fires when an error occurred during the loading of an audio/video.
53829   *
53830   * @private
53831   * @listens Tech#error
53832   */
53833  handleTechError_() {
53834    const error = this.tech_.error();
53835    if (error) {
53836      this.error(error);
53837    }
53838  }
53839
53840  /**
53841   * Retrigger the `textdata` event that was triggered by the {@link Tech}.
53842   *
53843   * @fires Player#textdata
53844   * @listens Tech#textdata
53845   * @private
53846   */
53847  handleTechTextData_() {
53848    let data = null;
53849    if (arguments.length > 1) {
53850      data = arguments[1];
53851    }
53852
53853    /**
53854     * Fires when we get a textdata event from tech
53855     *
53856     * @event Player#textdata
53857     * @type {Event}
53858     */
53859    this.trigger('textdata', data);
53860  }
53861
53862  /**
53863   * Get object for cached values.
53864   *
53865   * @return {Object}
53866   *         get the current object cache
53867   */
53868  getCache() {
53869    return this.cache_;
53870  }
53871
53872  /**
vendor: 13,173 bytes, lines 53873-54310
53873   * Resets the internal cache object.
53874   *
53875   * Using this function outside the player constructor or reset method may
53876   * have unintended side-effects.
53877   *
53878   * @private
53879   */
53880  resetCache_() {
53881    this.cache_ = {
53882      // Right now, the currentTime is not _really_ cached because it is always
53883      // retrieved from the tech (see: currentTime). However, for completeness,
53884      // we set it to zero here to ensure that if we do start actually caching
53885      // it, we reset it along with everything else.
53886      currentTime: 0,
53887      initTime: 0,
53888      inactivityTimeout: this.options_.inactivityTimeout,
53889      duration: NaN,
53890      lastVolume: 1,
53891      lastPlaybackRate: this.defaultPlaybackRate(),
53892      media: null,
53893      src: '',
53894      source: {},
53895      sources: [],
53896      playbackRates: [],
53897      volume: 1
53898    };
53899  }
53900
53901  /**
53902   * Pass values to the playback tech
53903   *
53904   * @param {string} [method]
53905   *        the method to call
53906   *
53907   * @param {Object} [arg]
53908   *        the argument to pass
53909   *
53910   * @private
53911   */
53912  techCall_(method, arg) {
53913    // If it's not ready yet, call method when it is
53914
53915    this.ready(function () {
53916      if (method in allowedSetters) {
53917        return set(this.middleware_, this.tech_, method, arg);
53918      } else if (method in allowedMediators) {
53919        return mediate(this.middleware_, this.tech_, method, arg);
53920      }
53921      try {
53922        if (this.tech_) {
53923          this.tech_[method](arg);
53924        }
53925      } catch (e) {
53926        log$1(e);
53927        throw e;
53928      }
53929    }, true);
53930  }
53931
53932  /**
53933   * Mediate attempt to call playback tech method
53934   * and return the value of the method called.
53935   *
53936   * @param {string} method
53937   *        Tech method
53938   *
53939   * @return {*}
53940   *         Value returned by the tech method called, undefined if tech
53941   *         is not ready or tech method is not present
53942   *
53943   * @private
53944   */
53945  techGet_(method) {
53946    if (!this.tech_ || !this.tech_.isReady_) {
53947      return;
53948    }
53949    if (method in allowedGetters) {
53950      return get(this.middleware_, this.tech_, method);
53951    } else if (method in allowedMediators) {
53952      return mediate(this.middleware_, this.tech_, method);
53953    }
53954
53955    // Log error when playback tech object is present but method
53956    // is undefined or unavailable
53957    try {
53958      return this.tech_[method]();
53959    } catch (e) {
53960      // When building additional tech libs, an expected method may not be defined yet
53961      if (this.tech_[method] === undefined) {
53962        log$1(`Video.js: ${method} method not defined for ${this.techName_} playback technology.`, e);
53963        throw e;
53964      }
53965
53966      // When a method isn't available on the object it throws a TypeError
53967      if (e.name === 'TypeError') {
53968        log$1(`Video.js: ${method} unavailable on ${this.techName_} playback technology element.`, e);
53969        this.tech_.isReady_ = false;
53970        throw e;
53971      }
53972
53973      // If error unknown, just log and throw
53974      log$1(e);
53975      throw e;
53976    }
53977  }
53978
53979  /**
53980   * Attempt to begin playback at the first opportunity.
53981   *
53982   * @return {Promise|undefined}
53983   *         Returns a promise if the browser supports Promises (or one
53984   *         was passed in as an option). This promise will be resolved on
53985   *         the return value of play. If this is undefined it will fulfill the
53986   *         promise chain otherwise the promise chain will be fulfilled when
53987   *         the promise from play is fulfilled.
53988   */
53989  play() {
53990    return new Promise(resolve => {
53991      this.play_(resolve);
53992    });
53993  }
53994
53995  /**
53996   * The actual logic for play, takes a callback that will be resolved on the
53997   * return value of play. This allows us to resolve to the play promise if there
53998   * is one on modern browsers.
53999   *
54000   * @private
54001   * @param {Function} [callback]
54002   *        The callback that should be called when the techs play is actually called
54003   */
54004  play_(callback = silencePromise) {
54005    this.playCallbacks_.push(callback);
54006    const isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc()));
54007    const isSafariOrIOS = Boolean(IS_ANY_SAFARI || IS_IOS);
54008
54009    // treat calls to play_ somewhat like the `one` event function
54010    if (this.waitToPlay_) {
54011      this.off(['ready', 'loadstart'], this.waitToPlay_);
54012      this.waitToPlay_ = null;
54013    }
54014
54015    // if the player/tech is not ready or the src itself is not ready
54016    // queue up a call to play on `ready` or `loadstart`
54017    if (!this.isReady_ || !isSrcReady) {
54018      this.waitToPlay_ = e => {
54019        this.play_();
54020      };
54021      this.one(['ready', 'loadstart'], this.waitToPlay_);
54022
54023      // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
54024      // in that case, we need to prime the video element by calling load so it'll be ready in time
54025      if (!isSrcReady && isSafariOrIOS) {
54026        this.load();
54027      }
54028      return;
54029    }
54030
54031    // If the player/tech is ready and we have a source, we can attempt playback.
54032    const val = this.techGet_('play');
54033
54034    // For native playback, reset the progress bar if we get a play call from a replay.
54035    const isNativeReplay = isSafariOrIOS && this.hasClass('vjs-ended');
54036    if (isNativeReplay) {
54037      this.resetProgressBar_();
54038    }
54039    // play was terminated if the returned value is null
54040    if (val === null) {
54041      this.runPlayTerminatedQueue_();
54042    } else {
54043      this.runPlayCallbacks_(val);
54044    }
54045  }
54046
54047  /**
54048   * These functions will be run when if play is terminated. If play
54049   * runPlayCallbacks_ is run these function will not be run. This allows us
54050   * to differentiate between a terminated play and an actual call to play.
54051   */
54052  runPlayTerminatedQueue_() {
54053    const queue = this.playTerminatedQueue_.slice(0);
54054    this.playTerminatedQueue_ = [];
54055    queue.forEach(function (q) {
54056      q();
54057    });
54058  }
54059
54060  /**
54061   * When a callback to play is delayed we have to run these
54062   * callbacks when play is actually called on the tech. This function
54063   * runs the callbacks that were delayed and accepts the return value
54064   * from the tech.
54065   *
54066   * @param {undefined|Promise} val
54067   *        The return value from the tech.
54068   */
54069  runPlayCallbacks_(val) {
54070    const callbacks = this.playCallbacks_.slice(0);
54071    this.playCallbacks_ = [];
54072    // clear play terminatedQueue since we finished a real play
54073    this.playTerminatedQueue_ = [];
54074    callbacks.forEach(function (cb) {
54075      cb(val);
54076    });
54077  }
54078
54079  /**
54080   * Pause the video playback
54081   */
54082  pause() {
54083    this.techCall_('pause');
54084  }
54085
54086  /**
54087   * Check if the player is paused or has yet to play
54088   *
54089   * @return {boolean}
54090   *         - false: if the media is currently playing
54091   *         - true: if media is not currently playing
54092   */
54093  paused() {
54094    // The initial state of paused should be true (in Safari it's actually false)
54095    return this.techGet_('paused') === false ? false : true;
54096  }
54097
54098  /**
54099   * Get a TimeRange object representing the current ranges of time that the user
54100   * has played.
54101   *
54102   * @return {TimeRange}
54103   *         A time range object that represents all the increments of time that have
54104   *         been played.
54105   */
54106  played() {
54107    return this.techGet_('played') || createTimeRanges$1(0, 0);
54108  }
54109
54110  /**
54111   * Sets or returns whether or not the user is "scrubbing". Scrubbing is
54112   * when the user has clicked the progress bar handle and is
54113   * dragging it along the progress bar.
54114   *
54115   * @param {boolean} [isScrubbing]
54116   *        whether the user is or is not scrubbing
54117   *
54118   * @return {boolean|undefined}
54119   *         - The value of scrubbing when getting
54120   *         - Nothing when setting
54121   */
54122  scrubbing(isScrubbing) {
54123    if (typeof isScrubbing === 'undefined') {
54124      return this.scrubbing_;
54125    }
54126    this.scrubbing_ = !!isScrubbing;
54127    this.techCall_('setScrubbing', this.scrubbing_);
54128    if (isScrubbing) {
54129      this.addClass('vjs-scrubbing');
54130    } else {
54131      this.removeClass('vjs-scrubbing');
54132    }
54133  }
54134
54135  /**
54136   * Get or set the current time (in seconds)
54137   *
54138   * @param {number|string} [seconds]
54139   *        The time to seek to in seconds
54140   *
54141   * @return {number|undefined}
54142   *         - the current time in seconds when getting
54143   *         - Nothing when setting
54144   */
54145  currentTime(seconds) {
54146    if (seconds === undefined) {
54147      // cache last currentTime and return. default to 0 seconds
54148      //
54149      // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
54150      // currentTime when scrubbing, but may not provide much performance benefit after all.
54151      // Should be tested. Also something has to read the actual current time or the cache will
54152      // never get updated.
54153      this.cache_.currentTime = this.techGet_('currentTime') || 0;
54154      return this.cache_.currentTime;
54155    }
54156    if (seconds < 0) {
54157      seconds = 0;
54158    }
54159    if (!this.isReady_ || this.changingSrc_ || !this.tech_ || !this.tech_.isReady_) {
54160      this.cache_.initTime = seconds;
54161      this.off('canplay', this.boundApplyInitTime_);
54162      this.one('canplay', this.boundApplyInitTime_);
54163      return;
54164    }
54165    this.techCall_('setCurrentTime', seconds);
54166    this.cache_.initTime = 0;
54167    if (isFinite(seconds)) {
54168      this.cache_.currentTime = Number(seconds);
54169    }
54170  }
54171
54172  /**
54173   * Apply the value of initTime stored in cache as currentTime.
54174   *
54175   * @private
54176   */
54177  applyInitTime_() {
54178    this.currentTime(this.cache_.initTime);
54179  }
54180
54181  /**
54182   * Normally gets the length in time of the video in seconds;
54183   * in all but the rarest use cases an argument will NOT be passed to the method
54184   *
54185   * > **NOTE**: The video must have started loading before the duration can be
54186   * known, and depending on preload behaviour may not be known until the video starts
54187   * playing.
54188   *
54189   * @fires Player#durationchange
54190   *
54191   * @param {number} [seconds]
54192   *        The duration of the video to set in seconds
54193   *
54194   * @return {number|undefined}
54195   *         - The duration of the video in seconds when getting
54196   *         - Nothing when setting
54197   */
54198  duration(seconds) {
54199    if (seconds === undefined) {
54200      // return NaN if the duration is not known
54201      return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
54202    }
54203    seconds = parseFloat(seconds);
54204
54205    // Standardize on Infinity for signaling video is live
54206    if (seconds < 0) {
54207      seconds = Infinity;
54208    }
54209    if (seconds !== this.cache_.duration) {
54210      // Cache the last set value for optimized scrubbing
54211      this.cache_.duration = seconds;
54212      if (seconds === Infinity) {
54213        this.addClass('vjs-live');
54214      } else {
54215        this.removeClass('vjs-live');
54216      }
54217      if (!isNaN(seconds)) {
54218        // Do not fire durationchange unless the duration value is known.
54219        // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
54220
54221        /**
54222         * @event Player#durationchange
54223         * @type {Event}
54224         */
54225        this.trigger('durationchange');
54226      }
54227    }
54228  }
54229
54230  /**
54231   * Calculates how much time is left in the video. Not part
54232   * of the native video API.
54233   *
54234   * @return {number}
54235   *         The time remaining in seconds
54236   */
54237  remainingTime() {
54238    return this.duration() - this.currentTime();
54239  }
54240
54241  /**
54242   * A remaining time function that is intended to be used when
54243   * the time is to be displayed directly to the user.
54244   *
54245   * @return {number}
54246   *         The rounded time remaining in seconds
54247   */
54248  remainingTimeDisplay() {
54249    return Math.floor(this.duration()) - Math.floor(this.currentTime());
54250  }
54251
54252  //
54253  // Kind of like an array of portions of the video that have been downloaded.
54254
54255  /**
54256   * Get a TimeRange object with an array of the times of the video
54257   * that have been downloaded. If you just want the percent of the
54258   * video that's been downloaded, use bufferedPercent.
54259   *
54260   * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
54261   *
54262   * @return {TimeRange}
54263   *         A mock {@link TimeRanges} object (following HTML spec)
54264   */
54265  buffered() {
54266    let buffered = this.techGet_('buffered');
54267    if (!buffered || !buffered.length) {
54268      buffered = createTimeRanges$1(0, 0);
54269    }
54270    return buffered;
54271  }
54272
54273  /**
54274   * Get the TimeRanges of the media that are currently available
54275   * for seeking to.
54276   *
54277   * @see [Seekable Spec]{@link https://html.spec.whatwg.org/multipage/media.html#dom-media-seekable}
54278   *
54279   * @return {TimeRange}
54280   *         A mock {@link TimeRanges} object (following HTML spec)
54281   */
54282  seekable() {
54283    let seekable = this.techGet_('seekable');
54284    if (!seekable || !seekable.length) {
54285      seekable = createTimeRanges$1(0, 0);
54286    }
54287    return seekable;
54288  }
54289
54290  /**
54291   * Returns whether the player is in the "seeking" state.
54292   *
54293   * @return {boolean} True if the player is in the seeking state, false if not.
54294   */
54295  seeking() {
54296    return this.techGet_('seeking');
54297  }
54298
54299  /**
54300   * Returns whether the player is in the "ended" state.
54301   *
54302   * @return {boolean} True if the player is in the ended state, false if not.
54303   */
54304  ended() {
54305    return this.techGet_('ended');
54306  }
54307
54308  /**
54309   * Returns the current state of network activity for the element, from
54310   * the codes in the list below.
vendor: 4,252 bytes, lines 54311-54440
54311   * - NETWORK_EMPTY (numeric value 0)
54312   *   The element has not yet been initialised. All attributes are in
54313   *   their initial states.
54314   * - NETWORK_IDLE (numeric value 1)
54315   *   The element's resource selection algorithm is active and has
54316   *   selected a resource, but it is not actually using the network at
54317   *   this time.
54318   * - NETWORK_LOADING (numeric value 2)
54319   *   The user agent is actively trying to download data.
54320   * - NETWORK_NO_SOURCE (numeric value 3)
54321   *   The element's resource selection algorithm is active, but it has
54322   *   not yet found a resource to use.
54323   *
54324   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
54325   * @return {number} the current network activity state
54326   */
54327  networkState() {
54328    return this.techGet_('networkState');
54329  }
54330
54331  /**
54332   * Returns a value that expresses the current state of the element
54333   * with respect to rendering the current playback position, from the
54334   * codes in the list below.
54335   * - HAVE_NOTHING (numeric value 0)
54336   *   No information regarding the media resource is available.
54337   * - HAVE_METADATA (numeric value 1)
54338   *   Enough of the resource has been obtained that the duration of the
54339   *   resource is available.
54340   * - HAVE_CURRENT_DATA (numeric value 2)
54341   *   Data for the immediate current playback position is available.
54342   * - HAVE_FUTURE_DATA (numeric value 3)
54343   *   Data for the immediate current playback position is available, as
54344   *   well as enough data for the user agent to advance the current
54345   *   playback position in the direction of playback.
54346   * - HAVE_ENOUGH_DATA (numeric value 4)
54347   *   The user agent estimates that enough data is available for
54348   *   playback to proceed uninterrupted.
54349   *
54350   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
54351   * @return {number} the current playback rendering state
54352   */
54353  readyState() {
54354    return this.techGet_('readyState');
54355  }
54356
54357  /**
54358   * Get the percent (as a decimal) of the video that's been downloaded.
54359   * This method is not a part of the native HTML video API.
54360   *
54361   * @return {number}
54362   *         A decimal between 0 and 1 representing the percent
54363   *         that is buffered 0 being 0% and 1 being 100%
54364   */
54365  bufferedPercent() {
54366    return bufferedPercent(this.buffered(), this.duration());
54367  }
54368
54369  /**
54370   * Get the ending time of the last buffered time range
54371   * This is used in the progress bar to encapsulate all time ranges.
54372   *
54373   * @return {number}
54374   *         The end of the last buffered time range
54375   */
54376  bufferedEnd() {
54377    const buffered = this.buffered();
54378    const duration = this.duration();
54379    let end = buffered.end(buffered.length - 1);
54380    if (end > duration) {
54381      end = duration;
54382    }
54383    return end;
54384  }
54385
54386  /**
54387   * Get or set the current volume of the media
54388   *
54389   * @param  {number} [percentAsDecimal]
54390   *         The new volume as a decimal percent:
54391   *         - 0 is muted/0%/off
54392   *         - 1.0 is 100%/full
54393   *         - 0.5 is half volume or 50%
54394   *
54395   * @return {number|undefined}
54396   *         The current volume as a percent when getting
54397   */
54398  volume(percentAsDecimal) {
54399    let vol;
54400    if (percentAsDecimal !== undefined) {
54401      // Force value to between 0 and 1
54402      vol = Math.max(0, Math.min(1, percentAsDecimal));
54403      this.cache_.volume = vol;
54404      this.techCall_('setVolume', vol);
54405      if (vol > 0) {
54406        this.lastVolume_(vol);
54407      }
54408      return;
54409    }
54410
54411    // Default to 1 when returning current volume.
54412    vol = parseFloat(this.techGet_('volume'));
54413    return isNaN(vol) ? 1 : vol;
54414  }
54415
54416  /**
54417   * Get the current muted state, or turn mute on or off
54418   *
54419   * @param {boolean} [muted]
54420   *        - true to mute
54421   *        - false to unmute
54422   *
54423   * @return {boolean|undefined}
54424   *         - true if mute is on and getting
54425   *         - false if mute is off and getting
54426   *         - nothing if setting
54427   */
54428  muted(muted) {
54429    if (muted !== undefined) {
54430      this.techCall_('setMuted', muted);
54431      return;
54432    }
54433    return this.techGet_('muted') || false;
54434  }
54435
54436  /**
54437   * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
54438   * indicates the state of muted on initial playback.
54439   *
54440   * ```js
vendor: 14,077 bytes, lines 54441-54858
54441   *   var myPlayer = videojs('some-player-id');
54442   *
54443   *   myPlayer.src("http://www.example.com/path/to/video.mp4");
54444   *
54445   *   // get, should be false
54446   *   console.log(myPlayer.defaultMuted());
54447   *   // set to true
54448   *   myPlayer.defaultMuted(true);
54449   *   // get should be true
54450   *   console.log(myPlayer.defaultMuted());
54451   * ```
54452   *
54453   * @param {boolean} [defaultMuted]
54454   *        - true to mute
54455   *        - false to unmute
54456   *
54457   * @return {boolean|undefined}
54458   *         - true if defaultMuted is on and getting
54459   *         - false if defaultMuted is off and getting
54460   *         - Nothing when setting
54461   */
54462  defaultMuted(defaultMuted) {
54463    if (defaultMuted !== undefined) {
54464      this.techCall_('setDefaultMuted', defaultMuted);
54465    }
54466    return this.techGet_('defaultMuted') || false;
54467  }
54468
54469  /**
54470   * Get the last volume, or set it
54471   *
54472   * @param  {number} [percentAsDecimal]
54473   *         The new last volume as a decimal percent:
54474   *         - 0 is muted/0%/off
54475   *         - 1.0 is 100%/full
54476   *         - 0.5 is half volume or 50%
54477   *
54478   * @return {number|undefined}
54479   *         - The current value of lastVolume as a percent when getting
54480   *         - Nothing when setting
54481   *
54482   * @private
54483   */
54484  lastVolume_(percentAsDecimal) {
54485    if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
54486      this.cache_.lastVolume = percentAsDecimal;
54487      return;
54488    }
54489    return this.cache_.lastVolume;
54490  }
54491
54492  /**
54493   * Check if current tech can support native fullscreen
54494   * (e.g. with built in controls like iOS)
54495   *
54496   * @return {boolean}
54497   *         if native fullscreen is supported
54498   */
54499  supportsFullScreen() {
54500    return this.techGet_('supportsFullScreen') || false;
54501  }
54502
54503  /**
54504   * Check if the player is in fullscreen mode or tell the player that it
54505   * is or is not in fullscreen mode.
54506   *
54507   * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
54508   * property and instead document.fullscreenElement is used. But isFullscreen is
54509   * still a valuable property for internal player workings.
54510   *
54511   * @param  {boolean} [isFS]
54512   *         Set the players current fullscreen state
54513   *
54514   * @return {boolean|undefined}
54515   *         - true if fullscreen is on and getting
54516   *         - false if fullscreen is off and getting
54517   *         - Nothing when setting
54518   */
54519  isFullscreen(isFS) {
54520    if (isFS !== undefined) {
54521      const oldValue = this.isFullscreen_;
54522      this.isFullscreen_ = Boolean(isFS);
54523
54524      // if we changed fullscreen state and we're in prefixed mode, trigger fullscreenchange
54525      // this is the only place where we trigger fullscreenchange events for older browsers
54526      // fullWindow mode is treated as a prefixed event and will get a fullscreenchange event as well
54527      if (this.isFullscreen_ !== oldValue && this.fsApi_.prefixed) {
54528        /**
54529           * @event Player#fullscreenchange
54530           * @type {Event}
54531           */
54532        this.trigger('fullscreenchange');
54533      }
54534      this.toggleFullscreenClass_();
54535      return;
54536    }
54537    return this.isFullscreen_;
54538  }
54539
54540  /**
54541   * Increase the size of the video to full screen
54542   * In some browsers, full screen is not supported natively, so it enters
54543   * "full window mode", where the video fills the browser window.
54544   * In browsers and devices that support native full screen, sometimes the
54545   * browser's default controls will be shown, and not the Video.js custom skin.
54546   * This includes most mobile devices (iOS, Android) and older versions of
54547   * Safari.
54548   *
54549   * @param  {Object} [fullscreenOptions]
54550   *         Override the player fullscreen options
54551   *
54552   * @fires Player#fullscreenchange
54553   */
54554  requestFullscreen(fullscreenOptions) {
54555    if (this.isInPictureInPicture()) {
54556      this.exitPictureInPicture();
54557    }
54558    const self = this;
54559    return new Promise((resolve, reject) => {
54560      function offHandler() {
54561        self.off('fullscreenerror', errorHandler);
54562        self.off('fullscreenchange', changeHandler);
54563      }
54564      function changeHandler() {
54565        offHandler();
54566        resolve();
54567      }
54568      function errorHandler(e, err) {
54569        offHandler();
54570        reject(err);
54571      }
54572      self.one('fullscreenchange', changeHandler);
54573      self.one('fullscreenerror', errorHandler);
54574      const promise = self.requestFullscreenHelper_(fullscreenOptions);
54575      if (promise) {
54576        promise.then(offHandler, offHandler);
54577        promise.then(resolve, reject);
54578      }
54579    });
54580  }
54581  requestFullscreenHelper_(fullscreenOptions) {
54582    let fsOptions;
54583
54584    // Only pass fullscreen options to requestFullscreen in spec-compliant browsers.
54585    // Use defaults or player configured option unless passed directly to this method.
54586    if (!this.fsApi_.prefixed) {
54587      fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {};
54588      if (fullscreenOptions !== undefined) {
54589        fsOptions = fullscreenOptions;
54590      }
54591    }
54592
54593    // This method works as follows:
54594    // 1. if a fullscreen api is available, use it
54595    //   1. call requestFullscreen with potential options
54596    //   2. if we got a promise from above, use it to update isFullscreen()
54597    // 2. otherwise, if the tech supports fullscreen, call `enterFullScreen` on it.
54598    //   This is particularly used for iPhone, older iPads, and non-safari browser on iOS.
54599    // 3. otherwise, use "fullWindow" mode
54600    if (this.fsApi_.requestFullscreen) {
54601      const promise = this.el_[this.fsApi_.requestFullscreen](fsOptions);
54602
54603      // Even on browsers with promise support this may not return a promise
54604      if (promise) {
54605        promise.then(() => this.isFullscreen(true), () => this.isFullscreen(false));
54606      }
54607      return promise;
54608    } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
54609      // we can't take the video.js controls fullscreen but we can go fullscreen
54610      // with native controls
54611      this.techCall_('enterFullScreen');
54612    } else {
54613      // fullscreen isn't supported so we'll just stretch the video element to
54614      // fill the viewport
54615      this.enterFullWindow();
54616    }
54617  }
54618
54619  /**
54620   * Return the video to its normal size after having been in full screen mode
54621   *
54622   * @fires Player#fullscreenchange
54623   */
54624  exitFullscreen() {
54625    const self = this;
54626    return new Promise((resolve, reject) => {
54627      function offHandler() {
54628        self.off('fullscreenerror', errorHandler);
54629        self.off('fullscreenchange', changeHandler);
54630      }
54631      function changeHandler() {
54632        offHandler();
54633        resolve();
54634      }
54635      function errorHandler(e, err) {
54636        offHandler();
54637        reject(err);
54638      }
54639      self.one('fullscreenchange', changeHandler);
54640      self.one('fullscreenerror', errorHandler);
54641      const promise = self.exitFullscreenHelper_();
54642      if (promise) {
54643        promise.then(offHandler, offHandler);
54644        // map the promise to our resolve/reject methods
54645        promise.then(resolve, reject);
54646      }
54647    });
54648  }
54649  exitFullscreenHelper_() {
54650    if (this.fsApi_.requestFullscreen) {
54651      const promise = document__default["default"][this.fsApi_.exitFullscreen]();
54652
54653      // Even on browsers with promise support this may not return a promise
54654      if (promise) {
54655        // we're splitting the promise here, so, we want to catch the
54656        // potential error so that this chain doesn't have unhandled errors
54657        silencePromise(promise.then(() => this.isFullscreen(false)));
54658      }
54659      return promise;
54660    } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
54661      this.techCall_('exitFullScreen');
54662    } else {
54663      this.exitFullWindow();
54664    }
54665  }
54666
54667  /**
54668   * When fullscreen isn't supported we can stretch the
54669   * video container to as wide as the browser will let us.
54670   *
54671   * @fires Player#enterFullWindow
54672   */
54673  enterFullWindow() {
54674    this.isFullscreen(true);
54675    this.isFullWindow = true;
54676
54677    // Storing original doc overflow value to return to when fullscreen is off
54678    this.docOrigOverflow = document__default["default"].documentElement.style.overflow;
54679
54680    // Add listener for esc key to exit fullscreen
54681    on(document__default["default"], 'keydown', this.boundFullWindowOnEscKey_);
54682
54683    // Hide any scroll bars
54684    document__default["default"].documentElement.style.overflow = 'hidden';
54685
54686    // Apply fullscreen styles
54687    addClass(document__default["default"].body, 'vjs-full-window');
54688
54689    /**
54690     * @event Player#enterFullWindow
54691     * @type {Event}
54692     */
54693    this.trigger('enterFullWindow');
54694  }
54695
54696  /**
54697   * Check for call to either exit full window or
54698   * full screen on ESC key
54699   *
54700   * @param {string} event
54701   *        Event to check for key press
54702   */
54703  fullWindowOnEscKey(event) {
54704    if (event.key === 'Escape') {
54705      if (this.isFullscreen() === true) {
54706        if (!this.isFullWindow) {
54707          this.exitFullscreen();
54708        } else {
54709          this.exitFullWindow();
54710        }
54711      }
54712    }
54713  }
54714
54715  /**
54716   * Exit full window
54717   *
54718   * @fires Player#exitFullWindow
54719   */
54720  exitFullWindow() {
54721    this.isFullscreen(false);
54722    this.isFullWindow = false;
54723    off(document__default["default"], 'keydown', this.boundFullWindowOnEscKey_);
54724
54725    // Unhide scroll bars.
54726    document__default["default"].documentElement.style.overflow = this.docOrigOverflow;
54727
54728    // Remove fullscreen styles
54729    removeClass(document__default["default"].body, 'vjs-full-window');
54730
54731    // Resize the box, controller, and poster to original sizes
54732    // this.positionAll();
54733    /**
54734     * @event Player#exitFullWindow
54735     * @type {Event}
54736     */
54737    this.trigger('exitFullWindow');
54738  }
54739
54740  /**
54741   * Get or set disable Picture-in-Picture mode.
54742   *
54743   * @param {boolean} [value]
54744   *                  - true will disable Picture-in-Picture mode
54745   *                  - false will enable Picture-in-Picture mode
54746   */
54747  disablePictureInPicture(value) {
54748    if (value === undefined) {
54749      return this.techGet_('disablePictureInPicture');
54750    }
54751    this.techCall_('setDisablePictureInPicture', value);
54752    this.options_.disablePictureInPicture = value;
54753    this.trigger('disablepictureinpicturechanged');
54754  }
54755
54756  /**
54757   * Check if the player is in Picture-in-Picture mode or tell the player that it
54758   * is or is not in Picture-in-Picture mode.
54759   *
54760   * @param  {boolean} [isPiP]
54761   *         Set the players current Picture-in-Picture state
54762   *
54763   * @return {boolean|undefined}
54764   *         - true if Picture-in-Picture is on and getting
54765   *         - false if Picture-in-Picture is off and getting
54766   *         - nothing if setting
54767   */
54768  isInPictureInPicture(isPiP) {
54769    if (isPiP !== undefined) {
54770      this.isInPictureInPicture_ = !!isPiP;
54771      this.togglePictureInPictureClass_();
54772      return;
54773    }
54774    return !!this.isInPictureInPicture_;
54775  }
54776
54777  /**
54778   * Create a floating video window always on top of other windows so that users may
54779   * continue consuming media while they interact with other content sites, or
54780   * applications on their device.
54781   *
54782   * This can use document picture-in-picture or element picture in picture
54783   *
54784   * Set `enableDocumentPictureInPicture` to `true` to use docPiP on a supported browser
54785   * Else set `disablePictureInPicture` to `false` to disable elPiP on a supported browser
54786   *
54787   *
54788   * @see [Spec]{@link https://w3c.github.io/picture-in-picture/}
54789   * @see [Spec]{@link https://wicg.github.io/document-picture-in-picture/}
54790   *
54791   * @fires Player#enterpictureinpicture
54792   *
54793   * @return {Promise}
54794   *         A promise with a Picture-in-Picture window.
54795   */
54796  requestPictureInPicture() {
54797    if (this.options_.enableDocumentPictureInPicture && window__default["default"].documentPictureInPicture) {
54798      const pipContainer = document__default["default"].createElement(this.el().tagName);
54799      pipContainer.classList = this.el().classList;
54800      pipContainer.classList.add('vjs-pip-container');
54801      if (this.posterImage) {
54802        pipContainer.appendChild(this.posterImage.el().cloneNode(true));
54803      }
54804      if (this.titleBar) {
54805        pipContainer.appendChild(this.titleBar.el().cloneNode(true));
54806      }
54807      pipContainer.appendChild(createEl('p', {
54808        className: 'vjs-pip-text'
54809      }, {}, this.localize('Playing in picture-in-picture')));
54810      return window__default["default"].documentPictureInPicture.requestWindow({
54811        // The aspect ratio won't be correct, Chrome bug https://crbug.com/1407629
54812        width: this.videoWidth(),
54813        height: this.videoHeight()
54814      }).then(pipWindow => {
54815        copyStyleSheetsToWindow(pipWindow);
54816        this.el_.parentNode.insertBefore(pipContainer, this.el_);
54817        pipWindow.document.body.appendChild(this.el_);
54818        pipWindow.document.body.classList.add('vjs-pip-window');
54819        this.player_.isInPictureInPicture(true);
54820        this.player_.trigger({
54821          type: 'enterpictureinpicture',
54822          pipWindow
54823        });
54824
54825        // Listen for the PiP closing event to move the video back.
54826        pipWindow.addEventListener('pagehide', event => {
54827          const pipVideo = event.target.querySelector('.video-js');
54828          pipContainer.parentNode.replaceChild(pipVideo, pipContainer);
54829          this.player_.isInPictureInPicture(false);
54830          this.player_.trigger('leavepictureinpicture');
54831        });
54832        return pipWindow;
54833      });
54834    }
54835    if ('pictureInPictureEnabled' in document__default["default"] && this.disablePictureInPicture() === false) {
54836      /**
54837       * This event fires when the player enters picture in picture mode
54838       *
54839       * @event Player#enterpictureinpicture
54840       * @type {Event}
54841       */
54842      return this.techGet_('requestPictureInPicture');
54843    }
54844    return Promise.reject('No PiP mode is available');
54845  }
54846
54847  /**
54848   * Exit Picture-in-Picture mode.
54849   *
54850   * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
54851   *
54852   * @fires Player#leavepictureinpicture
54853   *
54854   * @return {Promise}
54855   *         A promise.
54856   */
54857  exitPictureInPicture() {
54858    if (window__default["default"].documentPictureInPicture && window__default["default"].documentPictureInPicture.window) {
vendor: 4,177 bytes, lines 54859-54979
54859      // With documentPictureInPicture, Player#leavepictureinpicture is fired in the pagehide handler
54860      window__default["default"].documentPictureInPicture.window.close();
54861      return Promise.resolve();
54862    }
54863    if ('pictureInPictureEnabled' in document__default["default"]) {
54864      /**
54865       * This event fires when the player leaves picture in picture mode
54866       *
54867       * @event Player#leavepictureinpicture
54868       * @type {Event}
54869       */
54870      return document__default["default"].exitPictureInPicture();
54871    }
54872  }
54873
54874  /**
54875   * Called when this Player has focus and a key gets pressed down, or when
54876   * any Component of this player receives a key press that it doesn't handle.
54877   * This allows player-wide hotkeys (either as defined below, or optionally
54878   * by an external function).
54879   *
54880   * @param {KeyboardEvent} event
54881   *        The `keydown` event that caused this function to be called.
54882   *
54883   * @listens keydown
54884   */
54885  handleKeyDown(event) {
54886    const {
54887      userActions
54888    } = this.options_;
54889
54890    // Bail out if hotkeys are not configured.
54891    if (!userActions || !userActions.hotkeys) {
54892      return;
54893    }
54894
54895    // Function that determines whether or not to exclude an element from
54896    // hotkeys handling.
54897    const excludeElement = el => {
54898      const tagName = el.tagName.toLowerCase();
54899
54900      // The first and easiest test is for `contenteditable` elements.
54901      if (el.isContentEditable) {
54902        return true;
54903      }
54904
54905      // Inputs matching these types will still trigger hotkey handling as
54906      // they are not text inputs.
54907      const allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit'];
54908      if (tagName === 'input') {
54909        return allowedInputTypes.indexOf(el.type) === -1;
54910      }
54911
54912      // The final test is by tag name. These tags will be excluded entirely.
54913      const excludedTags = ['textarea'];
54914      return excludedTags.indexOf(tagName) !== -1;
54915    };
54916
54917    // Bail out if the user is focused on an interactive form element.
54918    if (excludeElement(this.el_.ownerDocument.activeElement)) {
54919      return;
54920    }
54921    if (typeof userActions.hotkeys === 'function') {
54922      userActions.hotkeys.call(this, event);
54923    } else {
54924      this.handleHotkeys(event);
54925    }
54926  }
54927
54928  /**
54929   * Called when this Player receives a hotkey keydown event.
54930   * Supported player-wide hotkeys are:
54931   *
54932   *   f          - toggle fullscreen
54933   *   m          - toggle mute
54934   *   k or Space - toggle play/pause
54935   *
54936   * @param {Event} event
54937   *        The `keydown` event that caused this function to be called.
54938   */
54939  handleHotkeys(event) {
54940    const hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {};
54941
54942    // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set
54943    const {
54944      fullscreenKey = keydownEvent => event.key.toLowerCase() === 'f',
54945      muteKey = keydownEvent => event.key.toLowerCase() === 'm',
54946      playPauseKey = keydownEvent => event.key.toLowerCase() === 'k' || event.key.toLowerCase() === ' '
54947    } = hotkeys;
54948    if (fullscreenKey.call(this, event)) {
54949      event.preventDefault();
54950      event.stopPropagation();
54951      const FSToggle = Component$1.getComponent('FullscreenToggle');
54952      if (document__default["default"][this.fsApi_.fullscreenEnabled] !== false) {
54953        FSToggle.prototype.handleClick.call(this, event);
54954      }
54955    } else if (muteKey.call(this, event)) {
54956      event.preventDefault();
54957      event.stopPropagation();
54958      const MuteToggle = Component$1.getComponent('MuteToggle');
54959      MuteToggle.prototype.handleClick.call(this, event);
54960    } else if (playPauseKey.call(this, event)) {
54961      event.preventDefault();
54962      event.stopPropagation();
54963      const PlayToggle = Component$1.getComponent('PlayToggle');
54964      PlayToggle.prototype.handleClick.call(this, event);
54965    }
54966  }
54967
54968  /**
54969   * Check whether the player can play a given mimetype
54970   *
54971   * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
54972   *
54973   * @param {string} type
54974   *        The mimetype to check
54975   *
54976   * @return {string}
54977   *         'probably', 'maybe', or '' (empty string)
54978   */
54979  canPlayType(type) {
vendor: 4,282 bytes, lines 54980-55105
54980    let can;
54981
54982    // Loop through each playback technology in the options order
54983    for (let i = 0, j = this.options_.techOrder; i < j.length; i++) {
54984      const techName = j[i];
54985      let tech = Tech.getTech(techName);
54986
54987      // Support old behavior of techs being registered as components.
54988      // Remove once that deprecated behavior is removed.
54989      if (!tech) {
54990        tech = Component$1.getComponent(techName);
54991      }
54992
54993      // Check if the current tech is defined before continuing
54994      if (!tech) {
54995        log$1.error(`The "${techName}" tech is undefined. Skipped browser support check for that tech.`);
54996        continue;
54997      }
54998
54999      // Check if the browser supports this technology
55000      if (tech.isSupported()) {
55001        can = tech.canPlayType(type);
55002        if (can) {
55003          return can;
55004        }
55005      }
55006    }
55007    return '';
55008  }
55009
55010  /**
55011   * Select source based on tech-order or source-order
55012   * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
55013   * defaults to tech-order selection
55014   *
55015   * @param {Array} sources
55016   *        The sources for a media asset
55017   *
55018   * @return {Object|boolean}
55019   *         Object of source and tech order or false
55020   */
55021  selectSource(sources) {
55022    // Get only the techs specified in `techOrder` that exist and are supported by the
55023    // current platform
55024    const techs = this.options_.techOrder.map(techName => {
55025      return [techName, Tech.getTech(techName)];
55026    }).filter(([techName, tech]) => {
55027      // Check if the current tech is defined before continuing
55028      if (tech) {
55029        // Check if the browser supports this technology
55030        return tech.isSupported();
55031      }
55032      log$1.error(`The "${techName}" tech is undefined. Skipped browser support check for that tech.`);
55033      return false;
55034    });
55035
55036    // Iterate over each `innerArray` element once per `outerArray` element and execute
55037    // `tester` with both. If `tester` returns a non-falsy value, exit early and return
55038    // that value.
55039    const findFirstPassingTechSourcePair = function (outerArray, innerArray, tester) {
55040      let found;
55041      outerArray.some(outerChoice => {
55042        return innerArray.some(innerChoice => {
55043          found = tester(outerChoice, innerChoice);
55044          if (found) {
55045            return true;
55046          }
55047        });
55048      });
55049      return found;
55050    };
55051    let foundSourceAndTech;
55052    const flip = fn => (a, b) => fn(b, a);
55053    const finder = ([techName, tech], source) => {
55054      if (tech.canPlaySource(source, this.options_[techName.toLowerCase()])) {
55055        return {
55056          source,
55057          tech: techName
55058        };
55059      }
55060    };
55061
55062    // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
55063    // to select from them based on their priority.
55064    if (this.options_.sourceOrder) {
55065      // Source-first ordering
55066      foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
55067    } else {
55068      // Tech-first ordering
55069      foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
55070    }
55071    return foundSourceAndTech || false;
55072  }
55073
55074  /**
55075   * Executes source setting and getting logic
55076   *
55077   * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
55078   *        A SourceObject, an array of SourceObjects, or a string referencing
55079   *        a URL to a media source. It is _highly recommended_ that an object
55080   *        or array of objects is used here, so that source selection
55081   *        algorithms can take the `type` into account.
55082   *
55083   *        If not provided, this method acts as a getter.
55084   * @param {boolean} [isRetry]
55085   *        Indicates whether this is being called internally as a result of a retry
55086   *
55087   * @return {string|undefined}
55088   *         If the `source` argument is missing, returns the current source
55089   *         URL. Otherwise, returns nothing/undefined.
55090   */
55091  handleSrc_(source, isRetry) {
55092    // getter usage
55093    if (typeof source === 'undefined') {
55094      return this.cache_.src || '';
55095    }
55096
55097    // Reset retry behavior for new source
55098    if (this.resetRetryOnError_) {
55099      this.resetRetryOnError_();
55100    }
55101
55102    // filter out invalid sources and turn our source into
55103    // an array of source objects
55104    const sources = filterSource(source);
55105
vendor: 18,946 bytes, lines 55106-55755
55106    // if a source was passed in then it is invalid because
55107    // it was filtered to a zero length Array. So we have to
55108    // show an error
55109    if (!sources.length) {
55110      this.setTimeout(function () {
55111        this.error({
55112          code: 4,
55113          message: this.options_.notSupportedMessage
55114        });
55115      }, 0);
55116      return;
55117    }
55118
55119    // initial sources
55120    this.changingSrc_ = true;
55121
55122    // Only update the cached source list if we are not retrying a new source after error,
55123    // since in that case we want to include the failed source(s) in the cache
55124    if (!isRetry) {
55125      this.cache_.sources = sources;
55126    }
55127    this.updateSourceCaches_(sources[0]);
55128
55129    // middlewareSource is the source after it has been changed by middleware
55130    setSource(this, sources[0], (middlewareSource, mws) => {
55131      this.middleware_ = mws;
55132
55133      // since sourceSet is async we have to update the cache again after we select a source since
55134      // the source that is selected could be out of order from the cache update above this callback.
55135      if (!isRetry) {
55136        this.cache_.sources = sources;
55137      }
55138      this.updateSourceCaches_(middlewareSource);
55139      const err = this.src_(middlewareSource);
55140      if (err) {
55141        if (sources.length > 1) {
55142          return this.handleSrc_(sources.slice(1));
55143        }
55144        this.changingSrc_ = false;
55145
55146        // We need to wrap this in a timeout to give folks a chance to add error event handlers
55147        this.setTimeout(function () {
55148          this.error({
55149            code: 4,
55150            message: this.options_.notSupportedMessage
55151          });
55152        }, 0);
55153
55154        // we could not find an appropriate tech, but let's still notify the delegate that this is it
55155        // this needs a better comment about why this is needed
55156        this.triggerReady();
55157        return;
55158      }
55159      setTech(mws, this.tech_);
55160    });
55161
55162    // Try another available source if this one fails before playback.
55163    if (sources.length > 1) {
55164      const retry = () => {
55165        // Remove the error modal
55166        this.error(null);
55167        this.handleSrc_(sources.slice(1), true);
55168      };
55169      const stopListeningForErrors = () => {
55170        this.off('error', retry);
55171      };
55172      this.one('error', retry);
55173      this.one('playing', stopListeningForErrors);
55174      this.resetRetryOnError_ = () => {
55175        this.off('error', retry);
55176        this.off('playing', stopListeningForErrors);
55177      };
55178    }
55179  }
55180
55181  /**
55182   * Get or set the video source.
55183   *
55184   * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
55185   *        A SourceObject, an array of SourceObjects, or a string referencing
55186   *        a URL to a media source. It is _highly recommended_ that an object
55187   *        or array of objects is used here, so that source selection
55188   *        algorithms can take the `type` into account.
55189   *
55190   *        If not provided, this method acts as a getter.
55191   *
55192   * @return {string|undefined}
55193   *         If the `source` argument is missing, returns the current source
55194   *         URL. Otherwise, returns nothing/undefined.
55195   */
55196  src(source) {
55197    return this.handleSrc_(source, false);
55198  }
55199
55200  /**
55201   * Set the source object on the tech, returns a boolean that indicates whether
55202   * there is a tech that can play the source or not
55203   *
55204   * @param {Tech~SourceObject} source
55205   *        The source object to set on the Tech
55206   *
55207   * @return {boolean}
55208   *         - True if there is no Tech to playback this source
55209   *         - False otherwise
55210   *
55211   * @private
55212   */
55213  src_(source) {
55214    const sourceTech = this.selectSource([source]);
55215    if (!sourceTech) {
55216      return true;
55217    }
55218    if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
55219      this.changingSrc_ = true;
55220      // load this technology with the chosen source
55221      this.loadTech_(sourceTech.tech, sourceTech.source);
55222      this.tech_.ready(() => {
55223        this.changingSrc_ = false;
55224      });
55225      return false;
55226    }
55227
55228    // wait until the tech is ready to set the source
55229    // and set it synchronously if possible (#2326)
55230    this.ready(function () {
55231      // The setSource tech method was added with source handlers
55232      // so older techs won't support it
55233      // We need to check the direct prototype for the case where subclasses
55234      // of the tech do not support source handlers
55235      if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
55236        this.techCall_('setSource', source);
55237      } else {
55238        this.techCall_('src', source.src);
55239      }
55240      this.changingSrc_ = false;
55241    }, true);
55242    return false;
55243  }
55244
55245  /**
55246   * Add a <source> element to the <video> element.
55247   *
55248   * @param {string} srcUrl
55249   *        The URL of the video source.
55250   *
55251   * @param {string} [mimeType]
55252   *        The MIME type of the video source. Optional but recommended.
55253   *
55254   * @return {boolean}
55255   *         Returns true if the source element was successfully added, false otherwise.
55256   */
55257  addSourceElement(srcUrl, mimeType) {
55258    if (!this.tech_) {
55259      return false;
55260    }
55261    return this.tech_.addSourceElement(srcUrl, mimeType);
55262  }
55263
55264  /**
55265   * Remove a <source> element from the <video> element by its URL.
55266   *
55267   * @param {string} srcUrl
55268   *        The URL of the source to remove.
55269   *
55270   * @return {boolean}
55271   *         Returns true if the source element was successfully removed, false otherwise.
55272   */
55273  removeSourceElement(srcUrl) {
55274    if (!this.tech_) {
55275      return false;
55276    }
55277    return this.tech_.removeSourceElement(srcUrl);
55278  }
55279
55280  /**
55281   * Begin loading the src data.
55282   */
55283  load() {
55284    // Workaround to use the load method with the VHS.
55285    // Does not cover the case when the load method is called directly from the mediaElement.
55286    if (this.tech_ && this.tech_.vhs) {
55287      this.src(this.currentSource());
55288      return;
55289    }
55290    this.techCall_('load');
55291  }
55292
55293  /**
55294   * Reset the player. Loads the first tech in the techOrder,
55295   * removes all the text tracks in the existing `tech`,
55296   * and calls `reset` on the `tech`.
55297   */
55298  reset() {
55299    if (this.paused()) {
55300      this.doReset_();
55301    } else {
55302      const playPromise = this.play();
55303      silencePromise(playPromise.then(() => this.doReset_()));
55304    }
55305  }
55306  doReset_() {
55307    if (this.tech_) {
55308      this.tech_.clearTracks('text');
55309    }
55310    this.removeClass('vjs-playing');
55311    this.addClass('vjs-paused');
55312    this.resetCache_();
55313    this.poster('');
55314    this.loadTech_(this.options_.techOrder[0], null);
55315    this.techCall_('reset');
55316    this.resetControlBarUI_();
55317    this.error(null);
55318    if (this.titleBar) {
55319      this.titleBar.update({
55320        title: undefined,
55321        description: undefined
55322      });
55323    }
55324    if (isEvented(this)) {
55325      this.trigger('playerreset');
55326    }
55327  }
55328
55329  /**
55330   * Reset Control Bar's UI by calling sub-methods that reset
55331   * all of Control Bar's components
55332   */
55333  resetControlBarUI_() {
55334    this.resetProgressBar_();
55335    this.resetPlaybackRate_();
55336    this.resetVolumeBar_();
55337  }
55338
55339  /**
55340   * Reset tech's progress so progress bar is reset in the UI
55341   */
55342  resetProgressBar_() {
55343    this.currentTime(0);
55344    const {
55345      currentTimeDisplay,
55346      durationDisplay,
55347      progressControl,
55348      remainingTimeDisplay
55349    } = this.controlBar || {};
55350    const {
55351      seekBar
55352    } = progressControl || {};
55353    if (currentTimeDisplay) {
55354      currentTimeDisplay.updateContent();
55355    }
55356    if (durationDisplay) {
55357      durationDisplay.updateContent();
55358    }
55359    if (remainingTimeDisplay) {
55360      remainingTimeDisplay.updateContent();
55361    }
55362    if (seekBar) {
55363      seekBar.update();
55364      if (seekBar.loadProgressBar) {
55365        seekBar.loadProgressBar.update();
55366      }
55367    }
55368  }
55369
55370  /**
55371   * Reset Playback ratio
55372   */
55373  resetPlaybackRate_() {
55374    this.playbackRate(this.defaultPlaybackRate());
55375    this.handleTechRateChange_();
55376  }
55377
55378  /**
55379   * Reset Volume bar
55380   */
55381  resetVolumeBar_() {
55382    this.volume(1.0);
55383    this.trigger('volumechange');
55384  }
55385
55386  /**
55387   * Returns all of the current source objects.
55388   *
55389   * @return {Tech~SourceObject[]}
55390   *         The current source objects
55391   */
55392  currentSources() {
55393    const source = this.currentSource();
55394    const sources = [];
55395
55396    // assume `{}` or `{ src }`
55397    if (Object.keys(source).length !== 0) {
55398      sources.push(source);
55399    }
55400    return this.cache_.sources || sources;
55401  }
55402
55403  /**
55404   * Returns the current source object.
55405   *
55406   * @return {Tech~SourceObject}
55407   *         The current source object
55408   */
55409  currentSource() {
55410    return this.cache_.source || {};
55411  }
55412
55413  /**
55414   * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
55415   * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
55416   *
55417   * @return {string}
55418   *         The current source
55419   */
55420  currentSrc() {
55421    return this.currentSource() && this.currentSource().src || '';
55422  }
55423
55424  /**
55425   * Get the current source type e.g. video/mp4
55426   * This can allow you rebuild the current source object so that you could load the same
55427   * source and tech later
55428   *
55429   * @return {string}
55430   *         The source MIME type
55431   */
55432  currentType() {
55433    return this.currentSource() && this.currentSource().type || '';
55434  }
55435
55436  /**
55437   * Get or set the preload attribute
55438   *
55439   * @param {'none'|'auto'|'metadata'} [value]
55440   *        Preload mode to pass to tech
55441   *
55442   * @return {string|undefined}
55443   *         - The preload attribute value when getting
55444   *         - Nothing when setting
55445   */
55446  preload(value) {
55447    if (value !== undefined) {
55448      this.techCall_('setPreload', value);
55449      this.options_.preload = value;
55450      return;
55451    }
55452    return this.techGet_('preload');
55453  }
55454
55455  /**
55456   * Get or set the autoplay option. When this is a boolean it will
55457   * modify the attribute on the tech. When this is a string the attribute on
55458   * the tech will be removed and `Player` will handle autoplay on loadstarts.
55459   *
55460   * @param {boolean|'play'|'muted'|'any'} [value]
55461   *        - true: autoplay using the browser behavior
55462   *        - false: do not autoplay
55463   *        - 'play': call play() on every loadstart
55464   *        - 'muted': call muted() then play() on every loadstart
55465   *        - 'any': call play() on every loadstart. if that fails call muted() then play().
55466   *        - *: values other than those listed here will be set `autoplay` to true
55467   *
55468   * @return {boolean|string|undefined}
55469   *         - The current value of autoplay when getting
55470   *         - Nothing when setting
55471   */
55472  autoplay(value) {
55473    // getter usage
55474    if (value === undefined) {
55475      return this.options_.autoplay || false;
55476    }
55477    let techAutoplay;
55478
55479    // if the value is a valid string set it to that, or normalize `true` to 'play', if need be
55480    if (typeof value === 'string' && /(any|play|muted)/.test(value) || value === true && this.options_.normalizeAutoplay) {
55481      this.options_.autoplay = value;
55482      this.manualAutoplay_(typeof value === 'string' ? value : 'play');
55483      techAutoplay = false;
55484
55485      // any falsy value sets autoplay to false in the browser,
55486      // lets do the same
55487    } else if (!value) {
55488      this.options_.autoplay = false;
55489
55490      // any other value (ie truthy) sets autoplay to true
55491    } else {
55492      this.options_.autoplay = true;
55493    }
55494    techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay;
55495
55496    // if we don't have a tech then we do not queue up
55497    // a setAutoplay call on tech ready. We do this because the
55498    // autoplay option will be passed in the constructor and we
55499    // do not need to set it twice
55500    if (this.tech_) {
55501      this.techCall_('setAutoplay', techAutoplay);
55502    }
55503  }
55504
55505  /**
55506   * Set or unset the playsinline attribute.
55507   * Playsinline tells the browser that non-fullscreen playback is preferred.
55508   *
55509   * @param {boolean} [value]
55510   *        - true means that we should try to play inline by default
55511   *        - false means that we should use the browser's default playback mode,
55512   *          which in most cases is inline. iOS Safari is a notable exception
55513   *          and plays fullscreen by default.
55514   *
55515   * @return {string|undefined}
55516   *         - the current value of playsinline
55517   *         - Nothing when setting
55518   *
55519   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
55520   */
55521  playsinline(value) {
55522    if (value !== undefined) {
55523      this.techCall_('setPlaysinline', value);
55524      this.options_.playsinline = value;
55525    }
55526    return this.techGet_('playsinline');
55527  }
55528
55529  /**
55530   * Get or set the loop attribute on the video element.
55531   *
55532   * @param {boolean} [value]
55533   *        - true means that we should loop the video
55534   *        - false means that we should not loop the video
55535   *
55536   * @return {boolean|undefined}
55537   *         - The current value of loop when getting
55538   *         - Nothing when setting
55539   */
55540  loop(value) {
55541    if (value !== undefined) {
55542      this.techCall_('setLoop', value);
55543      this.options_.loop = value;
55544      return;
55545    }
55546    return this.techGet_('loop');
55547  }
55548
55549  /**
55550   * Get or set the poster image source url
55551   *
55552   * @fires Player#posterchange
55553   *
55554   * @param {string} [src]
55555   *        Poster image source URL
55556   *
55557   * @return {string|undefined}
55558   *         - The current value of poster when getting
55559   *         - Nothing when setting
55560   */
55561  poster(src) {
55562    if (src === undefined) {
55563      return this.poster_;
55564    }
55565
55566    // The correct way to remove a poster is to set as an empty string
55567    // other falsey values will throw errors
55568    if (!src) {
55569      src = '';
55570    }
55571    if (src === this.poster_) {
55572      return;
55573    }
55574
55575    // update the internal poster variable
55576    this.poster_ = src;
55577
55578    // update the tech's poster
55579    this.techCall_('setPoster', src);
55580    this.isPosterFromTech_ = false;
55581
55582    // alert components that the poster has been set
55583    /**
55584     * This event fires when the poster image is changed on the player.
55585     *
55586     * @event Player#posterchange
55587     * @type {Event}
55588     */
55589    this.trigger('posterchange');
55590  }
55591
55592  /**
55593   * Some techs (e.g. YouTube) can provide a poster source in an
55594   * asynchronous way. We want the poster component to use this
55595   * poster source so that it covers up the tech's controls.
55596   * (YouTube's play button). However we only want to use this
55597   * source if the player user hasn't set a poster through
55598   * the normal APIs.
55599   *
55600   * @fires Player#posterchange
55601   * @listens Tech#posterchange
55602   * @private
55603   */
55604  handleTechPosterChange_() {
55605    if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
55606      const newPoster = this.tech_.poster() || '';
55607      if (newPoster !== this.poster_) {
55608        this.poster_ = newPoster;
55609        this.isPosterFromTech_ = true;
55610
55611        // Let components know the poster has changed
55612        this.trigger('posterchange');
55613      }
55614    }
55615  }
55616
55617  /**
55618   * Get or set whether or not the controls are showing.
55619   *
55620   * @fires Player#controlsenabled
55621   *
55622   * @param {boolean} [bool]
55623   *        - true to turn controls on
55624   *        - false to turn controls off
55625   *
55626   * @return {boolean|undefined}
55627   *         - The current value of controls when getting
55628   *         - Nothing when setting
55629   */
55630  controls(bool) {
55631    if (bool === undefined) {
55632      return !!this.controls_;
55633    }
55634    bool = !!bool;
55635
55636    // Don't trigger a change event unless it actually changed
55637    if (this.controls_ === bool) {
55638      return;
55639    }
55640    this.controls_ = bool;
55641    if (this.usingNativeControls()) {
55642      this.techCall_('setControls', bool);
55643    }
55644    if (this.controls_) {
55645      this.removeClass('vjs-controls-disabled');
55646      this.addClass('vjs-controls-enabled');
55647      /**
55648       * @event Player#controlsenabled
55649       * @type {Event}
55650       */
55651      this.trigger('controlsenabled');
55652      if (!this.usingNativeControls()) {
55653        this.addTechControlsListeners_();
55654      }
55655    } else {
55656      this.removeClass('vjs-controls-enabled');
55657      this.addClass('vjs-controls-disabled');
55658      /**
55659       * @event Player#controlsdisabled
55660       * @type {Event}
55661       */
55662      this.trigger('controlsdisabled');
55663      if (!this.usingNativeControls()) {
55664        this.removeTechControlsListeners_();
55665      }
55666    }
55667  }
55668
55669  /**
55670   * Toggle native controls on/off. Native controls are the controls built into
55671   * devices (e.g. default iPhone controls) or other techs
55672   * (e.g. Vimeo Controls)
55673   * **This should only be set by the current tech, because only the tech knows
55674   * if it can support native controls**
55675   *
55676   * @fires Player#usingnativecontrols
55677   * @fires Player#usingcustomcontrols
55678   *
55679   * @param {boolean} [bool]
55680   *        - true to turn native controls on
55681   *        - false to turn native controls off
55682   *
55683   * @return {boolean|undefined}
55684   *         - The current value of native controls when getting
55685   *         - Nothing when setting
55686   */
55687  usingNativeControls(bool) {
55688    if (bool === undefined) {
55689      return !!this.usingNativeControls_;
55690    }
55691    bool = !!bool;
55692
55693    // Don't trigger a change event unless it actually changed
55694    if (this.usingNativeControls_ === bool) {
55695      return;
55696    }
55697    this.usingNativeControls_ = bool;
55698    if (this.usingNativeControls_) {
55699      this.addClass('vjs-using-native-controls');
55700
55701      /**
55702       * player is using the native device controls
55703       *
55704       * @event Player#usingnativecontrols
55705       * @type {Event}
55706       */
55707      this.trigger('usingnativecontrols');
55708    } else {
55709      this.removeClass('vjs-using-native-controls');
55710
55711      /**
55712       * player is using the custom HTML controls
55713       *
55714       * @event Player#usingcustomcontrols
55715       * @type {Event}
55716       */
55717      this.trigger('usingcustomcontrols');
55718    }
55719  }
55720
55721  /**
55722   * Set, clear, or get the current MediaError
55723   *
55724   * @fires Player#error
55725   *
55726   * @param  {MediaError|string|number|null} [err]
55727   *         A MediaError or a string/number to be turned
55728   *         into a MediaError. Pass `null` to clear the current error state.
55729   *
55730   * @return {MediaError|null|undefined}
55731   *         - The current MediaError when getting (or null)
55732   *         - Nothing when setting
55733   */
55734  error(err) {
55735    if (err === undefined) {
55736      return this.error_ || null;
55737    }
55738
55739    // allow hooks to modify error object
55740    hooks('beforeerror').forEach(hookFunction => {
55741      const newErr = hookFunction(this, err);
55742      if (!(isObject(newErr) && !Array.isArray(newErr) || typeof newErr === 'string' || typeof newErr === 'number' || newErr === null)) {
55743        this.log.error('please return a value that MediaError expects in beforeerror hooks');
55744        return;
55745      }
55746      err = newErr;
55747    });
55748
55749    // Suppress the first error message for no compatible source until
55750    // user interaction
55751    if (this.options_.suppressNotSupportedError && err && err.code === 4) {
55752      const triggerSuppressedError = function () {
55753        this.error(err);
55754      };
55755      this.options_.suppressNotSupportedError = false;
vendor: 2,717 bytes, lines 55756-55851
55756      this.any(['click', 'touchstart'], triggerSuppressedError);
55757      this.one('loadstart', function () {
55758        this.off(['click', 'touchstart'], triggerSuppressedError);
55759      });
55760      return;
55761    }
55762
55763    // restoring to default
55764    if (err === null) {
55765      this.error_ = null;
55766      this.removeClass('vjs-error');
55767      if (this.errorDisplay) {
55768        this.errorDisplay.close();
55769      }
55770      return;
55771    }
55772    this.error_ = new MediaError(err);
55773
55774    // add the vjs-error classname to the player
55775    this.addClass('vjs-error');
55776
55777    // log the name of the error type and any message
55778    // IE11 logs "[object object]" and required you to expand message to see error object
55779    log$1.error(`(CODE:${this.error_.code} ${MediaError.errorTypes[this.error_.code]})`, this.error_.message, this.error_);
55780
55781    /**
55782     * @event Player#error
55783     * @type {Event}
55784     */
55785    this.trigger('error');
55786
55787    // notify hooks of the per player error
55788    hooks('error').forEach(hookFunction => hookFunction(this, this.error_));
55789    return;
55790  }
55791
55792  /**
55793   * Report user activity
55794   *
55795   * @param {Object} event
55796   *        Event object
55797   */
55798  reportUserActivity(event) {
55799    this.userActivity_ = true;
55800  }
55801
55802  /**
55803   * Get/set if user is active
55804   *
55805   * @fires Player#useractive
55806   * @fires Player#userinactive
55807   *
55808   * @param {boolean} [bool]
55809   *        - true if the user is active
55810   *        - false if the user is inactive
55811   *
55812   * @return {boolean|undefined}
55813   *         - The current value of userActive when getting
55814   *         - Nothing when setting
55815   */
55816  userActive(bool) {
55817    if (bool === undefined) {
55818      return this.userActive_;
55819    }
55820    bool = !!bool;
55821    if (bool === this.userActive_) {
55822      return;
55823    }
55824    this.userActive_ = bool;
55825    if (this.userActive_) {
55826      this.userActivity_ = true;
55827      this.removeClass('vjs-user-inactive');
55828      this.addClass('vjs-user-active');
55829      /**
55830       * @event Player#useractive
55831       * @type {Event}
55832       */
55833      this.trigger('useractive');
55834      return;
55835    }
55836
55837    // Chrome/Safari/IE have bugs where when you change the cursor it can
55838    // trigger a mousemove event. This causes an issue when you're hiding
55839    // the cursor when the user is inactive, and a mousemove signals user
55840    // activity. Making it impossible to go into inactive mode. Specifically
55841    // this happens in fullscreen when we really need to hide the cursor.
55842    //
55843    // When this gets resolved in ALL browsers it can be removed
55844    // https://code.google.com/p/chromium/issues/detail?id=103041
55845    if (this.tech_) {
55846      this.tech_.one('mousemove', function (e) {
55847        e.stopPropagation();
55848        e.preventDefault();
55849      });
55850    }
55851    this.userActivity_ = false;
vendor: 14,229 bytes, lines 55852-56278
55852    this.removeClass('vjs-user-active');
55853    this.addClass('vjs-user-inactive');
55854    /**
55855     * @event Player#userinactive
55856     * @type {Event}
55857     */
55858    this.trigger('userinactive');
55859  }
55860
55861  /**
55862   * Listen for user activity based on timeout value
55863   *
55864   * @private
55865   */
55866  listenForUserActivity_() {
55867    let mouseInProgress;
55868    let lastMoveX;
55869    let lastMoveY;
55870    const handleActivity = bind_(this, this.reportUserActivity);
55871    const handleMouseMove = function (e) {
55872      // #1068 - Prevent mousemove spamming
55873      // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
55874      if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
55875        lastMoveX = e.screenX;
55876        lastMoveY = e.screenY;
55877        handleActivity();
55878      }
55879    };
55880    const handleMouseDown = function () {
55881      handleActivity();
55882      // For as long as the they are touching the device or have their mouse down,
55883      // we consider them active even if they're not moving their finger or mouse.
55884      // So we want to continue to update that they are active
55885      this.clearInterval(mouseInProgress);
55886      // Setting userActivity=true now and setting the interval to the same time
55887      // as the activityCheck interval (250) should ensure we never miss the
55888      // next activityCheck
55889      mouseInProgress = this.setInterval(handleActivity, 250);
55890    };
55891    const handleMouseUpAndMouseLeave = function (event) {
55892      handleActivity();
55893      // Stop the interval that maintains activity if the mouse/touch is down
55894      this.clearInterval(mouseInProgress);
55895    };
55896
55897    // Any mouse movement will be considered user activity
55898    this.on('mousedown', handleMouseDown);
55899    this.on('mousemove', handleMouseMove);
55900    this.on('mouseup', handleMouseUpAndMouseLeave);
55901    this.on('mouseleave', handleMouseUpAndMouseLeave);
55902    const controlBar = this.getChild('controlBar');
55903
55904    // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed)
55905    // controlBar would no longer be hidden by default timeout.
55906    if (controlBar && !IS_IOS && !IS_ANDROID) {
55907      controlBar.on('mouseenter', function (event) {
55908        if (this.player().options_.inactivityTimeout !== 0) {
55909          this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
55910        }
55911        this.player().options_.inactivityTimeout = 0;
55912      });
55913      controlBar.on('mouseleave', function (event) {
55914        this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
55915      });
55916    }
55917
55918    // Listen for keyboard navigation
55919    // Shouldn't need to use inProgress interval because of key repeat
55920    this.on('keydown', handleActivity);
55921    this.on('keyup', handleActivity);
55922
55923    // Run an interval every 250 milliseconds instead of stuffing everything into
55924    // the mousemove/touchmove function itself, to prevent performance degradation.
55925    // `this.reportUserActivity` simply sets this.userActivity_ to true, which
55926    // then gets picked up by this loop
55927    // http://ejohn.org/blog/learning-from-twitter/
55928    let inactivityTimeout;
55929
55930    /** @this Player */
55931    const activityCheck = function () {
55932      // Check to see if mouse/touch activity has happened
55933      if (!this.userActivity_) {
55934        return;
55935      }
55936
55937      // Reset the activity tracker
55938      this.userActivity_ = false;
55939
55940      // If the user state was inactive, set the state to active
55941      this.userActive(true);
55942
55943      // Clear any existing inactivity timeout to start the timer over
55944      this.clearTimeout(inactivityTimeout);
55945      const timeout = this.options_.inactivityTimeout;
55946      if (timeout <= 0) {
55947        return;
55948      }
55949
55950      // In <timeout> milliseconds, if no more activity has occurred the
55951      // user will be considered inactive
55952      inactivityTimeout = this.setTimeout(function () {
55953        // Protect against the case where the inactivityTimeout can trigger just
55954        // before the next user activity is picked up by the activity check loop
55955        // causing a flicker
55956        if (!this.userActivity_) {
55957          this.userActive(false);
55958        }
55959      }, timeout);
55960    };
55961    this.setInterval(activityCheck, 250);
55962  }
55963
55964  /**
55965   * Gets or sets the current playback rate. A playback rate of
55966   * 1.0 represents normal speed and 0.5 would indicate half-speed
55967   * playback, for instance.
55968   *
55969   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
55970   *
55971   * @param {number} [rate]
55972   *       New playback rate to set.
55973   *
55974   * @return {number|undefined}
55975   *         - The current playback rate when getting or 1.0
55976   *         - Nothing when setting
55977   */
55978  playbackRate(rate) {
55979    if (rate !== undefined) {
55980      // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
55981      // that is registered above
55982      this.techCall_('setPlaybackRate', rate);
55983      return;
55984    }
55985    if (this.tech_ && this.tech_.featuresPlaybackRate) {
55986      return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
55987    }
55988    return 1.0;
55989  }
55990
55991  /**
55992   * Gets or sets the current default playback rate. A default playback rate of
55993   * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
55994   * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
55995   * not the current playbackRate.
55996   *
55997   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
55998   *
55999   * @param {number} [rate]
56000   *       New default playback rate to set.
56001   *
56002   * @return {number|undefined}
56003   *         - The default playback rate when getting or 1.0
56004   *         - Nothing when setting
56005   */
56006  defaultPlaybackRate(rate) {
56007    if (rate !== undefined) {
56008      return this.techCall_('setDefaultPlaybackRate', rate);
56009    }
56010    if (this.tech_ && this.tech_.featuresPlaybackRate) {
56011      return this.techGet_('defaultPlaybackRate');
56012    }
56013    return 1.0;
56014  }
56015
56016  /**
56017   * Gets or sets the audio flag
56018   *
56019   * @param {boolean} [bool]
56020   *        - true signals that this is an audio player
56021   *        - false signals that this is not an audio player
56022   *
56023   * @return {boolean|undefined}
56024   *         - The current value of isAudio when getting
56025   *         - Nothing when setting
56026   */
56027  isAudio(bool) {
56028    if (bool !== undefined) {
56029      this.isAudio_ = !!bool;
56030      return;
56031    }
56032    return !!this.isAudio_;
56033  }
56034  updatePlayerHeightOnAudioOnlyMode_() {
56035    const controlBar = this.getChild('ControlBar');
56036    if (!controlBar || this.audioOnlyCache_.controlBarHeight === controlBar.currentHeight()) {
56037      return;
56038    }
56039    this.audioOnlyCache_.controlBarHeight = controlBar.currentHeight();
56040    this.height(this.audioOnlyCache_.controlBarHeight);
56041  }
56042  enableAudioOnlyUI_() {
56043    // Update styling immediately to show the control bar so we can get its height
56044    this.addClass('vjs-audio-only-mode');
56045    const playerChildren = this.children();
56046    const controlBar = this.getChild('ControlBar');
56047    const controlBarHeight = controlBar && controlBar.currentHeight();
56048
56049    // Hide all player components except the control bar. Control bar components
56050    // needed only for video are hidden with CSS
56051    playerChildren.forEach(child => {
56052      if (child === controlBar) {
56053        return;
56054      }
56055      if (child.el_ && !child.hasClass('vjs-hidden')) {
56056        child.hide();
56057        this.audioOnlyCache_.hiddenChildren.push(child);
56058      }
56059    });
56060    this.audioOnlyCache_.playerHeight = this.currentHeight();
56061    this.audioOnlyCache_.controlBarHeight = controlBarHeight;
56062    this.on('playerresize', this.boundUpdatePlayerHeightOnAudioOnlyMode_);
56063
56064    // Set the player height the same as the control bar
56065    this.height(controlBarHeight);
56066    this.trigger('audioonlymodechange');
56067  }
56068  disableAudioOnlyUI_() {
56069    this.removeClass('vjs-audio-only-mode');
56070    this.off('playerresize', this.boundUpdatePlayerHeightOnAudioOnlyMode_);
56071
56072    // Show player components that were previously hidden
56073    this.audioOnlyCache_.hiddenChildren.forEach(child => child.show());
56074
56075    // Reset player height
56076    this.height(this.audioOnlyCache_.playerHeight);
56077    this.trigger('audioonlymodechange');
56078  }
56079
56080  /**
56081   * Get the current audioOnlyMode state or set audioOnlyMode to true or false.
56082   *
56083   * Setting this to `true` will hide all player components except the control bar,
56084   * as well as control bar components needed only for video.
56085   *
56086   * @param {boolean} [value]
56087   *         The value to set audioOnlyMode to.
56088   *
56089   * @return {Promise|boolean}
56090   *        A Promise is returned when setting the state, and a boolean when getting
56091   *        the present state
56092   */
56093  audioOnlyMode(value) {
56094    if (typeof value !== 'boolean' || value === this.audioOnlyMode_) {
56095      return this.audioOnlyMode_;
56096    }
56097    this.audioOnlyMode_ = value;
56098
56099    // Enable Audio Only Mode
56100    if (value) {
56101      const exitPromises = [];
56102
56103      // Fullscreen and PiP are not supported in audioOnlyMode, so exit if we need to.
56104      if (this.isInPictureInPicture()) {
56105        exitPromises.push(this.exitPictureInPicture());
56106      }
56107      if (this.isFullscreen()) {
56108        exitPromises.push(this.exitFullscreen());
56109      }
56110      if (this.audioPosterMode()) {
56111        exitPromises.push(this.audioPosterMode(false));
56112      }
56113      return Promise.all(exitPromises).then(() => this.enableAudioOnlyUI_());
56114    }
56115
56116    // Disable Audio Only Mode
56117    return Promise.resolve().then(() => this.disableAudioOnlyUI_());
56118  }
56119  enablePosterModeUI_() {
56120    // Hide the video element and show the poster image to enable posterModeUI
56121    const tech = this.tech_ && this.tech_;
56122    tech.hide();
56123    this.addClass('vjs-audio-poster-mode');
56124    this.trigger('audiopostermodechange');
56125  }
56126  disablePosterModeUI_() {
56127    // Show the video element and hide the poster image to disable posterModeUI
56128    const tech = this.tech_ && this.tech_;
56129    tech.show();
56130    this.removeClass('vjs-audio-poster-mode');
56131    this.trigger('audiopostermodechange');
56132  }
56133
56134  /**
56135   * Get the current audioPosterMode state or set audioPosterMode to true or false
56136   *
56137   * @param {boolean} [value]
56138   *         The value to set audioPosterMode to.
56139   *
56140   * @return {Promise|boolean}
56141   *         A Promise is returned when setting the state, and a boolean when getting
56142   *        the present state
56143   */
56144  audioPosterMode(value) {
56145    if (typeof value !== 'boolean' || value === this.audioPosterMode_) {
56146      return this.audioPosterMode_;
56147    }
56148    this.audioPosterMode_ = value;
56149    if (value) {
56150      if (this.audioOnlyMode()) {
56151        const audioOnlyModePromise = this.audioOnlyMode(false);
56152        return audioOnlyModePromise.then(() => {
56153          // enable audio poster mode after audio only mode is disabled
56154          this.enablePosterModeUI_();
56155        });
56156      }
56157      return Promise.resolve().then(() => {
56158        // enable audio poster mode
56159        this.enablePosterModeUI_();
56160      });
56161    }
56162    return Promise.resolve().then(() => {
56163      // disable audio poster mode
56164      this.disablePosterModeUI_();
56165    });
56166  }
56167
56168  /**
56169   * A helper method for adding a {@link TextTrack} to our
56170   * {@link TextTrackList}.
56171   *
56172   * In addition to the W3C settings we allow adding additional info through options.
56173   *
56174   * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
56175   *
56176   * @param {string} [kind]
56177   *        the kind of TextTrack you are adding
56178   *
56179   * @param {string} [label]
56180   *        the label to give the TextTrack label
56181   *
56182   * @param {string} [language]
56183   *        the language to set on the TextTrack
56184   *
56185   * @return {TextTrack|undefined}
56186   *         the TextTrack that was added or undefined
56187   *         if there is no tech
56188   */
56189  addTextTrack(kind, label, language) {
56190    if (this.tech_) {
56191      return this.tech_.addTextTrack(kind, label, language);
56192    }
56193  }
56194
56195  /**
56196   * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}.
56197   *
56198   * @param {Object} options
56199   *        Options to pass to {@link HTMLTrackElement} during creation. See
56200   *        {@link HTMLTrackElement} for object properties that you should use.
56201   *
56202   * @param {boolean} [manualCleanup=false] if set to true, the TextTrack will not be removed
56203   *                                        from the TextTrackList and HtmlTrackElementList
56204   *                                        after a source change
56205   *
56206   * @return {HtmlTrackElement}
56207   *         the HTMLTrackElement that was created and added
56208   *         to the HtmlTrackElementList and the remote
56209   *         TextTrackList
56210   *
56211   */
56212  addRemoteTextTrack(options, manualCleanup) {
56213    if (this.tech_) {
56214      return this.tech_.addRemoteTextTrack(options, manualCleanup);
56215    }
56216  }
56217
56218  /**
56219   * Remove a remote {@link TextTrack} from the respective
56220   * {@link TextTrackList} and {@link HtmlTrackElementList}.
56221   *
56222   * @param {Object} track
56223   *        Remote {@link TextTrack} to remove
56224   *
56225   * @return {undefined}
56226   *         does not return anything
56227   */
56228  removeRemoteTextTrack(obj = {}) {
56229    let {
56230      track
56231    } = obj;
56232    if (!track) {
56233      track = obj;
56234    }
56235
56236    // destructure the input into an object with a track argument, defaulting to arguments[0]
56237    // default the whole argument to an empty object if nothing was passed in
56238
56239    if (this.tech_) {
56240      return this.tech_.removeRemoteTextTrack(track);
56241    }
56242  }
56243
56244  /**
56245   * Gets available media playback quality metrics as specified by the W3C's Media
56246   * Playback Quality API.
56247   *
56248   * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
56249   *
56250   * @return {Object|undefined}
56251   *         An object with supported media playback quality metrics or undefined if there
56252   *         is no tech or the tech does not support it.
56253   */
56254  getVideoPlaybackQuality() {
56255    return this.techGet_('getVideoPlaybackQuality');
56256  }
56257
56258  /**
56259   * Get video width
56260   *
56261   * @return {number}
56262   *         current video width
56263   */
56264  videoWidth() {
56265    return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
56266  }
56267
56268  /**
56269   * Get video height
56270   *
56271   * @return {number}
56272   *         current video height
56273   */
56274  videoHeight() {
56275    return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
56276  }
56277
56278  /**
vendor: 3,473 bytes, lines 56279-56392
56279   * Set or get the player's language code.
56280   *
56281   * Changing the language will trigger
56282   * [languagechange]{@link Player#event:languagechange}
56283   * which Components can use to update control text.
56284   * ClickableComponent will update its control text by default on
56285   * [languagechange]{@link Player#event:languagechange}.
56286   *
56287   * @fires Player#languagechange
56288   *
56289   * @param {string} [code]
56290   *        the language code to set the player to
56291   *
56292   * @return {string|undefined}
56293   *         - The current language code when getting
56294   *         - Nothing when setting
56295   */
56296  language(code) {
56297    if (code === undefined) {
56298      return this.language_;
56299    }
56300    if (this.language_ !== String(code).toLowerCase()) {
56301      this.language_ = String(code).toLowerCase();
56302
56303      // during first init, it's possible some things won't be evented
56304      if (isEvented(this)) {
56305        /**
56306        * fires when the player language change
56307        *
56308        * @event Player#languagechange
56309        * @type {Event}
56310        */
56311        this.trigger('languagechange');
56312      }
56313    }
56314  }
56315
56316  /**
56317   * Get the player's language dictionary
56318   * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
56319   * Languages specified directly in the player options have precedence
56320   *
56321   * @return {Array}
56322   *         An array of of supported languages
56323   */
56324  languages() {
56325    return merge$1(Player.prototype.options_.languages, this.languages_);
56326  }
56327
56328  /**
56329   * returns a JavaScript object representing the current track
56330   * information. **DOES not return it as JSON**
56331   *
56332   * @return {Object}
56333   *         Object representing the current of track info
56334   */
56335  toJSON() {
56336    const options = merge$1(this.options_);
56337    const tracks = options.tracks;
56338    options.tracks = [];
56339    for (let i = 0; i < tracks.length; i++) {
56340      let track = tracks[i];
56341
56342      // deep merge tracks and null out player so no circular references
56343      track = merge$1(track);
56344      track.player = undefined;
56345      options.tracks[i] = track;
56346    }
56347    return options;
56348  }
56349
56350  /**
56351   * Creates a simple modal dialog (an instance of the {@link ModalDialog}
56352   * component) that immediately overlays the player with arbitrary
56353   * content and removes itself when closed.
56354   *
56355   * @param {string|Function|Element|Array|null} content
56356   *        Same as {@link ModalDialog#content}'s param of the same name.
56357   *        The most straight-forward usage is to provide a string or DOM
56358   *        element.
56359   *
56360   * @param {Object} [options]
56361   *        Extra options which will be passed on to the {@link ModalDialog}.
56362   *
56363   * @return {ModalDialog}
56364   *         the {@link ModalDialog} that was created
56365   */
56366  createModal(content, options) {
56367    options = options || {};
56368    options.content = content || '';
56369    const modal = new ModalDialog(this, options);
56370    this.addChild(modal);
56371    modal.on('dispose', () => {
56372      this.removeChild(modal);
56373    });
56374    modal.open();
56375    return modal;
56376  }
56377
56378  /**
56379   * Change breakpoint classes when the player resizes.
56380   *
56381   * @private
56382   */
56383  updateCurrentBreakpoint_() {
56384    if (!this.responsive()) {
56385      return;
56386    }
56387    const currentBreakpoint = this.currentBreakpoint();
56388    const currentWidth = this.currentWidth();
56389    for (let i = 0; i < BREAKPOINT_ORDER.length; i++) {
56390      const candidateBreakpoint = BREAKPOINT_ORDER[i];
56391      const maxWidth = this.breakpoints_[candidateBreakpoint];
56392      if (currentWidth <= maxWidth) {
vendor: 5,641 bytes, lines 56393-56566
56393        // The current breakpoint did not change, nothing to do.
56394        if (currentBreakpoint === candidateBreakpoint) {
56395          return;
56396        }
56397
56398        // Only remove a class if there is a current breakpoint.
56399        if (currentBreakpoint) {
56400          this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
56401        }
56402        this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
56403        this.breakpoint_ = candidateBreakpoint;
56404        break;
56405      }
56406    }
56407  }
56408
56409  /**
56410   * Removes the current breakpoint.
56411   *
56412   * @private
56413   */
56414  removeCurrentBreakpoint_() {
56415    const className = this.currentBreakpointClass();
56416    this.breakpoint_ = '';
56417    if (className) {
56418      this.removeClass(className);
56419    }
56420  }
56421
56422  /**
56423   * Get or set breakpoints on the player.
56424   *
56425   * Calling this method with an object or `true` will remove any previous
56426   * custom breakpoints and start from the defaults again.
56427   *
56428   * @param  {Object|boolean} [breakpoints]
56429   *         If an object is given, it can be used to provide custom
56430   *         breakpoints. If `true` is given, will set default breakpoints.
56431   *         If this argument is not given, will simply return the current
56432   *         breakpoints.
56433   *
56434   * @param  {number} [breakpoints.tiny]
56435   *         The maximum width for the "vjs-layout-tiny" class.
56436   *
56437   * @param  {number} [breakpoints.xsmall]
56438   *         The maximum width for the "vjs-layout-x-small" class.
56439   *
56440   * @param  {number} [breakpoints.small]
56441   *         The maximum width for the "vjs-layout-small" class.
56442   *
56443   * @param  {number} [breakpoints.medium]
56444   *         The maximum width for the "vjs-layout-medium" class.
56445   *
56446   * @param  {number} [breakpoints.large]
56447   *         The maximum width for the "vjs-layout-large" class.
56448   *
56449   * @param  {number} [breakpoints.xlarge]
56450   *         The maximum width for the "vjs-layout-x-large" class.
56451   *
56452   * @param  {number} [breakpoints.huge]
56453   *         The maximum width for the "vjs-layout-huge" class.
56454   *
56455   * @return {Object}
56456   *         An object mapping breakpoint names to maximum width values.
56457   */
56458  breakpoints(breakpoints) {
56459    // Used as a getter.
56460    if (breakpoints === undefined) {
56461      return Object.assign(this.breakpoints_);
56462    }
56463    this.removeCurrentBreakpoint_();
56464    this.breakpoints_ = Object.assign({}, DEFAULT_BREAKPOINTS, breakpoints);
56465
56466    // When breakpoint definitions change, we need to update the currently
56467    // selected breakpoint.
56468    this.updateCurrentBreakpoint_();
56469
56470    // Clone the breakpoints before returning.
56471    return Object.assign(this.breakpoints_);
56472  }
56473
56474  /**
56475   * Get or set a flag indicating whether or not this player should adjust
56476   * its UI based on its dimensions.
56477   *
56478   * @param  {boolean} [value]
56479   *         Should be `true` if the player should adjust its UI based on its
56480   *         dimensions; otherwise, should be `false`.
56481   *
56482   * @return {boolean|undefined}
56483   *         Will be `true` if this player should adjust its UI based on its
56484   *         dimensions; otherwise, will be `false`.
56485   *         Nothing if setting
56486   */
56487  responsive(value) {
56488    // Used as a getter.
56489    if (value === undefined) {
56490      return this.responsive_;
56491    }
56492    value = Boolean(value);
56493    const current = this.responsive_;
56494
56495    // Nothing changed.
56496    if (value === current) {
56497      return;
56498    }
56499
56500    // The value actually changed, set it.
56501    this.responsive_ = value;
56502
56503    // Start listening for breakpoints and set the initial breakpoint if the
56504    // player is now responsive.
56505    if (value) {
56506      this.on('playerresize', this.boundUpdateCurrentBreakpoint_);
56507      this.updateCurrentBreakpoint_();
56508
56509      // Stop listening for breakpoints if the player is no longer responsive.
56510    } else {
56511      this.off('playerresize', this.boundUpdateCurrentBreakpoint_);
56512      this.removeCurrentBreakpoint_();
56513    }
56514    return value;
56515  }
56516
56517  /**
56518   * Get current breakpoint name, if any.
56519   *
56520   * @return {string}
56521   *         If there is currently a breakpoint set, returns a the key from the
56522   *         breakpoints object matching it. Otherwise, returns an empty string.
56523   */
56524  currentBreakpoint() {
56525    return this.breakpoint_;
56526  }
56527
56528  /**
56529   * Get the current breakpoint class name.
56530   *
56531   * @return {string}
56532   *         The matching class name (e.g. `"vjs-layout-tiny"` or
56533   *         `"vjs-layout-large"`) for the current breakpoint. Empty string if
56534   *         there is no current breakpoint.
56535   */
56536  currentBreakpointClass() {
56537    return BREAKPOINT_CLASSES[this.breakpoint_] || '';
56538  }
56539
56540  /**
56541   * An object that describes a single piece of media.
56542   *
56543   * Properties that are not part of this type description will be retained; so,
56544   * this can be viewed as a generic metadata storage mechanism as well.
56545   *
56546   * @see      {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
56547   * @typedef  {Object} Player~MediaObject
56548   *
56549   * @property {string} [album]
56550   *           Unused, except if this object is passed to the `MediaSession`
56551   *           API.
56552   *
56553   * @property {string} [artist]
56554   *           Unused, except if this object is passed to the `MediaSession`
56555   *           API.
56556   *
56557   * @property {Object[]} [artwork]
56558   *           Unused, except if this object is passed to the `MediaSession`
56559   *           API. If not specified, will be populated via the `poster`, if
56560   *           available.
56561   *
56562   * @property {string} [poster]
56563   *           URL to an image that will display before playback.
56564   *
56565   * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
56566   *           A single source object, an array of source objects, or a string
vendor: 5,208 bytes, lines 56567-56749
56567   *           referencing a URL to a media source. It is _highly recommended_
56568   *           that an object or array of objects is used here, so that source
56569   *           selection algorithms can take the `type` into account.
56570   *
56571   * @property {string} [title]
56572   *           Unused, except if this object is passed to the `MediaSession`
56573   *           API.
56574   *
56575   * @property {Object[]} [textTracks]
56576   *           An array of objects to be used to create text tracks, following
56577   *           the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}.
56578   *           For ease of removal, these will be created as "remote" text
56579   *           tracks and set to automatically clean up on source changes.
56580   *
56581   *           These objects may have properties like `src`, `kind`, `label`,
56582   *           and `language`, see {@link Tech#createRemoteTextTrack}.
56583   */
56584
56585  /**
56586   * Populate the player using a {@link Player~MediaObject|MediaObject}.
56587   *
56588   * @param  {Player~MediaObject} media
56589   *         A media object.
56590   *
56591   * @param  {Function} ready
56592   *         A callback to be called when the player is ready.
56593   */
56594  loadMedia(media, ready) {
56595    if (!media || typeof media !== 'object') {
56596      return;
56597    }
56598    const crossOrigin = this.crossOrigin();
56599    this.reset();
56600
56601    // Clone the media object so it cannot be mutated from outside.
56602    this.cache_.media = merge$1(media);
56603    const {
56604      artist,
56605      artwork,
56606      description,
56607      poster,
56608      src,
56609      textTracks,
56610      title
56611    } = this.cache_.media;
56612
56613    // If `artwork` is not given, create it using `poster`.
56614    if (!artwork && poster) {
56615      this.cache_.media.artwork = [{
56616        src: poster,
56617        type: getMimetype(poster)
56618      }];
56619    }
56620    if (crossOrigin) {
56621      this.crossOrigin(crossOrigin);
56622    }
56623    if (src) {
56624      this.src(src);
56625    }
56626    if (poster) {
56627      this.poster(poster);
56628    }
56629    if (Array.isArray(textTracks)) {
56630      textTracks.forEach(tt => this.addRemoteTextTrack(tt, false));
56631    }
56632    if (this.titleBar) {
56633      this.titleBar.update({
56634        title,
56635        description: description || artist || ''
56636      });
56637    }
56638    this.ready(ready);
56639  }
56640
56641  /**
56642   * Get a clone of the current {@link Player~MediaObject} for this player.
56643   *
56644   * If the `loadMedia` method has not been used, will attempt to return a
56645   * {@link Player~MediaObject} based on the current state of the player.
56646   *
56647   * @return {Player~MediaObject}
56648   */
56649  getMedia() {
56650    if (!this.cache_.media) {
56651      const poster = this.poster();
56652      const src = this.currentSources();
56653      const textTracks = Array.prototype.map.call(this.remoteTextTracks(), tt => ({
56654        kind: tt.kind,
56655        label: tt.label,
56656        language: tt.language,
56657        src: tt.src
56658      }));
56659      const media = {
56660        src,
56661        textTracks
56662      };
56663      if (poster) {
56664        media.poster = poster;
56665        media.artwork = [{
56666          src: media.poster,
56667          type: getMimetype(media.poster)
56668        }];
56669      }
56670      return media;
56671    }
56672    return merge$1(this.cache_.media);
56673  }
56674
56675  /**
56676   * Gets tag settings
56677   *
56678   * @param {Element} tag
56679   *        The player tag
56680   *
56681   * @return {Object}
56682   *         An object containing all of the settings
56683   *         for a player tag
56684   */
56685  static getTagSettings(tag) {
56686    const baseOptions = {
56687      sources: [],
56688      tracks: []
56689    };
56690    const tagOptions = getAttributes(tag);
56691    const dataSetup = tagOptions['data-setup'];
56692    if (hasClass(tag, 'vjs-fill')) {
56693      tagOptions.fill = true;
56694    }
56695    if (hasClass(tag, 'vjs-fluid')) {
56696      tagOptions.fluid = true;
56697    }
56698
56699    // Check if data-setup attr exists.
56700    if (dataSetup !== null) {
56701      // Parse options JSON
56702      try {
56703        // If empty string, make it a parsable json object.
56704        Object.assign(tagOptions, JSON.parse(dataSetup || '{}'));
56705      } catch (e) {
56706        log$1.error('data-setup', e);
56707      }
56708    }
56709    Object.assign(baseOptions, tagOptions);
56710
56711    // Get tag children settings
56712    if (tag.hasChildNodes()) {
56713      const children = tag.childNodes;
56714      for (let i = 0, j = children.length; i < j; i++) {
56715        const child = children[i];
56716        // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
56717        const childName = child.nodeName.toLowerCase();
56718        if (childName === 'source') {
56719          baseOptions.sources.push(getAttributes(child));
56720        } else if (childName === 'track') {
56721          baseOptions.tracks.push(getAttributes(child));
56722        }
56723      }
56724    }
56725    return baseOptions;
56726  }
56727
56728  /**
56729   * Set debug mode to enable/disable logs at info level.
56730   *
56731   * @param {boolean} enabled
56732   * @fires Player#debugon
56733   * @fires Player#debugoff
56734   * @return {boolean|undefined}
56735   */
56736  debug(enabled) {
56737    if (enabled === undefined) {
56738      return this.debugEnabled_;
56739    }
56740    if (enabled) {
56741      this.trigger('debugon');
56742      this.previousLogLevel_ = this.log.level;
56743      this.log.level('debug');
56744      this.debugEnabled_ = true;
56745    } else {
56746      this.trigger('debugoff');
56747      this.log.level(this.previousLogLevel_);
56748      this.previousLogLevel_ = undefined;
56749      this.debugEnabled_ = false;
vendor: 5,192 bytes, lines 56750-56935
56750    }
56751  }
56752
56753  /**
56754   * Set or get current playback rates.
56755   * Takes an array and updates the playback rates menu with the new items.
56756   * Pass in an empty array to hide the menu.
56757   * Values other than arrays are ignored.
56758   *
56759   * @fires Player#playbackrateschange
56760   * @param {number[]} [newRates]
56761   *                   The new rates that the playback rates menu should update to.
56762   *                   An empty array will hide the menu
56763   * @return {number[]} When used as a getter will return the current playback rates
56764   */
56765  playbackRates(newRates) {
56766    if (newRates === undefined) {
56767      return this.cache_.playbackRates;
56768    }
56769
56770    // ignore any value that isn't an array
56771    if (!Array.isArray(newRates)) {
56772      return;
56773    }
56774
56775    // ignore any arrays that don't only contain numbers
56776    if (!newRates.every(rate => typeof rate === 'number')) {
56777      return;
56778    }
56779    this.cache_.playbackRates = newRates;
56780
56781    /**
56782    * fires when the playback rates in a player are changed
56783    *
56784    * @event Player#playbackrateschange
56785    * @type {Event}
56786    */
56787    this.trigger('playbackrateschange');
56788  }
56789
56790  /**
56791   * Reports whether or not a player has a plugin available.
56792   *
56793   * This does not report whether or not the plugin has ever been initialized
56794   * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
56795   *
56796   * @method hasPlugin
56797   * @param  {string}  name
56798   *         The name of a plugin.
56799   *
56800   * @return {boolean}
56801   *         Whether or not this player has the requested plugin available.
56802   */
56803
56804  /**
56805   * Reports whether or not a player is using a plugin by name.
56806   *
56807   * For basic plugins, this only reports whether the plugin has _ever_ been
56808   * initialized on this player.
56809   *
56810   * @method Player#usingPlugin
56811   * @param  {string} name
56812   *         The name of a plugin.
56813   *
56814   * @return {boolean}
56815   *         Whether or not this player is using the requested plugin.
56816   */
56817}
56818
56819/**
56820 * Get the {@link VideoTrackList}
56821 *
56822 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
56823 *
56824 * @return {VideoTrackList}
56825 *         the current video track list
56826 *
56827 * @method Player.prototype.videoTracks
56828 */
56829Player.prototype.videoTracks = () => {};
56830
56831/**
56832 * Get the {@link AudioTrackList}
56833 *
56834 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
56835 *
56836 * @return {AudioTrackList}
56837 *         the current audio track list
56838 *
56839 * @method Player.prototype.audioTracks
56840 */
56841Player.prototype.audioTracks = () => {};
56842
56843/**
56844 * Get the {@link TextTrackList}
56845 *
56846 * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
56847 *
56848 * @return {TextTrackList}
56849 *         the current text track list
56850 *
56851 * @method Player.prototype.textTracks
56852 */
56853Player.prototype.textTracks = () => {};
56854
56855/**
56856 * Get the remote {@link TextTrackList}
56857 *
56858 * @return {TextTrackList}
56859 *         The current remote text track list
56860 *
56861 * @method Player.prototype.remoteTextTracks
56862 */
56863Player.prototype.remoteTextTracks = () => {};
56864
56865/**
56866 * Get the remote {@link HtmlTrackElementList} tracks.
56867 *
56868 * @return {HtmlTrackElementList}
56869 *         The current remote text track element list
56870 *
56871 * @method Player.prototype.remoteTextTrackEls
56872 */
56873Player.prototype.remoteTextTrackEls = () => {};
56874ALL.names.forEach(function (name) {
56875  const props = ALL[name];
56876  Player.prototype[props.getterName] = function () {
56877    if (this.tech_) {
56878      return this.tech_[props.getterName]();
56879    }
56880
56881    // if we have not yet loadTech_, we create {video,audio,text}Tracks_
56882    // these will be passed to the tech during loading
56883    this[props.privateName] = this[props.privateName] || new props.ListClass();
56884    return this[props.privateName];
56885  };
56886});
56887
56888/**
56889 * Get or set the `Player`'s crossorigin option. For the HTML5 player, this
56890 * sets the `crossOrigin` property on the `<video>` tag to control the CORS
56891 * behavior.
56892 *
56893 * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
56894 *
56895 * @param {string} [value]
56896 *        The value to set the `Player`'s crossorigin to. If an argument is
56897 *        given, must be one of `anonymous` or `use-credentials`.
56898 *
56899 * @return {string|undefined}
56900 *         - The current crossorigin value of the `Player` when getting.
56901 *         - undefined when setting
56902 */
56903Player.prototype.crossorigin = Player.prototype.crossOrigin;
56904
56905/**
56906 * Global enumeration of players.
56907 *
56908 * The keys are the player IDs and the values are either the {@link Player}
56909 * instance or `null` for disposed players.
56910 *
56911 * @type {Object}
56912 */
56913Player.players = {};
56914const navigator = window__default["default"].navigator;
56915
56916/*
56917 * Player instance options, surfaced using options
56918 * options = Player.prototype.options_
56919 * Make changes in options, not here.
56920 *
56921 * @type {Object}
56922 * @private
56923 */
56924Player.prototype.options_ = {
56925  // Default order of fallback technology
56926  techOrder: Tech.defaultTechOrder_,
56927  html5: {},
56928  // enable sourceset by default
56929  enableSourceset: true,
56930  // default inactivity timeout
56931  inactivityTimeout: 2000,
56932  // default playback rates
56933  playbackRates: [],
56934  // Add playback rate selection by adding rates
56935  // 'playbackRates': [0.5, 1, 1.5, 2],
vendor: 4,897 bytes, lines 56936-57127
56936  liveui: false,
56937  // Included control sets
56938  children: ['mediaLoader', 'posterImage', 'titleBar', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
56939  language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
56940  // locales and their language translations
56941  languages: {},
56942  // Default message to show when a video cannot be played.
56943  notSupportedMessage: 'No compatible source was found for this media.',
56944  normalizeAutoplay: false,
56945  fullscreen: {
56946    options: {
56947      navigationUI: 'hide'
56948    }
56949  },
56950  breakpoints: {},
56951  responsive: false,
56952  audioOnlyMode: false,
56953  audioPosterMode: false,
56954  spatialNavigation: {
56955    enabled: false,
56956    horizontalSeek: false
56957  },
56958  // Default smooth seeking to false
56959  enableSmoothSeeking: false,
56960  disableSeekWhileScrubbingOnMobile: false,
56961  disableSeekWhileScrubbingOnSTV: false
56962};
56963TECH_EVENTS_RETRIGGER.forEach(function (event) {
56964  Player.prototype[`handleTech${toTitleCase$1(event)}_`] = function () {
56965    return this.trigger(event);
56966  };
56967});
56968
56969/**
56970 * Fired when the player has initial duration and dimension information
56971 *
56972 * @event Player#loadedmetadata
56973 * @type {Event}
56974 */
56975
56976/**
56977 * Fired when the player has downloaded data at the current playback position
56978 *
56979 * @event Player#loadeddata
56980 * @type {Event}
56981 */
56982
56983/**
56984 * Fired when the current playback position has changed *
56985 * During playback this is fired every 15-250 milliseconds, depending on the
56986 * playback technology in use.
56987 *
56988 * @event Player#timeupdate
56989 * @type {Event}
56990 */
56991
56992/**
56993 * Fired when the volume changes
56994 *
56995 * @event Player#volumechange
56996 * @type {Event}
56997 */
56998
56999Component$1.registerComponent('Player', Player);
57000
57001/**
57002 * @file plugin.js
57003 */
57004
57005/**
57006 * The base plugin name.
57007 *
57008 * @private
57009 * @constant
57010 * @type {string}
57011 */
57012const BASE_PLUGIN_NAME = 'plugin';
57013
57014/**
57015 * The key on which a player's active plugins cache is stored.
57016 *
57017 * @private
57018 * @constant
57019 * @type     {string}
57020 */
57021const PLUGIN_CACHE_KEY = 'activePlugins_';
57022
57023/**
57024 * Stores registered plugins in a private space.
57025 *
57026 * @private
57027 * @type    {Object}
57028 */
57029const pluginStorage = {};
57030
57031/**
57032 * Reports whether or not a plugin has been registered.
57033 *
57034 * @private
57035 * @param   {string} name
57036 *          The name of a plugin.
57037 *
57038 * @return {boolean}
57039 *          Whether or not the plugin has been registered.
57040 */
57041const pluginExists = name => pluginStorage.hasOwnProperty(name);
57042
57043/**
57044 * Get a single registered plugin by name.
57045 *
57046 * @private
57047 * @param   {string} name
57048 *          The name of a plugin.
57049 *
57050 * @return {typeof Plugin|Function|undefined}
57051 *          The plugin (or undefined).
57052 */
57053const getPlugin = name => pluginExists(name) ? pluginStorage[name] : undefined;
57054
57055/**
57056 * Marks a plugin as "active" on a player.
57057 *
57058 * Also, ensures that the player has an object for tracking active plugins.
57059 *
57060 * @private
57061 * @param   {Player} player
57062 *          A Video.js player instance.
57063 *
57064 * @param   {string} name
57065 *          The name of a plugin.
57066 */
57067const markPluginAsActive = (player, name) => {
57068  player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
57069  player[PLUGIN_CACHE_KEY][name] = true;
57070};
57071
57072/**
57073 * Triggers a pair of plugin setup events.
57074 *
57075 * @private
57076 * @param  {Player} player
57077 *         A Video.js player instance.
57078 *
57079 * @param  {PluginEventHash} hash
57080 *         A plugin event hash.
57081 *
57082 * @param  {boolean} [before]
57083 *         If true, prefixes the event name with "before". In other words,
57084 *         use this to trigger "beforepluginsetup" instead of "pluginsetup".
57085 */
57086const triggerSetupEvent = (player, hash, before) => {
57087  const eventName = (before ? 'before' : '') + 'pluginsetup';
57088  player.trigger(eventName, hash);
57089  player.trigger(eventName + ':' + hash.name, hash);
57090};
57091
57092/**
57093 * Takes a basic plugin function and returns a wrapper function which marks
57094 * on the player that the plugin has been activated.
57095 *
57096 * @private
57097 * @param   {string} name
57098 *          The name of the plugin.
57099 *
57100 * @param   {Function} plugin
57101 *          The basic plugin.
57102 *
57103 * @return {Function}
57104 *          A wrapper function for the given plugin.
57105 */
57106const createBasicPlugin = function (name, plugin) {
57107  const basicPluginWrapper = function () {
57108    // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
57109    // regardless, but we want the hash to be consistent with the hash provided
57110    // for advanced plugins.
57111    //
57112    // The only potentially counter-intuitive thing here is the `instance` in
57113    // the "pluginsetup" event is the value returned by the `plugin` function.
57114    triggerSetupEvent(this, {
57115      name,
57116      plugin,
57117      instance: null
57118    }, true);
57119    const instance = plugin.apply(this, arguments);
57120    markPluginAsActive(this, name);
57121    triggerSetupEvent(this, {
57122      name,
57123      plugin,
57124      instance
57125    });
57126    return instance;
57127  };
vendor: 4,950 bytes, lines 57128-57301
57128  Object.keys(plugin).forEach(function (prop) {
57129    basicPluginWrapper[prop] = plugin[prop];
57130  });
57131  return basicPluginWrapper;
57132};
57133
57134/**
57135 * Takes a plugin sub-class and returns a factory function for generating
57136 * instances of it.
57137 *
57138 * This factory function will replace itself with an instance of the requested
57139 * sub-class of Plugin.
57140 *
57141 * @private
57142 * @param   {string} name
57143 *          The name of the plugin.
57144 *
57145 * @param   {Plugin} PluginSubClass
57146 *          The advanced plugin.
57147 *
57148 * @return {Function}
57149 */
57150const createPluginFactory = (name, PluginSubClass) => {
57151  // Add a `name` property to the plugin prototype so that each plugin can
57152  // refer to itself by name.
57153  PluginSubClass.prototype.name = name;
57154  return function (...args) {
57155    triggerSetupEvent(this, {
57156      name,
57157      plugin: PluginSubClass,
57158      instance: null
57159    }, true);
57160    const instance = new PluginSubClass(...[this, ...args]);
57161
57162    // The plugin is replaced by a function that returns the current instance.
57163    this[name] = () => instance;
57164    triggerSetupEvent(this, instance.getEventHash());
57165    return instance;
57166  };
57167};
57168
57169/**
57170 * Parent class for all advanced plugins.
57171 *
57172 * @mixes   module:evented~EventedMixin
57173 * @mixes   module:stateful~StatefulMixin
57174 * @fires   Player#beforepluginsetup
57175 * @fires   Player#beforepluginsetup:$name
57176 * @fires   Player#pluginsetup
57177 * @fires   Player#pluginsetup:$name
57178 * @listens Player#dispose
57179 * @throws  {Error}
57180 *          If attempting to instantiate the base {@link Plugin} class
57181 *          directly instead of via a sub-class.
57182 */
57183class Plugin {
57184  /**
57185   * Creates an instance of this class.
57186   *
57187   * Sub-classes should call `super` to ensure plugins are properly initialized.
57188   *
57189   * @param {Player} player
57190   *        A Video.js player instance.
57191   */
57192  constructor(player) {
57193    if (this.constructor === Plugin) {
57194      throw new Error('Plugin must be sub-classed; not directly instantiated.');
57195    }
57196    this.player = player;
57197    if (!this.log) {
57198      this.log = this.player.log.createLogger(this.name);
57199    }
57200
57201    // Make this object evented, but remove the added `trigger` method so we
57202    // use the prototype version instead.
57203    evented(this);
57204    delete this.trigger;
57205    stateful(this, this.constructor.defaultState);
57206    markPluginAsActive(player, this.name);
57207
57208    // Auto-bind the dispose method so we can use it as a listener and unbind
57209    // it later easily.
57210    this.dispose = this.dispose.bind(this);
57211
57212    // If the player is disposed, dispose the plugin.
57213    player.on('dispose', this.dispose);
57214  }
57215
57216  /**
57217   * Get the version of the plugin that was set on <pluginName>.VERSION
57218   */
57219  version() {
57220    return this.constructor.VERSION;
57221  }
57222
57223  /**
57224   * Each event triggered by plugins includes a hash of additional data with
57225   * conventional properties.
57226   *
57227   * This returns that object or mutates an existing hash.
57228   *
57229   * @param   {Object} [hash={}]
57230   *          An object to be used as event an event hash.
57231   *
57232   * @return {PluginEventHash}
57233   *          An event hash object with provided properties mixed-in.
57234   */
57235  getEventHash(hash = {}) {
57236    hash.name = this.name;
57237    hash.plugin = this.constructor;
57238    hash.instance = this;
57239    return hash;
57240  }
57241
57242  /**
57243   * Triggers an event on the plugin object and overrides
57244   * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
57245   *
57246   * @param   {string|Object} event
57247   *          An event type or an object with a type property.
57248   *
57249   * @param   {Object} [hash={}]
57250   *          Additional data hash to merge with a
57251   *          {@link PluginEventHash|PluginEventHash}.
57252   *
57253   * @return {boolean}
57254   *          Whether or not default was prevented.
57255   */
57256  trigger(event, hash = {}) {
57257    return trigger(this.eventBusEl_, event, this.getEventHash(hash));
57258  }
57259
57260  /**
57261   * Handles "statechanged" events on the plugin. No-op by default, override by
57262   * subclassing.
57263   *
57264   * @abstract
57265   * @param    {Event} e
57266   *           An event object provided by a "statechanged" event.
57267   *
57268   * @param    {Object} e.changes
57269   *           An object describing changes that occurred with the "statechanged"
57270   *           event.
57271   */
57272  handleStateChanged(e) {}
57273
57274  /**
57275   * Disposes a plugin.
57276   *
57277   * Subclasses can override this if they want, but for the sake of safety,
57278   * it's probably best to subscribe the "dispose" event.
57279   *
57280   * @fires Plugin#dispose
57281   */
57282  dispose() {
57283    const {
57284      name,
57285      player
57286    } = this;
57287
57288    /**
57289     * Signals that a advanced plugin is about to be disposed.
57290     *
57291     * @event Plugin#dispose
57292     * @type  {Event}
57293     */
57294    this.trigger('dispose');
57295    this.off();
57296    player.off('dispose', this.dispose);
57297
57298    // Eliminate any possible sources of leaking memory by clearing up
57299    // references between the player and the plugin instance and nulling out
57300    // the plugin's state and replacing methods with a function that throws.
57301    player[PLUGIN_CACHE_KEY][name] = false;
vendor: 10,333 bytes, lines 57302-57643
57302    this.player = this.state = null;
57303
57304    // Finally, replace the plugin name on the player with a new factory
57305    // function, so that the plugin is ready to be set up again.
57306    player[name] = createPluginFactory(name, pluginStorage[name]);
57307  }
57308
57309  /**
57310   * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
57311   *
57312   * @param   {string|Function} plugin
57313   *          If a string, matches the name of a plugin. If a function, will be
57314   *          tested directly.
57315   *
57316   * @return {boolean}
57317   *          Whether or not a plugin is a basic plugin.
57318   */
57319  static isBasic(plugin) {
57320    const p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
57321    return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
57322  }
57323
57324  /**
57325   * Register a Video.js plugin.
57326   *
57327   * @param   {string} name
57328   *          The name of the plugin to be registered. Must be a string and
57329   *          must not match an existing plugin or a method on the `Player`
57330   *          prototype.
57331   *
57332   * @param   {typeof Plugin|Function} plugin
57333   *          A sub-class of `Plugin` or a function for basic plugins.
57334   *
57335   * @return {typeof Plugin|Function}
57336   *          For advanced plugins, a factory function for that plugin. For
57337   *          basic plugins, a wrapper function that initializes the plugin.
57338   */
57339  static registerPlugin(name, plugin) {
57340    if (typeof name !== 'string') {
57341      throw new Error(`Illegal plugin name, "${name}", must be a string, was ${typeof name}.`);
57342    }
57343    if (pluginExists(name)) {
57344      log$1.warn(`A plugin named "${name}" already exists. You may want to avoid re-registering plugins!`);
57345    } else if (Player.prototype.hasOwnProperty(name)) {
57346      throw new Error(`Illegal plugin name, "${name}", cannot share a name with an existing player method!`);
57347    }
57348    if (typeof plugin !== 'function') {
57349      throw new Error(`Illegal plugin for "${name}", must be a function, was ${typeof plugin}.`);
57350    }
57351    pluginStorage[name] = plugin;
57352
57353    // Add a player prototype method for all sub-classed plugins (but not for
57354    // the base Plugin class).
57355    if (name !== BASE_PLUGIN_NAME) {
57356      if (Plugin.isBasic(plugin)) {
57357        Player.prototype[name] = createBasicPlugin(name, plugin);
57358      } else {
57359        Player.prototype[name] = createPluginFactory(name, plugin);
57360      }
57361    }
57362    return plugin;
57363  }
57364
57365  /**
57366   * De-register a Video.js plugin.
57367   *
57368   * @param  {string} name
57369   *         The name of the plugin to be de-registered. Must be a string that
57370   *         matches an existing plugin.
57371   *
57372   * @throws {Error}
57373   *         If an attempt is made to de-register the base plugin.
57374   */
57375  static deregisterPlugin(name) {
57376    if (name === BASE_PLUGIN_NAME) {
57377      throw new Error('Cannot de-register base plugin.');
57378    }
57379    if (pluginExists(name)) {
57380      delete pluginStorage[name];
57381      delete Player.prototype[name];
57382    }
57383  }
57384
57385  /**
57386   * Gets an object containing multiple Video.js plugins.
57387   *
57388   * @param   {Array} [names]
57389   *          If provided, should be an array of plugin names. Defaults to _all_
57390   *          plugin names.
57391   *
57392   * @return {Object|undefined}
57393   *          An object containing plugin(s) associated with their name(s) or
57394   *          `undefined` if no matching plugins exist).
57395   */
57396  static getPlugins(names = Object.keys(pluginStorage)) {
57397    let result;
57398    names.forEach(name => {
57399      const plugin = getPlugin(name);
57400      if (plugin) {
57401        result = result || {};
57402        result[name] = plugin;
57403      }
57404    });
57405    return result;
57406  }
57407
57408  /**
57409   * Gets a plugin's version, if available
57410   *
57411   * @param   {string} name
57412   *          The name of a plugin.
57413   *
57414   * @return {string}
57415   *          The plugin's version or an empty string.
57416   */
57417  static getPluginVersion(name) {
57418    const plugin = getPlugin(name);
57419    return plugin && plugin.VERSION || '';
57420  }
57421}
57422
57423/**
57424 * Gets a plugin by name if it exists.
57425 *
57426 * @static
57427 * @method   getPlugin
57428 * @memberOf Plugin
57429 * @param    {string} name
57430 *           The name of a plugin.
57431 *
57432 * @returns  {typeof Plugin|Function|undefined}
57433 *           The plugin (or `undefined`).
57434 */
57435Plugin.getPlugin = getPlugin;
57436
57437/**
57438 * The name of the base plugin class as it is registered.
57439 *
57440 * @type {string}
57441 */
57442Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
57443Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
57444
57445/**
57446 * Documented in player.js
57447 *
57448 * @ignore
57449 */
57450Player.prototype.usingPlugin = function (name) {
57451  return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
57452};
57453
57454/**
57455 * Documented in player.js
57456 *
57457 * @ignore
57458 */
57459Player.prototype.hasPlugin = function (name) {
57460  return !!pluginExists(name);
57461};
57462
57463/**
57464 * Signals that a plugin is about to be set up on a player.
57465 *
57466 * @event    Player#beforepluginsetup
57467 * @type     {PluginEventHash}
57468 */
57469
57470/**
57471 * Signals that a plugin is about to be set up on a player - by name. The name
57472 * is the name of the plugin.
57473 *
57474 * @event    Player#beforepluginsetup:$name
57475 * @type     {PluginEventHash}
57476 */
57477
57478/**
57479 * Signals that a plugin has just been set up on a player.
57480 *
57481 * @event    Player#pluginsetup
57482 * @type     {PluginEventHash}
57483 */
57484
57485/**
57486 * Signals that a plugin has just been set up on a player - by name. The name
57487 * is the name of the plugin.
57488 *
57489 * @event    Player#pluginsetup:$name
57490 * @type     {PluginEventHash}
57491 */
57492
57493/**
57494 * @typedef  {Object} PluginEventHash
57495 *
57496 * @property {string} instance
57497 *           For basic plugins, the return value of the plugin function. For
57498 *           advanced plugins, the plugin instance on which the event is fired.
57499 *
57500 * @property {string} name
57501 *           The name of the plugin.
57502 *
57503 * @property {string} plugin
57504 *           For basic plugins, the plugin function. For advanced plugins, the
57505 *           plugin class/constructor.
57506 */
57507
57508/**
57509 * @file deprecate.js
57510 * @module deprecate
57511 */
57512
57513/**
57514 * Decorate a function with a deprecation message the first time it is called.
57515 *
57516 * @param  {string}   message
57517 *         A deprecation message to log the first time the returned function
57518 *         is called.
57519 *
57520 * @param  {Function} fn
57521 *         The function to be deprecated.
57522 *
57523 * @return {Function}
57524 *         A wrapper function that will log a deprecation warning the first
57525 *         time it is called. The return value will be the return value of
57526 *         the wrapped function.
57527 */
57528function deprecate(message, fn) {
57529  let warned = false;
57530  return function (...args) {
57531    if (!warned) {
57532      log$1.warn(message);
57533    }
57534    warned = true;
57535    return fn.apply(this, args);
57536  };
57537}
57538
57539/**
57540 * Internal function used to mark a function as deprecated in the next major
57541 * version with consistent messaging.
57542 *
57543 * @param  {number}   major   The major version where it will be removed
57544 * @param  {string}   oldName The old function name
57545 * @param  {string}   newName The new function name
57546 * @param  {Function} fn      The function to deprecate
57547 * @return {Function}         The decorated function
57548 */
57549function deprecateForMajor(major, oldName, newName, fn) {
57550  return deprecate(`${oldName} is deprecated and will be removed in ${major}.0; please use ${newName} instead.`, fn);
57551}
57552
57553var VjsErrors = {
57554  NetworkBadStatus: 'networkbadstatus',
57555  NetworkRequestFailed: 'networkrequestfailed',
57556  NetworkRequestAborted: 'networkrequestaborted',
57557  NetworkRequestTimeout: 'networkrequesttimeout',
57558  NetworkBodyParserFailed: 'networkbodyparserfailed',
57559  StreamingHlsPlaylistParserError: 'streaminghlsplaylistparsererror',
57560  StreamingDashManifestParserError: 'streamingdashmanifestparsererror',
57561  StreamingContentSteeringParserError: 'streamingcontentsteeringparsererror',
57562  StreamingVttParserError: 'streamingvttparsererror',
57563  StreamingFailedToSelectNextSegment: 'streamingfailedtoselectnextsegment',
57564  StreamingFailedToDecryptSegment: 'streamingfailedtodecryptsegment',
57565  StreamingFailedToTransmuxSegment: 'streamingfailedtotransmuxsegment',
57566  StreamingFailedToAppendSegment: 'streamingfailedtoappendsegment',
57567  StreamingCodecsChangeError: 'streamingcodecschangeerror'
57568};
57569
57570/**
57571 * @file video.js
57572 * @module videojs
57573 */
57574
57575/** @import { PlayerReadyCallback } from './player' */
57576
57577/**
57578 * Normalize an `id` value by trimming off a leading `#`
57579 *
57580 * @private
57581 * @param   {string} id
57582 *          A string, maybe with a leading `#`.
57583 *
57584 * @return {string}
57585 *          The string, without any leading `#`.
57586 */
57587const normalizeId = id => id.indexOf('#') === 0 ? id.slice(1) : id;
57588
57589/**
57590 * The `videojs()` function doubles as the main function for users to create a
57591 * {@link Player} instance as well as the main library namespace.
57592 *
57593 * It can also be used as a getter for a pre-existing {@link Player} instance.
57594 * However, we _strongly_ recommend using `videojs.getPlayer()` for this
57595 * purpose because it avoids any potential for unintended initialization.
57596 *
57597 * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
57598 * of our JSDoc template, we cannot properly document this as both a function
57599 * and a namespace, so its function signature is documented here.
57600 *
57601 * #### Arguments
57602 * ##### id
57603 * string|Element, **required**
57604 *
57605 * Video element or video element ID.
57606 *
57607 * ##### options
57608 * Object, optional
57609 *
57610 * Options object for providing settings.
57611 * See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
57612 *
57613 * ##### ready
57614 * {@link Component~ReadyCallback}, optional
57615 *
57616 * A function to be called when the {@link Player} and {@link Tech} are ready.
57617 *
57618 * #### Return Value
57619 *
57620 * The `videojs()` function returns a {@link Player} instance.
57621 *
57622 * @namespace
57623 *
57624 * @borrows AudioTrack as AudioTrack
57625 * @borrows Component.getComponent as getComponent
57626 * @borrows module:events.on as on
57627 * @borrows module:events.one as one
57628 * @borrows module:events.off as off
57629 * @borrows module:events.trigger as trigger
57630 * @borrows EventTarget as EventTarget
57631 * @borrows module:middleware.use as use
57632 * @borrows Player.players as players
57633 * @borrows Plugin.registerPlugin as registerPlugin
57634 * @borrows Plugin.deregisterPlugin as deregisterPlugin
57635 * @borrows Plugin.getPlugins as getPlugins
57636 * @borrows Plugin.getPlugin as getPlugin
57637 * @borrows Plugin.getPluginVersion as getPluginVersion
57638 * @borrows Tech.getTech as getTech
57639 * @borrows Tech.registerTech as registerTech
57640 * @borrows TextTrack as TextTrack
57641 * @borrows VideoTrack as VideoTrack
57642 *
57643 * @param  {string|Element}
vendor: 16,412 bytes, lines 57643-58148
57643 id
57644 *         Video element or video element ID.
57645 *
57646 * @param  {Object} [options]
57647 *         Options object for providing settings.
57648 *         See: [Options Guide](https://docs.videojs.com/tutorial-options.html).
57649 *
57650 * @param  {PlayerReadyCallback} [ready]
57651 *         A function to be called when the {@link Player} and {@link Tech} are
57652 *         ready.
57653 *
57654 * @return {Player}
57655 *         The `videojs()` function returns a {@link Player|Player} instance.
57656 */
57657function videojs(id, options, ready) {
57658  let player = videojs.getPlayer(id);
57659  if (player) {
57660    if (options) {
57661      log$1.warn(`Player "${id}" is already initialised. Options will not be applied.`);
57662    }
57663    if (ready) {
57664      player.ready(ready);
57665    }
57666    return player;
57667  }
57668  const el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
57669  if (!isEl(el)) {
57670    throw new TypeError('The element or ID supplied is not valid. (videojs)');
57671  }
57672
57673  // document.body.contains(el) will only check if el is contained within that one document.
57674  // This causes problems for elements in iframes.
57675  // Instead, use the element's ownerDocument instead of the global document.
57676  // This will make sure that the element is indeed in the dom of that document.
57677  // Additionally, check that the document in question has a default view.
57678  // If the document is no longer attached to the dom, the defaultView of the document will be null.
57679  // If element is inside Shadow DOM (e.g. is part of a Custom element), ownerDocument.body
57680  // always returns false. Instead, use the Shadow DOM root.
57681  const inShadowDom = 'getRootNode' in el ? el.getRootNode() instanceof window__default["default"].ShadowRoot : false;
57682  const rootNode = inShadowDom ? el.getRootNode() : el.ownerDocument.body;
57683  if (!el.ownerDocument.defaultView || !rootNode.contains(el)) {
57684    log$1.warn('The element supplied is not included in the DOM');
57685  }
57686  options = options || {};
57687
57688  // Store a copy of the el before modification, if it is to be restored in destroy()
57689  // If div ingest, store the parent div
57690  if (options.restoreEl === true) {
57691    options.restoreEl = (el.parentNode && el.parentNode.hasAttribute && el.parentNode.hasAttribute('data-vjs-player') ? el.parentNode : el).cloneNode(true);
57692  }
57693  hooks('beforesetup').forEach(hookFunction => {
57694    const opts = hookFunction(el, merge$1(options));
57695    if (!isObject(opts) || Array.isArray(opts)) {
57696      log$1.error('please return an object in beforesetup hooks');
57697      return;
57698    }
57699    options = merge$1(options, opts);
57700  });
57701
57702  // We get the current "Player" component here in case an integration has
57703  // replaced it with a custom player.
57704  const PlayerComponent = Component$1.getComponent('Player');
57705  player = new PlayerComponent(el, options, ready);
57706  hooks('setup').forEach(hookFunction => hookFunction(player));
57707  return player;
57708}
57709videojs.hooks_ = hooks_;
57710videojs.hooks = hooks;
57711videojs.hook = hook;
57712videojs.hookOnce = hookOnce;
57713videojs.removeHook = removeHook;
57714
57715// Add default styles
57716if (window__default["default"].VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
57717  let style = $('.vjs-styles-defaults');
57718  if (!style) {
57719    style = createStyleElement('vjs-styles-defaults');
57720    const head = $('head');
57721    if (head) {
57722      head.insertBefore(style, head.firstChild);
57723    }
57724    setTextContent(style, `
57725      .video-js {
57726        width: 300px;
57727        height: 150px;
57728      }
57729
57730      .vjs-fluid:not(.vjs-audio-only-mode) {
57731        padding-top: 56.25%
57732      }
57733    `);
57734  }
57735}
57736
57737// Run Auto-load players
57738// You have to wait at least once in case this script is loaded after your
57739// video in the DOM (weird behavior only with minified version)
57740autoSetupTimeout(1, videojs);
57741
57742/**
57743 * Current Video.js version. Follows [semantic versioning](https://semver.org/).
57744 *
57745 * @type {string}
57746 */
57747videojs.VERSION = version$6;
57748
57749/**
57750 * The global options object. These are the settings that take effect
57751 * if no overrides are specified when the player is created.
57752 *
57753 * @type {Object}
57754 */
57755videojs.options = Player.prototype.options_;
57756
57757/**
57758 * Get an object with the currently created players, keyed by player ID
57759 *
57760 * @return {Object}
57761 *         The created players
57762 */
57763videojs.getPlayers = () => Player.players;
57764
57765/**
57766 * Get a single player based on an ID or DOM element.
57767 *
57768 * This is useful if you want to check if an element or ID has an associated
57769 * Video.js player, but not create one if it doesn't.
57770 *
57771 * @param   {string|Element} id
57772 *          An HTML element - `<video>`, `<audio>`, or `<video-js>` -
57773 *          or a string matching the `id` of such an element.
57774 *
57775 * @return {Player|undefined}
57776 *          A player instance or `undefined` if there is no player instance
57777 *          matching the argument.
57778 */
57779videojs.getPlayer = id => {
57780  const players = Player.players;
57781  let tag;
57782  if (typeof id === 'string') {
57783    const nId = normalizeId(id);
57784    const player = players[nId];
57785    if (player) {
57786      return player;
57787    }
57788    tag = $('#' + nId);
57789  } else {
57790    tag = id;
57791  }
57792  if (isEl(tag)) {
57793    const {
57794      player,
57795      playerId
57796    } = tag;
57797
57798    // Element may have a `player` property referring to an already created
57799    // player instance. If so, return that.
57800    if (player || players[playerId]) {
57801      return player || players[playerId];
57802    }
57803  }
57804};
57805
57806/**
57807 * Returns an array of all current players.
57808 *
57809 * @return {Array}
57810 *         An array of all players. The array will be in the order that
57811 *         `Object.keys` provides, which could potentially vary between
57812 *         JavaScript engines.
57813 *
57814 */
57815videojs.getAllPlayers = () =>
57816// Disposed players leave a key with a `null` value, so we need to make sure
57817// we filter those out.
57818Object.keys(Player.players).map(k => Player.players[k]).filter(Boolean);
57819videojs.players = Player.players;
57820videojs.getComponent = Component$1.getComponent;
57821
57822/**
57823 * Register a component so it can referred to by name. Used when adding to other
57824 * components, either through addChild `component.addChild('myComponent')` or through
57825 * default children options  `{ children: ['myComponent'] }`.
57826 *
57827 * > NOTE: You could also just initialize the component before adding.
57828 * `component.addChild(new MyComponent());`
57829 *
57830 * @param {string} name
57831 *        The class name of the component
57832 *
57833 * @param {typeof Component} comp
57834 *        The component class
57835 *
57836 * @return {typeof Component}
57837 *         The newly registered component
57838 */
57839videojs.registerComponent = (name, comp) => {
57840  if (Tech.isTech(comp)) {
57841    log$1.warn(`The ${name} tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)`);
57842  }
57843  return Component$1.registerComponent.call(Component$1, name, comp);
57844};
57845videojs.getTech = Tech.getTech;
57846videojs.registerTech = Tech.registerTech;
57847videojs.use = use;
57848
57849/**
57850 * An object that can be returned by a middleware to signify
57851 * that the middleware is being terminated.
57852 *
57853 * @type {object}
57854 * @property {object} middleware.TERMINATOR
57855 */
57856Object.defineProperty(videojs, 'middleware', {
57857  value: {},
57858  writeable: false,
57859  enumerable: true
57860});
57861Object.defineProperty(videojs.middleware, 'TERMINATOR', {
57862  value: TERMINATOR,
57863  writeable: false,
57864  enumerable: true
57865});
57866
57867/**
57868 * A reference to the {@link module:browser|browser utility module} as an object.
57869 *
57870 * @type {Object}
57871 * @see  {@link module:browser|browser}
57872 */
57873videojs.browser = browser;
57874
57875/**
57876 * A reference to the {@link module:obj|obj utility module} as an object.
57877 *
57878 * @type {Object}
57879 * @see  {@link module:obj|obj}
57880 */
57881videojs.obj = Obj;
57882
57883/**
57884 * Deprecated reference to the {@link module:obj.merge|merge function}
57885 *
57886 * @type {Function}
57887 * @see {@link module:obj.merge|merge}
57888 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.obj.merge instead.
57889 */
57890videojs.mergeOptions = deprecateForMajor(9, 'videojs.mergeOptions', 'videojs.obj.merge', merge$1);
57891
57892/**
57893 * Deprecated reference to the {@link module:obj.defineLazyProperty|defineLazyProperty function}
57894 *
57895 * @type {Function}
57896 * @see {@link module:obj.defineLazyProperty|defineLazyProperty}
57897 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.obj.defineLazyProperty instead.
57898 */
57899videojs.defineLazyProperty = deprecateForMajor(9, 'videojs.defineLazyProperty', 'videojs.obj.defineLazyProperty', defineLazyProperty);
57900
57901/**
57902 * Deprecated reference to the {@link module:fn.bind_|fn.bind_ function}
57903 *
57904 * @type {Function}
57905 * @see {@link module:fn.bind_|fn.bind_}
57906 * @deprecated Deprecated and will be removed in 9.0. Please use native Function.prototype.bind instead.
57907 */
57908videojs.bind = deprecateForMajor(9, 'videojs.bind', 'native Function.prototype.bind', bind_);
57909videojs.registerPlugin = Plugin.registerPlugin;
57910videojs.deregisterPlugin = Plugin.deregisterPlugin;
57911
57912/**
57913 * Deprecated method to register a plugin with Video.js
57914 *
57915 * @deprecated Deprecated and will be removed in 9.0. Use videojs.registerPlugin() instead.
57916 *
57917 * @param {string} name
57918 *        The plugin name
57919*
57920 * @param {typeof Plugin|Function} plugin
57921 *         The plugin sub-class or function
57922 *
57923 * @return {typeof Plugin|Function}
57924 */
57925videojs.plugin = (name, plugin) => {
57926  log$1.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
57927  return Plugin.registerPlugin(name, plugin);
57928};
57929videojs.getPlugins = Plugin.getPlugins;
57930videojs.getPlugin = Plugin.getPlugin;
57931videojs.getPluginVersion = Plugin.getPluginVersion;
57932
57933/**
57934 * Adding languages so that they're available to all players.
57935 * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
57936 *
57937 * @param {string} code
57938 *        The language code or dictionary property
57939 *
57940 * @param {Object} data
57941 *        The data values to be translated
57942 *
57943 * @return {Object}
57944 *         The resulting language dictionary object
57945 */
57946videojs.addLanguage = function (code, data) {
57947  code = ('' + code).toLowerCase();
57948  videojs.options.languages = merge$1(videojs.options.languages, {
57949    [code]: data
57950  });
57951  return videojs.options.languages[code];
57952};
57953
57954/**
57955 * A reference to the {@link module:log|log utility module} as an object.
57956 *
57957 * @type {Function}
57958 * @see  {@link module:log|log}
57959 */
57960videojs.log = log$1;
57961videojs.createLogger = createLogger;
57962
57963/**
57964 * A reference to the {@link module:time|time utility module} as an object.
57965 *
57966 * @type {Object}
57967 * @see {@link module:time|time}
57968 */
57969videojs.time = Time;
57970
57971/**
57972 * Deprecated reference to the {@link module:time.createTimeRanges|createTimeRanges function}
57973 *
57974 * @type {Function}
57975 * @see {@link module:time.createTimeRanges|createTimeRanges}
57976 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.createTimeRanges instead.
57977 */
57978videojs.createTimeRange = deprecateForMajor(9, 'videojs.createTimeRange', 'videojs.time.createTimeRanges', createTimeRanges$1);
57979
57980/**
57981 * Deprecated reference to the {@link module:time.createTimeRanges|createTimeRanges function}
57982 *
57983 * @type {Function}
57984 * @see {@link module:time.createTimeRanges|createTimeRanges}
57985 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.createTimeRanges instead.
57986 */
57987videojs.createTimeRanges = deprecateForMajor(9, 'videojs.createTimeRanges', 'videojs.time.createTimeRanges', createTimeRanges$1);
57988
57989/**
57990 * Deprecated reference to the {@link module:time.formatTime|formatTime function}
57991 *
57992 * @type {Function}
57993 * @see {@link module:time.formatTime|formatTime}
57994 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.format instead.
57995 */
57996videojs.formatTime = deprecateForMajor(9, 'videojs.formatTime', 'videojs.time.formatTime', formatTime);
57997
57998/**
57999 * Deprecated reference to the {@link module:time.setFormatTime|setFormatTime function}
58000 *
58001 * @type {Function}
58002 * @see {@link module:time.setFormatTime|setFormatTime}
58003 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.setFormat instead.
58004 */
58005videojs.setFormatTime = deprecateForMajor(9, 'videojs.setFormatTime', 'videojs.time.setFormatTime', setFormatTime);
58006
58007/**
58008 * Deprecated reference to the {@link module:time.resetFormatTime|resetFormatTime function}
58009 *
58010 * @type {Function}
58011 * @see {@link module:time.resetFormatTime|resetFormatTime}
58012 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.time.resetFormat instead.
58013 */
58014videojs.resetFormatTime = deprecateForMajor(9, 'videojs.resetFormatTime', 'videojs.time.resetFormatTime', resetFormatTime);
58015
58016/**
58017 * Deprecated reference to the {@link module:url.parseUrl|Url.parseUrl function}
58018 *
58019 * @type {Function}
58020 * @see {@link module:url.parseUrl|parseUrl}
58021 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.url.parseUrl instead.
58022 */
58023videojs.parseUrl = deprecateForMajor(9, 'videojs.parseUrl', 'videojs.url.parseUrl', parseUrl);
58024
58025/**
58026 * Deprecated reference to the {@link module:url.isCrossOrigin|Url.isCrossOrigin function}
58027 *
58028 * @type {Function}
58029 * @see {@link module:url.isCrossOrigin|isCrossOrigin}
58030 * @deprecated Deprecated and will be removed in 9.0. Please use videojs.url.isCrossOrigin instead.
58031 */
58032videojs.isCrossOrigin = deprecateForMajor(9, 'videojs.isCrossOrigin', 'videojs.url.isCrossOrigin', isCrossOrigin);
58033videojs.EventTarget = EventTarget$2;
58034videojs.any = any;
58035videojs.on = on;
58036videojs.one = one;
58037videojs.off = off;
58038videojs.trigger = trigger;
58039
58040/**
58041 * A cross-browser XMLHttpRequest wrapper.
58042 *
58043 * @function
58044 * @param    {Object} options
58045 *           Settings for the request.
58046 *
58047 * @return   {XMLHttpRequest|XDomainRequest}
58048 *           The request object.
58049 *
58050 * @see      https://github.com/Raynos/xhr
58051 */
58052videojs.xhr = XHR__default["default"];
58053videojs.TrackList = TrackList;
58054videojs.TextTrack = TextTrack;
58055videojs.TextTrackList = TextTrackList;
58056videojs.AudioTrack = AudioTrack;
58057videojs.AudioTrackList = AudioTrackList;
58058videojs.VideoTrack = VideoTrack;
58059videojs.VideoTrackList = VideoTrackList;
58060['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(k => {
58061  videojs[k] = function () {
58062    log$1.warn(`videojs.${k}() is deprecated; use videojs.dom.${k}() instead`);
58063    return Dom[k].apply(null, arguments);
58064  };
58065});
58066videojs.computedStyle = deprecateForMajor(9, 'videojs.computedStyle', 'videojs.dom.computedStyle', computedStyle);
58067
58068/**
58069 * A reference to the {@link module:dom|DOM utility module} as an object.
58070 *
58071 * @type {Object}
58072 * @see {@link module:dom|dom}
58073 */
58074videojs.dom = Dom;
58075
58076/**
58077 * A reference to the {@link module:fn|fn utility module} as an object.
58078 *
58079 * @type {Object}
58080 * @see {@link module:fn|fn}
58081 */
58082videojs.fn = Fn;
58083
58084/**
58085 * A reference to the {@link module:num|num utility module} as an object.
58086 *
58087 * @type {Object}
58088 * @see {@link module:num|num}
58089 */
58090videojs.num = Num;
58091
58092/**
58093 * A reference to the {@link module:str|str utility module} as an object.
58094 *
58095 * @type {Object}
58096 * @see {@link module:str|str}
58097 */
58098videojs.str = Str;
58099
58100/**
58101 * A reference to the {@link module:url|URL utility module} as an object.
58102 *
58103 * @type {Object}
58104 * @see {@link module:url|url}
58105 */
58106videojs.url = Url;
58107
58108// The list of possible error types to occur in video.js
58109videojs.Error = VjsErrors;
58110
58111/*! @name videojs-contrib-quality-levels @version 4.1.0 @license Apache-2.0 */
58112
58113/**
58114 * A single QualityLevel.
58115 *
58116 * interface QualityLevel {
58117 *   readonly attribute DOMString id;
58118 *            attribute DOMString label;
58119 *   readonly attribute long width;
58120 *   readonly attribute long height;
58121 *   readonly attribute long bitrate;
58122 *            attribute boolean enabled;
58123 * };
58124 *
58125 * @class QualityLevel
58126 */
58127class QualityLevel {
58128  /**
58129   * Creates a QualityLevel
58130   *
58131   * @param {Representation|Object} representation The representation of the quality level
58132   * @param {string}   representation.id        Unique id of the QualityLevel
58133   * @param {number=}  representation.width     Resolution width of the QualityLevel
58134   * @param {number=}  representation.height    Resolution height of the QualityLevel
58135   * @param {number}   representation.bandwidth Bitrate of the QualityLevel
58136   * @param {number=}  representation.frameRate Frame-rate of the QualityLevel
58137   * @param {Function} representation.enabled   Callback to enable/disable QualityLevel
58138   */
58139  constructor(representation) {
58140    let level = this; // eslint-disable-line
58141
58142    level.id = representation.id;
58143    level.label = level.id;
58144    level.width = representation.width;
58145    level.height = representation.height;
58146    level.bitrate = representation.bandwidth;
58147    level.frameRate = representation.frameRate;
58148    level.enabled_ = representation.enabled;
vendor: 5,616 bytes, lines 58149-58349
58149    Object.defineProperty(level, 'enabled', {
58150      /**
58151       * Get whether the QualityLevel is enabled.
58152       *
58153       * @return {boolean} True if the QualityLevel is enabled.
58154       */
58155      get() {
58156        return level.enabled_();
58157      },
58158      /**
58159       * Enable or disable the QualityLevel.
58160       *
58161       * @param {boolean} enable true to enable QualityLevel, false to disable.
58162       */
58163      set(enable) {
58164        level.enabled_(enable);
58165      }
58166    });
58167    return level;
58168  }
58169}
58170
58171/**
58172 * A list of QualityLevels.
58173 *
58174 * interface QualityLevelList : EventTarget {
58175 *   getter QualityLevel (unsigned long index);
58176 *   readonly attribute unsigned long length;
58177 *   readonly attribute long selectedIndex;
58178 *
58179 *   void addQualityLevel(QualityLevel qualityLevel)
58180 *   void removeQualityLevel(QualityLevel remove)
58181 *   QualityLevel? getQualityLevelById(DOMString id);
58182 *
58183 *   attribute EventHandler onchange;
58184 *   attribute EventHandler onaddqualitylevel;
58185 *   attribute EventHandler onremovequalitylevel;
58186 * };
58187 *
58188 * @extends videojs.EventTarget
58189 * @class QualityLevelList
58190 */
58191class QualityLevelList extends videojs.EventTarget {
58192  /**
58193   * Creates a QualityLevelList.
58194   */
58195  constructor() {
58196    super();
58197    let list = this; // eslint-disable-line
58198
58199    list.levels_ = [];
58200    list.selectedIndex_ = -1;
58201
58202    /**
58203     * Get the index of the currently selected QualityLevel.
58204     *
58205     * @returns {number} The index of the selected QualityLevel. -1 if none selected.
58206     * @readonly
58207     */
58208    Object.defineProperty(list, 'selectedIndex', {
58209      get() {
58210        return list.selectedIndex_;
58211      }
58212    });
58213
58214    /**
58215     * Get the length of the list of QualityLevels.
58216     *
58217     * @returns {number} The length of the list.
58218     * @readonly
58219     */
58220    Object.defineProperty(list, 'length', {
58221      get() {
58222        return list.levels_.length;
58223      }
58224    });
58225    list[Symbol.iterator] = () => list.levels_.values();
58226    return list;
58227  }
58228
58229  /**
58230   * Adds a quality level to the list.
58231   *
58232   * @param {Representation|Object} representation The representation of the quality level
58233   * @param {string}   representation.id        Unique id of the QualityLevel
58234   * @param {number=}  representation.width     Resolution width of the QualityLevel
58235   * @param {number=}  representation.height    Resolution height of the QualityLevel
58236   * @param {number}   representation.bandwidth Bitrate of the QualityLevel
58237   * @param {number=}  representation.frameRate Frame-rate of the QualityLevel
58238   * @param {Function} representation.enabled   Callback to enable/disable QualityLevel
58239   * @return {QualityLevel} the QualityLevel added to the list
58240   * @method addQualityLevel
58241   */
58242  addQualityLevel(representation) {
58243    let qualityLevel = this.getQualityLevelById(representation.id);
58244
58245    // Do not add duplicate quality levels
58246    if (qualityLevel) {
58247      return qualityLevel;
58248    }
58249    const index = this.levels_.length;
58250    qualityLevel = new QualityLevel(representation);
58251    if (!('' + index in this)) {
58252      Object.defineProperty(this, index, {
58253        get() {
58254          return this.levels_[index];
58255        }
58256      });
58257    }
58258    this.levels_.push(qualityLevel);
58259    this.trigger({
58260      qualityLevel,
58261      type: 'addqualitylevel'
58262    });
58263    return qualityLevel;
58264  }
58265
58266  /**
58267   * Removes a quality level from the list.
58268   *
58269   * @param {QualityLevel} qualityLevel The QualityLevel to remove from the list.
58270   * @return {QualityLevel|null} the QualityLevel removed or null if nothing removed
58271   * @method removeQualityLevel
58272   */
58273  removeQualityLevel(qualityLevel) {
58274    let removed = null;
58275    for (let i = 0, l = this.length; i < l; i++) {
58276      if (this[i] === qualityLevel) {
58277        removed = this.levels_.splice(i, 1)[0];
58278        if (this.selectedIndex_ === i) {
58279          this.selectedIndex_ = -1;
58280        } else if (this.selectedIndex_ > i) {
58281          this.selectedIndex_--;
58282        }
58283        break;
58284      }
58285    }
58286    if (removed) {
58287      this.trigger({
58288        qualityLevel,
58289        type: 'removequalitylevel'
58290      });
58291    }
58292    return removed;
58293  }
58294
58295  /**
58296   * Searches for a QualityLevel with the given id.
58297   *
58298   * @param {string} id The id of the QualityLevel to find.
58299   * @return {QualityLevel|null} The QualityLevel with id, or null if not found.
58300   * @method getQualityLevelById
58301   */
58302  getQualityLevelById(id) {
58303    for (let i = 0, l = this.length; i < l; i++) {
58304      const level = this[i];
58305      if (level.id === id) {
58306        return level;
58307      }
58308    }
58309    return null;
58310  }
58311
58312  /**
58313   * Resets the list of QualityLevels to empty
58314   *
58315   * @method dispose
58316   */
58317  dispose() {
58318    this.selectedIndex_ = -1;
58319    this.levels_.length = 0;
58320  }
58321}
58322
58323/**
58324 * change - The selected QualityLevel has changed.
58325 * addqualitylevel - A QualityLevel has been added to the QualityLevelList.
58326 * removequalitylevel - A QualityLevel has been removed from the QualityLevelList.
58327 */
58328QualityLevelList.prototype.allowedEvents_ = {
58329  change: 'change',
58330  addqualitylevel: 'addqualitylevel',
58331  removequalitylevel: 'removequalitylevel'
58332};
58333
58334// emulate attribute EventHandler support to allow for feature detection
58335for (const event in QualityLevelList.prototype.allowedEvents_) {
58336  QualityLevelList.prototype['on' + event] = null;
58337}
58338var version$5 = "4.1.0";
58339
58340/**
58341 * Initialization function for the qualityLevels plugin. Sets up the QualityLevelList and
58342 * event handlers.
58343 *
58344 * @param {Player} player Player object.
58345 * @param {Object} options Plugin options object.
58346 * @return {QualityLevelList} a list of QualityLevels
58347 */
58348const initPlugin$1 = function (player, options) {
58349  const originalPluginFn = player.qualityLevels;
vendor: 6,291 bytes, lines 58350-58562
58350  const qualityLevelList = new QualityLevelList();
58351  const disposeHandler = function () {
58352    qualityLevelList.dispose();
58353    player.qualityLevels = originalPluginFn;
58354    player.off('dispose', disposeHandler);
58355  };
58356  player.on('dispose', disposeHandler);
58357  player.qualityLevels = () => qualityLevelList;
58358  player.qualityLevels.VERSION = version$5;
58359  return qualityLevelList;
58360};
58361
58362/**
58363 * A video.js plugin.
58364 *
58365 * In the plugin function, the value of `this` is a video.js `Player`
58366 * instance. You cannot rely on the player being in a "ready" state here,
58367 * depending on how the plugin is invoked. This may or may not be important
58368 * to you; if not, remove the wait for "ready"!
58369 *
58370 * @param {Object} options Plugin options object
58371 * @return {QualityLevelList} a list of QualityLevels
58372 */
58373const qualityLevels = function (options) {
58374  return initPlugin$1(this, videojs.obj.merge({}, options));
58375};
58376
58377// Register the plugin with video.js.
58378videojs.registerPlugin('qualityLevels', qualityLevels);
58379
58380// Include the version number.
58381qualityLevels.VERSION = version$5;
58382
58383/*! @name @videojs/http-streaming @version 3.17.5 @license Apache-2.0 */
58384
58385/**
58386 * @file resolve-url.js - Handling how URLs are resolved and manipulated
58387 */
58388const resolveUrl = _resolveUrl__default["default"];
58389/**
58390 * If the xhr request was redirected, return the responseURL, otherwise,
58391 * return the original url.
58392 *
58393 * @api private
58394 *
58395 * @param  {string} url - an url being requested
58396 * @param  {XMLHttpRequest} req - xhr request result
58397 *
58398 * @return {string}
58399 */
58400
58401const resolveManifestRedirect = (url, req) => {
58402  // To understand how the responseURL below is set and generated:
58403  // - https://fetch.spec.whatwg.org/#concept-response-url
58404  // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
58405  if (req && req.responseURL && url !== req.responseURL) {
58406    return req.responseURL;
58407  }
58408  return url;
58409};
58410const logger = source => {
58411  if (videojs.log.debug) {
58412    return videojs.log.debug.bind(videojs, 'VHS:', `${source} >`);
58413  }
58414  return function () {};
58415};
58416
58417/**
58418 * Provides a compatibility layer between Video.js 7 and 8 API changes for VHS.
58419 */
58420/**
58421 * Delegates to videojs.obj.merge (Video.js 8) or
58422 * videojs.mergeOptions (Video.js 7).
58423 */
58424
58425function merge(...args) {
58426  const context = videojs.obj || videojs;
58427  const fn = context.merge || context.mergeOptions;
58428  return fn.apply(context, args);
58429}
58430/**
58431 * Delegates to videojs.time.createTimeRanges (Video.js 8) or
58432 * videojs.createTimeRanges (Video.js 7).
58433 */
58434
58435function createTimeRanges(...args) {
58436  const context = videojs.time || videojs;
58437  const fn = context.createTimeRanges || context.createTimeRanges;
58438  return fn.apply(context, args);
58439}
58440/**
58441 * Converts provided buffered ranges to a descriptive string
58442 *
58443 * @param {TimeRanges} buffered - received buffered time ranges
58444 *
58445 * @return {string} - descriptive string
58446 */
58447
58448function bufferedRangesToString(buffered) {
58449  if (buffered.length === 0) {
58450    return 'Buffered Ranges are empty';
58451  }
58452  let bufferedRangesStr = 'Buffered Ranges: \n';
58453  for (let i = 0; i < buffered.length; i++) {
58454    const start = buffered.start(i);
58455    const end = buffered.end(i);
58456    bufferedRangesStr += `${start} --> ${end}. Duration (${end - start})\n`;
58457  }
58458  return bufferedRangesStr;
58459}
58460
58461/**
58462 * ranges
58463 *
58464 * Utilities for working with TimeRanges.
58465 *
58466 */
58467
58468const TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range
58469// can be misleading because of precision differences or when the current media has poorly
58470// aligned audio and video, which can cause values to be slightly off from what you would
58471// expect. This value is what we consider to be safe to use in such comparisons to account
58472// for these scenarios.
58473
58474const SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
58475const filterRanges = function (timeRanges, predicate) {
58476  const results = [];
58477  let i;
58478  if (timeRanges && timeRanges.length) {
58479    // Search for ranges that match the predicate
58480    for (i = 0; i < timeRanges.length; i++) {
58481      if (predicate(timeRanges.start(i), timeRanges.end(i))) {
58482        results.push([timeRanges.start(i), timeRanges.end(i)]);
58483      }
58484    }
58485  }
58486  return createTimeRanges(results);
58487};
58488/**
58489 * Attempts to find the buffered TimeRange that contains the specified
58490 * time.
58491 *
58492 * @param {TimeRanges} buffered - the TimeRanges object to query
58493 * @param {number} time  - the time to filter on.
58494 * @return {TimeRanges} a new TimeRanges object
58495 */
58496
58497const findRange = function (buffered, time) {
58498  return filterRanges(buffered, function (start, end) {
58499    return start - SAFE_TIME_DELTA <= time && end + SAFE_TIME_DELTA >= time;
58500  });
58501};
58502/**
58503 * Returns the TimeRanges that begin later than the specified time.
58504 *
58505 * @param {TimeRanges} timeRanges - the TimeRanges object to query
58506 * @param {number} time - the time to filter on.
58507 * @return {TimeRanges} a new TimeRanges object.
58508 */
58509
58510const findNextRange = function (timeRanges, time) {
58511  return filterRanges(timeRanges, function (start) {
58512    return start - TIME_FUDGE_FACTOR >= time;
58513  });
58514};
58515/**
58516 * Returns gaps within a list of TimeRanges
58517 *
58518 * @param {TimeRanges} buffered - the TimeRanges object
58519 * @return {TimeRanges} a TimeRanges object of gaps
58520 */
58521
58522const findGaps = function (buffered) {
58523  if (buffered.length < 2) {
58524    return createTimeRanges();
58525  }
58526  const ranges = [];
58527  for (let i = 1; i < buffered.length; i++) {
58528    const start = buffered.end(i - 1);
58529    const end = buffered.start(i);
58530    ranges.push([start, end]);
58531  }
58532  return createTimeRanges(ranges);
58533};
58534/**
58535 * Calculate the intersection of two TimeRanges
58536 *
58537 * @param {TimeRanges} bufferA
58538 * @param {TimeRanges} bufferB
58539 * @return {TimeRanges} The interesection of `bufferA` with `bufferB`
58540 */
58541
58542const bufferIntersection = function (bufferA, bufferB) {
58543  let start = null;
58544  let end = null;
58545  let arity = 0;
58546  const extents = [];
58547  const ranges = [];
58548  if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) {
58549    return createTimeRanges();
58550  } // Handle the case where we have both buffers and create an
58551  // intersection of the two
58552
58553  let count = bufferA.length; // A) Gather up all start and end times
58554
58555  while (count--) {
58556    extents.push({
58557      time: bufferA.start(count),
58558      type: 'start'
58559    });
58560    extents.push({
58561      time: bufferA.end(count),
58562      type: 'end'
vendor: 10,071 bytes, lines 58563-58940
58563    });
58564  }
58565  count = bufferB.length;
58566  while (count--) {
58567    extents.push({
58568      time: bufferB.start(count),
58569      type: 'start'
58570    });
58571    extents.push({
58572      time: bufferB.end(count),
58573      type: 'end'
58574    });
58575  } // B) Sort them by time
58576
58577  extents.sort(function (a, b) {
58578    return a.time - b.time;
58579  }); // C) Go along one by one incrementing arity for start and decrementing
58580  //    arity for ends
58581
58582  for (count = 0; count < extents.length; count++) {
58583    if (extents[count].type === 'start') {
58584      arity++; // D) If arity is ever incremented to 2 we are entering an
58585      //    overlapping range
58586
58587      if (arity === 2) {
58588        start = extents[count].time;
58589      }
58590    } else if (extents[count].type === 'end') {
58591      arity--; // E) If arity is ever decremented to 1 we leaving an
58592      //    overlapping range
58593
58594      if (arity === 1) {
58595        end = extents[count].time;
58596      }
58597    } // F) Record overlapping ranges
58598
58599    if (start !== null && end !== null) {
58600      ranges.push([start, end]);
58601      start = null;
58602      end = null;
58603    }
58604  }
58605  return createTimeRanges(ranges);
58606};
58607/**
58608 * Gets a human readable string for a TimeRange
58609 *
58610 * @param {TimeRange} range
58611 * @return {string} a human readable string
58612 */
58613
58614const printableRange = range => {
58615  const strArr = [];
58616  if (!range || !range.length) {
58617    return '';
58618  }
58619  for (let i = 0; i < range.length; i++) {
58620    strArr.push(range.start(i) + ' => ' + range.end(i));
58621  }
58622  return strArr.join(', ');
58623};
58624/**
58625 * Calculates the amount of time left in seconds until the player hits the end of the
58626 * buffer and causes a rebuffer
58627 *
58628 * @param {TimeRange} buffered
58629 *        The state of the buffer
58630 * @param {Numnber} currentTime
58631 *        The current time of the player
58632 * @param {number} playbackRate
58633 *        The current playback rate of the player. Defaults to 1.
58634 * @return {number}
58635 *         Time until the player has to start rebuffering in seconds.
58636 * @function timeUntilRebuffer
58637 */
58638
58639const timeUntilRebuffer = function (buffered, currentTime, playbackRate = 1) {
58640  const bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
58641  return (bufferedEnd - currentTime) / playbackRate;
58642};
58643/**
58644 * Converts a TimeRanges object into an array representation
58645 *
58646 * @param {TimeRanges} timeRanges
58647 * @return {Array}
58648 */
58649
58650const timeRangesToArray = timeRanges => {
58651  const timeRangesList = [];
58652  for (let i = 0; i < timeRanges.length; i++) {
58653    timeRangesList.push({
58654      start: timeRanges.start(i),
58655      end: timeRanges.end(i)
58656    });
58657  }
58658  return timeRangesList;
58659};
58660/**
58661 * Determines if two time range objects are different.
58662 *
58663 * @param {TimeRange} a
58664 *        the first time range object to check
58665 *
58666 * @param {TimeRange} b
58667 *        the second time range object to check
58668 *
58669 * @return {Boolean}
58670 *         Whether the time range objects differ
58671 */
58672
58673const isRangeDifferent = function (a, b) {
58674  // same object
58675  if (a === b) {
58676    return false;
58677  } // one or the other is undefined
58678
58679  if (!a && b || !b && a) {
58680    return true;
58681  } // length is different
58682
58683  if (a.length !== b.length) {
58684    return true;
58685  } // see if any start/end pair is different
58686
58687  for (let i = 0; i < a.length; i++) {
58688    if (a.start(i) !== b.start(i) || a.end(i) !== b.end(i)) {
58689      return true;
58690    }
58691  } // if the length and every pair is the same
58692  // this is the same time range
58693
58694  return false;
58695};
58696const lastBufferedEnd = function (a) {
58697  if (!a || !a.length || !a.end) {
58698    return;
58699  }
58700  return a.end(a.length - 1);
58701};
58702/**
58703 * A utility function to add up the amount of time in a timeRange
58704 * after a specified startTime.
58705 * ie:[[0, 10], [20, 40], [50, 60]] with a startTime 0
58706 *     would return 40 as there are 40s seconds after 0 in the timeRange
58707 *
58708 * @param {TimeRange} range
58709 *        The range to check against
58710 * @param {number} startTime
58711 *        The time in the time range that you should start counting from
58712 *
58713 * @return {number}
58714 *          The number of seconds in the buffer passed the specified time.
58715 */
58716
58717const timeAheadOf = function (range, startTime) {
58718  let time = 0;
58719  if (!range || !range.length) {
58720    return time;
58721  }
58722  for (let i = 0; i < range.length; i++) {
58723    const start = range.start(i);
58724    const end = range.end(i); // startTime is after this range entirely
58725
58726    if (startTime > end) {
58727      continue;
58728    } // startTime is within this range
58729
58730    if (startTime > start && startTime <= end) {
58731      time += end - startTime;
58732      continue;
58733    } // startTime is before this range.
58734
58735    time += end - start;
58736  }
58737  return time;
58738};
58739
58740/**
58741 * @file playlist.js
58742 *
58743 * Playlist related utilities.
58744 */
58745/**
58746 * Get the duration of a segment, with special cases for
58747 * llhls segments that do not have a duration yet.
58748 *
58749 * @param {Object} playlist
58750 *        the playlist that the segment belongs to.
58751 * @param {Object} segment
58752 *        the segment to get a duration for.
58753 *
58754 * @return {number}
58755 *          the segment duration
58756 */
58757
58758const segmentDurationWithParts = (playlist, segment) => {
58759  // if this isn't a preload segment
58760  // then we will have a segment duration that is accurate.
58761  if (!segment.preload) {
58762    return segment.duration;
58763  } // otherwise we have to add up parts and preload hints
58764  // to get an up to date duration.
58765
58766  let result = 0;
58767  (segment.parts || []).forEach(function (p) {
58768    result += p.duration;
58769  }); // for preload hints we have to use partTargetDuration
58770  // as they won't even have a duration yet.
58771
58772  (segment.preloadHints || []).forEach(function (p) {
58773    if (p.type === 'PART') {
58774      result += playlist.partTargetDuration;
58775    }
58776  });
58777  return result;
58778};
58779/**
58780 * A function to get a combined list of parts and segments with durations
58781 * and indexes.
58782 *
58783 * @param {Playlist} playlist the playlist to get the list for.
58784 *
58785 * @return {Array} The part/segment list.
58786 */
58787
58788const getPartsAndSegments = playlist => (playlist.segments || []).reduce((acc, segment, si) => {
58789  if (segment.parts) {
58790    segment.parts.forEach(function (part, pi) {
58791      acc.push({
58792        duration: part.duration,
58793        segmentIndex: si,
58794        partIndex: pi,
58795        part,
58796        segment
58797      });
58798    });
58799  } else {
58800    acc.push({
58801      duration: segment.duration,
58802      segmentIndex: si,
58803      partIndex: null,
58804      segment,
58805      part: null
58806    });
58807  }
58808  return acc;
58809}, []);
58810const getLastParts = media => {
58811  const lastSegment = media.segments && media.segments.length && media.segments[media.segments.length - 1];
58812  return lastSegment && lastSegment.parts || [];
58813};
58814const getKnownPartCount = ({
58815  preloadSegment
58816}) => {
58817  if (!preloadSegment) {
58818    return;
58819  }
58820  const {
58821    parts,
58822    preloadHints
58823  } = preloadSegment;
58824  let partCount = (preloadHints || []).reduce((count, hint) => count + (hint.type === 'PART' ? 1 : 0), 0);
58825  partCount += parts && parts.length ? parts.length : 0;
58826  return partCount;
58827};
58828/**
58829 * Get the number of seconds to delay from the end of a
58830 * live playlist.
58831 *
58832 * @param {Playlist} main the main playlist
58833 * @param {Playlist} media the media playlist
58834 * @return {number} the hold back in seconds.
58835 */
58836
58837const liveEdgeDelay = (main, media) => {
58838  if (media.endList) {
58839    return 0;
58840  } // dash suggestedPresentationDelay trumps everything
58841
58842  if (main && main.suggestedPresentationDelay) {
58843    return main.suggestedPresentationDelay;
58844  }
58845  const hasParts = getLastParts(media).length > 0; // look for "part" delays from ll-hls first
58846
58847  if (hasParts && media.serverControl && media.serverControl.partHoldBack) {
58848    return media.serverControl.partHoldBack;
58849  } else if (hasParts && media.partTargetDuration) {
58850    return media.partTargetDuration * 3; // finally look for full segment delays
58851  } else if (media.serverControl && media.serverControl.holdBack) {
58852    return media.serverControl.holdBack;
58853  } else if (media.targetDuration) {
58854    return media.targetDuration * 3;
58855  }
58856  return 0;
58857};
58858/**
58859 * walk backward until we find a duration we can use
58860 * or return a failure
58861 *
58862 * @param {Playlist} playlist the playlist to walk through
58863 * @param {Number} endSequence the mediaSequence to stop walking on
58864 */
58865
58866const backwardDuration = function (playlist, endSequence) {
58867  let result = 0;
58868  let i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following
58869  // the interval, use it
58870
58871  let segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline
58872  // information that is earlier than endSequence
58873
58874  if (segment) {
58875    if (typeof segment.start !== 'undefined') {
58876      return {
58877        result: segment.start,
58878        precise: true
58879      };
58880    }
58881    if (typeof segment.end !== 'undefined') {
58882      return {
58883        result: segment.end - segment.duration,
58884        precise: true
58885      };
58886    }
58887  }
58888  while (i--) {
58889    segment = playlist.segments[i];
58890    if (typeof segment.end !== 'undefined') {
58891      return {
58892        result: result + segment.end,
58893        precise: true
58894      };
58895    }
58896    result += segmentDurationWithParts(playlist, segment);
58897    if (typeof segment.start !== 'undefined') {
58898      return {
58899        result: result + segment.start,
58900        precise: true
58901      };
58902    }
58903  }
58904  return {
58905    result,
58906    precise: false
58907  };
58908};
58909/**
58910 * walk forward until we find a duration we can use
58911 * or return a failure
58912 *
58913 * @param {Playlist} playlist the playlist to walk through
58914 * @param {number} endSequence the mediaSequence to stop walking on
58915 */
58916
58917const forwardDuration = function (playlist, endSequence) {
58918  let result = 0;
58919  let segment;
58920  let i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline
58921  // information
58922
58923  for (; i < playlist.segments.length; i++) {
58924    segment = playlist.segments[i];
58925    if (typeof segment.start !== 'undefined') {
58926      return {
58927        result: segment.start - result,
58928        precise: true
58929      };
58930    }
58931    result += segmentDurationWithParts(playlist, segment);
58932    if (typeof segment.end !== 'undefined') {
58933      return {
58934        result: segment.end - result,
58935        precise: true
58936      };
58937    }
58938  } // indicate we didn't find a useful duration estimate
58939
58940  return {
vendor: 4,837 bytes, lines 58941-59068
58941    result: -1,
58942    precise: false
58943  };
58944};
58945/**
58946  * Calculate the media duration from the segments associated with a
58947  * playlist. The duration of a subinterval of the available segments
58948  * may be calculated by specifying an end index.
58949  *
58950  * @param {Object} playlist a media playlist object
58951  * @param {number=} endSequence an exclusive upper boundary
58952  * for the playlist.  Defaults to playlist length.
58953  * @param {number} expired the amount of time that has dropped
58954  * off the front of the playlist in a live scenario
58955  * @return {number} the duration between the first available segment
58956  * and end index.
58957  */
58958
58959const intervalDuration = function (playlist, endSequence, expired) {
58960  if (typeof endSequence === 'undefined') {
58961    endSequence = playlist.mediaSequence + playlist.segments.length;
58962  }
58963  if (endSequence < playlist.mediaSequence) {
58964    return 0;
58965  } // do a backward walk to estimate the duration
58966
58967  const backward = backwardDuration(playlist, endSequence);
58968  if (backward.precise) {
58969    // if we were able to base our duration estimate on timing
58970    // information provided directly from the Media Source, return
58971    // it
58972    return backward.result;
58973  } // walk forward to see if a precise duration estimate can be made
58974  // that way
58975
58976  const forward = forwardDuration(playlist, endSequence);
58977  if (forward.precise) {
58978    // we found a segment that has been buffered and so it's
58979    // position is known precisely
58980    return forward.result;
58981  } // return the less-precise, playlist-based duration estimate
58982
58983  return backward.result + expired;
58984};
58985/**
58986  * Calculates the duration of a playlist. If a start and end index
58987  * are specified, the duration will be for the subset of the media
58988  * timeline between those two indices. The total duration for live
58989  * playlists is always Infinity.
58990  *
58991  * @param {Object} playlist a media playlist object
58992  * @param {number=} endSequence an exclusive upper
58993  * boundary for the playlist. Defaults to the playlist media
58994  * sequence number plus its length.
58995  * @param {number=} expired the amount of time that has
58996  * dropped off the front of the playlist in a live scenario
58997  * @return {number} the duration between the start index and end
58998  * index.
58999  */
59000
59001const duration = function (playlist, endSequence, expired) {
59002  if (!playlist) {
59003    return 0;
59004  }
59005  if (typeof expired !== 'number') {
59006    expired = 0;
59007  } // if a slice of the total duration is not requested, use
59008  // playlist-level duration indicators when they're present
59009
59010  if (typeof endSequence === 'undefined') {
59011    // if present, use the duration specified in the playlist
59012    if (playlist.totalDuration) {
59013      return playlist.totalDuration;
59014    } // duration should be Infinity for live playlists
59015
59016    if (!playlist.endList) {
59017      return window__default["default"].Infinity;
59018    }
59019  } // calculate the total duration based on the segment durations
59020
59021  return intervalDuration(playlist, endSequence, expired);
59022};
59023/**
59024  * Calculate the time between two indexes in the current playlist
59025  * neight the start- nor the end-index need to be within the current
59026  * playlist in which case, the targetDuration of the playlist is used
59027  * to approximate the durations of the segments
59028  *
59029  * @param {Array} options.durationList list to iterate over for durations.
59030  * @param {number} options.defaultDuration duration to use for elements before or after the durationList
59031  * @param {number} options.startIndex partsAndSegments index to start
59032  * @param {number} options.endIndex partsAndSegments index to end.
59033  * @return {number} the number of seconds between startIndex and endIndex
59034  */
59035
59036const sumDurations = function ({
59037  defaultDuration,
59038  durationList,
59039  startIndex,
59040  endIndex
59041}) {
59042  let durations = 0;
59043  if (startIndex > endIndex) {
59044    [startIndex, endIndex] = [endIndex, startIndex];
59045  }
59046  if (startIndex < 0) {
59047    for (let i = startIndex; i < Math.min(0, endIndex); i++) {
59048      durations += defaultDuration;
59049    }
59050    startIndex = 0;
59051  }
59052  for (let i = startIndex; i < endIndex; i++) {
59053    durations += durationList[i].duration;
59054  }
59055  return durations;
59056};
59057/**
59058 * Calculates the playlist end time
59059 *
59060 * @param {Object} playlist a media playlist object
59061 * @param {number=} expired the amount of time that has
59062 *                  dropped off the front of the playlist in a live scenario
59063 * @param {boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
59064 *                        playlist end calculation should consider the safe live end
59065 *                        (truncate the playlist end by three segments). This is normally
59066 *                        used for calculating the end of the playlist's seekable range.
59067 *                        This takes into account the value of liveEdgePadding.
59068 *                        Setting liveEdgePadding to 0 is equivalent to setting this to fal
vendor: 12,125 bytes, lines 59068-59411
59068se.
59069 * @param {number} liveEdgePadding a number indicating how far from the end of the playlist we should be in seconds.
59070 *                 If this is provided, it is used in the safe live end calculation.
59071 *                 Setting useSafeLiveEnd=false or liveEdgePadding=0 are equivalent.
59072 *                 Corresponds to suggestedPresentationDelay in DASH manifests.
59073 * @return {number} the end time of playlist
59074 * @function playlistEnd
59075 */
59076
59077const playlistEnd = function (playlist, expired, useSafeLiveEnd, liveEdgePadding) {
59078  if (!playlist || !playlist.segments) {
59079    return null;
59080  }
59081  if (playlist.endList) {
59082    return duration(playlist);
59083  }
59084  if (expired === null) {
59085    return null;
59086  }
59087  expired = expired || 0;
59088  let lastSegmentEndTime = intervalDuration(playlist, playlist.mediaSequence + playlist.segments.length, expired);
59089  if (useSafeLiveEnd) {
59090    liveEdgePadding = typeof liveEdgePadding === 'number' ? liveEdgePadding : liveEdgeDelay(null, playlist);
59091    lastSegmentEndTime -= liveEdgePadding;
59092  } // don't return a time less than zero
59093
59094  return Math.max(0, lastSegmentEndTime);
59095};
59096/**
59097  * Calculates the interval of time that is currently seekable in a
59098  * playlist. The returned time ranges are relative to the earliest
59099  * moment in the specified playlist that is still available. A full
59100  * seekable implementation for live streams would need to offset
59101  * these values by the duration of content that has expired from the
59102  * stream.
59103  *
59104  * @param {Object} playlist a media playlist object
59105  * dropped off the front of the playlist in a live scenario
59106  * @param {number=} expired the amount of time that has
59107  * dropped off the front of the playlist in a live scenario
59108  * @param {number} liveEdgePadding how far from the end of the playlist we should be in seconds.
59109  *        Corresponds to suggestedPresentationDelay in DASH manifests.
59110  * @return {TimeRanges} the periods of time that are valid targets
59111  * for seeking
59112  */
59113
59114const seekable = function (playlist, expired, liveEdgePadding) {
59115  const useSafeLiveEnd = true;
59116  const seekableStart = expired || 0;
59117  let seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding);
59118  if (seekableEnd === null) {
59119    return createTimeRanges();
59120  } // Clamp seekable end since it can not be less than the seekable start
59121
59122  if (seekableEnd < seekableStart) {
59123    seekableEnd = seekableStart;
59124  }
59125  return createTimeRanges(seekableStart, seekableEnd);
59126};
59127/**
59128 * Determine the index and estimated starting time of the segment that
59129 * contains a specified playback position in a media playlist.
59130 *
59131 * @param {Object} options.playlist the media playlist to query
59132 * @param {number} options.currentTime The number of seconds since the earliest
59133 * possible position to determine the containing segment for
59134 * @param {number} options.startTime the time when the segment/part starts
59135 * @param {number} options.startingSegmentIndex the segment index to start looking at.
59136 * @param {number?} [options.startingPartIndex] the part index to look at within the segment.
59137 *
59138 * @return {Object} an object with partIndex, segmentIndex, and startTime.
59139 */
59140
59141const getMediaInfoForTime = function ({
59142  playlist,
59143  currentTime,
59144  startingSegmentIndex,
59145  startingPartIndex,
59146  startTime,
59147  exactManifestTimings
59148}) {
59149  let time = currentTime - startTime;
59150  const partsAndSegments = getPartsAndSegments(playlist);
59151  let startIndex = 0;
59152  for (let i = 0; i < partsAndSegments.length; i++) {
59153    const partAndSegment = partsAndSegments[i];
59154    if (startingSegmentIndex !== partAndSegment.segmentIndex) {
59155      continue;
59156    } // skip this if part index does not match.
59157
59158    if (typeof startingPartIndex === 'number' && typeof partAndSegment.partIndex === 'number' && startingPartIndex !== partAndSegment.partIndex) {
59159      continue;
59160    }
59161    startIndex = i;
59162    break;
59163  }
59164  if (time < 0) {
59165    // Walk backward from startIndex in the playlist, adding durations
59166    // until we find a segment that contains `time` and return it
59167    if (startIndex > 0) {
59168      for (let i = startIndex - 1; i >= 0; i--) {
59169        const partAndSegment = partsAndSegments[i];
59170        time += partAndSegment.duration;
59171        if (exactManifestTimings) {
59172          if (time < 0) {
59173            continue;
59174          }
59175        } else if (time + TIME_FUDGE_FACTOR <= 0) {
59176          continue;
59177        }
59178        return {
59179          partIndex: partAndSegment.partIndex,
59180          segmentIndex: partAndSegment.segmentIndex,
59181          startTime: startTime - sumDurations({
59182            defaultDuration: playlist.targetDuration,
59183            durationList: partsAndSegments,
59184            startIndex,
59185            endIndex: i
59186          })
59187        };
59188      }
59189    } // We were unable to find a good segment within the playlist
59190    // so select the first segment
59191
59192    return {
59193      partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null,
59194      segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
59195      startTime: currentTime
59196    };
59197  } // When startIndex is negative, we first walk forward to first segment
59198  // adding target durations. If we "run out of time" before getting to
59199  // the first segment, return the first segment
59200
59201  if (startIndex < 0) {
59202    for (let i = startIndex; i < 0; i++) {
59203      time -= playlist.targetDuration;
59204      if (time < 0) {
59205        return {
59206          partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null,
59207          segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
59208          startTime: currentTime
59209        };
59210      }
59211    }
59212    startIndex = 0;
59213  } // Walk forward from startIndex in the playlist, subtracting durations
59214  // until we find a segment that contains `time` and return it
59215
59216  for (let i = startIndex; i < partsAndSegments.length; i++) {
59217    const partAndSegment = partsAndSegments[i];
59218    time -= partAndSegment.duration;
59219    const canUseFudgeFactor = partAndSegment.duration > TIME_FUDGE_FACTOR;
59220    const isExactlyAtTheEnd = time === 0;
59221    const isExtremelyCloseToTheEnd = canUseFudgeFactor && time + TIME_FUDGE_FACTOR >= 0;
59222    if (isExactlyAtTheEnd || isExtremelyCloseToTheEnd) {
59223      // 1) We are exactly at the end of the current segment.
59224      // 2) We are extremely close to the end of the current segment (The difference is less than  1 / 30).
59225      //    We may encounter this situation when
59226      //    we don't have exact match between segment duration info in the manifest and the actual duration of the segment
59227      //    For example:
59228      //    We appended 3 segments 10 seconds each, meaning we should have 30 sec buffered,
59229      //    but we the actual buffered is 29.99999
59230      //
59231      // In both cases:
59232      // if we passed current time -> it means that we already played current segment
59233      // if we passed buffered.end -> it means that this segment is already loaded and buffered
59234      // we should select the next segment if we have one:
59235      if (i !== partsAndSegments.length - 1) {
59236        continue;
59237      }
59238    }
59239    if (exactManifestTimings) {
59240      if (time > 0) {
59241        continue;
59242      }
59243    } else if (time - TIME_FUDGE_FACTOR >= 0) {
59244      continue;
59245    }
59246    return {
59247      partIndex: partAndSegment.partIndex,
59248      segmentIndex: partAndSegment.segmentIndex,
59249      startTime: startTime + sumDurations({
59250        defaultDuration: playlist.targetDuration,
59251        durationList: partsAndSegments,
59252        startIndex,
59253        endIndex: i
59254      })
59255    };
59256  } // We are out of possible candidates so load the last one...
59257
59258  return {
59259    segmentIndex: partsAndSegments[partsAndSegments.length - 1].segmentIndex,
59260    partIndex: partsAndSegments[partsAndSegments.length - 1].partIndex,
59261    startTime: currentTime
59262  };
59263};
59264/**
59265 * Check whether the playlist is excluded or not.
59266 *
59267 * @param {Object} playlist the media playlist object
59268 * @return {boolean} whether the playlist is excluded or not
59269 * @function isExcluded
59270 */
59271
59272const isExcluded = function (playlist) {
59273  return playlist.excludeUntil && playlist.excludeUntil > Date.now();
59274};
59275/**
59276 * Check whether the playlist is compatible with current playback configuration or has
59277 * been excluded permanently for being incompatible.
59278 *
59279 * @param {Object} playlist the media playlist object
59280 * @return {boolean} whether the playlist is incompatible or not
59281 * @function isIncompatible
59282 */
59283
59284const isIncompatible = function (playlist) {
59285  return playlist.excludeUntil && playlist.excludeUntil === Infinity;
59286};
59287/**
59288 * Check whether the playlist is enabled or not.
59289 *
59290 * @param {Object} playlist the media playlist object
59291 * @return {boolean} whether the playlist is enabled or not
59292 * @function isEnabled
59293 */
59294
59295const isEnabled = function (playlist) {
59296  const excluded = isExcluded(playlist);
59297  return !playlist.disabled && !excluded;
59298};
59299/**
59300 * Check whether the playlist has been manually disabled through the representations api.
59301 *
59302 * @param {Object} playlist the media playlist object
59303 * @return {boolean} whether the playlist is disabled manually or not
59304 * @function isDisabled
59305 */
59306
59307const isDisabled = function (playlist) {
59308  return playlist.disabled;
59309};
59310/**
59311 * Returns whether the current playlist is an AES encrypted HLS stream
59312 *
59313 * @return {boolean} true if it's an AES encrypted HLS stream
59314 */
59315
59316const isAes = function (media) {
59317  for (let i = 0; i < media.segments.length; i++) {
59318    if (media.segments[i].key) {
59319      return true;
59320    }
59321  }
59322  return false;
59323};
59324/**
59325 * Checks if the playlist has a value for the specified attribute
59326 *
59327 * @param {string} attr
59328 *        Attribute to check for
59329 * @param {Object} playlist
59330 *        The media playlist object
59331 * @return {boolean}
59332 *         Whether the playlist contains a value for the attribute or not
59333 * @function hasAttribute
59334 */
59335
59336const hasAttribute = function (attr, playlist) {
59337  return playlist.attributes && playlist.attributes[attr];
59338};
59339/**
59340 * Estimates the time required to complete a segment download from the specified playlist
59341 *
59342 * @param {number} segmentDuration
59343 *        Duration of requested segment
59344 * @param {number} bandwidth
59345 *        Current measured bandwidth of the player
59346 * @param {Object} playlist
59347 *        The media playlist object
59348 * @param {number=} bytesReceived
59349 *        Number of bytes already received for the request. Defaults to 0
59350 * @return {number|NaN}
59351 *         The estimated time to request the segment. NaN if bandwidth information for
59352 *         the given playlist is unavailable
59353 * @function estimateSegmentRequestTime
59354 */
59355
59356const estimateSegmentRequestTime = function (segmentDuration, bandwidth, playlist, bytesReceived = 0) {
59357  if (!hasAttribute('BANDWIDTH', playlist)) {
59358    return NaN;
59359  }
59360  const size = segmentDuration * playlist.attributes.BANDWIDTH;
59361  return (size - bytesReceived * 8) / bandwidth;
59362};
59363/*
59364 * Returns whether the current playlist is the lowest rendition
59365 *
59366 * @return {Boolean} true if on lowest rendition
59367 */
59368
59369const isLowestEnabledRendition = (main, media) => {
59370  if (main.playlists.length === 1) {
59371    return true;
59372  }
59373  const currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
59374  return main.playlists.filter(playlist => {
59375    if (!isEnabled(playlist)) {
59376      return false;
59377    }
59378    return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
59379  }).length === 0;
59380};
59381const playlistMatch = (a, b) => {
59382  // both playlits are null
59383  // or only one playlist is non-null
59384  // no match
59385  if (!a && !b || !a && b || a && !b) {
59386    return false;
59387  } // playlist objects are the same, match
59388
59389  if (a === b) {
59390    return true;
59391  } // first try to use id as it should be the most
59392  // accurate
59393
59394  if (a.id && b.id && a.id === b.id) {
59395    return true;
59396  } // next try to use reslovedUri as it should be the
59397  // second most accurate.
59398
59399  if (a.resolvedUri && b.resolvedUri && a.resolvedUri === b.resolvedUri) {
59400    return true;
59401  } // finally try to use uri as it should be accurate
59402  // but might miss a few cases for relative uris
59403
59404  if (a.uri && b.uri && a.uri === b.uri) {
59405    return true;
59406  }
59407  return false;
59408};
59409const someAudioVariant = function (main, callback) {
59410  const AUDIO = main && main.mediaGroups && main.mediaGroups.AUDIO || {};
59411  let found = false;
vendor: 5,003 bytes, lines 59412-59575
59412  for (const groupName in AUDIO) {
59413    for (const label in AUDIO[groupName]) {
59414      found = callback(AUDIO[groupName][label]);
59415      if (found) {
59416        break;
59417      }
59418    }
59419    if (found) {
59420      break;
59421    }
59422  }
59423  return !!found;
59424};
59425const isAudioOnly = main => {
59426  // we are audio only if we have no main playlists but do
59427  // have media group playlists.
59428  if (!main || !main.playlists || !main.playlists.length) {
59429    // without audio variants or playlists this
59430    // is not an audio only main.
59431    const found = someAudioVariant(main, variant => variant.playlists && variant.playlists.length || variant.uri);
59432    return found;
59433  } // if every playlist has only an audio codec it is audio only
59434
59435  for (let i = 0; i < main.playlists.length; i++) {
59436    const playlist = main.playlists[i];
59437    const CODECS = playlist.attributes && playlist.attributes.CODECS; // all codecs are audio, this is an audio playlist.
59438
59439    if (CODECS && CODECS.split(',').every(c => codecs_js.isAudioCodec(c))) {
59440      continue;
59441    } // playlist is in an audio group it is audio only
59442
59443    const found = someAudioVariant(main, variant => playlistMatch(playlist, variant));
59444    if (found) {
59445      continue;
59446    } // if we make it here this playlist isn't audio and we
59447    // are not audio only
59448
59449    return false;
59450  } // if we make it past every playlist without returning, then
59451  // this is an audio only playlist.
59452
59453  return true;
59454}; // exports
59455
59456var Playlist = {
59457  liveEdgeDelay,
59458  duration,
59459  seekable,
59460  getMediaInfoForTime,
59461  isEnabled,
59462  isDisabled,
59463  isExcluded,
59464  isIncompatible,
59465  playlistEnd,
59466  isAes,
59467  hasAttribute,
59468  estimateSegmentRequestTime,
59469  isLowestEnabledRendition,
59470  isAudioOnly,
59471  playlistMatch,
59472  segmentDurationWithParts
59473};
59474const {
59475  log
59476} = videojs;
59477const createPlaylistID = (index, uri) => {
59478  return `${index}-${uri}`;
59479}; // default function for creating a group id
59480
59481const groupID = (type, group, label) => {
59482  return `placeholder-uri-${type}-${group}-${label}`;
59483};
59484/**
59485 * Parses a given m3u8 playlist
59486 *
59487 * @param {Function} [onwarn]
59488 *        a function to call when the parser triggers a warning event.
59489 * @param {Function} [oninfo]
59490 *        a function to call when the parser triggers an info event.
59491 * @param {string} manifestString
59492 *        The downloaded manifest string
59493 * @param {Object[]} [customTagParsers]
59494 *        An array of custom tag parsers for the m3u8-parser instance
59495 * @param {Object[]} [customTagMappers]
59496 *        An array of custom tag mappers for the m3u8-parser instance
59497 * @param {boolean} [llhls]
59498 *        Whether to keep ll-hls features in the manifest after parsing.
59499 * @return {Object}
59500 *         The manifest object
59501 */
59502
59503const parseManifest = ({
59504  onwarn,
59505  oninfo,
59506  manifestString,
59507  customTagParsers = [],
59508  customTagMappers = [],
59509  llhls
59510}) => {
59511  const parser = new m3u8Parser.Parser();
59512  if (onwarn) {
59513    parser.on('warn', onwarn);
59514  }
59515  if (oninfo) {
59516    parser.on('info', oninfo);
59517  }
59518  customTagParsers.forEach(customParser => parser.addParser(customParser));
59519  customTagMappers.forEach(mapper => parser.addTagMapper(mapper));
59520  parser.push(manifestString);
59521  parser.end();
59522  const manifest = parser.manifest; // remove llhls features from the parsed manifest
59523  // if we don't want llhls support.
59524
59525  if (!llhls) {
59526    ['preloadSegment', 'skip', 'serverControl', 'renditionReports', 'partInf', 'partTargetDuration'].forEach(function (k) {
59527      if (manifest.hasOwnProperty(k)) {
59528        delete manifest[k];
59529      }
59530    });
59531    if (manifest.segments) {
59532      manifest.segments.forEach(function (segment) {
59533        ['parts', 'preloadHints'].forEach(function (k) {
59534          if (segment.hasOwnProperty(k)) {
59535            delete segment[k];
59536          }
59537        });
59538      });
59539    }
59540  }
59541  if (!manifest.targetDuration) {
59542    let targetDuration = 10;
59543    if (manifest.segments && manifest.segments.length) {
59544      targetDuration = manifest.segments.reduce((acc, s) => Math.max(acc, s.duration), 0);
59545    }
59546    if (onwarn) {
59547      onwarn({
59548        message: `manifest has no targetDuration defaulting to ${targetDuration}`
59549      });
59550    }
59551    manifest.targetDuration = targetDuration;
59552  }
59553  const parts = getLastParts(manifest);
59554  if (parts.length && !manifest.partTargetDuration) {
59555    const partTargetDuration = parts.reduce((acc, p) => Math.max(acc, p.duration), 0);
59556    if (onwarn) {
59557      onwarn({
59558        message: `manifest has no partTargetDuration defaulting to ${partTargetDuration}`
59559      });
59560      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.');
59561    }
59562    manifest.partTargetDuration = partTargetDuration;
59563  }
59564  return manifest;
59565};
59566/**
59567 * Loops through all supported media groups in main and calls the provided
59568 * callback for each group
59569 *
59570 * @param {Object} main
59571 *        The parsed main manifest object
59572 * @param {Function} callback
59573 *        Callback to call for each media group
59574 */
59575
vendor: 1,226 bytes, lines 59576-59616
59576const forEachMediaGroup = (main, callback) => {
59577  if (!main.mediaGroups) {
59578    return;
59579  }
59580  ['AUDIO', 'SUBTITLES'].forEach(mediaType => {
59581    if (!main.mediaGroups[mediaType]) {
59582      return;
59583    }
59584    for (const groupKey in main.mediaGroups[mediaType]) {
59585      for (const labelKey in main.mediaGroups[mediaType][groupKey]) {
59586        const mediaProperties = main.mediaGroups[mediaType][groupKey][labelKey];
59587        callback(mediaProperties, mediaType, groupKey, labelKey);
59588      }
59589    }
59590  });
59591};
59592/**
59593 * Adds properties and attributes to the playlist to keep consistent functionality for
59594 * playlists throughout VHS.
59595 *
59596 * @param {Object} config
59597 *        Arguments object
59598 * @param {Object} config.playlist
59599 *        The media playlist
59600 * @param {string} [config.uri]
59601 *        The uri to the media playlist (if media playlist is not from within a main
59602 *        playlist)
59603 * @param {string} id
59604 *        ID to use for the playlist
59605 */
59606
59607const setupMediaPlaylist = ({
59608  playlist,
59609  uri,
59610  id
59611}) => {
59612  playlist.id = id;
59613  playlist.playlistErrors_ = 0;
59614  if (uri) {
59615    // For media playlists, m3u8-parser does not have access to a URI, as HLS media
59616    // playlists do not contain their own source URI, but one is needed for c
vendor: 17,018 bytes, lines 59616-60095
59616onsistency in
59617    // VHS.
59618    playlist.uri = uri;
59619  } // For HLS main playlists, even though certain attributes MUST be defined, the
59620  // stream may still be played without them.
59621  // For HLS media playlists, m3u8-parser does not attach an attributes object to the
59622  // manifest.
59623  //
59624  // To avoid undefined reference errors through the project, and make the code easier
59625  // to write/read, add an empty attributes object for these cases.
59626
59627  playlist.attributes = playlist.attributes || {};
59628};
59629/**
59630 * Adds ID, resolvedUri, and attributes properties to each playlist of the main, where
59631 * necessary. In addition, creates playlist IDs for each playlist and adds playlist ID to
59632 * playlist references to the playlists array.
59633 *
59634 * @param {Object} main
59635 *        The main playlist
59636 */
59637
59638const setupMediaPlaylists = main => {
59639  let i = main.playlists.length;
59640  while (i--) {
59641    const playlist = main.playlists[i];
59642    setupMediaPlaylist({
59643      playlist,
59644      id: createPlaylistID(i, playlist.uri)
59645    });
59646    playlist.resolvedUri = resolveUrl(main.uri, playlist.uri);
59647    main.playlists[playlist.id] = playlist; // URI reference added for backwards compatibility
59648
59649    main.playlists[playlist.uri] = playlist; // Although the spec states an #EXT-X-STREAM-INF tag MUST have a BANDWIDTH attribute,
59650    // the stream can be played without it. Although an attributes property may have been
59651    // added to the playlist to prevent undefined references, issue a warning to fix the
59652    // manifest.
59653
59654    if (!playlist.attributes.BANDWIDTH) {
59655      log.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
59656    }
59657  }
59658};
59659/**
59660 * Adds resolvedUri properties to each media group.
59661 *
59662 * @param {Object} main
59663 *        The main playlist
59664 */
59665
59666const resolveMediaGroupUris = main => {
59667  forEachMediaGroup(main, properties => {
59668    if (properties.uri) {
59669      properties.resolvedUri = resolveUrl(main.uri, properties.uri);
59670    }
59671  });
59672};
59673/**
59674 * Creates a main playlist wrapper to insert a sole media playlist into.
59675 *
59676 * @param {Object} media
59677 *        Media playlist
59678 * @param {string} uri
59679 *        The media URI
59680 *
59681 * @return {Object}
59682 *         main playlist
59683 */
59684
59685const mainForMedia = (media, uri) => {
59686  const id = createPlaylistID(0, uri);
59687  const main = {
59688    mediaGroups: {
59689      'AUDIO': {},
59690      'VIDEO': {},
59691      'CLOSED-CAPTIONS': {},
59692      'SUBTITLES': {}
59693    },
59694    uri: window__default["default"].location.href,
59695    resolvedUri: window__default["default"].location.href,
59696    playlists: [{
59697      uri,
59698      id,
59699      resolvedUri: uri,
59700      // m3u8-parser does not attach an attributes property to media playlists so make
59701      // sure that the property is attached to avoid undefined reference errors
59702      attributes: {}
59703    }]
59704  }; // set up ID reference
59705
59706  main.playlists[id] = main.playlists[0]; // URI reference added for backwards compatibility
59707
59708  main.playlists[uri] = main.playlists[0];
59709  return main;
59710};
59711/**
59712 * Does an in-place update of the main manifest to add updated playlist URI references
59713 * as well as other properties needed by VHS that aren't included by the parser.
59714 *
59715 * @param {Object} main
59716 *        main manifest object
59717 * @param {string} uri
59718 *        The source URI
59719 * @param {function} createGroupID
59720 *        A function to determine how to create the groupID for mediaGroups
59721 */
59722
59723const addPropertiesToMain = (main, uri, createGroupID = groupID) => {
59724  main.uri = uri;
59725  for (let i = 0; i < main.playlists.length; i++) {
59726    if (!main.playlists[i].uri) {
59727      // Set up phony URIs for the playlists since playlists are referenced by their URIs
59728      // throughout VHS, but some formats (e.g., DASH) don't have external URIs
59729      // TODO: consider adding dummy URIs in mpd-parser
59730      const phonyUri = `placeholder-uri-${i}`;
59731      main.playlists[i].uri = phonyUri;
59732    }
59733  }
59734  const audioOnlyMain = isAudioOnly(main);
59735  forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
59736    // add a playlist array under properties
59737    if (!properties.playlists || !properties.playlists.length) {
59738      // If the manifest is audio only and this media group does not have a uri, check
59739      // if the media group is located in the main list of playlists. If it is, don't add
59740      // placeholder properties as it shouldn't be considered an alternate audio track.
59741      if (audioOnlyMain && mediaType === 'AUDIO' && !properties.uri) {
59742        for (let i = 0; i < main.playlists.length; i++) {
59743          const p = main.playlists[i];
59744          if (p.attributes && p.attributes.AUDIO && p.attributes.AUDIO === groupKey) {
59745            return;
59746          }
59747        }
59748      }
59749      properties.playlists = [_extends__default["default"]({}, properties)];
59750    }
59751    properties.playlists.forEach(function (p, i) {
59752      const groupId = createGroupID(mediaType, groupKey, labelKey, p);
59753      const id = createPlaylistID(i, groupId);
59754      if (p.uri) {
59755        p.resolvedUri = p.resolvedUri || resolveUrl(main.uri, p.uri);
59756      } else {
59757        // DEPRECATED, this has been added to prevent a breaking change.
59758        // previously we only ever had a single media group playlist, so
59759        // we mark the first playlist uri without prepending the index as we used to
59760        // ideally we would do all of the playlists the same way.
59761        p.uri = i === 0 ? groupId : id; // don't resolve a placeholder uri to an absolute url, just use
59762        // the placeholder again
59763
59764        p.resolvedUri = p.uri;
59765      }
59766      p.id = p.id || id; // add an empty attributes object, all playlists are
59767      // expected to have this.
59768
59769      p.attributes = p.attributes || {}; // setup ID and URI references (URI for backwards compatibility)
59770
59771      main.playlists[p.id] = p;
59772      main.playlists[p.uri] = p;
59773    });
59774  });
59775  setupMediaPlaylists(main);
59776  resolveMediaGroupUris(main);
59777};
59778class DateRangesStorage {
59779  constructor() {
59780    this.offset_ = null;
59781    this.pendingDateRanges_ = new Map();
59782    this.processedDateRanges_ = new Map();
59783  }
59784  setOffset(segments = []) {
59785    // already set
59786    if (this.offset_ !== null) {
59787      return;
59788    } // no segment to process
59789
59790    if (!segments.length) {
59791      return;
59792    }
59793    const [firstSegment] = segments; // no program date time
59794
59795    if (firstSegment.programDateTime === undefined) {
59796      return;
59797    } // Set offset as ProgramDateTime for the very first segment of the very first playlist load:
59798
59799    this.offset_ = firstSegment.programDateTime / 1000;
59800  }
59801  setPendingDateRanges(dateRanges = []) {
59802    if (!dateRanges.length) {
59803      return;
59804    }
59805    const [dateRange] = dateRanges;
59806    const startTime = dateRange.startDate.getTime();
59807    this.trimProcessedDateRanges_(startTime);
59808    this.pendingDateRanges_ = dateRanges.reduce((map, pendingDateRange) => {
59809      map.set(pendingDateRange.id, pendingDateRange);
59810      return map;
59811    }, new Map());
59812  }
59813  processDateRange(dateRange) {
59814    this.pendingDateRanges_.delete(dateRange.id);
59815    this.processedDateRanges_.set(dateRange.id, dateRange);
59816  }
59817  getDateRangesToProcess() {
59818    if (this.offset_ === null) {
59819      return [];
59820    }
59821    const dateRangeClasses = {};
59822    const dateRangesToProcess = [];
59823    this.pendingDateRanges_.forEach((dateRange, id) => {
59824      if (this.processedDateRanges_.has(id)) {
59825        return;
59826      }
59827      dateRange.startTime = dateRange.startDate.getTime() / 1000 - this.offset_;
59828      dateRange.processDateRange = () => this.processDateRange(dateRange);
59829      dateRangesToProcess.push(dateRange);
59830      if (!dateRange.class) {
59831        return;
59832      }
59833      if (dateRangeClasses[dateRange.class]) {
59834        const length = dateRangeClasses[dateRange.class].push(dateRange);
59835        dateRange.classListIndex = length - 1;
59836      } else {
59837        dateRangeClasses[dateRange.class] = [dateRange];
59838        dateRange.classListIndex = 0;
59839      }
59840    });
59841    for (const dateRange of dateRangesToProcess) {
59842      const classList = dateRangeClasses[dateRange.class] || [];
59843      if (dateRange.endDate) {
59844        dateRange.endTime = dateRange.endDate.getTime() / 1000 - this.offset_;
59845      } else if (dateRange.endOnNext && classList[dateRange.classListIndex + 1]) {
59846        dateRange.endTime = classList[dateRange.classListIndex + 1].startTime;
59847      } else if (dateRange.duration) {
59848        dateRange.endTime = dateRange.startTime + dateRange.duration;
59849      } else if (dateRange.plannedDuration) {
59850        dateRange.endTime = dateRange.startTime + dateRange.plannedDuration;
59851      } else {
59852        dateRange.endTime = dateRange.startTime;
59853      }
59854    }
59855    return dateRangesToProcess;
59856  }
59857  trimProcessedDateRanges_(startTime) {
59858    const copy = new Map(this.processedDateRanges_);
59859    copy.forEach((dateRange, id) => {
59860      if (dateRange.startDate.getTime() < startTime) {
59861        this.processedDateRanges_.delete(id);
59862      }
59863    });
59864  }
59865}
59866const QUOTA_EXCEEDED_ERR = 22;
59867const getStreamingNetworkErrorMetadata = ({
59868  requestType,
59869  request,
59870  error,
59871  parseFailure
59872}) => {
59873  const isBadStatus = request.status < 200 || request.status > 299;
59874  const isFailure = request.status >= 400 && request.status <= 499;
59875  const errorMetadata = {
59876    uri: request.uri,
59877    requestType
59878  };
59879  const isBadStatusOrParseFailure = isBadStatus && !isFailure || parseFailure;
59880  if (error && isFailure) {
59881    // copy original error and add to the metadata.
59882    errorMetadata.error = _extends__default["default"]({}, error);
59883    errorMetadata.errorType = videojs.Error.NetworkRequestFailed;
59884  } else if (request.aborted) {
59885    errorMetadata.errorType = videojs.Error.NetworkRequestAborted;
59886  } else if (request.timedout) {
59887    errorMetadata.errorType = videojs.Error.NetworkRequestTimeout;
59888  } else if (isBadStatusOrParseFailure) {
59889    const errorType = parseFailure ? videojs.Error.NetworkBodyParserFailed : videojs.Error.NetworkBadStatus;
59890    errorMetadata.errorType = errorType;
59891    errorMetadata.status = request.status;
59892    errorMetadata.headers = request.headers;
59893  }
59894  return errorMetadata;
59895};
59896
59897/**
59898 * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
59899 * codec strings, or translating codec strings into objects that can be examined.
59900 */
59901const logFn$1 = logger('CodecUtils');
59902/**
59903 * Returns a set of codec strings parsed from the playlist or the default
59904 * codec strings if no codecs were specified in the playlist
59905 *
59906 * @param {Playlist} media the current media playlist
59907 * @return {Object} an object with the video and audio codecs
59908 */
59909
59910const getCodecs = function (media) {
59911  // if the codecs were explicitly specified, use them instead of the
59912  // defaults
59913  const mediaAttributes = media.attributes || {};
59914  if (mediaAttributes.CODECS) {
59915    return codecs_js.parseCodecs(mediaAttributes.CODECS);
59916  }
59917};
59918const isMaat = (main, media) => {
59919  const mediaAttributes = media.attributes || {};
59920  return main && main.mediaGroups && main.mediaGroups.AUDIO && mediaAttributes.AUDIO && main.mediaGroups.AUDIO[mediaAttributes.AUDIO];
59921};
59922const isMuxed = (main, media) => {
59923  if (!isMaat(main, media)) {
59924    return true;
59925  }
59926  const mediaAttributes = media.attributes || {};
59927  const audioGroup = main.mediaGroups.AUDIO[mediaAttributes.AUDIO];
59928  for (const groupId in audioGroup) {
59929    // If an audio group has a URI (the case for HLS, as HLS will use external playlists),
59930    // or there are listed playlists (the case for DASH, as the manifest will have already
59931    // provided all of the details necessary to generate the audio playlist, as opposed to
59932    // HLS' externally requested playlists), then the content is demuxed.
59933    if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
59934      return true;
59935    }
59936  }
59937  return false;
59938};
59939const unwrapCodecList = function (codecList) {
59940  const codecs = {};
59941  codecList.forEach(({
59942    mediaType,
59943    type,
59944    details
59945  }) => {
59946    codecs[mediaType] = codecs[mediaType] || [];
59947    codecs[mediaType].push(codecs_js.translateLegacyCodec(`${type}${details}`));
59948  });
59949  Object.keys(codecs).forEach(function (mediaType) {
59950    if (codecs[mediaType].length > 1) {
59951      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.`);
59952      codecs[mediaType] = null;
59953      return;
59954    }
59955    codecs[mediaType] = codecs[mediaType][0];
59956  });
59957  return codecs;
59958};
59959const codecCount = function (codecObj) {
59960  let count = 0;
59961  if (codecObj.audio) {
59962    count++;
59963  }
59964  if (codecObj.video) {
59965    count++;
59966  }
59967  return count;
59968};
59969/**
59970 * Calculates the codec strings for a working configuration of
59971 * SourceBuffers to play variant streams in a main playlist. If
59972 * there is no possible working configuration, an empty object will be
59973 * returned.
59974 *
59975 * @param main {Object} the m3u8 object for the main playlist
59976 * @param media {Object} the m3u8 object for the variant playlist
59977 * @return {Object} the codec strings.
59978 *
59979 * @private
59980 */
59981
59982const codecsForPlaylist = function (main, media) {
59983  const mediaAttributes = media.attributes || {};
59984  const codecInfo = unwrapCodecList(getCodecs(media) || []); // HLS with multiple-audio tracks must always get an audio codec.
59985  // Put another way, there is no way to have a video-only multiple-audio HLS!
59986
59987  if (isMaat(main, media) && !codecInfo.audio) {
59988    if (!isMuxed(main, media)) {
59989      // It is possible for codecs to be specified on the audio media group playlist but
59990      // not on the rendition playlist. This is mostly the case for DASH, where audio and
59991      // video are always separate (and separately specified).
59992      const defaultCodecs = unwrapCodecList(codecs_js.codecsFromDefault(main, mediaAttributes.AUDIO) || []);
59993      if (defaultCodecs.audio) {
59994        codecInfo.audio = defaultCodecs.audio;
59995      }
59996    }
59997  }
59998  return codecInfo;
59999};
60000const {
60001  EventTarget: EventTarget$1
60002} = videojs;
60003const addLLHLSQueryDirectives = (uri, media) => {
60004  if (media.endList || !media.serverControl) {
60005    return uri;
60006  }
60007  const parameters = {};
60008  if (media.serverControl.canBlockReload) {
60009    const {
60010      preloadSegment
60011    } = media; // next msn is a zero based value, length is not.
60012
60013    let nextMSN = media.mediaSequence + media.segments.length; // If preload segment has parts then it is likely
60014    // that we are going to request a part of that preload segment.
60015    // the logic below is used to determine that.
60016
60017    if (preloadSegment) {
60018      const parts = preloadSegment.parts || []; // _HLS_part is a zero based index
60019
60020      const nextPart = getKnownPartCount(media) - 1; // if nextPart is > -1 and not equal to just the
60021      // length of parts, then we know we had part preload hints
60022      // and we need to add the _HLS_part= query
60023
60024      if (nextPart > -1 && nextPart !== parts.length - 1) {
60025        // add existing parts to our preload hints
60026        // eslint-disable-next-line
60027        parameters._HLS_part = nextPart;
60028      } // this if statement makes sure that we request the msn
60029      // of the preload segment if:
60030      // 1. the preload segment had parts (and was not yet a full segment)
60031      //    but was added to our segments array
60032      // 2. the preload segment had preload hints for parts that are not in
60033      //    the manifest yet.
60034      // in all other cases we want the segment after the preload segment
60035      // which will be given by using media.segments.length because it is 1 based
60036      // rather than 0 based.
60037
60038      if (nextPart > -1 || parts.length) {
60039        nextMSN--;
60040      }
60041    } // add _HLS_msn= in front of any _HLS_part query
60042    // eslint-disable-next-line
60043
60044    parameters._HLS_msn = nextMSN;
60045  }
60046  if (media.serverControl && media.serverControl.canSkipUntil) {
60047    // add _HLS_skip= infront of all other queries.
60048    // eslint-disable-next-line
60049    parameters._HLS_skip = media.serverControl.canSkipDateranges ? 'v2' : 'YES';
60050  }
60051  if (Object.keys(parameters).length) {
60052    const parsedUri = new window__default["default"].URL(uri);
60053    ['_HLS_skip', '_HLS_msn', '_HLS_part'].forEach(function (name) {
60054      if (!parameters.hasOwnProperty(name)) {
60055        return;
60056      }
60057      parsedUri.searchParams.set(name, parameters[name]);
60058    });
60059    uri = parsedUri.toString();
60060  }
60061  return uri;
60062};
60063/**
60064 * Returns a new segment object with properties and
60065 * the parts array merged.
60066 *
60067 * @param {Object} a the old segment
60068 * @param {Object} b the new segment
60069 *
60070 * @return {Object} the merged segment
60071 */
60072
60073const updateSegment = (a, b) => {
60074  if (!a) {
60075    return b;
60076  }
60077  const result = merge(a, b); // if only the old segment has preload hints
60078  // and the new one does not, remove preload hints.
60079
60080  if (a.preloadHints && !b.preloadHints) {
60081    delete result.preloadHints;
60082  } // if only the old segment has parts
60083  // then the parts are no longer valid
60084
60085  if (a.parts && !b.parts) {
60086    delete result.parts; // if both segments have parts
60087    // copy part propeties from the old segment
60088    // to the new one.
60089  } else if (a.parts && b.parts) {
60090    for (let i = 0; i < b.parts.length; i++) {
60091      if (a.parts && a.parts[i]) {
60092        result.parts[i] = merge(a.parts[i], b.parts[i]);
60093      }
60094    }
60095  }
vendor: 13,298 bytes, lines 60095-60482
60095 // set skipped to false for segments that have
60096  // have had information merged from the old segment.
60097
60098  if (!a.skipped && b.skipped) {
60099    result.skipped = false;
60100  } // set preload to false for segments that have
60101  // had information added in the new segment.
60102
60103  if (a.preload && !b.preload) {
60104    result.preload = false;
60105  }
60106  return result;
60107};
60108/**
60109 * Returns a new array of segments that is the result of merging
60110 * properties from an older list of segments onto an updated
60111 * list. No properties on the updated playlist will be ovewritten.
60112 *
60113 * @param {Array} original the outdated list of segments
60114 * @param {Array} update the updated list of segments
60115 * @param {number=} offset the index of the first update
60116 * segment in the original segment list. For non-live playlists,
60117 * this should always be zero and does not need to be
60118 * specified. For live playlists, it should be the difference
60119 * between the media sequence numbers in the original and updated
60120 * playlists.
60121 * @return {Array} a list of merged segment objects
60122 */
60123
60124const updateSegments = (original, update, offset) => {
60125  const oldSegments = original.slice();
60126  const newSegments = update.slice();
60127  offset = offset || 0;
60128  const result = [];
60129  let currentMap;
60130  for (let newIndex = 0; newIndex < newSegments.length; newIndex++) {
60131    const oldSegment = oldSegments[newIndex + offset];
60132    const newSegment = newSegments[newIndex];
60133    if (oldSegment) {
60134      currentMap = oldSegment.map || currentMap;
60135      result.push(updateSegment(oldSegment, newSegment));
60136    } else {
60137      // carry over map to new segment if it is missing
60138      if (currentMap && !newSegment.map) {
60139        newSegment.map = currentMap;
60140      }
60141      result.push(newSegment);
60142    }
60143  }
60144  return result;
60145};
60146const resolveSegmentUris = (segment, baseUri) => {
60147  // preloadSegment will not have a uri at all
60148  // as the segment isn't actually in the manifest yet, only parts
60149  if (!segment.resolvedUri && segment.uri) {
60150    segment.resolvedUri = resolveUrl(baseUri, segment.uri);
60151  }
60152  if (segment.key && !segment.key.resolvedUri) {
60153    segment.key.resolvedUri = resolveUrl(baseUri, segment.key.uri);
60154  }
60155  if (segment.map && !segment.map.resolvedUri) {
60156    segment.map.resolvedUri = resolveUrl(baseUri, segment.map.uri);
60157  }
60158  if (segment.map && segment.map.key && !segment.map.key.resolvedUri) {
60159    segment.map.key.resolvedUri = resolveUrl(baseUri, segment.map.key.uri);
60160  }
60161  if (segment.parts && segment.parts.length) {
60162    segment.parts.forEach(p => {
60163      if (p.resolvedUri) {
60164        return;
60165      }
60166      p.resolvedUri = resolveUrl(baseUri, p.uri);
60167    });
60168  }
60169  if (segment.preloadHints && segment.preloadHints.length) {
60170    segment.preloadHints.forEach(p => {
60171      if (p.resolvedUri) {
60172        return;
60173      }
60174      p.resolvedUri = resolveUrl(baseUri, p.uri);
60175    });
60176  }
60177};
60178const getAllSegments = function (media) {
60179  const segments = media.segments || [];
60180  const preloadSegment = media.preloadSegment; // a preloadSegment with only preloadHints is not currently
60181  // a usable segment, only include a preloadSegment that has
60182  // parts.
60183
60184  if (preloadSegment && preloadSegment.parts && preloadSegment.parts.length) {
60185    // if preloadHints has a MAP that means that the
60186    // init segment is going to change. We cannot use any of the parts
60187    // from this preload segment.
60188    if (preloadSegment.preloadHints) {
60189      for (let i = 0; i < preloadSegment.preloadHints.length; i++) {
60190        if (preloadSegment.preloadHints[i].type === 'MAP') {
60191          return segments;
60192        }
60193      }
60194    } // set the duration for our preload segment to target duration.
60195
60196    preloadSegment.duration = media.targetDuration;
60197    preloadSegment.preload = true;
60198    segments.push(preloadSegment);
60199  }
60200  return segments;
60201}; // consider the playlist unchanged if the playlist object is the same or
60202// the number of segments is equal, the media sequence number is unchanged,
60203// and this playlist hasn't become the end of the playlist
60204
60205const 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;
60206/**
60207  * Returns a new main playlist that is the result of merging an
60208  * updated media playlist into the original version. If the
60209  * updated media playlist does not match any of the playlist
60210  * entries in the original main playlist, null is returned.
60211  *
60212  * @param {Object} main a parsed main M3U8 object
60213  * @param {Object} media a parsed media M3U8 object
60214  * @return {Object} a new object that represents the original
60215  * main playlist with the updated media playlist merged in, or
60216  * null if the merge produced no change.
60217  */
60218
60219const updateMain$1 = (main, newMedia, unchangedCheck = isPlaylistUnchanged) => {
60220  const result = merge(main, {});
60221  const oldMedia = result.playlists[newMedia.id];
60222  if (!oldMedia) {
60223    return null;
60224  }
60225  if (unchangedCheck(oldMedia, newMedia)) {
60226    return null;
60227  }
60228  newMedia.segments = getAllSegments(newMedia);
60229  const mergedPlaylist = merge(oldMedia, newMedia); // always use the new media's preload segment
60230
60231  if (mergedPlaylist.preloadSegment && !newMedia.preloadSegment) {
60232    delete mergedPlaylist.preloadSegment;
60233  } // if the update could overlap existing segment information, merge the two segment lists
60234
60235  if (oldMedia.segments) {
60236    if (newMedia.skip) {
60237      newMedia.segments = newMedia.segments || []; // add back in objects for skipped segments, so that we merge
60238      // old properties into the new segments
60239
60240      for (let i = 0; i < newMedia.skip.skippedSegments; i++) {
60241        newMedia.segments.unshift({
60242          skipped: true
60243        });
60244      }
60245    }
60246    mergedPlaylist.segments = updateSegments(oldMedia.segments, newMedia.segments, newMedia.mediaSequence - oldMedia.mediaSequence);
60247  } // resolve any segment URIs to prevent us from having to do it later
60248
60249  mergedPlaylist.segments.forEach(segment => {
60250    resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
60251  }); // TODO Right now in the playlists array there are two references to each playlist, one
60252  // that is referenced by index, and one by URI. The index reference may no longer be
60253  // necessary.
60254
60255  for (let i = 0; i < result.playlists.length; i++) {
60256    if (result.playlists[i].id === newMedia.id) {
60257      result.playlists[i] = mergedPlaylist;
60258    }
60259  }
60260  result.playlists[newMedia.id] = mergedPlaylist; // URI reference added for backwards compatibility
60261
60262  result.playlists[newMedia.uri] = mergedPlaylist; // update media group playlist references.
60263
60264  forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
60265    if (!properties.playlists) {
60266      return;
60267    }
60268    for (let i = 0; i < properties.playlists.length; i++) {
60269      if (newMedia.id === properties.playlists[i].id) {
60270        properties.playlists[i] = mergedPlaylist;
60271      }
60272    }
60273  });
60274  return result;
60275};
60276/**
60277 * Calculates the time to wait before refreshing a live playlist
60278 *
60279 * @param {Object} media
60280 *        The current media
60281 * @param {boolean} update
60282 *        True if there were any updates from the last refresh, false otherwise
60283 * @return {number}
60284 *         The time in ms to wait before refreshing the live playlist
60285 */
60286
60287const refreshDelay = (media, update) => {
60288  const segments = media.segments || [];
60289  const lastSegment = segments[segments.length - 1];
60290  const lastPart = lastSegment && lastSegment.parts && lastSegment.parts[lastSegment.parts.length - 1];
60291  const lastDuration = lastPart && lastPart.duration || lastSegment && lastSegment.duration;
60292  if (update && lastDuration) {
60293    return lastDuration * 1000;
60294  } // if the playlist is unchanged since the last reload or last segment duration
60295  // cannot be determined, try again after half the target duration
60296
60297  return (media.partTargetDuration || media.targetDuration || 10) * 500;
60298};
60299const playlistMetadataPayload = (playlists, type, isLive) => {
60300  if (!playlists) {
60301    return;
60302  }
60303  const renditions = [];
60304  playlists.forEach(playlist => {
60305    // we need attributes to populate rendition data.
60306    if (!playlist.attributes) {
60307      return;
60308    }
60309    const {
60310      BANDWIDTH,
60311      RESOLUTION,
60312      CODECS
60313    } = playlist.attributes;
60314    renditions.push({
60315      id: playlist.id,
60316      bandwidth: BANDWIDTH,
60317      resolution: RESOLUTION,
60318      codecs: CODECS
60319    });
60320  });
60321  return {
60322    type,
60323    isLive,
60324    renditions
60325  };
60326};
60327/**
60328 * Load a playlist from a remote location
60329 *
60330 * @class PlaylistLoader
60331 * @extends Stream
60332 * @param {string|Object} src url or object of manifest
60333 * @param {boolean} withCredentials the withCredentials xhr option
60334 * @class
60335 */
60336
60337class PlaylistLoader extends EventTarget$1 {
60338  constructor(src, vhs, options = {}) {
60339    super();
60340    if (!src) {
60341      throw new Error('A non-empty playlist URL or object is required');
60342    }
60343    this.logger_ = logger('PlaylistLoader');
60344    const {
60345      withCredentials = false
60346    } = options;
60347    this.src = src;
60348    this.vhs_ = vhs;
60349    this.withCredentials = withCredentials;
60350    this.addDateRangesToTextTrack_ = options.addDateRangesToTextTrack;
60351    const vhsOptions = vhs.options_;
60352    this.customTagParsers = vhsOptions && vhsOptions.customTagParsers || [];
60353    this.customTagMappers = vhsOptions && vhsOptions.customTagMappers || [];
60354    this.llhls = vhsOptions && vhsOptions.llhls;
60355    this.dateRangesStorage_ = new DateRangesStorage(); // initialize the loader state
60356
60357    this.state = 'HAVE_NOTHING'; // live playlist staleness timeout
60358
60359    this.handleMediaupdatetimeout_ = this.handleMediaupdatetimeout_.bind(this);
60360    this.on('mediaupdatetimeout', this.handleMediaupdatetimeout_);
60361    this.on('loadedplaylist', this.handleLoadedPlaylist_.bind(this));
60362  }
60363  handleLoadedPlaylist_() {
60364    const mediaPlaylist = this.media();
60365    if (!mediaPlaylist) {
60366      return;
60367    }
60368    this.dateRangesStorage_.setOffset(mediaPlaylist.segments);
60369    this.dateRangesStorage_.setPendingDateRanges(mediaPlaylist.dateRanges);
60370    const availableDateRanges = this.dateRangesStorage_.getDateRangesToProcess();
60371    if (!availableDateRanges.length || !this.addDateRangesToTextTrack_) {
60372      return;
60373    }
60374    this.addDateRangesToTextTrack_(availableDateRanges);
60375  }
60376  handleMediaupdatetimeout_() {
60377    if (this.state !== 'HAVE_METADATA') {
60378      // only refresh the media playlist if no other activity is going on
60379      return;
60380    }
60381    const media = this.media();
60382    let uri = resolveUrl(this.main.uri, media.uri);
60383    if (this.llhls) {
60384      uri = addLLHLSQueryDirectives(uri, media);
60385    }
60386    this.state = 'HAVE_CURRENT_METADATA';
60387    this.request = this.vhs_.xhr({
60388      uri,
60389      withCredentials: this.withCredentials,
60390      requestType: 'hls-playlist'
60391    }, (error, req) => {
60392      // disposed
60393      if (!this.request) {
60394        return;
60395      }
60396      if (error) {
60397        return this.playlistRequestError(this.request, this.media(), 'HAVE_METADATA');
60398      }
60399      this.haveMetadata({
60400        playlistString: this.request.responseText,
60401        url: this.media().uri,
60402        id: this.media().id
60403      });
60404    });
60405  }
60406  playlistRequestError(xhr, playlist, startingState) {
60407    const {
60408      uri,
60409      id
60410    } = playlist; // any in-flight request is now finished
60411
60412    this.request = null;
60413    if (startingState) {
60414      this.state = startingState;
60415    }
60416    this.error = {
60417      playlist: this.main.playlists[id],
60418      status: xhr.status,
60419      message: `HLS playlist request error at URL: ${uri}.`,
60420      responseText: xhr.responseText,
60421      code: xhr.status >= 500 ? 4 : 2,
60422      metadata: getStreamingNetworkErrorMetadata({
60423        requestType: xhr.requestType,
60424        request: xhr,
60425        error: xhr.error
60426      })
60427    };
60428    this.trigger('error');
60429  }
60430  parseManifest_({
60431    url,
60432    manifestString
60433  }) {
60434    try {
60435      const parsed = parseManifest({
60436        onwarn: ({
60437          message
60438        }) => this.logger_(`m3u8-parser warn for ${url}: ${message}`),
60439        oninfo: ({
60440          message
60441        }) => this.logger_(`m3u8-parser info for ${url}: ${message}`),
60442        manifestString,
60443        customTagParsers: this.customTagParsers,
60444        customTagMappers: this.customTagMappers,
60445        llhls: this.llhls
60446      });
60447      /**
60448       * VHS does not support switching between variants with and without audio and video
60449       * so we want to filter out audio-only variants when variants with video and(or) audio are also detected.
60450       */
60451
60452      if (!parsed.playlists || !parsed.playlists.length) {
60453        return parsed;
60454      }
60455      this.excludeAudioOnlyVariants(parsed.playlists);
60456      return parsed;
60457    } catch (error) {
60458      this.error = error;
60459      this.error.metadata = {
60460        errorType: videojs.Error.StreamingHlsPlaylistParserError,
60461        error
60462      };
60463    }
60464  }
60465  excludeAudioOnlyVariants(playlists) {
60466    // helper function
60467    const hasVideo = playlist => {
60468      const attributes = playlist.attributes || {};
60469      const {
60470        width,
60471        height
60472      } = attributes.RESOLUTION || {};
60473      if (width && height) {
60474        return true;
60475      } // parse codecs string from playlist attributes
60476
60477      const codecsList = getCodecs(playlist) || []; // unwrap list
60478
60479      const codecsInfo = unwrapCodecList(codecsList);
60480      return Boolean(codecsInfo.video);
60481    };
60482    if (playlists.some(hasVideo)) {
vendor: 12,106 bytes, lines 60483-60845
60483      playlists.forEach(playlist => {
60484        if (!hasVideo(playlist)) {
60485          playlist.excludeUntil = Infinity;
60486        }
60487      });
60488    }
60489  }
60490  /**
60491   * Update the playlist loader's state in response to a new or updated playlist.
60492   *
60493   * @param {string} [playlistString]
60494   *        Playlist string (if playlistObject is not provided)
60495   * @param {Object} [playlistObject]
60496   *        Playlist object (if playlistString is not provided)
60497   * @param {string} url
60498   *        URL of playlist
60499   * @param {string} id
60500   *        ID to use for playlist
60501   */
60502
60503  haveMetadata({
60504    playlistString,
60505    playlistObject,
60506    url,
60507    id
60508  }) {
60509    // any in-flight request is now finished
60510    this.request = null;
60511    this.state = 'HAVE_METADATA';
60512    const metadata = {
60513      playlistInfo: {
60514        type: 'media',
60515        uri: url
60516      }
60517    };
60518    this.trigger({
60519      type: 'playlistparsestart',
60520      metadata
60521    });
60522    const playlist = playlistObject || this.parseManifest_({
60523      url,
60524      manifestString: playlistString
60525    });
60526    playlist.lastRequest = Date.now();
60527    setupMediaPlaylist({
60528      playlist,
60529      uri: url,
60530      id
60531    }); // merge this playlist into the main manifest
60532
60533    const update = updateMain$1(this.main, playlist);
60534    this.targetDuration = playlist.partTargetDuration || playlist.targetDuration;
60535    this.pendingMedia_ = null;
60536    if (update) {
60537      this.main = update;
60538      this.media_ = this.main.playlists[id];
60539    } else {
60540      this.trigger('playlistunchanged');
60541    }
60542    this.updateMediaUpdateTimeout_(refreshDelay(this.media(), !!update));
60543    metadata.parsedPlaylist = playlistMetadataPayload(this.main.playlists, metadata.playlistInfo.type, !this.media_.endList);
60544    this.trigger({
60545      type: 'playlistparsecomplete',
60546      metadata
60547    });
60548    this.trigger('loadedplaylist');
60549  }
60550  /**
60551    * Abort any outstanding work and clean up.
60552    */
60553
60554  dispose() {
60555    this.trigger('dispose');
60556    this.stopRequest();
60557    window__default["default"].clearTimeout(this.mediaUpdateTimeout);
60558    window__default["default"].clearTimeout(this.finalRenditionTimeout);
60559    this.dateRangesStorage_ = new DateRangesStorage();
60560    this.off();
60561  }
60562  stopRequest() {
60563    if (this.request) {
60564      const oldRequest = this.request;
60565      this.request = null;
60566      oldRequest.onreadystatechange = null;
60567      oldRequest.abort();
60568    }
60569  }
60570  /**
60571    * When called without any arguments, returns the currently
60572    * active media playlist. When called with a single argument,
60573    * triggers the playlist loader to asynchronously switch to the
60574    * specified media playlist. Calling this method while the
60575    * loader is in the HAVE_NOTHING causes an error to be emitted
60576    * but otherwise has no effect.
60577    *
60578    * @param {Object=} playlist the parsed media playlist
60579    * object to switch to
60580    * @param {boolean=} shouldDelay whether we should delay the request by half target duration
60581    *
60582    * @return {Playlist} the current loaded media
60583    */
60584
60585  media(playlist, shouldDelay) {
60586    // getter
60587    if (!playlist) {
60588      return this.media_;
60589    } // setter
60590
60591    if (this.state === 'HAVE_NOTHING') {
60592      throw new Error('Cannot switch media playlist from ' + this.state);
60593    } // find the playlist object if the target playlist has been
60594    // specified by URI
60595
60596    if (typeof playlist === 'string') {
60597      if (!this.main.playlists[playlist]) {
60598        throw new Error('Unknown playlist URI: ' + playlist);
60599      }
60600      playlist = this.main.playlists[playlist];
60601    }
60602    window__default["default"].clearTimeout(this.finalRenditionTimeout);
60603    if (shouldDelay) {
60604      const delay = (playlist.partTargetDuration || playlist.targetDuration) / 2 * 1000 || 5 * 1000;
60605      this.finalRenditionTimeout = window__default["default"].setTimeout(this.media.bind(this, playlist, false), delay);
60606      return;
60607    }
60608    const startingState = this.state;
60609    const mediaChange = !this.media_ || playlist.id !== this.media_.id;
60610    const mainPlaylistRef = this.main.playlists[playlist.id]; // switch to fully loaded playlists immediately
60611
60612    if (mainPlaylistRef && mainPlaylistRef.endList ||
60613    // handle the case of a playlist object (e.g., if using vhs-json with a resolved
60614    // media playlist or, for the case of demuxed audio, a resolved audio media group)
60615    playlist.endList && playlist.segments.length) {
60616      // abort outstanding playlist requests
60617      if (this.request) {
60618        this.request.onreadystatechange = null;
60619        this.request.abort();
60620        this.request = null;
60621      }
60622      this.state = 'HAVE_METADATA';
60623      this.media_ = playlist; // trigger media change if the active media has been updated
60624
60625      if (mediaChange) {
60626        this.trigger('mediachanging');
60627        if (startingState === 'HAVE_MAIN_MANIFEST') {
60628          // The initial playlist was a main manifest, and the first media selected was
60629          // also provided (in the form of a resolved playlist object) as part of the
60630          // source object (rather than just a URL). Therefore, since the media playlist
60631          // doesn't need to be requested, loadedmetadata won't trigger as part of the
60632          // normal flow, and needs an explicit trigger here.
60633          this.trigger('loadedmetadata');
60634        } else {
60635          this.trigger('mediachange');
60636        }
60637      }
60638      return;
60639    } // We update/set the timeout here so that live playlists
60640    // that are not a media change will "start" the loader as expected.
60641    // We expect that this function will start the media update timeout
60642    // cycle again. This also prevents a playlist switch failure from
60643    // causing us to stall during live.
60644
60645    this.updateMediaUpdateTimeout_(refreshDelay(playlist, true)); // switching to the active playlist is a no-op
60646
60647    if (!mediaChange) {
60648      return;
60649    }
60650    this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request
60651
60652    if (this.request) {
60653      if (playlist.resolvedUri === this.request.url) {
60654        // requesting to switch to the same playlist multiple times
60655        // has no effect after the first
60656        return;
60657      }
60658      this.request.onreadystatechange = null;
60659      this.request.abort();
60660      this.request = null;
60661    } // request the new playlist
60662
60663    if (this.media_) {
60664      this.trigger('mediachanging');
60665    }
60666    this.pendingMedia_ = playlist;
60667    const metadata = {
60668      playlistInfo: {
60669        type: 'media',
60670        uri: playlist.uri
60671      }
60672    };
60673    this.trigger({
60674      type: 'playlistrequeststart',
60675      metadata
60676    });
60677    this.request = this.vhs_.xhr({
60678      uri: playlist.resolvedUri,
60679      withCredentials: this.withCredentials,
60680      requestType: 'hls-playlist'
60681    }, (error, req) => {
60682      // disposed
60683      if (!this.request) {
60684        return;
60685      }
60686      playlist.lastRequest = Date.now();
60687      playlist.resolvedUri = resolveManifestRedirect(playlist.resolvedUri, req);
60688      if (error) {
60689        return this.playlistRequestError(this.request, playlist, startingState);
60690      }
60691      this.trigger({
60692        type: 'playlistrequestcomplete',
60693        metadata
60694      });
60695      this.haveMetadata({
60696        playlistString: req.responseText,
60697        url: playlist.uri,
60698        id: playlist.id
60699      }); // fire loadedmetadata the first time a media playlist is loaded
60700
60701      if (startingState === 'HAVE_MAIN_MANIFEST') {
60702        this.trigger('loadedmetadata');
60703      } else {
60704        this.trigger('mediachange');
60705      }
60706    });
60707  }
60708  /**
60709   * pause loading of the playlist
60710   */
60711
60712  pause() {
60713    if (this.mediaUpdateTimeout) {
60714      window__default["default"].clearTimeout(this.mediaUpdateTimeout);
60715      this.mediaUpdateTimeout = null;
60716    }
60717    this.stopRequest();
60718    if (this.state === 'HAVE_NOTHING') {
60719      // If we pause the loader before any data has been retrieved, its as if we never
60720      // started, so reset to an unstarted state.
60721      this.started = false;
60722    } // Need to restore state now that no activity is happening
60723
60724    if (this.state === 'SWITCHING_MEDIA') {
60725      // if the loader was in the process of switching media, it should either return to
60726      // HAVE_MAIN_MANIFEST or HAVE_METADATA depending on if the loader has loaded a media
60727      // playlist yet. This is determined by the existence of loader.media_
60728      if (this.media_) {
60729        this.state = 'HAVE_METADATA';
60730      } else {
60731        this.state = 'HAVE_MAIN_MANIFEST';
60732      }
60733    } else if (this.state === 'HAVE_CURRENT_METADATA') {
60734      this.state = 'HAVE_METADATA';
60735    }
60736  }
60737  /**
60738   * start loading of the playlist
60739   */
60740
60741  load(shouldDelay) {
60742    if (this.mediaUpdateTimeout) {
60743      window__default["default"].clearTimeout(this.mediaUpdateTimeout);
60744      this.mediaUpdateTimeout = null;
60745    }
60746    const media = this.media();
60747    if (shouldDelay) {
60748      const delay = media ? (media.partTargetDuration || media.targetDuration) / 2 * 1000 : 5 * 1000;
60749      this.mediaUpdateTimeout = window__default["default"].setTimeout(() => {
60750        this.mediaUpdateTimeout = null;
60751        this.load();
60752      }, delay);
60753      return;
60754    }
60755    if (!this.started) {
60756      this.start();
60757      return;
60758    }
60759    if (media && !media.endList) {
60760      this.trigger('mediaupdatetimeout');
60761    } else {
60762      this.trigger('loadedplaylist');
60763    }
60764  }
60765  updateMediaUpdateTimeout_(delay) {
60766    if (this.mediaUpdateTimeout) {
60767      window__default["default"].clearTimeout(this.mediaUpdateTimeout);
60768      this.mediaUpdateTimeout = null;
60769    } // we only have use mediaupdatetimeout for live playlists.
60770
60771    if (!this.media() || this.media().endList) {
60772      return;
60773    }
60774    this.mediaUpdateTimeout = window__default["default"].setTimeout(() => {
60775      this.mediaUpdateTimeout = null;
60776      this.trigger('mediaupdatetimeout');
60777      this.updateMediaUpdateTimeout_(delay);
60778    }, delay);
60779  }
60780  /**
60781   * start loading of the playlist
60782   */
60783
60784  start() {
60785    this.started = true;
60786    if (typeof this.src === 'object') {
60787      // in the case of an entirely constructed manifest object (meaning there's no actual
60788      // manifest on a server), default the uri to the page's href
60789      if (!this.src.uri) {
60790        this.src.uri = window__default["default"].location.href;
60791      } // resolvedUri is added on internally after the initial request. Since there's no
60792      // request for pre-resolved manifests, add on resolvedUri here.
60793
60794      this.src.resolvedUri = this.src.uri; // Since a manifest object was passed in as the source (instead of a URL), the first
60795      // request can be skipped (since the top level of the manifest, at a minimum, is
60796      // already available as a parsed manifest object). However, if the manifest object
60797      // represents a main playlist, some media playlists may need to be resolved before
60798      // the starting segment list is available. Therefore, go directly to setup of the
60799      // initial playlist, and let the normal flow continue from there.
60800      //
60801      // Note that the call to setup is asynchronous, as other sections of VHS may assume
60802      // that the first request is asynchronous.
60803
60804      setTimeout(() => {
60805        this.setupInitialPlaylist(this.src);
60806      }, 0);
60807      return;
60808    }
60809    const metadata = {
60810      playlistInfo: {
60811        type: 'multivariant',
60812        uri: this.src
60813      }
60814    };
60815    this.trigger({
60816      type: 'playlistrequeststart',
60817      metadata
60818    }); // request the specified URL
60819
60820    this.request = this.vhs_.xhr({
60821      uri: this.src,
60822      withCredentials: this.withCredentials,
60823      requestType: 'hls-playlist'
60824    }, (error, req) => {
60825      // disposed
60826      if (!this.request) {
60827        return;
60828      } // clear the loader's request reference
60829
60830      this.request = null;
60831      if (error) {
60832        this.error = {
60833          status: req.status,
60834          message: `HLS playlist request error at URL: ${this.src}.`,
60835          responseText: req.responseText,
60836          // MEDIA_ERR_NETWORK
60837          code: 2,
60838          metadata: getStreamingNetworkErrorMetadata({
60839            requestType: req.requestType,
60840            request: req,
60841            error
60842          })
60843        };
60844        if (this.state === 'HAVE_NOTHING') {
60845          this.started = false;
vendor: 2,093 bytes, lines 60846-60900
60846        }
60847        return this.trigger('error');
60848      }
60849      this.trigger({
60850        type: 'playlistrequestcomplete',
60851        metadata
60852      });
60853      this.src = resolveManifestRedirect(this.src, req);
60854      this.trigger({
60855        type: 'playlistparsestart',
60856        metadata
60857      });
60858      const manifest = this.parseManifest_({
60859        manifestString: req.responseText,
60860        url: this.src
60861      }); // we haven't loaded any variant playlists here so we default to false for isLive.
60862
60863      metadata.parsedPlaylist = playlistMetadataPayload(manifest.playlists, metadata.playlistInfo.type, false);
60864      this.trigger({
60865        type: 'playlistparsecomplete',
60866        metadata
60867      });
60868      this.setupInitialPlaylist(manifest);
60869    });
60870  }
60871  srcUri() {
60872    return typeof this.src === 'string' ? this.src : this.src.uri;
60873  }
60874  /**
60875   * Given a manifest object that's either a main or media playlist, trigger the proper
60876   * events and set the state of the playlist loader.
60877   *
60878   * If the manifest object represents a main playlist, `loadedplaylist` will be
60879   * triggered to allow listeners to select a playlist. If none is selected, the loader
60880   * will default to the first one in the playlists array.
60881   *
60882   * If the manifest object represents a media playlist, `loadedplaylist` will be
60883   * triggered followed by `loadedmetadata`, as the only available playlist is loaded.
60884   *
60885   * In the case of a media playlist, a main playlist object wrapper with one playlist
60886   * will be created so that all logic can handle playlists in the same fashion (as an
60887   * assumed manifest object schema).
60888   *
60889   * @param {Object} manifest
60890   *        The parsed manifest object
60891   */
60892
60893  setupInitialPlaylist(manifest) {
60894    this.state = 'HAVE_MAIN_MANIFEST';
60895    if (manifest.playlists) {
60896      this.main = manifest;
60897      addPropertiesToMain(this.main, this.srcUri()); // If the initial main playlist has playlists wtih segments already resolved,
60898      // then resolve URIs in advance, as they are usually done after a playlist request,
60899      // which may not happen if the playlist is resolved.
60900
vendor: 6,969 bytes, lines 60901-61068
60901      manifest.playlists.forEach(playlist => {
60902        playlist.segments = getAllSegments(playlist);
60903        playlist.segments.forEach(segment => {
60904          resolveSegmentUris(segment, playlist.resolvedUri);
60905        });
60906      });
60907      this.trigger('loadedplaylist');
60908      if (!this.request) {
60909        // no media playlist was specifically selected so start
60910        // from the first listed one
60911        this.media(this.main.playlists[0]);
60912      }
60913      return;
60914    } // In order to support media playlists passed in as vhs-json, the case where the uri
60915    // is not provided as part of the manifest should be considered, and an appropriate
60916    // default used.
60917
60918    const uri = this.srcUri() || window__default["default"].location.href;
60919    this.main = mainForMedia(manifest, uri);
60920    this.haveMetadata({
60921      playlistObject: manifest,
60922      url: uri,
60923      id: this.main.playlists[0].id
60924    });
60925    this.trigger('loadedmetadata');
60926  }
60927  /**
60928   * Updates or deletes a preexisting pathway clone.
60929   * Ensures that all playlists related to the old pathway clone are
60930   * either updated or deleted.
60931   *
60932   * @param {Object} clone On update, the pathway clone object for the newly updated pathway clone.
60933   *        On delete, the old pathway clone object to be deleted.
60934   * @param {boolean} isUpdate True if the pathway is to be updated,
60935   *        false if it is meant to be deleted.
60936   */
60937
60938  updateOrDeleteClone(clone, isUpdate) {
60939    const main = this.main;
60940    const pathway = clone.ID;
60941    let i = main.playlists.length; // Iterate backwards through the playlist so we can remove playlists if necessary.
60942
60943    while (i--) {
60944      const p = main.playlists[i];
60945      if (p.attributes['PATHWAY-ID'] === pathway) {
60946        const oldPlaylistUri = p.resolvedUri;
60947        const oldPlaylistId = p.id; // update the indexed playlist and add new playlists by ID and URI
60948
60949        if (isUpdate) {
60950          const newPlaylistUri = this.createCloneURI_(p.resolvedUri, clone);
60951          const newPlaylistId = createPlaylistID(pathway, newPlaylistUri);
60952          const attributes = this.createCloneAttributes_(pathway, p.attributes);
60953          const updatedPlaylist = this.createClonePlaylist_(p, newPlaylistId, clone, attributes);
60954          main.playlists[i] = updatedPlaylist;
60955          main.playlists[newPlaylistId] = updatedPlaylist;
60956          main.playlists[newPlaylistUri] = updatedPlaylist;
60957        } else {
60958          // Remove the indexed playlist.
60959          main.playlists.splice(i, 1);
60960        } // Remove playlists by the old ID and URI.
60961
60962        delete main.playlists[oldPlaylistId];
60963        delete main.playlists[oldPlaylistUri];
60964      }
60965    }
60966    this.updateOrDeleteCloneMedia(clone, isUpdate);
60967  }
60968  /**
60969   * Updates or deletes media data based on the pathway clone object.
60970   * Due to the complexity of the media groups and playlists, in all cases
60971   * we remove all of the old media groups and playlists.
60972   * On updates, we then create new media groups and playlists based on the
60973   * new pathway clone object.
60974   *
60975   * @param {Object} clone The pathway clone object for the newly updated pathway clone.
60976   * @param {boolean} isUpdate True if the pathway is to be updated,
60977   *        false if it is meant to be deleted.
60978   */
60979
60980  updateOrDeleteCloneMedia(clone, isUpdate) {
60981    const main = this.main;
60982    const id = clone.ID;
60983    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(mediaType => {
60984      if (!main.mediaGroups[mediaType] || !main.mediaGroups[mediaType][id]) {
60985        return;
60986      }
60987      for (const groupKey in main.mediaGroups[mediaType]) {
60988        // Remove all media playlists for the media group for this pathway clone.
60989        if (groupKey === id) {
60990          for (const labelKey in main.mediaGroups[mediaType][groupKey]) {
60991            const oldMedia = main.mediaGroups[mediaType][groupKey][labelKey];
60992            oldMedia.playlists.forEach((p, i) => {
60993              const oldMediaPlaylist = main.playlists[p.id];
60994              const oldPlaylistId = oldMediaPlaylist.id;
60995              const oldPlaylistUri = oldMediaPlaylist.resolvedUri;
60996              delete main.playlists[oldPlaylistId];
60997              delete main.playlists[oldPlaylistUri];
60998            });
60999          } // Delete the old media group.
61000
61001          delete main.mediaGroups[mediaType][groupKey];
61002        }
61003      }
61004    }); // Create the new media groups and playlists if there is an update.
61005
61006    if (isUpdate) {
61007      this.createClonedMediaGroups_(clone);
61008    }
61009  }
61010  /**
61011   * Given a pathway clone object, clones all necessary playlists.
61012   *
61013   * @param {Object} clone The pathway clone object.
61014   * @param {Object} basePlaylist The original playlist to clone from.
61015   */
61016
61017  addClonePathway(clone, basePlaylist = {}) {
61018    const main = this.main;
61019    const index = main.playlists.length;
61020    const uri = this.createCloneURI_(basePlaylist.resolvedUri, clone);
61021    const playlistId = createPlaylistID(clone.ID, uri);
61022    const attributes = this.createCloneAttributes_(clone.ID, basePlaylist.attributes);
61023    const playlist = this.createClonePlaylist_(basePlaylist, playlistId, clone, attributes);
61024    main.playlists[index] = playlist; // add playlist by ID and URI
61025
61026    main.playlists[playlistId] = playlist;
61027    main.playlists[uri] = playlist;
61028    this.createClonedMediaGroups_(clone);
61029  }
61030  /**
61031   * Given a pathway clone object we create clones of all media.
61032   * In this function, all necessary information and updated playlists
61033   * are added to the `mediaGroup` object.
61034   * Playlists are also added to the `playlists` array so the media groups
61035   * will be properly linked.
61036   *
61037   * @param {Object} clone The pathway clone object.
61038   */
61039
61040  createClonedMediaGroups_(clone) {
61041    const id = clone.ID;
61042    const baseID = clone['BASE-ID'];
61043    const main = this.main;
61044    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(mediaType => {
61045      // If the media type doesn't exist, or there is already a clone, skip
61046      // to the next media type.
61047      if (!main.mediaGroups[mediaType] || main.mediaGroups[mediaType][id]) {
61048        return;
61049      }
61050      for (const groupKey in main.mediaGroups[mediaType]) {
61051        if (groupKey === baseID) {
61052          // Create the group.
61053          main.mediaGroups[mediaType][id] = {};
61054        } else {
61055          // There is no need to iterate over label keys in this case.
61056          continue;
61057        }
61058        for (const labelKey in main.mediaGroups[mediaType][groupKey]) {
61059          const oldMedia = main.mediaGroups[mediaType][groupKey][labelKey];
61060          main.mediaGroups[mediaType][id][labelKey] = _extends__default["default"]({}, oldMedia);
61061          const newMedia = main.mediaGroups[mediaType][id][labelKey]; // update URIs on the media
61062
61063          const newUri = this.createCloneURI_(oldMedia.resolvedUri, clone);
61064          newMedia.resolvedUri = newUri;
61065          newMedia.uri = newUri; // Reset playlists in the new media group.
61066
61067          newMedia.playlists = []; // Create new playlists in the newly cloned media group.
61068
vendor: 4,496 bytes, lines 61069-61193
61069          oldMedia.playlists.forEach((p, i) => {
61070            const oldMediaPlaylist = main.playlists[p.id];
61071            const group = groupID(mediaType, id, labelKey);
61072            const newPlaylistID = createPlaylistID(id, group); // Check to see if it already exists
61073
61074            if (oldMediaPlaylist && !main.playlists[newPlaylistID]) {
61075              const newMediaPlaylist = this.createClonePlaylist_(oldMediaPlaylist, newPlaylistID, clone);
61076              const newPlaylistUri = newMediaPlaylist.resolvedUri;
61077              main.playlists[newPlaylistID] = newMediaPlaylist;
61078              main.playlists[newPlaylistUri] = newMediaPlaylist;
61079            }
61080            newMedia.playlists[i] = this.createClonePlaylist_(p, newPlaylistID, clone);
61081          });
61082        }
61083      }
61084    });
61085  }
61086  /**
61087   * Using the original playlist to be cloned, and the pathway clone object
61088   * information, we create a new playlist.
61089   *
61090   * @param {Object} basePlaylist  The original playlist to be cloned from.
61091   * @param {string} id The desired id of the newly cloned playlist.
61092   * @param {Object} clone The pathway clone object.
61093   * @param {Object} attributes An optional object to populate the `attributes` property in the playlist.
61094   *
61095   * @return {Object} The combined cloned playlist.
61096   */
61097
61098  createClonePlaylist_(basePlaylist, id, clone, attributes) {
61099    const uri = this.createCloneURI_(basePlaylist.resolvedUri, clone);
61100    const newProps = {
61101      resolvedUri: uri,
61102      uri,
61103      id
61104    }; // Remove all segments from previous playlist in the clone.
61105
61106    if (basePlaylist.segments) {
61107      newProps.segments = [];
61108    }
61109    if (attributes) {
61110      newProps.attributes = attributes;
61111    }
61112    return merge(basePlaylist, newProps);
61113  }
61114  /**
61115   * Generates an updated URI for a cloned pathway based on the original
61116   * pathway's URI and the paramaters from the pathway clone object in the
61117   * content steering server response.
61118   *
61119   * @param {string} baseUri URI to be updated in the cloned pathway.
61120   * @param {Object} clone The pathway clone object.
61121   *
61122   * @return {string} The updated URI for the cloned pathway.
61123   */
61124
61125  createCloneURI_(baseURI, clone) {
61126    const uri = new URL(baseURI);
61127    uri.hostname = clone['URI-REPLACEMENT'].HOST;
61128    const params = clone['URI-REPLACEMENT'].PARAMS; // Add params to the cloned URL.
61129
61130    for (const key of Object.keys(params)) {
61131      uri.searchParams.set(key, params[key]);
61132    }
61133    return uri.href;
61134  }
61135  /**
61136   * Helper function to create the attributes needed for the new clone.
61137   * This mainly adds the necessary media attributes.
61138   *
61139   * @param {string} id The pathway clone object ID.
61140   * @param {Object} oldAttributes The old attributes to compare to.
61141   * @return {Object} The new attributes to add to the playlist.
61142   */
61143
61144  createCloneAttributes_(id, oldAttributes) {
61145    const attributes = {
61146      ['PATHWAY-ID']: id
61147    };
61148    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(mediaType => {
61149      if (oldAttributes[mediaType]) {
61150        attributes[mediaType] = id;
61151      }
61152    });
61153    return attributes;
61154  }
61155  /**
61156   * Returns the key ID set from a playlist
61157   *
61158   * @param {playlist} playlist to fetch the key ID set from.
61159   * @return a Set of 32 digit hex strings that represent the unique keyIds for that playlist.
61160   */
61161
61162  getKeyIdSet(playlist) {
61163    const keyIds = new Set();
61164    if (!playlist || !playlist.contentProtection) {
61165      return keyIds;
61166    }
61167    for (const keysystem in playlist.contentProtection) {
61168      if (playlist.contentProtection[keysystem] && playlist.contentProtection[keysystem].attributes && playlist.contentProtection[keysystem].attributes.keyId) {
61169        const keyId = playlist.contentProtection[keysystem].attributes.keyId;
61170        keyIds.add(keyId.toLowerCase());
61171      }
61172    }
61173    return keyIds;
61174  }
61175}
61176
61177/**
61178 * @file xhr.js
61179 */
61180
61181const callbackWrapper = function (request, error, response, callback) {
61182  const reqResponse = request.responseType === 'arraybuffer' ? request.response : request.responseText;
61183  if (!error && reqResponse) {
61184    request.responseTime = Date.now();
61185    request.roundTripTime = request.responseTime - request.requestTime;
61186    request.bytesReceived = reqResponse.byteLength || reqResponse.length;
61187    if (!request.bandwidth) {
61188      request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
61189    }
61190  }
61191  if (response.headers) {
61192    request.responseHeaders = response.headers;
61193  } // videojs.xhr now uses a specific code on the error
vendor: 8,965 bytes, lines 61194-61458
61194  // object to signal that a request has timed out instead
61195  // of setting a boolean on the request object
61196
61197  if (error && error.code === 'ETIMEDOUT') {
61198    request.timedout = true;
61199  } // videojs.xhr no longer considers status codes outside of 200 and 0
61200  // (for file uris) to be errors, but the old XHR did, so emulate that
61201  // behavior. Status 206 may be used in response to byterange requests.
61202
61203  if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
61204    error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText)));
61205  }
61206  callback(error, request);
61207};
61208/**
61209 * Iterates over the request hooks Set and calls them in order
61210 *
61211 * @param {Set} hooks the hook Set to iterate over
61212 * @param {Object} options the request options to pass to the xhr wrapper
61213 * @return the callback hook function return value, the modified or new options Object.
61214 */
61215
61216const callAllRequestHooks = (requestSet, options) => {
61217  if (!requestSet || !requestSet.size) {
61218    return;
61219  }
61220  let newOptions = options;
61221  requestSet.forEach(requestCallback => {
61222    newOptions = requestCallback(newOptions);
61223  });
61224  return newOptions;
61225};
61226/**
61227 * Iterates over the response hooks Set and calls them in order.
61228 *
61229 * @param {Set} hooks the hook Set to iterate over
61230 * @param {Object} request the xhr request object
61231 * @param {Object} error the xhr error object
61232 * @param {Object} response the xhr response object
61233 */
61234
61235const callAllResponseHooks = (responseSet, request, error, response) => {
61236  if (!responseSet || !responseSet.size) {
61237    return;
61238  }
61239  responseSet.forEach(responseCallback => {
61240    responseCallback(request, error, response);
61241  });
61242};
61243const xhrFactory = function () {
61244  const xhr = function XhrFunction(options, callback) {
61245    // Add a default timeout
61246    options = merge({
61247      timeout: 45e3
61248    }, options); // Allow an optional user-specified function to modify the option
61249    // object before we construct the xhr request
61250    // TODO: Remove beforeRequest in the next major release.
61251
61252    const beforeRequest = XhrFunction.beforeRequest || videojs.Vhs.xhr.beforeRequest; // onRequest and onResponse hooks as a Set, at either the player or global level.
61253    // TODO: new Set added here for beforeRequest alias. Remove this when beforeRequest is removed.
61254
61255    const _requestCallbackSet = XhrFunction._requestCallbackSet || videojs.Vhs.xhr._requestCallbackSet || new Set();
61256    const _responseCallbackSet = XhrFunction._responseCallbackSet || videojs.Vhs.xhr._responseCallbackSet;
61257    if (beforeRequest && typeof beforeRequest === 'function') {
61258      videojs.log.warn('beforeRequest is deprecated, use onRequest instead.');
61259      _requestCallbackSet.add(beforeRequest);
61260    } // Use the standard videojs.xhr() method unless `videojs.Vhs.xhr` has been overriden
61261    // TODO: switch back to videojs.Vhs.xhr.name === 'XhrFunction' when we drop IE11
61262
61263    const xhrMethod = videojs.Vhs.xhr.original === true ? videojs.xhr : videojs.Vhs.xhr; // call all registered onRequest hooks, assign new options.
61264
61265    const beforeRequestOptions = callAllRequestHooks(_requestCallbackSet, options); // Remove the beforeRequest function from the hooks set so stale beforeRequest functions are not called.
61266
61267    _requestCallbackSet.delete(beforeRequest); // xhrMethod will call XMLHttpRequest.open and XMLHttpRequest.send
61268
61269    const request = xhrMethod(beforeRequestOptions || options, function (error, response) {
61270      // call all registered onResponse hooks
61271      callAllResponseHooks(_responseCallbackSet, request, error, response);
61272      return callbackWrapper(request, error, response, callback);
61273    });
61274    const originalAbort = request.abort;
61275    request.abort = function () {
61276      request.aborted = true;
61277      return originalAbort.apply(request, arguments);
61278    };
61279    request.uri = options.uri;
61280    request.requestType = options.requestType;
61281    request.requestTime = Date.now();
61282    return request;
61283  };
61284  xhr.original = true;
61285  return xhr;
61286};
61287/**
61288 * Turns segment byterange into a string suitable for use in
61289 * HTTP Range requests
61290 *
61291 * @param {Object} byterange - an object with two values defining the start and end
61292 *                             of a byte-range
61293 */
61294
61295const byterangeStr = function (byterange) {
61296  // `byterangeEnd` is one less than `offset + length` because the HTTP range
61297  // header uses inclusive ranges
61298  let byterangeEnd;
61299  const byterangeStart = byterange.offset;
61300  if (typeof byterange.offset === 'bigint' || typeof byterange.length === 'bigint') {
61301    byterangeEnd = window__default["default"].BigInt(byterange.offset) + window__default["default"].BigInt(byterange.length) - window__default["default"].BigInt(1);
61302  } else {
61303    byterangeEnd = byterange.offset + byterange.length - 1;
61304  }
61305  return 'bytes=' + byterangeStart + '-' + byterangeEnd;
61306};
61307/**
61308 * Defines headers for use in the xhr request for a particular segment.
61309 *
61310 * @param {Object} segment - a simplified copy of the segmentInfo object
61311 *                           from SegmentLoader
61312 */
61313
61314const segmentXhrHeaders = function (segment) {
61315  const headers = {};
61316  if (segment.byterange) {
61317    headers.Range = byterangeStr(segment.byterange);
61318  }
61319  return headers;
61320};
61321
61322/**
61323 * @file bin-utils.js
61324 */
61325
61326/**
61327 * convert a TimeRange to text
61328 *
61329 * @param {TimeRange} range the timerange to use for conversion
61330 * @param {number} i the iterator on the range to convert
61331 * @return {string} the range in string format
61332 */
61333
61334const textRange = function (range, i) {
61335  return range.start(i) + '-' + range.end(i);
61336};
61337/**
61338 * format a number as hex string
61339 *
61340 * @param {number} e The number
61341 * @param {number} i the iterator
61342 * @return {string} the hex formatted number as a string
61343 */
61344
61345const formatHexString = function (e, i) {
61346  const value = e.toString(16);
61347  return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
61348};
61349const formatAsciiString = function (e) {
61350  if (e >= 0x20 && e < 0x7e) {
61351    return String.fromCharCode(e);
61352  }
61353  return '.';
61354};
61355/**
61356 * Creates an object for sending to a web worker modifying properties that are TypedArrays
61357 * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
61358 *
61359 * @param {Object} message
61360 *        Object of properties and values to send to the web worker
61361 * @return {Object}
61362 *         Modified message with TypedArray values expanded
61363 * @function createTransferableMessage
61364 */
61365
61366const createTransferableMessage = function (message) {
61367  const transferable = {};
61368  Object.keys(message).forEach(key => {
61369    const value = message[key];
61370    if (byteHelpers.isArrayBufferView(value)) {
61371      transferable[key] = {
61372        bytes: value.buffer,
61373        byteOffset: value.byteOffset,
61374        byteLength: value.byteLength
61375      };
61376    } else {
61377      transferable[key] = value;
61378    }
61379  });
61380  return transferable;
61381};
61382/**
61383 * Returns a unique string identifier for a media initialization
61384 * segment.
61385 *
61386 * @param {Object} initSegment
61387 *        the init segment object.
61388 *
61389 * @return {string} the generated init segment id
61390 */
61391
61392const initSegmentId = function (initSegment) {
61393  const byterange = initSegment.byterange || {
61394    length: Infinity,
61395    offset: 0
61396  };
61397  return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
61398};
61399/**
61400 * Returns a unique string identifier for a media segment key.
61401 *
61402 * @param {Object} key the encryption key
61403 * @return {string} the unique id for the media segment key.
61404 */
61405
61406const segmentKeyId = function (key) {
61407  return key.resolvedUri;
61408};
61409/**
61410 * utils to help dump binary data to the console
61411 *
61412 * @param {Array|TypedArray} data
61413 *        data to dump to a string
61414 *
61415 * @return {string} the data as a hex string.
61416 */
61417
61418const hexDump = data => {
61419  const bytes = Array.prototype.slice.call(data);
61420  const step = 16;
61421  let result = '';
61422  let hex;
61423  let ascii;
61424  for (let j = 0; j < bytes.length / step; j++) {
61425    hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
61426    ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
61427    result += hex + ' ' + ascii + '\n';
61428  }
61429  return result;
61430};
61431const tagDump = ({
61432  bytes
61433}) => hexDump(bytes);
61434const textRanges = ranges => {
61435  let result = '';
61436  let i;
61437  for (i = 0; i < ranges.length; i++) {
61438    result += textRange(ranges, i) + ' ';
61439  }
61440  return result;
61441};
61442var utils = /*#__PURE__*/Object.freeze({
61443  __proto__: null,
61444  createTransferableMessage: createTransferableMessage,
61445  initSegmentId: initSegmentId,
61446  segmentKeyId: segmentKeyId,
61447  hexDump: hexDump,
61448  tagDump: tagDump,
61449  textRanges: textRanges
61450});
61451
61452// TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux
61453// 25% was arbitrarily chosen, and may need to be refined over time.
61454
61455const SEGMENT_END_FUDGE_PERCENT = 0.25;
61456/**
61457 * Converts a player time (any time that can be gotten/set from player.currentTime(),
61458 * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a
vendor: 11,794 bytes, lines 61459-61763
61459 * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)).
61460 *
61461 * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor
61462 * point" (a point where we have a mapping from program time to player time, with player
61463 * time being the post transmux start of the segment).
61464 *
61465 * For more details, see [this doc](../../docs/program-time-from-player-time.md).
61466 *
61467 * @param {number} playerTime the player time
61468 * @param {Object} segment the segment which contains the player time
61469 * @return {Date} program time
61470 */
61471
61472const playerTimeToProgramTime = (playerTime, segment) => {
61473  if (!segment.dateTimeObject) {
61474    // Can't convert without an "anchor point" for the program time (i.e., a time that can
61475    // be used to map the start of a segment with a real world time).
61476    return null;
61477  }
61478  const transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds;
61479  const transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended
61480
61481  const startOfSegment = transmuxedStart + transmuxerPrependedSeconds;
61482  const offsetFromSegmentStart = playerTime - startOfSegment;
61483  return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000);
61484};
61485const originalSegmentVideoDuration = videoTimingInfo => {
61486  return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds;
61487};
61488/**
61489 * Finds a segment that contains the time requested given as an ISO-8601 string. The
61490 * returned segment might be an estimate or an accurate match.
61491 *
61492 * @param {string} programTime The ISO-8601 programTime to find a match for
61493 * @param {Object} playlist A playlist object to search within
61494 */
61495
61496const findSegmentForProgramTime = (programTime, playlist) => {
61497  // Assumptions:
61498  //  - verifyProgramDateTimeTags has already been run
61499  //  - live streams have been started
61500  let dateTimeObject;
61501  try {
61502    dateTimeObject = new Date(programTime);
61503  } catch (e) {
61504    return null;
61505  }
61506  if (!playlist || !playlist.segments || playlist.segments.length === 0) {
61507    return null;
61508  }
61509  let segment = playlist.segments[0];
61510  if (dateTimeObject < new Date(segment.dateTimeObject)) {
61511    // Requested time is before stream start.
61512    return null;
61513  }
61514  for (let i = 0; i < playlist.segments.length - 1; i++) {
61515    segment = playlist.segments[i];
61516    const nextSegmentStart = new Date(playlist.segments[i + 1].dateTimeObject);
61517    if (dateTimeObject < nextSegmentStart) {
61518      break;
61519    }
61520  }
61521  const lastSegment = playlist.segments[playlist.segments.length - 1];
61522  const lastSegmentStart = lastSegment.dateTimeObject;
61523  const lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT;
61524  const lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000);
61525  if (dateTimeObject > lastSegmentEnd) {
61526    // Beyond the end of the stream, or our best guess of the end of the stream.
61527    return null;
61528  }
61529  if (dateTimeObject > new Date(lastSegmentStart)) {
61530    segment = lastSegment;
61531  }
61532  return {
61533    segment,
61534    estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)),
61535    // Although, given that all segments have accurate date time objects, the segment
61536    // selected should be accurate, unless the video has been transmuxed at some point
61537    // (determined by the presence of the videoTimingInfo object), the segment's "player
61538    // time" (the start time in the player) can't be considered accurate.
61539    type: segment.videoTimingInfo ? 'accurate' : 'estimate'
61540  };
61541};
61542/**
61543 * Finds a segment that contains the given player time(in seconds).
61544 *
61545 * @param {number} time The player time to find a match for
61546 * @param {Object} playlist A playlist object to search within
61547 */
61548
61549const findSegmentForPlayerTime = (time, playlist) => {
61550  // Assumptions:
61551  // - there will always be a segment.duration
61552  // - we can start from zero
61553  // - segments are in time order
61554  if (!playlist || !playlist.segments || playlist.segments.length === 0) {
61555    return null;
61556  }
61557  let segmentEnd = 0;
61558  let segment;
61559  for (let i = 0; i < playlist.segments.length; i++) {
61560    segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and
61561    // should contain the most accurate values we have for the segment's player times.
61562    //
61563    // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall
61564    // back to an estimate based on the manifest derived (inaccurate) segment.duration, to
61565    // calculate an end value.
61566
61567    segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration;
61568    if (time <= segmentEnd) {
61569      break;
61570    }
61571  }
61572  const lastSegment = playlist.segments[playlist.segments.length - 1];
61573  if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) {
61574    // The time requested is beyond the stream end.
61575    return null;
61576  }
61577  if (time > segmentEnd) {
61578    // The time is within or beyond the last segment.
61579    //
61580    // Check to see if the time is beyond a reasonable guess of the end of the stream.
61581    if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) {
61582      // Technically, because the duration value is only an estimate, the time may still
61583      // exist in the last segment, however, there isn't enough information to make even
61584      // a reasonable estimate.
61585      return null;
61586    }
61587    segment = lastSegment;
61588  }
61589  return {
61590    segment,
61591    estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration,
61592    // Because videoTimingInfo is only set after transmux, it is the only way to get
61593    // accurate timing values.
61594    type: segment.videoTimingInfo ? 'accurate' : 'estimate'
61595  };
61596};
61597/**
61598 * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds.
61599 * If the offset returned is positive, the programTime occurs after the
61600 * comparisonTimestamp.
61601 * If the offset is negative, the programTime occurs before the comparisonTimestamp.
61602 *
61603 * @param {string} comparisonTimeStamp An ISO-8601 timestamp to compare against
61604 * @param {string} programTime The programTime as an ISO-8601 string
61605 * @return {number} offset
61606 */
61607
61608const getOffsetFromTimestamp = (comparisonTimeStamp, programTime) => {
61609  let segmentDateTime;
61610  let programDateTime;
61611  try {
61612    segmentDateTime = new Date(comparisonTimeStamp);
61613    programDateTime = new Date(programTime);
61614  } catch (e) {// TODO handle error
61615  }
61616  const segmentTimeEpoch = segmentDateTime.getTime();
61617  const programTimeEpoch = programDateTime.getTime();
61618  return (programTimeEpoch - segmentTimeEpoch) / 1000;
61619};
61620/**
61621 * Checks that all segments in this playlist have programDateTime tags.
61622 *
61623 * @param {Object} playlist A playlist object
61624 */
61625
61626const verifyProgramDateTimeTags = playlist => {
61627  if (!playlist.segments || playlist.segments.length === 0) {
61628    return false;
61629  }
61630  for (let i = 0; i < playlist.segments.length; i++) {
61631    const segment = playlist.segments[i];
61632    if (!segment.dateTimeObject) {
61633      return false;
61634    }
61635  }
61636  return true;
61637};
61638/**
61639 * Returns the programTime of the media given a playlist and a playerTime.
61640 * The playlist must have programDateTime tags for a programDateTime tag to be returned.
61641 * If the segments containing the time requested have not been buffered yet, an estimate
61642 * may be returned to the callback.
61643 *
61644 * @param {Object} args
61645 * @param {Object} args.playlist A playlist object to search within
61646 * @param {number} time A playerTime in seconds
61647 * @param {Function} callback(err, programTime)
61648 * @return {string} err.message A detailed error message
61649 * @return {Object} programTime
61650 * @return {number} programTime.mediaSeconds The streamTime in seconds
61651 * @return {string} programTime.programDateTime The programTime as an ISO-8601 String
61652 */
61653
61654const getProgramTime = ({
61655  playlist,
61656  time = undefined,
61657  callback
61658}) => {
61659  if (!callback) {
61660    throw new Error('getProgramTime: callback must be provided');
61661  }
61662  if (!playlist || time === undefined) {
61663    return callback({
61664      message: 'getProgramTime: playlist and time must be provided'
61665    });
61666  }
61667  const matchedSegment = findSegmentForPlayerTime(time, playlist);
61668  if (!matchedSegment) {
61669    return callback({
61670      message: 'valid programTime was not found'
61671    });
61672  }
61673  if (matchedSegment.type === 'estimate') {
61674    return callback({
61675      message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again',
61676      seekTime: matchedSegment.estimatedStart
61677    });
61678  }
61679  const programTimeObject = {
61680    mediaSeconds: time
61681  };
61682  const programTime = playerTimeToProgramTime(time, matchedSegment.segment);
61683  if (programTime) {
61684    programTimeObject.programDateTime = programTime.toISOString();
61685  }
61686  return callback(null, programTimeObject);
61687};
61688/**
61689 * Seeks in the player to a time that matches the given programTime ISO-8601 string.
61690 *
61691 * @param {Object} args
61692 * @param {string} args.programTime A programTime to seek to as an ISO-8601 String
61693 * @param {Object} args.playlist A playlist to look within
61694 * @param {number} args.retryCount The number of times to try for an accurate seek. Default is 2.
61695 * @param {Function} args.seekTo A method to perform a seek
61696 * @param {boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true.
61697 * @param {Object} args.tech The tech to seek on
61698 * @param {Function} args.callback(err, newTime) A callback to return the new time to
61699 * @return {string} err.message A detailed error message
61700 * @return {number} newTime The exact time that was seeked to in seconds
61701 */
61702
61703const seekToProgramTime = ({
61704  programTime,
61705  playlist,
61706  retryCount = 2,
61707  seekTo,
61708  pauseAfterSeek = true,
61709  tech,
61710  callback
61711}) => {
61712  if (!callback) {
61713    throw new Error('seekToProgramTime: callback must be provided');
61714  }
61715  if (typeof programTime === 'undefined' || !playlist || !seekTo) {
61716    return callback({
61717      message: 'seekToProgramTime: programTime, seekTo and playlist must be provided'
61718    });
61719  }
61720  if (!playlist.endList && !tech.hasStarted_) {
61721    return callback({
61722      message: 'player must be playing a live stream to start buffering'
61723    });
61724  }
61725  if (!verifyProgramDateTimeTags(playlist)) {
61726    return callback({
61727      message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri
61728    });
61729  }
61730  const matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match
61731
61732  if (!matchedSegment) {
61733    return callback({
61734      message: `${programTime} was not found in the stream`
61735    });
61736  }
61737  const segment = matchedSegment.segment;
61738  const mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime);
61739  if (matchedSegment.type === 'estimate') {
61740    // we've run out of retries
61741    if (retryCount === 0) {
61742      return callback({
61743        message: `${programTime} is not buffered yet. Try again`
61744      });
61745    }
61746    seekTo(matchedSegment.estimatedStart + mediaOffset);
61747    tech.one('seeked', () => {
61748      seekToProgramTime({
61749        programTime,
61750        playlist,
61751        retryCount: retryCount - 1,
61752        seekTo,
61753        pauseAfterSeek,
61754        tech,
61755        callback
61756      });
61757    });
61758    return;
61759  } // Since the segment.start value is determined from the buffered end or ending time
61760  // of the prior segment, the seekToTime doesn't need to account for any transmuxer
61761  // modifications.
61762
61763  const seekToTime = segment.start + mediaOffset;
vendor: 33,477 bytes, lines 61764-62721
61764  const seekedCallback = () => {
61765    return callback(null, tech.currentTime());
61766  }; // listen for seeked event
61767
61768  tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state
61769
61770  if (pauseAfterSeek) {
61771    tech.pause();
61772  }
61773  seekTo(seekToTime);
61774};
61775
61776// which will only happen if the request is complete.
61777
61778const callbackOnCompleted = (request, cb) => {
61779  if (request.readyState === 4) {
61780    return cb();
61781  }
61782  return;
61783};
61784const containerRequest = (uri, xhr, cb, requestType) => {
61785  let bytes = [];
61786  let id3Offset;
61787  let finished = false;
61788  const endRequestAndCallback = function (err, req, type, _bytes) {
61789    req.abort();
61790    finished = true;
61791    return cb(err, req, type, _bytes);
61792  };
61793  const progressListener = function (error, request) {
61794    if (finished) {
61795      return;
61796    }
61797    if (error) {
61798      error.metadata = getStreamingNetworkErrorMetadata({
61799        requestType,
61800        request,
61801        error
61802      });
61803      return endRequestAndCallback(error, request, '', bytes);
61804    } // grap the new part of content that was just downloaded
61805
61806    const newPart = request.responseText.substring(bytes && bytes.byteLength || 0, request.responseText.length); // add that onto bytes
61807
61808    bytes = byteHelpers.concatTypedArrays(bytes, byteHelpers.stringToBytes(newPart, true));
61809    id3Offset = id3Offset || id3Helpers.getId3Offset(bytes); // we need at least 10 bytes to determine a type
61810    // or we need at least two bytes after an id3Offset
61811
61812    if (bytes.length < 10 || id3Offset && bytes.length < id3Offset + 2) {
61813      return callbackOnCompleted(request, () => endRequestAndCallback(error, request, '', bytes));
61814    }
61815    const type = containers.detectContainerForBytes(bytes); // if this looks like a ts segment but we don't have enough data
61816    // to see the second sync byte, wait until we have enough data
61817    // before declaring it ts
61818
61819    if (type === 'ts' && bytes.length < 188) {
61820      return callbackOnCompleted(request, () => endRequestAndCallback(error, request, '', bytes));
61821    } // this may be an unsynced ts segment
61822    // wait for 376 bytes before detecting no container
61823
61824    if (!type && bytes.length < 376) {
61825      return callbackOnCompleted(request, () => endRequestAndCallback(error, request, '', bytes));
61826    }
61827    return endRequestAndCallback(null, request, type, bytes);
61828  };
61829  const options = {
61830    uri,
61831    beforeSend(request) {
61832      // this forces the browser to pass the bytes to us unprocessed
61833      request.overrideMimeType('text/plain; charset=x-user-defined');
61834      request.addEventListener('progress', function ({
61835        total,
61836        loaded
61837      }) {
61838        return callbackWrapper(request, null, {
61839          statusCode: request.status
61840        }, progressListener);
61841      });
61842    }
61843  };
61844  const request = xhr(options, function (error, response) {
61845    return callbackWrapper(request, error, response, progressListener);
61846  });
61847  return request;
61848};
61849const {
61850  EventTarget
61851} = videojs;
61852const dashPlaylistUnchanged = function (a, b) {
61853  if (!isPlaylistUnchanged(a, b)) {
61854    return false;
61855  } // for dash the above check will often return true in scenarios where
61856  // the playlist actually has changed because mediaSequence isn't a
61857  // dash thing, and we often set it to 1. So if the playlists have the same amount
61858  // of segments we return true.
61859  // So for dash we need to make sure that the underlying segments are different.
61860  // if sidx changed then the playlists are different.
61861
61862  if (a.sidx && b.sidx && (a.sidx.offset !== b.sidx.offset || a.sidx.length !== b.sidx.length)) {
61863    return false;
61864  } else if (!a.sidx && b.sidx || a.sidx && !b.sidx) {
61865    return false;
61866  } // one or the other does not have segments
61867  // there was a change.
61868
61869  if (a.segments && !b.segments || !a.segments && b.segments) {
61870    return false;
61871  } // neither has segments nothing changed
61872
61873  if (!a.segments && !b.segments) {
61874    return true;
61875  } // check segments themselves
61876
61877  for (let i = 0; i < a.segments.length; i++) {
61878    const aSegment = a.segments[i];
61879    const bSegment = b.segments[i]; // if uris are different between segments there was a change
61880
61881    if (aSegment.uri !== bSegment.uri) {
61882      return false;
61883    } // neither segment has a byterange, there will be no byterange change.
61884
61885    if (!aSegment.byterange && !bSegment.byterange) {
61886      continue;
61887    }
61888    const aByterange = aSegment.byterange;
61889    const bByterange = bSegment.byterange; // if byterange only exists on one of the segments, there was a change.
61890
61891    if (aByterange && !bByterange || !aByterange && bByterange) {
61892      return false;
61893    } // if both segments have byterange with different offsets, there was a change.
61894
61895    if (aByterange.offset !== bByterange.offset || aByterange.length !== bByterange.length) {
61896      return false;
61897    }
61898  } // if everything was the same with segments, this is the same playlist.
61899
61900  return true;
61901};
61902/**
61903 * Use the representation IDs from the mpd object to create groupIDs, the NAME is set to mandatory representation
61904 * ID in the parser. This allows for continuous playout across periods with the same representation IDs
61905 * (continuous periods as defined in DASH-IF 3.2.12). This is assumed in the mpd-parser as well. If we want to support
61906 * periods without continuous playback this function may need modification as well as the parser.
61907 */
61908
61909const dashGroupId = (type, group, label, playlist) => {
61910  // If the manifest somehow does not have an ID (non-dash compliant), use the label.
61911  const playlistId = playlist.attributes.NAME || label;
61912  return `placeholder-uri-${type}-${group}-${playlistId}`;
61913};
61914/**
61915 * Parses the main XML string and updates playlist URI references.
61916 *
61917 * @param {Object} config
61918 *        Object of arguments
61919 * @param {string} config.mainXml
61920 *        The mpd XML
61921 * @param {string} config.srcUrl
61922 *        The mpd URL
61923 * @param {Date} config.clientOffset
61924 *         A time difference between server and client
61925 * @param {Object} config.sidxMapping
61926 *        SIDX mappings for moof/mdat URIs and byte ranges
61927 * @return {Object}
61928 *         The parsed mpd manifest object
61929 */
61930
61931const parseMainXml = ({
61932  mainXml,
61933  srcUrl,
61934  clientOffset,
61935  sidxMapping,
61936  previousManifest
61937}) => {
61938  const manifest = mpdParser.parse(mainXml, {
61939    manifestUri: srcUrl,
61940    clientOffset,
61941    sidxMapping,
61942    previousManifest
61943  });
61944  addPropertiesToMain(manifest, srcUrl, dashGroupId);
61945  return manifest;
61946};
61947/**
61948 * Removes any mediaGroup labels that no longer exist in the newMain
61949 *
61950 * @param {Object} update
61951 *         The previous mpd object being updated
61952 * @param {Object} newMain
61953 *         The new mpd object
61954 */
61955
61956const removeOldMediaGroupLabels = (update, newMain) => {
61957  forEachMediaGroup(update, (properties, type, group, label) => {
61958    if (!newMain.mediaGroups[type][group] || !(label in newMain.mediaGroups[type][group])) {
61959      delete update.mediaGroups[type][group][label];
61960    }
61961  });
61962};
61963/**
61964 * Returns a new main manifest that is the result of merging an updated main manifest
61965 * into the original version.
61966 *
61967 * @param {Object} oldMain
61968 *        The old parsed mpd object
61969 * @param {Object} newMain
61970 *        The updated parsed mpd object
61971 * @return {Object}
61972 *         A new object representing the original main manifest with the updated media
61973 *         playlists merged in
61974 */
61975
61976const updateMain = (oldMain, newMain, sidxMapping) => {
61977  let noChanges = true;
61978  let update = merge(oldMain, {
61979    // These are top level properties that can be updated
61980    duration: newMain.duration,
61981    minimumUpdatePeriod: newMain.minimumUpdatePeriod,
61982    timelineStarts: newMain.timelineStarts
61983  }); // First update the playlists in playlist list
61984
61985  for (let i = 0; i < newMain.playlists.length; i++) {
61986    const playlist = newMain.playlists[i];
61987    if (playlist.sidx) {
61988      const sidxKey = mpdParser.generateSidxKey(playlist.sidx); // add sidx segments to the playlist if we have all the sidx info already
61989
61990      if (sidxMapping && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx) {
61991        mpdParser.addSidxSegmentsToPlaylist(playlist, sidxMapping[sidxKey].sidx, playlist.sidx.resolvedUri);
61992      }
61993    }
61994    const playlistUpdate = updateMain$1(update, playlist, dashPlaylistUnchanged);
61995    if (playlistUpdate) {
61996      update = playlistUpdate;
61997      noChanges = false;
61998    }
61999  } // Then update media group playlists
62000
62001  forEachMediaGroup(newMain, (properties, type, group, label) => {
62002    if (properties.playlists && properties.playlists.length) {
62003      const id = properties.playlists[0].id;
62004      const playlistUpdate = updateMain$1(update, properties.playlists[0], dashPlaylistUnchanged);
62005      if (playlistUpdate) {
62006        update = playlistUpdate; // add new mediaGroup label if it doesn't exist and assign the new mediaGroup.
62007
62008        if (!(label in update.mediaGroups[type][group])) {
62009          update.mediaGroups[type][group][label] = properties;
62010        } // update the playlist reference within media groups
62011
62012        update.mediaGroups[type][group][label].playlists[0] = update.playlists[id];
62013        noChanges = false;
62014      }
62015    }
62016  }); // remove mediaGroup labels and references that no longer exist in the newMain
62017
62018  removeOldMediaGroupLabels(update, newMain);
62019  if (newMain.minimumUpdatePeriod !== oldMain.minimumUpdatePeriod) {
62020    noChanges = false;
62021  }
62022  if (noChanges) {
62023    return null;
62024  }
62025  return update;
62026}; // SIDX should be equivalent if the URI and byteranges of the SIDX match.
62027// If the SIDXs have maps, the two maps should match,
62028// both `a` and `b` missing SIDXs is considered matching.
62029// If `a` or `b` but not both have a map, they aren't matching.
62030
62031const equivalentSidx = (a, b) => {
62032  const neitherMap = Boolean(!a.map && !b.map);
62033  const equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length);
62034  return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length;
62035}; // exported for testing
62036
62037const compareSidxEntry = (playlists, oldSidxMapping) => {
62038  const newSidxMapping = {};
62039  for (const id in playlists) {
62040    const playlist = playlists[id];
62041    const currentSidxInfo = playlist.sidx;
62042    if (currentSidxInfo) {
62043      const key = mpdParser.generateSidxKey(currentSidxInfo);
62044      if (!oldSidxMapping[key]) {
62045        break;
62046      }
62047      const savedSidxInfo = oldSidxMapping[key].sidxInfo;
62048      if (equivalentSidx(savedSidxInfo, currentSidxInfo)) {
62049        newSidxMapping[key] = oldSidxMapping[key];
62050      }
62051    }
62052  }
62053  return newSidxMapping;
62054};
62055/**
62056 *  A function that filters out changed items as they need to be requested separately.
62057 *
62058 *  The method is exported for testing
62059 *
62060 *  @param {Object} main the parsed mpd XML returned via mpd-parser
62061 *  @param {Object} oldSidxMapping the SIDX to compare against
62062 */
62063
62064const filterChangedSidxMappings = (main, oldSidxMapping) => {
62065  const videoSidx = compareSidxEntry(main.playlists, oldSidxMapping);
62066  let mediaGroupSidx = videoSidx;
62067  forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
62068    if (properties.playlists && properties.playlists.length) {
62069      const playlists = properties.playlists;
62070      mediaGroupSidx = merge(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping));
62071    }
62072  });
62073  return mediaGroupSidx;
62074};
62075class DashPlaylistLoader extends EventTarget {
62076  // DashPlaylistLoader must accept either a src url or a playlist because subsequent
62077  // playlist loader setups from media groups will expect to be able to pass a playlist
62078  // (since there aren't external URLs to media playlists with DASH)
62079  constructor(srcUrlOrPlaylist, vhs, options = {}, mainPlaylistLoader) {
62080    super();
62081    this.isPaused_ = true;
62082    this.mainPlaylistLoader_ = mainPlaylistLoader || this;
62083    if (!mainPlaylistLoader) {
62084      this.isMain_ = true;
62085    }
62086    const {
62087      withCredentials = false
62088    } = options;
62089    this.vhs_ = vhs;
62090    this.withCredentials = withCredentials;
62091    this.addMetadataToTextTrack = options.addMetadataToTextTrack;
62092    if (!srcUrlOrPlaylist) {
62093      throw new Error('A non-empty playlist URL or object is required');
62094    } // event naming?
62095
62096    this.on('minimumUpdatePeriod', () => {
62097      this.refreshXml_();
62098    }); // live playlist staleness timeout
62099
62100    this.on('mediaupdatetimeout', () => {
62101      this.refreshMedia_(this.media().id);
62102    });
62103    this.state = 'HAVE_NOTHING';
62104    this.loadedPlaylists_ = {};
62105    this.logger_ = logger('DashPlaylistLoader'); // initialize the loader state
62106    // The mainPlaylistLoader will be created with a string
62107
62108    if (this.isMain_) {
62109      this.mainPlaylistLoader_.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes
62110      // once multi-period is refactored
62111
62112      this.mainPlaylistLoader_.sidxMapping_ = {};
62113    } else {
62114      this.childPlaylist_ = srcUrlOrPlaylist;
62115    }
62116  }
62117  get isPaused() {
62118    return this.isPaused_;
62119  }
62120  requestErrored_(err, request, startingState) {
62121    // disposed
62122    if (!this.request) {
62123      return true;
62124    } // pending request is cleared
62125
62126    this.request = null;
62127    if (err) {
62128      // use the provided error object or create one
62129      // based on the request/response
62130      this.error = typeof err === 'object' && !(err instanceof Error) ? err : {
62131        status: request.status,
62132        message: 'DASH request error at URL: ' + request.uri,
62133        response: request.response,
62134        // MEDIA_ERR_NETWORK
62135        code: 2,
62136        metadata: err.metadata
62137      };
62138      if (startingState) {
62139        this.state = startingState;
62140      }
62141      this.trigger('error');
62142      return true;
62143    }
62144  }
62145  /**
62146   * Verify that the container of the sidx segment can be parsed
62147   * and if it can, get and parse that segment.
62148   */
62149
62150  addSidxSegments_(playlist, startingState, cb) {
62151    const sidxKey = playlist.sidx && mpdParser.generateSidxKey(playlist.sidx); // playlist lacks sidx or sidx segments were added to this playlist already.
62152
62153    if (!playlist.sidx || !sidxKey || this.mainPlaylistLoader_.sidxMapping_[sidxKey]) {
62154      // keep this function async
62155      window__default["default"].clearTimeout(this.mediaRequest_);
62156      this.mediaRequest_ = window__default["default"].setTimeout(() => cb(false), 0);
62157      return;
62158    } // resolve the segment URL relative to the playlist
62159
62160    const uri = resolveManifestRedirect(playlist.sidx.resolvedUri);
62161    const fin = (err, request) => {
62162      if (this.requestErrored_(err, request, startingState)) {
62163        return;
62164      }
62165      const sidxMapping = this.mainPlaylistLoader_.sidxMapping_;
62166      const {
62167        requestType
62168      } = request;
62169      let sidx;
62170      try {
62171        sidx = parseSidx__default["default"](byteHelpers.toUint8(request.response).subarray(8));
62172      } catch (e) {
62173        e.metadata = getStreamingNetworkErrorMetadata({
62174          requestType,
62175          request,
62176          parseFailure: true
62177        }); // sidx parsing failed.
62178
62179        this.requestErrored_(e, request, startingState);
62180        return;
62181      }
62182      sidxMapping[sidxKey] = {
62183        sidxInfo: playlist.sidx,
62184        sidx
62185      };
62186      mpdParser.addSidxSegmentsToPlaylist(playlist, sidx, playlist.sidx.resolvedUri);
62187      return cb(true);
62188    };
62189    const REQUEST_TYPE = 'dash-sidx';
62190    this.request = containerRequest(uri, this.vhs_.xhr, (err, request, container, bytes) => {
62191      if (err) {
62192        return fin(err, request);
62193      }
62194      if (!container || container !== 'mp4') {
62195        const sidxContainer = container || 'unknown';
62196        return fin({
62197          status: request.status,
62198          message: `Unsupported ${sidxContainer} container type for sidx segment at URL: ${uri}`,
62199          // response is just bytes in this case
62200          // but we really don't want to return that.
62201          response: '',
62202          playlist,
62203          internal: true,
62204          playlistExclusionDuration: Infinity,
62205          // MEDIA_ERR_NETWORK
62206          code: 2
62207        }, request);
62208      } // if we already downloaded the sidx bytes in the container request, use them
62209
62210      const {
62211        offset,
62212        length
62213      } = playlist.sidx.byterange;
62214      if (bytes.length >= length + offset) {
62215        return fin(err, {
62216          response: bytes.subarray(offset, offset + length),
62217          status: request.status,
62218          uri: request.uri
62219        });
62220      } // otherwise request sidx bytes
62221
62222      this.request = this.vhs_.xhr({
62223        uri,
62224        responseType: 'arraybuffer',
62225        requestType: 'dash-sidx',
62226        headers: segmentXhrHeaders({
62227          byterange: playlist.sidx.byterange
62228        })
62229      }, fin);
62230    }, REQUEST_TYPE);
62231  }
62232  dispose() {
62233    this.isPaused_ = true;
62234    this.trigger('dispose');
62235    this.stopRequest();
62236    this.loadedPlaylists_ = {};
62237    window__default["default"].clearTimeout(this.minimumUpdatePeriodTimeout_);
62238    window__default["default"].clearTimeout(this.mediaRequest_);
62239    window__default["default"].clearTimeout(this.mediaUpdateTimeout);
62240    this.mediaUpdateTimeout = null;
62241    this.mediaRequest_ = null;
62242    this.minimumUpdatePeriodTimeout_ = null;
62243    if (this.mainPlaylistLoader_.createMupOnMedia_) {
62244      this.off('loadedmetadata', this.mainPlaylistLoader_.createMupOnMedia_);
62245      this.mainPlaylistLoader_.createMupOnMedia_ = null;
62246    }
62247    this.off();
62248  }
62249  hasPendingRequest() {
62250    return this.request || this.mediaRequest_;
62251  }
62252  stopRequest() {
62253    if (this.request) {
62254      const oldRequest = this.request;
62255      this.request = null;
62256      oldRequest.onreadystatechange = null;
62257      oldRequest.abort();
62258    }
62259  }
62260  media(playlist) {
62261    // getter
62262    if (!playlist) {
62263      return this.media_;
62264    } // setter
62265
62266    if (this.state === 'HAVE_NOTHING') {
62267      throw new Error('Cannot switch media playlist from ' + this.state);
62268    }
62269    const startingState = this.state; // find the playlist object if the target playlist has been specified by URI
62270
62271    if (typeof playlist === 'string') {
62272      if (!this.mainPlaylistLoader_.main.playlists[playlist]) {
62273        throw new Error('Unknown playlist URI: ' + playlist);
62274      }
62275      playlist = this.mainPlaylistLoader_.main.playlists[playlist];
62276    }
62277    const mediaChange = !this.media_ || playlist.id !== this.media_.id; // switch to previously loaded playlists immediately
62278
62279    if (mediaChange && this.loadedPlaylists_[playlist.id] && this.loadedPlaylists_[playlist.id].endList) {
62280      this.state = 'HAVE_METADATA';
62281      this.media_ = playlist; // trigger media change if the active media has been updated
62282
62283      if (mediaChange) {
62284        this.trigger('mediachanging');
62285        this.trigger('mediachange');
62286      }
62287      return;
62288    } // switching to the active playlist is a no-op
62289
62290    if (!mediaChange) {
62291      return;
62292    } // switching from an already loaded playlist
62293
62294    if (this.media_) {
62295      this.trigger('mediachanging');
62296    }
62297    this.addSidxSegments_(playlist, startingState, sidxChanged => {
62298      // everything is ready just continue to haveMetadata
62299      this.haveMetadata({
62300        startingState,
62301        playlist
62302      });
62303    });
62304  }
62305  haveMetadata({
62306    startingState,
62307    playlist
62308  }) {
62309    this.state = 'HAVE_METADATA';
62310    this.loadedPlaylists_[playlist.id] = playlist;
62311    window__default["default"].clearTimeout(this.mediaRequest_);
62312    this.mediaRequest_ = null; // This will trigger loadedplaylist
62313
62314    this.refreshMedia_(playlist.id); // fire loadedmetadata the first time a media playlist is loaded
62315    // to resolve setup of media groups
62316
62317    if (startingState === 'HAVE_MAIN_MANIFEST') {
62318      this.trigger('loadedmetadata');
62319    } else {
62320      // trigger media change if the active media has been updated
62321      this.trigger('mediachange');
62322    }
62323  }
62324  pause() {
62325    this.isPaused_ = true;
62326    if (this.mainPlaylistLoader_.createMupOnMedia_) {
62327      this.off('loadedmetadata', this.mainPlaylistLoader_.createMupOnMedia_);
62328      this.mainPlaylistLoader_.createMupOnMedia_ = null;
62329    }
62330    this.stopRequest();
62331    window__default["default"].clearTimeout(this.mediaUpdateTimeout);
62332    this.mediaUpdateTimeout = null;
62333    if (this.isMain_) {
62334      window__default["default"].clearTimeout(this.mainPlaylistLoader_.minimumUpdatePeriodTimeout_);
62335      this.mainPlaylistLoader_.minimumUpdatePeriodTimeout_ = null;
62336    }
62337    if (this.state === 'HAVE_NOTHING') {
62338      // If we pause the loader before any data has been retrieved, its as if we never
62339      // started, so reset to an unstarted state.
62340      this.started = false;
62341    }
62342  }
62343  load(isFinalRendition) {
62344    this.isPaused_ = false;
62345    window__default["default"].clearTimeout(this.mediaUpdateTimeout);
62346    this.mediaUpdateTimeout = null;
62347    const media = this.media();
62348    if (isFinalRendition) {
62349      const delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
62350      this.mediaUpdateTimeout = window__default["default"].setTimeout(() => this.load(), delay);
62351      return;
62352    } // because the playlists are internal to the manifest, load should either load the
62353    // main manifest, or do nothing but trigger an event
62354
62355    if (!this.started) {
62356      this.start();
62357      return;
62358    }
62359    if (media && !media.endList) {
62360      // Check to see if this is the main loader and the MUP was cleared (this happens
62361      // when the loader was paused). `media` should be set at this point since one is always
62362      // set during `start()`.
62363      if (this.isMain_ && !this.minimumUpdatePeriodTimeout_) {
62364        // Trigger minimumUpdatePeriod to refresh the main manifest
62365        this.trigger('minimumUpdatePeriod'); // Since there was no prior minimumUpdatePeriodTimeout it should be recreated
62366
62367        this.updateMinimumUpdatePeriodTimeout_();
62368      }
62369      this.trigger('mediaupdatetimeout');
62370    } else {
62371      this.trigger('loadedplaylist');
62372    }
62373  }
62374  start() {
62375    this.started = true; // We don't need to request the main manifest again
62376    // Call this asynchronously to match the xhr request behavior below
62377
62378    if (!this.isMain_) {
62379      window__default["default"].clearTimeout(this.mediaRequest_);
62380      this.mediaRequest_ = window__default["default"].setTimeout(() => this.haveMain_(), 0);
62381      return;
62382    }
62383    this.requestMain_((req, mainChanged) => {
62384      this.haveMain_();
62385      if (!this.hasPendingRequest() && !this.media_) {
62386        this.media(this.mainPlaylistLoader_.main.playlists[0]);
62387      }
62388    });
62389  }
62390  requestMain_(cb) {
62391    const metadata = {
62392      manifestInfo: {
62393        uri: this.mainPlaylistLoader_.srcUrl
62394      }
62395    };
62396    this.trigger({
62397      type: 'manifestrequeststart',
62398      metadata
62399    });
62400    this.request = this.vhs_.xhr({
62401      uri: this.mainPlaylistLoader_.srcUrl,
62402      withCredentials: this.withCredentials,
62403      requestType: 'dash-manifest'
62404    }, (error, req) => {
62405      if (error) {
62406        const {
62407          requestType
62408        } = req;
62409        error.metadata = getStreamingNetworkErrorMetadata({
62410          requestType,
62411          request: req,
62412          error
62413        });
62414      }
62415      if (this.requestErrored_(error, req)) {
62416        if (this.state === 'HAVE_NOTHING') {
62417          this.started = false;
62418        }
62419        return;
62420      }
62421      this.trigger({
62422        type: 'manifestrequestcomplete',
62423        metadata
62424      });
62425      const mainChanged = req.responseText !== this.mainPlaylistLoader_.mainXml_;
62426      this.mainPlaylistLoader_.mainXml_ = req.responseText;
62427      if (req.responseHeaders && req.responseHeaders.date) {
62428        this.mainLoaded_ = Date.parse(req.responseHeaders.date);
62429      } else {
62430        this.mainLoaded_ = Date.now();
62431      }
62432      this.mainPlaylistLoader_.srcUrl = resolveManifestRedirect(this.mainPlaylistLoader_.srcUrl, req);
62433      if (mainChanged) {
62434        this.handleMain_();
62435        this.syncClientServerClock_(() => {
62436          return cb(req, mainChanged);
62437        });
62438        return;
62439      }
62440      return cb(req, mainChanged);
62441    });
62442  }
62443  /**
62444   * Parses the main xml for UTCTiming node to sync the client clock to the server
62445   * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
62446   *
62447   * @param {Function} done
62448   *        Function to call when clock sync has completed
62449   */
62450
62451  syncClientServerClock_(done) {
62452    const utcTiming = mpdParser.parseUTCTiming(this.mainPlaylistLoader_.mainXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the
62453    // server clock
62454
62455    if (utcTiming === null) {
62456      this.mainPlaylistLoader_.clientOffset_ = this.mainLoaded_ - Date.now();
62457      return done();
62458    }
62459    if (utcTiming.method === 'DIRECT') {
62460      this.mainPlaylistLoader_.clientOffset_ = utcTiming.value - Date.now();
62461      return done();
62462    }
62463    this.request = this.vhs_.xhr({
62464      uri: resolveUrl(this.mainPlaylistLoader_.srcUrl, utcTiming.value),
62465      method: utcTiming.method,
62466      withCredentials: this.withCredentials,
62467      requestType: 'dash-clock-sync'
62468    }, (error, req) => {
62469      // disposed
62470      if (!this.request) {
62471        return;
62472      }
62473      if (error) {
62474        const {
62475          requestType
62476        } = req;
62477        this.error.metadata = getStreamingNetworkErrorMetadata({
62478          requestType,
62479          request: req,
62480          error
62481        }); // sync request failed, fall back to using date header from mpd
62482        // TODO: log warning
62483
62484        this.mainPlaylistLoader_.clientOffset_ = this.mainLoaded_ - Date.now();
62485        return done();
62486      }
62487      let serverTime;
62488      if (utcTiming.method === 'HEAD') {
62489        if (!req.responseHeaders || !req.responseHeaders.date) {
62490          // expected date header not preset, fall back to using date header from mpd
62491          // TODO: log warning
62492          serverTime = this.mainLoaded_;
62493        } else {
62494          serverTime = Date.parse(req.responseHeaders.date);
62495        }
62496      } else {
62497        serverTime = Date.parse(req.responseText);
62498      }
62499      this.mainPlaylistLoader_.clientOffset_ = serverTime - Date.now();
62500      done();
62501    });
62502  }
62503  haveMain_() {
62504    this.state = 'HAVE_MAIN_MANIFEST';
62505    if (this.isMain_) {
62506      // We have the main playlist at this point, so
62507      // trigger this to allow PlaylistController
62508      // to make an initial playlist selection
62509      this.trigger('loadedplaylist');
62510    } else if (!this.media_) {
62511      // no media playlist was specifically selected so select
62512      // the one the child playlist loader was created with
62513      this.media(this.childPlaylist_);
62514    }
62515  }
62516  handleMain_() {
62517    // clear media request
62518    window__default["default"].clearTimeout(this.mediaRequest_);
62519    this.mediaRequest_ = null;
62520    const oldMain = this.mainPlaylistLoader_.main;
62521    const metadata = {
62522      manifestInfo: {
62523        uri: this.mainPlaylistLoader_.srcUrl
62524      }
62525    };
62526    this.trigger({
62527      type: 'manifestparsestart',
62528      metadata
62529    });
62530    let newMain;
62531    try {
62532      newMain = parseMainXml({
62533        mainXml: this.mainPlaylistLoader_.mainXml_,
62534        srcUrl: this.mainPlaylistLoader_.srcUrl,
62535        clientOffset: this.mainPlaylistLoader_.clientOffset_,
62536        sidxMapping: this.mainPlaylistLoader_.sidxMapping_,
62537        previousManifest: oldMain
62538      });
62539    } catch (error) {
62540      this.error = error;
62541      this.error.metadata = {
62542        errorType: videojs.Error.StreamingDashManifestParserError,
62543        error
62544      };
62545      this.trigger('error');
62546    } // if we have an old main to compare the new main against
62547
62548    if (oldMain) {
62549      newMain = updateMain(oldMain, newMain, this.mainPlaylistLoader_.sidxMapping_);
62550    } // only update main if we have a new main
62551
62552    this.mainPlaylistLoader_.main = newMain ? newMain : oldMain;
62553    const location = this.mainPlaylistLoader_.main.locations && this.mainPlaylistLoader_.main.locations[0];
62554    if (location && location !== this.mainPlaylistLoader_.srcUrl) {
62555      this.mainPlaylistLoader_.srcUrl = location;
62556    }
62557    if (!oldMain || newMain && newMain.minimumUpdatePeriod !== oldMain.minimumUpdatePeriod) {
62558      this.updateMinimumUpdatePeriodTimeout_();
62559    }
62560    this.addEventStreamToMetadataTrack_(newMain);
62561    if (newMain) {
62562      const {
62563        duration,
62564        endList
62565      } = newMain;
62566      const renditions = [];
62567      newMain.playlists.forEach(playlist => {
62568        renditions.push({
62569          id: playlist.id,
62570          bandwidth: playlist.attributes.BANDWIDTH,
62571          resolution: playlist.attributes.RESOLUTION,
62572          codecs: playlist.attributes.CODECS
62573        });
62574      });
62575      const parsedManifest = {
62576        duration,
62577        isLive: !endList,
62578        renditions
62579      };
62580      metadata.parsedManifest = parsedManifest;
62581      this.trigger({
62582        type: 'manifestparsecomplete',
62583        metadata
62584      });
62585    }
62586    return Boolean(newMain);
62587  }
62588  updateMinimumUpdatePeriodTimeout_() {
62589    const mpl = this.mainPlaylistLoader_; // cancel any pending creation of mup on media
62590    // a new one will be added if needed.
62591
62592    if (mpl.createMupOnMedia_) {
62593      mpl.off('loadedmetadata', mpl.createMupOnMedia_);
62594      mpl.createMupOnMedia_ = null;
62595    } // clear any pending timeouts
62596
62597    if (mpl.minimumUpdatePeriodTimeout_) {
62598      window__default["default"].clearTimeout(mpl.minimumUpdatePeriodTimeout_);
62599      mpl.minimumUpdatePeriodTimeout_ = null;
62600    }
62601    let mup = mpl.main && mpl.main.minimumUpdatePeriod; // If the minimumUpdatePeriod has a value of 0, that indicates that the current
62602    // MPD has no future validity, so a new one will need to be acquired when new
62603    // media segments are to be made available. Thus, we use the target duration
62604    // in this case
62605
62606    if (mup === 0) {
62607      if (mpl.media()) {
62608        mup = mpl.media().targetDuration * 1000;
62609      } else {
62610        mpl.createMupOnMedia_ = mpl.updateMinimumUpdatePeriodTimeout_;
62611        mpl.one('loadedmetadata', mpl.createMupOnMedia_);
62612      }
62613    } // if minimumUpdatePeriod is invalid or <= zero, which
62614    // can happen when a live video becomes VOD. skip timeout
62615    // creation.
62616
62617    if (typeof mup !== 'number' || mup <= 0) {
62618      if (mup < 0) {
62619        this.logger_(`found invalid minimumUpdatePeriod of ${mup}, not setting a timeout`);
62620      }
62621      return;
62622    }
62623    this.createMUPTimeout_(mup);
62624  }
62625  createMUPTimeout_(mup) {
62626    const mpl = this.mainPlaylistLoader_;
62627    mpl.minimumUpdatePeriodTimeout_ = window__default["default"].setTimeout(() => {
62628      mpl.minimumUpdatePeriodTimeout_ = null;
62629      mpl.trigger('minimumUpdatePeriod');
62630      mpl.createMUPTimeout_(mup);
62631    }, mup);
62632  }
62633  /**
62634   * Sends request to refresh the main xml and updates the parsed main manifest
62635   */
62636
62637  refreshXml_() {
62638    this.requestMain_((req, mainChanged) => {
62639      if (!mainChanged) {
62640        return;
62641      }
62642      if (this.media_) {
62643        this.media_ = this.mainPlaylistLoader_.main.playlists[this.media_.id];
62644      } // This will filter out updated sidx info from the mapping
62645
62646      this.mainPlaylistLoader_.sidxMapping_ = filterChangedSidxMappings(this.mainPlaylistLoader_.main, this.mainPlaylistLoader_.sidxMapping_);
62647      this.addSidxSegments_(this.media(), this.state, sidxChanged => {
62648        // TODO: do we need to reload the current playlist?
62649        this.refreshMedia_(this.media().id);
62650      });
62651    });
62652  }
62653  /**
62654   * Refreshes the media playlist by re-parsing the main xml and updating playlist
62655   * references. If this is an alternate loader, the updated parsed manifest is retrieved
62656   * from the main loader.
62657   */
62658
62659  refreshMedia_(mediaID) {
62660    if (!mediaID) {
62661      throw new Error('refreshMedia_ must take a media id');
62662    } // for main we have to reparse the main xml
62663    // to re-create segments based on current timing values
62664    // which may change media. We only skip updating the main manifest
62665    // if this is the first time this.media_ is being set.
62666    // as main was just parsed in that case.
62667
62668    if (this.media_ && this.isMain_) {
62669      this.handleMain_();
62670    }
62671    const playlists = this.mainPlaylistLoader_.main.playlists;
62672    const mediaChanged = !this.media_ || this.media_ !== playlists[mediaID];
62673    if (mediaChanged) {
62674      this.media_ = playlists[mediaID];
62675    } else {
62676      this.trigger('playlistunchanged');
62677    }
62678    if (!this.mediaUpdateTimeout) {
62679      const createMediaUpdateTimeout = () => {
62680        if (this.media().endList) {
62681          return;
62682        }
62683        this.mediaUpdateTimeout = window__default["default"].setTimeout(() => {
62684          this.trigger('mediaupdatetimeout');
62685          createMediaUpdateTimeout();
62686        }, refreshDelay(this.media(), Boolean(mediaChanged)));
62687      };
62688      createMediaUpdateTimeout();
62689    }
62690    this.trigger('loadedplaylist');
62691  }
62692  /**
62693   * Takes eventstream data from a parsed DASH manifest and adds it to the metadata text track.
62694   *
62695   * @param {manifest} newMain the newly parsed manifest
62696   */
62697
62698  addEventStreamToMetadataTrack_(newMain) {
62699    // Only add new event stream metadata if we have a new manifest.
62700    if (newMain && this.mainPlaylistLoader_.main.eventStream) {
62701      // convert EventStream to ID3-like data.
62702      const metadataArray = this.mainPlaylistLoader_.main.eventStream.map(eventStreamNode => {
62703        return {
62704          cueTime: eventStreamNode.start,
62705          frames: [{
62706            data: eventStreamNode.messageData
62707          }]
62708        };
62709      });
62710      this.addMetadataToTextTrack('EventStream', metadataArray, this.mainPlaylistLoader_.main.duration);
62711    }
62712  }
62713  /**
62714   * Returns the key ID set from a playlist
62715   *
62716   * @param {playlist} playlist to fetch the key ID set from.
62717   * @return a Set of 32 digit hex strings that represent the unique keyIds for that playlist.
62718   */
62719
62720  getKeyIdSet(playlist) {
62721    if (playlist.contentProtection) {
vendor: 335,946 bytes, lines 62722-72250
62722      const keyIds = new Set();
62723      for (const keysystem in playlist.contentProtection) {
62724        const defaultKID = playlist.contentProtection[keysystem].attributes['cenc:default_KID'];
62725        if (defaultKID) {
62726          // DASH keyIds are separated by dashes.
62727          keyIds.add(defaultKID.replace(/-/g, '').toLowerCase());
62728        }
62729      }
62730      return keyIds;
62731    }
62732  }
62733}
62734var Config = {
62735  GOAL_BUFFER_LENGTH: 30,
62736  MAX_GOAL_BUFFER_LENGTH: 60,
62737  BACK_BUFFER_LENGTH: 30,
62738  GOAL_BUFFER_LENGTH_RATE: 1,
62739  // 0.5 MB/s
62740  INITIAL_BANDWIDTH: 4194304,
62741  // A fudge factor to apply to advertised playlist bitrates to account for
62742  // temporary flucations in client bandwidth
62743  BANDWIDTH_VARIANCE: 1.2,
62744  // How much of the buffer must be filled before we consider upswitching
62745  BUFFER_LOW_WATER_LINE: 0,
62746  MAX_BUFFER_LOW_WATER_LINE: 30,
62747  // TODO: Remove this when experimentalBufferBasedABR is removed
62748  EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE: 16,
62749  BUFFER_LOW_WATER_LINE_RATE: 1,
62750  // If the buffer is greater than the high water line, we won't switch down
62751  BUFFER_HIGH_WATER_LINE: 30
62752};
62753const stringToArrayBuffer = string => {
62754  const view = new Uint8Array(new ArrayBuffer(string.length));
62755  for (let i = 0; i < string.length; i++) {
62756    view[i] = string.charCodeAt(i);
62757  }
62758  return view.buffer;
62759};
62760
62761/* global Blob, BlobBuilder, Worker */
62762// unify worker interface
62763const browserWorkerPolyFill = function (workerObj) {
62764  // node only supports on/off
62765  workerObj.on = workerObj.addEventListener;
62766  workerObj.off = workerObj.removeEventListener;
62767  return workerObj;
62768};
62769const createObjectURL = function (str) {
62770  try {
62771    return URL.createObjectURL(new Blob([str], {
62772      type: 'application/javascript'
62773    }));
62774  } catch (e) {
62775    const blob = new BlobBuilder();
62776    blob.append(str);
62777    return URL.createObjectURL(blob.getBlob());
62778  }
62779};
62780const factory = function (code) {
62781  return function () {
62782    const objectUrl = createObjectURL(code);
62783    const worker = browserWorkerPolyFill(new Worker(objectUrl));
62784    worker.objURL = objectUrl;
62785    const terminate = worker.terminate;
62786    worker.on = worker.addEventListener;
62787    worker.off = worker.removeEventListener;
62788    worker.terminate = function () {
62789      URL.revokeObjectURL(objectUrl);
62790      return terminate.call(this);
62791    };
62792    return worker;
62793  };
62794};
62795const transform = function (code) {
62796  return `var browserWorkerPolyFill = ${browserWorkerPolyFill.toString()};\n` + 'browserWorkerPolyFill(self);\n' + code;
62797};
62798const getWorkerString = function (fn) {
62799  return fn.toString().replace(/^function.+?{/, '').slice(0, -1);
62800};
62801
62802/* rollup-plugin-worker-factory start for worker!/Users/srimron-soutter/repos/github/vjs/http-streaming/src/transmuxer-worker.js */
62803const workerCode$1 = transform(getWorkerString(function () {
62804  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
62805  /**
62806   * mux.js
62807   *
62808   * Copyright (c) Brightcove
62809   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
62810   *
62811   * A lightweight readable stream implemention that handles event dispatching.
62812   * Objects that inherit from streams should call init in their constructors.
62813   */
62814
62815  var Stream$8 = function () {
62816    this.init = function () {
62817      var listeners = {};
62818      /**
62819       * Add a listener for a specified event type.
62820       * @param type {string} the event name
62821       * @param listener {function} the callback to be invoked when an event of
62822       * the specified type occurs
62823       */
62824
62825      this.on = function (type, listener) {
62826        if (!listeners[type]) {
62827          listeners[type] = [];
62828        }
62829        listeners[type] = listeners[type].concat(listener);
62830      };
62831      /**
62832       * Remove a listener for a specified event type.
62833       * @param type {string} the event name
62834       * @param listener {function} a function previously registered for this
62835       * type of event through `on`
62836       */
62837
62838      this.off = function (type, listener) {
62839        var index;
62840        if (!listeners[type]) {
62841          return false;
62842        }
62843        index = listeners[type].indexOf(listener);
62844        listeners[type] = listeners[type].slice();
62845        listeners[type].splice(index, 1);
62846        return index > -1;
62847      };
62848      /**
62849       * Trigger an event of the specified type on this stream. Any additional
62850       * arguments to this function are passed as parameters to event listeners.
62851       * @param type {string} the event name
62852       */
62853
62854      this.trigger = function (type) {
62855        var callbacks, i, length, args;
62856        callbacks = listeners[type];
62857        if (!callbacks) {
62858          return;
62859        } // Slicing the arguments on every invocation of this method
62860        // can add a significant amount of overhead. Avoid the
62861        // intermediate object creation for the common case of a
62862        // single callback argument
62863
62864        if (arguments.length === 2) {
62865          length = callbacks.length;
62866          for (i = 0; i < length; ++i) {
62867            callbacks[i].call(this, arguments[1]);
62868          }
62869        } else {
62870          args = [];
62871          i = arguments.length;
62872          for (i = 1; i < arguments.length; ++i) {
62873            args.push(arguments[i]);
62874          }
62875          length = callbacks.length;
62876          for (i = 0; i < length; ++i) {
62877            callbacks[i].apply(this, args);
62878          }
62879        }
62880      };
62881      /**
62882       * Destroys the stream and cleans up.
62883       */
62884
62885      this.dispose = function () {
62886        listeners = {};
62887      };
62888    };
62889  };
62890  /**
62891   * Forwards all `data` events on this stream to the destination stream. The
62892   * destination stream should provide a method `push` to receive the data
62893   * events as they arrive.
62894   * @param destination {stream} the stream that will receive all `data` events
62895   * @param autoFlush {boolean} if false, we will not call `flush` on the destination
62896   *                            when the current stream emits a 'done' event
62897   * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
62898   */
62899
62900  Stream$8.prototype.pipe = function (destination) {
62901    this.on('data', function (data) {
62902      destination.push(data);
62903    });
62904    this.on('done', function (flushSource) {
62905      destination.flush(flushSource);
62906    });
62907    this.on('partialdone', function (flushSource) {
62908      destination.partialFlush(flushSource);
62909    });
62910    this.on('endedtimeline', function (flushSource) {
62911      destination.endTimeline(flushSource);
62912    });
62913    this.on('reset', function (flushSource) {
62914      destination.reset(flushSource);
62915    });
62916    return destination;
62917  }; // Default stream functions that are expected to be overridden to perform
62918  // actual work. These are provided by the prototype as a sort of no-op
62919  // implementation so that we don't have to check for their existence in the
62920  // `pipe` function above.
62921
62922  Stream$8.prototype.push = function (data) {
62923    this.trigger('data', data);
62924  };
62925  Stream$8.prototype.flush = function (flushSource) {
62926    this.trigger('done', flushSource);
62927  };
62928  Stream$8.prototype.partialFlush = function (flushSource) {
62929    this.trigger('partialdone', flushSource);
62930  };
62931  Stream$8.prototype.endTimeline = function (flushSource) {
62932    this.trigger('endedtimeline', flushSource);
62933  };
62934  Stream$8.prototype.reset = function (flushSource) {
62935    this.trigger('reset', flushSource);
62936  };
62937  var stream = Stream$8;
62938  var MAX_UINT32$1 = Math.pow(2, 32);
62939  var getUint64$5 = function (uint8) {
62940    var dv = new DataView(uint8.buffer, uint8.byteOffset, uint8.byteLength);
62941    var value;
62942    if (dv.getBigUint64) {
62943      value = dv.getBigUint64(0);
62944      if (value < Number.MAX_SAFE_INTEGER) {
62945        return Number(value);
62946      }
62947      return value;
62948    }
62949    return dv.getUint32(0) * MAX_UINT32$1 + dv.getUint32(4);
62950  };
62951  var numbers = {
62952    getUint64: getUint64$5,
62953    MAX_UINT32: MAX_UINT32$1
62954  };
62955  /**
62956   * mux.js
62957   *
62958   * Copyright (c) Brightcove
62959   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
62960   *
62961   * Functions that generate fragmented MP4s suitable for use with Media
62962   * Source Extensions.
62963   */
62964
62965  var MAX_UINT32 = numbers.MAX_UINT32;
62966  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
62967
62968  (function () {
62969    var i;
62970    types = {
62971      avc1: [],
62972      // codingname
62973      avcC: [],
62974      btrt: [],
62975      dinf: [],
62976      dref: [],
62977      esds: [],
62978      ftyp: [],
62979      hdlr: [],
62980      mdat: [],
62981      mdhd: [],
62982      mdia: [],
62983      mfhd: [],
62984      minf: [],
62985      moof: [],
62986      moov: [],
62987      mp4a: [],
62988      // codingname
62989      mvex: [],
62990      mvhd: [],
62991      pasp: [],
62992      sdtp: [],
62993      smhd: [],
62994      stbl: [],
62995      stco: [],
62996      stsc: [],
62997      stsd: [],
62998      stsz: [],
62999      stts: [],
63000      styp: [],
63001      tfdt: [],
63002      tfhd: [],
63003      traf: [],
63004      trak: [],
63005      trun: [],
63006      trex: [],
63007      tkhd: [],
63008      vmhd: []
63009    }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
63010    // don't throw an error
63011
63012    if (typeof Uint8Array === 'undefined') {
63013      return;
63014    }
63015    for (i in types) {
63016      if (types.hasOwnProperty(i)) {
63017        types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
63018      }
63019    }
63020    MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
63021    AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
63022    MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
63023    VIDEO_HDLR = new Uint8Array([0x00,
63024    // version 0
63025    0x00, 0x00, 0x00,
63026    // flags
63027    0x00, 0x00, 0x00, 0x00,
63028    // pre_defined
63029    0x76, 0x69, 0x64, 0x65,
63030    // handler_type: 'vide'
63031    0x00, 0x00, 0x00, 0x00,
63032    // reserved
63033    0x00, 0x00, 0x00, 0x00,
63034    // reserved
63035    0x00, 0x00, 0x00, 0x00,
63036    // reserved
63037    0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
63038    ]);
63039    AUDIO_HDLR = new Uint8Array([0x00,
63040    // version 0
63041    0x00, 0x00, 0x00,
63042    // flags
63043    0x00, 0x00, 0x00, 0x00,
63044    // pre_defined
63045    0x73, 0x6f, 0x75, 0x6e,
63046    // handler_type: 'soun'
63047    0x00, 0x00, 0x00, 0x00,
63048    // reserved
63049    0x00, 0x00, 0x00, 0x00,
63050    // reserved
63051    0x00, 0x00, 0x00, 0x00,
63052    // reserved
63053    0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
63054    ]);
63055    HDLR_TYPES = {
63056      video: VIDEO_HDLR,
63057      audio: AUDIO_HDLR
63058    };
63059    DREF = new Uint8Array([0x00,
63060    // version 0
63061    0x00, 0x00, 0x00,
63062    // flags
63063    0x00, 0x00, 0x00, 0x01,
63064    // entry_count
63065    0x00, 0x00, 0x00, 0x0c,
63066    // entry_size
63067    0x75, 0x72, 0x6c, 0x20,
63068    // 'url' type
63069    0x00,
63070    // version 0
63071    0x00, 0x00, 0x01 // entry_flags
63072    ]);
63073    SMHD = new Uint8Array([0x00,
63074    // version
63075    0x00, 0x00, 0x00,
63076    // flags
63077    0x00, 0x00,
63078    // balance, 0 means centered
63079    0x00, 0x00 // reserved
63080    ]);
63081    STCO = new Uint8Array([0x00,
63082    // version
63083    0x00, 0x00, 0x00,
63084    // flags
63085    0x00, 0x00, 0x00, 0x00 // entry_count
63086    ]);
63087    STSC = STCO;
63088    STSZ = new Uint8Array([0x00,
63089    // version
63090    0x00, 0x00, 0x00,
63091    // flags
63092    0x00, 0x00, 0x00, 0x00,
63093    // sample_size
63094    0x00, 0x00, 0x00, 0x00 // sample_count
63095    ]);
63096    STTS = STCO;
63097    VMHD = new Uint8Array([0x00,
63098    // version
63099    0x00, 0x00, 0x01,
63100    // flags
63101    0x00, 0x00,
63102    // graphicsmode
63103    0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
63104    ]);
63105  })();
63106  box = function (type) {
63107    var payload = [],
63108      size = 0,
63109      i,
63110      result,
63111      view;
63112    for (i = 1; i < arguments.length; i++) {
63113      payload.push(arguments[i]);
63114    }
63115    i = payload.length; // calculate the total size we need to allocate
63116
63117    while (i--) {
63118      size += payload[i].byteLength;
63119    }
63120    result = new Uint8Array(size + 8);
63121    view = new DataView(result.buffer, result.byteOffset, result.byteLength);
63122    view.setUint32(0, result.byteLength);
63123    result.set(type, 4); // copy the payload into the result
63124
63125    for (i = 0, size = 8; i < payload.length; i++) {
63126      result.set(payload[i], size);
63127      size += payload[i].byteLength;
63128    }
63129    return result;
63130  };
63131  dinf = function () {
63132    return box(types.dinf, box(types.dref, DREF));
63133  };
63134  esds = function (track) {
63135    return box(types.esds, new Uint8Array([0x00,
63136    // version
63137    0x00, 0x00, 0x00,
63138    // flags
63139    // ES_Descriptor
63140    0x03,
63141    // tag, ES_DescrTag
63142    0x19,
63143    // length
63144    0x00, 0x00,
63145    // ES_ID
63146    0x00,
63147    // streamDependenceFlag, URL_flag, reserved, streamPriority
63148    // DecoderConfigDescriptor
63149    0x04,
63150    // tag, DecoderConfigDescrTag
63151    0x11,
63152    // length
63153    0x40,
63154    // object type
63155    0x15,
63156    // streamType
63157    0x00, 0x06, 0x00,
63158    // bufferSizeDB
63159    0x00, 0x00, 0xda, 0xc0,
63160    // maxBitrate
63161    0x00, 0x00, 0xda, 0xc0,
63162    // avgBitrate
63163    // DecoderSpecificInfo
63164    0x05,
63165    // tag, DecoderSpecificInfoTag
63166    0x02,
63167    // length
63168    // ISO/IEC 14496-3, AudioSpecificConfig
63169    // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
63170    track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
63171    ]));
63172  };
63173  ftyp = function () {
63174    return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
63175  };
63176  hdlr = function (type) {
63177    return box(types.hdlr, HDLR_TYPES[type]);
63178  };
63179  mdat = function (data) {
63180    return box(types.mdat, data);
63181  };
63182  mdhd = function (track) {
63183    var result = new Uint8Array([0x00,
63184    // version 0
63185    0x00, 0x00, 0x00,
63186    // flags
63187    0x00, 0x00, 0x00, 0x02,
63188    // creation_time
63189    0x00, 0x00, 0x00, 0x03,
63190    // modification_time
63191    0x00, 0x01, 0x5f, 0x90,
63192    // timescale, 90,000 "ticks" per second
63193    track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF,
63194    // duration
63195    0x55, 0xc4,
63196    // 'und' language (undetermined)
63197    0x00, 0x00]); // Use the sample rate from the track metadata, when it is
63198    // defined. The sample rate can be parsed out of an ADTS header, for
63199    // instance.
63200
63201    if (track.samplerate) {
63202      result[12] = track.samplerate >>> 24 & 0xFF;
63203      result[13] = track.samplerate >>> 16 & 0xFF;
63204      result[14] = track.samplerate >>> 8 & 0xFF;
63205      result[15] = track.samplerate & 0xFF;
63206    }
63207    return box(types.mdhd, result);
63208  };
63209  mdia = function (track) {
63210    return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
63211  };
63212  mfhd = function (sequenceNumber) {
63213    return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00,
63214    // flags
63215    (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
63216    ]));
63217  };
63218  minf = function (track) {
63219    return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
63220  };
63221  moof = function (sequenceNumber, tracks) {
63222    var trackFragments = [],
63223      i = tracks.length; // build traf boxes for each track fragment
63224
63225    while (i--) {
63226      trackFragments[i] = traf(tracks[i]);
63227    }
63228    return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
63229  };
63230  /**
63231   * Returns a movie box.
63232   * @param tracks {array} the tracks associated with this movie
63233   * @see ISO/IEC 14496-12:2012(E), section 8.2.1
63234   */
63235
63236  moov = function (tracks) {
63237    var i = tracks.length,
63238      boxes = [];
63239    while (i--) {
63240      boxes[i] = trak(tracks[i]);
63241    }
63242    return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
63243  };
63244  mvex = function (tracks) {
63245    var i = tracks.length,
63246      boxes = [];
63247    while (i--) {
63248      boxes[i] = trex(tracks[i]);
63249    }
63250    return box.apply(null, [types.mvex].concat(boxes));
63251  };
63252  mvhd = function (duration) {
63253    var bytes = new Uint8Array([0x00,
63254    // version 0
63255    0x00, 0x00, 0x00,
63256    // flags
63257    0x00, 0x00, 0x00, 0x01,
63258    // creation_time
63259    0x00, 0x00, 0x00, 0x02,
63260    // modification_time
63261    0x00, 0x01, 0x5f, 0x90,
63262    // timescale, 90,000 "ticks" per second
63263    (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF,
63264    // duration
63265    0x00, 0x01, 0x00, 0x00,
63266    // 1.0 rate
63267    0x01, 0x00,
63268    // 1.0 volume
63269    0x00, 0x00,
63270    // reserved
63271    0x00, 0x00, 0x00, 0x00,
63272    // reserved
63273    0x00, 0x00, 0x00, 0x00,
63274    // reserved
63275    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,
63276    // transformation: unity matrix
63277    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
63278    // pre_defined
63279    0xff, 0xff, 0xff, 0xff // next_track_ID
63280    ]);
63281    return box(types.mvhd, bytes);
63282  };
63283  sdtp = function (track) {
63284    var samples = track.samples || [],
63285      bytes = new Uint8Array(4 + samples.length),
63286      flags,
63287      i; // leave the full box header (4 bytes) all zero
63288    // write the sample table
63289
63290    for (i = 0; i < samples.length; i++) {
63291      flags = samples[i].flags;
63292      bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
63293    }
63294    return box(types.sdtp, bytes);
63295  };
63296  stbl = function (track) {
63297    return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
63298  };
63299  (function () {
63300    var videoSample, audioSample;
63301    stsd = function (track) {
63302      return box(types.stsd, new Uint8Array([0x00,
63303      // version 0
63304      0x00, 0x00, 0x00,
63305      // flags
63306      0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
63307    };
63308    videoSample = function (track) {
63309      var sps = track.sps || [],
63310        pps = track.pps || [],
63311        sequenceParameterSets = [],
63312        pictureParameterSets = [],
63313        i,
63314        avc1Box; // assemble the SPSs
63315
63316      for (i = 0; i < sps.length; i++) {
63317        sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
63318        sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
63319
63320        sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
63321      } // assemble the PPSs
63322
63323      for (i = 0; i < pps.length; i++) {
63324        pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
63325        pictureParameterSets.push(pps[i].byteLength & 0xFF);
63326        pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
63327      }
63328      avc1Box = [types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
63329      // reserved
63330      0x00, 0x01,
63331      // data_reference_index
63332      0x00, 0x00,
63333      // pre_defined
63334      0x00, 0x00,
63335      // reserved
63336      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
63337      // pre_defined
63338      (track.width & 0xff00) >> 8, track.width & 0xff,
63339      // width
63340      (track.height & 0xff00) >> 8, track.height & 0xff,
63341      // height
63342      0x00, 0x48, 0x00, 0x00,
63343      // horizresolution
63344      0x00, 0x48, 0x00, 0x00,
63345      // vertresolution
63346      0x00, 0x00, 0x00, 0x00,
63347      // reserved
63348      0x00, 0x01,
63349      // frame_count
63350      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,
63351      // compressorname
63352      0x00, 0x18,
63353      // depth = 24
63354      0x11, 0x11 // pre_defined = -1
63355      ]), box(types.avcC, new Uint8Array([0x01,
63356      // configurationVersion
63357      track.profileIdc,
63358      // AVCProfileIndication
63359      track.profileCompatibility,
63360      // profile_compatibility
63361      track.levelIdc,
63362      // AVCLevelIndication
63363      0xff // lengthSizeMinusOne, hard-coded to 4 bytes
63364      ].concat([sps.length],
63365      // numOfSequenceParameterSets
63366      sequenceParameterSets,
63367      // "SPS"
63368      [pps.length],
63369      // numOfPictureParameterSets
63370      pictureParameterSets // "PPS"
63371      ))), box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
63372      // bufferSizeDB
63373      0x00, 0x2d, 0xc6, 0xc0,
63374      // maxBitrate
63375      0x00, 0x2d, 0xc6, 0xc0 // avgBitrate
63376      ]))];
63377      if (track.sarRatio) {
63378        var hSpacing = track.sarRatio[0],
63379          vSpacing = track.sarRatio[1];
63380        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])));
63381      }
63382      return box.apply(null, avc1Box);
63383    };
63384    audioSample = function (track) {
63385      return box(types.mp4a, new Uint8Array([
63386      // SampleEntry, ISO/IEC 14496-12
63387      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
63388      // reserved
63389      0x00, 0x01,
63390      // data_reference_index
63391      // AudioSampleEntry, ISO/IEC 14496-12
63392      0x00, 0x00, 0x00, 0x00,
63393      // reserved
63394      0x00, 0x00, 0x00, 0x00,
63395      // reserved
63396      (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff,
63397      // channelcount
63398      (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff,
63399      // samplesize
63400      0x00, 0x00,
63401      // pre_defined
63402      0x00, 0x00,
63403      // reserved
63404      (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
63405      // MP4AudioSampleEntry, ISO/IEC 14496-14
63406      ]), esds(track));
63407    };
63408  })();
63409  tkhd = function (track) {
63410    var result = new Uint8Array([0x00,
63411    // version 0
63412    0x00, 0x00, 0x07,
63413    // flags
63414    0x00, 0x00, 0x00, 0x00,
63415    // creation_time
63416    0x00, 0x00, 0x00, 0x00,
63417    // modification_time
63418    (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF,
63419    // track_ID
63420    0x00, 0x00, 0x00, 0x00,
63421    // reserved
63422    (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF,
63423    // duration
63424    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
63425    // reserved
63426    0x00, 0x00,
63427    // layer
63428    0x00, 0x00,
63429    // alternate_group
63430    0x01, 0x00,
63431    // non-audio track volume
63432    0x00, 0x00,
63433    // reserved
63434    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,
63435    // transformation: unity matrix
63436    (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00,
63437    // width
63438    (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
63439    ]);
63440    return box(types.tkhd, result);
63441  };
63442  /**
63443   * Generate a track fragment (traf) box. A traf box collects metadata
63444   * about tracks in a movie fragment (moof) box.
63445   */
63446
63447  traf = function (track) {
63448    var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
63449    trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00,
63450    // version 0
63451    0x00, 0x00, 0x3a,
63452    // flags
63453    (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF,
63454    // track_ID
63455    0x00, 0x00, 0x00, 0x01,
63456    // sample_description_index
63457    0x00, 0x00, 0x00, 0x00,
63458    // default_sample_duration
63459    0x00, 0x00, 0x00, 0x00,
63460    // default_sample_size
63461    0x00, 0x00, 0x00, 0x00 // default_sample_flags
63462    ]));
63463    upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / MAX_UINT32);
63464    lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % MAX_UINT32);
63465    trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01,
63466    // version 1
63467    0x00, 0x00, 0x00,
63468    // flags
63469    // baseMediaDecodeTime
63470    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
63471    // the containing moof to the first payload byte of the associated
63472    // mdat
63473
63474    dataOffset = 32 +
63475    // tfhd
63476    20 +
63477    // tfdt
63478    8 +
63479    // traf header
63480    16 +
63481    // mfhd
63482    8 +
63483    // moof header
63484    8; // mdat header
63485    // audio tracks require less metadata
63486
63487    if (track.type === 'audio') {
63488      trackFragmentRun = trun$1(track, dataOffset);
63489      return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
63490    } // video tracks should contain an independent and disposable samples
63491    // box (sdtp)
63492    // generate one and adjust offsets to match
63493
63494    sampleDependencyTable = sdtp(track);
63495    trackFragmentRun = trun$1(track, sampleDependencyTable.length + dataOffset);
63496    return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
63497  };
63498  /**
63499   * Generate a track box.
63500   * @param track {object} a track definition
63501   * @return {Uint8Array} the track box
63502   */
63503
63504  trak = function (track) {
63505    track.duration = track.duration || 0xffffffff;
63506    return box(types.trak, tkhd(track), mdia(track));
63507  };
63508  trex = function (track) {
63509    var result = new Uint8Array([0x00,
63510    // version 0
63511    0x00, 0x00, 0x00,
63512    // flags
63513    (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF,
63514    // track_ID
63515    0x00, 0x00, 0x00, 0x01,
63516    // default_sample_description_index
63517    0x00, 0x00, 0x00, 0x00,
63518    // default_sample_duration
63519    0x00, 0x00, 0x00, 0x00,
63520    // default_sample_size
63521    0x00, 0x01, 0x00, 0x01 // default_sample_flags
63522    ]); // the last two bytes of default_sample_flags is the sample
63523    // degradation priority, a hint about the importance of this sample
63524    // relative to others. Lower the degradation priority for all sample
63525    // types other than video.
63526
63527    if (track.type !== 'video') {
63528      result[result.length - 1] = 0x00;
63529    }
63530    return box(types.trex, result);
63531  };
63532  (function () {
63533    var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
63534    // duration is present for the first sample, it will be present for
63535    // all subsequent samples.
63536    // see ISO/IEC 14496-12:2012, Section 8.8.8.1
63537
63538    trunHeader = function (samples, offset) {
63539      var durationPresent = 0,
63540        sizePresent = 0,
63541        flagsPresent = 0,
63542        compositionTimeOffset = 0; // trun flag constants
63543
63544      if (samples.length) {
63545        if (samples[0].duration !== undefined) {
63546          durationPresent = 0x1;
63547        }
63548        if (samples[0].size !== undefined) {
63549          sizePresent = 0x2;
63550        }
63551        if (samples[0].flags !== undefined) {
63552          flagsPresent = 0x4;
63553        }
63554        if (samples[0].compositionTimeOffset !== undefined) {
63555          compositionTimeOffset = 0x8;
63556        }
63557      }
63558      return [0x00,
63559      // version 0
63560      0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01,
63561      // flags
63562      (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF,
63563      // sample_count
63564      (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
63565      ];
63566    };
63567    videoTrun = function (track, offset) {
63568      var bytesOffest, bytes, header, samples, sample, i;
63569      samples = track.samples || [];
63570      offset += 8 + 12 + 16 * samples.length;
63571      header = trunHeader(samples, offset);
63572      bytes = new Uint8Array(header.length + samples.length * 16);
63573      bytes.set(header);
63574      bytesOffest = header.length;
63575      for (i = 0; i < samples.length; i++) {
63576        sample = samples[i];
63577        bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
63578        bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
63579        bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
63580        bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
63581
63582        bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
63583        bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
63584        bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
63585        bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
63586
63587        bytes[bytesOffest++] = sample.flags.isLeading << 2 | sample.flags.dependsOn;
63588        bytes[bytesOffest++] = sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample;
63589        bytes[bytesOffest++] = sample.flags.degradationPriority & 0xF0 << 8;
63590        bytes[bytesOffest++] = sample.flags.degradationPriority & 0x0F; // sample_flags
63591
63592        bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF000000) >>> 24;
63593        bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF0000) >>> 16;
63594        bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF00) >>> 8;
63595        bytes[bytesOffest++] = sample.compositionTimeOffset & 0xFF; // sample_composition_time_offset
63596      }
63597      return box(types.trun, bytes);
63598    };
63599    audioTrun = function (track, offset) {
63600      var bytes, bytesOffest, header, samples, sample, i;
63601      samples = track.samples || [];
63602      offset += 8 + 12 + 8 * samples.length;
63603      header = trunHeader(samples, offset);
63604      bytes = new Uint8Array(header.length + samples.length * 8);
63605      bytes.set(header);
63606      bytesOffest = header.length;
63607      for (i = 0; i < samples.length; i++) {
63608        sample = samples[i];
63609        bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
63610        bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
63611        bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
63612        bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
63613
63614        bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
63615        bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
63616        bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
63617        bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
63618      }
63619      return box(types.trun, bytes);
63620    };
63621    trun$1 = function (track, offset) {
63622      if (track.type === 'audio') {
63623        return audioTrun(track, offset);
63624      }
63625      return videoTrun(track, offset);
63626    };
63627  })();
63628  var mp4Generator = {
63629    ftyp: ftyp,
63630    mdat: mdat,
63631    moof: moof,
63632    moov: moov,
63633    initSegment: function (tracks) {
63634      var fileType = ftyp(),
63635        movie = moov(tracks),
63636        result;
63637      result = new Uint8Array(fileType.byteLength + movie.byteLength);
63638      result.set(fileType);
63639      result.set(movie, fileType.byteLength);
63640      return result;
63641    }
63642  };
63643  /**
63644   * mux.js
63645   *
63646   * Copyright (c) Brightcove
63647   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
63648   */
63649  // composed of the nal units that make up that frame
63650  // Also keep track of cummulative data about the frame from the nal units such
63651  // as the frame duration, starting pts, etc.
63652
63653  var groupNalsIntoFrames = function (nalUnits) {
63654    var i,
63655      currentNal,
63656      currentFrame = [],
63657      frames = []; // TODO added for LHLS, make sure this is OK
63658
63659    frames.byteLength = 0;
63660    frames.nalCount = 0;
63661    frames.duration = 0;
63662    currentFrame.byteLength = 0;
63663    for (i = 0; i < nalUnits.length; i++) {
63664      currentNal = nalUnits[i]; // Split on 'aud'-type nal units
63665
63666      if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
63667        // Since the very first nal unit is expected to be an AUD
63668        // only push to the frames array when currentFrame is not empty
63669        if (currentFrame.length) {
63670          currentFrame.duration = currentNal.dts - currentFrame.dts; // TODO added for LHLS, make sure this is OK
63671
63672          frames.byteLength += currentFrame.byteLength;
63673          frames.nalCount += currentFrame.length;
63674          frames.duration += currentFrame.duration;
63675          frames.push(currentFrame);
63676        }
63677        currentFrame = [currentNal];
63678        currentFrame.byteLength = currentNal.data.byteLength;
63679        currentFrame.pts = currentNal.pts;
63680        currentFrame.dts = currentNal.dts;
63681      } else {
63682        // Specifically flag key frames for ease of use later
63683        if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
63684          currentFrame.keyFrame = true;
63685        }
63686        currentFrame.duration = currentNal.dts - currentFrame.dts;
63687        currentFrame.byteLength += currentNal.data.byteLength;
63688        currentFrame.push(currentNal);
63689      }
63690    } // For the last frame, use the duration of the previous frame if we
63691    // have nothing better to go on
63692
63693    if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
63694      currentFrame.duration = frames[frames.length - 1].duration;
63695    } // Push the final frame
63696    // TODO added for LHLS, make sure this is OK
63697
63698    frames.byteLength += currentFrame.byteLength;
63699    frames.nalCount += currentFrame.length;
63700    frames.duration += currentFrame.duration;
63701    frames.push(currentFrame);
63702    return frames;
63703  }; // Convert an array of frames into an array of Gop with each Gop being composed
63704  // of the frames that make up that Gop
63705  // Also keep track of cummulative data about the Gop from the frames such as the
63706  // Gop duration, starting pts, etc.
63707
63708  var groupFramesIntoGops = function (frames) {
63709    var i,
63710      currentFrame,
63711      currentGop = [],
63712      gops = []; // We must pre-set some of the values on the Gop since we
63713    // keep running totals of these values
63714
63715    currentGop.byteLength = 0;
63716    currentGop.nalCount = 0;
63717    currentGop.duration = 0;
63718    currentGop.pts = frames[0].pts;
63719    currentGop.dts = frames[0].dts; // store some metadata about all the Gops
63720
63721    gops.byteLength = 0;
63722    gops.nalCount = 0;
63723    gops.duration = 0;
63724    gops.pts = frames[0].pts;
63725    gops.dts = frames[0].dts;
63726    for (i = 0; i < frames.length; i++) {
63727      currentFrame = frames[i];
63728      if (currentFrame.keyFrame) {
63729        // Since the very first frame is expected to be an keyframe
63730        // only push to the gops array when currentGop is not empty
63731        if (currentGop.length) {
63732          gops.push(currentGop);
63733          gops.byteLength += currentGop.byteLength;
63734          gops.nalCount += currentGop.nalCount;
63735          gops.duration += currentGop.duration;
63736        }
63737        currentGop = [currentFrame];
63738        currentGop.nalCount = currentFrame.length;
63739        currentGop.byteLength = currentFrame.byteLength;
63740        currentGop.pts = currentFrame.pts;
63741        currentGop.dts = currentFrame.dts;
63742        currentGop.duration = currentFrame.duration;
63743      } else {
63744        currentGop.duration += currentFrame.duration;
63745        currentGop.nalCount += currentFrame.length;
63746        currentGop.byteLength += currentFrame.byteLength;
63747        currentGop.push(currentFrame);
63748      }
63749    }
63750    if (gops.length && currentGop.duration <= 0) {
63751      currentGop.duration = gops[gops.length - 1].duration;
63752    }
63753    gops.byteLength += currentGop.byteLength;
63754    gops.nalCount += currentGop.nalCount;
63755    gops.duration += currentGop.duration; // push the final Gop
63756
63757    gops.push(currentGop);
63758    return gops;
63759  };
63760  /*
63761   * Search for the first keyframe in the GOPs and throw away all frames
63762   * until that keyframe. Then extend the duration of the pulled keyframe
63763   * and pull the PTS and DTS of the keyframe so that it covers the time
63764   * range of the frames that were disposed.
63765   *
63766   * @param {Array} gops video GOPs
63767   * @returns {Array} modified video GOPs
63768   */
63769
63770  var extendFirstKeyFrame = function (gops) {
63771    var currentGop;
63772    if (!gops[0][0].keyFrame && gops.length > 1) {
63773      // Remove the first GOP
63774      currentGop = gops.shift();
63775      gops.byteLength -= currentGop.byteLength;
63776      gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
63777      // first gop to cover the time period of the
63778      // frames we just removed
63779
63780      gops[0][0].dts = currentGop.dts;
63781      gops[0][0].pts = currentGop.pts;
63782      gops[0][0].duration += currentGop.duration;
63783    }
63784    return gops;
63785  };
63786  /**
63787   * Default sample object
63788   * see ISO/IEC 14496-12:2012, section 8.6.4.3
63789   */
63790
63791  var createDefaultSample = function () {
63792    return {
63793      size: 0,
63794      flags: {
63795        isLeading: 0,
63796        dependsOn: 1,
63797        isDependedOn: 0,
63798        hasRedundancy: 0,
63799        degradationPriority: 0,
63800        isNonSyncSample: 1
63801      }
63802    };
63803  };
63804  /*
63805   * Collates information from a video frame into an object for eventual
63806   * entry into an MP4 sample table.
63807   *
63808   * @param {Object} frame the video frame
63809   * @param {Number} dataOffset the byte offset to position the sample
63810   * @return {Object} object containing sample table info for a frame
63811   */
63812
63813  var sampleForFrame = function (frame, dataOffset) {
63814    var sample = createDefaultSample();
63815    sample.dataOffset = dataOffset;
63816    sample.compositionTimeOffset = frame.pts - frame.dts;
63817    sample.duration = frame.duration;
63818    sample.size = 4 * frame.length; // Space for nal unit size
63819
63820    sample.size += frame.byteLength;
63821    if (frame.keyFrame) {
63822      sample.flags.dependsOn = 2;
63823      sample.flags.isNonSyncSample = 0;
63824    }
63825    return sample;
63826  }; // generate the track's sample table from an array of gops
63827
63828  var generateSampleTable$1 = function (gops, baseDataOffset) {
63829    var h,
63830      i,
63831      sample,
63832      currentGop,
63833      currentFrame,
63834      dataOffset = baseDataOffset || 0,
63835      samples = [];
63836    for (h = 0; h < gops.length; h++) {
63837      currentGop = gops[h];
63838      for (i = 0; i < currentGop.length; i++) {
63839        currentFrame = currentGop[i];
63840        sample = sampleForFrame(currentFrame, dataOffset);
63841        dataOffset += sample.size;
63842        samples.push(sample);
63843      }
63844    }
63845    return samples;
63846  }; // generate the track's raw mdat data from an array of gops
63847
63848  var concatenateNalData = function (gops) {
63849    var h,
63850      i,
63851      j,
63852      currentGop,
63853      currentFrame,
63854      currentNal,
63855      dataOffset = 0,
63856      nalsByteLength = gops.byteLength,
63857      numberOfNals = gops.nalCount,
63858      totalByteLength = nalsByteLength + 4 * numberOfNals,
63859      data = new Uint8Array(totalByteLength),
63860      view = new DataView(data.buffer); // For each Gop..
63861
63862    for (h = 0; h < gops.length; h++) {
63863      currentGop = gops[h]; // For each Frame..
63864
63865      for (i = 0; i < currentGop.length; i++) {
63866        currentFrame = currentGop[i]; // For each NAL..
63867
63868        for (j = 0; j < currentFrame.length; j++) {
63869          currentNal = currentFrame[j];
63870          view.setUint32(dataOffset, currentNal.data.byteLength);
63871          dataOffset += 4;
63872          data.set(currentNal.data, dataOffset);
63873          dataOffset += currentNal.data.byteLength;
63874        }
63875      }
63876    }
63877    return data;
63878  }; // generate the track's sample table from a frame
63879
63880  var generateSampleTableForFrame = function (frame, baseDataOffset) {
63881    var sample,
63882      dataOffset = baseDataOffset || 0,
63883      samples = [];
63884    sample = sampleForFrame(frame, dataOffset);
63885    samples.push(sample);
63886    return samples;
63887  }; // generate the track's raw mdat data from a frame
63888
63889  var concatenateNalDataForFrame = function (frame) {
63890    var i,
63891      currentNal,
63892      dataOffset = 0,
63893      nalsByteLength = frame.byteLength,
63894      numberOfNals = frame.length,
63895      totalByteLength = nalsByteLength + 4 * numberOfNals,
63896      data = new Uint8Array(totalByteLength),
63897      view = new DataView(data.buffer); // For each NAL..
63898
63899    for (i = 0; i < frame.length; i++) {
63900      currentNal = frame[i];
63901      view.setUint32(dataOffset, currentNal.data.byteLength);
63902      dataOffset += 4;
63903      data.set(currentNal.data, dataOffset);
63904      dataOffset += currentNal.data.byteLength;
63905    }
63906    return data;
63907  };
63908  var frameUtils$1 = {
63909    groupNalsIntoFrames: groupNalsIntoFrames,
63910    groupFramesIntoGops: groupFramesIntoGops,
63911    extendFirstKeyFrame: extendFirstKeyFrame,
63912    generateSampleTable: generateSampleTable$1,
63913    concatenateNalData: concatenateNalData,
63914    generateSampleTableForFrame: generateSampleTableForFrame,
63915    concatenateNalDataForFrame: concatenateNalDataForFrame
63916  };
63917  /**
63918   * mux.js
63919   *
63920   * Copyright (c) Brightcove
63921   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
63922   */
63923
63924  var highPrefix = [33, 16, 5, 32, 164, 27];
63925  var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
63926  var zeroFill = function (count) {
63927    var a = [];
63928    while (count--) {
63929      a.push(0);
63930    }
63931    return a;
63932  };
63933  var makeTable = function (metaTable) {
63934    return Object.keys(metaTable).reduce(function (obj, key) {
63935      obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
63936        return arr.concat(part);
63937      }, []));
63938      return obj;
63939    }, {});
63940  };
63941  var silence;
63942  var silence_1 = function () {
63943    if (!silence) {
63944      // Frames-of-silence to use for filling in missing AAC frames
63945      var coneOfSilence = {
63946        96000: [highPrefix, [227, 64], zeroFill(154), [56]],
63947        88200: [highPrefix, [231], zeroFill(170), [56]],
63948        64000: [highPrefix, [248, 192], zeroFill(240), [56]],
63949        48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
63950        44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
63951        32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
63952        24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
63953        16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
63954        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]],
63955        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]],
63956        8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
63957      };
63958      silence = makeTable(coneOfSilence);
63959    }
63960    return silence;
63961  };
63962  /**
63963   * mux.js
63964   *
63965   * Copyright (c) Brightcove
63966   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
63967   */
63968
63969  var ONE_SECOND_IN_TS$4 = 90000,
63970    // 90kHz clock
63971    secondsToVideoTs,
63972    secondsToAudioTs,
63973    videoTsToSeconds,
63974    audioTsToSeconds,
63975    audioTsToVideoTs,
63976    videoTsToAudioTs,
63977    metadataTsToSeconds;
63978  secondsToVideoTs = function (seconds) {
63979    return seconds * ONE_SECOND_IN_TS$4;
63980  };
63981  secondsToAudioTs = function (seconds, sampleRate) {
63982    return seconds * sampleRate;
63983  };
63984  videoTsToSeconds = function (timestamp) {
63985    return timestamp / ONE_SECOND_IN_TS$4;
63986  };
63987  audioTsToSeconds = function (timestamp, sampleRate) {
63988    return timestamp / sampleRate;
63989  };
63990  audioTsToVideoTs = function (timestamp, sampleRate) {
63991    return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
63992  };
63993  videoTsToAudioTs = function (timestamp, sampleRate) {
63994    return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
63995  };
63996  /**
63997   * Adjust ID3 tag or caption timing information by the timeline pts values
63998   * (if keepOriginalTimestamps is false) and convert to seconds
63999   */
64000
64001  metadataTsToSeconds = function (timestamp, timelineStartPts, keepOriginalTimestamps) {
64002    return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
64003  };
64004  var clock$2 = {
64005    ONE_SECOND_IN_TS: ONE_SECOND_IN_TS$4,
64006    secondsToVideoTs: secondsToVideoTs,
64007    secondsToAudioTs: secondsToAudioTs,
64008    videoTsToSeconds: videoTsToSeconds,
64009    audioTsToSeconds: audioTsToSeconds,
64010    audioTsToVideoTs: audioTsToVideoTs,
64011    videoTsToAudioTs: videoTsToAudioTs,
64012    metadataTsToSeconds: metadataTsToSeconds
64013  };
64014  /**
64015   * mux.js
64016   *
64017   * Copyright (c) Brightcove
64018   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
64019   */
64020
64021  var coneOfSilence = silence_1;
64022  var clock$1 = clock$2;
64023  /**
64024   * Sum the `byteLength` properties of the data in each AAC frame
64025   */
64026
64027  var sumFrameByteLengths = function (array) {
64028    var i,
64029      currentObj,
64030      sum = 0; // sum the byteLength's all each nal unit in the frame
64031
64032    for (i = 0; i < array.length; i++) {
64033      currentObj = array[i];
64034      sum += currentObj.data.byteLength;
64035    }
64036    return sum;
64037  }; // Possibly pad (prefix) the audio track with silence if appending this track
64038  // would lead to the introduction of a gap in the audio buffer
64039
64040  var prefixWithSilence = function (track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
64041    var baseMediaDecodeTimeTs,
64042      frameDuration = 0,
64043      audioGapDuration = 0,
64044      audioFillFrameCount = 0,
64045      audioFillDuration = 0,
64046      silentFrame,
64047      i,
64048      firstFrame;
64049    if (!frames.length) {
64050      return;
64051    }
64052    baseMediaDecodeTimeTs = clock$1.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
64053
64054    frameDuration = Math.ceil(clock$1.ONE_SECOND_IN_TS / (track.samplerate / 1024));
64055    if (audioAppendStartTs && videoBaseMediaDecodeTime) {
64056      // insert the shortest possible amount (audio gap or audio to video gap)
64057      audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
64058
64059      audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
64060      audioFillDuration = audioFillFrameCount * frameDuration;
64061    } // don't attempt to fill gaps smaller than a single frame or larger
64062    // than a half second
64063
64064    if (audioFillFrameCount < 1 || audioFillDuration > clock$1.ONE_SECOND_IN_TS / 2) {
64065      return;
64066    }
64067    silentFrame = coneOfSilence()[track.samplerate];
64068    if (!silentFrame) {
64069      // we don't have a silent frame pregenerated for the sample rate, so use a frame
64070      // from the content instead
64071      silentFrame = frames[0].data;
64072    }
64073    for (i = 0; i < audioFillFrameCount; i++) {
64074      firstFrame = frames[0];
64075      frames.splice(0, 0, {
64076        data: silentFrame,
64077        dts: firstFrame.dts - frameDuration,
64078        pts: firstFrame.pts - frameDuration
64079      });
64080    }
64081    track.baseMediaDecodeTime -= Math.floor(clock$1.videoTsToAudioTs(audioFillDuration, track.samplerate));
64082    return audioFillDuration;
64083  }; // If the audio segment extends before the earliest allowed dts
64084  // value, remove AAC frames until starts at or after the earliest
64085  // allowed DTS so that we don't end up with a negative baseMedia-
64086  // DecodeTime for the audio track
64087
64088  var trimAdtsFramesByEarliestDts = function (adtsFrames, track, earliestAllowedDts) {
64089    if (track.minSegmentDts >= earliestAllowedDts) {
64090      return adtsFrames;
64091    } // We will need to recalculate the earliest segment Dts
64092
64093    track.minSegmentDts = Infinity;
64094    return adtsFrames.filter(function (currentFrame) {
64095      // If this is an allowed frame, keep it and record it's Dts
64096      if (currentFrame.dts >= earliestAllowedDts) {
64097        track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
64098        track.minSegmentPts = track.minSegmentDts;
64099        return true;
64100      } // Otherwise, discard it
64101
64102      return false;
64103    });
64104  }; // generate the track's raw mdat data from an array of frames
64105
64106  var generateSampleTable = function (frames) {
64107    var i,
64108      currentFrame,
64109      samples = [];
64110    for (i = 0; i < frames.length; i++) {
64111      currentFrame = frames[i];
64112      samples.push({
64113        size: currentFrame.data.byteLength,
64114        duration: 1024 // For AAC audio, all samples contain 1024 samples
64115      });
64116    }
64117    return samples;
64118  }; // generate the track's sample table from an array of frames
64119
64120  var concatenateFrameData = function (frames) {
64121    var i,
64122      currentFrame,
64123      dataOffset = 0,
64124      data = new Uint8Array(sumFrameByteLengths(frames));
64125    for (i = 0; i < frames.length; i++) {
64126      currentFrame = frames[i];
64127      data.set(currentFrame.data, dataOffset);
64128      dataOffset += currentFrame.data.byteLength;
64129    }
64130    return data;
64131  };
64132  var audioFrameUtils$1 = {
64133    prefixWithSilence: prefixWithSilence,
64134    trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
64135    generateSampleTable: generateSampleTable,
64136    concatenateFrameData: concatenateFrameData
64137  };
64138  /**
64139   * mux.js
64140   *
64141   * Copyright (c) Brightcove
64142   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
64143   */
64144
64145  var ONE_SECOND_IN_TS$3 = clock$2.ONE_SECOND_IN_TS;
64146  /**
64147   * Store information about the start and end of the track and the
64148   * duration for each frame/sample we process in order to calculate
64149   * the baseMediaDecodeTime
64150   */
64151
64152  var collectDtsInfo = function (track, data) {
64153    if (typeof data.pts === 'number') {
64154      if (track.timelineStartInfo.pts === undefined) {
64155        track.timelineStartInfo.pts = data.pts;
64156      }
64157      if (track.minSegmentPts === undefined) {
64158        track.minSegmentPts = data.pts;
64159      } else {
64160        track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
64161      }
64162      if (track.maxSegmentPts === undefined) {
64163        track.maxSegmentPts = data.pts;
64164      } else {
64165        track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
64166      }
64167    }
64168    if (typeof data.dts === 'number') {
64169      if (track.timelineStartInfo.dts === undefined) {
64170        track.timelineStartInfo.dts = data.dts;
64171      }
64172      if (track.minSegmentDts === undefined) {
64173        track.minSegmentDts = data.dts;
64174      } else {
64175        track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
64176      }
64177      if (track.maxSegmentDts === undefined) {
64178        track.maxSegmentDts = data.dts;
64179      } else {
64180        track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
64181      }
64182    }
64183  };
64184  /**
64185   * Clear values used to calculate the baseMediaDecodeTime between
64186   * tracks
64187   */
64188
64189  var clearDtsInfo = function (track) {
64190    delete track.minSegmentDts;
64191    delete track.maxSegmentDts;
64192    delete track.minSegmentPts;
64193    delete track.maxSegmentPts;
64194  };
64195  /**
64196   * Calculate the track's baseMediaDecodeTime based on the earliest
64197   * DTS the transmuxer has ever seen and the minimum DTS for the
64198   * current track
64199   * @param track {object} track metadata configuration
64200   * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
64201   *        in the source; false to adjust the first segment to start at 0.
64202   */
64203
64204  var calculateTrackBaseMediaDecodeTime = function (track, keepOriginalTimestamps) {
64205    var baseMediaDecodeTime,
64206      scale,
64207      minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
64208
64209    if (!keepOriginalTimestamps) {
64210      minSegmentDts -= track.timelineStartInfo.dts;
64211    } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
64212    // we want the start of the first segment to be placed
64213
64214    baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
64215
64216    baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
64217
64218    baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
64219    if (track.type === 'audio') {
64220      // Audio has a different clock equal to the sampling_rate so we need to
64221      // scale the PTS values into the clock rate of the track
64222      scale = track.samplerate / ONE_SECOND_IN_TS$3;
64223      baseMediaDecodeTime *= scale;
64224      baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
64225    }
64226    return baseMediaDecodeTime;
64227  };
64228  var trackDecodeInfo$1 = {
64229    clearDtsInfo: clearDtsInfo,
64230    calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
64231    collectDtsInfo: collectDtsInfo
64232  };
64233  /**
64234   * mux.js
64235   *
64236   * Copyright (c) Brightcove
64237   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
64238   *
64239   * Reads in-band caption information from a video elementary
64240   * stream. Captions must follow the CEA-708 standard for injection
64241   * into an MPEG-2 transport streams.
64242   * @see https://en.wikipedia.org/wiki/CEA-708
64243   * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
64244   */
64245  // payload type field to indicate how they are to be
64246  // interpreted. CEAS-708 caption content is always transmitted with
64247  // payload type 0x04.
64248
64249  var USER_DATA_REGISTERED_ITU_T_T35 = 4,
64250    RBSP_TRAILING_BITS = 128;
64251  /**
64252    * Parse a supplemental enhancement information (SEI) NAL unit.
64253    * Stops parsing once a message of type ITU T T35 has been found.
64254    *
64255    * @param bytes {Uint8Array} the bytes of a SEI NAL unit
64256    * @return {object} the parsed SEI payload
64257    * @see Rec. ITU-T H.264, 7.3.2.3.1
64258    */
64259
64260  var parseSei = function (bytes) {
64261    var i = 0,
64262      result = {
64263        payloadType: -1,
64264        payloadSize: 0
64265      },
64266      payloadType = 0,
64267      payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
64268
64269    while (i < bytes.byteLength) {
64270      // stop once we have hit the end of the sei_rbsp
64271      if (bytes[i] === RBSP_TRAILING_BITS) {
64272        break;
64273      } // Parse payload type
64274
64275      while (bytes[i] === 0xFF) {
64276        payloadType += 255;
64277        i++;
64278      }
64279      payloadType += bytes[i++]; // Parse payload size
64280
64281      while (bytes[i] === 0xFF) {
64282        payloadSize += 255;
64283        i++;
64284      }
64285      payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
64286      // there can only ever be one caption message in a frame's sei
64287
64288      if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
64289        var userIdentifier = String.fromCharCode(bytes[i + 3], bytes[i + 4], bytes[i + 5], bytes[i + 6]);
64290        if (userIdentifier === 'GA94') {
64291          result.payloadType = payloadType;
64292          result.payloadSize = payloadSize;
64293          result.payload = bytes.subarray(i, i + payloadSize);
64294          break;
64295        } else {
64296          result.payload = void 0;
64297        }
64298      } // skip the payload and parse the next message
64299
64300      i += payloadSize;
64301      payloadType = 0;
64302      payloadSize = 0;
64303    }
64304    return result;
64305  }; // see ANSI/SCTE 128-1 (2013), section 8.1
64306
64307  var parseUserData = function (sei) {
64308    // itu_t_t35_contry_code must be 181 (United States) for
64309    // captions
64310    if (sei.payload[0] !== 181) {
64311      return null;
64312    } // itu_t_t35_provider_code should be 49 (ATSC) for captions
64313
64314    if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
64315      return null;
64316    } // the user_identifier should be "GA94" to indicate ATSC1 data
64317
64318    if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
64319      return null;
64320    } // finally, user_data_type_code should be 0x03 for caption data
64321
64322    if (sei.payload[7] !== 0x03) {
64323      return null;
64324    } // return the user_data_type_structure and strip the trailing
64325    // marker bits
64326
64327    return sei.payload.subarray(8, sei.payload.length - 1);
64328  }; // see CEA-708-D, section 4.4
64329
64330  var parseCaptionPackets = function (pts, userData) {
64331    var results = [],
64332      i,
64333      count,
64334      offset,
64335      data; // if this is just filler, return immediately
64336
64337    if (!(userData[0] & 0x40)) {
64338      return results;
64339    } // parse out the cc_data_1 and cc_data_2 fields
64340
64341    count = userData[0] & 0x1f;
64342    for (i = 0; i < count; i++) {
64343      offset = i * 3;
64344      data = {
64345        type: userData[offset + 2] & 0x03,
64346        pts: pts
64347      }; // capture cc data when cc_valid is 1
64348
64349      if (userData[offset + 2] & 0x04) {
64350        data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
64351        results.push(data);
64352      }
64353    }
64354    return results;
64355  };
64356  var discardEmulationPreventionBytes$1 = function (data) {
64357    var length = data.byteLength,
64358      emulationPreventionBytesPositions = [],
64359      i = 1,
64360      newLength,
64361      newData; // Find all `Emulation Prevention Bytes`
64362
64363    while (i < length - 2) {
64364      if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
64365        emulationPreventionBytesPositions.push(i + 2);
64366        i += 2;
64367      } else {
64368        i++;
64369      }
64370    } // If no Emulation Prevention Bytes were found just return the original
64371    // array
64372
64373    if (emulationPreventionBytesPositions.length === 0) {
64374      return data;
64375    } // Create a new array to hold the NAL unit data
64376
64377    newLength = length - emulationPreventionBytesPositions.length;
64378    newData = new Uint8Array(newLength);
64379    var sourceIndex = 0;
64380    for (i = 0; i < newLength; sourceIndex++, i++) {
64381      if (sourceIndex === emulationPreventionBytesPositions[0]) {
64382        // Skip this byte
64383        sourceIndex++; // Remove this position index
64384
64385        emulationPreventionBytesPositions.shift();
64386      }
64387      newData[i] = data[sourceIndex];
64388    }
64389    return newData;
64390  }; // exports
64391
64392  var captionPacketParser = {
64393    parseSei: parseSei,
64394    parseUserData: parseUserData,
64395    parseCaptionPackets: parseCaptionPackets,
64396    discardEmulationPreventionBytes: discardEmulationPreventionBytes$1,
64397    USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
64398  };
64399  /**
64400   * mux.js
64401   *
64402   * Copyright (c) Brightcove
64403   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
64404   *
64405   * Reads in-band caption information from a video elementary
64406   * stream. Captions must follow the CEA-708 standard for injection
64407   * into an MPEG-2 transport streams.
64408   * @see https://en.wikipedia.org/wiki/CEA-708
64409   * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
64410   */
64411  // Link To Transport
64412  // -----------------
64413
64414  var Stream$7 = stream;
64415  var cea708Parser = captionPacketParser;
64416  var CaptionStream$2 = function (options) {
64417    options = options || {};
64418    CaptionStream$2.prototype.init.call(this); // parse708captions flag, default to true
64419
64420    this.parse708captions_ = typeof options.parse708captions === 'boolean' ? options.parse708captions : true;
64421    this.captionPackets_ = [];
64422    this.ccStreams_ = [new Cea608Stream(0, 0),
64423    // eslint-disable-line no-use-before-define
64424    new Cea608Stream(0, 1),
64425    // eslint-disable-line no-use-before-define
64426    new Cea608Stream(1, 0),
64427    // eslint-disable-line no-use-before-define
64428    new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
64429    ];
64430    if (this.parse708captions_) {
64431      this.cc708Stream_ = new Cea708Stream({
64432        captionServices: options.captionServices
64433      }); // eslint-disable-line no-use-before-define
64434    }
64435    this.reset(); // forward data and done events from CCs to this CaptionStream
64436
64437    this.ccStreams_.forEach(function (cc) {
64438      cc.on('data', this.trigger.bind(this, 'data'));
64439      cc.on('partialdone', this.trigger.bind(this, 'partialdone'));
64440      cc.on('done', this.trigger.bind(this, 'done'));
64441    }, this);
64442    if (this.parse708captions_) {
64443      this.cc708Stream_.on('data', this.trigger.bind(this, 'data'));
64444      this.cc708Stream_.on('partialdone', this.trigger.bind(this, 'partialdone'));
64445      this.cc708Stream_.on('done', this.trigger.bind(this, 'done'));
64446    }
64447  };
64448  CaptionStream$2.prototype = new Stream$7();
64449  CaptionStream$2.prototype.push = function (event) {
64450    var sei, userData, newCaptionPackets; // only examine SEI NALs
64451
64452    if (event.nalUnitType !== 'sei_rbsp') {
64453      return;
64454    } // parse the sei
64455
64456    sei = cea708Parser.parseSei(event.escapedRBSP); // no payload data, skip
64457
64458    if (!sei.payload) {
64459      return;
64460    } // ignore everything but user_data_registered_itu_t_t35
64461
64462    if (sei.payloadType !== cea708Parser.USER_DATA_REGISTERED_ITU_T_T35) {
64463      return;
64464    } // parse out the user data payload
64465
64466    userData = cea708Parser.parseUserData(sei); // ignore unrecognized userData
64467
64468    if (!userData) {
64469      return;
64470    } // Sometimes, the same segment # will be downloaded twice. To stop the
64471    // caption data from being processed twice, we track the latest dts we've
64472    // received and ignore everything with a dts before that. However, since
64473    // data for a specific dts can be split across packets on either side of
64474    // a segment boundary, we need to make sure we *don't* ignore the packets
64475    // from the *next* segment that have dts === this.latestDts_. By constantly
64476    // tracking the number of packets received with dts === this.latestDts_, we
64477    // know how many should be ignored once we start receiving duplicates.
64478
64479    if (event.dts < this.latestDts_) {
64480      // We've started getting older data, so set the flag.
64481      this.ignoreNextEqualDts_ = true;
64482      return;
64483    } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
64484      this.numSameDts_--;
64485      if (!this.numSameDts_) {
64486        // We've received the last duplicate packet, time to start processing again
64487        this.ignoreNextEqualDts_ = false;
64488      }
64489      return;
64490    } // parse out CC data packets and save them for later
64491
64492    newCaptionPackets = cea708Parser.parseCaptionPackets(event.pts, userData);
64493    this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
64494    if (this.latestDts_ !== event.dts) {
64495      this.numSameDts_ = 0;
64496    }
64497    this.numSameDts_++;
64498    this.latestDts_ = event.dts;
64499  };
64500  CaptionStream$2.prototype.flushCCStreams = function (flushType) {
64501    this.ccStreams_.forEach(function (cc) {
64502      return flushType === 'flush' ? cc.flush() : cc.partialFlush();
64503    }, this);
64504  };
64505  CaptionStream$2.prototype.flushStream = function (flushType) {
64506    // make sure we actually parsed captions before proceeding
64507    if (!this.captionPackets_.length) {
64508      this.flushCCStreams(flushType);
64509      return;
64510    } // In Chrome, the Array#sort function is not stable so add a
64511    // presortIndex that we can use to ensure we get a stable-sort
64512
64513    this.captionPackets_.forEach(function (elem, idx) {
64514      elem.presortIndex = idx;
64515    }); // sort caption byte-pairs based on their PTS values
64516
64517    this.captionPackets_.sort(function (a, b) {
64518      if (a.pts === b.pts) {
64519        return a.presortIndex - b.presortIndex;
64520      }
64521      return a.pts - b.pts;
64522    });
64523    this.captionPackets_.forEach(function (packet) {
64524      if (packet.type < 2) {
64525        // Dispatch packet to the right Cea608Stream
64526        this.dispatchCea608Packet(packet);
64527      } else {
64528        // Dispatch packet to the Cea708Stream
64529        this.dispatchCea708Packet(packet);
64530      }
64531    }, this);
64532    this.captionPackets_.length = 0;
64533    this.flushCCStreams(flushType);
64534  };
64535  CaptionStream$2.prototype.flush = function () {
64536    return this.flushStream('flush');
64537  }; // Only called if handling partial data
64538
64539  CaptionStream$2.prototype.partialFlush = function () {
64540    return this.flushStream('partialFlush');
64541  };
64542  CaptionStream$2.prototype.reset = function () {
64543    this.latestDts_ = null;
64544    this.ignoreNextEqualDts_ = false;
64545    this.numSameDts_ = 0;
64546    this.activeCea608Channel_ = [null, null];
64547    this.ccStreams_.forEach(function (ccStream) {
64548      ccStream.reset();
64549    });
64550  }; // From the CEA-608 spec:
64551
64552  /*
64553   * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed
64554   * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is
64555   * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair
64556   * and subsequent data should then be processed according to the FCC rules. It may be necessary for the
64557   * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD)
64558   * to switch to captioning or Text.
64559  */
64560  // With that in mind, we ignore any data between an XDS control code and a
64561  // subsequent closed-captioning control code.
64562
64563  CaptionStream$2.prototype.dispatchCea608Packet = function (packet) {
64564    // NOTE: packet.type is the CEA608 field
64565    if (this.setsTextOrXDSActive(packet)) {
64566      this.activeCea608Channel_[packet.type] = null;
64567    } else if (this.setsChannel1Active(packet)) {
64568      this.activeCea608Channel_[packet.type] = 0;
64569    } else if (this.setsChannel2Active(packet)) {
64570      this.activeCea608Channel_[packet.type] = 1;
64571    }
64572    if (this.activeCea608Channel_[packet.type] === null) {
64573      // If we haven't received anything to set the active channel, or the
64574      // packets are Text/XDS data, discard the data; we don't want jumbled
64575      // captions
64576      return;
64577    }
64578    this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
64579  };
64580  CaptionStream$2.prototype.setsChannel1Active = function (packet) {
64581    return (packet.ccData & 0x7800) === 0x1000;
64582  };
64583  CaptionStream$2.prototype.setsChannel2Active = function (packet) {
64584    return (packet.ccData & 0x7800) === 0x1800;
64585  };
64586  CaptionStream$2.prototype.setsTextOrXDSActive = function (packet) {
64587    return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a;
64588  };
64589  CaptionStream$2.prototype.dispatchCea708Packet = function (packet) {
64590    if (this.parse708captions_) {
64591      this.cc708Stream_.push(packet);
64592    }
64593  }; // ----------------------
64594  // Session to Application
64595  // ----------------------
64596  // This hash maps special and extended character codes to their
64597  // proper Unicode equivalent. The first one-byte key is just a
64598  // non-standard character code. The two-byte keys that follow are
64599  // the extended CEA708 character codes, along with the preceding
64600  // 0x10 extended character byte to distinguish these codes from
64601  // non-extended character codes. Every CEA708 character code that
64602  // is not in this object maps directly to a standard unicode
64603  // character code.
64604  // The transparent space and non-breaking transparent space are
64605  // technically not fully supported since there is no code to
64606  // make them transparent, so they have normal non-transparent
64607  // stand-ins.
64608  // The special closed caption (CC) character isn't a standard
64609  // unicode character, so a fairly similar unicode character was
64610  // chosen in it's place.
64611
64612  var CHARACTER_TRANSLATION_708 = {
64613    0x7f: 0x266a,
64614    // ♪
64615    0x1020: 0x20,
64616    // Transparent Space
64617    0x1021: 0xa0,
64618    // Nob-breaking Transparent Space
64619    0x1025: 0x2026,
64620    // …
64621    0x102a: 0x0160,
64622    // Å 
64623    0x102c: 0x0152,
64624    // Œ
64625    0x1030: 0x2588,
64626    // █
64627    0x1031: 0x2018,
64628    // ‘
64629    0x1032: 0x2019,
64630    // ’
64631    0x1033: 0x201c,
64632    // “
64633    0x1034: 0x201d,
64634    // ”
64635    0x1035: 0x2022,
64636    // •
64637    0x1039: 0x2122,
64638    // ™
64639    0x103a: 0x0161,
64640    // Å¡
64641    0x103c: 0x0153,
64642    // œ
64643    0x103d: 0x2120,
64644    // ℠
64645    0x103f: 0x0178,
64646    // Ÿ
64647    0x1076: 0x215b,
64648    // ⅛
64649    0x1077: 0x215c,
64650    // ⅜
64651    0x1078: 0x215d,
64652    // ⅝
64653    0x1079: 0x215e,
64654    // ⅞
64655    0x107a: 0x23d0,
64656    // ⏐
64657    0x107b: 0x23a4,
64658    // ⎤
64659    0x107c: 0x23a3,
64660    // ⎣
64661    0x107d: 0x23af,
64662    // ⎯
64663    0x107e: 0x23a6,
64664    // ⎦
64665    0x107f: 0x23a1,
64666    // ⎡
64667    0x10a0: 0x3138 // ㄸ (CC char)
64668  };
64669  var get708CharFromCode = function (code) {
64670    var newCode = CHARACTER_TRANSLATION_708[code] || code;
64671    if (code & 0x1000 && code === newCode) {
64672      // Invalid extended code
64673      return '';
64674    }
64675    return String.fromCharCode(newCode);
64676  };
64677  var within708TextBlock = function (b) {
64678    return 0x20 <= b && b <= 0x7f || 0xa0 <= b && b <= 0xff;
64679  };
64680  var Cea708Window = function (windowNum) {
64681    this.windowNum = windowNum;
64682    this.reset();
64683  };
64684  Cea708Window.prototype.reset = function () {
64685    this.clearText();
64686    this.pendingNewLine = false;
64687    this.winAttr = {};
64688    this.penAttr = {};
64689    this.penLoc = {};
64690    this.penColor = {}; // These default values are arbitrary,
64691    // defineWindow will usually override them
64692
64693    this.visible = 0;
64694    this.rowLock = 0;
64695    this.columnLock = 0;
64696    this.priority = 0;
64697    this.relativePositioning = 0;
64698    this.anchorVertical = 0;
64699    this.anchorHorizontal = 0;
64700    this.anchorPoint = 0;
64701    this.rowCount = 1;
64702    this.virtualRowCount = this.rowCount + 1;
64703    this.columnCount = 41;
64704    this.windowStyle = 0;
64705    this.penStyle = 0;
64706  };
64707  Cea708Window.prototype.getText = function () {
64708    return this.rows.join('\n');
64709  };
64710  Cea708Window.prototype.clearText = function () {
64711    this.rows = [''];
64712    this.rowIdx = 0;
64713  };
64714  Cea708Window.prototype.newLine = function (pts) {
64715    if (this.rows.length >= this.virtualRowCount && typeof this.beforeRowOverflow === 'function') {
64716      this.beforeRowOverflow(pts);
64717    }
64718    if (this.rows.length > 0) {
64719      this.rows.push('');
64720      this.rowIdx++;
64721    } // Show all virtual rows since there's no visible scrolling
64722
64723    while (this.rows.length > this.virtualRowCount) {
64724      this.rows.shift();
64725      this.rowIdx--;
64726    }
64727  };
64728  Cea708Window.prototype.isEmpty = function () {
64729    if (this.rows.length === 0) {
64730      return true;
64731    } else if (this.rows.length === 1) {
64732      return this.rows[0] === '';
64733    }
64734    return false;
64735  };
64736  Cea708Window.prototype.addText = function (text) {
64737    this.rows[this.rowIdx] += text;
64738  };
64739  Cea708Window.prototype.backspace = function () {
64740    if (!this.isEmpty()) {
64741      var row = this.rows[this.rowIdx];
64742      this.rows[this.rowIdx] = row.substr(0, row.length - 1);
64743    }
64744  };
64745  var Cea708Service = function (serviceNum, encoding, stream) {
64746    this.serviceNum = serviceNum;
64747    this.text = '';
64748    this.currentWindow = new Cea708Window(-1);
64749    this.windows = [];
64750    this.stream = stream; // Try to setup a TextDecoder if an `encoding` value was provided
64751
64752    if (typeof encoding === 'string') {
64753      this.createTextDecoder(encoding);
64754    }
64755  };
64756  /**
64757   * Initialize service windows
64758   * Must be run before service use
64759   *
64760   * @param  {Integer}  pts               PTS value
64761   * @param  {Function} beforeRowOverflow Function to execute before row overflow of a window
64762   */
64763
64764  Cea708Service.prototype.init = function (pts, beforeRowOverflow) {
64765    this.startPts = pts;
64766    for (var win = 0; win < 8; win++) {
64767      this.windows[win] = new Cea708Window(win);
64768      if (typeof beforeRowOverflow === 'function') {
64769        this.windows[win].beforeRowOverflow = beforeRowOverflow;
64770      }
64771    }
64772  };
64773  /**
64774   * Set current window of service to be affected by commands
64775   *
64776   * @param  {Integer} windowNum Window number
64777   */
64778
64779  Cea708Service.prototype.setCurrentWindow = function (windowNum) {
64780    this.currentWindow = this.windows[windowNum];
64781  };
64782  /**
64783   * Try to create a TextDecoder if it is natively supported
64784   */
64785
64786  Cea708Service.prototype.createTextDecoder = function (encoding) {
64787    if (typeof TextDecoder === 'undefined') {
64788      this.stream.trigger('log', {
64789        level: 'warn',
64790        message: 'The `encoding` option is unsupported without TextDecoder support'
64791      });
64792    } else {
64793      try {
64794        this.textDecoder_ = new TextDecoder(encoding);
64795      } catch (error) {
64796        this.stream.trigger('log', {
64797          level: 'warn',
64798          message: 'TextDecoder could not be created with ' + encoding + ' encoding. ' + error
64799        });
64800      }
64801    }
64802  };
64803  var Cea708Stream = function (options) {
64804    options = options || {};
64805    Cea708Stream.prototype.init.call(this);
64806    var self = this;
64807    var captionServices = options.captionServices || {};
64808    var captionServiceEncodings = {};
64809    var serviceProps; // Get service encodings from captionServices option block
64810
64811    Object.keys(captionServices).forEach(serviceName => {
64812      serviceProps = captionServices[serviceName];
64813      if (/^SERVICE/.test(serviceName)) {
64814        captionServiceEncodings[serviceName] = serviceProps.encoding;
64815      }
64816    });
64817    this.serviceEncodings = captionServiceEncodings;
64818    this.current708Packet = null;
64819    this.services = {};
64820    this.push = function (packet) {
64821      if (packet.type === 3) {
64822        // 708 packet start
64823        self.new708Packet();
64824        self.add708Bytes(packet);
64825      } else {
64826        if (self.current708Packet === null) {
64827          // This should only happen at the start of a file if there's no packet start.
64828          self.new708Packet();
64829        }
64830        self.add708Bytes(packet);
64831      }
64832    };
64833  };
64834  Cea708Stream.prototype = new Stream$7();
64835  /**
64836   * Push current 708 packet, create new 708 packet.
64837   */
64838
64839  Cea708Stream.prototype.new708Packet = function () {
64840    if (this.current708Packet !== null) {
64841      this.push708Packet();
64842    }
64843    this.current708Packet = {
64844      data: [],
64845      ptsVals: []
64846    };
64847  };
64848  /**
64849   * Add pts and both bytes from packet into current 708 packet.
64850   */
64851
64852  Cea708Stream.prototype.add708Bytes = function (packet) {
64853    var data = packet.ccData;
64854    var byte0 = data >>> 8;
64855    var byte1 = data & 0xff; // I would just keep a list of packets instead of bytes, but it isn't clear in the spec
64856    // that service blocks will always line up with byte pairs.
64857
64858    this.current708Packet.ptsVals.push(packet.pts);
64859    this.current708Packet.data.push(byte0);
64860    this.current708Packet.data.push(byte1);
64861  };
64862  /**
64863   * Parse completed 708 packet into service blocks and push each service block.
64864   */
64865
64866  Cea708Stream.prototype.push708Packet = function () {
64867    var packet708 = this.current708Packet;
64868    var packetData = packet708.data;
64869    var serviceNum = null;
64870    var blockSize = null;
64871    var i = 0;
64872    var b = packetData[i++];
64873    packet708.seq = b >> 6;
64874    packet708.sizeCode = b & 0x3f; // 0b00111111;
64875
64876    for (; i < packetData.length; i++) {
64877      b = packetData[i++];
64878      serviceNum = b >> 5;
64879      blockSize = b & 0x1f; // 0b00011111
64880
64881      if (serviceNum === 7 && blockSize > 0) {
64882        // Extended service num
64883        b = packetData[i++];
64884        serviceNum = b;
64885      }
64886      this.pushServiceBlock(serviceNum, i, blockSize);
64887      if (blockSize > 0) {
64888        i += blockSize - 1;
64889      }
64890    }
64891  };
64892  /**
64893   * Parse service block, execute commands, read text.
64894   *
64895   * Note: While many of these commands serve important purposes,
64896   * many others just parse out the parameters or attributes, but
64897   * nothing is done with them because this is not a full and complete
64898   * implementation of the entire 708 spec.
64899   *
64900   * @param  {Integer} serviceNum Service number
64901   * @param  {Integer} start      Start index of the 708 packet data
64902   * @param  {Integer} size       Block size
64903   */
64904
64905  Cea708Stream.prototype.pushServiceBlock = function (serviceNum, start, size) {
64906    var b;
64907    var i = start;
64908    var packetData = this.current708Packet.data;
64909    var service = this.services[serviceNum];
64910    if (!service) {
64911      service = this.initService(serviceNum, i);
64912    }
64913    for (; i < start + size && i < packetData.length; i++) {
64914      b = packetData[i];
64915      if (within708TextBlock(b)) {
64916        i = this.handleText(i, service);
64917      } else if (b === 0x18) {
64918        i = this.multiByteCharacter(i, service);
64919      } else if (b === 0x10) {
64920        i = this.extendedCommands(i, service);
64921      } else if (0x80 <= b && b <= 0x87) {
64922        i = this.setCurrentWindow(i, service);
64923      } else if (0x98 <= b && b <= 0x9f) {
64924        i = this.defineWindow(i, service);
64925      } else if (b === 0x88) {
64926        i = this.clearWindows(i, service);
64927      } else if (b === 0x8c) {
64928        i = this.deleteWindows(i, service);
64929      } else if (b === 0x89) {
64930        i = this.displayWindows(i, service);
64931      } else if (b === 0x8a) {
64932        i = this.hideWindows(i, service);
64933      } else if (b === 0x8b) {
64934        i = this.toggleWindows(i, service);
64935      } else if (b === 0x97) {
64936        i = this.setWindowAttributes(i, service);
64937      } else if (b === 0x90) {
64938        i = this.setPenAttributes(i, service);
64939      } else if (b === 0x91) {
64940        i = this.setPenColor(i, service);
64941      } else if (b === 0x92) {
64942        i = this.setPenLocation(i, service);
64943      } else if (b === 0x8f) {
64944        service = this.reset(i, service);
64945      } else if (b === 0x08) {
64946        // BS: Backspace
64947        service.currentWindow.backspace();
64948      } else if (b === 0x0c) {
64949        // FF: Form feed
64950        service.currentWindow.clearText();
64951      } else if (b === 0x0d) {
64952        // CR: Carriage return
64953        service.currentWindow.pendingNewLine = true;
64954      } else if (b === 0x0e) {
64955        // HCR: Horizontal carriage return
64956        service.currentWindow.clearText();
64957      } else if (b === 0x8d) {
64958        // DLY: Delay, nothing to do
64959        i++;
64960      } else ;
64961    }
64962  };
64963  /**
64964   * Execute an extended command
64965   *
64966   * @param  {Integer} i        Current index in the 708 packet
64967   * @param  {Service} service  The service object to be affected
64968   * @return {Integer}          New index after parsing
64969   */
64970
64971  Cea708Stream.prototype.extendedCommands = function (i, service) {
64972    var packetData = this.current708Packet.data;
64973    var b = packetData[++i];
64974    if (within708TextBlock(b)) {
64975      i = this.handleText(i, service, {
64976        isExtended: true
64977      });
64978    }
64979    return i;
64980  };
64981  /**
64982   * Get PTS value of a given byte index
64983   *
64984   * @param  {Integer} byteIndex  Index of the byte
64985   * @return {Integer}            PTS
64986   */
64987
64988  Cea708Stream.prototype.getPts = function (byteIndex) {
64989    // There's 1 pts value per 2 bytes
64990    return this.current708Packet.ptsVals[Math.floor(byteIndex / 2)];
64991  };
64992  /**
64993   * Initializes a service
64994   *
64995   * @param  {Integer} serviceNum Service number
64996   * @return {Service}            Initialized service object
64997   */
64998
64999  Cea708Stream.prototype.initService = function (serviceNum, i) {
65000    var serviceName = 'SERVICE' + serviceNum;
65001    var self = this;
65002    var serviceName;
65003    var encoding;
65004    if (serviceName in this.serviceEncodings) {
65005      encoding = this.serviceEncodings[serviceName];
65006    }
65007    this.services[serviceNum] = new Cea708Service(serviceNum, encoding, self);
65008    this.services[serviceNum].init(this.getPts(i), function (pts) {
65009      self.flushDisplayed(pts, self.services[serviceNum]);
65010    });
65011    return this.services[serviceNum];
65012  };
65013  /**
65014   * Execute text writing to current window
65015   *
65016   * @param  {Integer} i        Current index in the 708 packet
65017   * @param  {Service} service  The service object to be affected
65018   * @return {Integer}          New index after parsing
65019   */
65020
65021  Cea708Stream.prototype.handleText = function (i, service, options) {
65022    var isExtended = options && options.isExtended;
65023    var isMultiByte = options && options.isMultiByte;
65024    var packetData = this.current708Packet.data;
65025    var extended = isExtended ? 0x1000 : 0x0000;
65026    var currentByte = packetData[i];
65027    var nextByte = packetData[i + 1];
65028    var win = service.currentWindow;
65029    var char;
65030    var charCodeArray; // Converts an array of bytes to a unicode hex string.
65031
65032    function toHexString(byteArray) {
65033      return byteArray.map(byte => {
65034        return ('0' + (byte & 0xFF).toString(16)).slice(-2);
65035      }).join('');
65036    }
65037    if (isMultiByte) {
65038      charCodeArray = [currentByte, nextByte];
65039      i++;
65040    } else {
65041      charCodeArray = [currentByte];
65042    } // Use the TextDecoder if one was created for this service
65043
65044    if (service.textDecoder_ && !isExtended) {
65045      char = service.textDecoder_.decode(new Uint8Array(charCodeArray));
65046    } else {
65047      // We assume any multi-byte char without a decoder is unicode.
65048      if (isMultiByte) {
65049        const unicode = toHexString(charCodeArray); // Takes a unicode hex string and creates a single character.
65050
65051        char = String.fromCharCode(parseInt(unicode, 16));
65052      } else {
65053        char = get708CharFromCode(extended | currentByte);
65054      }
65055    }
65056    if (win.pendingNewLine && !win.isEmpty()) {
65057      win.newLine(this.getPts(i));
65058    }
65059    win.pendingNewLine = false;
65060    win.addText(char);
65061    return i;
65062  };
65063  /**
65064   * Handle decoding of multibyte character
65065   *
65066   * @param  {Integer} i        Current index in the 708 packet
65067   * @param  {Service} service  The service object to be affected
65068   * @return {Integer}          New index after parsing
65069   */
65070
65071  Cea708Stream.prototype.multiByteCharacter = function (i, service) {
65072    var packetData = this.current708Packet.data;
65073    var firstByte = packetData[i + 1];
65074    var secondByte = packetData[i + 2];
65075    if (within708TextBlock(firstByte) && within708TextBlock(secondByte)) {
65076      i = this.handleText(++i, service, {
65077        isMultiByte: true
65078      });
65079    }
65080    return i;
65081  };
65082  /**
65083   * Parse and execute the CW# command.
65084   *
65085   * Set the current window.
65086   *
65087   * @param  {Integer} i        Current index in the 708 packet
65088   * @param  {Service} service  The service object to be affected
65089   * @return {Integer}          New index after parsing
65090   */
65091
65092  Cea708Stream.prototype.setCurrentWindow = function (i, service) {
65093    var packetData = this.current708Packet.data;
65094    var b = packetData[i];
65095    var windowNum = b & 0x07;
65096    service.setCurrentWindow(windowNum);
65097    return i;
65098  };
65099  /**
65100   * Parse and execute the DF# command.
65101   *
65102   * Define a window and set it as the current window.
65103   *
65104   * @param  {Integer} i        Current index in the 708 packet
65105   * @param  {Service} service  The service object to be affected
65106   * @return {Integer}          New index after parsing
65107   */
65108
65109  Cea708Stream.prototype.defineWindow = function (i, service) {
65110    var packetData = this.current708Packet.data;
65111    var b = packetData[i];
65112    var windowNum = b & 0x07;
65113    service.setCurrentWindow(windowNum);
65114    var win = service.currentWindow;
65115    b = packetData[++i];
65116    win.visible = (b & 0x20) >> 5; // v
65117
65118    win.rowLock = (b & 0x10) >> 4; // rl
65119
65120    win.columnLock = (b & 0x08) >> 3; // cl
65121
65122    win.priority = b & 0x07; // p
65123
65124    b = packetData[++i];
65125    win.relativePositioning = (b & 0x80) >> 7; // rp
65126
65127    win.anchorVertical = b & 0x7f; // av
65128
65129    b = packetData[++i];
65130    win.anchorHorizontal = b; // ah
65131
65132    b = packetData[++i];
65133    win.anchorPoint = (b & 0xf0) >> 4; // ap
65134
65135    win.rowCount = b & 0x0f; // rc
65136
65137    b = packetData[++i];
65138    win.columnCount = b & 0x3f; // cc
65139
65140    b = packetData[++i];
65141    win.windowStyle = (b & 0x38) >> 3; // ws
65142
65143    win.penStyle = b & 0x07; // ps
65144    // The spec says there are (rowCount+1) "virtual rows"
65145
65146    win.virtualRowCount = win.rowCount + 1;
65147    return i;
65148  };
65149  /**
65150   * Parse and execute the SWA command.
65151   *
65152   * Set attributes of the current window.
65153   *
65154   * @param  {Integer} i        Current index in the 708 packet
65155   * @param  {Service} service  The service object to be affected
65156   * @return {Integer}          New index after parsing
65157   */
65158
65159  Cea708Stream.prototype.setWindowAttributes = function (i, service) {
65160    var packetData = this.current708Packet.data;
65161    var b = packetData[i];
65162    var winAttr = service.currentWindow.winAttr;
65163    b = packetData[++i];
65164    winAttr.fillOpacity = (b & 0xc0) >> 6; // fo
65165
65166    winAttr.fillRed = (b & 0x30) >> 4; // fr
65167
65168    winAttr.fillGreen = (b & 0x0c) >> 2; // fg
65169
65170    winAttr.fillBlue = b & 0x03; // fb
65171
65172    b = packetData[++i];
65173    winAttr.borderType = (b & 0xc0) >> 6; // bt
65174
65175    winAttr.borderRed = (b & 0x30) >> 4; // br
65176
65177    winAttr.borderGreen = (b & 0x0c) >> 2; // bg
65178
65179    winAttr.borderBlue = b & 0x03; // bb
65180
65181    b = packetData[++i];
65182    winAttr.borderType += (b & 0x80) >> 5; // bt
65183
65184    winAttr.wordWrap = (b & 0x40) >> 6; // ww
65185
65186    winAttr.printDirection = (b & 0x30) >> 4; // pd
65187
65188    winAttr.scrollDirection = (b & 0x0c) >> 2; // sd
65189
65190    winAttr.justify = b & 0x03; // j
65191
65192    b = packetData[++i];
65193    winAttr.effectSpeed = (b & 0xf0) >> 4; // es
65194
65195    winAttr.effectDirection = (b & 0x0c) >> 2; // ed
65196
65197    winAttr.displayEffect = b & 0x03; // de
65198
65199    return i;
65200  };
65201  /**
65202   * Gather text from all displayed windows and push a caption to output.
65203   *
65204   * @param  {Integer} i        Current index in the 708 packet
65205   * @param  {Service} service  The service object to be affected
65206   */
65207
65208  Cea708Stream.prototype.flushDisplayed = function (pts, service) {
65209    var displayedText = []; // TODO: Positioning not supported, displaying multiple windows will not necessarily
65210    // display text in the correct order, but sample files so far have not shown any issue.
65211
65212    for (var winId = 0; winId < 8; winId++) {
65213      if (service.windows[winId].visible && !service.windows[winId].isEmpty()) {
65214        displayedText.push(service.windows[winId].getText());
65215      }
65216    }
65217    service.endPts = pts;
65218    service.text = displayedText.join('\n\n');
65219    this.pushCaption(service);
65220    service.startPts = pts;
65221  };
65222  /**
65223   * Push a caption to output if the caption contains text.
65224   *
65225   * @param  {Service} service  The service object to be affected
65226   */
65227
65228  Cea708Stream.prototype.pushCaption = function (service) {
65229    if (service.text !== '') {
65230      this.trigger('data', {
65231        startPts: service.startPts,
65232        endPts: service.endPts,
65233        text: service.text,
65234        stream: 'cc708_' + service.serviceNum
65235      });
65236      service.text = '';
65237      service.startPts = service.endPts;
65238    }
65239  };
65240  /**
65241   * Parse and execute the DSW command.
65242   *
65243   * Set visible property of windows based on the parsed bitmask.
65244   *
65245   * @param  {Integer} i        Current index in the 708 packet
65246   * @param  {Service} service  The service object to be affected
65247   * @return {Integer}          New index after parsing
65248   */
65249
65250  Cea708Stream.prototype.displayWindows = function (i, service) {
65251    var packetData = this.current708Packet.data;
65252    var b = packetData[++i];
65253    var pts = this.getPts(i);
65254    this.flushDisplayed(pts, service);
65255    for (var winId = 0; winId < 8; winId++) {
65256      if (b & 0x01 << winId) {
65257        service.windows[winId].visible = 1;
65258      }
65259    }
65260    return i;
65261  };
65262  /**
65263   * Parse and execute the HDW command.
65264   *
65265   * Set visible property of windows based on the parsed bitmask.
65266   *
65267   * @param  {Integer} i        Current index in the 708 packet
65268   * @param  {Service} service  The service object to be affected
65269   * @return {Integer}          New index after parsing
65270   */
65271
65272  Cea708Stream.prototype.hideWindows = function (i, service) {
65273    var packetData = this.current708Packet.data;
65274    var b = packetData[++i];
65275    var pts = this.getPts(i);
65276    this.flushDisplayed(pts, service);
65277    for (var winId = 0; winId < 8; winId++) {
65278      if (b & 0x01 << winId) {
65279        service.windows[winId].visible = 0;
65280      }
65281    }
65282    return i;
65283  };
65284  /**
65285   * Parse and execute the TGW command.
65286   *
65287   * Set visible property of windows based on the parsed bitmask.
65288   *
65289   * @param  {Integer} i        Current index in the 708 packet
65290   * @param  {Service} service  The service object to be affected
65291   * @return {Integer}          New index after parsing
65292   */
65293
65294  Cea708Stream.prototype.toggleWindows = function (i, service) {
65295    var packetData = this.current708Packet.data;
65296    var b = packetData[++i];
65297    var pts = this.getPts(i);
65298    this.flushDisplayed(pts, service);
65299    for (var winId = 0; winId < 8; winId++) {
65300      if (b & 0x01 << winId) {
65301        service.windows[winId].visible ^= 1;
65302      }
65303    }
65304    return i;
65305  };
65306  /**
65307   * Parse and execute the CLW command.
65308   *
65309   * Clear text of windows based on the parsed bitmask.
65310   *
65311   * @param  {Integer} i        Current index in the 708 packet
65312   * @param  {Service} service  The service object to be affected
65313   * @return {Integer}          New index after parsing
65314   */
65315
65316  Cea708Stream.prototype.clearWindows = function (i, service) {
65317    var packetData = this.current708Packet.data;
65318    var b = packetData[++i];
65319    var pts = this.getPts(i);
65320    this.flushDisplayed(pts, service);
65321    for (var winId = 0; winId < 8; winId++) {
65322      if (b & 0x01 << winId) {
65323        service.windows[winId].clearText();
65324      }
65325    }
65326    return i;
65327  };
65328  /**
65329   * Parse and execute the DLW command.
65330   *
65331   * Re-initialize windows based on the parsed bitmask.
65332   *
65333   * @param  {Integer} i        Current index in the 708 packet
65334   * @param  {Service} service  The service object to be affected
65335   * @return {Integer}          New index after parsing
65336   */
65337
65338  Cea708Stream.prototype.deleteWindows = function (i, service) {
65339    var packetData = this.current708Packet.data;
65340    var b = packetData[++i];
65341    var pts = this.getPts(i);
65342    this.flushDisplayed(pts, service);
65343    for (var winId = 0; winId < 8; winId++) {
65344      if (b & 0x01 << winId) {
65345        service.windows[winId].reset();
65346      }
65347    }
65348    return i;
65349  };
65350  /**
65351   * Parse and execute the SPA command.
65352   *
65353   * Set pen attributes of the current window.
65354   *
65355   * @param  {Integer} i        Current index in the 708 packet
65356   * @param  {Service} service  The service object to be affected
65357   * @return {Integer}          New index after parsing
65358   */
65359
65360  Cea708Stream.prototype.setPenAttributes = function (i, service) {
65361    var packetData = this.current708Packet.data;
65362    var b = packetData[i];
65363    var penAttr = service.currentWindow.penAttr;
65364    b = packetData[++i];
65365    penAttr.textTag = (b & 0xf0) >> 4; // tt
65366
65367    penAttr.offset = (b & 0x0c) >> 2; // o
65368
65369    penAttr.penSize = b & 0x03; // s
65370
65371    b = packetData[++i];
65372    penAttr.italics = (b & 0x80) >> 7; // i
65373
65374    penAttr.underline = (b & 0x40) >> 6; // u
65375
65376    penAttr.edgeType = (b & 0x38) >> 3; // et
65377
65378    penAttr.fontStyle = b & 0x07; // fs
65379
65380    return i;
65381  };
65382  /**
65383   * Parse and execute the SPC command.
65384   *
65385   * Set pen color of the current window.
65386   *
65387   * @param  {Integer} i        Current index in the 708 packet
65388   * @param  {Service} service  The service object to be affected
65389   * @return {Integer}          New index after parsing
65390   */
65391
65392  Cea708Stream.prototype.setPenColor = function (i, service) {
65393    var packetData = this.current708Packet.data;
65394    var b = packetData[i];
65395    var penColor = service.currentWindow.penColor;
65396    b = packetData[++i];
65397    penColor.fgOpacity = (b & 0xc0) >> 6; // fo
65398
65399    penColor.fgRed = (b & 0x30) >> 4; // fr
65400
65401    penColor.fgGreen = (b & 0x0c) >> 2; // fg
65402
65403    penColor.fgBlue = b & 0x03; // fb
65404
65405    b = packetData[++i];
65406    penColor.bgOpacity = (b & 0xc0) >> 6; // bo
65407
65408    penColor.bgRed = (b & 0x30) >> 4; // br
65409
65410    penColor.bgGreen = (b & 0x0c) >> 2; // bg
65411
65412    penColor.bgBlue = b & 0x03; // bb
65413
65414    b = packetData[++i];
65415    penColor.edgeRed = (b & 0x30) >> 4; // er
65416
65417    penColor.edgeGreen = (b & 0x0c) >> 2; // eg
65418
65419    penColor.edgeBlue = b & 0x03; // eb
65420
65421    return i;
65422  };
65423  /**
65424   * Parse and execute the SPL command.
65425   *
65426   * Set pen location of the current window.
65427   *
65428   * @param  {Integer} i        Current index in the 708 packet
65429   * @param  {Service} service  The service object to be affected
65430   * @return {Integer}          New index after parsing
65431   */
65432
65433  Cea708Stream.prototype.setPenLocation = function (i, service) {
65434    var packetData = this.current708Packet.data;
65435    var b = packetData[i];
65436    var penLoc = service.currentWindow.penLoc; // Positioning isn't really supported at the moment, so this essentially just inserts a linebreak
65437
65438    service.currentWindow.pendingNewLine = true;
65439    b = packetData[++i];
65440    penLoc.row = b & 0x0f; // r
65441
65442    b = packetData[++i];
65443    penLoc.column = b & 0x3f; // c
65444
65445    return i;
65446  };
65447  /**
65448   * Execute the RST command.
65449   *
65450   * Reset service to a clean slate. Re-initialize.
65451   *
65452   * @param  {Integer} i        Current index in the 708 packet
65453   * @param  {Service} service  The service object to be affected
65454   * @return {Service}          Re-initialized service
65455   */
65456
65457  Cea708Stream.prototype.reset = function (i, service) {
65458    var pts = this.getPts(i);
65459    this.flushDisplayed(pts, service);
65460    return this.initService(service.serviceNum, i);
65461  }; // This hash maps non-ASCII, special, and extended character codes to their
65462  // proper Unicode equivalent. The first keys that are only a single byte
65463  // are the non-standard ASCII characters, which simply map the CEA608 byte
65464  // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
65465  // character codes, but have their MSB bitmasked with 0x03 so that a lookup
65466  // can be performed regardless of the field and data channel on which the
65467  // character code was received.
65468
65469  var CHARACTER_TRANSLATION = {
65470    0x2a: 0xe1,
65471    // á
65472    0x5c: 0xe9,
65473    // é
65474    0x5e: 0xed,
65475    // í
65476    0x5f: 0xf3,
65477    // ó
65478    0x60: 0xfa,
65479    // ú
65480    0x7b: 0xe7,
65481    // ç
65482    0x7c: 0xf7,
65483    // ÷
65484    0x7d: 0xd1,
65485    // Ñ
65486    0x7e: 0xf1,
65487    // ñ
65488    0x7f: 0x2588,
65489    // █
65490    0x0130: 0xae,
65491    // ®
65492    0x0131: 0xb0,
65493    // °
65494    0x0132: 0xbd,
65495    // ½
65496    0x0133: 0xbf,
65497    // ¿
65498    0x0134: 0x2122,
65499    // ™
65500    0x0135: 0xa2,
65501    // ¢
65502    0x0136: 0xa3,
65503    // £
65504    0x0137: 0x266a,
65505    // ♪
65506    0x0138: 0xe0,
65507    // à
65508    0x0139: 0xa0,
65509    //
65510    0x013a: 0xe8,
65511    // è
65512    0x013b: 0xe2,
65513    // â
65514    0x013c: 0xea,
65515    // ê
65516    0x013d: 0xee,
65517    // î
65518    0x013e: 0xf4,
65519    // ô
65520    0x013f: 0xfb,
65521    // û
65522    0x0220: 0xc1,
65523    // Á
65524    0x0221: 0xc9,
65525    // É
65526    0x0222: 0xd3,
65527    // Ó
65528    0x0223: 0xda,
65529    // Ú
65530    0x0224: 0xdc,
65531    // Ü
65532    0x0225: 0xfc,
65533    // ü
65534    0x0226: 0x2018,
65535    // ‘
65536    0x0227: 0xa1,
65537    // ¡
65538    0x0228: 0x2a,
65539    // *
65540    0x0229: 0x27,
65541    // '
65542    0x022a: 0x2014,
65543    // —
65544    0x022b: 0xa9,
65545    // ©
65546    0x022c: 0x2120,
65547    // ℠
65548    0x022d: 0x2022,
65549    // •
65550    0x022e: 0x201c,
65551    // “
65552    0x022f: 0x201d,
65553    // ”
65554    0x0230: 0xc0,
65555    // À
65556    0x0231: 0xc2,
65557    // Â
65558    0x0232: 0xc7,
65559    // Ç
65560    0x0233: 0xc8,
65561    // È
65562    0x0234: 0xca,
65563    // Ê
65564    0x0235: 0xcb,
65565    // Ë
65566    0x0236: 0xeb,
65567    // ë
65568    0x0237: 0xce,
65569    // Î
65570    0x0238: 0xcf,
65571    // Ï
65572    0x0239: 0xef,
65573    // ï
65574    0x023a: 0xd4,
65575    // Ô
65576    0x023b: 0xd9,
65577    // Ù
65578    0x023c: 0xf9,
65579    // ù
65580    0x023d: 0xdb,
65581    // Û
65582    0x023e: 0xab,
65583    // «
65584    0x023f: 0xbb,
65585    // »
65586    0x0320: 0xc3,
65587    // Ã
65588    0x0321: 0xe3,
65589    // ã
65590    0x0322: 0xcd,
65591    // Í
65592    0x0323: 0xcc,
65593    // Ì
65594    0x0324: 0xec,
65595    // ì
65596    0x0325: 0xd2,
65597    // Ò
65598    0x0326: 0xf2,
65599    // ò
65600    0x0327: 0xd5,
65601    // Õ
65602    0x0328: 0xf5,
65603    // õ
65604    0x0329: 0x7b,
65605    // {
65606    0x032a: 0x7d,
65607    // }
65608    0x032b: 0x5c,
65609    // \
65610    0x032c: 0x5e,
65611    // ^
65612    0x032d: 0x5f,
65613    // _
65614    0x032e: 0x7c,
65615    // |
65616    0x032f: 0x7e,
65617    // ~
65618    0x0330: 0xc4,
65619    // Ä
65620    0x0331: 0xe4,
65621    // ä
65622    0x0332: 0xd6,
65623    // Ö
65624    0x0333: 0xf6,
65625    // ö
65626    0x0334: 0xdf,
65627    // ß
65628    0x0335: 0xa5,
65629    // ¥
65630    0x0336: 0xa4,
65631    // ¤
65632    0x0337: 0x2502,
65633    // │
65634    0x0338: 0xc5,
65635    // Å
65636    0x0339: 0xe5,
65637    // å
65638    0x033a: 0xd8,
65639    // Ø
65640    0x033b: 0xf8,
65641    // ø
65642    0x033c: 0x250c,
65643    // ┌
65644    0x033d: 0x2510,
65645    // ┐
65646    0x033e: 0x2514,
65647    // └
65648    0x033f: 0x2518 // ┘
65649  };
65650  var getCharFromCode = function (code) {
65651    if (code === null) {
65652      return '';
65653    }
65654    code = CHARACTER_TRANSLATION[code] || code;
65655    return String.fromCharCode(code);
65656  }; // the index of the last row in a CEA-608 display buffer
65657
65658  var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of
65659  // getting it through bit logic.
65660
65661  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
65662  // cells. The "bottom" row is the last element in the outer array.
65663  // We keep track of positioning information as we go by storing the
65664  // number of indentations and the tab offset in this buffer.
65665
65666  var createDisplayBuffer = function () {
65667    var result = [],
65668      i = BOTTOM_ROW + 1;
65669    while (i--) {
65670      result.push({
65671        text: '',
65672        indent: 0,
65673        offset: 0
65674      });
65675    }
65676    return result;
65677  };
65678  var Cea608Stream = function (field, dataChannel) {
65679    Cea608Stream.prototype.init.call(this);
65680    this.field_ = field || 0;
65681    this.dataChannel_ = dataChannel || 0;
65682    this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
65683    this.setConstants();
65684    this.reset();
65685    this.push = function (packet) {
65686      var data, swap, char0, char1, text; // remove the parity bits
65687
65688      data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice
65689
65690      if (data === this.lastControlCode_) {
65691        this.lastControlCode_ = null;
65692        return;
65693      } // Store control codes
65694
65695      if ((data & 0xf000) === 0x1000) {
65696        this.lastControlCode_ = data;
65697      } else if (data !== this.PADDING_) {
65698        this.lastControlCode_ = null;
65699      }
65700      char0 = data >>> 8;
65701      char1 = data & 0xff;
65702      if (data === this.PADDING_) {
65703        return;
65704      } else if (data === this.RESUME_CAPTION_LOADING_) {
65705        this.mode_ = 'popOn';
65706      } else if (data === this.END_OF_CAPTION_) {
65707        // If an EOC is received while in paint-on mode, the displayed caption
65708        // text should be swapped to non-displayed memory as if it was a pop-on
65709        // caption. Because of that, we should explicitly switch back to pop-on
65710        // mode
65711        this.mode_ = 'popOn';
65712        this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now
65713
65714        this.flushDisplayed(packet.pts); // flip memory
65715
65716        swap = this.displayed_;
65717        this.displayed_ = this.nonDisplayed_;
65718        this.nonDisplayed_ = swap; // start measuring the time to display the caption
65719
65720        this.startPts_ = packet.pts;
65721      } else if (data === this.ROLL_UP_2_ROWS_) {
65722        this.rollUpRows_ = 2;
65723        this.setRollUp(packet.pts);
65724      } else if (data === this.ROLL_UP_3_ROWS_) {
65725        this.rollUpRows_ = 3;
65726        this.setRollUp(packet.pts);
65727      } else if (data === this.ROLL_UP_4_ROWS_) {
65728        this.rollUpRows_ = 4;
65729        this.setRollUp(packet.pts);
65730      } else if (data === this.CARRIAGE_RETURN_) {
65731        this.clearFormatting(packet.pts);
65732        this.flushDisplayed(packet.pts);
65733        this.shiftRowsUp_();
65734        this.startPts_ = packet.pts;
65735      } else if (data === this.BACKSPACE_) {
65736        if (this.mode_ === 'popOn') {
65737          this.nonDisplayed_[this.row_].text = this.nonDisplayed_[this.row_].text.slice(0, -1);
65738        } else {
65739          this.displayed_[this.row_].text = this.displayed_[this.row_].text.slice(0, -1);
65740        }
65741      } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
65742        this.flushDisplayed(packet.pts);
65743        this.displayed_ = createDisplayBuffer();
65744      } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
65745        this.nonDisplayed_ = createDisplayBuffer();
65746      } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
65747        if (this.mode_ !== 'paintOn') {
65748          // NOTE: This should be removed when proper caption positioning is
65749          // implemented
65750          this.flushDisplayed(packet.pts);
65751          this.displayed_ = createDisplayBuffer();
65752        }
65753        this.mode_ = 'paintOn';
65754        this.startPts_ = packet.pts; // Append special characters to caption text
65755      } else if (this.isSpecialCharacter(char0, char1)) {
65756        // Bitmask char0 so that we can apply character transformations
65757        // regardless of field and data channel.
65758        // Then byte-shift to the left and OR with char1 so we can pass the
65759        // entire character code to `getCharFromCode`.
65760        char0 = (char0 & 0x03) << 8;
65761        text = getCharFromCode(char0 | char1);
65762        this[this.mode_](packet.pts, text);
65763        this.column_++; // Append extended characters to caption text
65764      } else if (this.isExtCharacter(char0, char1)) {
65765        // Extended characters always follow their "non-extended" equivalents.
65766        // IE if a "è" is desired, you'll always receive "eè"; non-compliant
65767        // decoders are supposed to drop the "è", while compliant decoders
65768        // backspace the "e" and insert "è".
65769        // Delete the previous character
65770        if (this.mode_ === 'popOn') {
65771          this.nonDisplayed_[this.row_].text = this.nonDisplayed_[this.row_].text.slice(0, -1);
65772        } else {
65773          this.displayed_[this.row_].text = this.displayed_[this.row_].text.slice(0, -1);
65774        } // Bitmask char0 so that we can apply character transformations
65775        // regardless of field and data channel.
65776        // Then byte-shift to the left and OR with char1 so we can pass the
65777        // entire character code to `getCharFromCode`.
65778
65779        char0 = (char0 & 0x03) << 8;
65780        text = getCharFromCode(char0 | char1);
65781        this[this.mode_](packet.pts, text);
65782        this.column_++; // Process mid-row codes
65783      } else if (this.isMidRowCode(char0, char1)) {
65784        // Attributes are not additive, so clear all formatting
65785        this.clearFormatting(packet.pts); // According to the standard, mid-row codes
65786        // should be replaced with spaces, so add one now
65787
65788        this[this.mode_](packet.pts, ' ');
65789        this.column_++;
65790        if ((char1 & 0xe) === 0xe) {
65791          this.addFormatting(packet.pts, ['i']);
65792        }
65793        if ((char1 & 0x1) === 0x1) {
65794          this.addFormatting(packet.pts, ['u']);
65795        } // Detect offset control codes and adjust cursor
65796      } else if (this.isOffsetControlCode(char0, char1)) {
65797        // Cursor position is set by indent PAC (see below) in 4-column
65798        // increments, with an additional offset code of 1-3 to reach any
65799        // of the 32 columns specified by CEA-608. So all we need to do
65800        // here is increment the column cursor by the given offset.
65801        const offset = char1 & 0x03; // For an offest value 1-3, set the offset for that caption
65802        // in the non-displayed array.
65803
65804        this.nonDisplayed_[this.row_].offset = offset;
65805        this.column_ += offset; // Detect PACs (Preamble Address Codes)
65806      } else if (this.isPAC(char0, char1)) {
65807        // There's no logic for PAC -> row mapping, so we have to just
65808        // find the row code in an array and use its index :(
65809        var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode
65810
65811        if (this.mode_ === 'rollUp') {
65812          // This implies that the base row is incorrectly set.
65813          // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
65814          // of roll-up rows set.
65815          if (row - this.rollUpRows_ + 1 < 0) {
65816            row = this.rollUpRows_ - 1;
65817          }
65818          this.setRollUp(packet.pts, row);
65819        } // Ensure the row is between 0 and 14, otherwise use the most
65820        // recent or default row.
65821
65822        if (row !== this.row_ && row >= 0 && row <= 14) {
65823          // formatting is only persistent for current row
65824          this.clearFormatting(packet.pts);
65825          this.row_ = row;
65826        } // All PACs can apply underline, so detect and apply
65827        // (All odd-numbered second bytes set underline)
65828
65829        if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
65830          this.addFormatting(packet.pts, ['u']);
65831        }
65832        if ((data & 0x10) === 0x10) {
65833          // We've got an indent level code. Each successive even number
65834          // increments the column cursor by 4, so we can get the desired
65835          // column position by bit-shifting to the right (to get n/2)
65836          // and multiplying by 4.
65837          const indentations = (data & 0xe) >> 1;
65838          this.column_ = indentations * 4; // add to the number of indentations for positioning
65839
65840          this.nonDisplayed_[this.row_].indent += indentations;
65841        }
65842        if (this.isColorPAC(char1)) {
65843          // it's a color code, though we only support white, which
65844          // can be either normal or italicized. white italics can be
65845          // either 0x4e or 0x6e depending on the row, so we just
65846          // bitwise-and with 0xe to see if italics should be turned on
65847          if ((char1 & 0xe) === 0xe) {
65848            this.addFormatting(packet.pts, ['i']);
65849          }
65850        } // We have a normal character in char0, and possibly one in char1
65851      } else if (this.isNormalChar(char0)) {
65852        if (char1 === 0x00) {
65853          char1 = null;
65854        }
65855        text = getCharFromCode(char0);
65856        text += getCharFromCode(char1);
65857        this[this.mode_](packet.pts, text);
65858        this.column_ += text.length;
65859      } // finish data processing
65860    };
65861  };
65862  Cea608Stream.prototype = new Stream$7(); // Trigger a cue point that captures the current state of the
65863  // display buffer
65864
65865  Cea608Stream.prototype.flushDisplayed = function (pts) {
65866    const logWarning = index => {
65867      this.trigger('log', {
65868        level: 'warn',
65869        message: 'Skipping a malformed 608 caption at index ' + index + '.'
65870      });
65871    };
65872    const content = [];
65873    this.displayed_.forEach((row, i) => {
65874      if (row && row.text && row.text.length) {
65875        try {
65876          // remove spaces from the start and end of the string
65877          row.text = row.text.trim();
65878        } catch (e) {
65879          // Ordinarily, this shouldn't happen. However, caption
65880          // parsing errors should not throw exceptions and
65881          // break playback.
65882          logWarning(i);
65883        } // See the below link for more details on the following fields:
65884        // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-608
65885
65886        if (row.text.length) {
65887          content.push({
65888            // The text to be displayed in the caption from this specific row, with whitespace removed.
65889            text: row.text,
65890            // Value between 1 and 15 representing the PAC row used to calculate line height.
65891            line: i + 1,
65892            // A number representing the indent position by percentage (CEA-608 PAC indent code).
65893            // The value will be a number between 10 and 80. Offset is used to add an aditional
65894            // value to the position if necessary.
65895            position: 10 + Math.min(70, row.indent * 10) + row.offset * 2.5
65896          });
65897        }
65898      } else if (row === undefined || row === null) {
65899        logWarning(i);
65900      }
65901    });
65902    if (content.length) {
65903      this.trigger('data', {
65904        startPts: this.startPts_,
65905        endPts: pts,
65906        content,
65907        stream: this.name_
65908      });
65909    }
65910  };
65911  /**
65912   * Zero out the data, used for startup and on seek
65913   */
65914
65915  Cea608Stream.prototype.reset = function () {
65916    this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will
65917    // actually display captions. If a caption is shifted to a row
65918    // with a lower index than this, it is cleared from the display
65919    // buffer
65920
65921    this.topRow_ = 0;
65922    this.startPts_ = 0;
65923    this.displayed_ = createDisplayBuffer();
65924    this.nonDisplayed_ = createDisplayBuffer();
65925    this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing
65926
65927    this.column_ = 0;
65928    this.row_ = BOTTOM_ROW;
65929    this.rollUpRows_ = 2; // This variable holds currently-applied formatting
65930
65931    this.formatting_ = [];
65932  };
65933  /**
65934   * Sets up control code and related constants for this instance
65935   */
65936
65937  Cea608Stream.prototype.setConstants = function () {
65938    // The following attributes have these uses:
65939    // ext_ :    char0 for mid-row codes, and the base for extended
65940    //           chars (ext_+0, ext_+1, and ext_+2 are char0s for
65941    //           extended codes)
65942    // control_: char0 for control codes, except byte-shifted to the
65943    //           left so that we can do this.control_ | CONTROL_CODE
65944    // offset_:  char0 for tab offset codes
65945    //
65946    // It's also worth noting that control codes, and _only_ control codes,
65947    // differ between field 1 and field2. Field 2 control codes are always
65948    // their field 1 value plus 1. That's why there's the "| field" on the
65949    // control value.
65950    if (this.dataChannel_ === 0) {
65951      this.BASE_ = 0x10;
65952      this.EXT_ = 0x11;
65953      this.CONTROL_ = (0x14 | this.field_) << 8;
65954      this.OFFSET_ = 0x17;
65955    } else if (this.dataChannel_ === 1) {
65956      this.BASE_ = 0x18;
65957      this.EXT_ = 0x19;
65958      this.CONTROL_ = (0x1c | this.field_) << 8;
65959      this.OFFSET_ = 0x1f;
65960    } // Constants for the LSByte command codes recognized by Cea608Stream. This
65961    // list is not exhaustive. For a more comprehensive listing and semantics see
65962    // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
65963    // Padding
65964
65965    this.PADDING_ = 0x0000; // Pop-on Mode
65966
65967    this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
65968    this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode
65969
65970    this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
65971    this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
65972    this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
65973    this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode
65974
65975    this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure
65976
65977    this.BACKSPACE_ = this.CONTROL_ | 0x21;
65978    this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
65979    this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
65980  };
65981  /**
65982   * Detects if the 2-byte packet data is a special character
65983   *
65984   * Special characters have a second byte in the range 0x30 to 0x3f,
65985   * with the first byte being 0x11 (for data channel 1) or 0x19 (for
65986   * data channel 2).
65987   *
65988   * @param  {Integer} char0 The first byte
65989   * @param  {Integer} char1 The second byte
65990   * @return {Boolean}       Whether the 2 bytes are an special character
65991   */
65992
65993  Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
65994    return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
65995  };
65996  /**
65997   * Detects if the 2-byte packet data is an extended character
65998   *
65999   * Extended characters have a second byte in the range 0x20 to 0x3f,
66000   * with the first byte being 0x12 or 0x13 (for data channel 1) or
66001   * 0x1a or 0x1b (for data channel 2).
66002   *
66003   * @param  {Integer} char0 The first byte
66004   * @param  {Integer} char1 The second byte
66005   * @return {Boolean}       Whether the 2 bytes are an extended character
66006   */
66007
66008  Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
66009    return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
66010  };
66011  /**
66012   * Detects if the 2-byte packet is a mid-row code
66013   *
66014   * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
66015   * the first byte being 0x11 (for data channel 1) or 0x19 (for data
66016   * channel 2).
66017   *
66018   * @param  {Integer} char0 The first byte
66019   * @param  {Integer} char1 The second byte
66020   * @return {Boolean}       Whether the 2 bytes are a mid-row code
66021   */
66022
66023  Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
66024    return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
66025  };
66026  /**
66027   * Detects if the 2-byte packet is an offset control code
66028   *
66029   * Offset control codes have a second byte in the range 0x21 to 0x23,
66030   * with the first byte being 0x17 (for data channel 1) or 0x1f (for
66031   * data channel 2).
66032   *
66033   * @param  {Integer} char0 The first byte
66034   * @param  {Integer} char1 The second byte
66035   * @return {Boolean}       Whether the 2 bytes are an offset control code
66036   */
66037
66038  Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
66039    return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
66040  };
66041  /**
66042   * Detects if the 2-byte packet is a Preamble Address Code
66043   *
66044   * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
66045   * or 0x18 to 0x1f (for data channel 2), with the second byte in the
66046   * range 0x40 to 0x7f.
66047   *
66048   * @param  {Integer} char0 The first byte
66049   * @param  {Integer} char1 The second byte
66050   * @return {Boolean}       Whether the 2 bytes are a PAC
66051   */
66052
66053  Cea608Stream.prototype.isPAC = function (char0, char1) {
66054    return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
66055  };
66056  /**
66057   * Detects if a packet's second byte is in the range of a PAC color code
66058   *
66059   * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
66060   * 0x60 to 0x6f.
66061   *
66062   * @param  {Integer} char1 The second byte
66063   * @return {Boolean}       Whether the byte is a color PAC
66064   */
66065
66066  Cea608Stream.prototype.isColorPAC = function (char1) {
66067    return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
66068  };
66069  /**
66070   * Detects if a single byte is in the range of a normal character
66071   *
66072   * Normal text bytes are in the range 0x20 to 0x7f.
66073   *
66074   * @param  {Integer} char  The byte
66075   * @return {Boolean}       Whether the byte is a normal character
66076   */
66077
66078  Cea608Stream.prototype.isNormalChar = function (char) {
66079    return char >= 0x20 && char <= 0x7f;
66080  };
66081  /**
66082   * Configures roll-up
66083   *
66084   * @param  {Integer} pts         Current PTS
66085   * @param  {Integer} newBaseRow  Used by PACs to slide the current window to
66086   *                               a new position
66087   */
66088
66089  Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) {
66090    // Reset the base row to the bottom row when switching modes
66091    if (this.mode_ !== 'rollUp') {
66092      this.row_ = BOTTOM_ROW;
66093      this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up
66094
66095      this.flushDisplayed(pts);
66096      this.nonDisplayed_ = createDisplayBuffer();
66097      this.displayed_ = createDisplayBuffer();
66098    }
66099    if (newBaseRow !== undefined && newBaseRow !== this.row_) {
66100      // move currently displayed captions (up or down) to the new base row
66101      for (var i = 0; i < this.rollUpRows_; i++) {
66102        this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
66103        this.displayed_[this.row_ - i] = {
66104          text: '',
66105          indent: 0,
66106          offset: 0
66107        };
66108      }
66109    }
66110    if (newBaseRow === undefined) {
66111      newBaseRow = this.row_;
66112    }
66113    this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
66114  }; // Adds the opening HTML tag for the passed character to the caption text,
66115  // and keeps track of it for later closing
66116
66117  Cea608Stream.prototype.addFormatting = function (pts, format) {
66118    this.formatting_ = this.formatting_.concat(format);
66119    var text = format.reduce(function (text, format) {
66120      return text + '<' + format + '>';
66121    }, '');
66122    this[this.mode_](pts, text);
66123  }; // Adds HTML closing tags for current formatting to caption text and
66124  // clears remembered formatting
66125
66126  Cea608Stream.prototype.clearFormatting = function (pts) {
66127    if (!this.formatting_.length) {
66128      return;
66129    }
66130    var text = this.formatting_.reverse().reduce(function (text, format) {
66131      return text + '</' + format + '>';
66132    }, '');
66133    this.formatting_ = [];
66134    this[this.mode_](pts, text);
66135  }; // Mode Implementations
66136
66137  Cea608Stream.prototype.popOn = function (pts, text) {
66138    var baseRow = this.nonDisplayed_[this.row_].text; // buffer characters
66139
66140    baseRow += text;
66141    this.nonDisplayed_[this.row_].text = baseRow;
66142  };
66143  Cea608Stream.prototype.rollUp = function (pts, text) {
66144    var baseRow = this.displayed_[this.row_].text;
66145    baseRow += text;
66146    this.displayed_[this.row_].text = baseRow;
66147  };
66148  Cea608Stream.prototype.shiftRowsUp_ = function () {
66149    var i; // clear out inactive rows
66150
66151    for (i = 0; i < this.topRow_; i++) {
66152      this.displayed_[i] = {
66153        text: '',
66154        indent: 0,
66155        offset: 0
66156      };
66157    }
66158    for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
66159      this.displayed_[i] = {
66160        text: '',
66161        indent: 0,
66162        offset: 0
66163      };
66164    } // shift displayed rows up
66165
66166    for (i = this.topRow_; i < this.row_; i++) {
66167      this.displayed_[i] = this.displayed_[i + 1];
66168    } // clear out the bottom row
66169
66170    this.displayed_[this.row_] = {
66171      text: '',
66172      indent: 0,
66173      offset: 0
66174    };
66175  };
66176  Cea608Stream.prototype.paintOn = function (pts, text) {
66177    var baseRow = this.displayed_[this.row_].text;
66178    baseRow += text;
66179    this.displayed_[this.row_].text = baseRow;
66180  }; // exports
66181
66182  var captionStream = {
66183    CaptionStream: CaptionStream$2,
66184    Cea608Stream: Cea608Stream,
66185    Cea708Stream: Cea708Stream
66186  };
66187  /**
66188   * mux.js
66189   *
66190   * Copyright (c) Brightcove
66191   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
66192   */
66193
66194  var streamTypes = {
66195    H264_STREAM_TYPE: 0x1B,
66196    ADTS_STREAM_TYPE: 0x0F,
66197    METADATA_STREAM_TYPE: 0x15
66198  };
66199  /**
66200   * mux.js
66201   *
66202   * Copyright (c) Brightcove
66203   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
66204   *
66205   * Accepts program elementary stream (PES) data events and corrects
66206   * decode and presentation time stamps to account for a rollover
66207   * of the 33 bit value.
66208   */
66209
66210  var Stream$6 = stream;
66211  var MAX_TS = 8589934592;
66212  var RO_THRESH = 4294967296;
66213  var TYPE_SHARED = 'shared';
66214  var handleRollover$1 = function (value, reference) {
66215    var direction = 1;
66216    if (value > reference) {
66217      // If the current timestamp value is greater than our reference timestamp and we detect a
66218      // timestamp rollover, this means the roll over is happening in the opposite direction.
66219      // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
66220      // point will be set to a small number, e.g. 1. The user then seeks backwards over the
66221      // rollover point. In loading this segment, the timestamp values will be very large,
66222      // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
66223      // the time stamp to be `value - 2^33`.
66224      direction = -1;
66225    } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
66226    // cause an incorrect adjustment.
66227
66228    while (Math.abs(reference - value) > RO_THRESH) {
66229      value += direction * MAX_TS;
66230    }
66231    return value;
66232  };
66233  var TimestampRolloverStream$1 = function (type) {
66234    var lastDTS, referenceDTS;
66235    TimestampRolloverStream$1.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed
66236    // video and audio. We could use `undefined` here, but having a string
66237    // makes debugging a little clearer.
66238
66239    this.type_ = type || TYPE_SHARED;
66240    this.push = function (data) {
66241      /**
66242       * Rollover stream expects data from elementary stream.
66243       * Elementary stream can push forward 2 types of data
66244       * - Parsed Video/Audio/Timed-metadata PES (packetized elementary stream) packets
66245       * - Tracks metadata from PMT (Program Map Table)
66246       * Rollover stream expects pts/dts info to be available, since it stores lastDTS
66247       * We should ignore non-PES packets since they may override lastDTS to undefined.
66248       * lastDTS is important to signal the next segments
66249       * about rollover from the previous segments.
66250       */
66251      if (data.type === 'metadata') {
66252        this.trigger('data', data);
66253        return;
66254      } // Any "shared" rollover streams will accept _all_ data. Otherwise,
66255      // streams will only accept data that matches their type.
66256
66257      if (this.type_ !== TYPE_SHARED && data.type !== this.type_) {
66258        return;
66259      }
66260      if (referenceDTS === undefined) {
66261        referenceDTS = data.dts;
66262      }
66263      data.dts = handleRollover$1(data.dts, referenceDTS);
66264      data.pts = handleRollover$1(data.pts, referenceDTS);
66265      lastDTS = data.dts;
66266      this.trigger('data', data);
66267    };
66268    this.flush = function () {
66269      referenceDTS = lastDTS;
66270      this.trigger('done');
66271    };
66272    this.endTimeline = function () {
66273      this.flush();
66274      this.trigger('endedtimeline');
66275    };
66276    this.discontinuity = function () {
66277      referenceDTS = void 0;
66278      lastDTS = void 0;
66279    };
66280    this.reset = function () {
66281      this.discontinuity();
66282      this.trigger('reset');
66283    };
66284  };
66285  TimestampRolloverStream$1.prototype = new Stream$6();
66286  var timestampRolloverStream = {
66287    TimestampRolloverStream: TimestampRolloverStream$1,
66288    handleRollover: handleRollover$1
66289  }; // Once IE11 support is dropped, this function should be removed.
66290
66291  var typedArrayIndexOf$1 = (typedArray, element, fromIndex) => {
66292    if (!typedArray) {
66293      return -1;
66294    }
66295    var currentIndex = fromIndex;
66296    for (; currentIndex < typedArray.length; currentIndex++) {
66297      if (typedArray[currentIndex] === element) {
66298        return currentIndex;
66299      }
66300    }
66301    return -1;
66302  };
66303  var typedArray = {
66304    typedArrayIndexOf: typedArrayIndexOf$1
66305  };
66306  /**
66307   * mux.js
66308   *
66309   * Copyright (c) Brightcove
66310   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
66311   *
66312   * Tools for parsing ID3 frame data
66313   * @see http://id3.org/id3v2.3.0
66314   */
66315
66316  var typedArrayIndexOf = typedArray.typedArrayIndexOf,
66317    // Frames that allow different types of text encoding contain a text
66318    // encoding description byte [ID3v2.4.0 section 4.]
66319    textEncodingDescriptionByte = {
66320      Iso88591: 0x00,
66321      // ISO-8859-1, terminated with \0.
66322      Utf16: 0x01,
66323      // UTF-16 encoded Unicode BOM, terminated with \0\0
66324      Utf16be: 0x02,
66325      // UTF-16BE encoded Unicode, without BOM, terminated with \0\0
66326      Utf8: 0x03 // UTF-8 encoded Unicode, terminated with \0
66327    },
66328    // return a percent-encoded representation of the specified byte range
66329    // @see http://en.wikipedia.org/wiki/Percent-encoding 
66330    percentEncode$1 = function (bytes, start, end) {
66331      var i,
66332        result = '';
66333      for (i = start; i < end; i++) {
66334        result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
66335      }
66336      return result;
66337    },
66338    // return the string representation of the specified byte range,
66339    // interpreted as UTf-8.
66340    parseUtf8 = function (bytes, start, end) {
66341      return decodeURIComponent(percentEncode$1(bytes, start, end));
66342    },
66343    // return the string representation of the specified byte range,
66344    // interpreted as ISO-8859-1.
66345    parseIso88591$1 = function (bytes, start, end) {
66346      return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
66347    },
66348    parseSyncSafeInteger$1 = function (data) {
66349      return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
66350    },
66351    frameParsers = {
66352      'APIC': function (frame) {
66353        var i = 1,
66354          mimeTypeEndIndex,
66355          descriptionEndIndex,
66356          LINK_MIME_TYPE = '-->';
66357        if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
66358          // ignore frames with unrecognized character encodings
66359          return;
66360        } // parsing fields [ID3v2.4.0 section 4.14.]
66361
66362        mimeTypeEndIndex = typedArrayIndexOf(frame.data, 0, i);
66363        if (mimeTypeEndIndex < 0) {
66364          // malformed frame
66365          return;
66366        } // parsing Mime type field (terminated with \0)
66367
66368        frame.mimeType = parseIso88591$1(frame.data, i, mimeTypeEndIndex);
66369        i = mimeTypeEndIndex + 1; // parsing 1-byte Picture Type field
66370
66371        frame.pictureType = frame.data[i];
66372        i++;
66373        descriptionEndIndex = typedArrayIndexOf(frame.data, 0, i);
66374        if (descriptionEndIndex < 0) {
66375          // malformed frame
66376          return;
66377        } // parsing Description field (terminated with \0)
66378
66379        frame.description = parseUtf8(frame.data, i, descriptionEndIndex);
66380        i = descriptionEndIndex + 1;
66381        if (frame.mimeType === LINK_MIME_TYPE) {
66382          // parsing Picture Data field as URL (always represented as ISO-8859-1 [ID3v2.4.0 section 4.])
66383          frame.url = parseIso88591$1(frame.data, i, frame.data.length);
66384        } else {
66385          // parsing Picture Data field as binary data
66386          frame.pictureData = frame.data.subarray(i, frame.data.length);
66387        }
66388      },
66389      'T*': function (frame) {
66390        if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
66391          // ignore frames with unrecognized character encodings
66392          return;
66393        } // parse text field, do not include null terminator in the frame value
66394        // frames that allow different types of encoding contain terminated text [ID3v2.4.0 section 4.]
66395
66396        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.]
66397
66398        frame.values = frame.value.split('\0');
66399      },
66400      'TXXX': function (frame) {
66401        var descriptionEndIndex;
66402        if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
66403          // ignore frames with unrecognized character encodings
66404          return;
66405        }
66406        descriptionEndIndex = typedArrayIndexOf(frame.data, 0, 1);
66407        if (descriptionEndIndex === -1) {
66408          return;
66409        } // parse the text fields
66410
66411        frame.description = parseUtf8(frame.data, 1, descriptionEndIndex); // do not include the null terminator in the tag value
66412        // frames that allow different types of encoding contain terminated text
66413        // [ID3v2.4.0 section 4.]
66414
66415        frame.value = parseUtf8(frame.data, descriptionEndIndex + 1, frame.data.length).replace(/\0*$/, '');
66416        frame.data = frame.value;
66417      },
66418      'W*': function (frame) {
66419        // parse URL field; URL fields are always represented as ISO-8859-1 [ID3v2.4.0 section 4.]
66420        // if the value is followed by a string termination all the following information should be ignored [ID3v2.4.0 section 4.3]
66421        frame.url = parseIso88591$1(frame.data, 0, frame.data.length).replace(/\0.*$/, '');
66422      },
66423      'WXXX': function (frame) {
66424        var descriptionEndIndex;
66425        if (frame.data[0] !== textEncodingDescriptionByte.Utf8) {
66426          // ignore frames with unrecognized character encodings
66427          return;
66428        }
66429        descriptionEndIndex = typedArrayIndexOf(frame.data, 0, 1);
66430        if (descriptionEndIndex === -1) {
66431          return;
66432        } // parse the description and URL fields
66433
66434        frame.description = parseUtf8(frame.data, 1, descriptionEndIndex); // URL fields are always represented as ISO-8859-1 [ID3v2.4.0 section 4.]
66435        // if the value is followed by a string termination all the following information
66436        // should be ignored [ID3v2.4.0 section 4.3]
66437
66438        frame.url = parseIso88591$1(frame.data, descriptionEndIndex + 1, frame.data.length).replace(/\0.*$/, '');
66439      },
66440      'PRIV': function (frame) {
66441        var i;
66442        for (i = 0; i < frame.data.length; i++) {
66443          if (frame.data[i] === 0) {
66444            // parse the description and URL fields
66445            frame.owner = parseIso88591$1(frame.data, 0, i);
66446            break;
66447          }
66448        }
66449        frame.privateData = frame.data.subarray(i + 1);
66450        frame.data = frame.privateData;
66451      }
66452    };
66453  var parseId3Frames$1 = function (data) {
66454    var frameSize,
66455      frameHeader,
66456      frameStart = 10,
66457      tagSize = 0,
66458      frames = []; // If we don't have enough data for a header, 10 bytes, 
66459    // or 'ID3' in the first 3 bytes this is not a valid ID3 tag.
66460
66461    if (data.length < 10 || data[0] !== 'I'.charCodeAt(0) || data[1] !== 'D'.charCodeAt(0) || data[2] !== '3'.charCodeAt(0)) {
66462      return;
66463    } // the frame size is transmitted as a 28-bit integer in the
66464    // last four bytes of the ID3 header.
66465    // The most significant bit of each byte is dropped and the
66466    // results concatenated to recover the actual value.
66467
66468    tagSize = parseSyncSafeInteger$1(data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
66469    // convenient for our comparisons to include it
66470
66471    tagSize += 10; // check bit 6 of byte 5 for the extended header flag.
66472
66473    var hasExtendedHeader = data[5] & 0x40;
66474    if (hasExtendedHeader) {
66475      // advance the frame start past the extended header
66476      frameStart += 4; // header size field
66477
66478      frameStart += parseSyncSafeInteger$1(data.subarray(10, 14));
66479      tagSize -= parseSyncSafeInteger$1(data.subarray(16, 20)); // clip any padding off the end
66480    } // parse one or more ID3 frames
66481    // http://id3.org/id3v2.3.0#ID3v2_frame_overview
66482
66483    do {
66484      // determine the number of bytes in this frame
66485      frameSize = parseSyncSafeInteger$1(data.subarray(frameStart + 4, frameStart + 8));
66486      if (frameSize < 1) {
66487        break;
66488      }
66489      frameHeader = String.fromCharCode(data[frameStart], data[frameStart + 1], data[frameStart + 2], data[frameStart + 3]);
66490      var frame = {
66491        id: frameHeader,
66492        data: data.subarray(frameStart + 10, frameStart + frameSize + 10)
66493      };
66494      frame.key = frame.id; // parse frame values
66495
66496      if (frameParsers[frame.id]) {
66497        // use frame specific parser
66498        frameParsers[frame.id](frame);
66499      } else if (frame.id[0] === 'T') {
66500        // use text frame generic parser
66501        frameParsers['T*'](frame);
66502      } else if (frame.id[0] === 'W') {
66503        // use URL link frame generic parser
66504        frameParsers['W*'](frame);
66505      }
66506      frames.push(frame);
66507      frameStart += 10; // advance past the frame header
66508
66509      frameStart += frameSize; // advance past the frame body
66510    } while (frameStart < tagSize);
66511    return frames;
66512  };
66513  var parseId3 = {
66514    parseId3Frames: parseId3Frames$1,
66515    parseSyncSafeInteger: parseSyncSafeInteger$1,
66516    frameParsers: frameParsers
66517  };
66518  /**
66519   * mux.js
66520   *
66521   * Copyright (c) Brightcove
66522   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
66523   *
66524   * Accepts program elementary stream (PES) data events and parses out
66525   * ID3 metadata from them, if present.
66526   * @see http://id3.org/id3v2.3.0
66527   */
66528
66529  var Stream$5 = stream,
66530    StreamTypes$3 = streamTypes,
66531    id3 = parseId3,
66532    MetadataStream;
66533  MetadataStream = function (options) {
66534    var settings = {
66535        // the bytes of the program-level descriptor field in MP2T
66536        // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
66537        // program element descriptors"
66538        descriptor: options && options.descriptor
66539      },
66540      // the total size in bytes of the ID3 tag being parsed
66541      tagSize = 0,
66542      // tag data that is not complete enough to be parsed
66543      buffer = [],
66544      // the total number of bytes currently in the buffer
66545      bufferSize = 0,
66546      i;
66547    MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type
66548    // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
66549
66550    this.dispatchType = StreamTypes$3.METADATA_STREAM_TYPE.toString(16);
66551    if (settings.descriptor) {
66552      for (i = 0; i < settings.descriptor.length; i++) {
66553        this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
66554      }
66555    }
66556    this.push = function (chunk) {
66557      var tag, frameStart, frameSize, frame, i, frameHeader;
66558      if (chunk.type !== 'timed-metadata') {
66559        return;
66560      } // if data_alignment_indicator is set in the PES header,
66561      // we must have the start of a new ID3 tag. Assume anything
66562      // remaining in the buffer was malformed and throw it out
66563
66564      if (chunk.dataAlignmentIndicator) {
66565        bufferSize = 0;
66566        buffer.length = 0;
66567      } // ignore events that don't look like ID3 data
66568
66569      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))) {
66570        this.trigger('log', {
66571          level: 'warn',
66572          message: 'Skipping unrecognized metadata packet'
66573        });
66574        return;
66575      } // add this chunk to the data we've collected so far
66576
66577      buffer.push(chunk);
66578      bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header
66579
66580      if (buffer.length === 1) {
66581        // the frame size is transmitted as a 28-bit integer in the
66582        // last four bytes of the ID3 header.
66583        // The most significant bit of each byte is dropped and the
66584        // results concatenated to recover the actual value.
66585        tagSize = id3.parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more
66586        // convenient for our comparisons to include it
66587
66588        tagSize += 10;
66589      } // if the entire frame has not arrived, wait for more data
66590
66591      if (bufferSize < tagSize) {
66592        return;
66593      } // collect the entire frame so it can be parsed
66594
66595      tag = {
66596        data: new Uint8Array(tagSize),
66597        frames: [],
66598        pts: buffer[0].pts,
66599        dts: buffer[0].dts
66600      };
66601      for (i = 0; i < tagSize;) {
66602        tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
66603        i += buffer[0].data.byteLength;
66604        bufferSize -= buffer[0].data.byteLength;
66605        buffer.shift();
66606      } // find the start of the first frame and the end of the tag
66607
66608      frameStart = 10;
66609      if (tag.data[5] & 0x40) {
66610        // advance the frame start past the extended header
66611        frameStart += 4; // header size field
66612
66613        frameStart += id3.parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end
66614
66615        tagSize -= id3.parseSyncSafeInteger(tag.data.subarray(16, 20));
66616      } // parse one or more ID3 frames
66617      // http://id3.org/id3v2.3.0#ID3v2_frame_overview
66618
66619      do {
66620        // determine the number of bytes in this frame
66621        frameSize = id3.parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
66622        if (frameSize < 1) {
66623          this.trigger('log', {
66624            level: 'warn',
66625            message: 'Malformed ID3 frame encountered. Skipping remaining metadata parsing.'
66626          }); // If the frame is malformed, don't parse any further frames but allow previous valid parsed frames
66627          // to be sent along.
66628
66629          break;
66630        }
66631        frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
66632        frame = {
66633          id: frameHeader,
66634          data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
66635        };
66636        frame.key = frame.id; // parse frame values
66637
66638        if (id3.frameParsers[frame.id]) {
66639          // use frame specific parser
66640          id3.frameParsers[frame.id](frame);
66641        } else if (frame.id[0] === 'T') {
66642          // use text frame generic parser
66643          id3.frameParsers['T*'](frame);
66644        } else if (frame.id[0] === 'W') {
66645          // use URL link frame generic parser
66646          id3.frameParsers['W*'](frame);
66647        } // handle the special PRIV frame used to indicate the start
66648        // time for raw AAC data
66649
66650        if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
66651          var d = frame.data,
66652            size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
66653          size *= 4;
66654          size += d[7] & 0x03;
66655          frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based
66656          // on the value of this frame
66657          // we couldn't have known the appropriate pts and dts before
66658          // parsing this ID3 tag so set those values now
66659
66660          if (tag.pts === undefined && tag.dts === undefined) {
66661            tag.pts = frame.timeStamp;
66662            tag.dts = frame.timeStamp;
66663          }
66664          this.trigger('timestamp', frame);
66665        }
66666        tag.frames.push(frame);
66667        frameStart += 10; // advance past the frame header
66668
66669        frameStart += frameSize; // advance past the frame body
66670      } while (frameStart < tagSize);
66671      this.trigger('data', tag);
66672    };
66673  };
66674  MetadataStream.prototype = new Stream$5();
66675  var metadataStream = MetadataStream;
66676  /**
66677   * mux.js
66678   *
66679   * Copyright (c) Brightcove
66680   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
66681   *
66682   * A stream-based mp2t to mp4 converter. This utility can be used to
66683   * deliver mp4s to a SourceBuffer on platforms that support native
66684   * Media Source Extensions.
66685   */
66686
66687  var Stream$4 = stream,
66688    CaptionStream$1 = captionStream,
66689    StreamTypes$2 = streamTypes,
66690    TimestampRolloverStream = timestampRolloverStream.TimestampRolloverStream; // object types
66691
66692  var TransportPacketStream, TransportParseStream, ElementaryStream; // constants
66693
66694  var MP2T_PACKET_LENGTH$1 = 188,
66695    // bytes
66696    SYNC_BYTE$1 = 0x47;
66697  /**
66698   * Splits an incoming stream of binary data into MPEG-2 Transport
66699   * Stream packets.
66700   */
66701
66702  TransportPacketStream = function () {
66703    var buffer = new Uint8Array(MP2T_PACKET_LENGTH$1),
66704      bytesInBuffer = 0;
66705    TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream.
66706
66707    /**
66708     * Split a stream of data into M2TS packets
66709    **/
66710
66711    this.push = function (bytes) {
66712      var startIndex = 0,
66713        endIndex = MP2T_PACKET_LENGTH$1,
66714        everything; // If there are bytes remaining from the last segment, prepend them to the
66715      // bytes that were pushed in
66716
66717      if (bytesInBuffer) {
66718        everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
66719        everything.set(buffer.subarray(0, bytesInBuffer));
66720        everything.set(bytes, bytesInBuffer);
66721        bytesInBuffer = 0;
66722      } else {
66723        everything = bytes;
66724      } // While we have enough data for a packet
66725
66726      while (endIndex < everything.byteLength) {
66727        // Look for a pair of start and end sync bytes in the data..
66728        if (everything[startIndex] === SYNC_BYTE$1 && everything[endIndex] === SYNC_BYTE$1) {
66729          // We found a packet so emit it and jump one whole packet forward in
66730          // the stream
66731          this.trigger('data', everything.subarray(startIndex, endIndex));
66732          startIndex += MP2T_PACKET_LENGTH$1;
66733          endIndex += MP2T_PACKET_LENGTH$1;
66734          continue;
66735        } // If we get here, we have somehow become de-synchronized and we need to step
66736        // forward one byte at a time until we find a pair of sync bytes that denote
66737        // a packet
66738
66739        startIndex++;
66740        endIndex++;
66741      } // If there was some data left over at the end of the segment that couldn't
66742      // possibly be a whole packet, keep it because it might be the start of a packet
66743      // that continues in the next segment
66744
66745      if (startIndex < everything.byteLength) {
66746        buffer.set(everything.subarray(startIndex), 0);
66747        bytesInBuffer = everything.byteLength - startIndex;
66748      }
66749    };
66750    /**
66751     * Passes identified M2TS packets to the TransportParseStream to be parsed
66752    **/
66753
66754    this.flush = function () {
66755      // If the buffer contains a whole packet when we are being flushed, emit it
66756      // and empty the buffer. Otherwise hold onto the data because it may be
66757      // important for decoding the next segment
66758      if (bytesInBuffer === MP2T_PACKET_LENGTH$1 && buffer[0] === SYNC_BYTE$1) {
66759        this.trigger('data', buffer);
66760        bytesInBuffer = 0;
66761      }
66762      this.trigger('done');
66763    };
66764    this.endTimeline = function () {
66765      this.flush();
66766      this.trigger('endedtimeline');
66767    };
66768    this.reset = function () {
66769      bytesInBuffer = 0;
66770      this.trigger('reset');
66771    };
66772  };
66773  TransportPacketStream.prototype = new Stream$4();
66774  /**
66775   * Accepts an MP2T TransportPacketStream and emits data events with parsed
66776   * forms of the individual transport stream packets.
66777   */
66778
66779  TransportParseStream = function () {
66780    var parsePsi, parsePat, parsePmt, self;
66781    TransportParseStream.prototype.init.call(this);
66782    self = this;
66783    this.packetsWaitingForPmt = [];
66784    this.programMapTable = undefined;
66785    parsePsi = function (payload, psi) {
66786      var offset = 0; // PSI packets may be split into multiple sections and those
66787      // sections may be split into multiple packets. If a PSI
66788      // section starts in this packet, the payload_unit_start_indicator
66789      // will be true and the first byte of the payload will indicate
66790      // the offset from the current position to the start of the
66791      // section.
66792
66793      if (psi.payloadUnitStartIndicator) {
66794        offset += payload[offset] + 1;
66795      }
66796      if (psi.type === 'pat') {
66797        parsePat(payload.subarray(offset), psi);
66798      } else {
66799        parsePmt(payload.subarray(offset), psi);
66800      }
66801    };
66802    parsePat = function (payload, pat) {
66803      pat.section_number = payload[7]; // eslint-disable-line camelcase
66804
66805      pat.last_section_number = payload[8]; // eslint-disable-line camelcase
66806      // skip the PSI header and parse the first PMT entry
66807
66808      self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
66809      pat.pmtPid = self.pmtPid;
66810    };
66811    /**
66812     * Parse out the relevant fields of a Program Map Table (PMT).
66813     * @param payload {Uint8Array} the PMT-specific portion of an MP2T
66814     * packet. The first byte in this array should be the table_id
66815     * field.
66816     * @param pmt {object} the object that should be decorated with
66817     * fields parsed from the PMT.
66818     */
66819
66820    parsePmt = function (payload, pmt) {
66821      var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually
66822      // take effect. We don't believe this should ever be the case
66823      // for HLS but we'll ignore "forward" PMT declarations if we see
66824      // them. Future PMT declarations have the current_next_indicator
66825      // set to zero.
66826
66827      if (!(payload[5] & 0x01)) {
66828        return;
66829      } // overwrite any existing program map table
66830
66831      self.programMapTable = {
66832        video: null,
66833        audio: null,
66834        'timed-metadata': {}
66835      }; // the mapping table ends at the end of the current section
66836
66837      sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
66838      tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
66839      // long the program info descriptors are
66840
66841      programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table
66842
66843      offset = 12 + programInfoLength;
66844      while (offset < tableEnd) {
66845        var streamType = payload[offset];
66846        var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types
66847        // TODO: should this be done for metadata too? for now maintain behavior of
66848        //       multiple metadata streams
66849
66850        if (streamType === StreamTypes$2.H264_STREAM_TYPE && self.programMapTable.video === null) {
66851          self.programMapTable.video = pid;
66852        } else if (streamType === StreamTypes$2.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
66853          self.programMapTable.audio = pid;
66854        } else if (streamType === StreamTypes$2.METADATA_STREAM_TYPE) {
66855          // map pid to stream type for metadata streams
66856          self.programMapTable['timed-metadata'][pid] = streamType;
66857        } // move to the next table entry
66858        // skip past the elementary stream descriptors, if present
66859
66860        offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
66861      } // record the map on the packet as well
66862
66863      pmt.programMapTable = self.programMapTable;
66864    };
66865    /**
66866     * Deliver a new MP2T packet to the next stream in the pipeline.
66867     */
66868
66869    this.push = function (packet) {
66870      var result = {},
66871        offset = 4;
66872      result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1]
66873
66874      result.pid = packet[1] & 0x1f;
66875      result.pid <<= 8;
66876      result.pid |= packet[2]; // if an adaption field is present, its length is specified by the
66877      // fifth byte of the TS packet header. The adaptation field is
66878      // used to add stuffing to PES packets that don't fill a complete
66879      // TS packet, and to specify some forms of timing and control data
66880      // that we do not currently use.
66881
66882      if ((packet[3] & 0x30) >>> 4 > 0x01) {
66883        offset += packet[offset] + 1;
66884      } // parse the rest of the packet based on the type
66885
66886      if (result.pid === 0) {
66887        result.type = 'pat';
66888        parsePsi(packet.subarray(offset), result);
66889        this.trigger('data', result);
66890      } else if (result.pid === this.pmtPid) {
66891        result.type = 'pmt';
66892        parsePsi(packet.subarray(offset), result);
66893        this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now
66894
66895        while (this.packetsWaitingForPmt.length) {
66896          this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
66897        }
66898      } else if (this.programMapTable === undefined) {
66899        // When we have not seen a PMT yet, defer further processing of
66900        // PES packets until one has been parsed
66901        this.packetsWaitingForPmt.push([packet, offset, result]);
66902      } else {
66903        this.processPes_(packet, offset, result);
66904      }
66905    };
66906    this.processPes_ = function (packet, offset, result) {
66907      // set the appropriate stream type
66908      if (result.pid === this.programMapTable.video) {
66909        result.streamType = StreamTypes$2.H264_STREAM_TYPE;
66910      } else if (result.pid === this.programMapTable.audio) {
66911        result.streamType = StreamTypes$2.ADTS_STREAM_TYPE;
66912      } else {
66913        // if not video or audio, it is timed-metadata or unknown
66914        // if unknown, streamType will be undefined
66915        result.streamType = this.programMapTable['timed-metadata'][result.pid];
66916      }
66917      result.type = 'pes';
66918      result.data = packet.subarray(offset);
66919      this.trigger('data', result);
66920    };
66921  };
66922  TransportParseStream.prototype = new Stream$4();
66923  TransportParseStream.STREAM_TYPES = {
66924    h264: 0x1b,
66925    adts: 0x0f
66926  };
66927  /**
66928   * Reconsistutes program elementary stream (PES) packets from parsed
66929   * transport stream packets. That is, if you pipe an
66930   * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
66931   * events will be events which capture the bytes for individual PES
66932   * packets plus relevant metadata that has been extracted from the
66933   * container.
66934   */
66935
66936  ElementaryStream = function () {
66937    var self = this,
66938      segmentHadPmt = false,
66939      // PES packet fragments
66940      video = {
66941        data: [],
66942        size: 0
66943      },
66944      audio = {
66945        data: [],
66946        size: 0
66947      },
66948      timedMetadata = {
66949        data: [],
66950        size: 0
66951      },
66952      programMapTable,
66953      parsePes = function (payload, pes) {
66954        var ptsDtsFlags;
66955        const startPrefix = payload[0] << 16 | payload[1] << 8 | payload[2]; // default to an empty array
66956
66957        pes.data = new Uint8Array(); // In certain live streams, the start of a TS fragment has ts packets
66958        // that are frame data that is continuing from the previous fragment. This
66959        // is to check that the pes data is the start of a new pes payload
66960
66961        if (startPrefix !== 1) {
66962          return;
66963        } // get the packet length, this will be 0 for video
66964
66965        pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe
66966
66967        pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value
66968        // and a DTS value. Determine what combination of values is
66969        // available to work with.
66970
66971        ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
66972        // performs all bitwise operations on 32-bit integers but javascript
66973        // supports a much greater range (52-bits) of integer using standard
66974        // mathematical operations.
66975        // We construct a 31-bit value using bitwise operators over the 31
66976        // most significant bits and then multiply by 4 (equal to a left-shift
66977        // of 2) before we add the final 2 least significant bits of the
66978        // timestamp (equal to an OR.)
66979
66980        if (ptsDtsFlags & 0xC0) {
66981          // the PTS and DTS are not written out directly. For information
66982          // on how they are encoded, see
66983          // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
66984          pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
66985          pes.pts *= 4; // Left shift by 2
66986
66987          pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
66988
66989          pes.dts = pes.pts;
66990          if (ptsDtsFlags & 0x40) {
66991            pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
66992            pes.dts *= 4; // Left shift by 2
66993
66994            pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
66995          }
66996        } // the data section starts immediately after the PES header.
66997        // pes_header_data_length specifies the number of header bytes
66998        // that follow the last byte of the field.
66999
67000        pes.data = payload.subarray(9 + payload[8]);
67001      },
67002      /**
67003        * Pass completely parsed PES packets to the next stream in the pipeline
67004       **/
67005      flushStream = function (stream, type, forceFlush) {
67006        var packetData = new Uint8Array(stream.size),
67007          event = {
67008            type: type
67009          },
67010          i = 0,
67011          offset = 0,
67012          packetFlushable = false,
67013          fragment; // do nothing if there is not enough buffered data for a complete
67014        // PES header
67015
67016        if (!stream.data.length || stream.size < 9) {
67017          return;
67018        }
67019        event.trackId = stream.data[0].pid; // reassemble the packet
67020
67021        for (i = 0; i < stream.data.length; i++) {
67022          fragment = stream.data[i];
67023          packetData.set(fragment.data, offset);
67024          offset += fragment.data.byteLength;
67025        } // parse assembled packet's PES header
67026
67027        parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length
67028        // check that there is enough stream data to fill the packet
67029
67030        packetFlushable = type === 'video' || event.packetLength <= stream.size; // flush pending packets if the conditions are right
67031
67032        if (forceFlush || packetFlushable) {
67033          stream.size = 0;
67034          stream.data.length = 0;
67035        } // only emit packets that are complete. this is to avoid assembling
67036        // incomplete PES packets due to poor segmentation
67037
67038        if (packetFlushable) {
67039          self.trigger('data', event);
67040        }
67041      };
67042    ElementaryStream.prototype.init.call(this);
67043    /**
67044     * Identifies M2TS packet types and parses PES packets using metadata
67045     * parsed from the PMT
67046     **/
67047
67048    this.push = function (data) {
67049      ({
67050        pat: function () {// we have to wait for the PMT to arrive as well before we
67051          // have any meaningful metadata
67052        },
67053        pes: function () {
67054          var stream, streamType;
67055          switch (data.streamType) {
67056            case StreamTypes$2.H264_STREAM_TYPE:
67057              stream = video;
67058              streamType = 'video';
67059              break;
67060            case StreamTypes$2.ADTS_STREAM_TYPE:
67061              stream = audio;
67062              streamType = 'audio';
67063              break;
67064            case StreamTypes$2.METADATA_STREAM_TYPE:
67065              stream = timedMetadata;
67066              streamType = 'timed-metadata';
67067              break;
67068            default:
67069              // ignore unknown stream types
67070              return;
67071          } // if a new packet is starting, we can flush the completed
67072          // packet
67073
67074          if (data.payloadUnitStartIndicator) {
67075            flushStream(stream, streamType, true);
67076          } // buffer this fragment until we are sure we've received the
67077          // complete payload
67078
67079          stream.data.push(data);
67080          stream.size += data.data.byteLength;
67081        },
67082        pmt: function () {
67083          var event = {
67084            type: 'metadata',
67085            tracks: []
67086          };
67087          programMapTable = data.programMapTable; // translate audio and video streams to tracks
67088
67089          if (programMapTable.video !== null) {
67090            event.tracks.push({
67091              timelineStartInfo: {
67092                baseMediaDecodeTime: 0
67093              },
67094              id: +programMapTable.video,
67095              codec: 'avc',
67096              type: 'video'
67097            });
67098          }
67099          if (programMapTable.audio !== null) {
67100            event.tracks.push({
67101              timelineStartInfo: {
67102                baseMediaDecodeTime: 0
67103              },
67104              id: +programMapTable.audio,
67105              codec: 'adts',
67106              type: 'audio'
67107            });
67108          }
67109          segmentHadPmt = true;
67110          self.trigger('data', event);
67111        }
67112      })[data.type]();
67113    };
67114    this.reset = function () {
67115      video.size = 0;
67116      video.data.length = 0;
67117      audio.size = 0;
67118      audio.data.length = 0;
67119      this.trigger('reset');
67120    };
67121    /**
67122     * Flush any remaining input. Video PES packets may be of variable
67123     * length. Normally, the start of a new video packet can trigger the
67124     * finalization of the previous packet. That is not possible if no
67125     * more video is forthcoming, however. In that case, some other
67126     * mechanism (like the end of the file) has to be employed. When it is
67127     * clear that no additional data is forthcoming, calling this method
67128     * will flush the buffered packets.
67129     */
67130
67131    this.flushStreams_ = function () {
67132      // !!THIS ORDER IS IMPORTANT!!
67133      // video first then audio
67134      flushStream(video, 'video');
67135      flushStream(audio, 'audio');
67136      flushStream(timedMetadata, 'timed-metadata');
67137    };
67138    this.flush = function () {
67139      // if on flush we haven't had a pmt emitted
67140      // and we have a pmt to emit. emit the pmt
67141      // so that we trigger a trackinfo downstream.
67142      if (!segmentHadPmt && programMapTable) {
67143        var pmt = {
67144          type: 'metadata',
67145          tracks: []
67146        }; // translate audio and video streams to tracks
67147
67148        if (programMapTable.video !== null) {
67149          pmt.tracks.push({
67150            timelineStartInfo: {
67151              baseMediaDecodeTime: 0
67152            },
67153            id: +programMapTable.video,
67154            codec: 'avc',
67155            type: 'video'
67156          });
67157        }
67158        if (programMapTable.audio !== null) {
67159          pmt.tracks.push({
67160            timelineStartInfo: {
67161              baseMediaDecodeTime: 0
67162            },
67163            id: +programMapTable.audio,
67164            codec: 'adts',
67165            type: 'audio'
67166          });
67167        }
67168        self.trigger('data', pmt);
67169      }
67170      segmentHadPmt = false;
67171      this.flushStreams_();
67172      this.trigger('done');
67173    };
67174  };
67175  ElementaryStream.prototype = new Stream$4();
67176  var m2ts$1 = {
67177    PAT_PID: 0x0000,
67178    MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH$1,
67179    TransportPacketStream: TransportPacketStream,
67180    TransportParseStream: TransportParseStream,
67181    ElementaryStream: ElementaryStream,
67182    TimestampRolloverStream: TimestampRolloverStream,
67183    CaptionStream: CaptionStream$1.CaptionStream,
67184    Cea608Stream: CaptionStream$1.Cea608Stream,
67185    Cea708Stream: CaptionStream$1.Cea708Stream,
67186    MetadataStream: metadataStream
67187  };
67188  for (var type in StreamTypes$2) {
67189    if (StreamTypes$2.hasOwnProperty(type)) {
67190      m2ts$1[type] = StreamTypes$2[type];
67191    }
67192  }
67193  var m2ts_1 = m2ts$1;
67194  /**
67195   * mux.js
67196   *
67197   * Copyright (c) Brightcove
67198   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
67199   */
67200
67201  var Stream$3 = stream;
67202  var ONE_SECOND_IN_TS$2 = clock$2.ONE_SECOND_IN_TS;
67203  var AdtsStream$1;
67204  var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
67205  /*
67206   * Accepts a ElementaryStream and emits data events with parsed
67207   * AAC Audio Frames of the individual packets. Input audio in ADTS
67208   * format is unpacked and re-emitted as AAC frames.
67209   *
67210   * @see http://wiki.multimedia.cx/index.php?title=ADTS
67211   * @see http://wiki.multimedia.cx/?title=Understanding_AAC
67212   */
67213
67214  AdtsStream$1 = function (handlePartialSegments) {
67215    var buffer,
67216      frameNum = 0;
67217    AdtsStream$1.prototype.init.call(this);
67218    this.skipWarn_ = function (start, end) {
67219      this.trigger('log', {
67220        level: 'warn',
67221        message: `adts skiping bytes ${start} to ${end} in frame ${frameNum} outside syncword`
67222      });
67223    };
67224    this.push = function (packet) {
67225      var i = 0,
67226        frameLength,
67227        protectionSkipBytes,
67228        oldBuffer,
67229        sampleCount,
67230        adtsFrameDuration;
67231      if (!handlePartialSegments) {
67232        frameNum = 0;
67233      }
67234      if (packet.type !== 'audio') {
67235        // ignore non-audio data
67236        return;
67237      } // Prepend any data in the buffer to the input data so that we can parse
67238      // aac frames the cross a PES packet boundary
67239
67240      if (buffer && buffer.length) {
67241        oldBuffer = buffer;
67242        buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
67243        buffer.set(oldBuffer);
67244        buffer.set(packet.data, oldBuffer.byteLength);
67245      } else {
67246        buffer = packet.data;
67247      } // unpack any ADTS frames which have been fully received
67248      // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
67249
67250      var skip; // We use i + 7 here because we want to be able to parse the entire header.
67251      // If we don't have enough bytes to do that, then we definitely won't have a full frame.
67252
67253      while (i + 7 < buffer.length) {
67254        // Look for the start of an ADTS header..
67255        if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
67256          if (typeof skip !== 'number') {
67257            skip = i;
67258          } // If a valid header was not found,  jump one forward and attempt to
67259          // find a valid ADTS header starting at the next byte
67260
67261          i++;
67262          continue;
67263        }
67264        if (typeof skip === 'number') {
67265          this.skipWarn_(skip, i);
67266          skip = null;
67267        } // The protection skip bit tells us if we have 2 bytes of CRC data at the
67268        // end of the ADTS header
67269
67270        protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the
67271        // end of the sync sequence
67272        // NOTE: frame length includes the size of the header
67273
67274        frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
67275        sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
67276        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,
67277        // then we have to wait for more data
67278
67279        if (buffer.byteLength - i < frameLength) {
67280          break;
67281        } // Otherwise, deliver the complete AAC frame
67282
67283        this.trigger('data', {
67284          pts: packet.pts + frameNum * adtsFrameDuration,
67285          dts: packet.dts + frameNum * adtsFrameDuration,
67286          sampleCount: sampleCount,
67287          audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
67288          channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
67289          samplerate: ADTS_SAMPLING_FREQUENCIES$1[(buffer[i + 2] & 0x3c) >>> 2],
67290          samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
67291          // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
67292          samplesize: 16,
67293          // data is the frame without it's header
67294          data: buffer.subarray(i + 7 + protectionSkipBytes, i + frameLength)
67295        });
67296        frameNum++;
67297        i += frameLength;
67298      }
67299      if (typeof skip === 'number') {
67300        this.skipWarn_(skip, i);
67301        skip = null;
67302      } // remove processed bytes from the buffer.
67303
67304      buffer = buffer.subarray(i);
67305    };
67306    this.flush = function () {
67307      frameNum = 0;
67308      this.trigger('done');
67309    };
67310    this.reset = function () {
67311      buffer = void 0;
67312      this.trigger('reset');
67313    };
67314    this.endTimeline = function () {
67315      buffer = void 0;
67316      this.trigger('endedtimeline');
67317    };
67318  };
67319  AdtsStream$1.prototype = new Stream$3();
67320  var adts = AdtsStream$1;
67321  /**
67322   * mux.js
67323   *
67324   * Copyright (c) Brightcove
67325   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
67326   */
67327
67328  var ExpGolomb$1;
67329  /**
67330   * Parser for exponential Golomb codes, a variable-bitwidth number encoding
67331   * scheme used by h264.
67332   */
67333
67334  ExpGolomb$1 = function (workingData) {
67335    var
67336      // the number of bytes left to examine in workingData
67337      workingBytesAvailable = workingData.byteLength,
67338      // the current word being examined
67339      workingWord = 0,
67340      // :uint
67341      // the number of bits left to examine in the current word
67342      workingBitsAvailable = 0; // :uint;
67343    // ():uint
67344
67345    this.length = function () {
67346      return 8 * workingBytesAvailable;
67347    }; // ():uint
67348
67349    this.bitsAvailable = function () {
67350      return 8 * workingBytesAvailable + workingBitsAvailable;
67351    }; // ():void
67352
67353    this.loadWord = function () {
67354      var position = workingData.byteLength - workingBytesAvailable,
67355        workingBytes = new Uint8Array(4),
67356        availableBytes = Math.min(4, workingBytesAvailable);
67357      if (availableBytes === 0) {
67358        throw new Error('no bytes available');
67359      }
67360      workingBytes.set(workingData.subarray(position, position + availableBytes));
67361      workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed
67362
67363      workingBitsAvailable = availableBytes * 8;
67364      workingBytesAvailable -= availableBytes;
67365    }; // (count:int):void
67366
67367    this.skipBits = function (count) {
67368      var skipBytes; // :int
67369
67370      if (workingBitsAvailable > count) {
67371        workingWord <<= count;
67372        workingBitsAvailable -= count;
67373      } else {
67374        count -= workingBitsAvailable;
67375        skipBytes = Math.floor(count / 8);
67376        count -= skipBytes * 8;
67377        workingBytesAvailable -= skipBytes;
67378        this.loadWord();
67379        workingWord <<= count;
67380        workingBitsAvailable -= count;
67381      }
67382    }; // (size:int):uint
67383
67384    this.readBits = function (size) {
67385      var bits = Math.min(workingBitsAvailable, size),
67386        // :uint
67387        valu = workingWord >>> 32 - bits; // :uint
67388      // if size > 31, handle error
67389
67390      workingBitsAvailable -= bits;
67391      if (workingBitsAvailable > 0) {
67392        workingWord <<= bits;
67393      } else if (workingBytesAvailable > 0) {
67394        this.loadWord();
67395      }
67396      bits = size - bits;
67397      if (bits > 0) {
67398        return valu << bits | this.readBits(bits);
67399      }
67400      return valu;
67401    }; // ():uint
67402
67403    this.skipLeadingZeros = function () {
67404      var leadingZeroCount; // :uint
67405
67406      for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
67407        if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
67408          // the first bit of working word is 1
67409          workingWord <<= leadingZeroCount;
67410          workingBitsAvailable -= leadingZeroCount;
67411          return leadingZeroCount;
67412        }
67413      } // we exhausted workingWord and still have not found a 1
67414
67415      this.loadWord();
67416      return leadingZeroCount + this.skipLeadingZeros();
67417    }; // ():void
67418
67419    this.skipUnsignedExpGolomb = function () {
67420      this.skipBits(1 + this.skipLeadingZeros());
67421    }; // ():void
67422
67423    this.skipExpGolomb = function () {
67424      this.skipBits(1 + this.skipLeadingZeros());
67425    }; // ():uint
67426
67427    this.readUnsignedExpGolomb = function () {
67428      var clz = this.skipLeadingZeros(); // :uint
67429
67430      return this.readBits(clz + 1) - 1;
67431    }; // ():int
67432
67433    this.readExpGolomb = function () {
67434      var valu = this.readUnsignedExpGolomb(); // :int
67435
67436      if (0x01 & valu) {
67437        // the number is odd if the low order bit is set
67438        return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
67439      }
67440      return -1 * (valu >>> 1); // divide by two then make it negative
67441    }; // Some convenience functions
67442    // :Boolean
67443
67444    this.readBoolean = function () {
67445      return this.readBits(1) === 1;
67446    }; // ():int
67447
67448    this.readUnsignedByte = function () {
67449      return this.readBits(8);
67450    };
67451    this.loadWord();
67452  };
67453  var expGolomb = ExpGolomb$1;
67454  /**
67455   * mux.js
67456   *
67457   * Copyright (c) Brightcove
67458   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
67459   */
67460
67461  var Stream$2 = stream;
67462  var ExpGolomb = expGolomb;
67463  var H264Stream$1, NalByteStream;
67464  var PROFILES_WITH_OPTIONAL_SPS_DATA;
67465  /**
67466   * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
67467   */
67468
67469  NalByteStream = function () {
67470    var syncPoint = 0,
67471      i,
67472      buffer;
67473    NalByteStream.prototype.init.call(this);
67474    /*
67475     * Scans a byte stream and triggers a data event with the NAL units found.
67476     * @param {Object} data Event received from H264Stream
67477     * @param {Uint8Array} data.data The h264 byte stream to be scanned
67478     *
67479     * @see H264Stream.push
67480     */
67481
67482    this.push = function (data) {
67483      var swapBuffer;
67484      if (!buffer) {
67485        buffer = data.data;
67486      } else {
67487        swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
67488        swapBuffer.set(buffer);
67489        swapBuffer.set(data.data, buffer.byteLength);
67490        buffer = swapBuffer;
67491      }
67492      var len = buffer.byteLength; // Rec. ITU-T H.264, Annex B
67493      // scan for NAL unit boundaries
67494      // a match looks like this:
67495      // 0 0 1 .. NAL .. 0 0 1
67496      // ^ sync point        ^ i
67497      // or this:
67498      // 0 0 1 .. NAL .. 0 0 0
67499      // ^ sync point        ^ i
67500      // advance the sync point to a NAL start, if necessary
67501
67502      for (; syncPoint < len - 3; syncPoint++) {
67503        if (buffer[syncPoint + 2] === 1) {
67504          // the sync point is properly aligned
67505          i = syncPoint + 5;
67506          break;
67507        }
67508      }
67509      while (i < len) {
67510        // look at the current byte to determine if we've hit the end of
67511        // a NAL unit boundary
67512        switch (buffer[i]) {
67513          case 0:
67514            // skip past non-sync sequences
67515            if (buffer[i - 1] !== 0) {
67516              i += 2;
67517              break;
67518            } else if (buffer[i - 2] !== 0) {
67519              i++;
67520              break;
67521            } // deliver the NAL unit if it isn't empty
67522
67523            if (syncPoint + 3 !== i - 2) {
67524              this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
67525            } // drop trailing zeroes
67526
67527            do {
67528              i++;
67529            } while (buffer[i] !== 1 && i < len);
67530            syncPoint = i - 2;
67531            i += 3;
67532            break;
67533          case 1:
67534            // skip past non-sync sequences
67535            if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
67536              i += 3;
67537              break;
67538            } // deliver the NAL unit
67539
67540            this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
67541            syncPoint = i - 2;
67542            i += 3;
67543            break;
67544          default:
67545            // the current byte isn't a one or zero, so it cannot be part
67546            // of a sync sequence
67547            i += 3;
67548            break;
67549        }
67550      } // filter out the NAL units that were delivered
67551
67552      buffer = buffer.subarray(syncPoint);
67553      i -= syncPoint;
67554      syncPoint = 0;
67555    };
67556    this.reset = function () {
67557      buffer = null;
67558      syncPoint = 0;
67559      this.trigger('reset');
67560    };
67561    this.flush = function () {
67562      // deliver the last buffered NAL unit
67563      if (buffer && buffer.byteLength > 3) {
67564        this.trigger('data', buffer.subarray(syncPoint + 3));
67565      } // reset the stream state
67566
67567      buffer = null;
67568      syncPoint = 0;
67569      this.trigger('done');
67570    };
67571    this.endTimeline = function () {
67572      this.flush();
67573      this.trigger('endedtimeline');
67574    };
67575  };
67576  NalByteStream.prototype = new Stream$2(); // values of profile_idc that indicate additional fields are included in the SPS
67577  // see Recommendation ITU-T H.264 (4/2013),
67578  // 7.3.2.1.1 Sequence parameter set data syntax
67579
67580  PROFILES_WITH_OPTIONAL_SPS_DATA = {
67581    100: true,
67582    110: true,
67583    122: true,
67584    244: true,
67585    44: true,
67586    83: true,
67587    86: true,
67588    118: true,
67589    128: true,
67590    // TODO: the three profiles below don't
67591    // appear to have sps data in the specificiation anymore?
67592    138: true,
67593    139: true,
67594    134: true
67595  };
67596  /**
67597   * Accepts input from a ElementaryStream and produces H.264 NAL unit data
67598   * events.
67599   */
67600
67601  H264Stream$1 = function () {
67602    var nalByteStream = new NalByteStream(),
67603      self,
67604      trackId,
67605      currentPts,
67606      currentDts,
67607      discardEmulationPreventionBytes,
67608      readSequenceParameterSet,
67609      skipScalingList;
67610    H264Stream$1.prototype.init.call(this);
67611    self = this;
67612    /*
67613     * Pushes a packet from a stream onto the NalByteStream
67614     *
67615     * @param {Object} packet - A packet received from a stream
67616     * @param {Uint8Array} packet.data - The raw bytes of the packet
67617     * @param {Number} packet.dts - Decode timestamp of the packet
67618     * @param {Number} packet.pts - Presentation timestamp of the packet
67619     * @param {Number} packet.trackId - The id of the h264 track this packet came from
67620     * @param {('video'|'audio')} packet.type - The type of packet
67621     *
67622     */
67623
67624    this.push = function (packet) {
67625      if (packet.type !== 'video') {
67626        return;
67627      }
67628      trackId = packet.trackId;
67629      currentPts = packet.pts;
67630      currentDts = packet.dts;
67631      nalByteStream.push(packet);
67632    };
67633    /*
67634     * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
67635     * for the NALUs to the next stream component.
67636     * Also, preprocess caption and sequence parameter NALUs.
67637     *
67638     * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
67639     * @see NalByteStream.push
67640     */
67641
67642    nalByteStream.on('data', function (data) {
67643      var event = {
67644        trackId: trackId,
67645        pts: currentPts,
67646        dts: currentDts,
67647        data: data,
67648        nalUnitTypeCode: data[0] & 0x1f
67649      };
67650      switch (event.nalUnitTypeCode) {
67651        case 0x05:
67652          event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
67653          break;
67654        case 0x06:
67655          event.nalUnitType = 'sei_rbsp';
67656          event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
67657          break;
67658        case 0x07:
67659          event.nalUnitType = 'seq_parameter_set_rbsp';
67660          event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
67661          event.config = readSequenceParameterSet(event.escapedRBSP);
67662          break;
67663        case 0x08:
67664          event.nalUnitType = 'pic_parameter_set_rbsp';
67665          break;
67666        case 0x09:
67667          event.nalUnitType = 'access_unit_delimiter_rbsp';
67668          break;
67669      } // This triggers data on the H264Stream
67670
67671      self.trigger('data', event);
67672    });
67673    nalByteStream.on('done', function () {
67674      self.trigger('done');
67675    });
67676    nalByteStream.on('partialdone', function () {
67677      self.trigger('partialdone');
67678    });
67679    nalByteStream.on('reset', function () {
67680      self.trigger('reset');
67681    });
67682    nalByteStream.on('endedtimeline', function () {
67683      self.trigger('endedtimeline');
67684    });
67685    this.flush = function () {
67686      nalByteStream.flush();
67687    };
67688    this.partialFlush = function () {
67689      nalByteStream.partialFlush();
67690    };
67691    this.reset = function () {
67692      nalByteStream.reset();
67693    };
67694    this.endTimeline = function () {
67695      nalByteStream.endTimeline();
67696    };
67697    /**
67698     * Advance the ExpGolomb decoder past a scaling list. The scaling
67699     * list is optionally transmitted as part of a sequence parameter
67700     * set and is not relevant to transmuxing.
67701     * @param count {number} the number of entries in this scaling list
67702     * @param expGolombDecoder {object} an ExpGolomb pointed to the
67703     * start of a scaling list
67704     * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
67705     */
67706
67707    skipScalingList = function (count, expGolombDecoder) {
67708      var lastScale = 8,
67709        nextScale = 8,
67710        j,
67711        deltaScale;
67712      for (j = 0; j < count; j++) {
67713        if (nextScale !== 0) {
67714          deltaScale = expGolombDecoder.readExpGolomb();
67715          nextScale = (lastScale + deltaScale + 256) % 256;
67716        }
67717        lastScale = nextScale === 0 ? lastScale : nextScale;
67718      }
67719    };
67720    /**
67721     * Expunge any "Emulation Prevention" bytes from a "Raw Byte
67722     * Sequence Payload"
67723     * @param data {Uint8Array} the bytes of a RBSP from a NAL
67724     * unit
67725     * @return {Uint8Array} the RBSP without any Emulation
67726     * Prevention Bytes
67727     */
67728
67729    discardEmulationPreventionBytes = function (data) {
67730      var length = data.byteLength,
67731        emulationPreventionBytesPositions = [],
67732        i = 1,
67733        newLength,
67734        newData; // Find all `Emulation Prevention Bytes`
67735
67736      while (i < length - 2) {
67737        if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
67738          emulationPreventionBytesPositions.push(i + 2);
67739          i += 2;
67740        } else {
67741          i++;
67742        }
67743      } // If no Emulation Prevention Bytes were found just return the original
67744      // array
67745
67746      if (emulationPreventionBytesPositions.length === 0) {
67747        return data;
67748      } // Create a new array to hold the NAL unit data
67749
67750      newLength = length - emulationPreventionBytesPositions.length;
67751      newData = new Uint8Array(newLength);
67752      var sourceIndex = 0;
67753      for (i = 0; i < newLength; sourceIndex++, i++) {
67754        if (sourceIndex === emulationPreventionBytesPositions[0]) {
67755          // Skip this byte
67756          sourceIndex++; // Remove this position index
67757
67758          emulationPreventionBytesPositions.shift();
67759        }
67760        newData[i] = data[sourceIndex];
67761      }
67762      return newData;
67763    };
67764    /**
67765     * Read a sequence parameter set and return some interesting video
67766     * properties. A sequence parameter set is the H264 metadata that
67767     * describes the properties of upcoming video frames.
67768     * @param data {Uint8Array} the bytes of a sequence parameter set
67769     * @return {object} an object with configuration parsed from the
67770     * sequence parameter set, including the dimensions of the
67771     * associated video frames.
67772     */
67773
67774    readSequenceParameterSet = function (data) {
67775      var frameCropLeftOffset = 0,
67776        frameCropRightOffset = 0,
67777        frameCropTopOffset = 0,
67778        frameCropBottomOffset = 0,
67779        expGolombDecoder,
67780        profileIdc,
67781        levelIdc,
67782        profileCompatibility,
67783        chromaFormatIdc,
67784        picOrderCntType,
67785        numRefFramesInPicOrderCntCycle,
67786        picWidthInMbsMinus1,
67787        picHeightInMapUnitsMinus1,
67788        frameMbsOnlyFlag,
67789        scalingListCount,
67790        sarRatio = [1, 1],
67791        aspectRatioIdc,
67792        i;
67793      expGolombDecoder = new ExpGolomb(data);
67794      profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
67795
67796      profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
67797
67798      levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
67799
67800      expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
67801      // some profiles have more optional data we don't need
67802
67803      if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
67804        chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
67805        if (chromaFormatIdc === 3) {
67806          expGolombDecoder.skipBits(1); // separate_colour_plane_flag
67807        }
67808        expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
67809
67810        expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
67811
67812        expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
67813
67814        if (expGolombDecoder.readBoolean()) {
67815          // seq_scaling_matrix_present_flag
67816          scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
67817          for (i = 0; i < scalingListCount; i++) {
67818            if (expGolombDecoder.readBoolean()) {
67819              // seq_scaling_list_present_flag[ i ]
67820              if (i < 6) {
67821                skipScalingList(16, expGolombDecoder);
67822              } else {
67823                skipScalingList(64, expGolombDecoder);
67824              }
67825            }
67826          }
67827        }
67828      }
67829      expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
67830
67831      picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
67832      if (picOrderCntType === 0) {
67833        expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
67834      } else if (picOrderCntType === 1) {
67835        expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
67836
67837        expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
67838
67839        expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
67840
67841        numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
67842        for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
67843          expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
67844        }
67845      }
67846      expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
67847
67848      expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
67849
67850      picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
67851      picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
67852      frameMbsOnlyFlag = expGolombDecoder.readBits(1);
67853      if (frameMbsOnlyFlag === 0) {
67854        expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
67855      }
67856      expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
67857
67858      if (expGolombDecoder.readBoolean()) {
67859        // frame_cropping_flag
67860        frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
67861        frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
67862        frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
67863        frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
67864      }
67865      if (expGolombDecoder.readBoolean()) {
67866        // vui_parameters_present_flag
67867        if (expGolombDecoder.readBoolean()) {
67868          // aspect_ratio_info_present_flag
67869          aspectRatioIdc = expGolombDecoder.readUnsignedByte();
67870          switch (aspectRatioIdc) {
67871            case 1:
67872              sarRatio = [1, 1];
67873              break;
67874            case 2:
67875              sarRatio = [12, 11];
67876              break;
67877            case 3:
67878              sarRatio = [10, 11];
67879              break;
67880            case 4:
67881              sarRatio = [16, 11];
67882              break;
67883            case 5:
67884              sarRatio = [40, 33];
67885              break;
67886            case 6:
67887              sarRatio = [24, 11];
67888              break;
67889            case 7:
67890              sarRatio = [20, 11];
67891              break;
67892            case 8:
67893              sarRatio = [32, 11];
67894              break;
67895            case 9:
67896              sarRatio = [80, 33];
67897              break;
67898            case 10:
67899              sarRatio = [18, 11];
67900              break;
67901            case 11:
67902              sarRatio = [15, 11];
67903              break;
67904            case 12:
67905              sarRatio = [64, 33];
67906              break;
67907            case 13:
67908              sarRatio = [160, 99];
67909              break;
67910            case 14:
67911              sarRatio = [4, 3];
67912              break;
67913            case 15:
67914              sarRatio = [3, 2];
67915              break;
67916            case 16:
67917              sarRatio = [2, 1];
67918              break;
67919            case 255:
67920              {
67921                sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
67922                break;
67923              }
67924          }
67925          if (sarRatio) {
67926            sarRatio[0] / sarRatio[1];
67927          }
67928        }
67929      }
67930      return {
67931        profileIdc: profileIdc,
67932        levelIdc: levelIdc,
67933        profileCompatibility: profileCompatibility,
67934        width: (picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2,
67935        height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2,
67936        // sar is sample aspect ratio
67937        sarRatio: sarRatio
67938      };
67939    };
67940  };
67941  H264Stream$1.prototype = new Stream$2();
67942  var h264 = {
67943    H264Stream: H264Stream$1,
67944    NalByteStream: NalByteStream
67945  };
67946  /**
67947   * mux.js
67948   *
67949   * Copyright (c) Brightcove
67950   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
67951   *
67952   * Utilities to detect basic properties and metadata about Aac data.
67953   */
67954
67955  var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
67956  var parseId3TagSize = function (header, byteIndex) {
67957    var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
67958      flags = header[byteIndex + 5],
67959      footerPresent = (flags & 16) >> 4; // if we get a negative returnSize clamp it to 0
67960
67961    returnSize = returnSize >= 0 ? returnSize : 0;
67962    if (footerPresent) {
67963      return returnSize + 20;
67964    }
67965    return returnSize + 10;
67966  };
67967  var getId3Offset = function (data, offset) {
67968    if (data.length - offset < 10 || data[offset] !== 'I'.charCodeAt(0) || data[offset + 1] !== 'D'.charCodeAt(0) || data[offset + 2] !== '3'.charCodeAt(0)) {
67969      return offset;
67970    }
67971    offset += parseId3TagSize(data, offset);
67972    return getId3Offset(data, offset);
67973  }; // TODO: use vhs-utils
67974
67975  var isLikelyAacData$1 = function (data) {
67976    var offset = getId3Offset(data, 0);
67977    return data.length >= offset + 2 && (data[offset] & 0xFF) === 0xFF && (data[offset + 1] & 0xF0) === 0xF0 &&
67978    // verify that the 2 layer bits are 0, aka this
67979    // is not mp3 data but aac data.
67980    (data[offset + 1] & 0x16) === 0x10;
67981  };
67982  var parseSyncSafeInteger = function (data) {
67983    return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
67984  }; // return a percent-encoded representation of the specified byte range
67985  // @see http://en.wikipedia.org/wiki/Percent-encoding
67986
67987  var percentEncode = function (bytes, start, end) {
67988    var i,
67989      result = '';
67990    for (i = start; i < end; i++) {
67991      result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
67992    }
67993    return result;
67994  }; // return the string representation of the specified byte range,
67995  // interpreted as ISO-8859-1.
67996
67997  var parseIso88591 = function (bytes, start, end) {
67998    return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
67999  };
68000  var parseAdtsSize = function (header, byteIndex) {
68001    var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
68002      middle = header[byteIndex + 4] << 3,
68003      highTwo = header[byteIndex + 3] & 0x3 << 11;
68004    return highTwo | middle | lowThree;
68005  };
68006  var parseType$5 = function (header, byteIndex) {
68007    if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
68008      return 'timed-metadata';
68009    } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
68010      return 'audio';
68011    }
68012    return null;
68013  };
68014  var parseSampleRate = function (packet) {
68015    var i = 0;
68016    while (i + 5 < packet.length) {
68017      if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
68018        // If a valid header was not found,  jump one forward and attempt to
68019        // find a valid ADTS header starting at the next byte
68020        i++;
68021        continue;
68022      }
68023      return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2];
68024    }
68025    return null;
68026  };
68027  var parseAacTimestamp = function (packet) {
68028    var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag
68029
68030    frameStart = 10;
68031    if (packet[5] & 0x40) {
68032      // advance the frame start past the extended header
68033      frameStart += 4; // header size field
68034
68035      frameStart += parseSyncSafeInteger(packet.subarray(10, 14));
68036    } // parse one or more ID3 frames
68037    // http://id3.org/id3v2.3.0#ID3v2_frame_overview
68038
68039    do {
68040      // determine the number of bytes in this frame
68041      frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8));
68042      if (frameSize < 1) {
68043        return null;
68044      }
68045      frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
68046      if (frameHeader === 'PRIV') {
68047        frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
68048        for (var i = 0; i < frame.byteLength; i++) {
68049          if (frame[i] === 0) {
68050            var owner = parseIso88591(frame, 0, i);
68051            if (owner === 'com.apple.streaming.transportStreamTimestamp') {
68052              var d = frame.subarray(i + 1);
68053              var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
68054              size *= 4;
68055              size += d[7] & 0x03;
68056              return size;
68057            }
68058            break;
68059          }
68060        }
68061      }
68062      frameStart += 10; // advance past the frame header
68063
68064      frameStart += frameSize; // advance past the frame body
68065    } while (frameStart < packet.byteLength);
68066    return null;
68067  };
68068  var utils = {
68069    isLikelyAacData: isLikelyAacData$1,
68070    parseId3TagSize: parseId3TagSize,
68071    parseAdtsSize: parseAdtsSize,
68072    parseType: parseType$5,
68073    parseSampleRate: parseSampleRate,
68074    parseAacTimestamp: parseAacTimestamp
68075  };
68076  /**
68077   * mux.js
68078   *
68079   * Copyright (c) Brightcove
68080   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
68081   *
68082   * A stream-based aac to mp4 converter. This utility can be used to
68083   * deliver mp4s to a SourceBuffer on platforms that support native
68084   * Media Source Extensions.
68085   */
68086
68087  var Stream$1 = stream;
68088  var aacUtils = utils; // Constants
68089
68090  var AacStream$1;
68091  /**
68092   * Splits an incoming stream of binary data into ADTS and ID3 Frames.
68093   */
68094
68095  AacStream$1 = function () {
68096    var everything = new Uint8Array(),
68097      timeStamp = 0;
68098    AacStream$1.prototype.init.call(this);
68099    this.setTimestamp = function (timestamp) {
68100      timeStamp = timestamp;
68101    };
68102    this.push = function (bytes) {
68103      var frameSize = 0,
68104        byteIndex = 0,
68105        bytesLeft,
68106        chunk,
68107        packet,
68108        tempLength; // If there are bytes remaining from the last segment, prepend them to the
68109      // bytes that were pushed in
68110
68111      if (everything.length) {
68112        tempLength = everything.length;
68113        everything = new Uint8Array(bytes.byteLength + tempLength);
68114        everything.set(everything.subarray(0, tempLength));
68115        everything.set(bytes, tempLength);
68116      } else {
68117        everything = bytes;
68118      }
68119      while (everything.length - byteIndex >= 3) {
68120        if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
68121          // Exit early because we don't have enough to parse
68122          // the ID3 tag header
68123          if (everything.length - byteIndex < 10) {
68124            break;
68125          } // check framesize
68126
68127          frameSize = aacUtils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
68128          // to emit a full packet
68129          // Add to byteIndex to support multiple ID3 tags in sequence
68130
68131          if (byteIndex + frameSize > everything.length) {
68132            break;
68133          }
68134          chunk = {
68135            type: 'timed-metadata',
68136            data: everything.subarray(byteIndex, byteIndex + frameSize)
68137          };
68138          this.trigger('data', chunk);
68139          byteIndex += frameSize;
68140          continue;
68141        } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
68142          // Exit early because we don't have enough to parse
68143          // the ADTS frame header
68144          if (everything.length - byteIndex < 7) {
68145            break;
68146          }
68147          frameSize = aacUtils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer
68148          // to emit a full packet
68149
68150          if (byteIndex + frameSize > everything.length) {
68151            break;
68152          }
68153          packet = {
68154            type: 'audio',
68155            data: everything.subarray(byteIndex, byteIndex + frameSize),
68156            pts: timeStamp,
68157            dts: timeStamp
68158          };
68159          this.trigger('data', packet);
68160          byteIndex += frameSize;
68161          continue;
68162        }
68163        byteIndex++;
68164      }
68165      bytesLeft = everything.length - byteIndex;
68166      if (bytesLeft > 0) {
68167        everything = everything.subarray(byteIndex);
68168      } else {
68169        everything = new Uint8Array();
68170      }
68171    };
68172    this.reset = function () {
68173      everything = new Uint8Array();
68174      this.trigger('reset');
68175    };
68176    this.endTimeline = function () {
68177      everything = new Uint8Array();
68178      this.trigger('endedtimeline');
68179    };
68180  };
68181  AacStream$1.prototype = new Stream$1();
68182  var aac = AacStream$1;
68183  var AUDIO_PROPERTIES$1 = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
68184  var audioProperties = AUDIO_PROPERTIES$1;
68185  var VIDEO_PROPERTIES$1 = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility', 'sarRatio'];
68186  var videoProperties = VIDEO_PROPERTIES$1;
68187  /**
68188   * mux.js
68189   *
68190   * Copyright (c) Brightcove
68191   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
68192   *
68193   * A stream-based mp2t to mp4 converter. This utility can be used to
68194   * deliver mp4s to a SourceBuffer on platforms that support native
68195   * Media Source Extensions.
68196   */
68197
68198  var Stream = stream;
68199  var mp4 = mp4Generator;
68200  var frameUtils = frameUtils$1;
68201  var audioFrameUtils = audioFrameUtils$1;
68202  var trackDecodeInfo = trackDecodeInfo$1;
68203  var m2ts = m2ts_1;
68204  var clock = clock$2;
68205  var AdtsStream = adts;
68206  var H264Stream = h264.H264Stream;
68207  var AacStream = aac;
68208  var isLikelyAacData = utils.isLikelyAacData;
68209  var ONE_SECOND_IN_TS$1 = clock$2.ONE_SECOND_IN_TS;
68210  var AUDIO_PROPERTIES = audioProperties;
68211  var VIDEO_PROPERTIES = videoProperties; // object types
68212
68213  var VideoSegmentStream, AudioSegmentStream, Transmuxer, CoalesceStream;
68214  var retriggerForStream = function (key, event) {
68215    event.stream = key;
68216    this.trigger('log', event);
68217  };
68218  var addPipelineLogRetriggers = function (transmuxer, pipeline) {
68219    var keys = Object.keys(pipeline);
68220    for (var i = 0; i < keys.length; i++) {
68221      var key = keys[i]; // skip non-stream keys and headOfPipeline
68222      // which is just a duplicate
68223
68224      if (key === 'headOfPipeline' || !pipeline[key].on) {
68225        continue;
68226      }
68227      pipeline[key].on('log', retriggerForStream.bind(transmuxer, key));
68228    }
68229  };
68230  /**
68231   * Compare two arrays (even typed) for same-ness
68232   */
68233
68234  var arrayEquals = function (a, b) {
68235    var i;
68236    if (a.length !== b.length) {
68237      return false;
68238    } // compare the value of each element in the array
68239
68240    for (i = 0; i < a.length; i++) {
68241      if (a[i] !== b[i]) {
68242        return false;
68243      }
68244    }
68245    return true;
68246  };
68247  var generateSegmentTimingInfo = function (baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) {
68248    var ptsOffsetFromDts = startPts - startDts,
68249      decodeDuration = endDts - startDts,
68250      presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment,
68251    // however, the player time values will reflect a start from the baseMediaDecodeTime.
68252    // In order to provide relevant values for the player times, base timing info on the
68253    // baseMediaDecodeTime and the DTS and PTS durations of the segment.
68254
68255    return {
68256      start: {
68257        dts: baseMediaDecodeTime,
68258        pts: baseMediaDecodeTime + ptsOffsetFromDts
68259      },
68260      end: {
68261        dts: baseMediaDecodeTime + decodeDuration,
68262        pts: baseMediaDecodeTime + presentationDuration
68263      },
68264      prependedContentDuration: prependedContentDuration,
68265      baseMediaDecodeTime: baseMediaDecodeTime
68266    };
68267  };
68268  /**
68269   * Constructs a single-track, ISO BMFF media segment from AAC data
68270   * events. The output of this stream can be fed to a SourceBuffer
68271   * configured with a suitable initialization segment.
68272   * @param track {object} track metadata configuration
68273   * @param options {object} transmuxer options object
68274   * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
68275   *        in the source; false to adjust the first segment to start at 0.
68276   */
68277
68278  AudioSegmentStream = function (track, options) {
68279    var adtsFrames = [],
68280      sequenceNumber,
68281      earliestAllowedDts = 0,
68282      audioAppendStartTs = 0,
68283      videoBaseMediaDecodeTime = Infinity;
68284    options = options || {};
68285    sequenceNumber = options.firstSequenceNumber || 0;
68286    AudioSegmentStream.prototype.init.call(this);
68287    this.push = function (data) {
68288      trackDecodeInfo.collectDtsInfo(track, data);
68289      if (track) {
68290        AUDIO_PROPERTIES.forEach(function (prop) {
68291          track[prop] = data[prop];
68292        });
68293      } // buffer audio data until end() is called
68294
68295      adtsFrames.push(data);
68296    };
68297    this.setEarliestDts = function (earliestDts) {
68298      earliestAllowedDts = earliestDts;
68299    };
68300    this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
68301      videoBaseMediaDecodeTime = baseMediaDecodeTime;
68302    };
68303    this.setAudioAppendStart = function (timestamp) {
68304      audioAppendStartTs = timestamp;
68305    };
68306    this.flush = function () {
68307      var frames, moof, mdat, boxes, frameDuration, segmentDuration, videoClockCyclesOfSilencePrefixed; // return early if no audio data has been observed
68308
68309      if (adtsFrames.length === 0) {
68310        this.trigger('done', 'AudioSegmentStream');
68311        return;
68312      }
68313      frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts);
68314      track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); // amount of audio filled but the value is in video clock rather than audio clock
68315
68316      videoClockCyclesOfSilencePrefixed = audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to
68317      // samples (that is, adts frames) in the audio data
68318
68319      track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat
68320
68321      mdat = mp4.mdat(audioFrameUtils.concatenateFrameData(frames));
68322      adtsFrames = [];
68323      moof = mp4.moof(sequenceNumber, [track]);
68324      boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time
68325
68326      sequenceNumber++;
68327      boxes.set(moof);
68328      boxes.set(mdat, moof.byteLength);
68329      trackDecodeInfo.clearDtsInfo(track);
68330      frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 * 1024 / track.samplerate); // TODO this check was added to maintain backwards compatibility (particularly with
68331      // tests) on adding the timingInfo event. However, it seems unlikely that there's a
68332      // valid use-case where an init segment/data should be triggered without associated
68333      // frames. Leaving for now, but should be looked into.
68334
68335      if (frames.length) {
68336        segmentDuration = frames.length * frameDuration;
68337        this.trigger('segmentTimingInfo', generateSegmentTimingInfo(
68338        // The audio track's baseMediaDecodeTime is in audio clock cycles, but the
68339        // frame info is in video clock cycles. Convert to match expectation of
68340        // listeners (that all timestamps will be based on video clock cycles).
68341        clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate),
68342        // frame times are already in video clock, as is segment duration
68343        frames[0].dts, frames[0].pts, frames[0].dts + segmentDuration, frames[0].pts + segmentDuration, videoClockCyclesOfSilencePrefixed || 0));
68344        this.trigger('timingInfo', {
68345          start: frames[0].pts,
68346          end: frames[0].pts + segmentDuration
68347        });
68348      }
68349      this.trigger('data', {
68350        track: track,
68351        boxes: boxes
68352      });
68353      this.trigger('done', 'AudioSegmentStream');
68354    };
68355    this.reset = function () {
68356      trackDecodeInfo.clearDtsInfo(track);
68357      adtsFrames = [];
68358      this.trigger('reset');
68359    };
68360  };
68361  AudioSegmentStream.prototype = new Stream();
68362  /**
68363   * Constructs a single-track, ISO BMFF media segment from H264 data
68364   * events. The output of this stream can be fed to a SourceBuffer
68365   * configured with a suitable initialization segment.
68366   * @param track {object} track metadata configuration
68367   * @param options {object} transmuxer options object
68368   * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
68369   *        gopsToAlignWith list when attempting to align gop pts
68370   * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
68371   *        in the source; false to adjust the first segment to start at 0.
68372   */
68373
68374  VideoSegmentStream = function (track, options) {
68375    var sequenceNumber,
68376      nalUnits = [],
68377      gopsToAlignWith = [],
68378      config,
68379      pps;
68380    options = options || {};
68381    sequenceNumber = options.firstSequenceNumber || 0;
68382    VideoSegmentStream.prototype.init.call(this);
68383    delete track.minPTS;
68384    this.gopCache_ = [];
68385    /**
68386      * Constructs a ISO BMFF segment given H264 nalUnits
68387      * @param {Object} nalUnit A data event representing a nalUnit
68388      * @param {String} nalUnit.nalUnitType
68389      * @param {Object} nalUnit.config Properties for a mp4 track
68390      * @param {Uint8Array} nalUnit.data The nalUnit bytes
68391      * @see lib/codecs/h264.js
68392     **/
68393
68394    this.push = function (nalUnit) {
68395      trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config
68396
68397      if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
68398        config = nalUnit.config;
68399        track.sps = [nalUnit.data];
68400        VIDEO_PROPERTIES.forEach(function (prop) {
68401          track[prop] = config[prop];
68402        }, this);
68403      }
68404      if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
68405        pps = nalUnit.data;
68406        track.pps = [nalUnit.data];
68407      } // buffer video until flush() is called
68408
68409      nalUnits.push(nalUnit);
68410    };
68411    /**
68412      * Pass constructed ISO BMFF track and boxes on to the
68413      * next stream in the pipeline
68414     **/
68415
68416    this.flush = function () {
68417      var frames,
68418        gopForFusion,
68419        gops,
68420        moof,
68421        mdat,
68422        boxes,
68423        prependedContentDuration = 0,
68424        firstGop,
68425        lastGop; // Throw away nalUnits at the start of the byte stream until
68426      // we find the first AUD
68427
68428      while (nalUnits.length) {
68429        if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
68430          break;
68431        }
68432        nalUnits.shift();
68433      } // Return early if no video data has been observed
68434
68435      if (nalUnits.length === 0) {
68436        this.resetStream_();
68437        this.trigger('done', 'VideoSegmentStream');
68438        return;
68439      } // Organize the raw nal-units into arrays that represent
68440      // higher-level constructs such as frames and gops
68441      // (group-of-pictures)
68442
68443      frames = frameUtils.groupNalsIntoFrames(nalUnits);
68444      gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have
68445      // a problem since MSE (on Chrome) requires a leading keyframe.
68446      //
68447      // We have two approaches to repairing this situation:
68448      // 1) GOP-FUSION:
68449      //    This is where we keep track of the GOPS (group-of-pictures)
68450      //    from previous fragments and attempt to find one that we can
68451      //    prepend to the current fragment in order to create a valid
68452      //    fragment.
68453      // 2) KEYFRAME-PULLING:
68454      //    Here we search for the first keyframe in the fragment and
68455      //    throw away all the frames between the start of the fragment
68456      //    and that keyframe. We then extend the duration and pull the
68457      //    PTS of the keyframe forward so that it covers the time range
68458      //    of the frames that were disposed of.
68459      //
68460      // #1 is far prefereable over #2 which can cause "stuttering" but
68461      // requires more things to be just right.
68462
68463      if (!gops[0][0].keyFrame) {
68464        // Search for a gop for fusion from our gopCache
68465        gopForFusion = this.getGopForFusion_(nalUnits[0], track);
68466        if (gopForFusion) {
68467          // in order to provide more accurate timing information about the segment, save
68468          // the number of seconds prepended to the original segment due to GOP fusion
68469          prependedContentDuration = gopForFusion.duration;
68470          gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the
68471          // new gop at the beginning
68472
68473          gops.byteLength += gopForFusion.byteLength;
68474          gops.nalCount += gopForFusion.nalCount;
68475          gops.pts = gopForFusion.pts;
68476          gops.dts = gopForFusion.dts;
68477          gops.duration += gopForFusion.duration;
68478        } else {
68479          // If we didn't find a candidate gop fall back to keyframe-pulling
68480          gops = frameUtils.extendFirstKeyFrame(gops);
68481        }
68482      } // Trim gops to align with gopsToAlignWith
68483
68484      if (gopsToAlignWith.length) {
68485        var alignedGops;
68486        if (options.alignGopsAtEnd) {
68487          alignedGops = this.alignGopsAtEnd_(gops);
68488        } else {
68489          alignedGops = this.alignGopsAtStart_(gops);
68490        }
68491        if (!alignedGops) {
68492          // save all the nals in the last GOP into the gop cache
68493          this.gopCache_.unshift({
68494            gop: gops.pop(),
68495            pps: track.pps,
68496            sps: track.sps
68497          }); // Keep a maximum of 6 GOPs in the cache
68498
68499          this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
68500
68501          nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith
68502
68503          this.resetStream_();
68504          this.trigger('done', 'VideoSegmentStream');
68505          return;
68506        } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
68507        // when recalculated before sending off to CoalesceStream
68508
68509        trackDecodeInfo.clearDtsInfo(track);
68510        gops = alignedGops;
68511      }
68512      trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to
68513      // samples (that is, frames) in the video data
68514
68515      track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat
68516
68517      mdat = mp4.mdat(frameUtils.concatenateNalData(gops));
68518      track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
68519      this.trigger('processedGopsInfo', gops.map(function (gop) {
68520        return {
68521          pts: gop.pts,
68522          dts: gop.dts,
68523          byteLength: gop.byteLength
68524        };
68525      }));
68526      firstGop = gops[0];
68527      lastGop = gops[gops.length - 1];
68528      this.trigger('segmentTimingInfo', generateSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration));
68529      this.trigger('timingInfo', {
68530        start: gops[0].pts,
68531        end: gops[gops.length - 1].pts + gops[gops.length - 1].duration
68532      }); // save all the nals in the last GOP into the gop cache
68533
68534      this.gopCache_.unshift({
68535        gop: gops.pop(),
68536        pps: track.pps,
68537        sps: track.sps
68538      }); // Keep a maximum of 6 GOPs in the cache
68539
68540      this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits
68541
68542      nalUnits = [];
68543      this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
68544      this.trigger('timelineStartInfo', track.timelineStartInfo);
68545      moof = mp4.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of
68546      // throwing away hundreds of media segment fragments
68547
68548      boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time
68549
68550      sequenceNumber++;
68551      boxes.set(moof);
68552      boxes.set(mdat, moof.byteLength);
68553      this.trigger('data', {
68554        track: track,
68555        boxes: boxes
68556      });
68557      this.resetStream_(); // Continue with the flush process now
68558
68559      this.trigger('done', 'VideoSegmentStream');
68560    };
68561    this.reset = function () {
68562      this.resetStream_();
68563      nalUnits = [];
68564      this.gopCache_.length = 0;
68565      gopsToAlignWith.length = 0;
68566      this.trigger('reset');
68567    };
68568    this.resetStream_ = function () {
68569      trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments
68570      // for instance, when we are rendition switching
68571
68572      config = undefined;
68573      pps = undefined;
68574    }; // Search for a candidate Gop for gop-fusion from the gop cache and
68575    // return it or return null if no good candidate was found
68576
68577    this.getGopForFusion_ = function (nalUnit) {
68578      var halfSecond = 45000,
68579        // Half-a-second in a 90khz clock
68580        allowableOverlap = 10000,
68581        // About 3 frames @ 30fps
68582        nearestDistance = Infinity,
68583        dtsDistance,
68584        nearestGopObj,
68585        currentGop,
68586        currentGopObj,
68587        i; // Search for the GOP nearest to the beginning of this nal unit
68588
68589      for (i = 0; i < this.gopCache_.length; i++) {
68590        currentGopObj = this.gopCache_[i];
68591        currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS
68592
68593        if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
68594          continue;
68595        } // Reject Gops that would require a negative baseMediaDecodeTime
68596
68597        if (currentGop.dts < track.timelineStartInfo.dts) {
68598          continue;
68599        } // The distance between the end of the gop and the start of the nalUnit
68600
68601        dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within
68602        // a half-second of the nal unit
68603
68604        if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
68605          // Always use the closest GOP we found if there is more than
68606          // one candidate
68607          if (!nearestGopObj || nearestDistance > dtsDistance) {
68608            nearestGopObj = currentGopObj;
68609            nearestDistance = dtsDistance;
68610          }
68611        }
68612      }
68613      if (nearestGopObj) {
68614        return nearestGopObj.gop;
68615      }
68616      return null;
68617    }; // trim gop list to the first gop found that has a matching pts with a gop in the list
68618    // of gopsToAlignWith starting from the START of the list
68619
68620    this.alignGopsAtStart_ = function (gops) {
68621      var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
68622      byteLength = gops.byteLength;
68623      nalCount = gops.nalCount;
68624      duration = gops.duration;
68625      alignIndex = gopIndex = 0;
68626      while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
68627        align = gopsToAlignWith[alignIndex];
68628        gop = gops[gopIndex];
68629        if (align.pts === gop.pts) {
68630          break;
68631        }
68632        if (gop.pts > align.pts) {
68633          // this current gop starts after the current gop we want to align on, so increment
68634          // align index
68635          alignIndex++;
68636          continue;
68637        } // current gop starts before the current gop we want to align on. so increment gop
68638        // index
68639
68640        gopIndex++;
68641        byteLength -= gop.byteLength;
68642        nalCount -= gop.nalCount;
68643        duration -= gop.duration;
68644      }
68645      if (gopIndex === 0) {
68646        // no gops to trim
68647        return gops;
68648      }
68649      if (gopIndex === gops.length) {
68650        // all gops trimmed, skip appending all gops
68651        return null;
68652      }
68653      alignedGops = gops.slice(gopIndex);
68654      alignedGops.byteLength = byteLength;
68655      alignedGops.duration = duration;
68656      alignedGops.nalCount = nalCount;
68657      alignedGops.pts = alignedGops[0].pts;
68658      alignedGops.dts = alignedGops[0].dts;
68659      return alignedGops;
68660    }; // trim gop list to the first gop found that has a matching pts with a gop in the list
68661    // of gopsToAlignWith starting from the END of the list
68662
68663    this.alignGopsAtEnd_ = function (gops) {
68664      var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
68665      alignIndex = gopsToAlignWith.length - 1;
68666      gopIndex = gops.length - 1;
68667      alignEndIndex = null;
68668      matchFound = false;
68669      while (alignIndex >= 0 && gopIndex >= 0) {
68670        align = gopsToAlignWith[alignIndex];
68671        gop = gops[gopIndex];
68672        if (align.pts === gop.pts) {
68673          matchFound = true;
68674          break;
68675        }
68676        if (align.pts > gop.pts) {
68677          alignIndex--;
68678          continue;
68679        }
68680        if (alignIndex === gopsToAlignWith.length - 1) {
68681          // gop.pts is greater than the last alignment candidate. If no match is found
68682          // by the end of this loop, we still want to append gops that come after this
68683          // point
68684          alignEndIndex = gopIndex;
68685        }
68686        gopIndex--;
68687      }
68688      if (!matchFound && alignEndIndex === null) {
68689        return null;
68690      }
68691      var trimIndex;
68692      if (matchFound) {
68693        trimIndex = gopIndex;
68694      } else {
68695        trimIndex = alignEndIndex;
68696      }
68697      if (trimIndex === 0) {
68698        return gops;
68699      }
68700      var alignedGops = gops.slice(trimIndex);
68701      var metadata = alignedGops.reduce(function (total, gop) {
68702        total.byteLength += gop.byteLength;
68703        total.duration += gop.duration;
68704        total.nalCount += gop.nalCount;
68705        return total;
68706      }, {
68707        byteLength: 0,
68708        duration: 0,
68709        nalCount: 0
68710      });
68711      alignedGops.byteLength = metadata.byteLength;
68712      alignedGops.duration = metadata.duration;
68713      alignedGops.nalCount = metadata.nalCount;
68714      alignedGops.pts = alignedGops[0].pts;
68715      alignedGops.dts = alignedGops[0].dts;
68716      return alignedGops;
68717    };
68718    this.alignGopsWith = function (newGopsToAlignWith) {
68719      gopsToAlignWith = newGopsToAlignWith;
68720    };
68721  };
68722  VideoSegmentStream.prototype = new Stream();
68723  /**
68724   * A Stream that can combine multiple streams (ie. audio & video)
68725   * into a single output segment for MSE. Also supports audio-only
68726   * and video-only streams.
68727   * @param options {object} transmuxer options object
68728   * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
68729   *        in the source; false to adjust the first segment to start at media timeline start.
68730   */
68731
68732  CoalesceStream = function (options, metadataStream) {
68733    // Number of Tracks per output segment
68734    // If greater than 1, we combine multiple
68735    // tracks into a single segment
68736    this.numberOfTracks = 0;
68737    this.metadataStream = metadataStream;
68738    options = options || {};
68739    if (typeof options.remux !== 'undefined') {
68740      this.remuxTracks = !!options.remux;
68741    } else {
68742      this.remuxTracks = true;
68743    }
68744    if (typeof options.keepOriginalTimestamps === 'boolean') {
68745      this.keepOriginalTimestamps = options.keepOriginalTimestamps;
68746    } else {
68747      this.keepOriginalTimestamps = false;
68748    }
68749    this.pendingTracks = [];
68750    this.videoTrack = null;
68751    this.pendingBoxes = [];
68752    this.pendingCaptions = [];
68753    this.pendingMetadata = [];
68754    this.pendingBytes = 0;
68755    this.emittedTracks = 0;
68756    CoalesceStream.prototype.init.call(this); // Take output from multiple
68757
68758    this.push = function (output) {
68759      // buffer incoming captions until the associated video segment
68760      // finishes
68761      if (output.content || output.text) {
68762        return this.pendingCaptions.push(output);
68763      } // buffer incoming id3 tags until the final flush
68764
68765      if (output.frames) {
68766        return this.pendingMetadata.push(output);
68767      } // Add this track to the list of pending tracks and store
68768      // important information required for the construction of
68769      // the final segment
68770
68771      this.pendingTracks.push(output.track);
68772      this.pendingBytes += output.boxes.byteLength; // TODO: is there an issue for this against chrome?
68773      // We unshift audio and push video because
68774      // as of Chrome 75 when switching from
68775      // one init segment to another if the video
68776      // mdat does not appear after the audio mdat
68777      // only audio will play for the duration of our transmux.
68778
68779      if (output.track.type === 'video') {
68780        this.videoTrack = output.track;
68781        this.pendingBoxes.push(output.boxes);
68782      }
68783      if (output.track.type === 'audio') {
68784        this.audioTrack = output.track;
68785        this.pendingBoxes.unshift(output.boxes);
68786      }
68787    };
68788  };
68789  CoalesceStream.prototype = new Stream();
68790  CoalesceStream.prototype.flush = function (flushSource) {
68791    var offset = 0,
68792      event = {
68793        captions: [],
68794        captionStreams: {},
68795        metadata: [],
68796        info: {}
68797      },
68798      caption,
68799      id3,
68800      initSegment,
68801      timelineStartPts = 0,
68802      i;
68803    if (this.pendingTracks.length < this.numberOfTracks) {
68804      if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
68805        // Return because we haven't received a flush from a data-generating
68806        // portion of the segment (meaning that we have only recieved meta-data
68807        // or captions.)
68808        return;
68809      } else if (this.remuxTracks) {
68810        // Return until we have enough tracks from the pipeline to remux (if we
68811        // are remuxing audio and video into a single MP4)
68812        return;
68813      } else if (this.pendingTracks.length === 0) {
68814        // In the case where we receive a flush without any data having been
68815        // received we consider it an emitted track for the purposes of coalescing
68816        // `done` events.
68817        // We do this for the case where there is an audio and video track in the
68818        // segment but no audio data. (seen in several playlists with alternate
68819        // audio tracks and no audio present in the main TS segments.)
68820        this.emittedTracks++;
68821        if (this.emittedTracks >= this.numberOfTracks) {
68822          this.trigger('done');
68823          this.emittedTracks = 0;
68824        }
68825        return;
68826      }
68827    }
68828    if (this.videoTrack) {
68829      timelineStartPts = this.videoTrack.timelineStartInfo.pts;
68830      VIDEO_PROPERTIES.forEach(function (prop) {
68831        event.info[prop] = this.videoTrack[prop];
68832      }, this);
68833    } else if (this.audioTrack) {
68834      timelineStartPts = this.audioTrack.timelineStartInfo.pts;
68835      AUDIO_PROPERTIES.forEach(function (prop) {
68836        event.info[prop] = this.audioTrack[prop];
68837      }, this);
68838    }
68839    if (this.videoTrack || this.audioTrack) {
68840      if (this.pendingTracks.length === 1) {
68841        event.type = this.pendingTracks[0].type;
68842      } else {
68843        event.type = 'combined';
68844      }
68845      this.emittedTracks += this.pendingTracks.length;
68846      initSegment = mp4.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment
68847
68848      event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov
68849      // and track definitions
68850
68851      event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats
68852
68853      event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together
68854
68855      for (i = 0; i < this.pendingBoxes.length; i++) {
68856        event.data.set(this.pendingBoxes[i], offset);
68857        offset += this.pendingBoxes[i].byteLength;
68858      } // Translate caption PTS times into second offsets to match the
68859      // video timeline for the segment, and add track info
68860
68861      for (i = 0; i < this.pendingCaptions.length; i++) {
68862        caption = this.pendingCaptions[i];
68863        caption.startTime = clock.metadataTsToSeconds(caption.startPts, timelineStartPts, this.keepOriginalTimestamps);
68864        caption.endTime = clock.metadataTsToSeconds(caption.endPts, timelineStartPts, this.keepOriginalTimestamps);
68865        event.captionStreams[caption.stream] = true;
68866        event.captions.push(caption);
68867      } // Translate ID3 frame PTS times into second offsets to match the
68868      // video timeline for the segment
68869
68870      for (i = 0; i < this.pendingMetadata.length; i++) {
68871        id3 = this.pendingMetadata[i];
68872        id3.cueTime = clock.metadataTsToSeconds(id3.pts, timelineStartPts, this.keepOriginalTimestamps);
68873        event.metadata.push(id3);
68874      } // We add this to every single emitted segment even though we only need
68875      // it for the first
68876
68877      event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
68878
68879      this.pendingTracks.length = 0;
68880      this.videoTrack = null;
68881      this.pendingBoxes.length = 0;
68882      this.pendingCaptions.length = 0;
68883      this.pendingBytes = 0;
68884      this.pendingMetadata.length = 0; // Emit the built segment
68885      // We include captions and ID3 tags for backwards compatibility,
68886      // ideally we should send only video and audio in the data event
68887
68888      this.trigger('data', event); // Emit each caption to the outside world
68889      // Ideally, this would happen immediately on parsing captions,
68890      // but we need to ensure that video data is sent back first
68891      // so that caption timing can be adjusted to match video timing
68892
68893      for (i = 0; i < event.captions.length; i++) {
68894        caption = event.captions[i];
68895        this.trigger('caption', caption);
68896      } // Emit each id3 tag to the outside world
68897      // Ideally, this would happen immediately on parsing the tag,
68898      // but we need to ensure that video data is sent back first
68899      // so that ID3 frame timing can be adjusted to match video timing
68900
68901      for (i = 0; i < event.metadata.length; i++) {
68902        id3 = event.metadata[i];
68903        this.trigger('id3Frame', id3);
68904      }
68905    } // Only emit `done` if all tracks have been flushed and emitted
68906
68907    if (this.emittedTracks >= this.numberOfTracks) {
68908      this.trigger('done');
68909      this.emittedTracks = 0;
68910    }
68911  };
68912  CoalesceStream.prototype.setRemux = function (val) {
68913    this.remuxTracks = val;
68914  };
68915  /**
68916   * A Stream that expects MP2T binary data as input and produces
68917   * corresponding media segments, suitable for use with Media Source
68918   * Extension (MSE) implementations that support the ISO BMFF byte
68919   * stream format, like Chrome.
68920   */
68921
68922  Transmuxer = function (options) {
68923    var self = this,
68924      hasFlushed = true,
68925      videoTrack,
68926      audioTrack;
68927    Transmuxer.prototype.init.call(this);
68928    options = options || {};
68929    this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
68930    this.transmuxPipeline_ = {};
68931    this.setupAacPipeline = function () {
68932      var pipeline = {};
68933      this.transmuxPipeline_ = pipeline;
68934      pipeline.type = 'aac';
68935      pipeline.metadataStream = new m2ts.MetadataStream(); // set up the parsing pipeline
68936
68937      pipeline.aacStream = new AacStream();
68938      pipeline.audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio');
68939      pipeline.timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream('timed-metadata');
68940      pipeline.adtsStream = new AdtsStream();
68941      pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
68942      pipeline.headOfPipeline = pipeline.aacStream;
68943      pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
68944      pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
68945      pipeline.metadataStream.on('timestamp', function (frame) {
68946        pipeline.aacStream.setTimestamp(frame.timeStamp);
68947      });
68948      pipeline.aacStream.on('data', function (data) {
68949        if (data.type !== 'timed-metadata' && data.type !== 'audio' || pipeline.audioSegmentStream) {
68950          return;
68951        }
68952        audioTrack = audioTrack || {
68953          timelineStartInfo: {
68954            baseMediaDecodeTime: self.baseMediaDecodeTime
68955          },
68956          codec: 'adts',
68957          type: 'audio'
68958        }; // hook up the audio segment stream to the first track with aac data
68959
68960        pipeline.coalesceStream.numberOfTracks++;
68961        pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack, options);
68962        pipeline.audioSegmentStream.on('log', self.getLogTrigger_('audioSegmentStream'));
68963        pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline
68964
68965        pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); // emit pmt info
68966
68967        self.trigger('trackinfo', {
68968          hasAudio: !!audioTrack,
68969          hasVideo: !!videoTrack
68970        });
68971      }); // Re-emit any data coming from the coalesce stream to the outside world
68972
68973      pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline
68974
68975      pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
68976      addPipelineLogRetriggers(this, pipeline);
68977    };
68978    this.setupTsPipeline = function () {
68979      var pipeline = {};
68980      this.transmuxPipeline_ = pipeline;
68981      pipeline.type = 'ts';
68982      pipeline.metadataStream = new m2ts.MetadataStream(); // set up the parsing pipeline
68983
68984      pipeline.packetStream = new m2ts.TransportPacketStream();
68985      pipeline.parseStream = new m2ts.TransportParseStream();
68986      pipeline.elementaryStream = new m2ts.ElementaryStream();
68987      pipeline.timestampRolloverStream = new m2ts.TimestampRolloverStream();
68988      pipeline.adtsStream = new AdtsStream();
68989      pipeline.h264Stream = new H264Stream();
68990      pipeline.captionStream = new m2ts.CaptionStream(options);
68991      pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
68992      pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams
68993
68994      pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream).pipe(pipeline.timestampRolloverStream); // !!THIS ORDER IS IMPORTANT!!
68995      // demux the streams
68996
68997      pipeline.timestampRolloverStream.pipe(pipeline.h264Stream);
68998      pipeline.timestampRolloverStream.pipe(pipeline.adtsStream);
68999      pipeline.timestampRolloverStream.pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream
69000
69001      pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
69002      pipeline.elementaryStream.on('data', function (data) {
69003        var i;
69004        if (data.type === 'metadata') {
69005          i = data.tracks.length; // scan the tracks listed in the metadata
69006
69007          while (i--) {
69008            if (!videoTrack && data.tracks[i].type === 'video') {
69009              videoTrack = data.tracks[i];
69010              videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
69011            } else if (!audioTrack && data.tracks[i].type === 'audio') {
69012              audioTrack = data.tracks[i];
69013              audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
69014            }
69015          } // hook up the video segment stream to the first track with h264 data
69016
69017          if (videoTrack && !pipeline.videoSegmentStream) {
69018            pipeline.coalesceStream.numberOfTracks++;
69019            pipeline.videoSegmentStream = new VideoSegmentStream(videoTrack, options);
69020            pipeline.videoSegmentStream.on('log', self.getLogTrigger_('videoSegmentStream'));
69021            pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
69022              // When video emits timelineStartInfo data after a flush, we forward that
69023              // info to the AudioSegmentStream, if it exists, because video timeline
69024              // data takes precedence.  Do not do this if keepOriginalTimestamps is set,
69025              // because this is a particularly subtle form of timestamp alteration.
69026              if (audioTrack && !options.keepOriginalTimestamps) {
69027                audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the
69028                // very earliest DTS we have seen in video because Chrome will
69029                // interpret any video track with a baseMediaDecodeTime that is
69030                // non-zero as a gap.
69031
69032                pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts - self.baseMediaDecodeTime);
69033              }
69034            });
69035            pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
69036            pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo'));
69037            pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
69038              if (audioTrack) {
69039                pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
69040              }
69041            });
69042            pipeline.videoSegmentStream.on('timingInfo', self.trigger.bind(self, 'videoTimingInfo')); // Set up the final part of the video pipeline
69043
69044            pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
69045          }
69046          if (audioTrack && !pipeline.audioSegmentStream) {
69047            // hook up the audio segment stream to the first track with aac data
69048            pipeline.coalesceStream.numberOfTracks++;
69049            pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack, options);
69050            pipeline.audioSegmentStream.on('log', self.getLogTrigger_('audioSegmentStream'));
69051            pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo'));
69052            pipeline.audioSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'audioSegmentTimingInfo')); // Set up the final part of the audio pipeline
69053
69054            pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
69055          } // emit pmt info
69056
69057          self.trigger('trackinfo', {
69058            hasAudio: !!audioTrack,
69059            hasVideo: !!videoTrack
69060          });
69061        }
69062      }); // Re-emit any data coming from the coalesce stream to the outside world
69063
69064      pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
69065      pipeline.coalesceStream.on('id3Frame', function (id3Frame) {
69066        id3Frame.dispatchType = pipeline.metadataStream.dispatchType;
69067        self.trigger('id3Frame', id3Frame);
69068      });
69069      pipeline.coalesceStream.on('caption', this.trigger.bind(this, 'caption')); // Let the consumer know we have finished flushing the entire pipeline
69070
69071      pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
69072      addPipelineLogRetriggers(this, pipeline);
69073    }; // hook up the segment streams once track metadata is delivered
69074
69075    this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
69076      var pipeline = this.transmuxPipeline_;
69077      if (!options.keepOriginalTimestamps) {
69078        this.baseMediaDecodeTime = baseMediaDecodeTime;
69079      }
69080      if (audioTrack) {
69081        audioTrack.timelineStartInfo.dts = undefined;
69082        audioTrack.timelineStartInfo.pts = undefined;
69083        trackDecodeInfo.clearDtsInfo(audioTrack);
69084        if (pipeline.audioTimestampRolloverStream) {
69085          pipeline.audioTimestampRolloverStream.discontinuity();
69086        }
69087      }
69088      if (videoTrack) {
69089        if (pipeline.videoSegmentStream) {
69090          pipeline.videoSegmentStream.gopCache_ = [];
69091        }
69092        videoTrack.timelineStartInfo.dts = undefined;
69093        videoTrack.timelineStartInfo.pts = undefined;
69094        trackDecodeInfo.clearDtsInfo(videoTrack);
69095        pipeline.captionStream.reset();
69096      }
69097      if (pipeline.timestampRolloverStream) {
69098        pipeline.timestampRolloverStream.discontinuity();
69099      }
69100    };
69101    this.setAudioAppendStart = function (timestamp) {
69102      if (audioTrack) {
69103        this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
69104      }
69105    };
69106    this.setRemux = function (val) {
69107      var pipeline = this.transmuxPipeline_;
69108      options.remux = val;
69109      if (pipeline && pipeline.coalesceStream) {
69110        pipeline.coalesceStream.setRemux(val);
69111      }
69112    };
69113    this.alignGopsWith = function (gopsToAlignWith) {
69114      if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
69115        this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
69116      }
69117    };
69118    this.getLogTrigger_ = function (key) {
69119      var self = this;
69120      return function (event) {
69121        event.stream = key;
69122        self.trigger('log', event);
69123      };
69124    }; // feed incoming data to the front of the parsing pipeline
69125
69126    this.push = function (data) {
69127      if (hasFlushed) {
69128        var isAac = isLikelyAacData(data);
69129        if (isAac && this.transmuxPipeline_.type !== 'aac') {
69130          this.setupAacPipeline();
69131        } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
69132          this.setupTsPipeline();
69133        }
69134        hasFlushed = false;
69135      }
69136      this.transmuxPipeline_.headOfPipeline.push(data);
69137    }; // flush any buffered data
69138
69139    this.flush = function () {
69140      hasFlushed = true; // Start at the top of the pipeline and flush all pending work
69141
69142      this.transmuxPipeline_.headOfPipeline.flush();
69143    };
69144    this.endTimeline = function () {
69145      this.transmuxPipeline_.headOfPipeline.endTimeline();
69146    };
69147    this.reset = function () {
69148      if (this.transmuxPipeline_.headOfPipeline) {
69149        this.transmuxPipeline_.headOfPipeline.reset();
69150      }
69151    }; // Caption data has to be reset when seeking outside buffered range
69152
69153    this.resetCaptions = function () {
69154      if (this.transmuxPipeline_.captionStream) {
69155        this.transmuxPipeline_.captionStream.reset();
69156      }
69157    };
69158  };
69159  Transmuxer.prototype = new Stream();
69160  var transmuxer = {
69161    Transmuxer: Transmuxer,
69162    VideoSegmentStream: VideoSegmentStream,
69163    AudioSegmentStream: AudioSegmentStream,
69164    AUDIO_PROPERTIES: AUDIO_PROPERTIES,
69165    VIDEO_PROPERTIES: VIDEO_PROPERTIES,
69166    // exported for testing
69167    generateSegmentTimingInfo: generateSegmentTimingInfo
69168  };
69169  /**
69170   * mux.js
69171   *
69172   * Copyright (c) Brightcove
69173   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
69174   */
69175
69176  var toUnsigned$3 = function (value) {
69177    return value >>> 0;
69178  };
69179  var toHexString$1 = function (value) {
69180    return ('00' + value.toString(16)).slice(-2);
69181  };
69182  var bin = {
69183    toUnsigned: toUnsigned$3,
69184    toHexString: toHexString$1
69185  };
69186  var parseType$4 = function (buffer) {
69187    var result = '';
69188    result += String.fromCharCode(buffer[0]);
69189    result += String.fromCharCode(buffer[1]);
69190    result += String.fromCharCode(buffer[2]);
69191    result += String.fromCharCode(buffer[3]);
69192    return result;
69193  };
69194  var parseType_1 = parseType$4;
69195  var toUnsigned$2 = bin.toUnsigned;
69196  var parseType$3 = parseType_1;
69197  var findBox$5 = function (data, path) {
69198    var results = [],
69199      i,
69200      size,
69201      type,
69202      end,
69203      subresults;
69204    if (!path.length) {
69205      // short-circuit the search for empty paths
69206      return null;
69207    }
69208    for (i = 0; i < data.byteLength;) {
69209      size = toUnsigned$2(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
69210      type = parseType$3(data.subarray(i + 4, i + 8));
69211      end = size > 1 ? i + size : data.byteLength;
69212      if (type === path[0]) {
69213        if (path.length === 1) {
69214          // this is the end of the path and we've found the box we were
69215          // looking for
69216          results.push(data.subarray(i + 8, end));
69217        } else {
69218          // recursively search for the next box along the path
69219          subresults = findBox$5(data.subarray(i + 8, end), path.slice(1));
69220          if (subresults.length) {
69221            results = results.concat(subresults);
69222          }
69223        }
69224      }
69225      i = end;
69226    } // we've finished searching all of data
69227
69228    return results;
69229  };
69230  var findBox_1 = findBox$5;
69231  var toUnsigned$1 = bin.toUnsigned;
69232  var getUint64$4 = numbers.getUint64;
69233  var tfdt = function (data) {
69234    var result = {
69235      version: data[0],
69236      flags: new Uint8Array(data.subarray(1, 4))
69237    };
69238    if (result.version === 1) {
69239      result.baseMediaDecodeTime = getUint64$4(data.subarray(4));
69240    } else {
69241      result.baseMediaDecodeTime = toUnsigned$1(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]);
69242    }
69243    return result;
69244  };
69245  var parseTfdt$3 = tfdt;
69246  var tfhd = function (data) {
69247    var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69248      result = {
69249        version: data[0],
69250        flags: new Uint8Array(data.subarray(1, 4)),
69251        trackId: view.getUint32(4)
69252      },
69253      baseDataOffsetPresent = result.flags[2] & 0x01,
69254      sampleDescriptionIndexPresent = result.flags[2] & 0x02,
69255      defaultSampleDurationPresent = result.flags[2] & 0x08,
69256      defaultSampleSizePresent = result.flags[2] & 0x10,
69257      defaultSampleFlagsPresent = result.flags[2] & 0x20,
69258      durationIsEmpty = result.flags[0] & 0x010000,
69259      defaultBaseIsMoof = result.flags[0] & 0x020000,
69260      i;
69261    i = 8;
69262    if (baseDataOffsetPresent) {
69263      i += 4; // truncate top 4 bytes
69264      // FIXME: should we read the full 64 bits?
69265
69266      result.baseDataOffset = view.getUint32(12);
69267      i += 4;
69268    }
69269    if (sampleDescriptionIndexPresent) {
69270      result.sampleDescriptionIndex = view.getUint32(i);
69271      i += 4;
69272    }
69273    if (defaultSampleDurationPresent) {
69274      result.defaultSampleDuration = view.getUint32(i);
69275      i += 4;
69276    }
69277    if (defaultSampleSizePresent) {
69278      result.defaultSampleSize = view.getUint32(i);
69279      i += 4;
69280    }
69281    if (defaultSampleFlagsPresent) {
69282      result.defaultSampleFlags = view.getUint32(i);
69283    }
69284    if (durationIsEmpty) {
69285      result.durationIsEmpty = true;
69286    }
69287    if (!baseDataOffsetPresent && defaultBaseIsMoof) {
69288      result.baseDataOffsetIsMoof = true;
69289    }
69290    return result;
69291  };
69292  var parseTfhd$2 = tfhd;
69293  var getUint64$3 = numbers.getUint64;
69294  var parseSidx = function (data) {
69295    var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69296      result = {
69297        version: data[0],
69298        flags: new Uint8Array(data.subarray(1, 4)),
69299        references: [],
69300        referenceId: view.getUint32(4),
69301        timescale: view.getUint32(8)
69302      },
69303      i = 12;
69304    if (result.version === 0) {
69305      result.earliestPresentationTime = view.getUint32(i);
69306      result.firstOffset = view.getUint32(i + 4);
69307      i += 8;
69308    } else {
69309      // read 64 bits
69310      result.earliestPresentationTime = getUint64$3(data.subarray(i));
69311      result.firstOffset = getUint64$3(data.subarray(i + 8));
69312      i += 16;
69313    }
69314    i += 2; // reserved
69315
69316    var referenceCount = view.getUint16(i);
69317    i += 2; // start of references
69318
69319    for (; referenceCount > 0; i += 12, referenceCount--) {
69320      result.references.push({
69321        referenceType: (data[i] & 0x80) >>> 7,
69322        referencedSize: view.getUint32(i) & 0x7FFFFFFF,
69323        subsegmentDuration: view.getUint32(i + 4),
69324        startsWithSap: !!(data[i + 8] & 0x80),
69325        sapType: (data[i + 8] & 0x70) >>> 4,
69326        sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
69327      });
69328    }
69329    return result;
69330  };
69331  var parseSidx_1 = parseSidx;
69332  var parseSampleFlags$1 = function (flags) {
69333    return {
69334      isLeading: (flags[0] & 0x0c) >>> 2,
69335      dependsOn: flags[0] & 0x03,
69336      isDependedOn: (flags[1] & 0xc0) >>> 6,
69337      hasRedundancy: (flags[1] & 0x30) >>> 4,
69338      paddingValue: (flags[1] & 0x0e) >>> 1,
69339      isNonSyncSample: flags[1] & 0x01,
69340      degradationPriority: flags[2] << 8 | flags[3]
69341    };
69342  };
69343  var parseSampleFlags_1 = parseSampleFlags$1;
69344  var parseSampleFlags = parseSampleFlags_1;
69345  var trun = function (data) {
69346    var result = {
69347        version: data[0],
69348        flags: new Uint8Array(data.subarray(1, 4)),
69349        samples: []
69350      },
69351      view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69352      // Flag interpretation
69353      dataOffsetPresent = result.flags[2] & 0x01,
69354      // compare with 2nd byte of 0x1
69355      firstSampleFlagsPresent = result.flags[2] & 0x04,
69356      // compare with 2nd byte of 0x4
69357      sampleDurationPresent = result.flags[1] & 0x01,
69358      // compare with 2nd byte of 0x100
69359      sampleSizePresent = result.flags[1] & 0x02,
69360      // compare with 2nd byte of 0x200
69361      sampleFlagsPresent = result.flags[1] & 0x04,
69362      // compare with 2nd byte of 0x400
69363      sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
69364      // compare with 2nd byte of 0x800
69365      sampleCount = view.getUint32(4),
69366      offset = 8,
69367      sample;
69368    if (dataOffsetPresent) {
69369      // 32 bit signed integer
69370      result.dataOffset = view.getInt32(offset);
69371      offset += 4;
69372    } // Overrides the flags for the first sample only. The order of
69373    // optional values will be: duration, size, compositionTimeOffset
69374
69375    if (firstSampleFlagsPresent && sampleCount) {
69376      sample = {
69377        flags: parseSampleFlags(data.subarray(offset, offset + 4))
69378      };
69379      offset += 4;
69380      if (sampleDurationPresent) {
69381        sample.duration = view.getUint32(offset);
69382        offset += 4;
69383      }
69384      if (sampleSizePresent) {
69385        sample.size = view.getUint32(offset);
69386        offset += 4;
69387      }
69388      if (sampleCompositionTimeOffsetPresent) {
69389        if (result.version === 1) {
69390          sample.compositionTimeOffset = view.getInt32(offset);
69391        } else {
69392          sample.compositionTimeOffset = view.getUint32(offset);
69393        }
69394        offset += 4;
69395      }
69396      result.samples.push(sample);
69397      sampleCount--;
69398    }
69399    while (sampleCount--) {
69400      sample = {};
69401      if (sampleDurationPresent) {
69402        sample.duration = view.getUint32(offset);
69403        offset += 4;
69404      }
69405      if (sampleSizePresent) {
69406        sample.size = view.getUint32(offset);
69407        offset += 4;
69408      }
69409      if (sampleFlagsPresent) {
69410        sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
69411        offset += 4;
69412      }
69413      if (sampleCompositionTimeOffsetPresent) {
69414        if (result.version === 1) {
69415          sample.compositionTimeOffset = view.getInt32(offset);
69416        } else {
69417          sample.compositionTimeOffset = view.getUint32(offset);
69418        }
69419        offset += 4;
69420      }
69421      result.samples.push(sample);
69422    }
69423    return result;
69424  };
69425  var parseTrun$2 = trun;
69426  /**
69427   * mux.js
69428   *
69429   * Copyright (c) Brightcove
69430   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
69431   *
69432   * Parse the internal MP4 structure into an equivalent javascript
69433   * object.
69434   */
69435
69436  var numberHelpers = numbers;
69437  var getUint64$2 = numberHelpers.getUint64;
69438  var inspectMp4,
69439    textifyMp4,
69440    parseMp4Date = function (seconds) {
69441      return new Date(seconds * 1000 - 2082844800000);
69442    },
69443    parseType$2 = parseType_1,
69444    findBox$4 = findBox_1,
69445    nalParse = function (avcStream) {
69446      var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
69447        result = [],
69448        i,
69449        length;
69450      for (i = 0; i + 4 < avcStream.length; i += length) {
69451        length = avcView.getUint32(i);
69452        i += 4; // bail if this doesn't appear to be an H264 stream
69453
69454        if (length <= 0) {
69455          result.push('<span style=\'color:red;\'>MALFORMED DATA</span>');
69456          continue;
69457        }
69458        switch (avcStream[i] & 0x1F) {
69459          case 0x01:
69460            result.push('slice_layer_without_partitioning_rbsp');
69461            break;
69462          case 0x05:
69463            result.push('slice_layer_without_partitioning_rbsp_idr');
69464            break;
69465          case 0x06:
69466            result.push('sei_rbsp');
69467            break;
69468          case 0x07:
69469            result.push('seq_parameter_set_rbsp');
69470            break;
69471          case 0x08:
69472            result.push('pic_parameter_set_rbsp');
69473            break;
69474          case 0x09:
69475            result.push('access_unit_delimiter_rbsp');
69476            break;
69477          default:
69478            result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F);
69479            break;
69480        }
69481      }
69482      return result;
69483    },
69484    // registry of handlers for individual mp4 box types
69485    parse = {
69486      // codingname, not a first-class box type. stsd entries share the
69487      // same format as real boxes so the parsing infrastructure can be
69488      // shared
69489      avc1: function (data) {
69490        var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
69491        return {
69492          dataReferenceIndex: view.getUint16(6),
69493          width: view.getUint16(24),
69494          height: view.getUint16(26),
69495          horizresolution: view.getUint16(28) + view.getUint16(30) / 16,
69496          vertresolution: view.getUint16(32) + view.getUint16(34) / 16,
69497          frameCount: view.getUint16(40),
69498          depth: view.getUint16(74),
69499          config: inspectMp4(data.subarray(78, data.byteLength))
69500        };
69501      },
69502      avcC: function (data) {
69503        var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69504          result = {
69505            configurationVersion: data[0],
69506            avcProfileIndication: data[1],
69507            profileCompatibility: data[2],
69508            avcLevelIndication: data[3],
69509            lengthSizeMinusOne: data[4] & 0x03,
69510            sps: [],
69511            pps: []
69512          },
69513          numOfSequenceParameterSets = data[5] & 0x1f,
69514          numOfPictureParameterSets,
69515          nalSize,
69516          offset,
69517          i; // iterate past any SPSs
69518
69519        offset = 6;
69520        for (i = 0; i < numOfSequenceParameterSets; i++) {
69521          nalSize = view.getUint16(offset);
69522          offset += 2;
69523          result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
69524          offset += nalSize;
69525        } // iterate past any PPSs
69526
69527        numOfPictureParameterSets = data[offset];
69528        offset++;
69529        for (i = 0; i < numOfPictureParameterSets; i++) {
69530          nalSize = view.getUint16(offset);
69531          offset += 2;
69532          result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
69533          offset += nalSize;
69534        }
69535        return result;
69536      },
69537      btrt: function (data) {
69538        var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
69539        return {
69540          bufferSizeDB: view.getUint32(0),
69541          maxBitrate: view.getUint32(4),
69542          avgBitrate: view.getUint32(8)
69543        };
69544      },
69545      edts: function edts(data) {
69546        return {
69547          boxes: inspectMp4(data)
69548        };
69549      },
69550      elst: function elst(data) {
69551        var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69552          result = {
69553            version: view.getUint8(0),
69554            flags: new Uint8Array(data.subarray(1, 4)),
69555            edits: []
69556          },
69557          entryCount = view.getUint32(4),
69558          i;
69559        for (i = 8; entryCount; entryCount--) {
69560          if (result.version === 0) {
69561            result.edits.push({
69562              segmentDuration: view.getUint32(i),
69563              mediaTime: view.getInt32(i + 4),
69564              mediaRate: view.getUint16(i + 8) + view.getUint16(i + 10) / (256 * 256)
69565            });
69566            i += 12;
69567          } else {
69568            result.edits.push({
69569              segmentDuration: getUint64$2(data.subarray(i)),
69570              mediaTime: getUint64$2(data.subarray(i + 8)),
69571              mediaRate: view.getUint16(i + 16) + view.getUint16(i + 18) / (256 * 256)
69572            });
69573            i += 20;
69574          }
69575        }
69576        return result;
69577      },
69578      esds: function (data) {
69579        return {
69580          version: data[0],
69581          flags: new Uint8Array(data.subarray(1, 4)),
69582          esId: data[6] << 8 | data[7],
69583          streamPriority: data[8] & 0x1f,
69584          decoderConfig: {
69585            objectProfileIndication: data[11],
69586            streamType: data[12] >>> 2 & 0x3f,
69587            bufferSize: data[13] << 16 | data[14] << 8 | data[15],
69588            maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
69589            avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23],
69590            decoderConfigDescriptor: {
69591              tag: data[24],
69592              length: data[25],
69593              audioObjectType: data[26] >>> 3 & 0x1f,
69594              samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01,
69595              channelConfiguration: data[27] >>> 3 & 0x0f
69596            }
69597          }
69598        };
69599      },
69600      ftyp: function (data) {
69601        var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69602          result = {
69603            majorBrand: parseType$2(data.subarray(0, 4)),
69604            minorVersion: view.getUint32(4),
69605            compatibleBrands: []
69606          },
69607          i = 8;
69608        while (i < data.byteLength) {
69609          result.compatibleBrands.push(parseType$2(data.subarray(i, i + 4)));
69610          i += 4;
69611        }
69612        return result;
69613      },
69614      dinf: function (data) {
69615        return {
69616          boxes: inspectMp4(data)
69617        };
69618      },
69619      dref: function (data) {
69620        return {
69621          version: data[0],
69622          flags: new Uint8Array(data.subarray(1, 4)),
69623          dataReferences: inspectMp4(data.subarray(8))
69624        };
69625      },
69626      hdlr: function (data) {
69627        var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69628          result = {
69629            version: view.getUint8(0),
69630            flags: new Uint8Array(data.subarray(1, 4)),
69631            handlerType: parseType$2(data.subarray(8, 12)),
69632            name: ''
69633          },
69634          i = 8; // parse out the name field
69635
69636        for (i = 24; i < data.byteLength; i++) {
69637          if (data[i] === 0x00) {
69638            // the name field is null-terminated
69639            i++;
69640            break;
69641          }
69642          result.name += String.fromCharCode(data[i]);
69643        } // decode UTF-8 to javascript's internal representation
69644        // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
69645
69646        result.name = decodeURIComponent(escape(result.name));
69647        return result;
69648      },
69649      mdat: function (data) {
69650        return {
69651          byteLength: data.byteLength,
69652          nals: nalParse(data)
69653        };
69654      },
69655      mdhd: function (data) {
69656        var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69657          i = 4,
69658          language,
69659          result = {
69660            version: view.getUint8(0),
69661            flags: new Uint8Array(data.subarray(1, 4)),
69662            language: ''
69663          };
69664        if (result.version === 1) {
69665          i += 4;
69666          result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
69667
69668          i += 8;
69669          result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
69670
69671          i += 4;
69672          result.timescale = view.getUint32(i);
69673          i += 8;
69674          result.duration = view.getUint32(i); // truncating top 4 bytes
69675        } else {
69676          result.creationTime = parseMp4Date(view.getUint32(i));
69677          i += 4;
69678          result.modificationTime = parseMp4Date(view.getUint32(i));
69679          i += 4;
69680          result.timescale = view.getUint32(i);
69681          i += 4;
69682          result.duration = view.getUint32(i);
69683        }
69684        i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
69685        // each field is the packed difference between its ASCII value and 0x60
69686
69687        language = view.getUint16(i);
69688        result.language += String.fromCharCode((language >> 10) + 0x60);
69689        result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60);
69690        result.language += String.fromCharCode((language & 0x1f) + 0x60);
69691        return result;
69692      },
69693      mdia: function (data) {
69694        return {
69695          boxes: inspectMp4(data)
69696        };
69697      },
69698      mfhd: function (data) {
69699        return {
69700          version: data[0],
69701          flags: new Uint8Array(data.subarray(1, 4)),
69702          sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
69703        };
69704      },
69705      minf: function (data) {
69706        return {
69707          boxes: inspectMp4(data)
69708        };
69709      },
69710      // codingname, not a first-class box type. stsd entries share the
69711      // same format as real boxes so the parsing infrastructure can be
69712      // shared
69713      mp4a: function (data) {
69714        var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69715          result = {
69716            // 6 bytes reserved
69717            dataReferenceIndex: view.getUint16(6),
69718            // 4 + 4 bytes reserved
69719            channelcount: view.getUint16(16),
69720            samplesize: view.getUint16(18),
69721            // 2 bytes pre_defined
69722            // 2 bytes reserved
69723            samplerate: view.getUint16(24) + view.getUint16(26) / 65536
69724          }; // if there are more bytes to process, assume this is an ISO/IEC
69725        // 14496-14 MP4AudioSampleEntry and parse the ESDBox
69726
69727        if (data.byteLength > 28) {
69728          result.streamDescriptor = inspectMp4(data.subarray(28))[0];
69729        }
69730        return result;
69731      },
69732      moof: function (data) {
69733        return {
69734          boxes: inspectMp4(data)
69735        };
69736      },
69737      moov: function (data) {
69738        return {
69739          boxes: inspectMp4(data)
69740        };
69741      },
69742      mvex: function (data) {
69743        return {
69744          boxes: inspectMp4(data)
69745        };
69746      },
69747      mvhd: function (data) {
69748        var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69749          i = 4,
69750          result = {
69751            version: view.getUint8(0),
69752            flags: new Uint8Array(data.subarray(1, 4))
69753          };
69754        if (result.version === 1) {
69755          i += 4;
69756          result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
69757
69758          i += 8;
69759          result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
69760
69761          i += 4;
69762          result.timescale = view.getUint32(i);
69763          i += 8;
69764          result.duration = view.getUint32(i); // truncating top 4 bytes
69765        } else {
69766          result.creationTime = parseMp4Date(view.getUint32(i));
69767          i += 4;
69768          result.modificationTime = parseMp4Date(view.getUint32(i));
69769          i += 4;
69770          result.timescale = view.getUint32(i);
69771          i += 4;
69772          result.duration = view.getUint32(i);
69773        }
69774        i += 4; // convert fixed-point, base 16 back to a number
69775
69776        result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16;
69777        i += 4;
69778        result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
69779        i += 2;
69780        i += 2;
69781        i += 2 * 4;
69782        result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
69783        i += 9 * 4;
69784        i += 6 * 4;
69785        result.nextTrackId = view.getUint32(i);
69786        return result;
69787      },
69788      pdin: function (data) {
69789        var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
69790        return {
69791          version: view.getUint8(0),
69792          flags: new Uint8Array(data.subarray(1, 4)),
69793          rate: view.getUint32(4),
69794          initialDelay: view.getUint32(8)
69795        };
69796      },
69797      sdtp: function (data) {
69798        var result = {
69799            version: data[0],
69800            flags: new Uint8Array(data.subarray(1, 4)),
69801            samples: []
69802          },
69803          i;
69804        for (i = 4; i < data.byteLength; i++) {
69805          result.samples.push({
69806            dependsOn: (data[i] & 0x30) >> 4,
69807            isDependedOn: (data[i] & 0x0c) >> 2,
69808            hasRedundancy: data[i] & 0x03
69809          });
69810        }
69811        return result;
69812      },
69813      sidx: parseSidx_1,
69814      smhd: function (data) {
69815        return {
69816          version: data[0],
69817          flags: new Uint8Array(data.subarray(1, 4)),
69818          balance: data[4] + data[5] / 256
69819        };
69820      },
69821      stbl: function (data) {
69822        return {
69823          boxes: inspectMp4(data)
69824        };
69825      },
69826      ctts: function (data) {
69827        var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69828          result = {
69829            version: view.getUint8(0),
69830            flags: new Uint8Array(data.subarray(1, 4)),
69831            compositionOffsets: []
69832          },
69833          entryCount = view.getUint32(4),
69834          i;
69835        for (i = 8; entryCount; i += 8, entryCount--) {
69836          result.compositionOffsets.push({
69837            sampleCount: view.getUint32(i),
69838            sampleOffset: view[result.version === 0 ? 'getUint32' : 'getInt32'](i + 4)
69839          });
69840        }
69841        return result;
69842      },
69843      stss: function (data) {
69844        var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69845          result = {
69846            version: view.getUint8(0),
69847            flags: new Uint8Array(data.subarray(1, 4)),
69848            syncSamples: []
69849          },
69850          entryCount = view.getUint32(4),
69851          i;
69852        for (i = 8; entryCount; i += 4, entryCount--) {
69853          result.syncSamples.push(view.getUint32(i));
69854        }
69855        return result;
69856      },
69857      stco: function (data) {
69858        var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69859          result = {
69860            version: data[0],
69861            flags: new Uint8Array(data.subarray(1, 4)),
69862            chunkOffsets: []
69863          },
69864          entryCount = view.getUint32(4),
69865          i;
69866        for (i = 8; entryCount; i += 4, entryCount--) {
69867          result.chunkOffsets.push(view.getUint32(i));
69868        }
69869        return result;
69870      },
69871      stsc: function (data) {
69872        var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69873          entryCount = view.getUint32(4),
69874          result = {
69875            version: data[0],
69876            flags: new Uint8Array(data.subarray(1, 4)),
69877            sampleToChunks: []
69878          },
69879          i;
69880        for (i = 8; entryCount; i += 12, entryCount--) {
69881          result.sampleToChunks.push({
69882            firstChunk: view.getUint32(i),
69883            samplesPerChunk: view.getUint32(i + 4),
69884            sampleDescriptionIndex: view.getUint32(i + 8)
69885          });
69886        }
69887        return result;
69888      },
69889      stsd: function (data) {
69890        return {
69891          version: data[0],
69892          flags: new Uint8Array(data.subarray(1, 4)),
69893          sampleDescriptions: inspectMp4(data.subarray(8))
69894        };
69895      },
69896      stsz: function (data) {
69897        var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69898          result = {
69899            version: data[0],
69900            flags: new Uint8Array(data.subarray(1, 4)),
69901            sampleSize: view.getUint32(4),
69902            entries: []
69903          },
69904          i;
69905        for (i = 12; i < data.byteLength; i += 4) {
69906          result.entries.push(view.getUint32(i));
69907        }
69908        return result;
69909      },
69910      stts: function (data) {
69911        var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69912          result = {
69913            version: data[0],
69914            flags: new Uint8Array(data.subarray(1, 4)),
69915            timeToSamples: []
69916          },
69917          entryCount = view.getUint32(4),
69918          i;
69919        for (i = 8; entryCount; i += 8, entryCount--) {
69920          result.timeToSamples.push({
69921            sampleCount: view.getUint32(i),
69922            sampleDelta: view.getUint32(i + 4)
69923          });
69924        }
69925        return result;
69926      },
69927      styp: function (data) {
69928        return parse.ftyp(data);
69929      },
69930      tfdt: parseTfdt$3,
69931      tfhd: parseTfhd$2,
69932      tkhd: function (data) {
69933        var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
69934          i = 4,
69935          result = {
69936            version: view.getUint8(0),
69937            flags: new Uint8Array(data.subarray(1, 4))
69938          };
69939        if (result.version === 1) {
69940          i += 4;
69941          result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
69942
69943          i += 8;
69944          result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
69945
69946          i += 4;
69947          result.trackId = view.getUint32(i);
69948          i += 4;
69949          i += 8;
69950          result.duration = view.getUint32(i); // truncating top 4 bytes
69951        } else {
69952          result.creationTime = parseMp4Date(view.getUint32(i));
69953          i += 4;
69954          result.modificationTime = parseMp4Date(view.getUint32(i));
69955          i += 4;
69956          result.trackId = view.getUint32(i);
69957          i += 4;
69958          i += 4;
69959          result.duration = view.getUint32(i);
69960        }
69961        i += 4;
69962        i += 2 * 4;
69963        result.layer = view.getUint16(i);
69964        i += 2;
69965        result.alternateGroup = view.getUint16(i);
69966        i += 2; // convert fixed-point, base 16 back to a number
69967
69968        result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
69969        i += 2;
69970        i += 2;
69971        result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
69972        i += 9 * 4;
69973        result.width = view.getUint16(i) + view.getUint16(i + 2) / 65536;
69974        i += 4;
69975        result.height = view.getUint16(i) + view.getUint16(i + 2) / 65536;
69976        return result;
69977      },
69978      traf: function (data) {
69979        return {
69980          boxes: inspectMp4(data)
69981        };
69982      },
69983      trak: function (data) {
69984        return {
69985          boxes: inspectMp4(data)
69986        };
69987      },
69988      trex: function (data) {
69989        var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
69990        return {
69991          version: data[0],
69992          flags: new Uint8Array(data.subarray(1, 4)),
69993          trackId: view.getUint32(4),
69994          defaultSampleDescriptionIndex: view.getUint32(8),
69995          defaultSampleDuration: view.getUint32(12),
69996          defaultSampleSize: view.getUint32(16),
69997          sampleDependsOn: data[20] & 0x03,
69998          sampleIsDependedOn: (data[21] & 0xc0) >> 6,
69999          sampleHasRedundancy: (data[21] & 0x30) >> 4,
70000          samplePaddingValue: (data[21] & 0x0e) >> 1,
70001          sampleIsDifferenceSample: !!(data[21] & 0x01),
70002          sampleDegradationPriority: view.getUint16(22)
70003        };
70004      },
70005      trun: parseTrun$2,
70006      'url ': function (data) {
70007        return {
70008          version: data[0],
70009          flags: new Uint8Array(data.subarray(1, 4))
70010        };
70011      },
70012      vmhd: function (data) {
70013        var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
70014        return {
70015          version: data[0],
70016          flags: new Uint8Array(data.subarray(1, 4)),
70017          graphicsmode: view.getUint16(4),
70018          opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)])
70019        };
70020      }
70021    };
70022  /**
70023   * Return a javascript array of box objects parsed from an ISO base
70024   * media file.
70025   * @param data {Uint8Array} the binary data of the media to be inspected
70026   * @return {array} a javascript array of potentially nested box objects
70027   */
70028
70029  inspectMp4 = function (data) {
70030    var i = 0,
70031      result = [],
70032      view,
70033      size,
70034      type,
70035      end,
70036      box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API
70037
70038    var ab = new ArrayBuffer(data.length);
70039    var v = new Uint8Array(ab);
70040    for (var z = 0; z < data.length; ++z) {
70041      v[z] = data[z];
70042    }
70043    view = new DataView(ab);
70044    while (i < data.byteLength) {
70045      // parse box data
70046      size = view.getUint32(i);
70047      type = parseType$2(data.subarray(i + 4, i + 8));
70048      end = size > 1 ? i + size : data.byteLength; // parse type-specific data
70049
70050      box = (parse[type] || function (data) {
70051        return {
70052          data: data
70053        };
70054      })(data.subarray(i + 8, end));
70055      box.size = size;
70056      box.type = type; // store this box and move to the next
70057
70058      result.push(box);
70059      i = end;
70060    }
70061    return result;
70062  };
70063  /**
70064   * Returns a textual representation of the javascript represtentation
70065   * of an MP4 file. You can use it as an alternative to
70066   * JSON.stringify() to compare inspected MP4s.
70067   * @param inspectedMp4 {array} the parsed array of boxes in an MP4
70068   * file
70069   * @param depth {number} (optional) the number of ancestor boxes of
70070   * the elements of inspectedMp4. Assumed to be zero if unspecified.
70071   * @return {string} a text representation of the parsed MP4
70072   */
70073
70074  textifyMp4 = function (inspectedMp4, depth) {
70075    var indent;
70076    depth = depth || 0;
70077    indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes
70078
70079    return inspectedMp4.map(function (box, index) {
70080      // list the box type first at the current indentation level
70081      return indent + box.type + '\n' +
70082      // the type is already included and handle child boxes separately
70083      Object.keys(box).filter(function (key) {
70084        return key !== 'type' && key !== 'boxes'; // output all the box properties
70085      }).map(function (key) {
70086        var prefix = indent + '  ' + key + ': ',
70087          value = box[key]; // print out raw bytes as hexademical
70088
70089        if (value instanceof Uint8Array || value instanceof Uint32Array) {
70090          var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (byte) {
70091            return ' ' + ('00' + byte.toString(16)).slice(-2);
70092          }).join('').match(/.{1,24}/g);
70093          if (!bytes) {
70094            return prefix + '<>';
70095          }
70096          if (bytes.length === 1) {
70097            return prefix + '<' + bytes.join('').slice(1) + '>';
70098          }
70099          return prefix + '<\n' + bytes.map(function (line) {
70100            return indent + '  ' + line;
70101          }).join('\n') + '\n' + indent + '  >';
70102        } // stringify generic objects
70103
70104        return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) {
70105          if (index === 0) {
70106            return line;
70107          }
70108          return indent + '  ' + line;
70109        }).join('\n');
70110      }).join('\n') + (
70111      // recursively textify the child boxes
70112      box.boxes ? '\n' + textifyMp4(box.boxes, depth + 1) : '');
70113    }).join('\n');
70114  };
70115  var mp4Inspector = {
70116    inspect: inspectMp4,
70117    textify: textifyMp4,
70118    parseType: parseType$2,
70119    findBox: findBox$4,
70120    parseTraf: parse.traf,
70121    parseTfdt: parse.tfdt,
70122    parseHdlr: parse.hdlr,
70123    parseTfhd: parse.tfhd,
70124    parseTrun: parse.trun,
70125    parseSidx: parse.sidx
70126  };
70127  /**
70128   * Returns the first string in the data array ending with a null char '\0'
70129   * @param {UInt8} data 
70130   * @returns the string with the null char
70131   */
70132
70133  var uint8ToCString$1 = function (data) {
70134    var index = 0;
70135    var curChar = String.fromCharCode(data[index]);
70136    var retString = '';
70137    while (curChar !== '\0') {
70138      retString += curChar;
70139      index++;
70140      curChar = String.fromCharCode(data[index]);
70141    } // Add nullChar
70142
70143    retString += curChar;
70144    return retString;
70145  };
70146  var string = {
70147    uint8ToCString: uint8ToCString$1
70148  };
70149  var uint8ToCString = string.uint8ToCString;
70150  var getUint64$1 = numbers.getUint64;
70151  /**
70152   * Based on: ISO/IEC 23009 Section: 5.10.3.3
70153   * References:
70154   * https://dashif-documents.azurewebsites.net/Events/master/event.html#emsg-format
70155   * https://aomediacodec.github.io/id3-emsg/
70156   * 
70157   * Takes emsg box data as a uint8 array and returns a emsg box object
70158   * @param {UInt8Array} boxData data from emsg box
70159   * @returns A parsed emsg box object
70160   */
70161
70162  var parseEmsgBox = function (boxData) {
70163    // version + flags
70164    var offset = 4;
70165    var version = boxData[0];
70166    var scheme_id_uri, value, timescale, presentation_time, presentation_time_delta, event_duration, id, message_data;
70167    if (version === 0) {
70168      scheme_id_uri = uint8ToCString(boxData.subarray(offset));
70169      offset += scheme_id_uri.length;
70170      value = uint8ToCString(boxData.subarray(offset));
70171      offset += value.length;
70172      var dv = new DataView(boxData.buffer);
70173      timescale = dv.getUint32(offset);
70174      offset += 4;
70175      presentation_time_delta = dv.getUint32(offset);
70176      offset += 4;
70177      event_duration = dv.getUint32(offset);
70178      offset += 4;
70179      id = dv.getUint32(offset);
70180      offset += 4;
70181    } else if (version === 1) {
70182      var dv = new DataView(boxData.buffer);
70183      timescale = dv.getUint32(offset);
70184      offset += 4;
70185      presentation_time = getUint64$1(boxData.subarray(offset));
70186      offset += 8;
70187      event_duration = dv.getUint32(offset);
70188      offset += 4;
70189      id = dv.getUint32(offset);
70190      offset += 4;
70191      scheme_id_uri = uint8ToCString(boxData.subarray(offset));
70192      offset += scheme_id_uri.length;
70193      value = uint8ToCString(boxData.subarray(offset));
70194      offset += value.length;
70195    }
70196    message_data = new Uint8Array(boxData.subarray(offset, boxData.byteLength));
70197    var emsgBox = {
70198      scheme_id_uri,
70199      value,
70200      // if timescale is undefined or 0 set to 1 
70201      timescale: timescale ? timescale : 1,
70202      presentation_time,
70203      presentation_time_delta,
70204      event_duration,
70205      id,
70206      message_data
70207    };
70208    return isValidEmsgBox(version, emsgBox) ? emsgBox : undefined;
70209  };
70210  /**
70211   * Scales a presentation time or time delta with an offset with a provided timescale
70212   * @param {number} presentationTime 
70213   * @param {number} timescale 
70214   * @param {number} timeDelta 
70215   * @param {number} offset 
70216   * @returns the scaled time as a number
70217   */
70218
70219  var scaleTime = function (presentationTime, timescale, timeDelta, offset) {
70220    return presentationTime || presentationTime === 0 ? presentationTime / timescale : offset + timeDelta / timescale;
70221  };
70222  /**
70223   * Checks the emsg box data for validity based on the version
70224   * @param {number} version of the emsg box to validate
70225   * @param {Object} emsg the emsg data to validate
70226   * @returns if the box is valid as a boolean
70227   */
70228
70229  var isValidEmsgBox = function (version, emsg) {
70230    var hasScheme = emsg.scheme_id_uri !== '\0';
70231    var isValidV0Box = version === 0 && isDefined(emsg.presentation_time_delta) && hasScheme;
70232    var isValidV1Box = version === 1 && isDefined(emsg.presentation_time) && hasScheme; // Only valid versions of emsg are 0 and 1
70233
70234    return !(version > 1) && isValidV0Box || isValidV1Box;
70235  }; // Utility function to check if an object is defined
70236
70237  var isDefined = function (data) {
70238    return data !== undefined || data !== null;
70239  };
70240  var emsg$1 = {
70241    parseEmsgBox: parseEmsgBox,
70242    scaleTime: scaleTime
70243  };
70244  var win;
70245  if (typeof window !== "undefined") {
70246    win = window;
70247  } else if (typeof commonjsGlobal !== "undefined") {
70248    win = commonjsGlobal;
70249  } else if (typeof self !== "undefined") {
70250    win = self;
70251  } else {
70252    win = {};
70253  }
70254  var window_1 = win;
70255  /**
70256   * mux.js
70257   *
70258   * Copyright (c) Brightcove
70259   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
70260   *
70261   * Utilities to detect basic properties and metadata about MP4s.
70262   */
70263
70264  var toUnsigned = bin.toUnsigned;
70265  var toHexString = bin.toHexString;
70266  var findBox$3 = findBox_1;
70267  var parseType$1 = parseType_1;
70268  var emsg = emsg$1;
70269  var parseTfhd$1 = parseTfhd$2;
70270  var parseTrun$1 = parseTrun$2;
70271  var parseTfdt$2 = parseTfdt$3;
70272  var getUint64 = numbers.getUint64;
70273  var timescale, startTime, compositionStartTime, getVideoTrackIds, getTracks, getTimescaleFromMediaHeader$1, getEmsgID3;
70274  var window$2 = window_1;
70275  var parseId3Frames = parseId3.parseId3Frames;
70276  /**
70277   * Parses an MP4 initialization segment and extracts the timescale
70278   * values for any declared tracks. Timescale values indicate the
70279   * number of clock ticks per second to assume for time-based values
70280   * elsewhere in the MP4.
70281   *
70282   * To determine the start time of an MP4, you need two pieces of
70283   * information: the timescale unit and the earliest base media decode
70284   * time. Multiple timescales can be specified within an MP4 but the
70285   * base media decode time is always expressed in the timescale from
70286   * the media header box for the track:
70287   * ```
70288   * moov > trak > mdia > mdhd.timescale
70289   * ```
70290   * @param init {Uint8Array} the bytes of the init segment
70291   * @return {object} a hash of track ids to timescale values or null if
70292   * the init segment is malformed.
70293   */
70294
70295  timescale = function (init) {
70296    var result = {},
70297      traks = findBox$3(init, ['moov', 'trak']); // mdhd timescale
70298
70299    return traks.reduce(function (result, trak) {
70300      var tkhd, version, index, id, mdhd;
70301      tkhd = findBox$3(trak, ['tkhd'])[0];
70302      if (!tkhd) {
70303        return null;
70304      }
70305      version = tkhd[0];
70306      index = version === 0 ? 12 : 20;
70307      id = toUnsigned(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
70308      mdhd = findBox$3(trak, ['mdia', 'mdhd'])[0];
70309      if (!mdhd) {
70310        return null;
70311      }
70312      version = mdhd[0];
70313      index = version === 0 ? 12 : 20;
70314      result[id] = toUnsigned(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
70315      return result;
70316    }, result);
70317  };
70318  /**
70319   * Determine the base media decode start time, in seconds, for an MP4
70320   * fragment. If multiple fragments are specified, the earliest time is
70321   * returned.
70322   *
70323   * The base media decode time can be parsed from track fragment
70324   * metadata:
70325   * ```
70326   * moof > traf > tfdt.baseMediaDecodeTime
70327   * ```
70328   * It requires the timescale value from the mdhd to interpret.
70329   *
70330   * @param timescale {object} a hash of track ids to timescale values.
70331   * @return {number} the earliest base media decode start time for the
70332   * fragment, in seconds
70333   */
70334
70335  startTime = function (timescale, fragment) {
70336    var trafs; // we need info from two childrend of each track fragment box
70337
70338    trafs = findBox$3(fragment, ['moof', 'traf']); // determine the start times for each track
70339
70340    var lowestTime = trafs.reduce(function (acc, traf) {
70341      var tfhd = findBox$3(traf, ['tfhd'])[0]; // get the track id from the tfhd
70342
70343      var id = toUnsigned(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
70344
70345      var scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
70346
70347      var tfdt = findBox$3(traf, ['tfdt'])[0];
70348      var dv = new DataView(tfdt.buffer, tfdt.byteOffset, tfdt.byteLength);
70349      var baseTime; // version 1 is 64 bit
70350
70351      if (tfdt[0] === 1) {
70352        baseTime = getUint64(tfdt.subarray(4, 12));
70353      } else {
70354        baseTime = dv.getUint32(4);
70355      } // convert base time to seconds if it is a valid number.
70356
70357      let seconds;
70358      if (typeof baseTime === 'bigint') {
70359        seconds = baseTime / window$2.BigInt(scale);
70360      } else if (typeof baseTime === 'number' && !isNaN(baseTime)) {
70361        seconds = baseTime / scale;
70362      }
70363      if (seconds < Number.MAX_SAFE_INTEGER) {
70364        seconds = Number(seconds);
70365      }
70366      if (seconds < acc) {
70367        acc = seconds;
70368      }
70369      return acc;
70370    }, Infinity);
70371    return typeof lowestTime === 'bigint' || isFinite(lowestTime) ? lowestTime : 0;
70372  };
70373  /**
70374   * Determine the composition start, in seconds, for an MP4
70375   * fragment.
70376   *
70377   * The composition start time of a fragment can be calculated using the base
70378   * media decode time, composition time offset, and timescale, as follows:
70379   *
70380   * compositionStartTime = (baseMediaDecodeTime + compositionTimeOffset) / timescale
70381   *
70382   * All of the aforementioned information is contained within a media fragment's
70383   * `traf` box, except for timescale info, which comes from the initialization
70384   * segment, so a track id (also contained within a `traf`) is also necessary to
70385   * associate it with a timescale
70386   *
70387   *
70388   * @param timescales {object} - a hash of track ids to timescale values.
70389   * @param fragment {Unit8Array} - the bytes of a media segment
70390   * @return {number} the composition start time for the fragment, in seconds
70391   **/
70392
70393  compositionStartTime = function (timescales, fragment) {
70394    var trafBoxes = findBox$3(fragment, ['moof', 'traf']);
70395    var baseMediaDecodeTime = 0;
70396    var compositionTimeOffset = 0;
70397    var trackId;
70398    if (trafBoxes && trafBoxes.length) {
70399      // The spec states that track run samples contained within a `traf` box are contiguous, but
70400      // it does not explicitly state whether the `traf` boxes themselves are contiguous.
70401      // We will assume that they are, so we only need the first to calculate start time.
70402      var tfhd = findBox$3(trafBoxes[0], ['tfhd'])[0];
70403      var trun = findBox$3(trafBoxes[0], ['trun'])[0];
70404      var tfdt = findBox$3(trafBoxes[0], ['tfdt'])[0];
70405      if (tfhd) {
70406        var parsedTfhd = parseTfhd$1(tfhd);
70407        trackId = parsedTfhd.trackId;
70408      }
70409      if (tfdt) {
70410        var parsedTfdt = parseTfdt$2(tfdt);
70411        baseMediaDecodeTime = parsedTfdt.baseMediaDecodeTime;
70412      }
70413      if (trun) {
70414        var parsedTrun = parseTrun$1(trun);
70415        if (parsedTrun.samples && parsedTrun.samples.length) {
70416          compositionTimeOffset = parsedTrun.samples[0].compositionTimeOffset || 0;
70417        }
70418      }
70419    } // Get timescale for this specific track. Assume a 90kHz clock if no timescale was
70420    // specified.
70421
70422    var timescale = timescales[trackId] || 90e3; // return the composition start time, in seconds
70423
70424    if (typeof baseMediaDecodeTime === 'bigint') {
70425      compositionTimeOffset = window$2.BigInt(compositionTimeOffset);
70426      timescale = window$2.BigInt(timescale);
70427    }
70428    var result = (baseMediaDecodeTime + compositionTimeOffset) / timescale;
70429    if (typeof result === 'bigint' && result < Number.MAX_SAFE_INTEGER) {
70430      result = Number(result);
70431    }
70432    return result;
70433  };
70434  /**
70435    * Find the trackIds of the video tracks in this source.
70436    * Found by parsing the Handler Reference and Track Header Boxes:
70437    *   moov > trak > mdia > hdlr
70438    *   moov > trak > tkhd
70439    *
70440    * @param {Uint8Array} init - The bytes of the init segment for this source
70441    * @return {Number[]} A list of trackIds
70442    *
70443    * @see ISO-BMFF-12/2015, Section 8.4.3
70444   **/
70445
70446  getVideoTrackIds = function (init) {
70447    var traks = findBox$3(init, ['moov', 'trak']);
70448    var videoTrackIds = [];
70449    traks.forEach(function (trak) {
70450      var hdlrs = findBox$3(trak, ['mdia', 'hdlr']);
70451      var tkhds = findBox$3(trak, ['tkhd']);
70452      hdlrs.forEach(function (hdlr, index) {
70453        var handlerType = parseType$1(hdlr.subarray(8, 12));
70454        var tkhd = tkhds[index];
70455        var view;
70456        var version;
70457        var trackId;
70458        if (handlerType === 'vide') {
70459          view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
70460          version = view.getUint8(0);
70461          trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
70462          videoTrackIds.push(trackId);
70463        }
70464      });
70465    });
70466    return videoTrackIds;
70467  };
70468  getTimescaleFromMediaHeader$1 = function (mdhd) {
70469    // mdhd is a FullBox, meaning it will have its own version as the first byte
70470    var version = mdhd[0];
70471    var index = version === 0 ? 12 : 20;
70472    return toUnsigned(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
70473  };
70474  /**
70475   * Get all the video, audio, and hint tracks from a non fragmented
70476   * mp4 segment
70477   */
70478
70479  getTracks = function (init) {
70480    var traks = findBox$3(init, ['moov', 'trak']);
70481    var tracks = [];
70482    traks.forEach(function (trak) {
70483      var track = {};
70484      var tkhd = findBox$3(trak, ['tkhd'])[0];
70485      var view, tkhdVersion; // id
70486
70487      if (tkhd) {
70488        view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
70489        tkhdVersion = view.getUint8(0);
70490        track.id = tkhdVersion === 0 ? view.getUint32(12) : view.getUint32(20);
70491      }
70492      var hdlr = findBox$3(trak, ['mdia', 'hdlr'])[0]; // type
70493
70494      if (hdlr) {
70495        var type = parseType$1(hdlr.subarray(8, 12));
70496        if (type === 'vide') {
70497          track.type = 'video';
70498        } else if (type === 'soun') {
70499          track.type = 'audio';
70500        } else {
70501          track.type = type;
70502        }
70503      } // codec
70504
70505      var stsd = findBox$3(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
70506      if (stsd) {
70507        var sampleDescriptions = stsd.subarray(8); // gives the codec type string
70508
70509        track.codec = parseType$1(sampleDescriptions.subarray(4, 8));
70510        var codecBox = findBox$3(sampleDescriptions, [track.codec])[0];
70511        var codecConfig, codecConfigType;
70512        if (codecBox) {
70513          // https://tools.ietf.org/html/rfc6381#section-3.3
70514          if (/^[asm]vc[1-9]$/i.test(track.codec)) {
70515            // we don't need anything but the "config" parameter of the
70516            // avc1 codecBox
70517            codecConfig = codecBox.subarray(78);
70518            codecConfigType = parseType$1(codecConfig.subarray(4, 8));
70519            if (codecConfigType === 'avcC' && codecConfig.length > 11) {
70520              track.codec += '.'; // left padded with zeroes for single digit hex
70521              // profile idc
70522
70523              track.codec += toHexString(codecConfig[9]); // the byte containing the constraint_set flags
70524
70525              track.codec += toHexString(codecConfig[10]); // level idc
70526
70527              track.codec += toHexString(codecConfig[11]);
70528            } else {
70529              // TODO: show a warning that we couldn't parse the codec
70530              // and are using the default
70531              track.codec = 'avc1.4d400d';
70532            }
70533          } else if (/^mp4[a,v]$/i.test(track.codec)) {
70534            // we do not need anything but the streamDescriptor of the mp4a codecBox
70535            codecConfig = codecBox.subarray(28);
70536            codecConfigType = parseType$1(codecConfig.subarray(4, 8));
70537            if (codecConfigType === 'esds' && codecConfig.length > 20 && codecConfig[19] !== 0) {
70538              track.codec += '.' + toHexString(codecConfig[19]); // this value is only a single digit
70539
70540              track.codec += '.' + toHexString(codecConfig[20] >>> 2 & 0x3f).replace(/^0/, '');
70541            } else {
70542              // TODO: show a warning that we couldn't parse the codec
70543              // and are using the default
70544              track.codec = 'mp4a.40.2';
70545            }
70546          } else {
70547            // flac, opus, etc
70548            track.codec = track.codec.toLowerCase();
70549          }
70550        }
70551      }
70552      var mdhd = findBox$3(trak, ['mdia', 'mdhd'])[0];
70553      if (mdhd) {
70554        track.timescale = getTimescaleFromMediaHeader$1(mdhd);
70555      }
70556      tracks.push(track);
70557    });
70558    return tracks;
70559  };
70560  /**
70561   * Returns an array of emsg ID3 data from the provided segmentData.
70562   * An offset can also be provided as the Latest Arrival Time to calculate 
70563   * the Event Start Time of v0 EMSG boxes. 
70564   * See: https://dashif-documents.azurewebsites.net/Events/master/event.html#Inband-event-timing
70565   * 
70566   * @param {Uint8Array} segmentData the segment byte array.
70567   * @param {number} offset the segment start time or Latest Arrival Time, 
70568   * @return {Object[]} an array of ID3 parsed from EMSG boxes
70569   */
70570
70571  getEmsgID3 = function (segmentData, offset = 0) {
70572    var emsgBoxes = findBox$3(segmentData, ['emsg']);
70573    return emsgBoxes.map(data => {
70574      var parsedBox = emsg.parseEmsgBox(new Uint8Array(data));
70575      var parsedId3Frames = parseId3Frames(parsedBox.message_data);
70576      return {
70577        cueTime: emsg.scaleTime(parsedBox.presentation_time, parsedBox.timescale, parsedBox.presentation_time_delta, offset),
70578        duration: emsg.scaleTime(parsedBox.event_duration, parsedBox.timescale),
70579        frames: parsedId3Frames
70580      };
70581    });
70582  };
70583  var probe$2 = {
70584    // export mp4 inspector's findBox and parseType for backwards compatibility
70585    findBox: findBox$3,
70586    parseType: parseType$1,
70587    timescale: timescale,
70588    startTime: startTime,
70589    compositionStartTime: compositionStartTime,
70590    videoTrackIds: getVideoTrackIds,
70591    tracks: getTracks,
70592    getTimescaleFromMediaHeader: getTimescaleFromMediaHeader$1,
70593    getEmsgID3: getEmsgID3
70594  };
70595  const {
70596    parseTrun
70597  } = mp4Inspector;
70598  const {
70599    findBox: findBox$2
70600  } = probe$2;
70601  var window$1 = window_1;
70602  /**
70603   * Utility function for parsing data from mdat boxes.
70604   * @param {Array<Uint8Array>} segment the segment data to create mdat/traf pairs from.
70605   * @returns mdat and traf boxes paired up for easier parsing.
70606   */
70607
70608  var getMdatTrafPairs$2 = function (segment) {
70609    var trafs = findBox$2(segment, ['moof', 'traf']);
70610    var mdats = findBox$2(segment, ['mdat']);
70611    var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
70612
70613    mdats.forEach(function (mdat, index) {
70614      var matchingTraf = trafs[index];
70615      mdatTrafPairs.push({
70616        mdat: mdat,
70617        traf: matchingTraf
70618      });
70619    });
70620    return mdatTrafPairs;
70621  };
70622  /**
70623    * Parses sample information out of Track Run Boxes and calculates
70624    * the absolute presentation and decode timestamps of each sample.
70625    *
70626    * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
70627    * @param {Number|BigInt} baseMediaDecodeTime - base media decode time from tfdt
70628        @see ISO-BMFF-12/2015, Section 8.8.12
70629    * @param {Object} tfhd - The parsed Track Fragment Header
70630    *   @see inspect.parseTfhd
70631    * @return {Object[]} the parsed samples
70632    *
70633    * @see ISO-BMFF-12/2015, Section 8.8.8
70634   **/
70635
70636  var parseSamples$2 = function (truns, baseMediaDecodeTime, tfhd) {
70637    var currentDts = baseMediaDecodeTime;
70638    var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
70639    var defaultSampleSize = tfhd.defaultSampleSize || 0;
70640    var trackId = tfhd.trackId;
70641    var allSamples = [];
70642    truns.forEach(function (trun) {
70643      // Note: We currently do not parse the sample table as well
70644      // as the trun. It's possible some sources will require this.
70645      // moov > trak > mdia > minf > stbl
70646      var trackRun = parseTrun(trun);
70647      var samples = trackRun.samples;
70648      samples.forEach(function (sample) {
70649        if (sample.duration === undefined) {
70650          sample.duration = defaultSampleDuration;
70651        }
70652        if (sample.size === undefined) {
70653          sample.size = defaultSampleSize;
70654        }
70655        sample.trackId = trackId;
70656        sample.dts = currentDts;
70657        if (sample.compositionTimeOffset === undefined) {
70658          sample.compositionTimeOffset = 0;
70659        }
70660        if (typeof currentDts === 'bigint') {
70661          sample.pts = currentDts + window$1.BigInt(sample.compositionTimeOffset);
70662          currentDts += window$1.BigInt(sample.duration);
70663        } else {
70664          sample.pts = currentDts + sample.compositionTimeOffset;
70665          currentDts += sample.duration;
70666        }
70667      });
70668      allSamples = allSamples.concat(samples);
70669    });
70670    return allSamples;
70671  };
70672  var samples = {
70673    getMdatTrafPairs: getMdatTrafPairs$2,
70674    parseSamples: parseSamples$2
70675  };
70676  /**
70677   * mux.js
70678   *
70679   * Copyright (c) Brightcove
70680   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
70681   *
70682   * Reads in-band CEA-708 captions out of FMP4 segments.
70683   * @see https://en.wikipedia.org/wiki/CEA-708
70684   */
70685
70686  var discardEmulationPreventionBytes = captionPacketParser.discardEmulationPreventionBytes;
70687  var CaptionStream = captionStream.CaptionStream;
70688  var findBox$1 = findBox_1;
70689  var parseTfdt$1 = parseTfdt$3;
70690  var parseTfhd = parseTfhd$2;
70691  var {
70692    getMdatTrafPairs: getMdatTrafPairs$1,
70693    parseSamples: parseSamples$1
70694  } = samples;
70695  /**
70696    * Maps an offset in the mdat to a sample based on the the size of the samples.
70697    * Assumes that `parseSamples` has been called first.
70698    *
70699    * @param {Number} offset - The offset into the mdat
70700    * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
70701    * @return {?Object} The matching sample, or null if no match was found.
70702    *
70703    * @see ISO-BMFF-12/2015, Section 8.8.8
70704   **/
70705
70706  var mapToSample = function (offset, samples) {
70707    var approximateOffset = offset;
70708    for (var i = 0; i < samples.length; i++) {
70709      var sample = samples[i];
70710      if (approximateOffset < sample.size) {
70711        return sample;
70712      }
70713      approximateOffset -= sample.size;
70714    }
70715    return null;
70716  };
70717  /**
70718    * Finds SEI nal units contained in a Media Data Box.
70719    * Assumes that `parseSamples` has been called first.
70720    *
70721    * @param {Uint8Array} avcStream - The bytes of the mdat
70722    * @param {Object[]} samples - The samples parsed out by `parseSamples`
70723    * @param {Number} trackId - The trackId of this video track
70724    * @return {Object[]} seiNals - the parsed SEI NALUs found.
70725    *   The contents of the seiNal should match what is expected by
70726    *   CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
70727    *
70728    * @see ISO-BMFF-12/2015, Section 8.1.1
70729    * @see Rec. ITU-T H.264, 7.3.2.3.1
70730   **/
70731
70732  var findSeiNals = function (avcStream, samples, trackId) {
70733    var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
70734      result = {
70735        logs: [],
70736        seiNals: []
70737      },
70738      seiNal,
70739      i,
70740      length,
70741      lastMatchedSample;
70742    for (i = 0; i + 4 < avcStream.length; i += length) {
70743      length = avcView.getUint32(i);
70744      i += 4; // Bail if this doesn't appear to be an H264 stream
70745
70746      if (length <= 0) {
70747        continue;
70748      }
70749      switch (avcStream[i] & 0x1F) {
70750        case 0x06:
70751          var data = avcStream.subarray(i + 1, i + 1 + length);
70752          var matchingSample = mapToSample(i, samples);
70753          seiNal = {
70754            nalUnitType: 'sei_rbsp',
70755            size: length,
70756            data: data,
70757            escapedRBSP: discardEmulationPreventionBytes(data),
70758            trackId: trackId
70759          };
70760          if (matchingSample) {
70761            seiNal.pts = matchingSample.pts;
70762            seiNal.dts = matchingSample.dts;
70763            lastMatchedSample = matchingSample;
70764          } else if (lastMatchedSample) {
70765            // If a matching sample cannot be found, use the last
70766            // sample's values as they should be as close as possible
70767            seiNal.pts = lastMatchedSample.pts;
70768            seiNal.dts = lastMatchedSample.dts;
70769          } else {
70770            result.logs.push({
70771              level: 'warn',
70772              message: 'We\'ve encountered a nal unit without data at ' + i + ' for trackId ' + trackId + '. See mux.js#223.'
70773            });
70774            break;
70775          }
70776          result.seiNals.push(seiNal);
70777          break;
70778      }
70779    }
70780    return result;
70781  };
70782  /**
70783    * Parses out caption nals from an FMP4 segment's video tracks.
70784    *
70785    * @param {Uint8Array} segment - The bytes of a single segment
70786    * @param {Number} videoTrackId - The trackId of a video track in the segment
70787    * @return {Object.<Number, Object[]>} A mapping of video trackId to
70788    *   a list of seiNals found in that track
70789   **/
70790
70791  var parseCaptionNals = function (segment, videoTrackId) {
70792    var captionNals = {};
70793    var mdatTrafPairs = getMdatTrafPairs$1(segment);
70794    mdatTrafPairs.forEach(function (pair) {
70795      var mdat = pair.mdat;
70796      var traf = pair.traf;
70797      var tfhd = findBox$1(traf, ['tfhd']); // Exactly 1 tfhd per traf
70798
70799      var headerInfo = parseTfhd(tfhd[0]);
70800      var trackId = headerInfo.trackId;
70801      var tfdt = findBox$1(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
70802
70803      var baseMediaDecodeTime = tfdt.length > 0 ? parseTfdt$1(tfdt[0]).baseMediaDecodeTime : 0;
70804      var truns = findBox$1(traf, ['trun']);
70805      var samples;
70806      var result; // Only parse video data for the chosen video track
70807
70808      if (videoTrackId === trackId && truns.length > 0) {
70809        samples = parseSamples$1(truns, baseMediaDecodeTime, headerInfo);
70810        result = findSeiNals(mdat, samples, trackId);
70811        if (!captionNals[trackId]) {
70812          captionNals[trackId] = {
70813            seiNals: [],
70814            logs: []
70815          };
70816        }
70817        captionNals[trackId].seiNals = captionNals[trackId].seiNals.concat(result.seiNals);
70818        captionNals[trackId].logs = captionNals[trackId].logs.concat(result.logs);
70819      }
70820    });
70821    return captionNals;
70822  };
70823  /**
70824    * Parses out inband captions from an MP4 container and returns
70825    * caption objects that can be used by WebVTT and the TextTrack API.
70826    * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
70827    * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
70828    * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
70829    *
70830    * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
70831    * @param {Number} trackId - The id of the video track to parse
70832    * @param {Number} timescale - The timescale for the video track from the init segment
70833    *
70834    * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
70835    * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
70836    * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
70837    * @return {Object[]} parsedCaptions[].content - A list of individual caption segments
70838    * @return {String} parsedCaptions[].content.text - The visible content of the caption segment
70839    * @return {Number} parsedCaptions[].content.line - The line height from 1-15 for positioning of the caption segment
70840    * @return {Number} parsedCaptions[].content.position - The column indent percentage for cue positioning from 10-80
70841   **/
70842
70843  var parseEmbeddedCaptions = function (segment, trackId, timescale) {
70844    var captionNals; // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
70845
70846    if (trackId === null) {
70847      return null;
70848    }
70849    captionNals = parseCaptionNals(segment, trackId);
70850    var trackNals = captionNals[trackId] || {};
70851    return {
70852      seiNals: trackNals.seiNals,
70853      logs: trackNals.logs,
70854      timescale: timescale
70855    };
70856  };
70857  /**
70858    * Converts SEI NALUs into captions that can be used by video.js
70859   **/
70860
70861  var CaptionParser = function () {
70862    var isInitialized = false;
70863    var captionStream; // Stores segments seen before trackId and timescale are set
70864
70865    var segmentCache; // Stores video track ID of the track being parsed
70866
70867    var trackId; // Stores the timescale of the track being parsed
70868
70869    var timescale; // Stores captions parsed so far
70870
70871    var parsedCaptions; // Stores whether we are receiving partial data or not
70872
70873    var parsingPartial;
70874    /**
70875      * A method to indicate whether a CaptionParser has been initalized
70876      * @returns {Boolean}
70877     **/
70878
70879    this.isInitialized = function () {
70880      return isInitialized;
70881    };
70882    /**
70883      * Initializes the underlying CaptionStream, SEI NAL parsing
70884      * and management, and caption collection
70885     **/
70886
70887    this.init = function (options) {
70888      captionStream = new CaptionStream();
70889      isInitialized = true;
70890      parsingPartial = options ? options.isPartial : false; // Collect dispatched captions
70891
70892      captionStream.on('data', function (event) {
70893        // Convert to seconds in the source's timescale
70894        event.startTime = event.startPts / timescale;
70895        event.endTime = event.endPts / timescale;
70896        parsedCaptions.captions.push(event);
70897        parsedCaptions.captionStreams[event.stream] = true;
70898      });
70899      captionStream.on('log', function (log) {
70900        parsedCaptions.logs.push(log);
70901      });
70902    };
70903    /**
70904      * Determines if a new video track will be selected
70905      * or if the timescale changed
70906      * @return {Boolean}
70907     **/
70908
70909    this.isNewInit = function (videoTrackIds, timescales) {
70910      if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
70911        return false;
70912      }
70913      return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
70914    };
70915    /**
70916      * Parses out SEI captions and interacts with underlying
70917      * CaptionStream to return dispatched captions
70918      *
70919      * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
70920      * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
70921      * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
70922      * @see parseEmbeddedCaptions
70923      * @see m2ts/caption-stream.js
70924     **/
70925
70926    this.parse = function (segment, videoTrackIds, timescales) {
70927      var parsedData;
70928      if (!this.isInitialized()) {
70929        return null; // This is not likely to be a video segment
70930      } else if (!videoTrackIds || !timescales) {
70931        return null;
70932      } else if (this.isNewInit(videoTrackIds, timescales)) {
70933        // Use the first video track only as there is no
70934        // mechanism to switch to other video tracks
70935        trackId = videoTrackIds[0];
70936        timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
70937        // data until we have one.
70938        // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
70939      } else if (trackId === null || !timescale) {
70940        segmentCache.push(segment);
70941        return null;
70942      } // Now that a timescale and trackId is set, parse cached segments
70943
70944      while (segmentCache.length > 0) {
70945        var cachedSegment = segmentCache.shift();
70946        this.parse(cachedSegment, videoTrackIds, timescales);
70947      }
70948      parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
70949      if (parsedData && parsedData.logs) {
70950        parsedCaptions.logs = parsedCaptions.logs.concat(parsedData.logs);
70951      }
70952      if (parsedData === null || !parsedData.seiNals) {
70953        if (parsedCaptions.logs.length) {
70954          return {
70955            logs: parsedCaptions.logs,
70956            captions: [],
70957            captionStreams: []
70958          };
70959        }
70960        return null;
70961      }
70962      this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
70963
70964      this.flushStream();
70965      return parsedCaptions;
70966    };
70967    /**
70968      * Pushes SEI NALUs onto CaptionStream
70969      * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
70970      * Assumes that `parseCaptionNals` has been called first
70971      * @see m2ts/caption-stream.js
70972      **/
70973
70974    this.pushNals = function (nals) {
70975      if (!this.isInitialized() || !nals || nals.length === 0) {
70976        return null;
70977      }
70978      nals.forEach(function (nal) {
70979        captionStream.push(nal);
70980      });
70981    };
70982    /**
70983      * Flushes underlying CaptionStream to dispatch processed, displayable captions
70984      * @see m2ts/caption-stream.js
70985     **/
70986
70987    this.flushStream = function () {
70988      if (!this.isInitialized()) {
70989        return null;
70990      }
70991      if (!parsingPartial) {
70992        captionStream.flush();
70993      } else {
70994        captionStream.partialFlush();
70995      }
70996    };
70997    /**
70998      * Reset caption buckets for new data
70999     **/
71000
71001    this.clearParsedCaptions = function () {
71002      parsedCaptions.captions = [];
71003      parsedCaptions.captionStreams = {};
71004      parsedCaptions.logs = [];
71005    };
71006    /**
71007      * Resets underlying CaptionStream
71008      * @see m2ts/caption-stream.js
71009     **/
71010
71011    this.resetCaptionStream = function () {
71012      if (!this.isInitialized()) {
71013        return null;
71014      }
71015      captionStream.reset();
71016    };
71017    /**
71018      * Convenience method to clear all captions flushed from the
71019      * CaptionStream and still being parsed
71020      * @see m2ts/caption-stream.js
71021     **/
71022
71023    this.clearAllCaptions = function () {
71024      this.clearParsedCaptions();
71025      this.resetCaptionStream();
71026    };
71027    /**
71028      * Reset caption parser
71029     **/
71030
71031    this.reset = function () {
71032      segmentCache = [];
71033      trackId = null;
71034      timescale = null;
71035      if (!parsedCaptions) {
71036        parsedCaptions = {
71037          captions: [],
71038          // CC1, CC2, CC3, CC4
71039          captionStreams: {},
71040          logs: []
71041        };
71042      } else {
71043        this.clearParsedCaptions();
71044      }
71045      this.resetCaptionStream();
71046    };
71047    this.reset();
71048  };
71049  var captionParser = CaptionParser;
71050  const {
71051    parseTfdt
71052  } = mp4Inspector;
71053  const findBox = findBox_1;
71054  const {
71055    getTimescaleFromMediaHeader
71056  } = probe$2;
71057  const {
71058    parseSamples,
71059    getMdatTrafPairs
71060  } = samples;
71061  /**
71062   * Module for parsing WebVTT text and styles from FMP4 segments.
71063   * Based on the ISO/IEC 14496-30.
71064   */
71065
71066  const WebVttParser = function () {
71067    // default timescale to 90k
71068    let timescale = 90e3;
71069    /**
71070     * Parses the timescale from the init segment.
71071     * @param {Array<Uint8Array>} segment The initialization segment to parse the timescale from.
71072     */
71073
71074    this.init = function (segment) {
71075      // We just need the timescale from the init segment.
71076      const mdhd = findBox(segment, ['moov', 'trak', 'mdia', 'mdhd'])[0];
71077      if (mdhd) {
71078        timescale = getTimescaleFromMediaHeader(mdhd);
71079      }
71080    };
71081    /**
71082     * Parses a WebVTT FMP4 segment.
71083     * @param {Array<Uint8Array>} segment The content segment to parse the WebVTT cues from.
71084     * @returns The WebVTT cue text, styling, and timing info as an array of cue objects.
71085     */
71086
71087    this.parseSegment = function (segment) {
71088      const vttCues = [];
71089      const mdatTrafPairs = getMdatTrafPairs(segment);
71090      let baseMediaDecodeTime = 0;
71091      mdatTrafPairs.forEach(function (pair) {
71092        const mdatBox = pair.mdat;
71093        const trafBox = pair.traf; // zero or one.
71094
71095        const tfdtBox = findBox(trafBox, ['tfdt'])[0]; // zero or one.
71096
71097        const tfhdBox = findBox(trafBox, ['tfhd'])[0]; // zero or more.
71098
71099        const trunBoxes = findBox(trafBox, ['trun']);
71100        if (tfdtBox) {
71101          const tfdt = parseTfdt(tfdtBox);
71102          baseMediaDecodeTime = tfdt.baseMediaDecodeTime;
71103        }
71104        if (trunBoxes.length && tfhdBox) {
71105          const samples = parseSamples(trunBoxes, baseMediaDecodeTime, tfhdBox);
71106          let mdatOffset = 0;
71107          samples.forEach(function (sample) {
71108            // decode utf8 payload
71109            const UTF_8 = 'utf-8';
71110            const textDecoder = new TextDecoder(UTF_8); // extract sample data from the mdat box.
71111            // WebVTT Sample format:
71112            // Exactly one VTTEmptyCueBox box
71113            // OR one or more VTTCueBox boxes.
71114
71115            const sampleData = mdatBox.slice(mdatOffset, mdatOffset + sample.size); // single vtte box.
71116
71117            const vtteBox = findBox(sampleData, ['vtte'])[0]; // empty box
71118
71119            if (vtteBox) {
71120              mdatOffset += sample.size;
71121              return;
71122            } // TODO: Support 'vtta' boxes.
71123            // VTTAdditionalTextBoxes can be interleaved between VTTCueBoxes.
71124
71125            const vttcBoxes = findBox(sampleData, ['vttc']);
71126            vttcBoxes.forEach(function (vttcBox) {
71127              // mandatory payload box.
71128              const paylBox = findBox(vttcBox, ['payl'])[0]; // optional settings box
71129
71130              const sttgBox = findBox(vttcBox, ['sttg'])[0];
71131              const start = sample.pts / timescale;
71132              const end = (sample.pts + sample.duration) / timescale;
71133              let cueText, settings; // contains cue text.
71134
71135              if (paylBox) {
71136                try {
71137                  cueText = textDecoder.decode(paylBox);
71138                } catch (e) {
71139                  console.error(e);
71140                }
71141              } // settings box contains styling.
71142
71143              if (sttgBox) {
71144                try {
71145                  settings = textDecoder.decode(sttgBox);
71146                } catch (e) {
71147                  console.error(e);
71148                }
71149              }
71150              if (sample.duration && cueText) {
71151                vttCues.push({
71152                  cueText,
71153                  start,
71154                  end,
71155                  settings
71156                });
71157              }
71158            });
71159            mdatOffset += sample.size;
71160          });
71161        }
71162      });
71163      return vttCues;
71164    };
71165  };
71166  var webvttParser = WebVttParser;
71167  /**
71168   * mux.js
71169   *
71170   * Copyright (c) Brightcove
71171   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
71172   *
71173   * Utilities to detect basic properties and metadata about TS Segments.
71174   */
71175
71176  var StreamTypes$1 = streamTypes;
71177  var parsePid = function (packet) {
71178    var pid = packet[1] & 0x1f;
71179    pid <<= 8;
71180    pid |= packet[2];
71181    return pid;
71182  };
71183  var parsePayloadUnitStartIndicator = function (packet) {
71184    return !!(packet[1] & 0x40);
71185  };
71186  var parseAdaptionField = function (packet) {
71187    var offset = 0; // if an adaption field is present, its length is specified by the
71188    // fifth byte of the TS packet header. The adaptation field is
71189    // used to add stuffing to PES packets that don't fill a complete
71190    // TS packet, and to specify some forms of timing and control data
71191    // that we do not currently use.
71192
71193    if ((packet[3] & 0x30) >>> 4 > 0x01) {
71194      offset += packet[4] + 1;
71195    }
71196    return offset;
71197  };
71198  var parseType = function (packet, pmtPid) {
71199    var pid = parsePid(packet);
71200    if (pid === 0) {
71201      return 'pat';
71202    } else if (pid === pmtPid) {
71203      return 'pmt';
71204    } else if (pmtPid) {
71205      return 'pes';
71206    }
71207    return null;
71208  };
71209  var parsePat = function (packet) {
71210    var pusi = parsePayloadUnitStartIndicator(packet);
71211    var offset = 4 + parseAdaptionField(packet);
71212    if (pusi) {
71213      offset += packet[offset] + 1;
71214    }
71215    return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11];
71216  };
71217  var parsePmt = function (packet) {
71218    var programMapTable = {};
71219    var pusi = parsePayloadUnitStartIndicator(packet);
71220    var payloadOffset = 4 + parseAdaptionField(packet);
71221    if (pusi) {
71222      payloadOffset += packet[payloadOffset] + 1;
71223    } // PMTs can be sent ahead of the time when they should actually
71224    // take effect. We don't believe this should ever be the case
71225    // for HLS but we'll ignore "forward" PMT declarations if we see
71226    // them. Future PMT declarations have the current_next_indicator
71227    // set to zero.
71228
71229    if (!(packet[payloadOffset + 5] & 0x01)) {
71230      return;
71231    }
71232    var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section
71233
71234    sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2];
71235    tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
71236    // long the program info descriptors are
71237
71238    programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table
71239
71240    var offset = 12 + programInfoLength;
71241    while (offset < tableEnd) {
71242      var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type
71243
71244      programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry
71245      // skip past the elementary stream descriptors, if present
71246
71247      offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5;
71248    }
71249    return programMapTable;
71250  };
71251  var parsePesType = function (packet, programMapTable) {
71252    var pid = parsePid(packet);
71253    var type = programMapTable[pid];
71254    switch (type) {
71255      case StreamTypes$1.H264_STREAM_TYPE:
71256        return 'video';
71257      case StreamTypes$1.ADTS_STREAM_TYPE:
71258        return 'audio';
71259      case StreamTypes$1.METADATA_STREAM_TYPE:
71260        return 'timed-metadata';
71261      default:
71262        return null;
71263    }
71264  };
71265  var parsePesTime = function (packet) {
71266    var pusi = parsePayloadUnitStartIndicator(packet);
71267    if (!pusi) {
71268      return null;
71269    }
71270    var offset = 4 + parseAdaptionField(packet);
71271    if (offset >= packet.byteLength) {
71272      // From the H 222.0 MPEG-TS spec
71273      // "For transport stream packets carrying PES packets, stuffing is needed when there
71274      //  is insufficient PES packet data to completely fill the transport stream packet
71275      //  payload bytes. Stuffing is accomplished by defining an adaptation field longer than
71276      //  the sum of the lengths of the data elements in it, so that the payload bytes
71277      //  remaining after the adaptation field exactly accommodates the available PES packet
71278      //  data."
71279      //
71280      // If the offset is >= the length of the packet, then the packet contains no data
71281      // and instead is just adaption field stuffing bytes
71282      return null;
71283    }
71284    var pes = null;
71285    var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value
71286    // and a DTS value. Determine what combination of values is
71287    // available to work with.
71288
71289    ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number.  Javascript
71290    // performs all bitwise operations on 32-bit integers but javascript
71291    // supports a much greater range (52-bits) of integer using standard
71292    // mathematical operations.
71293    // We construct a 31-bit value using bitwise operators over the 31
71294    // most significant bits and then multiply by 4 (equal to a left-shift
71295    // of 2) before we add the final 2 least significant bits of the
71296    // timestamp (equal to an OR.)
71297
71298    if (ptsDtsFlags & 0xC0) {
71299      pes = {}; // the PTS and DTS are not written out directly. For information
71300      // on how they are encoded, see
71301      // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
71302
71303      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;
71304      pes.pts *= 4; // Left shift by 2
71305
71306      pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
71307
71308      pes.dts = pes.pts;
71309      if (ptsDtsFlags & 0x40) {
71310        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;
71311        pes.dts *= 4; // Left shift by 2
71312
71313        pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
71314      }
71315    }
71316    return pes;
71317  };
71318  var parseNalUnitType = function (type) {
71319    switch (type) {
71320      case 0x05:
71321        return 'slice_layer_without_partitioning_rbsp_idr';
71322      case 0x06:
71323        return 'sei_rbsp';
71324      case 0x07:
71325        return 'seq_parameter_set_rbsp';
71326      case 0x08:
71327        return 'pic_parameter_set_rbsp';
71328      case 0x09:
71329        return 'access_unit_delimiter_rbsp';
71330      default:
71331        return null;
71332    }
71333  };
71334  var videoPacketContainsKeyFrame = function (packet) {
71335    var offset = 4 + parseAdaptionField(packet);
71336    var frameBuffer = packet.subarray(offset);
71337    var frameI = 0;
71338    var frameSyncPoint = 0;
71339    var foundKeyFrame = false;
71340    var nalType; // advance the sync point to a NAL start, if necessary
71341
71342    for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
71343      if (frameBuffer[frameSyncPoint + 2] === 1) {
71344        // the sync point is properly aligned
71345        frameI = frameSyncPoint + 5;
71346        break;
71347      }
71348    }
71349    while (frameI < frameBuffer.byteLength) {
71350      // look at the current byte to determine if we've hit the end of
71351      // a NAL unit boundary
71352      switch (frameBuffer[frameI]) {
71353        case 0:
71354          // skip past non-sync sequences
71355          if (frameBuffer[frameI - 1] !== 0) {
71356            frameI += 2;
71357            break;
71358          } else if (frameBuffer[frameI - 2] !== 0) {
71359            frameI++;
71360            break;
71361          }
71362          if (frameSyncPoint + 3 !== frameI - 2) {
71363            nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
71364            if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
71365              foundKeyFrame = true;
71366            }
71367          } // drop trailing zeroes
71368
71369          do {
71370            frameI++;
71371          } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
71372          frameSyncPoint = frameI - 2;
71373          frameI += 3;
71374          break;
71375        case 1:
71376          // skip past non-sync sequences
71377          if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
71378            frameI += 3;
71379            break;
71380          }
71381          nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
71382          if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
71383            foundKeyFrame = true;
71384          }
71385          frameSyncPoint = frameI - 2;
71386          frameI += 3;
71387          break;
71388        default:
71389          // the current byte isn't a one or zero, so it cannot be part
71390          // of a sync sequence
71391          frameI += 3;
71392          break;
71393      }
71394    }
71395    frameBuffer = frameBuffer.subarray(frameSyncPoint);
71396    frameI -= frameSyncPoint;
71397    frameSyncPoint = 0; // parse the final nal
71398
71399    if (frameBuffer && frameBuffer.byteLength > 3) {
71400      nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
71401      if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
71402        foundKeyFrame = true;
71403      }
71404    }
71405    return foundKeyFrame;
71406  };
71407  var probe$1 = {
71408    parseType: parseType,
71409    parsePat: parsePat,
71410    parsePmt: parsePmt,
71411    parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
71412    parsePesType: parsePesType,
71413    parsePesTime: parsePesTime,
71414    videoPacketContainsKeyFrame: videoPacketContainsKeyFrame
71415  };
71416  /**
71417   * mux.js
71418   *
71419   * Copyright (c) Brightcove
71420   * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
71421   *
71422   * Parse mpeg2 transport stream packets to extract basic timing information
71423   */
71424
71425  var StreamTypes = streamTypes;
71426  var handleRollover = timestampRolloverStream.handleRollover;
71427  var probe = {};
71428  probe.ts = probe$1;
71429  probe.aac = utils;
71430  var ONE_SECOND_IN_TS = clock$2.ONE_SECOND_IN_TS;
71431  var MP2T_PACKET_LENGTH = 188,
71432    // bytes
71433    SYNC_BYTE = 0x47;
71434  /**
71435   * walks through segment data looking for pat and pmt packets to parse out
71436   * program map table information
71437   */
71438
71439  var parsePsi_ = function (bytes, pmt) {
71440    var startIndex = 0,
71441      endIndex = MP2T_PACKET_LENGTH,
71442      packet,
71443      type;
71444    while (endIndex < bytes.byteLength) {
71445      // Look for a pair of start and end sync bytes in the data..
71446      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
71447        // We found a packet
71448        packet = bytes.subarray(startIndex, endIndex);
71449        type = probe.ts.parseType(packet, pmt.pid);
71450        switch (type) {
71451          case 'pat':
71452            pmt.pid = probe.ts.parsePat(packet);
71453            break;
71454          case 'pmt':
71455            var table = probe.ts.parsePmt(packet);
71456            pmt.table = pmt.table || {};
71457            Object.keys(table).forEach(function (key) {
71458              pmt.table[key] = table[key];
71459            });
71460            break;
71461        }
71462        startIndex += MP2T_PACKET_LENGTH;
71463        endIndex += MP2T_PACKET_LENGTH;
71464        continue;
71465      } // If we get here, we have somehow become de-synchronized and we need to step
71466      // forward one byte at a time until we find a pair of sync bytes that denote
71467      // a packet
71468
71469      startIndex++;
71470      endIndex++;
71471    }
71472  };
71473  /**
71474   * walks through the segment data from the start and end to get timing information
71475   * for the first and last audio pes packets
71476   */
71477
71478  var parseAudioPes_ = function (bytes, pmt, result) {
71479    var startIndex = 0,
71480      endIndex = MP2T_PACKET_LENGTH,
71481      packet,
71482      type,
71483      pesType,
71484      pusi,
71485      parsed;
71486    var endLoop = false; // Start walking from start of segment to get first audio packet
71487
71488    while (endIndex <= bytes.byteLength) {
71489      // Look for a pair of start and end sync bytes in the data..
71490      if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
71491        // We found a packet
71492        packet = bytes.subarray(startIndex, endIndex);
71493        type = probe.ts.parseType(packet, pmt.pid);
71494        switch (type) {
71495          case 'pes':
71496            pesType = probe.ts.parsePesType(packet, pmt.table);
71497            pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
71498            if (pesType === 'audio' && pusi) {
71499              parsed = probe.ts.parsePesTime(packet);
71500              if (parsed) {
71501                parsed.type = 'audio';
71502                result.audio.push(parsed);
71503                endLoop = true;
71504              }
71505            }
71506            break;
71507        }
71508        if (endLoop) {
71509          break;
71510        }
71511        startIndex += MP2T_PACKET_LENGTH;
71512        endIndex += MP2T_PACKET_LENGTH;
71513        continue;
71514      } // If we get here, we have somehow become de-synchronized and we need to step
71515      // forward one byte at a time until we find a pair of sync bytes that denote
71516      // a packet
71517
71518      startIndex++;
71519      endIndex++;
71520    } // Start walking from end of segment to get last audio packet
71521
71522    endIndex = bytes.byteLength;
71523    startIndex = endIndex - MP2T_PACKET_LENGTH;
71524    endLoop = false;
71525    while (startIndex >= 0) {
71526      // Look for a pair of start and end sync bytes in the data..
71527      if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
71528        // We found a packet
71529        packet = bytes.subarray(startIndex, endIndex);
71530        type = probe.ts.parseType(packet, pmt.pid);
71531        switch (type) {
71532          case 'pes':
71533            pesType = probe.ts.parsePesType(packet, pmt.table);
71534            pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
71535            if (pesType === 'audio' && pusi) {
71536              parsed = probe.ts.parsePesTime(packet);
71537              if (parsed) {
71538                parsed.type = 'audio';
71539                result.audio.push(parsed);
71540                endLoop = true;
71541              }
71542            }
71543            break;
71544        }
71545        if (endLoop) {
71546          break;
71547        }
71548        startIndex -= MP2T_PACKET_LENGTH;
71549        endIndex -= MP2T_PACKET_LENGTH;
71550        continue;
71551      } // If we get here, we have somehow become de-synchronized and we need to step
71552      // forward one byte at a time until we find a pair of sync bytes that denote
71553      // a packet
71554
71555      startIndex--;
71556      endIndex--;
71557    }
71558  };
71559  /**
71560   * walks through the segment data from the start and end to get timing information
71561   * for the first and last video pes packets as well as timing information for the first
71562   * key frame.
71563   */
71564
71565  var parseVideoPes_ = function (bytes, pmt, result) {
71566    var startIndex = 0,
71567      endIndex = MP2T_PACKET_LENGTH,
71568      packet,
71569      type,
71570      pesType,
71571      pusi,
71572      parsed,
71573      frame,
71574      i,
71575      pes;
71576    var endLoop = false;
71577    var currentFrame = {
71578      data: [],
71579      size: 0
71580    }; // Start walking from start of segment to get first video packet
71581
71582    while (endIndex < bytes.byteLength) {
71583      // Look for a pair of start and end sync bytes in the data..
71584      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
71585        // We found a packet
71586        packet = bytes.subarray(startIndex, endIndex);
71587        type = probe.ts.parseType(packet, pmt.pid);
71588        switch (type) {
71589          case 'pes':
71590            pesType = probe.ts.parsePesType(packet, pmt.table);
71591            pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
71592            if (pesType === 'video') {
71593              if (pusi && !endLoop) {
71594                parsed = probe.ts.parsePesTime(packet);
71595                if (parsed) {
71596                  parsed.type = 'video';
71597                  result.video.push(parsed);
71598                  endLoop = true;
71599                }
71600              }
71601              if (!result.firstKeyFrame) {
71602                if (pusi) {
71603                  if (currentFrame.size !== 0) {
71604                    frame = new Uint8Array(currentFrame.size);
71605                    i = 0;
71606                    while (currentFrame.data.length) {
71607                      pes = currentFrame.data.shift();
71608                      frame.set(pes, i);
71609                      i += pes.byteLength;
71610                    }
71611                    if (probe.ts.videoPacketContainsKeyFrame(frame)) {
71612                      var firstKeyFrame = probe.ts.parsePesTime(frame); // PTS/DTS may not be available. Simply *not* setting
71613                      // the keyframe seems to work fine with HLS playback
71614                      // and definitely preferable to a crash with TypeError...
71615
71616                      if (firstKeyFrame) {
71617                        result.firstKeyFrame = firstKeyFrame;
71618                        result.firstKeyFrame.type = 'video';
71619                      } else {
71620                        // eslint-disable-next-line
71621                        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.');
71622                      }
71623                    }
71624                    currentFrame.size = 0;
71625                  }
71626                }
71627                currentFrame.data.push(packet);
71628                currentFrame.size += packet.byteLength;
71629              }
71630            }
71631            break;
71632        }
71633        if (endLoop && result.firstKeyFrame) {
71634          break;
71635        }
71636        startIndex += MP2T_PACKET_LENGTH;
71637        endIndex += MP2T_PACKET_LENGTH;
71638        continue;
71639      } // If we get here, we have somehow become de-synchronized and we need to step
71640      // forward one byte at a time until we find a pair of sync bytes that denote
71641      // a packet
71642
71643      startIndex++;
71644      endIndex++;
71645    } // Start walking from end of segment to get last video packet
71646
71647    endIndex = bytes.byteLength;
71648    startIndex = endIndex - MP2T_PACKET_LENGTH;
71649    endLoop = false;
71650    while (startIndex >= 0) {
71651      // Look for a pair of start and end sync bytes in the data..
71652      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
71653        // We found a packet
71654        packet = bytes.subarray(startIndex, endIndex);
71655        type = probe.ts.parseType(packet, pmt.pid);
71656        switch (type) {
71657          case 'pes':
71658            pesType = probe.ts.parsePesType(packet, pmt.table);
71659            pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
71660            if (pesType === 'video' && pusi) {
71661              parsed = probe.ts.parsePesTime(packet);
71662              if (parsed) {
71663                parsed.type = 'video';
71664                result.video.push(parsed);
71665                endLoop = true;
71666              }
71667            }
71668            break;
71669        }
71670        if (endLoop) {
71671          break;
71672        }
71673        startIndex -= MP2T_PACKET_LENGTH;
71674        endIndex -= MP2T_PACKET_LENGTH;
71675        continue;
71676      } // If we get here, we have somehow become de-synchronized and we need to step
71677      // forward one byte at a time until we find a pair of sync bytes that denote
71678      // a packet
71679
71680      startIndex--;
71681      endIndex--;
71682    }
71683  };
71684  /**
71685   * Adjusts the timestamp information for the segment to account for
71686   * rollover and convert to seconds based on pes packet timescale (90khz clock)
71687   */
71688
71689  var adjustTimestamp_ = function (segmentInfo, baseTimestamp) {
71690    if (segmentInfo.audio && segmentInfo.audio.length) {
71691      var audioBaseTimestamp = baseTimestamp;
71692      if (typeof audioBaseTimestamp === 'undefined' || isNaN(audioBaseTimestamp)) {
71693        audioBaseTimestamp = segmentInfo.audio[0].dts;
71694      }
71695      segmentInfo.audio.forEach(function (info) {
71696        info.dts = handleRollover(info.dts, audioBaseTimestamp);
71697        info.pts = handleRollover(info.pts, audioBaseTimestamp); // time in seconds
71698
71699        info.dtsTime = info.dts / ONE_SECOND_IN_TS;
71700        info.ptsTime = info.pts / ONE_SECOND_IN_TS;
71701      });
71702    }
71703    if (segmentInfo.video && segmentInfo.video.length) {
71704      var videoBaseTimestamp = baseTimestamp;
71705      if (typeof videoBaseTimestamp === 'undefined' || isNaN(videoBaseTimestamp)) {
71706        videoBaseTimestamp = segmentInfo.video[0].dts;
71707      }
71708      segmentInfo.video.forEach(function (info) {
71709        info.dts = handleRollover(info.dts, videoBaseTimestamp);
71710        info.pts = handleRollover(info.pts, videoBaseTimestamp); // time in seconds
71711
71712        info.dtsTime = info.dts / ONE_SECOND_IN_TS;
71713        info.ptsTime = info.pts / ONE_SECOND_IN_TS;
71714      });
71715      if (segmentInfo.firstKeyFrame) {
71716        var frame = segmentInfo.firstKeyFrame;
71717        frame.dts = handleRollover(frame.dts, videoBaseTimestamp);
71718        frame.pts = handleRollover(frame.pts, videoBaseTimestamp); // time in seconds
71719
71720        frame.dtsTime = frame.dts / ONE_SECOND_IN_TS;
71721        frame.ptsTime = frame.pts / ONE_SECOND_IN_TS;
71722      }
71723    }
71724  };
71725  /**
71726   * inspects the aac data stream for start and end time information
71727   */
71728
71729  var inspectAac_ = function (bytes) {
71730    var endLoop = false,
71731      audioCount = 0,
71732      sampleRate = null,
71733      timestamp = null,
71734      frameSize = 0,
71735      byteIndex = 0,
71736      packet;
71737    while (bytes.length - byteIndex >= 3) {
71738      var type = probe.aac.parseType(bytes, byteIndex);
71739      switch (type) {
71740        case 'timed-metadata':
71741          // Exit early because we don't have enough to parse
71742          // the ID3 tag header
71743          if (bytes.length - byteIndex < 10) {
71744            endLoop = true;
71745            break;
71746          }
71747          frameSize = probe.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
71748          // to emit a full packet
71749
71750          if (frameSize > bytes.length) {
71751            endLoop = true;
71752            break;
71753          }
71754          if (timestamp === null) {
71755            packet = bytes.subarray(byteIndex, byteIndex + frameSize);
71756            timestamp = probe.aac.parseAacTimestamp(packet);
71757          }
71758          byteIndex += frameSize;
71759          break;
71760        case 'audio':
71761          // Exit early because we don't have enough to parse
71762          // the ADTS frame header
71763          if (bytes.length - byteIndex < 7) {
71764            endLoop = true;
71765            break;
71766          }
71767          frameSize = probe.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
71768          // to emit a full packet
71769
71770          if (frameSize > bytes.length) {
71771            endLoop = true;
71772            break;
71773          }
71774          if (sampleRate === null) {
71775            packet = bytes.subarray(byteIndex, byteIndex + frameSize);
71776            sampleRate = probe.aac.parseSampleRate(packet);
71777          }
71778          audioCount++;
71779          byteIndex += frameSize;
71780          break;
71781        default:
71782          byteIndex++;
71783          break;
71784      }
71785      if (endLoop) {
71786        return null;
71787      }
71788    }
71789    if (sampleRate === null || timestamp === null) {
71790      return null;
71791    }
71792    var audioTimescale = ONE_SECOND_IN_TS / sampleRate;
71793    var result = {
71794      audio: [{
71795        type: 'audio',
71796        dts: timestamp,
71797        pts: timestamp
71798      }, {
71799        type: 'audio',
71800        dts: timestamp + audioCount * 1024 * audioTimescale,
71801        pts: timestamp + audioCount * 1024 * audioTimescale
71802      }]
71803    };
71804    return result;
71805  };
71806  /**
71807   * inspects the transport stream segment data for start and end time information
71808   * of the audio and video tracks (when present) as well as the first key frame's
71809   * start time.
71810   */
71811
71812  var inspectTs_ = function (bytes) {
71813    var pmt = {
71814      pid: null,
71815      table: null
71816    };
71817    var result = {};
71818    parsePsi_(bytes, pmt);
71819    for (var pid in pmt.table) {
71820      if (pmt.table.hasOwnProperty(pid)) {
71821        var type = pmt.table[pid];
71822        switch (type) {
71823          case StreamTypes.H264_STREAM_TYPE:
71824            result.video = [];
71825            parseVideoPes_(bytes, pmt, result);
71826            if (result.video.length === 0) {
71827              delete result.video;
71828            }
71829            break;
71830          case StreamTypes.ADTS_STREAM_TYPE:
71831            result.audio = [];
71832            parseAudioPes_(bytes, pmt, result);
71833            if (result.audio.length === 0) {
71834              delete result.audio;
71835            }
71836            break;
71837        }
71838      }
71839    }
71840    return result;
71841  };
71842  /**
71843   * Inspects segment byte data and returns an object with start and end timing information
71844   *
71845   * @param {Uint8Array} bytes The segment byte data
71846   * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame
71847   *  timestamps for rollover. This value must be in 90khz clock.
71848   * @return {Object} Object containing start and end frame timing info of segment.
71849   */
71850
71851  var inspect = function (bytes, baseTimestamp) {
71852    var isAacData = probe.aac.isLikelyAacData(bytes);
71853    var result;
71854    if (isAacData) {
71855      result = inspectAac_(bytes);
71856    } else {
71857      result = inspectTs_(bytes);
71858    }
71859    if (!result || !result.audio && !result.video) {
71860      return null;
71861    }
71862    adjustTimestamp_(result, baseTimestamp);
71863    return result;
71864  };
71865  var tsInspector = {
71866    inspect: inspect,
71867    parseAudioPes_: parseAudioPes_
71868  };
71869  /* global self */
71870
71871  /**
71872   * Re-emits transmuxer events by converting them into messages to the
71873   * world outside the worker.
71874   *
71875   * @param {Object} transmuxer the transmuxer to wire events on
71876   * @private
71877   */
71878
71879  const wireTransmuxerEvents = function (self, transmuxer) {
71880    transmuxer.on('data', function (segment) {
71881      // transfer ownership of the underlying ArrayBuffer
71882      // instead of doing a copy to save memory
71883      // ArrayBuffers are transferable but generic TypedArrays are not
71884      // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
71885      const initArray = segment.initSegment;
71886      segment.initSegment = {
71887        data: initArray.buffer,
71888        byteOffset: initArray.byteOffset,
71889        byteLength: initArray.byteLength
71890      };
71891      const typedArray = segment.data;
71892      segment.data = typedArray.buffer;
71893      self.postMessage({
71894        action: 'data',
71895        segment,
71896        byteOffset: typedArray.byteOffset,
71897        byteLength: typedArray.byteLength
71898      }, [segment.data]);
71899    });
71900    transmuxer.on('done', function (data) {
71901      self.postMessage({
71902        action: 'done'
71903      });
71904    });
71905    transmuxer.on('gopInfo', function (gopInfo) {
71906      self.postMessage({
71907        action: 'gopInfo',
71908        gopInfo
71909      });
71910    });
71911    transmuxer.on('videoSegmentTimingInfo', function (timingInfo) {
71912      const videoSegmentTimingInfo = {
71913        start: {
71914          decode: clock$2.videoTsToSeconds(timingInfo.start.dts),
71915          presentation: clock$2.videoTsToSeconds(timingInfo.start.pts)
71916        },
71917        end: {
71918          decode: clock$2.videoTsToSeconds(timingInfo.end.dts),
71919          presentation: clock$2.videoTsToSeconds(timingInfo.end.pts)
71920        },
71921        baseMediaDecodeTime: clock$2.videoTsToSeconds(timingInfo.baseMediaDecodeTime)
71922      };
71923      if (timingInfo.prependedContentDuration) {
71924        videoSegmentTimingInfo.prependedContentDuration = clock$2.videoTsToSeconds(timingInfo.prependedContentDuration);
71925      }
71926      self.postMessage({
71927        action: 'videoSegmentTimingInfo',
71928        videoSegmentTimingInfo
71929      });
71930    });
71931    transmuxer.on('audioSegmentTimingInfo', function (timingInfo) {
71932      // Note that all times for [audio/video]SegmentTimingInfo events are in video clock
71933      const audioSegmentTimingInfo = {
71934        start: {
71935          decode: clock$2.videoTsToSeconds(timingInfo.start.dts),
71936          presentation: clock$2.videoTsToSeconds(timingInfo.start.pts)
71937        },
71938        end: {
71939          decode: clock$2.videoTsToSeconds(timingInfo.end.dts),
71940          presentation: clock$2.videoTsToSeconds(timingInfo.end.pts)
71941        },
71942        baseMediaDecodeTime: clock$2.videoTsToSeconds(timingInfo.baseMediaDecodeTime)
71943      };
71944      if (timingInfo.prependedContentDuration) {
71945        audioSegmentTimingInfo.prependedContentDuration = clock$2.videoTsToSeconds(timingInfo.prependedContentDuration);
71946      }
71947      self.postMessage({
71948        action: 'audioSegmentTimingInfo',
71949        audioSegmentTimingInfo
71950      });
71951    });
71952    transmuxer.on('id3Frame', function (id3Frame) {
71953      self.postMessage({
71954        action: 'id3Frame',
71955        id3Frame
71956      });
71957    });
71958    transmuxer.on('caption', function (caption) {
71959      self.postMessage({
71960        action: 'caption',
71961        caption
71962      });
71963    });
71964    transmuxer.on('trackinfo', function (trackInfo) {
71965      self.postMessage({
71966        action: 'trackinfo',
71967        trackInfo
71968      });
71969    });
71970    transmuxer.on('audioTimingInfo', function (audioTimingInfo) {
71971      // convert to video TS since we prioritize video time over audio
71972      self.postMessage({
71973        action: 'audioTimingInfo',
71974        audioTimingInfo: {
71975          start: clock$2.videoTsToSeconds(audioTimingInfo.start),
71976          end: clock$2.videoTsToSeconds(audioTimingInfo.end)
71977        }
71978      });
71979    });
71980    transmuxer.on('videoTimingInfo', function (videoTimingInfo) {
71981      self.postMessage({
71982        action: 'videoTimingInfo',
71983        videoTimingInfo: {
71984          start: clock$2.videoTsToSeconds(videoTimingInfo.start),
71985          end: clock$2.videoTsToSeconds(videoTimingInfo.end)
71986        }
71987      });
71988    });
71989    transmuxer.on('log', function (log) {
71990      self.postMessage({
71991        action: 'log',
71992        log
71993      });
71994    });
71995  };
71996  /**
71997   * All incoming messages route through this hash. If no function exists
71998   * to handle an incoming message, then we ignore the message.
71999   *
72000   * @class MessageHandlers
72001   * @param {Object} options the options to initialize with
72002   */
72003
72004  class MessageHandlers {
72005    constructor(self, options) {
72006      this.options = options || {};
72007      this.self = self;
72008      this.init();
72009    }
72010    /**
72011     * initialize our web worker and wire all the events.
72012     */
72013
72014    init() {
72015      if (this.transmuxer) {
72016        this.transmuxer.dispose();
72017      }
72018      this.transmuxer = new transmuxer.Transmuxer(this.options);
72019      wireTransmuxerEvents(this.self, this.transmuxer);
72020    }
72021    pushMp4Captions(data) {
72022      if (!this.captionParser) {
72023        this.captionParser = new captionParser();
72024        this.captionParser.init();
72025      }
72026      const segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
72027      const parsed = this.captionParser.parse(segment, data.trackIds, data.timescales);
72028      this.self.postMessage({
72029        action: 'mp4Captions',
72030        captions: parsed && parsed.captions || [],
72031        logs: parsed && parsed.logs || [],
72032        data: segment.buffer
72033      }, [segment.buffer]);
72034    }
72035    /**
72036     * Initializes the WebVttParser and passes the init segment.
72037     *
72038     * @param {Uint8Array} data mp4 boxed WebVTT init segment data
72039     */
72040
72041    initMp4WebVttParser(data) {
72042      if (!this.webVttParser) {
72043        this.webVttParser = new webvttParser();
72044      }
72045      const segment = new Uint8Array(data.data, data.byteOffset, data.byteLength); // Set the timescale for the parser.
72046      // This can be called repeatedly in order to set and re-set the timescale.
72047
72048      this.webVttParser.init(segment);
72049    }
72050    /**
72051     * Parse an mp4 encapsulated WebVTT segment and return an array of cues.
72052     *
72053     * @param {Uint8Array} data a text/webvtt segment
72054     * @return {Object[]} an array of parsed cue objects
72055     */
72056
72057    getMp4WebVttText(data) {
72058      if (!this.webVttParser) {
72059        // timescale might not be set yet if the parser is created before an init segment is passed.
72060        // default timescale is 90k.
72061        this.webVttParser = new webvttParser();
72062      }
72063      const segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
72064      const parsed = this.webVttParser.parseSegment(segment);
72065      this.self.postMessage({
72066        action: 'getMp4WebVttText',
72067        mp4VttCues: parsed || [],
72068        data: segment.buffer
72069      }, [segment.buffer]);
72070    }
72071    probeMp4StartTime({
72072      timescales,
72073      data
72074    }) {
72075      const startTime = probe$2.startTime(timescales, data);
72076      this.self.postMessage({
72077        action: 'probeMp4StartTime',
72078        startTime,
72079        data
72080      }, [data.buffer]);
72081    }
72082    probeMp4Tracks({
72083      data
72084    }) {
72085      const tracks = probe$2.tracks(data);
72086      this.self.postMessage({
72087        action: 'probeMp4Tracks',
72088        tracks,
72089        data
72090      }, [data.buffer]);
72091    }
72092    /**
72093     * Probes an mp4 segment for EMSG boxes containing ID3 data.
72094     * https://aomediacodec.github.io/id3-emsg/
72095     *
72096     * @param {Uint8Array} data segment data
72097     * @param {number} offset segment start time
72098     * @return {Object[]} an array of ID3 frames
72099     */
72100
72101    probeEmsgID3({
72102      data,
72103      offset
72104    }) {
72105      const id3Frames = probe$2.getEmsgID3(data, offset);
72106      this.self.postMessage({
72107        action: 'probeEmsgID3',
72108        id3Frames,
72109        emsgData: data
72110      }, [data.buffer]);
72111    }
72112    /**
72113     * Probe an mpeg2-ts segment to determine the start time of the segment in it's
72114     * internal "media time," as well as whether it contains video and/or audio.
72115     *
72116     * @private
72117     * @param {Uint8Array} bytes - segment bytes
72118     * @param {number} baseStartTime
72119     *        Relative reference timestamp used when adjusting frame timestamps for rollover.
72120     *        This value should be in seconds, as it's converted to a 90khz clock within the
72121     *        function body.
72122     * @return {Object} The start time of the current segment in "media time" as well as
72123     *                  whether it contains video and/or audio
72124     */
72125
72126    probeTs({
72127      data,
72128      baseStartTime
72129    }) {
72130      const tsStartTime = typeof baseStartTime === 'number' && !isNaN(baseStartTime) ? baseStartTime * clock$2.ONE_SECOND_IN_TS : void 0;
72131      const timeInfo = tsInspector.inspect(data, tsStartTime);
72132      let result = null;
72133      if (timeInfo) {
72134        result = {
72135          // each type's time info comes back as an array of 2 times, start and end
72136          hasVideo: timeInfo.video && timeInfo.video.length === 2 || false,
72137          hasAudio: timeInfo.audio && timeInfo.audio.length === 2 || false
72138        };
72139        if (result.hasVideo) {
72140          result.videoStart = timeInfo.video[0].ptsTime;
72141        }
72142        if (result.hasAudio) {
72143          result.audioStart = timeInfo.audio[0].ptsTime;
72144        }
72145      }
72146      this.self.postMessage({
72147        action: 'probeTs',
72148        result,
72149        data
72150      }, [data.buffer]);
72151    }
72152    clearAllMp4Captions() {
72153      if (this.captionParser) {
72154        this.captionParser.clearAllCaptions();
72155      }
72156    }
72157    clearParsedMp4Captions() {
72158      if (this.captionParser) {
72159        this.captionParser.clearParsedCaptions();
72160      }
72161    }
72162    /**
72163     * Adds data (a ts segment) to the start of the transmuxer pipeline for
72164     * processing.
72165     *
72166     * @param {ArrayBuffer} data data to push into the muxer
72167     */
72168
72169    push(data) {
72170      // Cast array buffer to correct type for transmuxer
72171      const segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
72172      this.transmuxer.push(segment);
72173    }
72174    /**
72175     * Recreate the transmuxer so that the next segment added via `push`
72176     * start with a fresh transmuxer.
72177     */
72178
72179    reset() {
72180      this.transmuxer.reset();
72181    }
72182    /**
72183     * Set the value that will be used as the `baseMediaDecodeTime` time for the
72184     * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
72185     * set relative to the first based on the PTS values.
72186     *
72187     * @param {Object} data used to set the timestamp offset in the muxer
72188     */
72189
72190    setTimestampOffset(data) {
72191      const timestampOffset = data.timestampOffset || 0;
72192      this.transmuxer.setBaseMediaDecodeTime(Math.round(clock$2.secondsToVideoTs(timestampOffset)));
72193    }
72194    setAudioAppendStart(data) {
72195      this.transmuxer.setAudioAppendStart(Math.ceil(clock$2.secondsToVideoTs(data.appendStart)));
72196    }
72197    setRemux(data) {
72198      this.transmuxer.setRemux(data.remux);
72199    }
72200    /**
72201     * Forces the pipeline to finish processing the last segment and emit it's
72202     * results.
72203     *
72204     * @param {Object} data event data, not really used
72205     */
72206
72207    flush(data) {
72208      this.transmuxer.flush(); // transmuxed done action is fired after both audio/video pipelines are flushed
72209
72210      self.postMessage({
72211        action: 'done',
72212        type: 'transmuxed'
72213      });
72214    }
72215    endTimeline() {
72216      this.transmuxer.endTimeline(); // transmuxed endedtimeline action is fired after both audio/video pipelines end their
72217      // timelines
72218
72219      self.postMessage({
72220        action: 'endedtimeline',
72221        type: 'transmuxed'
72222      });
72223    }
72224    alignGopsWith(data) {
72225      this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
72226    }
72227  }
72228  /**
72229   * Our web worker interface so that things can talk to mux.js
72230   * that will be running in a web worker. the scope is passed to this by
72231   * webworkify.
72232   *
72233   * @param {Object} self the scope for the web worker
72234   */
72235
72236  self.onmessage = function (event) {
72237    if (event.data.action === 'init' && event.data.options) {
72238      this.messageHandlers = new MessageHandlers(self, event.data.options);
72239      return;
72240    }
72241    if (!this.messageHandlers) {
72242      this.messageHandlers = new MessageHandlers(self);
72243    }
72244    if (event.data && event.data.action && event.data.action !== 'init') {
72245      if (this.messageHandlers[event.data.action]) {
72246        this.messageHandlers[event.data.action](event.data);
72247      }
72248    }
72249  };
72250}));
vendor: 8,929 bytes, lines 72251-72556
72251var TransmuxWorker = factory(workerCode$1);
72252/* rollup-plugin-worker-factory end for worker!/Users/srimron-soutter/repos/github/vjs/http-streaming/src/transmuxer-worker.js */
72253
72254const handleData_ = (event, transmuxedData, callback) => {
72255  const {
72256    type,
72257    initSegment,
72258    captions,
72259    captionStreams,
72260    metadata,
72261    videoFrameDtsTime,
72262    videoFramePtsTime
72263  } = event.data.segment;
72264  transmuxedData.buffer.push({
72265    captions,
72266    captionStreams,
72267    metadata
72268  });
72269  const boxes = event.data.segment.boxes || {
72270    data: event.data.segment.data
72271  };
72272  const result = {
72273    type,
72274    // cast ArrayBuffer to TypedArray
72275    data: new Uint8Array(boxes.data, boxes.data.byteOffset, boxes.data.byteLength),
72276    initSegment: new Uint8Array(initSegment.data, initSegment.byteOffset, initSegment.byteLength)
72277  };
72278  if (typeof videoFrameDtsTime !== 'undefined') {
72279    result.videoFrameDtsTime = videoFrameDtsTime;
72280  }
72281  if (typeof videoFramePtsTime !== 'undefined') {
72282    result.videoFramePtsTime = videoFramePtsTime;
72283  }
72284  callback(result);
72285};
72286const handleDone_ = ({
72287  transmuxedData,
72288  callback
72289}) => {
72290  // Previously we only returned data on data events,
72291  // not on done events. Clear out the buffer to keep that consistent.
72292  transmuxedData.buffer = []; // all buffers should have been flushed from the muxer, so start processing anything we
72293  // have received
72294
72295  callback(transmuxedData);
72296};
72297const handleGopInfo_ = (event, transmuxedData) => {
72298  transmuxedData.gopInfo = event.data.gopInfo;
72299};
72300const processTransmux = options => {
72301  const {
72302    transmuxer,
72303    bytes,
72304    audioAppendStart,
72305    gopsToAlignWith,
72306    remux,
72307    onData,
72308    onTrackInfo,
72309    onAudioTimingInfo,
72310    onVideoTimingInfo,
72311    onVideoSegmentTimingInfo,
72312    onAudioSegmentTimingInfo,
72313    onId3,
72314    onCaptions,
72315    onDone,
72316    onEndedTimeline,
72317    onTransmuxerLog,
72318    isEndOfTimeline,
72319    segment,
72320    triggerSegmentEventFn
72321  } = options;
72322  const transmuxedData = {
72323    buffer: []
72324  };
72325  let waitForEndedTimelineEvent = isEndOfTimeline;
72326  const handleMessage = event => {
72327    if (transmuxer.currentTransmux !== options) {
72328      // disposed
72329      return;
72330    }
72331    if (event.data.action === 'data') {
72332      handleData_(event, transmuxedData, onData);
72333    }
72334    if (event.data.action === 'trackinfo') {
72335      onTrackInfo(event.data.trackInfo);
72336    }
72337    if (event.data.action === 'gopInfo') {
72338      handleGopInfo_(event, transmuxedData);
72339    }
72340    if (event.data.action === 'audioTimingInfo') {
72341      onAudioTimingInfo(event.data.audioTimingInfo);
72342    }
72343    if (event.data.action === 'videoTimingInfo') {
72344      onVideoTimingInfo(event.data.videoTimingInfo);
72345    }
72346    if (event.data.action === 'videoSegmentTimingInfo') {
72347      onVideoSegmentTimingInfo(event.data.videoSegmentTimingInfo);
72348    }
72349    if (event.data.action === 'audioSegmentTimingInfo') {
72350      onAudioSegmentTimingInfo(event.data.audioSegmentTimingInfo);
72351    }
72352    if (event.data.action === 'id3Frame') {
72353      onId3([event.data.id3Frame], event.data.id3Frame.dispatchType);
72354    }
72355    if (event.data.action === 'caption') {
72356      onCaptions(event.data.caption);
72357    }
72358    if (event.data.action === 'endedtimeline') {
72359      waitForEndedTimelineEvent = false;
72360      onEndedTimeline();
72361    }
72362    if (event.data.action === 'log') {
72363      onTransmuxerLog(event.data.log);
72364    } // wait for the transmuxed event since we may have audio and video
72365
72366    if (event.data.type !== 'transmuxed') {
72367      return;
72368    } // If the "endedtimeline" event has not yet fired, and this segment represents the end
72369    // of a timeline, that means there may still be data events before the segment
72370    // processing can be considerred complete. In that case, the final event should be
72371    // an "endedtimeline" event with the type "transmuxed."
72372
72373    if (waitForEndedTimelineEvent) {
72374      return;
72375    }
72376    transmuxer.onmessage = null;
72377    handleDone_({
72378      transmuxedData,
72379      callback: onDone
72380    });
72381    /* eslint-disable no-use-before-define */
72382
72383    dequeue(transmuxer);
72384    /* eslint-enable */
72385  };
72386  const handleError = () => {
72387    const error = {
72388      message: 'Received an error message from the transmuxer worker',
72389      metadata: {
72390        errorType: videojs.Error.StreamingFailedToTransmuxSegment,
72391        segmentInfo: segmentInfoPayload({
72392          segment
72393        })
72394      }
72395    };
72396    onDone(null, error);
72397  };
72398  transmuxer.onmessage = handleMessage;
72399  transmuxer.onerror = handleError;
72400  if (audioAppendStart) {
72401    transmuxer.postMessage({
72402      action: 'setAudioAppendStart',
72403      appendStart: audioAppendStart
72404    });
72405  } // allow empty arrays to be passed to clear out GOPs
72406
72407  if (Array.isArray(gopsToAlignWith)) {
72408    transmuxer.postMessage({
72409      action: 'alignGopsWith',
72410      gopsToAlignWith
72411    });
72412  }
72413  if (typeof remux !== 'undefined') {
72414    transmuxer.postMessage({
72415      action: 'setRemux',
72416      remux
72417    });
72418  }
72419  if (bytes.byteLength) {
72420    const buffer = bytes instanceof ArrayBuffer ? bytes : bytes.buffer;
72421    const byteOffset = bytes instanceof ArrayBuffer ? 0 : bytes.byteOffset;
72422    triggerSegmentEventFn({
72423      type: 'segmenttransmuxingstart',
72424      segment
72425    });
72426    transmuxer.postMessage({
72427      action: 'push',
72428      // Send the typed-array of data as an ArrayBuffer so that
72429      // it can be sent as a "Transferable" and avoid the costly
72430      // memory copy
72431      data: buffer,
72432      // To recreate the original typed-array, we need information
72433      // about what portion of the ArrayBuffer it was a view into
72434      byteOffset,
72435      byteLength: bytes.byteLength
72436    }, [buffer]);
72437  }
72438  if (isEndOfTimeline) {
72439    transmuxer.postMessage({
72440      action: 'endTimeline'
72441    });
72442  } // even if we didn't push any bytes, we have to make sure we flush in case we reached
72443  // the end of the segment
72444
72445  transmuxer.postMessage({
72446    action: 'flush'
72447  });
72448};
72449const dequeue = transmuxer => {
72450  transmuxer.currentTransmux = null;
72451  if (transmuxer.transmuxQueue.length) {
72452    transmuxer.currentTransmux = transmuxer.transmuxQueue.shift();
72453    if (typeof transmuxer.currentTransmux === 'function') {
72454      transmuxer.currentTransmux();
72455    } else {
72456      processTransmux(transmuxer.currentTransmux);
72457    }
72458  }
72459};
72460const processAction = (transmuxer, action) => {
72461  transmuxer.postMessage({
72462    action
72463  });
72464  dequeue(transmuxer);
72465};
72466const enqueueAction = (action, transmuxer) => {
72467  if (!transmuxer.currentTransmux) {
72468    transmuxer.currentTransmux = action;
72469    processAction(transmuxer, action);
72470    return;
72471  }
72472  transmuxer.transmuxQueue.push(processAction.bind(null, transmuxer, action));
72473};
72474const reset = transmuxer => {
72475  enqueueAction('reset', transmuxer);
72476};
72477const endTimeline = transmuxer => {
72478  enqueueAction('endTimeline', transmuxer);
72479};
72480const transmux = options => {
72481  if (!options.transmuxer.currentTransmux) {
72482    options.transmuxer.currentTransmux = options;
72483    processTransmux(options);
72484    return;
72485  }
72486  options.transmuxer.transmuxQueue.push(options);
72487};
72488const createTransmuxer = options => {
72489  const transmuxer = new TransmuxWorker();
72490  transmuxer.currentTransmux = null;
72491  transmuxer.transmuxQueue = [];
72492  const term = transmuxer.terminate;
72493  transmuxer.terminate = () => {
72494    transmuxer.currentTransmux = null;
72495    transmuxer.transmuxQueue.length = 0;
72496    return term.call(transmuxer);
72497  };
72498  transmuxer.postMessage({
72499    action: 'init',
72500    options
72501  });
72502  return transmuxer;
72503};
72504var segmentTransmuxer = {
72505  reset,
72506  endTimeline,
72507  transmux,
72508  createTransmuxer
72509};
72510const workerCallback = function (options) {
72511  const transmuxer = options.transmuxer;
72512  const endAction = options.endAction || options.action;
72513  const callback = options.callback;
72514  const message = _extends__default["default"]({}, options, {
72515    endAction: null,
72516    transmuxer: null,
72517    callback: null
72518  });
72519  const listenForEndEvent = event => {
72520    if (event.data.action !== endAction) {
72521      return;
72522    }
72523    transmuxer.removeEventListener('message', listenForEndEvent); // transfer ownership of bytes back to us.
72524
72525    if (event.data.data) {
72526      event.data.data = new Uint8Array(event.data.data, options.byteOffset || 0, options.byteLength || event.data.data.byteLength);
72527      if (options.data) {
72528        options.data = event.data.data;
72529      }
72530    }
72531    callback(event.data);
72532  };
72533  transmuxer.addEventListener('message', listenForEndEvent);
72534  if (options.data) {
72535    const isArrayBuffer = options.data instanceof ArrayBuffer;
72536    message.byteOffset = isArrayBuffer ? 0 : options.data.byteOffset;
72537    message.byteLength = options.data.byteLength;
72538    const transfers = [isArrayBuffer ? options.data : options.data.buffer];
72539    transmuxer.postMessage(message, transfers);
72540  } else {
72541    transmuxer.postMessage(message);
72542  }
72543};
72544const REQUEST_ERRORS = {
72545  FAILURE: 2,
72546  TIMEOUT: -101,
72547  ABORTED: -102
72548};
72549const WEB_VTT_CODEC = 'wvtt';
72550/**
72551 * Abort all requests
72552 *
72553 * @param {Object} activeXhrs - an object that tracks all XHR requests
72554 */
72555
72556const abortAll = activeXhrs => {
vendor: 3,122 bytes, lines 72557-72663
72557  activeXhrs.forEach(xhr => {
72558    xhr.abort();
72559  });
72560};
72561/**
72562 * Gather important bandwidth stats once a request has completed
72563 *
72564 * @param {Object} request - the XHR request from which to gather stats
72565 */
72566
72567const getRequestStats = request => {
72568  return {
72569    bandwidth: request.bandwidth,
72570    bytesReceived: request.bytesReceived || 0,
72571    roundTripTime: request.roundTripTime || 0
72572  };
72573};
72574/**
72575 * If possible gather bandwidth stats as a request is in
72576 * progress
72577 *
72578 * @param {Event} progressEvent - an event object from an XHR's progress event
72579 */
72580
72581const getProgressStats = progressEvent => {
72582  const request = progressEvent.target;
72583  const roundTripTime = Date.now() - request.requestTime;
72584  const stats = {
72585    bandwidth: Infinity,
72586    bytesReceived: 0,
72587    roundTripTime: roundTripTime || 0
72588  };
72589  stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok
72590  // because we should only use bandwidth stats on progress to determine when
72591  // abort a request early due to insufficient bandwidth
72592
72593  stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000);
72594  return stats;
72595};
72596/**
72597 * Handle all error conditions in one place and return an object
72598 * with all the information
72599 *
72600 * @param {Error|null} error - if non-null signals an error occured with the XHR
72601 * @param {Object} request -  the XHR request that possibly generated the error
72602 */
72603
72604const handleErrors = (error, request) => {
72605  const {
72606    requestType
72607  } = request;
72608  const metadata = getStreamingNetworkErrorMetadata({
72609    requestType,
72610    request,
72611    error
72612  });
72613  if (request.timedout) {
72614    return {
72615      status: request.status,
72616      message: 'HLS request timed-out at URL: ' + request.uri,
72617      code: REQUEST_ERRORS.TIMEOUT,
72618      xhr: request,
72619      metadata
72620    };
72621  }
72622  if (request.aborted) {
72623    return {
72624      status: request.status,
72625      message: 'HLS request aborted at URL: ' + request.uri,
72626      code: REQUEST_ERRORS.ABORTED,
72627      xhr: request,
72628      metadata
72629    };
72630  }
72631  if (error) {
72632    return {
72633      status: request.status,
72634      message: 'HLS request errored at URL: ' + request.uri,
72635      code: REQUEST_ERRORS.FAILURE,
72636      xhr: request,
72637      metadata
72638    };
72639  }
72640  if (request.responseType === 'arraybuffer' && request.response.byteLength === 0) {
72641    return {
72642      status: request.status,
72643      message: 'Empty HLS response at URL: ' + request.uri,
72644      code: REQUEST_ERRORS.FAILURE,
72645      xhr: request,
72646      metadata
72647    };
72648  }
72649  return null;
72650};
72651/**
72652 * Handle responses for key data and convert the key data to the correct format
72653 * for the decryption step later
72654 *
72655 * @param {Object} segment - a simplified copy of the segmentInfo object
72656 *                           from SegmentLoader
72657 * @param {Array} objects - objects to add the key bytes to.
72658 * @param {Function} finishProcessingFn - a callback to execute to continue processing
72659 *                                        this request
72660 */
72661
72662const handleKeyResponse = (segment, objects, finishProcessingFn, triggerSegmentEventFn) => (error, request) => {
72663  const response = request.response;
vendor: 4,540 bytes, lines 72664-72818
72664  const errorObj = handleErrors(error, request);
72665  if (errorObj) {
72666    return finishProcessingFn(errorObj, segment);
72667  }
72668  if (response.byteLength !== 16) {
72669    return finishProcessingFn({
72670      status: request.status,
72671      message: 'Invalid HLS key at URL: ' + request.uri,
72672      code: REQUEST_ERRORS.FAILURE,
72673      xhr: request
72674    }, segment);
72675  }
72676  const view = new DataView(response);
72677  const bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
72678  for (let i = 0; i < objects.length; i++) {
72679    objects[i].bytes = bytes;
72680  }
72681  const keyInfo = {
72682    uri: request.uri
72683  };
72684  triggerSegmentEventFn({
72685    type: 'segmentkeyloadcomplete',
72686    segment,
72687    keyInfo
72688  });
72689  return finishProcessingFn(null, segment);
72690};
72691/**
72692 * Processes an mp4 init segment depending on the codec through the transmuxer.
72693 *
72694 * @param {Object} segment init segment to process
72695 * @param {string} codec the codec of the text segments
72696 */
72697
72698const initMp4Text = (segment, codec) => {
72699  if (codec === WEB_VTT_CODEC) {
72700    segment.transmuxer.postMessage({
72701      action: 'initMp4WebVttParser',
72702      data: segment.map.bytes
72703    });
72704  }
72705};
72706/**
72707 * Parses an mp4 text segment with the transmuxer and calls the doneFn from
72708 * the segment loader.
72709 *
72710 * @param {Object} segment the text segment to parse
72711 * @param {string} codec the codec of the text segment
72712 * @param {Function} doneFn the doneFn passed from the segment loader
72713 */
72714
72715const parseMp4TextSegment = (segment, codec, doneFn) => {
72716  if (codec === WEB_VTT_CODEC) {
72717    workerCallback({
72718      action: 'getMp4WebVttText',
72719      data: segment.bytes,
72720      transmuxer: segment.transmuxer,
72721      callback: ({
72722        data,
72723        mp4VttCues
72724      }) => {
72725        segment.bytes = data;
72726        doneFn(null, segment, {
72727          mp4VttCues
72728        });
72729      }
72730    });
72731  }
72732};
72733const parseInitSegment = (segment, callback) => {
72734  const type = containers.detectContainerForBytes(segment.map.bytes); // TODO: We should also handle ts init segments here, but we
72735  // only know how to parse mp4 init segments at the moment
72736
72737  if (type !== 'mp4') {
72738    const uri = segment.map.resolvedUri || segment.map.uri;
72739    const mediaType = type || 'unknown';
72740    return callback({
72741      internal: true,
72742      message: `Found unsupported ${mediaType} container for initialization segment at URL: ${uri}`,
72743      code: REQUEST_ERRORS.FAILURE,
72744      metadata: {
72745        mediaType
72746      }
72747    });
72748  }
72749  workerCallback({
72750    action: 'probeMp4Tracks',
72751    data: segment.map.bytes,
72752    transmuxer: segment.transmuxer,
72753    callback: ({
72754      tracks,
72755      data
72756    }) => {
72757      // transfer bytes back to us
72758      segment.map.bytes = data;
72759      tracks.forEach(function (track) {
72760        segment.map.tracks = segment.map.tracks || {}; // only support one track of each type for now
72761
72762        if (segment.map.tracks[track.type]) {
72763          return;
72764        }
72765        segment.map.tracks[track.type] = track;
72766        if (typeof track.id === 'number' && track.timescale) {
72767          segment.map.timescales = segment.map.timescales || {};
72768          segment.map.timescales[track.id] = track.timescale;
72769        }
72770        if (track.type === 'text') {
72771          initMp4Text(segment, track.codec);
72772        }
72773      });
72774      return callback(null);
72775    }
72776  });
72777};
72778/**
72779 * Handle init-segment responses
72780 *
72781 * @param {Object} segment - a simplified copy of the segmentInfo object
72782 *                           from SegmentLoader
72783 * @param {Function} finishProcessingFn - a callback to execute to continue processing
72784 *                                        this request
72785 */
72786
72787const handleInitSegmentResponse = ({
72788  segment,
72789  finishProcessingFn,
72790  triggerSegmentEventFn
72791}) => (error, request) => {
72792  const errorObj = handleErrors(error, request);
72793  if (errorObj) {
72794    return finishProcessingFn(errorObj, segment);
72795  }
72796  const bytes = new Uint8Array(request.response);
72797  triggerSegmentEventFn({
72798    type: 'segmentloaded',
72799    segment
72800  }); // init segment is encypted, we will have to wait
72801  // until the key request is done to decrypt.
72802
72803  if (segment.map.key) {
72804    segment.map.encryptedBytes = bytes;
72805    return finishProcessingFn(null, segment);
72806  }
72807  segment.map.bytes = bytes;
72808  parseInitSegment(segment, function (parseError) {
72809    if (parseError) {
72810      parseError.xhr = request;
72811      parseError.status = request.status;
72812      return finishProcessingFn(parseError, segment);
72813    }
72814    finishProcessingFn(null, segment);
72815  });
72816};
72817/**
72818 * Response handler for segment-requests being sure to set the correct
vendor: 6,295 bytes, lines 72819-73013
72819 * property depending on whether the segment is encryped or not
72820 * Also records and keeps track of stats that are used for ABR purposes
72821 *
72822 * @param {Object} segment - a simplified copy of the segmentInfo object
72823 *                           from SegmentLoader
72824 * @param {Function} finishProcessingFn - a callback to execute to continue processing
72825 *                                        this request
72826 */
72827
72828const handleSegmentResponse = ({
72829  segment,
72830  finishProcessingFn,
72831  responseType,
72832  triggerSegmentEventFn
72833}) => (error, request) => {
72834  const errorObj = handleErrors(error, request);
72835  if (errorObj) {
72836    return finishProcessingFn(errorObj, segment);
72837  }
72838  triggerSegmentEventFn({
72839    type: 'segmentloaded',
72840    segment
72841  });
72842  const newBytes =
72843  // although responseText "should" exist, this guard serves to prevent an error being
72844  // thrown for two primary cases:
72845  // 1. the mime type override stops working, or is not implemented for a specific
72846  //    browser
72847  // 2. when using mock XHR libraries like sinon that do not allow the override behavior
72848  responseType === 'arraybuffer' || !request.responseText ? request.response : stringToArrayBuffer(request.responseText.substring(segment.lastReachedChar || 0));
72849  segment.stats = getRequestStats(request);
72850  if (segment.key) {
72851    segment.encryptedBytes = new Uint8Array(newBytes);
72852  } else {
72853    segment.bytes = new Uint8Array(newBytes);
72854  }
72855  return finishProcessingFn(null, segment);
72856};
72857const transmuxAndNotify = ({
72858  segment,
72859  bytes,
72860  trackInfoFn,
72861  timingInfoFn,
72862  videoSegmentTimingInfoFn,
72863  audioSegmentTimingInfoFn,
72864  id3Fn,
72865  captionsFn,
72866  isEndOfTimeline,
72867  endedTimelineFn,
72868  dataFn,
72869  doneFn,
72870  onTransmuxerLog,
72871  triggerSegmentEventFn
72872}) => {
72873  const fmp4Tracks = segment.map && segment.map.tracks || {};
72874  const isMuxed = Boolean(fmp4Tracks.audio && fmp4Tracks.video); // Keep references to each function so we can null them out after we're done with them.
72875  // One reason for this is that in the case of full segments, we want to trust start
72876  // times from the probe, rather than the transmuxer.
72877
72878  let audioStartFn = timingInfoFn.bind(null, segment, 'audio', 'start');
72879  const audioEndFn = timingInfoFn.bind(null, segment, 'audio', 'end');
72880  let videoStartFn = timingInfoFn.bind(null, segment, 'video', 'start');
72881  const videoEndFn = timingInfoFn.bind(null, segment, 'video', 'end');
72882  const finish = () => transmux({
72883    bytes,
72884    transmuxer: segment.transmuxer,
72885    audioAppendStart: segment.audioAppendStart,
72886    gopsToAlignWith: segment.gopsToAlignWith,
72887    remux: isMuxed,
72888    onData: result => {
72889      result.type = result.type === 'combined' ? 'video' : result.type;
72890      dataFn(segment, result);
72891    },
72892    onTrackInfo: trackInfo => {
72893      if (trackInfoFn) {
72894        if (isMuxed) {
72895          trackInfo.isMuxed = true;
72896        }
72897        trackInfoFn(segment, trackInfo);
72898      }
72899    },
72900    onAudioTimingInfo: audioTimingInfo => {
72901      // we only want the first start value we encounter
72902      if (audioStartFn && typeof audioTimingInfo.start !== 'undefined') {
72903        audioStartFn(audioTimingInfo.start);
72904        audioStartFn = null;
72905      } // we want to continually update the end time
72906
72907      if (audioEndFn && typeof audioTimingInfo.end !== 'undefined') {
72908        audioEndFn(audioTimingInfo.end);
72909      }
72910    },
72911    onVideoTimingInfo: videoTimingInfo => {
72912      // we only want the first start value we encounter
72913      if (videoStartFn && typeof videoTimingInfo.start !== 'undefined') {
72914        videoStartFn(videoTimingInfo.start);
72915        videoStartFn = null;
72916      } // we want to continually update the end time
72917
72918      if (videoEndFn && typeof videoTimingInfo.end !== 'undefined') {
72919        videoEndFn(videoTimingInfo.end);
72920      }
72921    },
72922    onVideoSegmentTimingInfo: videoSegmentTimingInfo => {
72923      const timingInfo = {
72924        pts: {
72925          start: videoSegmentTimingInfo.start.presentation,
72926          end: videoSegmentTimingInfo.end.presentation
72927        },
72928        dts: {
72929          start: videoSegmentTimingInfo.start.decode,
72930          end: videoSegmentTimingInfo.end.decode
72931        }
72932      };
72933      triggerSegmentEventFn({
72934        type: 'segmenttransmuxingtiminginfoavailable',
72935        segment,
72936        timingInfo
72937      });
72938      videoSegmentTimingInfoFn(videoSegmentTimingInfo);
72939    },
72940    onAudioSegmentTimingInfo: audioSegmentTimingInfo => {
72941      const timingInfo = {
72942        pts: {
72943          start: audioSegmentTimingInfo.start.pts,
72944          end: audioSegmentTimingInfo.end.pts
72945        },
72946        dts: {
72947          start: audioSegmentTimingInfo.start.dts,
72948          end: audioSegmentTimingInfo.end.dts
72949        }
72950      };
72951      triggerSegmentEventFn({
72952        type: 'segmenttransmuxingtiminginfoavailable',
72953        segment,
72954        timingInfo
72955      });
72956      audioSegmentTimingInfoFn(audioSegmentTimingInfo);
72957    },
72958    onId3: (id3Frames, dispatchType) => {
72959      id3Fn(segment, id3Frames, dispatchType);
72960    },
72961    onCaptions: captions => {
72962      captionsFn(segment, [captions]);
72963    },
72964    isEndOfTimeline,
72965    onEndedTimeline: () => {
72966      endedTimelineFn();
72967    },
72968    onTransmuxerLog,
72969    onDone: (result, error) => {
72970      if (!doneFn) {
72971        return;
72972      }
72973      result.type = result.type === 'combined' ? 'video' : result.type;
72974      triggerSegmentEventFn({
72975        type: 'segmenttransmuxingcomplete',
72976        segment
72977      });
72978      doneFn(error, segment, result);
72979    },
72980    segment,
72981    triggerSegmentEventFn
72982  }); // In the transmuxer, we don't yet have the ability to extract a "proper" start time.
72983  // Meaning cached frame data may corrupt our notion of where this segment
72984  // really starts. To get around this, probe for the info needed.
72985
72986  workerCallback({
72987    action: 'probeTs',
72988    transmuxer: segment.transmuxer,
72989    data: bytes,
72990    baseStartTime: segment.baseStartTime,
72991    callback: data => {
72992      segment.bytes = bytes = data.data;
72993      const probeResult = data.result;
72994      if (probeResult) {
72995        trackInfoFn(segment, {
72996          hasAudio: probeResult.hasAudio,
72997          hasVideo: probeResult.hasVideo,
72998          isMuxed
72999        });
73000        trackInfoFn = null;
73001      }
73002      finish();
73003    }
73004  });
73005};
73006const handleSegmentBytes = ({
73007  segment,
73008  bytes,
73009  trackInfoFn,
73010  timingInfoFn,
73011  videoSegmentTimingInfoFn,
73012  audioSegmentTimingInfoFn,
73013  id3Fn,
vendor: 11,105 bytes, lines 73014-73341
73014  captionsFn,
73015  isEndOfTimeline,
73016  endedTimelineFn,
73017  dataFn,
73018  doneFn,
73019  onTransmuxerLog,
73020  triggerSegmentEventFn
73021}) => {
73022  let bytesAsUint8Array = new Uint8Array(bytes); // TODO:
73023  // We should have a handler that fetches the number of bytes required
73024  // to check if something is fmp4. This will allow us to save bandwidth
73025  // because we can only exclude a playlist and abort requests
73026  // by codec after trackinfo triggers.
73027
73028  if (containers.isLikelyFmp4MediaSegment(bytesAsUint8Array)) {
73029    segment.isFmp4 = true;
73030    const {
73031      tracks
73032    } = segment.map;
73033    const isMp4TextSegment = tracks.text && (!tracks.audio || !tracks.video);
73034    if (isMp4TextSegment) {
73035      dataFn(segment, {
73036        data: bytesAsUint8Array,
73037        type: 'text'
73038      });
73039      parseMp4TextSegment(segment, tracks.text.codec, doneFn);
73040      return;
73041    }
73042    const trackInfo = {
73043      isFmp4: true,
73044      hasVideo: !!tracks.video,
73045      hasAudio: !!tracks.audio
73046    }; // if we have a audio track, with a codec that is not set to
73047    // encrypted audio
73048
73049    if (tracks.audio && tracks.audio.codec && tracks.audio.codec !== 'enca') {
73050      trackInfo.audioCodec = tracks.audio.codec;
73051    } // if we have a video track, with a codec that is not set to
73052    // encrypted video
73053
73054    if (tracks.video && tracks.video.codec && tracks.video.codec !== 'encv') {
73055      trackInfo.videoCodec = tracks.video.codec;
73056    }
73057    if (tracks.video && tracks.audio) {
73058      trackInfo.isMuxed = true;
73059    } // since we don't support appending fmp4 data on progress, we know we have the full
73060    // segment here
73061
73062    trackInfoFn(segment, trackInfo); // The probe doesn't provide the segment end time, so only callback with the start
73063    // time. The end time can be roughly calculated by the receiver using the duration.
73064    //
73065    // Note that the start time returned by the probe reflects the baseMediaDecodeTime, as
73066    // that is the true start of the segment (where the playback engine should begin
73067    // decoding).
73068
73069    const finishLoading = (captions, id3Frames) => {
73070      // if the track still has audio at this point it is only possible
73071      // for it to be audio only. See `tracks.video && tracks.audio` if statement
73072      // above.
73073      // we make sure to use segment.bytes here as that
73074      dataFn(segment, {
73075        data: bytesAsUint8Array,
73076        type: trackInfo.hasAudio && !trackInfo.isMuxed ? 'audio' : 'video'
73077      });
73078      if (id3Frames && id3Frames.length) {
73079        id3Fn(segment, id3Frames);
73080      }
73081      if (captions && captions.length) {
73082        captionsFn(segment, captions);
73083      }
73084      doneFn(null, segment, {});
73085    };
73086    workerCallback({
73087      action: 'probeMp4StartTime',
73088      timescales: segment.map.timescales,
73089      data: bytesAsUint8Array,
73090      transmuxer: segment.transmuxer,
73091      callback: ({
73092        data,
73093        startTime
73094      }) => {
73095        // transfer bytes back to us
73096        bytes = data.buffer;
73097        segment.bytes = bytesAsUint8Array = data;
73098        if (trackInfo.hasAudio && !trackInfo.isMuxed) {
73099          timingInfoFn(segment, 'audio', 'start', startTime);
73100        }
73101        if (trackInfo.hasVideo) {
73102          timingInfoFn(segment, 'video', 'start', startTime);
73103        }
73104        workerCallback({
73105          action: 'probeEmsgID3',
73106          data: bytesAsUint8Array,
73107          transmuxer: segment.transmuxer,
73108          offset: startTime,
73109          callback: ({
73110            emsgData,
73111            id3Frames
73112          }) => {
73113            // transfer bytes back to us
73114            bytes = emsgData.buffer;
73115            segment.bytes = bytesAsUint8Array = emsgData; // Run through the CaptionParser in case there are captions.
73116            // Initialize CaptionParser if it hasn't been yet
73117
73118            if (!tracks.video || !emsgData.byteLength || !segment.transmuxer) {
73119              finishLoading(undefined, id3Frames);
73120              return;
73121            }
73122            workerCallback({
73123              action: 'pushMp4Captions',
73124              endAction: 'mp4Captions',
73125              transmuxer: segment.transmuxer,
73126              data: bytesAsUint8Array,
73127              timescales: segment.map.timescales,
73128              trackIds: [tracks.video.id],
73129              callback: message => {
73130                // transfer bytes back to us
73131                bytes = message.data.buffer;
73132                segment.bytes = bytesAsUint8Array = message.data;
73133                message.logs.forEach(function (log) {
73134                  onTransmuxerLog(merge(log, {
73135                    stream: 'mp4CaptionParser'
73136                  }));
73137                });
73138                finishLoading(message.captions, id3Frames);
73139              }
73140            });
73141          }
73142        });
73143      }
73144    });
73145    return;
73146  } // VTT or other segments that don't need processing
73147
73148  if (!segment.transmuxer) {
73149    doneFn(null, segment, {});
73150    return;
73151  }
73152  if (typeof segment.container === 'undefined') {
73153    segment.container = containers.detectContainerForBytes(bytesAsUint8Array);
73154  }
73155  if (segment.container !== 'ts' && segment.container !== 'aac') {
73156    trackInfoFn(segment, {
73157      hasAudio: false,
73158      hasVideo: false
73159    });
73160    doneFn(null, segment, {});
73161    return;
73162  } // ts or aac
73163
73164  transmuxAndNotify({
73165    segment,
73166    bytes,
73167    trackInfoFn,
73168    timingInfoFn,
73169    videoSegmentTimingInfoFn,
73170    audioSegmentTimingInfoFn,
73171    id3Fn,
73172    captionsFn,
73173    isEndOfTimeline,
73174    endedTimelineFn,
73175    dataFn,
73176    doneFn,
73177    onTransmuxerLog,
73178    triggerSegmentEventFn
73179  });
73180};
73181const decrypt = function ({
73182  id,
73183  key,
73184  encryptedBytes,
73185  decryptionWorker,
73186  segment,
73187  doneFn
73188}, callback) {
73189  const decryptionHandler = event => {
73190    if (event.data.source === id) {
73191      decryptionWorker.removeEventListener('message', decryptionHandler);
73192      const decrypted = event.data.decrypted;
73193      callback(new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength));
73194    }
73195  };
73196  decryptionWorker.onerror = () => {
73197    const message = 'An error occurred in the decryption worker';
73198    const segmentInfo = segmentInfoPayload({
73199      segment
73200    });
73201    const decryptError = {
73202      message,
73203      metadata: {
73204        error: new Error(message),
73205        errorType: videojs.Error.StreamingFailedToDecryptSegment,
73206        segmentInfo,
73207        keyInfo: {
73208          uri: segment.key.resolvedUri || segment.map.key.resolvedUri
73209        }
73210      }
73211    };
73212    doneFn(decryptError, segment);
73213  };
73214  decryptionWorker.addEventListener('message', decryptionHandler);
73215  let keyBytes;
73216  if (key.bytes.slice) {
73217    keyBytes = key.bytes.slice();
73218  } else {
73219    keyBytes = new Uint32Array(Array.prototype.slice.call(key.bytes));
73220  } // incrementally decrypt the bytes
73221
73222  decryptionWorker.postMessage(createTransferableMessage({
73223    source: id,
73224    encrypted: encryptedBytes,
73225    key: keyBytes,
73226    iv: key.iv
73227  }), [encryptedBytes.buffer, keyBytes.buffer]);
73228};
73229/**
73230 * Decrypt the segment via the decryption web worker
73231 *
73232 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption
73233 *                                       routines
73234 * @param {Object} segment - a simplified copy of the segmentInfo object
73235 *                           from SegmentLoader
73236 * @param {Function} trackInfoFn - a callback that receives track info
73237 * @param {Function} timingInfoFn - a callback that receives timing info
73238 * @param {Function} videoSegmentTimingInfoFn
73239 *                   a callback that receives video timing info based on media times and
73240 *                   any adjustments made by the transmuxer
73241 * @param {Function} audioSegmentTimingInfoFn
73242 *                   a callback that receives audio timing info based on media times and
73243 *                   any adjustments made by the transmuxer
73244 * @param {boolean}  isEndOfTimeline
73245 *                   true if this segment represents the last segment in a timeline
73246 * @param {Function} endedTimelineFn
73247 *                   a callback made when a timeline is ended, will only be called if
73248 *                   isEndOfTimeline is true
73249 * @param {Function} dataFn - a callback that is executed when segment bytes are available
73250 *                            and ready to use
73251 * @param {Function} doneFn - a callback that is executed after decryption has completed
73252 */
73253
73254const decryptSegment = ({
73255  decryptionWorker,
73256  segment,
73257  trackInfoFn,
73258  timingInfoFn,
73259  videoSegmentTimingInfoFn,
73260  audioSegmentTimingInfoFn,
73261  id3Fn,
73262  captionsFn,
73263  isEndOfTimeline,
73264  endedTimelineFn,
73265  dataFn,
73266  doneFn,
73267  onTransmuxerLog,
73268  triggerSegmentEventFn
73269}) => {
73270  triggerSegmentEventFn({
73271    type: 'segmentdecryptionstart'
73272  });
73273  decrypt({
73274    id: segment.requestId,
73275    key: segment.key,
73276    encryptedBytes: segment.encryptedBytes,
73277    decryptionWorker,
73278    segment,
73279    doneFn
73280  }, decryptedBytes => {
73281    segment.bytes = decryptedBytes;
73282    triggerSegmentEventFn({
73283      type: 'segmentdecryptioncomplete',
73284      segment
73285    });
73286    handleSegmentBytes({
73287      segment,
73288      bytes: segment.bytes,
73289      trackInfoFn,
73290      timingInfoFn,
73291      videoSegmentTimingInfoFn,
73292      audioSegmentTimingInfoFn,
73293      id3Fn,
73294      captionsFn,
73295      isEndOfTimeline,
73296      endedTimelineFn,
73297      dataFn,
73298      doneFn,
73299      onTransmuxerLog,
73300      triggerSegmentEventFn
73301    });
73302  });
73303};
73304/**
73305 * This function waits for all XHRs to finish (with either success or failure)
73306 * before continueing processing via it's callback. The function gathers errors
73307 * from each request into a single errors array so that the error status for
73308 * each request can be examined later.
73309 *
73310 * @param {Object} activeXhrs - an object that tracks all XHR requests
73311 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption
73312 *                                       routines
73313 * @param {Function} trackInfoFn - a callback that receives track info
73314 * @param {Function} timingInfoFn - a callback that receives timing info
73315 * @param {Function} videoSegmentTimingInfoFn
73316 *                   a callback that receives video timing info based on media times and
73317 *                   any adjustments made by the transmuxer
73318 * @param {Function} audioSegmentTimingInfoFn
73319 *                   a callback that receives audio timing info based on media times and
73320 *                   any adjustments made by the transmuxer
73321 * @param {Function} id3Fn - a callback that receives ID3 metadata
73322 * @param {Function} captionsFn - a callback that receives captions
73323 * @param {boolean}  isEndOfTimeline
73324 *                   true if this segment represents the last segment in a timeline
73325 * @param {Function} endedTimelineFn
73326 *                   a callback made when a timeline is ended, will only be called if
73327 *                   isEndOfTimeline is true
73328 * @param {Function} dataFn - a callback that is executed when segment bytes are available
73329 *                            and ready to use
73330 * @param {Function} doneFn - a callback that is executed after all resources have been
73331 *                            downloaded and any decryption completed
73332 */
73333
73334const waitForCompletion = ({
73335  activeXhrs,
73336  decryptionWorker,
73337  trackInfoFn,
73338  timingInfoFn,
73339  videoSegmentTimingInfoFn,
73340  audioSegmentTimingInfoFn,
73341  id3Fn,
vendor: 9,817 bytes, lines 73342-73627
73342  captionsFn,
73343  isEndOfTimeline,
73344  endedTimelineFn,
73345  dataFn,
73346  doneFn,
73347  onTransmuxerLog,
73348  triggerSegmentEventFn
73349}) => {
73350  let count = 0;
73351  let didError = false;
73352  return (error, segment) => {
73353    if (didError) {
73354      return;
73355    }
73356    if (error) {
73357      didError = true; // If there are errors, we have to abort any outstanding requests
73358
73359      abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we
73360      // handle the aborted events from those requests, there are some cases where we may
73361      // never get an aborted event. For instance, if the network connection is lost and
73362      // there were two requests, the first may have triggered an error immediately, while
73363      // the second request remains unsent. In that case, the aborted algorithm will not
73364      // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method
73365      //
73366      // We also can't rely on the ready state of the XHR, since the request that
73367      // triggered the connection error may also show as a ready state of 0 (unsent).
73368      // Therefore, we have to finish this group of requests immediately after the first
73369      // seen error.
73370
73371      return doneFn(error, segment);
73372    }
73373    count += 1;
73374    if (count === activeXhrs.length) {
73375      const segmentFinish = function () {
73376        if (segment.encryptedBytes) {
73377          return decryptSegment({
73378            decryptionWorker,
73379            segment,
73380            trackInfoFn,
73381            timingInfoFn,
73382            videoSegmentTimingInfoFn,
73383            audioSegmentTimingInfoFn,
73384            id3Fn,
73385            captionsFn,
73386            isEndOfTimeline,
73387            endedTimelineFn,
73388            dataFn,
73389            doneFn,
73390            onTransmuxerLog,
73391            triggerSegmentEventFn
73392          });
73393        } // Otherwise, everything is ready just continue
73394
73395        handleSegmentBytes({
73396          segment,
73397          bytes: segment.bytes,
73398          trackInfoFn,
73399          timingInfoFn,
73400          videoSegmentTimingInfoFn,
73401          audioSegmentTimingInfoFn,
73402          id3Fn,
73403          captionsFn,
73404          isEndOfTimeline,
73405          endedTimelineFn,
73406          dataFn,
73407          doneFn,
73408          onTransmuxerLog,
73409          triggerSegmentEventFn
73410        });
73411      }; // Keep track of when *all* of the requests have completed
73412
73413      segment.endOfAllRequests = Date.now();
73414      if (segment.map && segment.map.encryptedBytes && !segment.map.bytes) {
73415        triggerSegmentEventFn({
73416          type: 'segmentdecryptionstart',
73417          segment
73418        });
73419        return decrypt({
73420          decryptionWorker,
73421          // add -init to the "id" to differentiate between segment
73422          // and init segment decryption, just in case they happen
73423          // at the same time at some point in the future.
73424          id: segment.requestId + '-init',
73425          encryptedBytes: segment.map.encryptedBytes,
73426          key: segment.map.key,
73427          segment,
73428          doneFn
73429        }, decryptedBytes => {
73430          segment.map.bytes = decryptedBytes;
73431          triggerSegmentEventFn({
73432            type: 'segmentdecryptioncomplete',
73433            segment
73434          });
73435          parseInitSegment(segment, parseError => {
73436            if (parseError) {
73437              abortAll(activeXhrs);
73438              return doneFn(parseError, segment);
73439            }
73440            segmentFinish();
73441          });
73442        });
73443      }
73444      segmentFinish();
73445    }
73446  };
73447};
73448/**
73449 * Calls the abort callback if any request within the batch was aborted. Will only call
73450 * the callback once per batch of requests, even if multiple were aborted.
73451 *
73452 * @param {Object} loadendState - state to check to see if the abort function was called
73453 * @param {Function} abortFn - callback to call for abort
73454 */
73455
73456const handleLoadEnd = ({
73457  loadendState,
73458  abortFn
73459}) => event => {
73460  const request = event.target;
73461  if (request.aborted && abortFn && !loadendState.calledAbortFn) {
73462    abortFn();
73463    loadendState.calledAbortFn = true;
73464  }
73465};
73466/**
73467 * Simple progress event callback handler that gathers some stats before
73468 * executing a provided callback with the `segment` object
73469 *
73470 * @param {Object} segment - a simplified copy of the segmentInfo object
73471 *                           from SegmentLoader
73472 * @param {Function} progressFn - a callback that is executed each time a progress event
73473 *                                is received
73474 * @param {Function} trackInfoFn - a callback that receives track info
73475 * @param {Function} timingInfoFn - a callback that receives timing info
73476 * @param {Function} videoSegmentTimingInfoFn
73477 *                   a callback that receives video timing info based on media times and
73478 *                   any adjustments made by the transmuxer
73479 * @param {Function} audioSegmentTimingInfoFn
73480 *                   a callback that receives audio timing info based on media times and
73481 *                   any adjustments made by the transmuxer
73482 * @param {boolean}  isEndOfTimeline
73483 *                   true if this segment represents the last segment in a timeline
73484 * @param {Function} endedTimelineFn
73485 *                   a callback made when a timeline is ended, will only be called if
73486 *                   isEndOfTimeline is true
73487 * @param {Function} dataFn - a callback that is executed when segment bytes are available
73488 *                            and ready to use
73489 * @param {Event} event - the progress event object from XMLHttpRequest
73490 */
73491
73492const handleProgress = ({
73493  segment,
73494  progressFn,
73495  trackInfoFn,
73496  timingInfoFn,
73497  videoSegmentTimingInfoFn,
73498  audioSegmentTimingInfoFn,
73499  id3Fn,
73500  captionsFn,
73501  isEndOfTimeline,
73502  endedTimelineFn,
73503  dataFn
73504}) => event => {
73505  const request = event.target;
73506  if (request.aborted) {
73507    return;
73508  }
73509  segment.stats = merge(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data
73510
73511  if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
73512    segment.stats.firstBytesReceivedAt = Date.now();
73513  }
73514  return progressFn(event, segment);
73515};
73516/**
73517 * Load all resources and does any processing necessary for a media-segment
73518 *
73519 * Features:
73520 *   decrypts the media-segment if it has a key uri and an iv
73521 *   aborts *all* requests if *any* one request fails
73522 *
73523 * The segment object, at minimum, has the following format:
73524 * {
73525 *   resolvedUri: String,
73526 *   [transmuxer]: Object,
73527 *   [byterange]: {
73528 *     offset: Number,
73529 *     length: Number
73530 *   },
73531 *   [key]: {
73532 *     resolvedUri: String
73533 *     [byterange]: {
73534 *       offset: Number,
73535 *       length: Number
73536 *     },
73537 *     iv: {
73538 *       bytes: Uint32Array
73539 *     }
73540 *   },
73541 *   [map]: {
73542 *     resolvedUri: String,
73543 *     [byterange]: {
73544 *       offset: Number,
73545 *       length: Number
73546 *     },
73547 *     [bytes]: Uint8Array
73548 *   }
73549 * }
73550 * ...where [name] denotes optional properties
73551 *
73552 * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
73553 * @param {Object} xhrOptions - the base options to provide to all xhr requests
73554 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
73555 *                                       decryption routines
73556 * @param {Object} segment - a simplified copy of the segmentInfo object
73557 *                           from SegmentLoader
73558 * @param {Function} abortFn - a callback called (only once) if any piece of a request was
73559 *                             aborted
73560 * @param {Function} progressFn - a callback that receives progress events from the main
73561 *                                segment's xhr request
73562 * @param {Function} trackInfoFn - a callback that receives track info
73563 * @param {Function} timingInfoFn - a callback that receives timing info
73564 * @param {Function} videoSegmentTimingInfoFn
73565 *                   a callback that receives video timing info based on media times and
73566 *                   any adjustments made by the transmuxer
73567 * @param {Function} audioSegmentTimingInfoFn
73568 *                   a callback that receives audio timing info based on media times and
73569 *                   any adjustments made by the transmuxer
73570 * @param {Function} id3Fn - a callback that receives ID3 metadata
73571 * @param {Function} captionsFn - a callback that receives captions
73572 * @param {boolean}  isEndOfTimeline
73573 *                   true if this segment represents the last segment in a timeline
73574 * @param {Function} endedTimelineFn
73575 *                   a callback made when a timeline is ended, will only be called if
73576 *                   isEndOfTimeline is true
73577 * @param {Function} dataFn - a callback that receives data from the main segment's xhr
73578 *                            request, transmuxed if needed
73579 * @param {Function} doneFn - a callback that is executed only once all requests have
73580 *                            succeeded or failed
73581 * @return {Function} a function that, when invoked, immediately aborts all
73582 *                     outstanding requests
73583 */
73584
73585const mediaSegmentRequest = ({
73586  xhr,
73587  xhrOptions,
73588  decryptionWorker,
73589  segment,
73590  abortFn,
73591  progressFn,
73592  trackInfoFn,
73593  timingInfoFn,
73594  videoSegmentTimingInfoFn,
73595  audioSegmentTimingInfoFn,
73596  id3Fn,
73597  captionsFn,
73598  isEndOfTimeline,
73599  endedTimelineFn,
73600  dataFn,
73601  doneFn,
73602  onTransmuxerLog,
73603  triggerSegmentEventFn
73604}) => {
73605  const activeXhrs = [];
73606  const finishProcessingFn = waitForCompletion({
73607    activeXhrs,
73608    decryptionWorker,
73609    trackInfoFn,
73610    timingInfoFn,
73611    videoSegmentTimingInfoFn,
73612    audioSegmentTimingInfoFn,
73613    id3Fn,
73614    captionsFn,
73615    isEndOfTimeline,
73616    endedTimelineFn,
73617    dataFn,
73618    doneFn,
73619    onTransmuxerLog,
73620    triggerSegmentEventFn
73621  }); // optionally, request the decryption key
73622
73623  if (segment.key && !segment.key.bytes) {
73624    const objects = [segment.key];
73625    if (segment.map && !segment.map.bytes && segment.map.key && segment.map.key.resolvedUri === segment.key.resolvedUri) {
73626      objects.push(segment.map.key);
73627    }
vendor: 18,360 bytes, lines 73628-74081
73628    const keyRequestOptions = merge(xhrOptions, {
73629      uri: segment.key.resolvedUri,
73630      responseType: 'arraybuffer',
73631      requestType: 'segment-key'
73632    });
73633    const keyRequestCallback = handleKeyResponse(segment, objects, finishProcessingFn, triggerSegmentEventFn);
73634    const keyInfo = {
73635      uri: segment.key.resolvedUri
73636    };
73637    triggerSegmentEventFn({
73638      type: 'segmentkeyloadstart',
73639      segment,
73640      keyInfo
73641    });
73642    const keyXhr = xhr(keyRequestOptions, keyRequestCallback);
73643    activeXhrs.push(keyXhr);
73644  } // optionally, request the associated media init segment
73645
73646  if (segment.map && !segment.map.bytes) {
73647    const differentMapKey = segment.map.key && (!segment.key || segment.key.resolvedUri !== segment.map.key.resolvedUri);
73648    if (differentMapKey) {
73649      const mapKeyRequestOptions = merge(xhrOptions, {
73650        uri: segment.map.key.resolvedUri,
73651        responseType: 'arraybuffer',
73652        requestType: 'segment-key'
73653      });
73654      const mapKeyRequestCallback = handleKeyResponse(segment, [segment.map.key], finishProcessingFn, triggerSegmentEventFn);
73655      const keyInfo = {
73656        uri: segment.map.key.resolvedUri
73657      };
73658      triggerSegmentEventFn({
73659        type: 'segmentkeyloadstart',
73660        segment,
73661        keyInfo
73662      });
73663      const mapKeyXhr = xhr(mapKeyRequestOptions, mapKeyRequestCallback);
73664      activeXhrs.push(mapKeyXhr);
73665    }
73666    const initSegmentOptions = merge(xhrOptions, {
73667      uri: segment.map.resolvedUri,
73668      responseType: 'arraybuffer',
73669      headers: segmentXhrHeaders(segment.map),
73670      requestType: 'segment-media-initialization'
73671    });
73672    const initSegmentRequestCallback = handleInitSegmentResponse({
73673      segment,
73674      finishProcessingFn,
73675      triggerSegmentEventFn
73676    });
73677    triggerSegmentEventFn({
73678      type: 'segmentloadstart',
73679      segment
73680    });
73681    const initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback);
73682    activeXhrs.push(initSegmentXhr);
73683  }
73684  const segmentRequestOptions = merge(xhrOptions, {
73685    uri: segment.part && segment.part.resolvedUri || segment.resolvedUri,
73686    responseType: 'arraybuffer',
73687    headers: segmentXhrHeaders(segment),
73688    requestType: 'segment'
73689  });
73690  const segmentRequestCallback = handleSegmentResponse({
73691    segment,
73692    finishProcessingFn,
73693    responseType: segmentRequestOptions.responseType,
73694    triggerSegmentEventFn
73695  });
73696  triggerSegmentEventFn({
73697    type: 'segmentloadstart',
73698    segment
73699  });
73700  const segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback);
73701  segmentXhr.addEventListener('progress', handleProgress({
73702    segment,
73703    progressFn,
73704    trackInfoFn,
73705    timingInfoFn,
73706    videoSegmentTimingInfoFn,
73707    audioSegmentTimingInfoFn,
73708    id3Fn,
73709    captionsFn,
73710    isEndOfTimeline,
73711    endedTimelineFn,
73712    dataFn
73713  }));
73714  activeXhrs.push(segmentXhr); // since all parts of the request must be considered, but should not make callbacks
73715  // multiple times, provide a shared state object
73716
73717  const loadendState = {};
73718  activeXhrs.forEach(activeXhr => {
73719    activeXhr.addEventListener('loadend', handleLoadEnd({
73720      loadendState,
73721      abortFn
73722    }));
73723  });
73724  return () => abortAll(activeXhrs);
73725};
73726const logFn = logger('PlaylistSelector');
73727const representationToString = function (representation) {
73728  if (!representation || !representation.playlist) {
73729    return;
73730  }
73731  const playlist = representation.playlist;
73732  return JSON.stringify({
73733    id: playlist.id,
73734    bandwidth: representation.bandwidth,
73735    width: representation.width,
73736    height: representation.height,
73737    codecs: playlist.attributes && playlist.attributes.CODECS || ''
73738  });
73739}; // Utilities
73740
73741/**
73742 * Returns the CSS value for the specified property on an element
73743 * using `getComputedStyle`. Firefox has a long-standing issue where
73744 * getComputedStyle() may return null when running in an iframe with
73745 * `display: none`.
73746 *
73747 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
73748 * @param {HTMLElement} el the htmlelement to work on
73749 * @param {string} the proprety to get the style for
73750 */
73751
73752const safeGetComputedStyle = function (el, property) {
73753  if (!el) {
73754    return '';
73755  }
73756  const result = window__default["default"].getComputedStyle(el);
73757  if (!result) {
73758    return '';
73759  }
73760  return result[property];
73761};
73762/**
73763 * Resuable stable sort function
73764 *
73765 * @param {Playlists} array
73766 * @param {Function} sortFn Different comparators
73767 * @function stableSort
73768 */
73769
73770const stableSort = function (array, sortFn) {
73771  const newArray = array.slice();
73772  array.sort(function (left, right) {
73773    const cmp = sortFn(left, right);
73774    if (cmp === 0) {
73775      return newArray.indexOf(left) - newArray.indexOf(right);
73776    }
73777    return cmp;
73778  });
73779};
73780/**
73781 * A comparator function to sort two playlist object by bandwidth.
73782 *
73783 * @param {Object} left a media playlist object
73784 * @param {Object} right a media playlist object
73785 * @return {number} Greater than zero if the bandwidth attribute of
73786 * left is greater than the corresponding attribute of right. Less
73787 * than zero if the bandwidth of right is greater than left and
73788 * exactly zero if the two are equal.
73789 */
73790
73791const comparePlaylistBandwidth = function (left, right) {
73792  let leftBandwidth;
73793  let rightBandwidth;
73794  if (left.attributes.BANDWIDTH) {
73795    leftBandwidth = left.attributes.BANDWIDTH;
73796  }
73797  leftBandwidth = leftBandwidth || window__default["default"].Number.MAX_VALUE;
73798  if (right.attributes.BANDWIDTH) {
73799    rightBandwidth = right.attributes.BANDWIDTH;
73800  }
73801  rightBandwidth = rightBandwidth || window__default["default"].Number.MAX_VALUE;
73802  return leftBandwidth - rightBandwidth;
73803};
73804/**
73805 * A comparator function to sort two playlist object by resolution (width).
73806 *
73807 * @param {Object} left a media playlist object
73808 * @param {Object} right a media playlist object
73809 * @return {number} Greater than zero if the resolution.width attribute of
73810 * left is greater than the corresponding attribute of right. Less
73811 * than zero if the resolution.width of right is greater than left and
73812 * exactly zero if the two are equal.
73813 */
73814
73815const comparePlaylistResolution = function (left, right) {
73816  let leftWidth;
73817  let rightWidth;
73818  if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
73819    leftWidth = left.attributes.RESOLUTION.width;
73820  }
73821  leftWidth = leftWidth || window__default["default"].Number.MAX_VALUE;
73822  if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
73823    rightWidth = right.attributes.RESOLUTION.width;
73824  }
73825  rightWidth = rightWidth || window__default["default"].Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
73826  // have the same media dimensions/ resolution
73827
73828  if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
73829    return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
73830  }
73831  return leftWidth - rightWidth;
73832};
73833/**
73834 * Chooses the appropriate media playlist based on bandwidth and player size
73835 *
73836 * @param {Object} settings
73837 *        Object of information required to use this selector
73838 * @param {Object} settings.main
73839 *        Object representation of the main manifest
73840 * @param {number} settings.bandwidth
73841 *        Current calculated bandwidth of the player
73842 * @param {number} settings.playerWidth
73843 *        Current width of the player element (should account for the device pixel ratio)
73844 * @param {number} settings.playerHeight
73845 *        Current height of the player element (should account for the device pixel ratio)
73846 * @param {number} settings.playerObjectFit
73847 *        Current value of the video element's object-fit CSS property. Allows taking into
73848 *        account that the video might be scaled up to cover the media element when selecting
73849 *        media playlists based on player size.
73850 * @param {boolean} settings.limitRenditionByPlayerDimensions
73851 *        True if the player width and height should be used during the selection, false otherwise
73852 * @param {Object} settings.playlistController
73853 *        the current playlistController object
73854 * @return {Playlist} the highest bitrate playlist less than the
73855 * currently detected bandwidth, accounting for some amount of
73856 * bandwidth variance
73857 */
73858
73859let simpleSelector = function (settings) {
73860  const {
73861    main,
73862    bandwidth: playerBandwidth,
73863    playerWidth,
73864    playerHeight,
73865    playerObjectFit,
73866    limitRenditionByPlayerDimensions,
73867    playlistController
73868  } = settings; // If we end up getting called before `main` is available, exit early
73869
73870  if (!main) {
73871    return;
73872  }
73873  const options = {
73874    bandwidth: playerBandwidth,
73875    width: playerWidth,
73876    height: playerHeight,
73877    limitRenditionByPlayerDimensions
73878  };
73879  let playlists = main.playlists; // if playlist is audio only, select between currently active audio group playlists.
73880
73881  if (Playlist.isAudioOnly(main)) {
73882    playlists = playlistController.getAudioTrackPlaylists_(); // add audioOnly to options so that we log audioOnly: true
73883    // at the buttom of this function for debugging.
73884
73885    options.audioOnly = true;
73886  } // convert the playlists to an intermediary representation to make comparisons easier
73887
73888  let sortedPlaylistReps = playlists.map(playlist => {
73889    let bandwidth;
73890    const width = playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
73891    const height = playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
73892    bandwidth = playlist.attributes && playlist.attributes.BANDWIDTH;
73893    bandwidth = bandwidth || window__default["default"].Number.MAX_VALUE;
73894    return {
73895      bandwidth,
73896      width,
73897      height,
73898      playlist
73899    };
73900  });
73901  stableSort(sortedPlaylistReps, (left, right) => left.bandwidth - right.bandwidth); // filter out any playlists that have been excluded due to
73902  // incompatible configurations
73903
73904  sortedPlaylistReps = sortedPlaylistReps.filter(rep => !Playlist.isIncompatible(rep.playlist)); // filter out any playlists that have been disabled manually through the representations
73905  // api or excluded temporarily due to playback errors.
73906
73907  let enabledPlaylistReps = sortedPlaylistReps.filter(rep => Playlist.isEnabled(rep.playlist));
73908  if (!enabledPlaylistReps.length) {
73909    // if there are no enabled playlists, then they have all been excluded or disabled
73910    // by the user through the representations api. In this case, ignore exclusion and
73911    // fallback to what the user wants by using playlists the user has not disabled.
73912    enabledPlaylistReps = sortedPlaylistReps.filter(rep => !Playlist.isDisabled(rep.playlist));
73913  } // filter out any variant that has greater effective bitrate
73914  // than the current estimated bandwidth
73915
73916  const bandwidthPlaylistReps = enabledPlaylistReps.filter(rep => rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth);
73917  let highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth
73918  // and then taking the very first element
73919
73920  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.
73921
73922  if (limitRenditionByPlayerDimensions === false) {
73923    const chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
73924    if (chosenRep && chosenRep.playlist) {
73925      let type = 'sortedPlaylistReps';
73926      if (bandwidthBestRep) {
73927        type = 'bandwidthBestRep';
73928      }
73929      if (enabledPlaylistReps[0]) {
73930        type = 'enabledPlaylistReps';
73931      }
73932      logFn(`choosing ${representationToString(chosenRep)} using ${type} with options`, options);
73933      return chosenRep.playlist;
73934    }
73935    logFn('could not choose a playlist with options', options);
73936    return null;
73937  } // filter out playlists without resolution information
73938
73939  const haveResolution = bandwidthPlaylistReps.filter(rep => rep.width && rep.height); // sort variants by resolution
73940
73941  stableSort(haveResolution, (left, right) => left.width - right.width); // if we have the exact resolution as the player use it
73942
73943  const resolutionBestRepList = haveResolution.filter(rep => rep.width === playerWidth && rep.height === playerHeight);
73944  highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution
73945
73946  const resolutionBestRep = resolutionBestRepList.filter(rep => rep.bandwidth === highestRemainingBandwidthRep.bandwidth)[0];
73947  let resolutionPlusOneList;
73948  let resolutionPlusOneSmallest;
73949  let resolutionPlusOneRep; // find the smallest variant that is larger than the player
73950  // if there is no match of exact resolution
73951
73952  if (!resolutionBestRep) {
73953    resolutionPlusOneList = haveResolution.filter(rep => {
73954      if (playerObjectFit === 'cover') {
73955        // video will be scaled up to cover the player. We need to
73956        // make sure rendition is at least as wide and as high as the
73957        // player.
73958        return rep.width > playerWidth && rep.height > playerHeight;
73959      } // video will be scaled down to fit inside the player soon as
73960      // its resolution exceeds player size in at least one dimension.
73961
73962      return rep.width > playerWidth || rep.height > playerHeight;
73963    }); // find all the variants have the same smallest resolution
73964
73965    resolutionPlusOneSmallest = resolutionPlusOneList.filter(rep => rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height); // ensure that we also pick the highest bandwidth variant that
73966    // is just-larger-than the video player
73967
73968    highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
73969    resolutionPlusOneRep = resolutionPlusOneSmallest.filter(rep => rep.bandwidth === highestRemainingBandwidthRep.bandwidth)[0];
73970  }
73971  let leastPixelDiffRep; // If this selector proves to be better than others,
73972  // resolutionPlusOneRep and resolutionBestRep and all
73973  // the code involving them should be removed.
73974
73975  if (playlistController.leastPixelDiffSelector) {
73976    // find the variant that is closest to the player's pixel size
73977    const leastPixelDiffList = haveResolution.map(rep => {
73978      rep.pixelDiff = Math.abs(rep.width - playerWidth) + Math.abs(rep.height - playerHeight);
73979      return rep;
73980    }); // get the highest bandwidth, closest resolution playlist
73981
73982    stableSort(leastPixelDiffList, (left, right) => {
73983      // sort by highest bandwidth if pixelDiff is the same
73984      if (left.pixelDiff === right.pixelDiff) {
73985        return right.bandwidth - left.bandwidth;
73986      }
73987      return left.pixelDiff - right.pixelDiff;
73988    });
73989    leastPixelDiffRep = leastPixelDiffList[0];
73990  } // fallback chain of variants
73991
73992  const chosenRep = leastPixelDiffRep || resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
73993  if (chosenRep && chosenRep.playlist) {
73994    let type = 'sortedPlaylistReps';
73995    if (leastPixelDiffRep) {
73996      type = 'leastPixelDiffRep';
73997    } else if (resolutionPlusOneRep) {
73998      type = 'resolutionPlusOneRep';
73999    } else if (resolutionBestRep) {
74000      type = 'resolutionBestRep';
74001    } else if (bandwidthBestRep) {
74002      type = 'bandwidthBestRep';
74003    } else if (enabledPlaylistReps[0]) {
74004      type = 'enabledPlaylistReps';
74005    }
74006    logFn(`choosing ${representationToString(chosenRep)} using ${type} with options`, options);
74007    return chosenRep.playlist;
74008  }
74009  logFn('could not choose a playlist with options', options);
74010  return null;
74011};
74012
74013/**
74014 * Chooses the appropriate media playlist based on the most recent
74015 * bandwidth estimate and the player size.
74016 *
74017 * Expects to be called within the context of an instance of VhsHandler
74018 *
74019 * @return {Playlist} the highest bitrate playlist less than the
74020 * currently detected bandwidth, accounting for some amount of
74021 * bandwidth variance
74022 */
74023
74024const lastBandwidthSelector = function () {
74025  let pixelRatio = this.useDevicePixelRatio ? window__default["default"].devicePixelRatio || 1 : 1;
74026  if (!isNaN(this.customPixelRatio)) {
74027    pixelRatio = this.customPixelRatio;
74028  }
74029  return simpleSelector({
74030    main: this.playlists.main,
74031    bandwidth: this.systemBandwidth,
74032    playerWidth: parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio,
74033    playerHeight: parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio,
74034    playerObjectFit: this.usePlayerObjectFit ? safeGetComputedStyle(this.tech_.el(), 'objectFit') : '',
74035    limitRenditionByPlayerDimensions: this.limitRenditionByPlayerDimensions,
74036    playlistController: this.playlistController_
74037  });
74038};
74039/**
74040 * Chooses the appropriate media playlist based on an
74041 * exponential-weighted moving average of the bandwidth after
74042 * filtering for player size.
74043 *
74044 * Expects to be called within the context of an instance of VhsHandler
74045 *
74046 * @param {number} decay - a number between 0 and 1. Higher values of
74047 * this parameter will cause previous bandwidth estimates to lose
74048 * significance more quickly.
74049 * @return {Function} a function which can be invoked to create a new
74050 * playlist selector function.
74051 * @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
74052 */
74053
74054const movingAverageBandwidthSelector = function (decay) {
74055  let average = -1;
74056  let lastSystemBandwidth = -1;
74057  if (decay < 0 || decay > 1) {
74058    throw new Error('Moving average bandwidth decay must be between 0 and 1.');
74059  }
74060  return function () {
74061    let pixelRatio = this.useDevicePixelRatio ? window__default["default"].devicePixelRatio || 1 : 1;
74062    if (!isNaN(this.customPixelRatio)) {
74063      pixelRatio = this.customPixelRatio;
74064    }
74065    if (average < 0) {
74066      average = this.systemBandwidth;
74067      lastSystemBandwidth = this.systemBandwidth;
74068    } // stop the average value from decaying for every 250ms
74069    // when the systemBandwidth is constant
74070    // and
74071    // stop average from setting to a very low value when the
74072    // systemBandwidth becomes 0 in case of chunk cancellation
74073
74074    if (this.systemBandwidth > 0 && this.systemBandwidth !== lastSystemBandwidth) {
74075      average = decay * this.systemBandwidth + (1 - decay) * average;
74076      lastSystemBandwidth = this.systemBandwidth;
74077    }
74078    return simpleSelector({
74079      main: this.playlists.main,
74080      bandwidth: average,
74081      playerWidth: parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio,
vendor: 4,368 bytes, lines 74082-74175
74082      playerHeight: parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio,
74083      playerObjectFit: this.usePlayerObjectFit ? safeGetComputedStyle(this.tech_.el(), 'objectFit') : '',
74084      limitRenditionByPlayerDimensions: this.limitRenditionByPlayerDimensions,
74085      playlistController: this.playlistController_
74086    });
74087  };
74088};
74089/**
74090 * Chooses the appropriate media playlist based on the potential to rebuffer
74091 *
74092 * @param {Object} settings
74093 *        Object of information required to use this selector
74094 * @param {Object} settings.main
74095 *        Object representation of the main manifest
74096 * @param {number} settings.currentTime
74097 *        The current time of the player
74098 * @param {number} settings.bandwidth
74099 *        Current measured bandwidth
74100 * @param {number} settings.duration
74101 *        Duration of the media
74102 * @param {number} settings.segmentDuration
74103 *        Segment duration to be used in round trip time calculations
74104 * @param {number} settings.timeUntilRebuffer
74105 *        Time left in seconds until the player has to rebuffer
74106 * @param {number} settings.currentTimeline
74107 *        The current timeline segments are being loaded from
74108 * @param {SyncController} settings.syncController
74109 *        SyncController for determining if we have a sync point for a given playlist
74110 * @return {Object|null}
74111 *         {Object} return.playlist
74112 *         The highest bandwidth playlist with the least amount of rebuffering
74113 *         {Number} return.rebufferingImpact
74114 *         The amount of time in seconds switching to this playlist will rebuffer. A
74115 *         negative value means that switching will cause zero rebuffering.
74116 */
74117
74118const minRebufferMaxBandwidthSelector = function (settings) {
74119  const {
74120    main,
74121    currentTime,
74122    bandwidth,
74123    duration,
74124    segmentDuration,
74125    timeUntilRebuffer,
74126    currentTimeline,
74127    syncController
74128  } = settings; // filter out any playlists that have been excluded due to
74129  // incompatible configurations
74130
74131  const compatiblePlaylists = main.playlists.filter(playlist => !Playlist.isIncompatible(playlist)); // filter out any playlists that have been disabled manually through the representations
74132  // api or excluded temporarily due to playback errors.
74133
74134  let enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
74135  if (!enabledPlaylists.length) {
74136    // if there are no enabled playlists, then they have all been excluded or disabled
74137    // by the user through the representations api. In this case, ignore exclusion and
74138    // fallback to what the user wants by using playlists the user has not disabled.
74139    enabledPlaylists = compatiblePlaylists.filter(playlist => !Playlist.isDisabled(playlist));
74140  }
74141  const bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
74142  const rebufferingEstimates = bandwidthPlaylists.map(playlist => {
74143    const syncPoint = syncController.getSyncPoint(playlist, duration, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a
74144    // sync request first. This will double the request time
74145
74146    const numRequests = syncPoint ? 1 : 2;
74147    const requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
74148    const rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
74149    return {
74150      playlist,
74151      rebufferingImpact
74152    };
74153  });
74154  const noRebufferingPlaylists = rebufferingEstimates.filter(estimate => estimate.rebufferingImpact <= 0); // Sort by bandwidth DESC
74155
74156  stableSort(noRebufferingPlaylists, (a, b) => comparePlaylistBandwidth(b.playlist, a.playlist));
74157  if (noRebufferingPlaylists.length) {
74158    return noRebufferingPlaylists[0];
74159  }
74160  stableSort(rebufferingEstimates, (a, b) => a.rebufferingImpact - b.rebufferingImpact);
74161  return rebufferingEstimates[0] || null;
74162};
74163/**
74164 * Chooses the appropriate media playlist, which in this case is the lowest bitrate
74165 * one with video.  If no renditions with video exist, return the lowest audio rendition.
74166 *
74167 * Expects to be called within the context of an instance of VhsHandler
74168 *
74169 * @return {Object|null}
74170 *         {Object} return.playlist
74171 *         The lowest bitrate playlist that contains a video codec.  If no such rendition
74172 *         exists pick the lowest audio rendition.
74173 */
74174
74175const lowestBitrateCompatibleVariantSelector = function () {
vendor: 18,370 bytes, lines 74176-74746
74176  // filter out any playlists that have been excluded due to
74177  // incompatible configurations or playback errors
74178  const playlists = this.playlists.main.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate
74179
74180  stableSort(playlists, (a, b) => comparePlaylistBandwidth(a, b)); // Parse and assume that playlists with no video codec have no video
74181  // (this is not necessarily true, although it is generally true).
74182  //
74183  // If an entire manifest has no valid videos everything will get filtered
74184  // out.
74185
74186  const playlistsWithVideo = playlists.filter(playlist => !!codecsForPlaylist(this.playlists.main, playlist).video);
74187  return playlistsWithVideo[0] || null;
74188};
74189
74190/**
74191 * Combine all segments into a single Uint8Array
74192 *
74193 * @param {Object} segmentObj
74194 * @return {Uint8Array} concatenated bytes
74195 * @private
74196 */
74197const concatSegments = segmentObj => {
74198  let offset = 0;
74199  let tempBuffer;
74200  if (segmentObj.bytes) {
74201    tempBuffer = new Uint8Array(segmentObj.bytes); // combine the individual segments into one large typed-array
74202
74203    segmentObj.segments.forEach(segment => {
74204      tempBuffer.set(segment, offset);
74205      offset += segment.byteLength;
74206    });
74207  }
74208  return tempBuffer;
74209};
74210/**
74211 * Example:
74212 * https://host.com/path1/path2/path3/segment.ts?arg1=val1
74213 * -->
74214 * path3/segment.ts
74215 *
74216 * @param resolvedUri
74217 * @return {string}
74218 */
74219
74220function compactSegmentUrlDescription(resolvedUri) {
74221  try {
74222    return new URL(resolvedUri).pathname.split('/').slice(-2).join('/');
74223  } catch (e) {
74224    return '';
74225  }
74226}
74227
74228/**
74229 * @file text-tracks.js
74230 */
74231/**
74232 * Create captions text tracks on video.js if they do not exist
74233 *
74234 * @param {Object} inbandTextTracks a reference to current inbandTextTracks
74235 * @param {Object} tech the video.js tech
74236 * @param {Object} captionStream the caption stream to create
74237 * @private
74238 */
74239
74240const createCaptionsTrackIfNotExists = function (inbandTextTracks, tech, captionStream) {
74241  if (!inbandTextTracks[captionStream]) {
74242    tech.trigger({
74243      type: 'usage',
74244      name: 'vhs-608'
74245    });
74246    let instreamId = captionStream; // we need to translate SERVICEn for 708 to how mux.js currently labels them
74247
74248    if (/^cc708_/.test(captionStream)) {
74249      instreamId = 'SERVICE' + captionStream.split('_')[1];
74250    }
74251    const track = tech.textTracks().getTrackById(instreamId);
74252    if (track) {
74253      // Resuse an existing track with a CC# id because this was
74254      // very likely created by videojs-contrib-hls from information
74255      // in the m3u8 for us to use
74256      inbandTextTracks[captionStream] = track;
74257    } else {
74258      // This section gets called when we have caption services that aren't specified in the manifest.
74259      // Manifest level caption services are handled in media-groups.js under CLOSED-CAPTIONS.
74260      const captionServices = tech.options_.vhs && tech.options_.vhs.captionServices || {};
74261      let label = captionStream;
74262      let language = captionStream;
74263      let def = false;
74264      const captionService = captionServices[instreamId];
74265      if (captionService) {
74266        label = captionService.label;
74267        language = captionService.language;
74268        def = captionService.default;
74269      } // Otherwise, create a track with the default `CC#` label and
74270      // without a language
74271
74272      inbandTextTracks[captionStream] = tech.addRemoteTextTrack({
74273        kind: 'captions',
74274        id: instreamId,
74275        // TODO: investigate why this doesn't seem to turn the caption on by default
74276        default: def,
74277        label,
74278        language
74279      }, false).track;
74280    }
74281  }
74282};
74283/**
74284 * Add caption text track data to a source handler given an array of captions
74285 *
74286 * @param {Object}
74287 *   @param {Object} inbandTextTracks the inband text tracks
74288 *   @param {number} timestampOffset the timestamp offset of the source buffer
74289 *   @param {Array} captionArray an array of caption data
74290 * @private
74291 */
74292
74293const addCaptionData = function ({
74294  inbandTextTracks,
74295  captionArray,
74296  timestampOffset
74297}) {
74298  if (!captionArray) {
74299    return;
74300  }
74301  const Cue = window__default["default"].WebKitDataCue || window__default["default"].VTTCue;
74302  captionArray.forEach(caption => {
74303    const track = caption.stream; // in CEA 608 captions, video.js/mux.js sends a content array
74304    // with positioning data
74305
74306    if (caption.content) {
74307      caption.content.forEach(value => {
74308        const cue = new Cue(caption.startTime + timestampOffset, caption.endTime + timestampOffset, value.text);
74309        cue.line = value.line;
74310        cue.align = 'left';
74311        cue.position = value.position;
74312        cue.positionAlign = 'line-left';
74313        inbandTextTracks[track].addCue(cue);
74314      });
74315    } else {
74316      // otherwise, a text value with combined captions is sent
74317      inbandTextTracks[track].addCue(new Cue(caption.startTime + timestampOffset, caption.endTime + timestampOffset, caption.text));
74318    }
74319  });
74320};
74321/**
74322 * Define properties on a cue for backwards compatability,
74323 * but warn the user that the way that they are using it
74324 * is depricated and will be removed at a later date.
74325 *
74326 * @param {Cue} cue the cue to add the properties on
74327 * @private
74328 */
74329
74330const deprecateOldCue = function (cue) {
74331  Object.defineProperties(cue.frame, {
74332    id: {
74333      get() {
74334        videojs.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
74335        return cue.value.key;
74336      }
74337    },
74338    value: {
74339      get() {
74340        videojs.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
74341        return cue.value.data;
74342      }
74343    },
74344    privateData: {
74345      get() {
74346        videojs.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
74347        return cue.value.data;
74348      }
74349    }
74350  });
74351};
74352/**
74353 * Add metadata text track data to a source handler given an array of metadata
74354 *
74355 * @param {Object}
74356 *   @param {Object} inbandTextTracks the inband text tracks
74357 *   @param {Array} metadataArray an array of meta data
74358 *   @param {number} timestampOffset the timestamp offset of the source buffer
74359 *   @param {number} videoDuration the duration of the video
74360 * @private
74361 */
74362
74363const addMetadata = ({
74364  inbandTextTracks,
74365  metadataArray,
74366  timestampOffset,
74367  videoDuration
74368}) => {
74369  if (!metadataArray) {
74370    return;
74371  }
74372  const Cue = window__default["default"].WebKitDataCue || window__default["default"].VTTCue;
74373  const metadataTrack = inbandTextTracks.metadataTrack_;
74374  if (!metadataTrack) {
74375    return;
74376  }
74377  metadataArray.forEach(metadata => {
74378    const time = metadata.cueTime + timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN,
74379    // ignore this bit of metadata.
74380    // This likely occurs when you have an non-timed ID3 tag like TIT2,
74381    // which is the "Title/Songname/Content description" frame
74382
74383    if (typeof time !== 'number' || window__default["default"].isNaN(time) || time < 0 || !(time < Infinity)) {
74384      return;
74385    } // If we have no frames, we can't create a cue.
74386
74387    if (!metadata.frames || !metadata.frames.length) {
74388      return;
74389    }
74390    metadata.frames.forEach(frame => {
74391      const cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
74392      cue.frame = frame;
74393      cue.value = frame;
74394      deprecateOldCue(cue);
74395      metadataTrack.addCue(cue);
74396    });
74397  });
74398  if (!metadataTrack.cues || !metadataTrack.cues.length) {
74399    return;
74400  } // Updating the metadeta cues so that
74401  // the endTime of each cue is the startTime of the next cue
74402  // the endTime of last cue is the duration of the video
74403
74404  const cues = metadataTrack.cues;
74405  const cuesArray = []; // Create a copy of the TextTrackCueList...
74406  // ...disregarding cues with a falsey value
74407
74408  for (let i = 0; i < cues.length; i++) {
74409    if (cues[i]) {
74410      cuesArray.push(cues[i]);
74411    }
74412  } // Group cues by their startTime value
74413
74414  const cuesGroupedByStartTime = cuesArray.reduce((obj, cue) => {
74415    const timeSlot = obj[cue.startTime] || [];
74416    timeSlot.push(cue);
74417    obj[cue.startTime] = timeSlot;
74418    return obj;
74419  }, {}); // Sort startTimes by ascending order
74420
74421  const sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort((a, b) => Number(a) - Number(b)); // Map each cue group's endTime to the next group's startTime
74422
74423  sortedStartTimes.forEach((startTime, idx) => {
74424    const cueGroup = cuesGroupedByStartTime[startTime];
74425    const finiteDuration = isFinite(videoDuration) ? videoDuration : startTime;
74426    const nextTime = Number(sortedStartTimes[idx + 1]) || finiteDuration; // Map each cue's endTime the next group's startTime
74427
74428    cueGroup.forEach(cue => {
74429      cue.endTime = nextTime;
74430    });
74431  });
74432}; // object for mapping daterange attributes
74433
74434const dateRangeAttr = {
74435  id: 'ID',
74436  class: 'CLASS',
74437  startDate: 'START-DATE',
74438  duration: 'DURATION',
74439  endDate: 'END-DATE',
74440  endOnNext: 'END-ON-NEXT',
74441  plannedDuration: 'PLANNED-DURATION',
74442  scte35Out: 'SCTE35-OUT',
74443  scte35In: 'SCTE35-IN'
74444};
74445const dateRangeKeysToOmit = new Set(['id', 'class', 'startDate', 'duration', 'endDate', 'endOnNext', 'startTime', 'endTime', 'processDateRange']);
74446/**
74447 * Add DateRange metadata text track to a source handler given an array of metadata
74448 *
74449 * @param {Object}
74450 *   @param {Object} inbandTextTracks the inband text tracks
74451 *   @param {Array} dateRanges parsed media playlist
74452 * @private
74453 */
74454
74455const addDateRangeMetadata = ({
74456  inbandTextTracks,
74457  dateRanges
74458}) => {
74459  const metadataTrack = inbandTextTracks.metadataTrack_;
74460  if (!metadataTrack) {
74461    return;
74462  }
74463  const Cue = window__default["default"].WebKitDataCue || window__default["default"].VTTCue;
74464  dateRanges.forEach(dateRange => {
74465    // we generate multiple cues for each date range with different attributes
74466    for (const key of Object.keys(dateRange)) {
74467      if (dateRangeKeysToOmit.has(key)) {
74468        continue;
74469      }
74470      const cue = new Cue(dateRange.startTime, dateRange.endTime, '');
74471      cue.id = dateRange.id;
74472      cue.type = 'com.apple.quicktime.HLS';
74473      cue.value = {
74474        key: dateRangeAttr[key],
74475        data: dateRange[key]
74476      };
74477      if (key === 'scte35Out' || key === 'scte35In') {
74478        cue.value.data = new Uint8Array(cue.value.data.match(/[\da-f]{2}/gi)).buffer;
74479      }
74480      metadataTrack.addCue(cue);
74481    }
74482    dateRange.processDateRange();
74483  });
74484};
74485/**
74486 * Create metadata text track on video.js if it does not exist
74487 *
74488 * @param {Object} inbandTextTracks a reference to current inbandTextTracks
74489 * @param {string} dispatchType the inband metadata track dispatch type
74490 * @param {Object} tech the video.js tech
74491 * @private
74492 */
74493
74494const createMetadataTrackIfNotExists = (inbandTextTracks, dispatchType, tech) => {
74495  if (inbandTextTracks.metadataTrack_) {
74496    return;
74497  }
74498  inbandTextTracks.metadataTrack_ = tech.addRemoteTextTrack({
74499    kind: 'metadata',
74500    label: 'Timed Metadata'
74501  }, false).track;
74502  if (!videojs.browser.IS_ANY_SAFARI) {
74503    inbandTextTracks.metadataTrack_.inBandMetadataTrackDispatchType = dispatchType;
74504  }
74505};
74506/**
74507 * Remove cues from a track on video.js.
74508 *
74509 * @param {Double} start start of where we should remove the cue
74510 * @param {Double} end end of where the we should remove the cue
74511 * @param {Object} track the text track to remove the cues from
74512 * @private
74513 */
74514
74515const removeCuesFromTrack = function (start, end, track) {
74516  let i;
74517  let cue;
74518  if (!track) {
74519    return;
74520  }
74521  if (!track.cues) {
74522    return;
74523  }
74524  i = track.cues.length;
74525  while (i--) {
74526    cue = track.cues[i]; // Remove any cue within the provided start and end time
74527
74528    if (cue.startTime >= start && cue.endTime <= end) {
74529      track.removeCue(cue);
74530    }
74531  }
74532};
74533/**
74534 * Remove duplicate cues from a track on video.js (a cue is considered a
74535 * duplicate if it has the same time interval and text as another)
74536 *
74537 * @param {Object} track the text track to remove the duplicate cues from
74538 * @private
74539 */
74540
74541const removeDuplicateCuesFromTrack = function (track) {
74542  const cues = track.cues;
74543  if (!cues) {
74544    return;
74545  }
74546  const uniqueCues = {};
74547  for (let i = cues.length - 1; i >= 0; i--) {
74548    const cue = cues[i];
74549    const cueKey = `${cue.startTime}-${cue.endTime}-${cue.text}`;
74550    if (uniqueCues[cueKey]) {
74551      track.removeCue(cue);
74552    } else {
74553      uniqueCues[cueKey] = cue;
74554    }
74555  }
74556};
74557
74558/**
74559 * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
74560 * front of current time.
74561 *
74562 * @param {Array} buffer
74563 *        The current buffer of gop information
74564 * @param {number} currentTime
74565 *        The current time
74566 * @param {Double} mapping
74567 *        Offset to map display time to stream presentation time
74568 * @return {Array}
74569 *         List of gops considered safe to append over
74570 */
74571
74572const gopsSafeToAlignWith = (buffer, currentTime, mapping) => {
74573  if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) {
74574    return [];
74575  } // pts value for current time + 3 seconds to give a bit more wiggle room
74576
74577  const currentTimePts = Math.ceil((currentTime - mapping + 3) * clock.ONE_SECOND_IN_TS);
74578  let i;
74579  for (i = 0; i < buffer.length; i++) {
74580    if (buffer[i].pts > currentTimePts) {
74581      break;
74582    }
74583  }
74584  return buffer.slice(i);
74585};
74586/**
74587 * Appends gop information (timing and byteLength) received by the transmuxer for the
74588 * gops appended in the last call to appendBuffer
74589 *
74590 * @param {Array} buffer
74591 *        The current buffer of gop information
74592 * @param {Array} gops
74593 *        List of new gop information
74594 * @param {boolean} replace
74595 *        If true, replace the buffer with the new gop information. If false, append the
74596 *        new gop information to the buffer in the right location of time.
74597 * @return {Array}
74598 *         Updated list of gop information
74599 */
74600
74601const updateGopBuffer = (buffer, gops, replace) => {
74602  if (!gops.length) {
74603    return buffer;
74604  }
74605  if (replace) {
74606    // If we are in safe append mode, then completely overwrite the gop buffer
74607    // with the most recent appeneded data. This will make sure that when appending
74608    // future segments, we only try to align with gops that are both ahead of current
74609    // time and in the last segment appended.
74610    return gops.slice();
74611  }
74612  const start = gops[0].pts;
74613  let i = 0;
74614  for (i; i < buffer.length; i++) {
74615    if (buffer[i].pts >= start) {
74616      break;
74617    }
74618  }
74619  return buffer.slice(0, i).concat(gops);
74620};
74621/**
74622 * Removes gop information in buffer that overlaps with provided start and end
74623 *
74624 * @param {Array} buffer
74625 *        The current buffer of gop information
74626 * @param {Double} start
74627 *        position to start the remove at
74628 * @param {Double} end
74629 *        position to end the remove at
74630 * @param {Double} mapping
74631 *        Offset to map display time to stream presentation time
74632 */
74633
74634const removeGopBuffer = (buffer, start, end, mapping) => {
74635  const startPts = Math.ceil((start - mapping) * clock.ONE_SECOND_IN_TS);
74636  const endPts = Math.ceil((end - mapping) * clock.ONE_SECOND_IN_TS);
74637  const updatedBuffer = buffer.slice();
74638  let i = buffer.length;
74639  while (i--) {
74640    if (buffer[i].pts <= endPts) {
74641      break;
74642    }
74643  }
74644  if (i === -1) {
74645    // no removal because end of remove range is before start of buffer
74646    return updatedBuffer;
74647  }
74648  let j = i + 1;
74649  while (j--) {
74650    if (buffer[j].pts <= startPts) {
74651      break;
74652    }
74653  } // clamp remove range start to 0 index
74654
74655  j = Math.max(j, 0);
74656  updatedBuffer.splice(j, i - j + 1);
74657  return updatedBuffer;
74658};
74659const shallowEqual = function (a, b) {
74660  // if both are undefined
74661  // or one or the other is undefined
74662  // they are not equal
74663  if (!a && !b || !a && b || a && !b) {
74664    return false;
74665  } // they are the same object and thus, equal
74666
74667  if (a === b) {
74668    return true;
74669  } // sort keys so we can make sure they have
74670  // all the same keys later.
74671
74672  const akeys = Object.keys(a).sort();
74673  const bkeys = Object.keys(b).sort(); // different number of keys, not equal
74674
74675  if (akeys.length !== bkeys.length) {
74676    return false;
74677  }
74678  for (let i = 0; i < akeys.length; i++) {
74679    const key = akeys[i]; // different sorted keys, not equal
74680
74681    if (key !== bkeys[i]) {
74682      return false;
74683    } // different values, not equal
74684
74685    if (a[key] !== b[key]) {
74686      return false;
74687    }
74688  }
74689  return true;
74690};
74691
74692/**
74693 * The segment loader has no recourse except to fetch a segment in the
74694 * current playlist and use the internal timestamps in that segment to
74695 * generate a syncPoint. This function returns a good candidate index
74696 * for that process.
74697 *
74698 * @param {Array} segments - the segments array from a playlist.
74699 * @return {number} An index of a segment from the playlist to load
74700 */
74701
74702const getSyncSegmentCandidate = function (currentTimeline, segments, targetTime) {
74703  segments = segments || [];
74704  const timelineSegments = [];
74705  let time = 0;
74706  for (let i = 0; i < segments.length; i++) {
74707    const segment = segments[i];
74708    if (currentTimeline === segment.timeline) {
74709      timelineSegments.push(i);
74710      time += segment.duration;
74711      if (time > targetTime) {
74712        return i;
74713      }
74714    }
74715  }
74716  if (timelineSegments.length === 0) {
74717    return 0;
74718  } // default to the last timeline segment
74719
74720  return timelineSegments[timelineSegments.length - 1];
74721}; // In the event of a quota exceeded error, keep at least one second of back buffer. This
74722// number was arbitrarily chosen and may be updated in the future, but seemed reasonable
74723// as a start to prevent any potential issues with removing content too close to the
74724// playhead.
74725
74726const MIN_BACK_BUFFER = 1; // in ms
74727
74728const CHECK_BUFFER_DELAY = 500;
74729const finite = num => typeof num === 'number' && isFinite(num); // With most content hovering around 30fps, if a segment has a duration less than a half
74730// frame at 30fps or one frame at 60fps, the bandwidth and throughput calculations will
74731// not accurately reflect the rest of the content.
74732
74733const MIN_SEGMENT_DURATION_TO_SAVE_STATS = 1 / 60;
74734const illegalMediaSwitch = (loaderType, startingMedia, trackInfo) => {
74735  // Although these checks should most likely cover non 'main' types, for now it narrows
74736  // the scope of our checks.
74737  if (loaderType !== 'main' || !startingMedia || !trackInfo) {
74738    return null;
74739  }
74740  if (!trackInfo.hasAudio && !trackInfo.hasVideo) {
74741    return 'Neither audio nor video found in segment.';
74742  }
74743  if (startingMedia.hasVideo && !trackInfo.hasVideo) {
74744    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.';
74745  }
74746  if (!startingMedia.hasVideo && trackInfo.hasVideo) {
vendor: 6,994 bytes, lines 74747-74905
74747    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.';
74748  }
74749  return null;
74750};
74751/**
74752 * Calculates a time value that is safe to remove from the back buffer without interrupting
74753 * playback.
74754 *
74755 * @param {TimeRange} seekable
74756 *        The current seekable range
74757 * @param {number} currentTime
74758 *        The current time of the player
74759 * @param {number} targetDuration
74760 *        The target duration of the current playlist
74761 * @return {number}
74762 *         Time that is safe to remove from the back buffer without interrupting playback
74763 */
74764
74765const safeBackBufferTrimTime = (seekable, currentTime, targetDuration) => {
74766  // 30 seconds before the playhead provides a safe default for trimming.
74767  //
74768  // Choosing a reasonable default is particularly important for high bitrate content and
74769  // VOD videos/live streams with large windows, as the buffer may end up overfilled and
74770  // throw an APPEND_BUFFER_ERR.
74771  let trimTime = currentTime - Config.BACK_BUFFER_LENGTH;
74772  if (seekable.length) {
74773    // Some live playlists may have a shorter window of content than the full allowed back
74774    // buffer. For these playlists, don't save content that's no longer within the window.
74775    trimTime = Math.max(trimTime, seekable.start(0));
74776  } // Don't remove within target duration of the current time to avoid the possibility of
74777  // removing the GOP currently being played, as removing it can cause playback stalls.
74778
74779  const maxTrimTime = currentTime - targetDuration;
74780  return Math.min(maxTrimTime, trimTime);
74781};
74782const segmentInfoString = segmentInfo => {
74783  const {
74784    startOfSegment,
74785    duration,
74786    segment,
74787    part,
74788    playlist: {
74789      mediaSequence: seq,
74790      id,
74791      segments = []
74792    },
74793    mediaIndex: index,
74794    partIndex,
74795    timeline
74796  } = segmentInfo;
74797  const segmentLen = segments.length - 1;
74798  let selection = 'mediaIndex/partIndex increment';
74799  if (segmentInfo.getMediaInfoForTime) {
74800    selection = `getMediaInfoForTime (${segmentInfo.getMediaInfoForTime})`;
74801  } else if (segmentInfo.isSyncRequest) {
74802    selection = 'getSyncSegmentCandidate (isSyncRequest)';
74803  }
74804  if (segmentInfo.independent) {
74805    selection += ` with independent ${segmentInfo.independent}`;
74806  }
74807  const hasPartIndex = typeof partIndex === 'number';
74808  const name = segmentInfo.segment.uri ? 'segment' : 'pre-segment';
74809  const zeroBasedPartCount = hasPartIndex ? getKnownPartCount({
74810    preloadSegment: segment
74811  }) - 1 : 0;
74812  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}]`;
74813};
74814const timingInfoPropertyForMedia = mediaType => `${mediaType}TimingInfo`;
74815/**
74816 * Returns the timestamp offset to use for the segment.
74817 *
74818 * @param {number} segmentTimeline
74819 *        The timeline of the segment
74820 * @param {number} currentTimeline
74821 *        The timeline currently being followed by the loader
74822 * @param {number} startOfSegment
74823 *        The estimated segment start
74824 * @param {TimeRange[]} buffered
74825 *        The loader's buffer
74826 * @param {boolean} overrideCheck
74827 *        If true, no checks are made to see if the timestamp offset value should be set,
74828 *        but sets it directly to a value.
74829 *
74830 * @return {number|null}
74831 *         Either a number representing a new timestamp offset, or null if the segment is
74832 *         part of the same timeline
74833 */
74834
74835const timestampOffsetForSegment = ({
74836  segmentTimeline,
74837  currentTimeline,
74838  startOfSegment,
74839  buffered,
74840  overrideCheck
74841}) => {
74842  // Check to see if we are crossing a discontinuity to see if we need to set the
74843  // timestamp offset on the transmuxer and source buffer.
74844  //
74845  // Previously, we changed the timestampOffset if the start of this segment was less than
74846  // the currently set timestampOffset, but this isn't desirable as it can produce bad
74847  // behavior, especially around long running live streams.
74848  if (!overrideCheck && segmentTimeline === currentTimeline) {
74849    return null;
74850  } // When changing renditions, it's possible to request a segment on an older timeline. For
74851  // instance, given two renditions with the following:
74852  //
74853  // #EXTINF:10
74854  // segment1
74855  // #EXT-X-DISCONTINUITY
74856  // #EXTINF:10
74857  // segment2
74858  // #EXTINF:10
74859  // segment3
74860  //
74861  // And the current player state:
74862  //
74863  // current time: 8
74864  // buffer: 0 => 20
74865  //
74866  // The next segment on the current rendition would be segment3, filling the buffer from
74867  // 20s onwards. However, if a rendition switch happens after segment2 was requested,
74868  // then the next segment to be requested will be segment1 from the new rendition in
74869  // order to fill time 8 and onwards. Using the buffered end would result in repeated
74870  // content (since it would position segment1 of the new rendition starting at 20s). This
74871  // case can be identified when the new segment's timeline is a prior value. Instead of
74872  // using the buffered end, the startOfSegment can be used, which, hopefully, will be
74873  // more accurate to the actual start time of the segment.
74874
74875  if (segmentTimeline < currentTimeline) {
74876    return startOfSegment;
74877  } // segmentInfo.startOfSegment used to be used as the timestamp offset, however, that
74878  // value uses the end of the last segment if it is available. While this value
74879  // should often be correct, it's better to rely on the buffered end, as the new
74880  // content post discontinuity should line up with the buffered end as if it were
74881  // time 0 for the new content.
74882
74883  return buffered.length ? buffered.end(buffered.length - 1) : startOfSegment;
74884};
74885/**
74886 * Returns whether or not the loader should wait for a timeline change from the timeline
74887 * change controller before processing the segment.
74888 *
74889 * Primary timing in VHS goes by video. This is different from most media players, as
74890 * audio is more often used as the primary timing source. For the foreseeable future, VHS
74891 * will continue to use video as the primary timing source, due to the current logic and
74892 * expectations built around it.
74893
74894 * Since the timing follows video, in order to maintain sync, the video loader is
74895 * responsible for setting both audio and video source buffer timestamp offsets.
74896 *
74897 * Setting different values for audio and video source buffers could lead to
74898 * desyncing. The following examples demonstrate some of the situations where this
74899 * distinction is important. Note that all of these cases involve demuxed content. When
74900 * content is muxed, the audio and video are packaged together, therefore syncing
74901 * separate media playlists is not an issue.
74902 *
74903 * CASE 1: Audio prepares to load a new timeline before video:
74904 *
74905 * Timeline:       0                 1
vendor: 4,173 bytes, lines 74906-74989
74906 * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
74907 * Audio Loader:                     ^
74908 * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
74909 * Video Loader              ^
74910 *
74911 * In the above example, the audio loader is preparing to load the 6th segment, the first
74912 * after a discontinuity, while the video loader is still loading the 5th segment, before
74913 * the discontinuity.
74914 *
74915 * If the audio loader goes ahead and loads and appends the 6th segment before the video
74916 * loader crosses the discontinuity, then when appended, the 6th audio segment will use
74917 * the timestamp offset from timeline 0. This will likely lead to desyncing. In addition,
74918 * the audio loader must provide the audioAppendStart value to trim the content in the
74919 * transmuxer, and that value relies on the audio timestamp offset. Since the audio
74920 * timestamp offset is set by the video (main) loader, the audio loader shouldn't load the
74921 * segment until that value is provided.
74922 *
74923 * CASE 2: Video prepares to load a new timeline before audio:
74924 *
74925 * Timeline:       0                 1
74926 * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
74927 * Audio Loader:             ^
74928 * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
74929 * Video Loader                      ^
74930 *
74931 * In the above example, the video loader is preparing to load the 6th segment, the first
74932 * after a discontinuity, while the audio loader is still loading the 5th segment, before
74933 * the discontinuity.
74934 *
74935 * If the video loader goes ahead and loads and appends the 6th segment, then once the
74936 * segment is loaded and processed, both the video and audio timestamp offsets will be
74937 * set, since video is used as the primary timing source. This is to ensure content lines
74938 * up appropriately, as any modifications to the video timing are reflected by audio when
74939 * the video loader sets the audio and video timestamp offsets to the same value. However,
74940 * setting the timestamp offset for audio before audio has had a chance to change
74941 * timelines will likely lead to desyncing, as the audio loader will append segment 5 with
74942 * a timestamp intended to apply to segments from timeline 1 rather than timeline 0.
74943 *
74944 * CASE 3: When seeking, audio prepares to load a new timeline before video
74945 *
74946 * Timeline:       0                 1
74947 * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
74948 * Audio Loader:           ^
74949 * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9
74950 * Video Loader            ^
74951 *
74952 * In the above example, both audio and video loaders are loading segments from timeline
74953 * 0, but imagine that the seek originated from timeline 1.
74954 *
74955 * When seeking to a new timeline, the timestamp offset will be set based on the expected
74956 * segment start of the loaded video segment. In order to maintain sync, the audio loader
74957 * must wait for the video loader to load its segment and update both the audio and video
74958 * timestamp offsets before it may load and append its own segment. This is the case
74959 * whether the seek results in a mismatched segment request (e.g., the audio loader
74960 * chooses to load segment 3 and the video loader chooses to load segment 4) or the
74961 * loaders choose to load the same segment index from each playlist, as the segments may
74962 * not be aligned perfectly, even for matching segment indexes.
74963 *
74964 * @param {Object} timelinechangeController
74965 * @param {number} currentTimeline
74966 *        The timeline currently being followed by the loader
74967 * @param {number} segmentTimeline
74968 *        The timeline of the segment being loaded
74969 * @param {('main'|'audio')} loaderType
74970 *        The loader type
74971 * @param {boolean} audioDisabled
74972 *        Whether the audio is disabled for the loader. This should only be true when the
74973 *        loader may have muxed audio in its segment, but should not append it, e.g., for
74974 *        the main loader when an alternate audio playlist is active.
74975 *
74976 * @return {boolean}
74977 *         Whether the loader should wait for a timeline change from the timeline change
74978 *         controller before processing the segment
74979 */
74980
74981const shouldWaitForTimelineChange = ({
74982  timelineChangeController,
74983  currentTimeline,
74984  segmentTimeline,
74985  loaderType,
74986  audioDisabled
74987}) => {
74988  if (currentTimeline === segmentTimeline) {
74989    return false;
vendor: 5,855 bytes, lines 74990-75132
74990  }
74991  if (loaderType === 'audio') {
74992    const lastMainTimelineChange = timelineChangeController.lastTimelineChange({
74993      type: 'main'
74994    }); // Audio loader should wait if:
74995    //
74996    // * main hasn't had a timeline change yet (thus has not loaded its first segment)
74997    // * main hasn't yet changed to the timeline audio is looking to load
74998
74999    return !lastMainTimelineChange || lastMainTimelineChange.to !== segmentTimeline;
75000  } // The main loader only needs to wait for timeline changes if there's demuxed audio.
75001  // Otherwise, there's nothing to wait for, since audio would be muxed into the main
75002  // loader's segments (or the content is audio/video only and handled by the main
75003  // loader).
75004
75005  if (loaderType === 'main' && audioDisabled) {
75006    const pendingAudioTimelineChange = timelineChangeController.pendingTimelineChange({
75007      type: 'audio'
75008    }); // Main loader should wait for the audio loader if audio is not pending a timeline
75009    // change to the current timeline.
75010    //
75011    // Since the main loader is responsible for setting the timestamp offset for both
75012    // audio and video, the main loader must wait for audio to be about to change to its
75013    // timeline before setting the offset, otherwise, if audio is behind in loading,
75014    // segments from the previous timeline would be adjusted by the new timestamp offset.
75015    //
75016    // This requirement means that video will not cross a timeline until the audio is
75017    // about to cross to it, so that way audio and video will always cross the timeline
75018    // together.
75019    //
75020    // In addition to normal timeline changes, these rules also apply to the start of a
75021    // stream (going from a non-existent timeline, -1, to timeline 0). It's important
75022    // that these rules apply to the first timeline change because if they did not, it's
75023    // possible that the main loader will cross two timelines before the audio loader has
75024    // crossed one. Logic may be implemented to handle the startup as a special case, but
75025    // it's easier to simply treat all timeline changes the same.
75026
75027    if (pendingAudioTimelineChange && pendingAudioTimelineChange.to === segmentTimeline) {
75028      return false;
75029    }
75030    return true;
75031  }
75032  return false;
75033};
75034const shouldFixBadTimelineChanges = timelineChangeController => {
75035  if (!timelineChangeController) {
75036    return false;
75037  }
75038  const pendingAudioTimelineChange = timelineChangeController.pendingTimelineChange({
75039    type: 'audio'
75040  });
75041  const pendingMainTimelineChange = timelineChangeController.pendingTimelineChange({
75042    type: 'main'
75043  });
75044  const hasPendingTimelineChanges = pendingAudioTimelineChange && pendingMainTimelineChange;
75045  const differentPendingChanges = hasPendingTimelineChanges && pendingAudioTimelineChange.to !== pendingMainTimelineChange.to;
75046  const isNotInitialPendingTimelineChange = hasPendingTimelineChanges && pendingAudioTimelineChange.from !== -1 && pendingMainTimelineChange.from !== -1;
75047  if (isNotInitialPendingTimelineChange && differentPendingChanges) {
75048    return true;
75049  }
75050  return false;
75051};
75052/**
75053 * Check if the pending audio timeline change is behind the
75054 * pending main timeline change.
75055 *
75056 * @param {SegmentLoader} segmentLoader
75057 * @return {boolean}
75058 */
75059
75060const isAudioTimelineBehind = segmentLoader => {
75061  const pendingAudioTimelineChange = segmentLoader.timelineChangeController_.pendingTimelineChange({
75062    type: 'audio'
75063  });
75064  const pendingMainTimelineChange = segmentLoader.timelineChangeController_.pendingTimelineChange({
75065    type: 'main'
75066  });
75067  const hasPendingTimelineChanges = pendingAudioTimelineChange && pendingMainTimelineChange;
75068  return hasPendingTimelineChanges && pendingAudioTimelineChange.to < pendingMainTimelineChange.to;
75069};
75070/**
75071 * A method to check if the player is waiting for a timeline change, and fixes
75072 * certain scenarios where the timelines need to be updated.
75073 *
75074 * @param {SegmentLoader} segmentLoader
75075 */
75076
75077const checkAndFixTimelines = segmentLoader => {
75078  const segmentInfo = segmentLoader.pendingSegment_;
75079  if (!segmentInfo) {
75080    return;
75081  }
75082  const waitingForTimelineChange = shouldWaitForTimelineChange({
75083    timelineChangeController: segmentLoader.timelineChangeController_,
75084    currentTimeline: segmentLoader.currentTimeline_,
75085    segmentTimeline: segmentInfo.timeline,
75086    loaderType: segmentLoader.loaderType_,
75087    audioDisabled: segmentLoader.audioDisabled_
75088  });
75089  if (waitingForTimelineChange && shouldFixBadTimelineChanges(segmentLoader.timelineChangeController_)) {
75090    if (isAudioTimelineBehind(segmentLoader)) {
75091      segmentLoader.timelineChangeController_.trigger('audioTimelineBehind');
75092      return;
75093    }
75094    segmentLoader.timelineChangeController_.trigger('fixBadTimelineChange');
75095  }
75096};
75097const mediaDuration = timingInfos => {
75098  let maxDuration = 0;
75099  ['video', 'audio'].forEach(function (type) {
75100    const typeTimingInfo = timingInfos[`${type}TimingInfo`];
75101    if (!typeTimingInfo) {
75102      return;
75103    }
75104    const {
75105      start,
75106      end
75107    } = typeTimingInfo;
75108    let duration;
75109    if (typeof start === 'bigint' || typeof end === 'bigint') {
75110      duration = window__default["default"].BigInt(end) - window__default["default"].BigInt(start);
75111    } else if (typeof start === 'number' && typeof end === 'number') {
75112      duration = end - start;
75113    }
75114    if (typeof duration !== 'undefined' && duration > maxDuration) {
75115      maxDuration = duration;
75116    }
75117  }); // convert back to a number if it is lower than MAX_SAFE_INTEGER
75118  // as we only need BigInt when we are above that.
75119
75120  if (typeof maxDuration === 'bigint' && maxDuration < Number.MAX_SAFE_INTEGER) {
75121    maxDuration = Number(maxDuration);
75122  }
75123  return maxDuration;
75124};
75125const segmentTooLong = ({
75126  segmentDuration,
75127  maxDuration
75128}) => {
75129  // 0 duration segments are most likely due to metadata only segments or a lack of
75130  // information.
75131  if (!segmentDuration) {
75132    return false;
vendor: 5,184 bytes, lines 75133-75266
75133  } // For HLS:
75134  //
75135  // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.3.1
75136  // The EXTINF duration of each Media Segment in the Playlist
75137  // file, when rounded to the nearest integer, MUST be less than or equal
75138  // to the target duration; longer segments can trigger playback stalls
75139  // or other errors.
75140  //
75141  // For DASH, the mpd-parser uses the largest reported segment duration as the target
75142  // duration. Although that reported duration is occasionally approximate (i.e., not
75143  // exact), a strict check may report that a segment is too long more often in DASH.
75144
75145  return Math.round(segmentDuration) > maxDuration + TIME_FUDGE_FACTOR;
75146};
75147const getTroublesomeSegmentDurationMessage = (segmentInfo, sourceType) => {
75148  // Right now we aren't following DASH's timing model exactly, so only perform
75149  // this check for HLS content.
75150  if (sourceType !== 'hls') {
75151    return null;
75152  }
75153  const segmentDuration = mediaDuration({
75154    audioTimingInfo: segmentInfo.audioTimingInfo,
75155    videoTimingInfo: segmentInfo.videoTimingInfo
75156  }); // Don't report if we lack information.
75157  //
75158  // If the segment has a duration of 0 it is either a lack of information or a
75159  // metadata only segment and shouldn't be reported here.
75160
75161  if (!segmentDuration) {
75162    return null;
75163  }
75164  const targetDuration = segmentInfo.playlist.targetDuration;
75165  const isSegmentWayTooLong = segmentTooLong({
75166    segmentDuration,
75167    maxDuration: targetDuration * 2
75168  });
75169  const isSegmentSlightlyTooLong = segmentTooLong({
75170    segmentDuration,
75171    maxDuration: targetDuration
75172  });
75173  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';
75174  if (isSegmentWayTooLong || isSegmentSlightlyTooLong) {
75175    return {
75176      severity: isSegmentWayTooLong ? 'warn' : 'info',
75177      message: segmentTooLongMessage
75178    };
75179  }
75180  return null;
75181};
75182/**
75183 *
75184 * @param {Object} options type of segment loader and segment either segmentInfo or simple segment
75185 * @return a segmentInfo payload for events or errors.
75186 */
75187
75188const segmentInfoPayload = ({
75189  type,
75190  segment
75191}) => {
75192  if (!segment) {
75193    return;
75194  }
75195  const isEncrypted = Boolean(segment.key || segment.map && segment.map.ke);
75196  const isMediaInitialization = Boolean(segment.map && !segment.map.bytes);
75197  const start = segment.startOfSegment === undefined ? segment.start : segment.startOfSegment;
75198  return {
75199    type: type || segment.type,
75200    uri: segment.resolvedUri || segment.uri,
75201    start,
75202    duration: segment.duration,
75203    isEncrypted,
75204    isMediaInitialization
75205  };
75206};
75207/**
75208 * An object that manages segment loading and appending.
75209 *
75210 * @class SegmentLoader
75211 * @param {Object} options required and optional options
75212 * @extends videojs.EventTarget
75213 */
75214
75215class SegmentLoader extends videojs.EventTarget {
75216  constructor(settings, options = {}) {
75217    super(); // check pre-conditions
75218
75219    if (!settings) {
75220      throw new TypeError('Initialization settings are required');
75221    }
75222    if (typeof settings.currentTime !== 'function') {
75223      throw new TypeError('No currentTime getter specified');
75224    }
75225    if (!settings.mediaSource) {
75226      throw new TypeError('No MediaSource specified');
75227    } // public properties
75228
75229    this.bandwidth = settings.bandwidth;
75230    this.throughput = {
75231      rate: 0,
75232      count: 0
75233    };
75234    this.roundTrip = NaN;
75235    this.resetStats_();
75236    this.mediaIndex = null;
75237    this.partIndex = null; // private settings
75238
75239    this.hasPlayed_ = settings.hasPlayed;
75240    this.currentTime_ = settings.currentTime;
75241    this.seekable_ = settings.seekable;
75242    this.seeking_ = settings.seeking;
75243    this.duration_ = settings.duration;
75244    this.mediaSource_ = settings.mediaSource;
75245    this.vhs_ = settings.vhs;
75246    this.loaderType_ = settings.loaderType;
75247    this.currentMediaInfo_ = void 0;
75248    this.startingMediaInfo_ = void 0;
75249    this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
75250    this.goalBufferLength_ = settings.goalBufferLength;
75251    this.sourceType_ = settings.sourceType;
75252    this.sourceUpdater_ = settings.sourceUpdater;
75253    this.inbandTextTracks_ = settings.inbandTextTracks;
75254    this.state_ = 'INIT';
75255    this.timelineChangeController_ = settings.timelineChangeController;
75256    this.shouldSaveSegmentTimingInfo_ = true;
75257    this.parse708captions_ = settings.parse708captions;
75258    this.useDtsForTimestampOffset_ = settings.useDtsForTimestampOffset;
75259    this.captionServices_ = settings.captionServices;
75260    this.exactManifestTimings = settings.exactManifestTimings;
75261    this.addMetadataToTextTrack = settings.addMetadataToTextTrack; // private instance variables
75262
75263    this.checkBufferTimeout_ = null;
75264    this.error_ = void 0;
75265    this.currentTimeline_ = -1;
75266    this.shouldForceTimestampOffsetAfterResync_ = false;
vendor: 20,086 bytes, lines 75267-75840
75267    this.pendingSegment_ = null;
75268    this.xhrOptions_ = null;
75269    this.pendingSegments_ = [];
75270    this.audioDisabled_ = false;
75271    this.isPendingTimestampOffset_ = false; // TODO possibly move gopBuffer and timeMapping info to a separate controller
75272
75273    this.gopBuffer_ = [];
75274    this.timeMapping_ = 0;
75275    this.safeAppend_ = false;
75276    this.appendInitSegment_ = {
75277      audio: true,
75278      video: true
75279    };
75280    this.playlistOfLastInitSegment_ = {
75281      audio: null,
75282      video: null
75283    };
75284    this.callQueue_ = []; // If the segment loader prepares to load a segment, but does not have enough
75285    // information yet to start the loading process (e.g., if the audio loader wants to
75286    // load a segment from the next timeline but the main loader hasn't yet crossed that
75287    // timeline), then the load call will be added to the queue until it is ready to be
75288    // processed.
75289
75290    this.loadQueue_ = [];
75291    this.metadataQueue_ = {
75292      id3: [],
75293      caption: []
75294    };
75295    this.waitingOnRemove_ = false;
75296    this.quotaExceededErrorRetryTimeout_ = null; // Fragmented mp4 playback
75297
75298    this.activeInitSegmentId_ = null;
75299    this.initSegments_ = {}; // HLSe playback
75300
75301    this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys;
75302    this.keyCache_ = {};
75303    this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings
75304    // between a time in the display time and a segment index within
75305    // a playlist
75306
75307    this.syncController_ = settings.syncController;
75308    this.syncPoint_ = {
75309      segmentIndex: 0,
75310      time: 0
75311    };
75312    this.transmuxer_ = this.createTransmuxer_();
75313    this.triggerSyncInfoUpdate_ = () => this.trigger('syncinfoupdate');
75314    this.syncController_.on('syncinfoupdate', this.triggerSyncInfoUpdate_);
75315    this.mediaSource_.addEventListener('sourceopen', () => {
75316      if (!this.isEndOfStream_()) {
75317        this.ended_ = false;
75318      }
75319    }); // ...for determining the fetch location
75320
75321    this.fetchAtBuffer_ = false;
75322    this.logger_ = logger(`SegmentLoader[${this.loaderType_}]`);
75323    Object.defineProperty(this, 'state', {
75324      get() {
75325        return this.state_;
75326      },
75327      set(newState) {
75328        if (newState !== this.state_) {
75329          this.logger_(`${this.state_} -> ${newState}`);
75330          this.state_ = newState;
75331          this.trigger('statechange');
75332        }
75333      }
75334    });
75335    this.sourceUpdater_.on('ready', () => {
75336      if (this.hasEnoughInfoToAppend_()) {
75337        this.processCallQueue_();
75338      } else {
75339        checkAndFixTimelines(this);
75340      }
75341    });
75342    this.sourceUpdater_.on('codecschange', metadata => {
75343      this.trigger(_extends__default["default"]({
75344        type: 'codecschange'
75345      }, metadata));
75346    }); // Only the main loader needs to listen for pending timeline changes, as the main
75347    // loader should wait for audio to be ready to change its timeline so that both main
75348    // and audio timelines change together. For more details, see the
75349    // shouldWaitForTimelineChange function.
75350
75351    if (this.loaderType_ === 'main') {
75352      this.timelineChangeController_.on('pendingtimelinechange', () => {
75353        if (this.hasEnoughInfoToAppend_()) {
75354          this.processCallQueue_();
75355        } else {
75356          checkAndFixTimelines(this);
75357        }
75358      });
75359    } // The main loader only listens on pending timeline changes, but the audio loader,
75360    // since its loads follow main, needs to listen on timeline changes. For more details,
75361    // see the shouldWaitForTimelineChange function.
75362
75363    if (this.loaderType_ === 'audio') {
75364      this.timelineChangeController_.on('timelinechange', metadata => {
75365        this.trigger(_extends__default["default"]({
75366          type: 'timelinechange'
75367        }, metadata));
75368        if (this.hasEnoughInfoToLoad_()) {
75369          this.processLoadQueue_();
75370        } else {
75371          checkAndFixTimelines(this);
75372        }
75373        if (this.hasEnoughInfoToAppend_()) {
75374          this.processCallQueue_();
75375        } else {
75376          checkAndFixTimelines(this);
75377        }
75378      });
75379    }
75380  }
75381  /**
75382   * TODO: Current sync controller consists of many hls-specific strategies
75383   * media sequence sync is also hls-specific, and we would like to be protocol-agnostic on this level
75384   * this should be a part of the sync-controller and sync controller should expect different strategy list based on the protocol.
75385   *
75386   * @return {MediaSequenceSync|null}
75387   * @private
75388   */
75389
75390  get mediaSequenceSync_() {
75391    return this.syncController_.getMediaSequenceSync(this.loaderType_);
75392  }
75393  createTransmuxer_() {
75394    return segmentTransmuxer.createTransmuxer({
75395      remux: false,
75396      alignGopsAtEnd: this.safeAppend_,
75397      keepOriginalTimestamps: true,
75398      parse708captions: this.parse708captions_,
75399      captionServices: this.captionServices_
75400    });
75401  }
75402  /**
75403   * reset all of our media stats
75404   *
75405   * @private
75406   */
75407
75408  resetStats_() {
75409    this.mediaBytesTransferred = 0;
75410    this.mediaRequests = 0;
75411    this.mediaRequestsAborted = 0;
75412    this.mediaRequestsTimedout = 0;
75413    this.mediaRequestsErrored = 0;
75414    this.mediaTransferDuration = 0;
75415    this.mediaSecondsLoaded = 0;
75416    this.mediaAppends = 0;
75417  }
75418  /**
75419   * dispose of the SegmentLoader and reset to the default state
75420   */
75421
75422  dispose() {
75423    this.trigger('dispose');
75424    this.state = 'DISPOSED';
75425    this.pause();
75426    this.abort_();
75427    if (this.transmuxer_) {
75428      this.transmuxer_.terminate();
75429    }
75430    this.resetStats_();
75431    if (this.checkBufferTimeout_) {
75432      window__default["default"].clearTimeout(this.checkBufferTimeout_);
75433    }
75434    if (this.syncController_ && this.triggerSyncInfoUpdate_) {
75435      this.syncController_.off('syncinfoupdate', this.triggerSyncInfoUpdate_);
75436    }
75437    this.off();
75438  }
75439  setAudio(enable) {
75440    this.audioDisabled_ = !enable;
75441    if (enable) {
75442      this.appendInitSegment_.audio = true;
75443    } else {
75444      // remove current track audio if it gets disabled
75445      this.sourceUpdater_.removeAudio(0, this.duration_());
75446    }
75447  }
75448  /**
75449   * abort anything that is currently doing on with the SegmentLoader
75450   * and reset to a default state
75451   */
75452
75453  abort() {
75454    if (this.state !== 'WAITING') {
75455      if (this.pendingSegment_) {
75456        this.pendingSegment_ = null;
75457      }
75458      this.timelineChangeController_.clearPendingTimelineChange(this.loaderType_);
75459      return;
75460    }
75461    this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY
75462    // since we are no longer "waiting" on any requests. XHR callback is not always run
75463    // when the request is aborted. This will prevent the loader from being stuck in the
75464    // WAITING state indefinitely.
75465
75466    this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the
75467    // next segment
75468
75469    if (!this.paused()) {
75470      this.monitorBuffer_();
75471    }
75472  }
75473  /**
75474   * abort all pending xhr requests and null any pending segements
75475   *
75476   * @private
75477   */
75478
75479  abort_() {
75480    if (this.pendingSegment_ && this.pendingSegment_.abortRequests) {
75481      this.pendingSegment_.abortRequests();
75482    } // clear out the segment being processed
75483
75484    this.pendingSegment_ = null;
75485    this.callQueue_ = [];
75486    this.loadQueue_ = [];
75487    this.metadataQueue_.id3 = [];
75488    this.metadataQueue_.caption = [];
75489    this.timelineChangeController_.clearPendingTimelineChange(this.loaderType_);
75490    this.waitingOnRemove_ = false;
75491    window__default["default"].clearTimeout(this.quotaExceededErrorRetryTimeout_);
75492    this.quotaExceededErrorRetryTimeout_ = null;
75493  }
75494  checkForAbort_(requestId) {
75495    // If the state is APPENDING, then aborts will not modify the state, meaning the first
75496    // callback that happens should reset the state to READY so that loading can continue.
75497    if (this.state === 'APPENDING' && !this.pendingSegment_) {
75498      this.state = 'READY';
75499      return true;
75500    }
75501    if (!this.pendingSegment_ || this.pendingSegment_.requestId !== requestId) {
75502      return true;
75503    }
75504    return false;
75505  }
75506  /**
75507   * set an error on the segment loader and null out any pending segements
75508   *
75509   * @param {Error} error the error to set on the SegmentLoader
75510   * @return {Error} the error that was set or that is currently set
75511   */
75512
75513  error(error) {
75514    if (typeof error !== 'undefined') {
75515      this.logger_('error occurred:', error);
75516      this.error_ = error;
75517    }
75518    this.pendingSegment_ = null;
75519    return this.error_;
75520  }
75521  endOfStream() {
75522    this.ended_ = true;
75523    if (this.transmuxer_) {
75524      // need to clear out any cached data to prepare for the new segment
75525      segmentTransmuxer.reset(this.transmuxer_);
75526    }
75527    this.gopBuffer_.length = 0;
75528    this.pause();
75529    this.trigger('ended');
75530  }
75531  /**
75532   * Indicates which time ranges are buffered
75533   *
75534   * @return {TimeRange}
75535   *         TimeRange object representing the current buffered ranges
75536   */
75537
75538  buffered_() {
75539    const trackInfo = this.getMediaInfo_();
75540    if (!this.sourceUpdater_ || !trackInfo) {
75541      return createTimeRanges();
75542    }
75543    if (this.loaderType_ === 'main') {
75544      const {
75545        hasAudio,
75546        hasVideo,
75547        isMuxed
75548      } = trackInfo;
75549      if (hasVideo && hasAudio && !this.audioDisabled_ && !isMuxed) {
75550        return this.sourceUpdater_.buffered();
75551      }
75552      if (hasVideo) {
75553        return this.sourceUpdater_.videoBuffered();
75554      }
75555    } // One case that can be ignored for now is audio only with alt audio,
75556    // as we don't yet have proper support for that.
75557
75558    return this.sourceUpdater_.audioBuffered();
75559  }
75560  /**
75561   * Gets and sets init segment for the provided map
75562   *
75563   * @param {Object} map
75564   *        The map object representing the init segment to get or set
75565   * @param {boolean=} set
75566   *        If true, the init segment for the provided map should be saved
75567   * @return {Object}
75568   *         map object for desired init segment
75569   */
75570
75571  initSegmentForMap(map, set = false) {
75572    if (!map) {
75573      return null;
75574    }
75575    const id = initSegmentId(map);
75576    let storedMap = this.initSegments_[id];
75577    if (set && !storedMap && map.bytes) {
75578      this.initSegments_[id] = storedMap = {
75579        resolvedUri: map.resolvedUri,
75580        byterange: map.byterange,
75581        bytes: map.bytes,
75582        tracks: map.tracks,
75583        timescales: map.timescales
75584      };
75585    }
75586    return storedMap || map;
75587  }
75588  /**
75589   * Gets and sets key for the provided key
75590   *
75591   * @param {Object} key
75592   *        The key object representing the key to get or set
75593   * @param {boolean=} set
75594   *        If true, the key for the provided key should be saved
75595   * @return {Object}
75596   *         Key object for desired key
75597   */
75598
75599  segmentKey(key, set = false) {
75600    if (!key) {
75601      return null;
75602    }
75603    const id = segmentKeyId(key);
75604    let storedKey = this.keyCache_[id]; // TODO: We should use the HTTP Expires header to invalidate our cache per
75605    // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3
75606
75607    if (this.cacheEncryptionKeys_ && set && !storedKey && key.bytes) {
75608      this.keyCache_[id] = storedKey = {
75609        resolvedUri: key.resolvedUri,
75610        bytes: key.bytes
75611      };
75612    }
75613    const result = {
75614      resolvedUri: (storedKey || key).resolvedUri
75615    };
75616    if (storedKey) {
75617      result.bytes = storedKey.bytes;
75618    }
75619    return result;
75620  }
75621  /**
75622   * Returns true if all configuration required for loading is present, otherwise false.
75623   *
75624   * @return {boolean} True if the all configuration is ready for loading
75625   * @private
75626   */
75627
75628  couldBeginLoading_() {
75629    return this.playlist_ && !this.paused();
75630  }
75631  /**
75632   * load a playlist and start to fill the buffer
75633   */
75634
75635  load() {
75636    // un-pause
75637    this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be
75638    // specified
75639
75640    if (!this.playlist_) {
75641      return;
75642    } // if all the configuration is ready, initialize and begin loading
75643
75644    if (this.state === 'INIT' && this.couldBeginLoading_()) {
75645      return this.init_();
75646    } // if we're in the middle of processing a segment already, don't
75647    // kick off an additional segment request
75648
75649    if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
75650      return;
75651    }
75652    this.state = 'READY';
75653  }
75654  /**
75655   * Once all the starting parameters have been specified, begin
75656   * operation. This method should only be invoked from the INIT
75657   * state.
75658   *
75659   * @private
75660   */
75661
75662  init_() {
75663    this.state = 'READY'; // if this is the audio segment loader, and it hasn't been inited before, then any old
75664    // audio data from the muxed content should be removed
75665
75666    this.resetEverything();
75667    return this.monitorBuffer_();
75668  }
75669  /**
75670   * set a playlist on the segment loader
75671   *
75672   * @param {PlaylistLoader} media the playlist to set on the segment loader
75673   */
75674
75675  playlist(newPlaylist, options = {}) {
75676    if (!newPlaylist) {
75677      return;
75678    }
75679    if (this.playlist_ && this.playlist_.endList && newPlaylist.endList && this.playlist_.uri === newPlaylist.uri) {
75680      // skip update if both prev and new are vod and have the same URI
75681      return;
75682    }
75683    const oldPlaylist = this.playlist_;
75684    const segmentInfo = this.pendingSegment_;
75685    this.playlist_ = newPlaylist;
75686    this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist
75687    // is always our zero-time so force a sync update each time the playlist
75688    // is refreshed from the server
75689    //
75690    // Use the INIT state to determine if playback has started, as the playlist sync info
75691    // should be fixed once requests begin (as sync points are generated based on sync
75692    // info), but not before then.
75693
75694    if (this.state === 'INIT') {
75695      newPlaylist.syncInfo = {
75696        mediaSequence: newPlaylist.mediaSequence,
75697        time: 0
75698      }; // Setting the date time mapping means mapping the program date time (if available)
75699      // to time 0 on the player's timeline. The playlist's syncInfo serves a similar
75700      // purpose, mapping the initial mediaSequence to time zero. Since the syncInfo can
75701      // be updated as the playlist is refreshed before the loader starts loading, the
75702      // program date time mapping needs to be updated as well.
75703      //
75704      // This mapping is only done for the main loader because a program date time should
75705      // map equivalently between playlists.
75706
75707      if (this.loaderType_ === 'main') {
75708        this.syncController_.setDateTimeMappingForStart(newPlaylist);
75709      }
75710    }
75711    let oldId = null;
75712    if (oldPlaylist) {
75713      if (oldPlaylist.id) {
75714        oldId = oldPlaylist.id;
75715      } else if (oldPlaylist.uri) {
75716        oldId = oldPlaylist.uri;
75717      }
75718    }
75719    this.logger_(`playlist update [${oldId} => ${newPlaylist.id || newPlaylist.uri}]`);
75720    if (this.mediaSequenceSync_) {
75721      this.mediaSequenceSync_.update(newPlaylist, this.currentTime_());
75722      this.logger_(`Playlist update:
75723currentTime: ${this.currentTime_()}
75724bufferedEnd: ${lastBufferedEnd(this.buffered_())}
75725`, this.mediaSequenceSync_.diagnostics);
75726    } // in VOD, this is always a rendition switch (or we updated our syncInfo above)
75727    // in LIVE, we always want to update with new playlists (including refreshes)
75728
75729    this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start
75730    // buffering now
75731
75732    if (this.state === 'INIT' && this.couldBeginLoading_()) {
75733      return this.init_();
75734    }
75735    if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
75736      if (this.mediaIndex !== null) {
75737        // we must reset/resync the segment loader when we switch renditions and
75738        // the segment loader is already synced to the previous rendition
75739        // We only want to reset the loader here for LLHLS playback, as resetLoader sets fetchAtBuffer_
75740        // to false, resulting in fetching segments at currentTime and causing repeated
75741        // same-segment requests on playlist change. This erroneously drives up the playback watcher
75742        // stalled segment count, as re-requesting segments at the currentTime or browser cached segments
75743        // will not change the buffer.
75744        // Reference for LLHLS fixes: https://github.com/videojs/http-streaming/pull/1201
75745        const isLLHLS = !newPlaylist.endList && typeof newPlaylist.partTargetDuration === 'number';
75746        if (isLLHLS) {
75747          this.resetLoader();
75748        } else {
75749          this.resyncLoader();
75750        }
75751      }
75752      this.currentMediaInfo_ = void 0;
75753      this.trigger('playlistupdate'); // the rest of this function depends on `oldPlaylist` being defined
75754
75755      return;
75756    } // we reloaded the same playlist so we are in a live scenario
75757    // and we will likely need to adjust the mediaIndex
75758
75759    const mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
75760    this.logger_(`live window shift [${mediaSequenceDiff}]`); // update the mediaIndex on the SegmentLoader
75761    // this is important because we can abort a request and this value must be
75762    // equal to the last appended mediaIndex
75763
75764    if (this.mediaIndex !== null) {
75765      this.mediaIndex -= mediaSequenceDiff; // this can happen if we are going to load the first segment, but get a playlist
75766      // update during that. mediaIndex would go from 0 to -1 if mediaSequence in the
75767      // new playlist was incremented by 1.
75768
75769      if (this.mediaIndex < 0) {
75770        this.mediaIndex = null;
75771        this.partIndex = null;
75772      } else {
75773        const segment = this.playlist_.segments[this.mediaIndex]; // partIndex should remain the same for the same segment
75774        // unless parts fell off of the playlist for this segment.
75775        // In that case we need to reset partIndex and resync
75776
75777        if (this.partIndex && (!segment.parts || !segment.parts.length || !segment.parts[this.partIndex])) {
75778          const mediaIndex = this.mediaIndex;
75779          this.logger_(`currently processing part (index ${this.partIndex}) no longer exists.`);
75780          this.resetLoader(); // We want to throw away the partIndex and the data associated with it,
75781          // as the part was dropped from our current playlists segment.
75782          // The mediaIndex will still be valid so keep that around.
75783
75784          this.mediaIndex = mediaIndex;
75785        }
75786      }
75787    } // update the mediaIndex on the SegmentInfo object
75788    // this is important because we will update this.mediaIndex with this value
75789    // in `handleAppendsDone_` after the segment has been successfully appended
75790
75791    if (segmentInfo) {
75792      segmentInfo.mediaIndex -= mediaSequenceDiff;
75793      if (segmentInfo.mediaIndex < 0) {
75794        segmentInfo.mediaIndex = null;
75795        segmentInfo.partIndex = null;
75796      } else {
75797        // we need to update the referenced segment so that timing information is
75798        // saved for the new playlist's segment, however, if the segment fell off the
75799        // playlist, we can leave the old reference and just lose the timing info
75800        if (segmentInfo.mediaIndex >= 0) {
75801          segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
75802        }
75803        if (segmentInfo.partIndex >= 0 && segmentInfo.segment.parts) {
75804          segmentInfo.part = segmentInfo.segment.parts[segmentInfo.partIndex];
75805        }
75806      }
75807    }
75808    this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
75809  }
75810  /**
75811   * Prevent the loader from fetching additional segments. If there
75812   * is a segment request outstanding, it will finish processing
75813   * before the loader halts. A segment loader can be unpaused by
75814   * calling load().
75815   */
75816
75817  pause() {
75818    if (this.checkBufferTimeout_) {
75819      window__default["default"].clearTimeout(this.checkBufferTimeout_);
75820      this.checkBufferTimeout_ = null;
75821    }
75822  }
75823  /**
75824   * Returns whether the segment loader is fetching additional
75825   * segments when given the opportunity. This property can be
75826   * modified through calls to pause() and load().
75827   */
75828
75829  paused() {
75830    return this.checkBufferTimeout_ === null;
75831  }
75832  /**
75833   * Delete all the buffered data and reset the SegmentLoader
75834   *
75835   * @param {Function} [done] an optional callback to be executed when the remove
75836   * operation is complete
75837   */
75838
75839  resetEverything(done) {
75840    this.ended_ = false;
vendor: 4,087 bytes, lines 75841-75953
75841    this.activeInitSegmentId_ = null;
75842    this.appendInitSegment_ = {
75843      audio: true,
75844      video: true
75845    };
75846    this.resetLoader(); // remove from 0, the earliest point, to Infinity, to signify removal of everything.
75847    // VTT Segment Loader doesn't need to do anything but in the regular SegmentLoader,
75848    // we then clamp the value to duration if necessary.
75849
75850    this.remove(0, Infinity, done); // clears fmp4 captions
75851
75852    if (this.transmuxer_) {
75853      this.transmuxer_.postMessage({
75854        action: 'clearAllMp4Captions'
75855      }); // reset the cache in the transmuxer
75856
75857      this.transmuxer_.postMessage({
75858        action: 'reset'
75859      });
75860    }
75861  }
75862  /**
75863   * Force the SegmentLoader to resync and start loading around the currentTime instead
75864   * of starting at the end of the buffer
75865   *
75866   * Useful for fast quality changes
75867   */
75868
75869  resetLoader() {
75870    this.fetchAtBuffer_ = false;
75871    if (this.mediaSequenceSync_) {
75872      this.mediaSequenceSync_.resetAppendedStatus();
75873    }
75874    this.resyncLoader();
75875  }
75876  /**
75877   * Force the SegmentLoader to restart synchronization and make a conservative guess
75878   * before returning to the simple walk-forward method
75879   */
75880
75881  resyncLoader() {
75882    if (this.transmuxer_) {
75883      // need to clear out any cached data to prepare for the new segment
75884      segmentTransmuxer.reset(this.transmuxer_);
75885    }
75886    this.mediaIndex = null;
75887    this.partIndex = null;
75888    this.syncPoint_ = null;
75889    this.isPendingTimestampOffset_ = false; // this is mainly to sync timing-info when switching between renditions with and without timestamp-rollover,
75890    // so we don't want it for DASH or fragmented mp4 segments.
75891
75892    const isFmp4 = this.currentMediaInfo_ && this.currentMediaInfo_.isFmp4;
75893    const isHlsTs = this.sourceType_ === 'hls' && !isFmp4;
75894    if (isHlsTs) {
75895      this.shouldForceTimestampOffsetAfterResync_ = true;
75896    }
75897    this.callQueue_ = [];
75898    this.loadQueue_ = [];
75899    this.metadataQueue_.id3 = [];
75900    this.metadataQueue_.caption = [];
75901    this.abort();
75902    if (this.transmuxer_) {
75903      this.transmuxer_.postMessage({
75904        action: 'clearParsedMp4Captions'
75905      });
75906    }
75907  }
75908  /**
75909   * Remove any data in the source buffer between start and end times
75910   *
75911   * @param {number} start - the start time of the region to remove from the buffer
75912   * @param {number} end - the end time of the region to remove from the buffer
75913   * @param {Function} [done] - an optional callback to be executed when the remove
75914   * @param {boolean} force - force all remove operations to happen
75915   * operation is complete
75916   */
75917
75918  remove(start, end, done = () => {}, force = false) {
75919    // clamp end to duration if we need to remove everything.
75920    // This is due to a browser bug that causes issues if we remove to Infinity.
75921    // videojs/videojs-contrib-hls#1225
75922    if (end === Infinity) {
75923      end = this.duration_();
75924    } // skip removes that would throw an error
75925    // commonly happens during a rendition switch at the start of a video
75926    // from start 0 to end 0
75927
75928    if (end <= start) {
75929      this.logger_('skipping remove because end ${end} is <= start ${start}');
75930      return;
75931    }
75932    if (!this.sourceUpdater_ || !this.getMediaInfo_()) {
75933      this.logger_('skipping remove because no source updater or starting media info'); // nothing to remove if we haven't processed any media
75934
75935      return;
75936    } // set it to one to complete this function's removes
75937
75938    let removesRemaining = 1;
75939    const removeFinished = () => {
75940      removesRemaining--;
75941      if (removesRemaining === 0) {
75942        done();
75943      }
75944    };
75945    if (force || !this.audioDisabled_) {
75946      removesRemaining++;
75947      this.sourceUpdater_.removeAudio(start, end, removeFinished);
75948    } // While it would be better to only remove video if the main loader has video, this
75949    // should be safe with audio only as removeVideo will call back even if there's no
75950    // video buffer.
75951    //
75952    // In theory we can check to see if there's video before calling the remove, but in
75953    // the event that we're switching between renditions and from video to audio only
vendor: 6,271 bytes, lines 75954-76117
75954    // (when we add support for that), we may need to clear the video contents despite
75955    // what the new media will contain.
75956
75957    if (force || this.loaderType_ === 'main') {
75958      this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
75959      removesRemaining++;
75960      this.sourceUpdater_.removeVideo(start, end, removeFinished);
75961    } // remove any captions and ID3 tags
75962
75963    for (const track in this.inbandTextTracks_) {
75964      removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
75965    }
75966    removeCuesFromTrack(start, end, this.segmentMetadataTrack_); // finished this function's removes
75967
75968    removeFinished();
75969  }
75970  /**
75971   * (re-)schedule monitorBufferTick_ to run as soon as possible
75972   *
75973   * @private
75974   */
75975
75976  monitorBuffer_() {
75977    if (this.checkBufferTimeout_) {
75978      window__default["default"].clearTimeout(this.checkBufferTimeout_);
75979    }
75980    this.checkBufferTimeout_ = window__default["default"].setTimeout(this.monitorBufferTick_.bind(this), 1);
75981  }
75982  /**
75983   * As long as the SegmentLoader is in the READY state, periodically
75984   * invoke fillBuffer_().
75985   *
75986   * @private
75987   */
75988
75989  monitorBufferTick_() {
75990    if (this.state === 'READY') {
75991      this.fillBuffer_();
75992    }
75993    if (this.checkBufferTimeout_) {
75994      window__default["default"].clearTimeout(this.checkBufferTimeout_);
75995    }
75996    this.checkBufferTimeout_ = window__default["default"].setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
75997  }
75998  /**
75999   * fill the buffer with segements unless the sourceBuffers are
76000   * currently updating
76001   *
76002   * Note: this function should only ever be called by monitorBuffer_
76003   * and never directly
76004   *
76005   * @private
76006   */
76007
76008  fillBuffer_() {
76009    // TODO since the source buffer maintains a queue, and we shouldn't call this function
76010    // except when we're ready for the next segment, this check can most likely be removed
76011    if (this.sourceUpdater_.updating()) {
76012      return;
76013    } // see if we need to begin loading immediately
76014
76015    const segmentInfo = this.chooseNextRequest_();
76016    if (!segmentInfo) {
76017      return;
76018    }
76019    const metadata = {
76020      segmentInfo: segmentInfoPayload({
76021        type: this.loaderType_,
76022        segment: segmentInfo
76023      })
76024    };
76025    this.trigger({
76026      type: 'segmentselected',
76027      metadata
76028    });
76029    if (typeof segmentInfo.timestampOffset === 'number') {
76030      this.isPendingTimestampOffset_ = false;
76031      this.timelineChangeController_.pendingTimelineChange({
76032        type: this.loaderType_,
76033        from: this.currentTimeline_,
76034        to: segmentInfo.timeline
76035      });
76036    }
76037    this.loadSegment_(segmentInfo);
76038  }
76039  /**
76040   * Determines if we should call endOfStream on the media source based
76041   * on the state of the buffer or if appened segment was the final
76042   * segment in the playlist.
76043   *
76044   * @param {number} [mediaIndex] the media index of segment we last appended
76045   * @param {Object} [playlist] a media playlist object
76046   * @return {boolean} do we need to call endOfStream on the MediaSource
76047   */
76048
76049  isEndOfStream_(mediaIndex = this.mediaIndex, playlist = this.playlist_, partIndex = this.partIndex) {
76050    if (!playlist || !this.mediaSource_) {
76051      return false;
76052    }
76053    const segment = typeof mediaIndex === 'number' && playlist.segments[mediaIndex]; // mediaIndex is zero based but length is 1 based
76054
76055    const appendedLastSegment = mediaIndex + 1 === playlist.segments.length; // true if there are no parts, or this is the last part.
76056
76057    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
76058    // so that MediaSources can trigger the `ended` event when it runs out of
76059    // buffered data instead of waiting for me
76060
76061    return playlist.endList && this.mediaSource_.readyState === 'open' && appendedLastSegment && appendedLastPart;
76062  }
76063  /**
76064   * Determines what request should be made given current segment loader state.
76065   *
76066   * @return {Object} a request object that describes the segment/part to load
76067   */
76068
76069  chooseNextRequest_() {
76070    const buffered = this.buffered_();
76071    const bufferedEnd = lastBufferedEnd(buffered) || 0;
76072    const bufferedTime = timeAheadOf(buffered, this.currentTime_());
76073    const preloaded = !this.hasPlayed_() && bufferedTime >= 1;
76074    const haveEnoughBuffer = bufferedTime >= this.goalBufferLength_();
76075    const segments = this.playlist_.segments; // return no segment if:
76076    // 1. we don't have segments
76077    // 2. The video has not yet played and we already downloaded a segment
76078    // 3. we already have enough buffered time
76079
76080    if (!segments.length || preloaded || haveEnoughBuffer) {
76081      return null;
76082    }
76083    this.syncPoint_ = this.syncPoint_ || this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_(), this.loaderType_);
76084    const next = {
76085      partIndex: null,
76086      mediaIndex: null,
76087      startOfSegment: null,
76088      playlist: this.playlist_,
76089      isSyncRequest: Boolean(!this.syncPoint_)
76090    };
76091    if (next.isSyncRequest) {
76092      next.mediaIndex = getSyncSegmentCandidate(this.currentTimeline_, segments, bufferedEnd);
76093      this.logger_(`choose next request. Can not find sync point. Fallback to media Index: ${next.mediaIndex}`);
76094    } else if (this.mediaIndex !== null) {
76095      const segment = segments[this.mediaIndex];
76096      const partIndex = typeof this.partIndex === 'number' ? this.partIndex : -1;
76097      next.startOfSegment = segment.end ? segment.end : bufferedEnd;
76098      if (segment.parts && segment.parts[partIndex + 1]) {
76099        next.mediaIndex = this.mediaIndex;
76100        next.partIndex = partIndex + 1;
76101      } else {
76102        next.mediaIndex = this.mediaIndex + 1;
76103      }
76104    } else {
76105      let segmentIndex;
76106      let partIndex;
76107      let startTime;
76108      const targetTime = this.fetchAtBuffer_ ? bufferedEnd : this.currentTime_();
76109      if (this.mediaSequenceSync_) {
76110        this.logger_(`chooseNextRequest_ request after Quality Switch:
76111For TargetTime: ${targetTime}.
76112CurrentTime: ${this.currentTime_()}
76113BufferedEnd: ${bufferedEnd}
76114Fetch At Buffer: ${this.fetchAtBuffer_}
76115`, this.mediaSequenceSync_.diagnostics);
76116      }
76117      if (this.mediaSequenceSync_ && this.mediaSequenceSync_.isReliable) {
vendor: 30,475 bytes, lines 76118-76819
76118        const syncInfo = this.getSyncInfoFromMediaSequenceSync_(targetTime);
76119        if (!syncInfo) {
76120          const message = 'No sync info found while using media sequence sync';
76121          this.error({
76122            message,
76123            metadata: {
76124              errorType: videojs.Error.StreamingFailedToSelectNextSegment,
76125              error: new Error(message)
76126            }
76127          });
76128          this.logger_('chooseNextRequest_ - no sync info found using media sequence sync'); // no match
76129
76130          return null;
76131        }
76132        this.logger_(`chooseNextRequest_ mediaSequence syncInfo (${syncInfo.start} --> ${syncInfo.end})`);
76133        segmentIndex = syncInfo.segmentIndex;
76134        partIndex = syncInfo.partIndex;
76135        startTime = syncInfo.start;
76136      } else {
76137        this.logger_('chooseNextRequest_ - fallback to a regular segment selection algorithm, based on a syncPoint.'); // fallback
76138
76139        const mediaInfoForTime = Playlist.getMediaInfoForTime({
76140          exactManifestTimings: this.exactManifestTimings,
76141          playlist: this.playlist_,
76142          currentTime: targetTime,
76143          startingPartIndex: this.syncPoint_.partIndex,
76144          startingSegmentIndex: this.syncPoint_.segmentIndex,
76145          startTime: this.syncPoint_.time
76146        });
76147        segmentIndex = mediaInfoForTime.segmentIndex;
76148        partIndex = mediaInfoForTime.partIndex;
76149        startTime = mediaInfoForTime.startTime;
76150      }
76151      next.getMediaInfoForTime = this.fetchAtBuffer_ ? `bufferedEnd ${targetTime}` : `currentTime ${targetTime}`;
76152      next.mediaIndex = segmentIndex;
76153      next.startOfSegment = startTime;
76154      next.partIndex = partIndex;
76155      this.logger_(`choose next request. Playlist switched and we have a sync point. Media Index: ${next.mediaIndex} `);
76156    }
76157    const nextSegment = segments[next.mediaIndex];
76158    let nextPart = nextSegment && typeof next.partIndex === 'number' && nextSegment.parts && nextSegment.parts[next.partIndex]; // if the next segment index is invalid or
76159    // the next partIndex is invalid do not choose a next segment.
76160
76161    if (!nextSegment || typeof next.partIndex === 'number' && !nextPart) {
76162      return null;
76163    } // if the next segment has parts, and we don't have a partIndex.
76164    // Set partIndex to 0
76165
76166    if (typeof next.partIndex !== 'number' && nextSegment.parts) {
76167      next.partIndex = 0;
76168      nextPart = nextSegment.parts[0];
76169    } // independentSegments applies to every segment in a playlist. If independentSegments appears in a main playlist,
76170    // it applies to each segment in each media playlist.
76171    // https://datatracker.ietf.org/doc/html/draft-pantos-http-live-streaming-23#section-4.3.5.1
76172
76173    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
76174    // that the next part we append is "independent" if possible.
76175    // So we check if the previous part is independent, and request
76176    // it if it is.
76177
76178    if (!bufferedTime && nextPart && !hasIndependentSegments && !nextPart.independent) {
76179      if (next.partIndex === 0) {
76180        const lastSegment = segments[next.mediaIndex - 1];
76181        const lastSegmentLastPart = lastSegment.parts && lastSegment.parts.length && lastSegment.parts[lastSegment.parts.length - 1];
76182        if (lastSegmentLastPart && lastSegmentLastPart.independent) {
76183          next.mediaIndex -= 1;
76184          next.partIndex = lastSegment.parts.length - 1;
76185          next.independent = 'previous segment';
76186        }
76187      } else if (nextSegment.parts[next.partIndex - 1].independent) {
76188        next.partIndex -= 1;
76189        next.independent = 'previous part';
76190      }
76191    }
76192    const ended = this.mediaSource_ && this.mediaSource_.readyState === 'ended'; // do not choose a next segment if all of the following:
76193    // 1. this is the last segment in the playlist
76194    // 2. end of stream has been called on the media source already
76195    // 3. the player is not seeking
76196
76197    if (next.mediaIndex >= segments.length - 1 && ended && !this.seeking_()) {
76198      return null;
76199    }
76200    if (this.shouldForceTimestampOffsetAfterResync_) {
76201      this.shouldForceTimestampOffsetAfterResync_ = false;
76202      next.forceTimestampOffset = true;
76203      this.logger_('choose next request. Force timestamp offset after loader resync');
76204    }
76205    return this.generateSegmentInfo_(next);
76206  }
76207  getSyncInfoFromMediaSequenceSync_(targetTime) {
76208    if (!this.mediaSequenceSync_) {
76209      return null;
76210    } // we should pull the target time to the least available time if we drop out of sync for any reason
76211
76212    const finalTargetTime = Math.max(targetTime, this.mediaSequenceSync_.start);
76213    if (targetTime !== finalTargetTime) {
76214      this.logger_(`getSyncInfoFromMediaSequenceSync_. Pulled target time from ${targetTime} to ${finalTargetTime}`);
76215    }
76216    const mediaSequenceSyncInfo = this.mediaSequenceSync_.getSyncInfoForTime(finalTargetTime);
76217    if (!mediaSequenceSyncInfo) {
76218      // no match at all
76219      return null;
76220    }
76221    if (!mediaSequenceSyncInfo.isAppended) {
76222      // has a perfect match
76223      return mediaSequenceSyncInfo;
76224    } // has match, but segment was already appended.
76225    // attempt to auto-advance to the nearest next segment:
76226
76227    const nextMediaSequenceSyncInfo = this.mediaSequenceSync_.getSyncInfoForTime(mediaSequenceSyncInfo.end);
76228    if (!nextMediaSequenceSyncInfo) {
76229      // no match at all
76230      return null;
76231    }
76232    if (nextMediaSequenceSyncInfo.isAppended) {
76233      this.logger_('getSyncInfoFromMediaSequenceSync_: We encounter unexpected scenario where next media sequence sync info is also appended!');
76234    } // got match with the nearest next segment
76235
76236    return nextMediaSequenceSyncInfo;
76237  }
76238  generateSegmentInfo_(options) {
76239    const {
76240      independent,
76241      playlist,
76242      mediaIndex,
76243      startOfSegment,
76244      isSyncRequest,
76245      partIndex,
76246      forceTimestampOffset,
76247      getMediaInfoForTime
76248    } = options;
76249    const segment = playlist.segments[mediaIndex];
76250    const part = typeof partIndex === 'number' && segment.parts[partIndex];
76251    const segmentInfo = {
76252      requestId: 'segment-loader-' + Math.random(),
76253      // resolve the segment URL relative to the playlist
76254      uri: part && part.resolvedUri || segment.resolvedUri,
76255      // the segment's mediaIndex at the time it was requested
76256      mediaIndex,
76257      partIndex: part ? partIndex : null,
76258      // whether or not to update the SegmentLoader's state with this
76259      // segment's mediaIndex
76260      isSyncRequest,
76261      startOfSegment,
76262      // the segment's playlist
76263      playlist,
76264      // unencrypted bytes of the segment
76265      bytes: null,
76266      // when a key is defined for this segment, the encrypted bytes
76267      encryptedBytes: null,
76268      // The target timestampOffset for this segment when we append it
76269      // to the source buffer
76270      timestampOffset: null,
76271      // The timeline that the segment is in
76272      timeline: segment.timeline,
76273      // The expected duration of the segment in seconds
76274      duration: part && part.duration || segment.duration,
76275      // retain the segment in case the playlist updates while doing an async process
76276      segment,
76277      part,
76278      byteLength: 0,
76279      transmuxer: this.transmuxer_,
76280      // type of getMediaInfoForTime that was used to get this segment
76281      getMediaInfoForTime,
76282      independent
76283    };
76284    const overrideCheck = typeof forceTimestampOffset !== 'undefined' ? forceTimestampOffset : this.isPendingTimestampOffset_;
76285    segmentInfo.timestampOffset = this.timestampOffsetForSegment_({
76286      segmentTimeline: segment.timeline,
76287      currentTimeline: this.currentTimeline_,
76288      startOfSegment,
76289      buffered: this.buffered_(),
76290      overrideCheck
76291    });
76292    const audioBufferedEnd = lastBufferedEnd(this.sourceUpdater_.audioBuffered());
76293    if (typeof audioBufferedEnd === 'number') {
76294      // since the transmuxer is using the actual timing values, but the buffer is
76295      // adjusted by the timestamp offset, we must adjust the value here
76296      segmentInfo.audioAppendStart = audioBufferedEnd - this.sourceUpdater_.audioTimestampOffset();
76297    }
76298    if (this.sourceUpdater_.videoBuffered().length) {
76299      segmentInfo.gopsToAlignWith = gopsSafeToAlignWith(this.gopBuffer_,
76300      // since the transmuxer is using the actual timing values, but the time is
76301      // adjusted by the timestmap offset, we must adjust the value here
76302      this.currentTime_() - this.sourceUpdater_.videoTimestampOffset(), this.timeMapping_);
76303    }
76304    return segmentInfo;
76305  } // get the timestampoffset for a segment,
76306  // added so that vtt segment loader can override and prevent
76307  // adding timestamp offsets.
76308
76309  timestampOffsetForSegment_(options) {
76310    return timestampOffsetForSegment(options);
76311  }
76312  /**
76313   * Determines if the network has enough bandwidth to complete the current segment
76314   * request in a timely manner. If not, the request will be aborted early and bandwidth
76315   * updated to trigger a playlist switch.
76316   *
76317   * @param {Object} stats
76318   *        Object containing stats about the request timing and size
76319   * @private
76320   */
76321
76322  earlyAbortWhenNeeded_(stats) {
76323    if (this.vhs_.tech_.paused() ||
76324    // Don't abort if the current playlist is on the lowestEnabledRendition
76325    // TODO: Replace using timeout with a boolean indicating whether this playlist is
76326    //       the lowestEnabledRendition.
76327    !this.xhrOptions_.timeout ||
76328    // Don't abort if we have no bandwidth information to estimate segment sizes
76329    !this.playlist_.attributes.BANDWIDTH) {
76330      return;
76331    } // Wait at least 1 second since the first byte of data has been received before
76332    // using the calculated bandwidth from the progress event to allow the bitrate
76333    // to stabilize
76334
76335    if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
76336      return;
76337    }
76338    const currentTime = this.currentTime_();
76339    const measuredBandwidth = stats.bandwidth;
76340    const segmentDuration = this.pendingSegment_.duration;
76341    const requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
76342    // if we are only left with less than 1 second when the request completes.
76343    // A negative timeUntilRebuffering indicates we are already rebuffering
76344
76345    const timeUntilRebuffer$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.vhs_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download
76346    // is larger than the estimated time until the player runs out of forward buffer
76347
76348    if (requestTimeRemaining <= timeUntilRebuffer$1) {
76349      return;
76350    }
76351    const switchCandidate = minRebufferMaxBandwidthSelector({
76352      main: this.vhs_.playlists.main,
76353      currentTime,
76354      bandwidth: measuredBandwidth,
76355      duration: this.duration_(),
76356      segmentDuration,
76357      timeUntilRebuffer: timeUntilRebuffer$1,
76358      currentTimeline: this.currentTimeline_,
76359      syncController: this.syncController_
76360    });
76361    if (!switchCandidate) {
76362      return;
76363    }
76364    const rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$1;
76365    const timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
76366    let minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the
76367    // potential round trip time of the new request so that we are not too aggressive
76368    // with switching to a playlist that might save us a fraction of a second.
76369
76370    if (timeUntilRebuffer$1 <= TIME_FUDGE_FACTOR) {
76371      minimumTimeSaving = 1;
76372    }
76373    if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
76374      return;
76375    } // set the bandwidth to that of the desired playlist being sure to scale by
76376    // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
76377    // don't trigger a bandwidthupdate as the bandwidth is artifial
76378
76379    this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
76380    this.trigger('earlyabort');
76381  }
76382  handleAbort_(segmentInfo) {
76383    this.logger_(`Aborting ${segmentInfoString(segmentInfo)}`);
76384    this.mediaRequestsAborted += 1;
76385  }
76386  /**
76387   * XHR `progress` event handler
76388   *
76389   * @param {Event}
76390   *        The XHR `progress` event
76391   * @param {Object} simpleSegment
76392   *        A simplified segment object copy
76393   * @private
76394   */
76395
76396  handleProgress_(event, simpleSegment) {
76397    this.earlyAbortWhenNeeded_(simpleSegment.stats);
76398    if (this.checkForAbort_(simpleSegment.requestId)) {
76399      return;
76400    }
76401    this.trigger('progress');
76402  }
76403  handleTrackInfo_(simpleSegment, trackInfo) {
76404    const {
76405      hasAudio,
76406      hasVideo
76407    } = trackInfo;
76408    const metadata = {
76409      segmentInfo: segmentInfoPayload({
76410        type: this.loaderType_,
76411        segment: simpleSegment
76412      }),
76413      trackInfo: {
76414        hasAudio,
76415        hasVideo
76416      }
76417    };
76418    this.trigger({
76419      type: 'segmenttransmuxingtrackinfoavailable',
76420      metadata
76421    });
76422    this.earlyAbortWhenNeeded_(simpleSegment.stats);
76423    if (this.checkForAbort_(simpleSegment.requestId)) {
76424      return;
76425    }
76426    if (this.checkForIllegalMediaSwitch(trackInfo)) {
76427      return;
76428    }
76429    trackInfo = trackInfo || {}; // When we have track info, determine what media types this loader is dealing with.
76430    // Guard against cases where we're not getting track info at all until we are
76431    // certain that all streams will provide it.
76432
76433    if (!shallowEqual(this.currentMediaInfo_, trackInfo)) {
76434      this.appendInitSegment_ = {
76435        audio: true,
76436        video: true
76437      };
76438      this.startingMediaInfo_ = trackInfo;
76439      this.currentMediaInfo_ = trackInfo;
76440      this.logger_('trackinfo update', trackInfo);
76441      this.trigger('trackinfo');
76442    } // trackinfo may cause an abort if the trackinfo
76443    // causes a codec change to an unsupported codec.
76444
76445    if (this.checkForAbort_(simpleSegment.requestId)) {
76446      return;
76447    } // set trackinfo on the pending segment so that
76448    // it can append.
76449
76450    this.pendingSegment_.trackInfo = trackInfo; // check if any calls were waiting on the track info
76451
76452    if (this.hasEnoughInfoToAppend_()) {
76453      this.processCallQueue_();
76454    } else {
76455      checkAndFixTimelines(this);
76456    }
76457  }
76458  handleTimingInfo_(simpleSegment, mediaType, timeType, time) {
76459    this.earlyAbortWhenNeeded_(simpleSegment.stats);
76460    if (this.checkForAbort_(simpleSegment.requestId)) {
76461      return;
76462    }
76463    const segmentInfo = this.pendingSegment_;
76464    const timingInfoProperty = timingInfoPropertyForMedia(mediaType);
76465    segmentInfo[timingInfoProperty] = segmentInfo[timingInfoProperty] || {};
76466    segmentInfo[timingInfoProperty][timeType] = time;
76467    this.logger_(`timinginfo: ${mediaType} - ${timeType} - ${time}`); // check if any calls were waiting on the timing info
76468
76469    if (this.hasEnoughInfoToAppend_()) {
76470      this.processCallQueue_();
76471    } else {
76472      checkAndFixTimelines(this);
76473    }
76474  }
76475  handleCaptions_(simpleSegment, captionData) {
76476    this.earlyAbortWhenNeeded_(simpleSegment.stats);
76477    if (this.checkForAbort_(simpleSegment.requestId)) {
76478      return;
76479    } // This could only happen with fmp4 segments, but
76480    // should still not happen in general
76481
76482    if (captionData.length === 0) {
76483      this.logger_('SegmentLoader received no captions from a caption event');
76484      return;
76485    }
76486    const segmentInfo = this.pendingSegment_; // Wait until we have some video data so that caption timing
76487    // can be adjusted by the timestamp offset
76488
76489    if (!segmentInfo.hasAppendedData_) {
76490      this.metadataQueue_.caption.push(this.handleCaptions_.bind(this, simpleSegment, captionData));
76491      return;
76492    }
76493    const timestampOffset = this.sourceUpdater_.videoTimestampOffset() === null ? this.sourceUpdater_.audioTimestampOffset() : this.sourceUpdater_.videoTimestampOffset();
76494    const captionTracks = {}; // get total start/end and captions for each track/stream
76495
76496    captionData.forEach(caption => {
76497      // caption.stream is actually a track name...
76498      // set to the existing values in tracks or default values
76499      captionTracks[caption.stream] = captionTracks[caption.stream] || {
76500        // Infinity, as any other value will be less than this
76501        startTime: Infinity,
76502        captions: [],
76503        // 0 as an other value will be more than this
76504        endTime: 0
76505      };
76506      const captionTrack = captionTracks[caption.stream];
76507      captionTrack.startTime = Math.min(captionTrack.startTime, caption.startTime + timestampOffset);
76508      captionTrack.endTime = Math.max(captionTrack.endTime, caption.endTime + timestampOffset);
76509      captionTrack.captions.push(caption);
76510    });
76511    Object.keys(captionTracks).forEach(trackName => {
76512      const {
76513        startTime,
76514        endTime,
76515        captions
76516      } = captionTracks[trackName];
76517      const inbandTextTracks = this.inbandTextTracks_;
76518      this.logger_(`adding cues from ${startTime} -> ${endTime} for ${trackName}`);
76519      createCaptionsTrackIfNotExists(inbandTextTracks, this.vhs_.tech_, trackName); // clear out any cues that start and end at the same time period for the same track.
76520      // We do this because a rendition change that also changes the timescale for captions
76521      // will result in captions being re-parsed for certain segments. If we add them again
76522      // without clearing we will have two of the same captions visible.
76523
76524      removeCuesFromTrack(startTime, endTime, inbandTextTracks[trackName]);
76525      addCaptionData({
76526        captionArray: captions,
76527        inbandTextTracks,
76528        timestampOffset
76529      });
76530    }); // Reset stored captions since we added parsed
76531    // captions to a text track at this point
76532
76533    if (this.transmuxer_) {
76534      this.transmuxer_.postMessage({
76535        action: 'clearParsedMp4Captions'
76536      });
76537    }
76538  }
76539  handleId3_(simpleSegment, id3Frames, dispatchType) {
76540    this.earlyAbortWhenNeeded_(simpleSegment.stats);
76541    if (this.checkForAbort_(simpleSegment.requestId)) {
76542      return;
76543    }
76544    const segmentInfo = this.pendingSegment_; // we need to have appended data in order for the timestamp offset to be set
76545
76546    if (!segmentInfo.hasAppendedData_) {
76547      this.metadataQueue_.id3.push(this.handleId3_.bind(this, simpleSegment, id3Frames, dispatchType));
76548      return;
76549    }
76550    this.addMetadataToTextTrack(dispatchType, id3Frames, this.duration_());
76551  }
76552  processMetadataQueue_() {
76553    this.metadataQueue_.id3.forEach(fn => fn());
76554    this.metadataQueue_.caption.forEach(fn => fn());
76555    this.metadataQueue_.id3 = [];
76556    this.metadataQueue_.caption = [];
76557  }
76558  processCallQueue_() {
76559    const callQueue = this.callQueue_; // Clear out the queue before the queued functions are run, since some of the
76560    // functions may check the length of the load queue and default to pushing themselves
76561    // back onto the queue.
76562
76563    this.callQueue_ = [];
76564    callQueue.forEach(fun => fun());
76565  }
76566  processLoadQueue_() {
76567    const loadQueue = this.loadQueue_; // Clear out the queue before the queued functions are run, since some of the
76568    // functions may check the length of the load queue and default to pushing themselves
76569    // back onto the queue.
76570
76571    this.loadQueue_ = [];
76572    loadQueue.forEach(fun => fun());
76573  }
76574  /**
76575   * Determines whether the loader has enough info to load the next segment.
76576   *
76577   * @return {boolean}
76578   *         Whether or not the loader has enough info to load the next segment
76579   */
76580
76581  hasEnoughInfoToLoad_() {
76582    // Since primary timing goes by video, only the audio loader potentially needs to wait
76583    // to load.
76584    if (this.loaderType_ !== 'audio') {
76585      return true;
76586    }
76587    const segmentInfo = this.pendingSegment_; // A fill buffer must have already run to establish a pending segment before there's
76588    // enough info to load.
76589
76590    if (!segmentInfo) {
76591      return false;
76592    } // The first segment can and should be loaded immediately so that source buffers are
76593    // created together (before appending). Source buffer creation uses the presence of
76594    // audio and video data to determine whether to create audio/video source buffers, and
76595    // uses processed (transmuxed or parsed) media to determine the types required.
76596
76597    if (!this.getCurrentMediaInfo_()) {
76598      return true;
76599    }
76600    if (
76601    // Technically, instead of waiting to load a segment on timeline changes, a segment
76602    // can be requested and downloaded and only wait before it is transmuxed or parsed.
76603    // But in practice, there are a few reasons why it is better to wait until a loader
76604    // is ready to append that segment before requesting and downloading:
76605    //
76606    // 1. Because audio and main loaders cross discontinuities together, if this loader
76607    //    is waiting for the other to catch up, then instead of requesting another
76608    //    segment and using up more bandwidth, by not yet loading, more bandwidth is
76609    //    allotted to the loader currently behind.
76610    // 2. media-segment-request doesn't have to have logic to consider whether a segment
76611    // is ready to be processed or not, isolating the queueing behavior to the loader.
76612    // 3. The audio loader bases some of its segment properties on timing information
76613    //    provided by the main loader, meaning that, if the logic for waiting on
76614    //    processing was in media-segment-request, then it would also need to know how
76615    //    to re-generate the segment information after the main loader caught up.
76616    shouldWaitForTimelineChange({
76617      timelineChangeController: this.timelineChangeController_,
76618      currentTimeline: this.currentTimeline_,
76619      segmentTimeline: segmentInfo.timeline,
76620      loaderType: this.loaderType_,
76621      audioDisabled: this.audioDisabled_
76622    })) {
76623      return false;
76624    }
76625    return true;
76626  }
76627  getCurrentMediaInfo_(segmentInfo = this.pendingSegment_) {
76628    return segmentInfo && segmentInfo.trackInfo || this.currentMediaInfo_;
76629  }
76630  getMediaInfo_(segmentInfo = this.pendingSegment_) {
76631    return this.getCurrentMediaInfo_(segmentInfo) || this.startingMediaInfo_;
76632  }
76633  getPendingSegmentPlaylist() {
76634    return this.pendingSegment_ ? this.pendingSegment_.playlist : null;
76635  }
76636  hasEnoughInfoToAppend_() {
76637    if (!this.sourceUpdater_.ready()) {
76638      return false;
76639    } // If content needs to be removed or the loader is waiting on an append reattempt,
76640    // then no additional content should be appended until the prior append is resolved.
76641
76642    if (this.waitingOnRemove_ || this.quotaExceededErrorRetryTimeout_) {
76643      return false;
76644    }
76645    const segmentInfo = this.pendingSegment_;
76646    const trackInfo = this.getCurrentMediaInfo_(); // no segment to append any data for or
76647    // we do not have information on this specific
76648    // segment yet
76649
76650    if (!segmentInfo || !trackInfo) {
76651      return false;
76652    }
76653    const {
76654      hasAudio,
76655      hasVideo,
76656      isMuxed
76657    } = trackInfo;
76658    if (hasVideo && !segmentInfo.videoTimingInfo) {
76659      return false;
76660    } // muxed content only relies on video timing information for now.
76661
76662    if (hasAudio && !this.audioDisabled_ && !isMuxed && !segmentInfo.audioTimingInfo) {
76663      return false;
76664    } // we need to allow an append here even if we're moving to different timelines.
76665
76666    if (shouldWaitForTimelineChange({
76667      timelineChangeController: this.timelineChangeController_,
76668      currentTimeline: this.currentTimeline_,
76669      segmentTimeline: segmentInfo.timeline,
76670      loaderType: this.loaderType_,
76671      audioDisabled: this.audioDisabled_
76672    })) {
76673      return false;
76674    }
76675    return true;
76676  }
76677  handleData_(simpleSegment, result) {
76678    this.earlyAbortWhenNeeded_(simpleSegment.stats);
76679    if (this.checkForAbort_(simpleSegment.requestId)) {
76680      return;
76681    } // If there's anything in the call queue, then this data came later and should be
76682    // executed after the calls currently queued.
76683
76684    if (this.callQueue_.length || !this.hasEnoughInfoToAppend_()) {
76685      checkAndFixTimelines(this);
76686      this.callQueue_.push(this.handleData_.bind(this, simpleSegment, result));
76687      return;
76688    }
76689    const segmentInfo = this.pendingSegment_; // update the time mapping so we can translate from display time to media time
76690
76691    this.setTimeMapping_(segmentInfo.timeline); // for tracking overall stats
76692
76693    this.updateMediaSecondsLoaded_(segmentInfo.part || segmentInfo.segment); // Note that the state isn't changed from loading to appending. This is because abort
76694    // logic may change behavior depending on the state, and changing state too early may
76695    // inflate our estimates of bandwidth. In the future this should be re-examined to
76696    // note more granular states.
76697    // don't process and append data if the mediaSource is closed
76698
76699    if (this.mediaSource_.readyState === 'closed') {
76700      return;
76701    } // if this request included an initialization segment, save that data
76702    // to the initSegment cache
76703
76704    if (simpleSegment.map) {
76705      simpleSegment.map = this.initSegmentForMap(simpleSegment.map, true); // move over init segment properties to media request
76706
76707      segmentInfo.segment.map = simpleSegment.map;
76708    } // if this request included a segment key, save that data in the cache
76709
76710    if (simpleSegment.key) {
76711      this.segmentKey(simpleSegment.key, true);
76712    }
76713    segmentInfo.isFmp4 = simpleSegment.isFmp4;
76714    segmentInfo.timingInfo = segmentInfo.timingInfo || {};
76715    if (segmentInfo.isFmp4) {
76716      this.trigger('fmp4');
76717      segmentInfo.timingInfo.start = segmentInfo[timingInfoPropertyForMedia(result.type)].start;
76718    } else {
76719      const trackInfo = this.getCurrentMediaInfo_();
76720      const useVideoTimingInfo = this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo;
76721      let firstVideoFrameTimeForData;
76722      if (useVideoTimingInfo) {
76723        firstVideoFrameTimeForData = segmentInfo.videoTimingInfo.start;
76724      } // Segment loader knows more about segment timing than the transmuxer (in certain
76725      // aspects), so make any changes required for a more accurate start time.
76726      // Don't set the end time yet, as the segment may not be finished processing.
76727
76728      segmentInfo.timingInfo.start = this.trueSegmentStart_({
76729        currentStart: segmentInfo.timingInfo.start,
76730        playlist: segmentInfo.playlist,
76731        mediaIndex: segmentInfo.mediaIndex,
76732        currentVideoTimestampOffset: this.sourceUpdater_.videoTimestampOffset(),
76733        useVideoTimingInfo,
76734        firstVideoFrameTimeForData,
76735        videoTimingInfo: segmentInfo.videoTimingInfo,
76736        audioTimingInfo: segmentInfo.audioTimingInfo
76737      });
76738    } // Init segments for audio and video only need to be appended in certain cases. Now
76739    // that data is about to be appended, we can check the final cases to determine
76740    // whether we should append an init segment.
76741
76742    this.updateAppendInitSegmentStatus(segmentInfo, result.type); // Timestamp offset should be updated once we get new data and have its timing info,
76743    // as we use the start of the segment to offset the best guess (playlist provided)
76744    // timestamp offset.
76745
76746    this.updateSourceBufferTimestampOffset_(segmentInfo); // if this is a sync request we need to determine whether it should
76747    // be appended or not.
76748
76749    if (segmentInfo.isSyncRequest) {
76750      // first save/update our timing info for this segment.
76751      // this is what allows us to choose an accurate segment
76752      // and the main reason we make a sync request.
76753      this.updateTimingInfoEnd_(segmentInfo);
76754      this.syncController_.saveSegmentTimingInfo({
76755        segmentInfo,
76756        shouldSaveTimelineMapping: this.loaderType_ === 'main'
76757      });
76758      const next = this.chooseNextRequest_(); // If the sync request isn't the segment that would be requested next
76759      // after taking into account its timing info, do not append it.
76760
76761      if (next.mediaIndex !== segmentInfo.mediaIndex || next.partIndex !== segmentInfo.partIndex) {
76762        this.logger_('sync segment was incorrect, not appending');
76763        return;
76764      } // otherwise append it like any other segment as our guess was correct.
76765
76766      this.logger_('sync segment was correct, appending');
76767    } // Save some state so that in the future anything waiting on first append (and/or
76768    // timestamp offset(s)) can process immediately. While the extra state isn't optimal,
76769    // we need some notion of whether the timestamp offset or other relevant information
76770    // has had a chance to be set.
76771
76772    segmentInfo.hasAppendedData_ = true; // Now that the timestamp offset should be set, we can append any waiting ID3 tags.
76773
76774    this.processMetadataQueue_();
76775    this.appendData_(segmentInfo, result);
76776  }
76777  updateAppendInitSegmentStatus(segmentInfo, type) {
76778    // alt audio doesn't manage timestamp offset
76779    if (this.loaderType_ === 'main' && typeof segmentInfo.timestampOffset === 'number' &&
76780    // in the case that we're handling partial data, we don't want to append an init
76781    // segment for each chunk
76782    !segmentInfo.changedTimestampOffset) {
76783      // if the timestamp offset changed, the timeline may have changed, so we have to re-
76784      // append init segments
76785      this.appendInitSegment_ = {
76786        audio: true,
76787        video: true
76788      };
76789    }
76790    if (this.playlistOfLastInitSegment_[type] !== segmentInfo.playlist) {
76791      // make sure we append init segment on playlist changes, in case the media config
76792      // changed
76793      this.appendInitSegment_[type] = true;
76794    }
76795  }
76796  getInitSegmentAndUpdateState_({
76797    type,
76798    initSegment,
76799    map,
76800    playlist
76801  }) {
76802    // "The EXT-X-MAP tag specifies how to obtain the Media Initialization Section
76803    // (Section 3) required to parse the applicable Media Segments.  It applies to every
76804    // Media Segment that appears after it in the Playlist until the next EXT-X-MAP tag
76805    // or until the end of the playlist."
76806    // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.5
76807    if (map) {
76808      const id = initSegmentId(map);
76809      if (this.activeInitSegmentId_ === id) {
76810        // don't need to re-append the init segment if the ID matches
76811        return null;
76812      } // a map-specified init segment takes priority over any transmuxed (or otherwise
76813      // obtained) init segment
76814      //
76815      // this also caches the init segment for later use
76816
76817      initSegment = this.initSegmentForMap(map, true).bytes;
76818      this.activeInitSegmentId_ = id;
76819    }
vendor: 10,377 bytes, lines 76819-77065
76819 // We used to always prepend init segments for video, however, that shouldn't be
76820    // necessary. Instead, we should only append on changes, similar to what we've always
76821    // done for audio. This is more important (though may not be that important) for
76822    // frame-by-frame appending for LHLS, simply because of the increased quantity of
76823    // appends.
76824
76825    if (initSegment && this.appendInitSegment_[type]) {
76826      // Make sure we track the playlist that we last used for the init segment, so that
76827      // we can re-append the init segment in the event that we get data from a new
76828      // playlist. Discontinuities and track changes are handled in other sections.
76829      this.playlistOfLastInitSegment_[type] = playlist; // Disable future init segment appends for this type. Until a change is necessary.
76830
76831      this.appendInitSegment_[type] = false; // we need to clear out the fmp4 active init segment id, since
76832      // we are appending the muxer init segment
76833
76834      this.activeInitSegmentId_ = null;
76835      return initSegment;
76836    }
76837    return null;
76838  }
76839  handleQuotaExceededError_({
76840    segmentInfo,
76841    type,
76842    bytes
76843  }, error) {
76844    const audioBuffered = this.sourceUpdater_.audioBuffered();
76845    const videoBuffered = this.sourceUpdater_.videoBuffered(); // For now we're ignoring any notion of gaps in the buffer, but they, in theory,
76846    // should be cleared out during the buffer removals. However, log in case it helps
76847    // debug.
76848
76849    if (audioBuffered.length > 1) {
76850      this.logger_('On QUOTA_EXCEEDED_ERR, found gaps in the audio buffer: ' + timeRangesToArray(audioBuffered).join(', '));
76851    }
76852    if (videoBuffered.length > 1) {
76853      this.logger_('On QUOTA_EXCEEDED_ERR, found gaps in the video buffer: ' + timeRangesToArray(videoBuffered).join(', '));
76854    }
76855    const audioBufferStart = audioBuffered.length ? audioBuffered.start(0) : 0;
76856    const audioBufferEnd = audioBuffered.length ? audioBuffered.end(audioBuffered.length - 1) : 0;
76857    const videoBufferStart = videoBuffered.length ? videoBuffered.start(0) : 0;
76858    const videoBufferEnd = videoBuffered.length ? videoBuffered.end(videoBuffered.length - 1) : 0;
76859    if (audioBufferEnd - audioBufferStart <= MIN_BACK_BUFFER && videoBufferEnd - videoBufferStart <= MIN_BACK_BUFFER) {
76860      // Can't remove enough buffer to make room for new segment (or the browser doesn't
76861      // allow for appends of segments this size). In the future, it may be possible to
76862      // split up the segment and append in pieces, but for now, error out this playlist
76863      // in an attempt to switch to a more manageable rendition.
76864      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(', ')}, `);
76865      this.error({
76866        message: 'Quota exceeded error with append of a single segment of content',
76867        excludeUntil: Infinity
76868      });
76869      this.trigger('error');
76870      return;
76871    } // To try to resolve the quota exceeded error, clear back buffer and retry. This means
76872    // that the segment-loader should block on future events until this one is handled, so
76873    // that it doesn't keep moving onto further segments. Adding the call to the call
76874    // queue will prevent further appends until waitingOnRemove_ and
76875    // quotaExceededErrorRetryTimeout_ are cleared.
76876    //
76877    // Note that this will only block the current loader. In the case of demuxed content,
76878    // the other load may keep filling as fast as possible. In practice, this should be
76879    // OK, as it is a rare case when either audio has a high enough bitrate to fill up a
76880    // source buffer, or video fills without enough room for audio to append (and without
76881    // the availability of clearing out seconds of back buffer to make room for audio).
76882    // But it might still be good to handle this case in the future as a TODO.
76883
76884    this.waitingOnRemove_ = true;
76885    this.callQueue_.push(this.appendToSourceBuffer_.bind(this, {
76886      segmentInfo,
76887      type,
76888      bytes
76889    }));
76890    const currentTime = this.currentTime_(); // Try to remove as much audio and video as possible to make room for new content
76891    // before retrying.
76892
76893    const timeToRemoveUntil = currentTime - MIN_BACK_BUFFER;
76894    this.logger_(`On QUOTA_EXCEEDED_ERR, removing audio/video from 0 to ${timeToRemoveUntil}`);
76895    this.remove(0, timeToRemoveUntil, () => {
76896      this.logger_(`On QUOTA_EXCEEDED_ERR, retrying append in ${MIN_BACK_BUFFER}s`);
76897      this.waitingOnRemove_ = false; // wait the length of time alotted in the back buffer to prevent wasted
76898      // attempts (since we can't clear less than the minimum)
76899
76900      this.quotaExceededErrorRetryTimeout_ = window__default["default"].setTimeout(() => {
76901        this.logger_('On QUOTA_EXCEEDED_ERR, re-processing call queue');
76902        this.quotaExceededErrorRetryTimeout_ = null;
76903        this.processCallQueue_();
76904      }, MIN_BACK_BUFFER * 1000);
76905    }, true);
76906  }
76907  handleAppendError_({
76908    segmentInfo,
76909    type,
76910    bytes
76911  }, error) {
76912    // if there's no error, nothing to do
76913    if (!error) {
76914      return;
76915    }
76916    if (error.code === QUOTA_EXCEEDED_ERR) {
76917      this.handleQuotaExceededError_({
76918        segmentInfo,
76919        type,
76920        bytes
76921      }); // A quota exceeded error should be recoverable with a future re-append, so no need
76922      // to trigger an append error.
76923
76924      return;
76925    }
76926    this.logger_('Received non QUOTA_EXCEEDED_ERR on append', error); // If an append errors, we often can't recover.
76927    // (see https://w3c.github.io/media-source/#sourcebuffer-append-error).
76928    //
76929    // Trigger a special error so that it can be handled separately from normal,
76930    // recoverable errors.
76931
76932    this.error({
76933      message: `${type} append of ${bytes.length}b failed for segment ` + `#${segmentInfo.mediaIndex} in playlist ${segmentInfo.playlist.id}`,
76934      metadata: {
76935        errorType: videojs.Error.StreamingFailedToAppendSegment
76936      }
76937    });
76938    this.trigger('appenderror');
76939  }
76940  appendToSourceBuffer_({
76941    segmentInfo,
76942    type,
76943    initSegment,
76944    data,
76945    bytes
76946  }) {
76947    // If this is a re-append, bytes were already created and don't need to be recreated
76948    if (!bytes) {
76949      const segments = [data];
76950      let byteLength = data.byteLength;
76951      if (initSegment) {
76952        // if the media initialization segment is changing, append it before the content
76953        // segment
76954        segments.unshift(initSegment);
76955        byteLength += initSegment.byteLength;
76956      } // Technically we should be OK appending the init segment separately, however, we
76957      // haven't yet tested that, and prepending is how we have always done things.
76958
76959      bytes = concatSegments({
76960        bytes: byteLength,
76961        segments
76962      });
76963    }
76964    const metadata = {
76965      segmentInfo: segmentInfoPayload({
76966        type: this.loaderType_,
76967        segment: segmentInfo
76968      })
76969    };
76970    this.trigger({
76971      type: 'segmentappendstart',
76972      metadata
76973    });
76974    this.sourceUpdater_.appendBuffer({
76975      segmentInfo,
76976      type,
76977      bytes
76978    }, this.handleAppendError_.bind(this, {
76979      segmentInfo,
76980      type,
76981      bytes
76982    }));
76983  }
76984  handleSegmentTimingInfo_(type, requestId, segmentTimingInfo) {
76985    if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) {
76986      return;
76987    }
76988    const segment = this.pendingSegment_.segment;
76989    const timingInfoProperty = `${type}TimingInfo`;
76990    if (!segment[timingInfoProperty]) {
76991      segment[timingInfoProperty] = {};
76992    }
76993    segment[timingInfoProperty].transmuxerPrependedSeconds = segmentTimingInfo.prependedContentDuration || 0;
76994    segment[timingInfoProperty].transmuxedPresentationStart = segmentTimingInfo.start.presentation;
76995    segment[timingInfoProperty].transmuxedDecodeStart = segmentTimingInfo.start.decode;
76996    segment[timingInfoProperty].transmuxedPresentationEnd = segmentTimingInfo.end.presentation;
76997    segment[timingInfoProperty].transmuxedDecodeEnd = segmentTimingInfo.end.decode; // mainly used as a reference for debugging
76998
76999    segment[timingInfoProperty].baseMediaDecodeTime = segmentTimingInfo.baseMediaDecodeTime;
77000  }
77001  appendData_(segmentInfo, result) {
77002    const {
77003      type,
77004      data
77005    } = result;
77006    if (!data || !data.byteLength) {
77007      return;
77008    }
77009    if (type === 'audio' && this.audioDisabled_) {
77010      return;
77011    }
77012    const initSegment = this.getInitSegmentAndUpdateState_({
77013      type,
77014      initSegment: result.initSegment,
77015      playlist: segmentInfo.playlist,
77016      map: segmentInfo.isFmp4 ? segmentInfo.segment.map : null
77017    });
77018    this.appendToSourceBuffer_({
77019      segmentInfo,
77020      type,
77021      initSegment,
77022      data
77023    });
77024  }
77025  /**
77026   * load a specific segment from a request into the buffer
77027   *
77028   * @private
77029   */
77030
77031  loadSegment_(segmentInfo) {
77032    this.state = 'WAITING';
77033    this.pendingSegment_ = segmentInfo;
77034    this.trimBackBuffer_(segmentInfo);
77035    if (typeof segmentInfo.timestampOffset === 'number') {
77036      if (this.transmuxer_) {
77037        this.transmuxer_.postMessage({
77038          action: 'clearAllMp4Captions'
77039        });
77040      }
77041    }
77042    if (!this.hasEnoughInfoToLoad_()) {
77043      checkAndFixTimelines(this);
77044      this.loadQueue_.push(() => {
77045        // regenerate the audioAppendStart, timestampOffset, etc as they
77046        // may have changed since this function was added to the queue.
77047        const options = _extends__default["default"]({}, segmentInfo, {
77048          forceTimestampOffset: true
77049        });
77050        _extends__default["default"](segmentInfo, this.generateSegmentInfo_(options));
77051        this.isPendingTimestampOffset_ = false;
77052        this.updateTransmuxerAndRequestSegment_(segmentInfo);
77053      });
77054      return;
77055    }
77056    this.updateTransmuxerAndRequestSegment_(segmentInfo);
77057  }
77058  updateTransmuxerAndRequestSegment_(segmentInfo) {
77059    // We'll update the source buffer's timestamp offset once we have transmuxed data, but
77060    // the transmuxer still needs to be updated before then.
77061    //
77062    // Even though keepOriginalTimestamps is set to true for the transmuxer, timestamp
77063    // offset must be passed to the transmuxer for stream correcting adjustments.
77064    if (this.shouldUpdateTransmuxerTimestampOffset_(segmentInfo.timestampOffset)) {
77065      this.gopBuffer_.length = 0;
vendor: 25,691 bytes, lines 77065-77688
77065 // gopsToAlignWith was set before the GOP buffer was cleared
77066
77067      segmentInfo.gopsToAlignWith = [];
77068      this.timeMapping_ = 0; // reset values in the transmuxer since a discontinuity should start fresh
77069
77070      this.transmuxer_.postMessage({
77071        action: 'reset'
77072      });
77073      this.transmuxer_.postMessage({
77074        action: 'setTimestampOffset',
77075        timestampOffset: segmentInfo.timestampOffset
77076      });
77077    }
77078    const simpleSegment = this.createSimplifiedSegmentObj_(segmentInfo);
77079    const isEndOfStream = this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist, segmentInfo.partIndex);
77080    const isWalkingForward = this.mediaIndex !== null;
77081    const isDiscontinuity = segmentInfo.timeline !== this.currentTimeline_ &&
77082    // currentTimeline starts at -1, so we shouldn't end the timeline switching to 0,
77083    // the first timeline
77084    segmentInfo.timeline > 0;
77085    const isEndOfTimeline = isEndOfStream || isWalkingForward && isDiscontinuity;
77086    this.logger_(`Requesting
77087${compactSegmentUrlDescription(segmentInfo.uri)}
77088${segmentInfoString(segmentInfo)}`); // If there's an init segment associated with this segment, but it is not cached (identified by a lack of bytes),
77089    // then this init segment has never been seen before and should be appended.
77090    //
77091    // At this point the content type (audio/video or both) is not yet known, but it should be safe to set
77092    // both to true and leave the decision of whether to append the init segment to append time.
77093
77094    if (simpleSegment.map && !simpleSegment.map.bytes) {
77095      this.logger_('going to request init segment.');
77096      this.appendInitSegment_ = {
77097        video: true,
77098        audio: true
77099      };
77100    }
77101    segmentInfo.abortRequests = mediaSegmentRequest({
77102      xhr: this.vhs_.xhr,
77103      xhrOptions: this.xhrOptions_,
77104      decryptionWorker: this.decrypter_,
77105      segment: simpleSegment,
77106      abortFn: this.handleAbort_.bind(this, segmentInfo),
77107      progressFn: this.handleProgress_.bind(this),
77108      trackInfoFn: this.handleTrackInfo_.bind(this),
77109      timingInfoFn: this.handleTimingInfo_.bind(this),
77110      videoSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(this, 'video', segmentInfo.requestId),
77111      audioSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(this, 'audio', segmentInfo.requestId),
77112      captionsFn: this.handleCaptions_.bind(this),
77113      isEndOfTimeline,
77114      endedTimelineFn: () => {
77115        this.logger_('received endedtimeline callback');
77116      },
77117      id3Fn: this.handleId3_.bind(this),
77118      dataFn: this.handleData_.bind(this),
77119      doneFn: this.segmentRequestFinished_.bind(this),
77120      onTransmuxerLog: ({
77121        message,
77122        level,
77123        stream
77124      }) => {
77125        this.logger_(`${segmentInfoString(segmentInfo)} logged from transmuxer stream ${stream} as a ${level}: ${message}`);
77126      },
77127      triggerSegmentEventFn: ({
77128        type,
77129        segment,
77130        keyInfo,
77131        trackInfo,
77132        timingInfo
77133      }) => {
77134        const segInfo = segmentInfoPayload({
77135          segment
77136        });
77137        const metadata = {
77138          segmentInfo: segInfo
77139        }; // add other properties if necessary.
77140
77141        if (keyInfo) {
77142          metadata.keyInfo = keyInfo;
77143        }
77144        if (trackInfo) {
77145          metadata.trackInfo = trackInfo;
77146        }
77147        if (timingInfo) {
77148          metadata.timingInfo = timingInfo;
77149        }
77150        this.trigger({
77151          type,
77152          metadata
77153        });
77154      }
77155    });
77156  }
77157  /**
77158   * trim the back buffer so that we don't have too much data
77159   * in the source buffer
77160   *
77161   * @private
77162   *
77163   * @param {Object} segmentInfo - the current segment
77164   */
77165
77166  trimBackBuffer_(segmentInfo) {
77167    const removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of
77168    // buffer and a very conservative "garbage collector"
77169    // We manually clear out the old buffer to ensure
77170    // we don't trigger the QuotaExceeded error
77171    // on the source buffer during subsequent appends
77172
77173    if (removeToTime > 0) {
77174      this.remove(0, removeToTime);
77175    }
77176  }
77177  /**
77178   * created a simplified copy of the segment object with just the
77179   * information necessary to perform the XHR and decryption
77180   *
77181   * @private
77182   *
77183   * @param {Object} segmentInfo - the current segment
77184   * @return {Object} a simplified segment object copy
77185   */
77186
77187  createSimplifiedSegmentObj_(segmentInfo) {
77188    const segment = segmentInfo.segment;
77189    const part = segmentInfo.part;
77190    const isEncrypted = segmentInfo.segment.key || segmentInfo.segment.map && segmentInfo.segment.map.key;
77191    const isMediaInitialization = segmentInfo.segment.map && !segmentInfo.segment.map.bytes;
77192    const simpleSegment = {
77193      resolvedUri: part ? part.resolvedUri : segment.resolvedUri,
77194      byterange: part ? part.byterange : segment.byterange,
77195      requestId: segmentInfo.requestId,
77196      transmuxer: segmentInfo.transmuxer,
77197      audioAppendStart: segmentInfo.audioAppendStart,
77198      gopsToAlignWith: segmentInfo.gopsToAlignWith,
77199      part: segmentInfo.part,
77200      type: this.loaderType_,
77201      start: segmentInfo.startOfSegment,
77202      duration: segmentInfo.duration,
77203      isEncrypted,
77204      isMediaInitialization
77205    };
77206    const previousSegment = segmentInfo.playlist.segments[segmentInfo.mediaIndex - 1];
77207    if (previousSegment && previousSegment.timeline === segment.timeline) {
77208      // The baseStartTime of a segment is used to handle rollover when probing the TS
77209      // segment to retrieve timing information. Since the probe only looks at the media's
77210      // times (e.g., PTS and DTS values of the segment), and doesn't consider the
77211      // player's time (e.g., player.currentTime()), baseStartTime should reflect the
77212      // media time as well. transmuxedDecodeEnd represents the end time of a segment, in
77213      // seconds of media time, so should be used here. The previous segment is used since
77214      // the end of the previous segment should represent the beginning of the current
77215      // segment, so long as they are on the same timeline.
77216      if (previousSegment.videoTimingInfo) {
77217        simpleSegment.baseStartTime = previousSegment.videoTimingInfo.transmuxedDecodeEnd;
77218      } else if (previousSegment.audioTimingInfo) {
77219        simpleSegment.baseStartTime = previousSegment.audioTimingInfo.transmuxedDecodeEnd;
77220      }
77221    }
77222    if (segment.key) {
77223      // if the media sequence is greater than 2^32, the IV will be incorrect
77224      // assuming 10s segments, that would be about 1300 years
77225      const iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
77226      simpleSegment.key = this.segmentKey(segment.key);
77227      simpleSegment.key.iv = iv;
77228    }
77229    if (segment.map) {
77230      simpleSegment.map = this.initSegmentForMap(segment.map);
77231    }
77232    return simpleSegment;
77233  }
77234  saveTransferStats_(stats) {
77235    // every request counts as a media request even if it has been aborted
77236    // or canceled due to a timeout
77237    this.mediaRequests += 1;
77238    if (stats) {
77239      this.mediaBytesTransferred += stats.bytesReceived;
77240      this.mediaTransferDuration += stats.roundTripTime;
77241    }
77242  }
77243  saveBandwidthRelatedStats_(duration, stats) {
77244    // byteLength will be used for throughput, and should be based on bytes receieved,
77245    // which we only know at the end of the request and should reflect total bytes
77246    // downloaded rather than just bytes processed from components of the segment
77247    this.pendingSegment_.byteLength = stats.bytesReceived;
77248    if (duration < MIN_SEGMENT_DURATION_TO_SAVE_STATS) {
77249      this.logger_(`Ignoring segment's bandwidth because its duration of ${duration}` + ` is less than the min to record ${MIN_SEGMENT_DURATION_TO_SAVE_STATS}`);
77250      return;
77251    }
77252    const metadata = {
77253      bandwidthInfo: {
77254        from: this.bandwidth,
77255        to: stats.bandwidth
77256      }
77257    }; // player event with payload
77258
77259    this.trigger({
77260      type: 'bandwidthupdated',
77261      metadata
77262    });
77263    this.bandwidth = stats.bandwidth;
77264    this.roundTrip = stats.roundTripTime;
77265  }
77266  handleTimeout_() {
77267    // although the VTT segment loader bandwidth isn't really used, it's good to
77268    // maintain functinality between segment loaders
77269    this.mediaRequestsTimedout += 1;
77270    this.bandwidth = 1;
77271    this.roundTrip = NaN;
77272    this.trigger('bandwidthupdate');
77273    this.trigger('timeout');
77274  }
77275  /**
77276   * Handle the callback from the segmentRequest function and set the
77277   * associated SegmentLoader state and errors if necessary
77278   *
77279   * @private
77280   */
77281
77282  segmentRequestFinished_(error, simpleSegment, result) {
77283    // TODO handle special cases, e.g., muxed audio/video but only audio in the segment
77284    // check the call queue directly since this function doesn't need to deal with any
77285    // data, and can continue even if the source buffers are not set up and we didn't get
77286    // any data from the segment
77287    if (this.callQueue_.length) {
77288      this.callQueue_.push(this.segmentRequestFinished_.bind(this, error, simpleSegment, result));
77289      return;
77290    }
77291    this.saveTransferStats_(simpleSegment.stats); // The request was aborted and the SegmentLoader has already been reset
77292
77293    if (!this.pendingSegment_) {
77294      return;
77295    } // the request was aborted and the SegmentLoader has already started
77296    // another request. this can happen when the timeout for an aborted
77297    // request triggers due to a limitation in the XHR library
77298    // do not count this as any sort of request or we risk double-counting
77299
77300    if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
77301      return;
77302    } // an error occurred from the active pendingSegment_ so reset everything
77303
77304    if (error) {
77305      this.pendingSegment_ = null;
77306      this.state = 'READY'; // aborts are not a true error condition and nothing corrective needs to be done
77307
77308      if (error.code === REQUEST_ERRORS.ABORTED) {
77309        return;
77310      }
77311      this.pause(); // the error is really just that at least one of the requests timed-out
77312      // set the bandwidth to a very low value and trigger an ABR switch to
77313      // take emergency action
77314
77315      if (error.code === REQUEST_ERRORS.TIMEOUT) {
77316        this.handleTimeout_();
77317        return;
77318      } // if control-flow has arrived here, then the error is real
77319      // emit an error event to exclude the current playlist
77320
77321      this.mediaRequestsErrored += 1;
77322      this.error(error);
77323      this.trigger('error');
77324      return;
77325    }
77326    const segmentInfo = this.pendingSegment_; // the response was a success so set any bandwidth stats the request
77327    // generated for ABR purposes
77328
77329    this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats);
77330    segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests;
77331    if (result.gopInfo) {
77332      this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, result.gopInfo, this.safeAppend_);
77333    } // Although we may have already started appending on progress, we shouldn't switch the
77334    // state away from loading until we are officially done loading the segment data.
77335
77336    this.state = 'APPENDING'; // used for testing
77337
77338    this.trigger('appending');
77339    this.waitForAppendsToComplete_(segmentInfo);
77340  }
77341  setTimeMapping_(timeline) {
77342    const timelineMapping = this.syncController_.mappingForTimeline(timeline);
77343    if (timelineMapping !== null) {
77344      this.timeMapping_ = timelineMapping;
77345    }
77346  }
77347  updateMediaSecondsLoaded_(segment) {
77348    if (typeof segment.start === 'number' && typeof segment.end === 'number') {
77349      this.mediaSecondsLoaded += segment.end - segment.start;
77350    } else {
77351      this.mediaSecondsLoaded += segment.duration;
77352    }
77353  }
77354  shouldUpdateTransmuxerTimestampOffset_(timestampOffset) {
77355    if (timestampOffset === null) {
77356      return false;
77357    } // note that we're potentially using the same timestamp offset for both video and
77358    // audio
77359
77360    if (this.loaderType_ === 'main' && timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) {
77361      return true;
77362    }
77363    if (!this.audioDisabled_ && timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) {
77364      return true;
77365    }
77366    return false;
77367  }
77368  trueSegmentStart_({
77369    currentStart,
77370    playlist,
77371    mediaIndex,
77372    firstVideoFrameTimeForData,
77373    currentVideoTimestampOffset,
77374    useVideoTimingInfo,
77375    videoTimingInfo,
77376    audioTimingInfo
77377  }) {
77378    if (typeof currentStart !== 'undefined') {
77379      // if start was set once, keep using it
77380      return currentStart;
77381    }
77382    if (!useVideoTimingInfo) {
77383      return audioTimingInfo.start;
77384    }
77385    const previousSegment = playlist.segments[mediaIndex - 1]; // The start of a segment should be the start of the first full frame contained
77386    // within that segment. Since the transmuxer maintains a cache of incomplete data
77387    // from and/or the last frame seen, the start time may reflect a frame that starts
77388    // in the previous segment. Check for that case and ensure the start time is
77389    // accurate for the segment.
77390
77391    if (mediaIndex === 0 || !previousSegment || typeof previousSegment.start === 'undefined' || previousSegment.end !== firstVideoFrameTimeForData + currentVideoTimestampOffset) {
77392      return firstVideoFrameTimeForData;
77393    }
77394    return videoTimingInfo.start;
77395  }
77396  waitForAppendsToComplete_(segmentInfo) {
77397    const trackInfo = this.getCurrentMediaInfo_(segmentInfo);
77398    if (!trackInfo) {
77399      this.error({
77400        message: 'No starting media returned, likely due to an unsupported media format.',
77401        playlistExclusionDuration: Infinity
77402      });
77403      this.trigger('error');
77404      return;
77405    } // Although transmuxing is done, appends may not yet be finished. Throw a marker
77406    // on each queue this loader is responsible for to ensure that the appends are
77407    // complete.
77408
77409    const {
77410      hasAudio,
77411      hasVideo,
77412      isMuxed
77413    } = trackInfo;
77414    const waitForVideo = this.loaderType_ === 'main' && hasVideo;
77415    const waitForAudio = !this.audioDisabled_ && hasAudio && !isMuxed;
77416    segmentInfo.waitingOnAppends = 0; // segments with no data
77417
77418    if (!segmentInfo.hasAppendedData_) {
77419      if (!segmentInfo.timingInfo && typeof segmentInfo.timestampOffset === 'number') {
77420        // When there's no audio or video data in the segment, there's no audio or video
77421        // timing information.
77422        //
77423        // If there's no audio or video timing information, then the timestamp offset
77424        // can't be adjusted to the appropriate value for the transmuxer and source
77425        // buffers.
77426        //
77427        // Therefore, the next segment should be used to set the timestamp offset.
77428        this.isPendingTimestampOffset_ = true;
77429      } // override settings for metadata only segments
77430
77431      segmentInfo.timingInfo = {
77432        start: 0
77433      };
77434      segmentInfo.waitingOnAppends++;
77435      if (!this.isPendingTimestampOffset_) {
77436        // update the timestampoffset
77437        this.updateSourceBufferTimestampOffset_(segmentInfo); // make sure the metadata queue is processed even though we have
77438        // no video/audio data.
77439
77440        this.processMetadataQueue_();
77441      } // append is "done" instantly with no data.
77442
77443      this.checkAppendsDone_(segmentInfo);
77444      return;
77445    } // Since source updater could call back synchronously, do the increments first.
77446
77447    if (waitForVideo) {
77448      segmentInfo.waitingOnAppends++;
77449    }
77450    if (waitForAudio) {
77451      segmentInfo.waitingOnAppends++;
77452    }
77453    if (waitForVideo) {
77454      this.sourceUpdater_.videoQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo));
77455    }
77456    if (waitForAudio) {
77457      this.sourceUpdater_.audioQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo));
77458    }
77459  }
77460  checkAppendsDone_(segmentInfo) {
77461    if (this.checkForAbort_(segmentInfo.requestId)) {
77462      return;
77463    }
77464    segmentInfo.waitingOnAppends--;
77465    if (segmentInfo.waitingOnAppends === 0) {
77466      this.handleAppendsDone_();
77467    }
77468  }
77469  checkForIllegalMediaSwitch(trackInfo) {
77470    const illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.getCurrentMediaInfo_(), trackInfo);
77471    if (illegalMediaSwitchError) {
77472      this.error({
77473        message: illegalMediaSwitchError,
77474        playlistExclusionDuration: Infinity
77475      });
77476      this.trigger('error');
77477      return true;
77478    }
77479    return false;
77480  }
77481  updateSourceBufferTimestampOffset_(segmentInfo) {
77482    if (segmentInfo.timestampOffset === null ||
77483    // we don't yet have the start for whatever media type (video or audio) has
77484    // priority, timing-wise, so we must wait
77485    typeof segmentInfo.timingInfo.start !== 'number' ||
77486    // already updated the timestamp offset for this segment
77487    segmentInfo.changedTimestampOffset ||
77488    // the alt audio loader should not be responsible for setting the timestamp offset
77489    this.loaderType_ !== 'main') {
77490      return;
77491    }
77492    let didChange = false; // Primary timing goes by video, and audio is trimmed in the transmuxer, meaning that
77493    // the timing info here comes from video. In the event that the audio is longer than
77494    // the video, this will trim the start of the audio.
77495    // This also trims any offset from 0 at the beginning of the media
77496
77497    segmentInfo.timestampOffset -= this.getSegmentStartTimeForTimestampOffsetCalculation_({
77498      videoTimingInfo: segmentInfo.segment.videoTimingInfo,
77499      audioTimingInfo: segmentInfo.segment.audioTimingInfo,
77500      timingInfo: segmentInfo.timingInfo
77501    }); // In the event that there are part segment downloads, each will try to update the
77502    // timestamp offset. Retaining this bit of state prevents us from updating in the
77503    // future (within the same segment), however, there may be a better way to handle it.
77504
77505    segmentInfo.changedTimestampOffset = true;
77506    if (segmentInfo.timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) {
77507      this.sourceUpdater_.videoTimestampOffset(segmentInfo.timestampOffset);
77508      didChange = true;
77509    }
77510    if (segmentInfo.timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) {
77511      this.sourceUpdater_.audioTimestampOffset(segmentInfo.timestampOffset);
77512      didChange = true;
77513    }
77514    if (didChange) {
77515      this.trigger('timestampoffset');
77516    }
77517  }
77518  getSegmentStartTimeForTimestampOffsetCalculation_({
77519    videoTimingInfo,
77520    audioTimingInfo,
77521    timingInfo
77522  }) {
77523    if (!this.useDtsForTimestampOffset_) {
77524      return timingInfo.start;
77525    }
77526    if (videoTimingInfo && typeof videoTimingInfo.transmuxedDecodeStart === 'number') {
77527      return videoTimingInfo.transmuxedDecodeStart;
77528    } // handle audio only
77529
77530    if (audioTimingInfo && typeof audioTimingInfo.transmuxedDecodeStart === 'number') {
77531      return audioTimingInfo.transmuxedDecodeStart;
77532    } // handle content not transmuxed (e.g., MP4)
77533
77534    return timingInfo.start;
77535  }
77536  updateTimingInfoEnd_(segmentInfo) {
77537    segmentInfo.timingInfo = segmentInfo.timingInfo || {};
77538    const trackInfo = this.getMediaInfo_();
77539    const useVideoTimingInfo = this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo;
77540    const prioritizedTimingInfo = useVideoTimingInfo && segmentInfo.videoTimingInfo ? segmentInfo.videoTimingInfo : segmentInfo.audioTimingInfo;
77541    if (!prioritizedTimingInfo) {
77542      return;
77543    }
77544    segmentInfo.timingInfo.end = typeof prioritizedTimingInfo.end === 'number' ?
77545    // End time may not exist in a case where we aren't parsing the full segment (one
77546    // current example is the case of fmp4), so use the rough duration to calculate an
77547    // end time.
77548    prioritizedTimingInfo.end : prioritizedTimingInfo.start + segmentInfo.duration;
77549  }
77550  /**
77551   * callback to run when appendBuffer is finished. detects if we are
77552   * in a good state to do things with the data we got, or if we need
77553   * to wait for more
77554   *
77555   * @private
77556   */
77557
77558  handleAppendsDone_() {
77559    // appendsdone can cause an abort
77560    if (this.pendingSegment_) {
77561      const metadata = {
77562        segmentInfo: segmentInfoPayload({
77563          type: this.loaderType_,
77564          segment: this.pendingSegment_
77565        })
77566      };
77567      this.trigger({
77568        type: 'appendsdone',
77569        metadata
77570      });
77571    }
77572    if (!this.pendingSegment_) {
77573      this.state = 'READY'; // TODO should this move into this.checkForAbort to speed up requests post abort in
77574      // all appending cases?
77575
77576      if (!this.paused()) {
77577        this.monitorBuffer_();
77578      }
77579      return;
77580    }
77581    const segmentInfo = this.pendingSegment_;
77582    if (segmentInfo.part && segmentInfo.part.syncInfo) {
77583      // low-latency flow
77584      segmentInfo.part.syncInfo.markAppended();
77585    } else if (segmentInfo.segment.syncInfo) {
77586      // normal flow
77587      segmentInfo.segment.syncInfo.markAppended();
77588    } // Now that the end of the segment has been reached, we can set the end time. It's
77589    // best to wait until all appends are done so we're sure that the primary media is
77590    // finished (and we have its end time).
77591
77592    this.updateTimingInfoEnd_(segmentInfo);
77593    if (this.shouldSaveSegmentTimingInfo_) {
77594      // Timeline mappings should only be saved for the main loader. This is for multiple
77595      // reasons:
77596      //
77597      // 1) Only one mapping is saved per timeline, meaning that if both the audio loader
77598      //    and the main loader try to save the timeline mapping, whichever comes later
77599      //    will overwrite the first. In theory this is OK, as the mappings should be the
77600      //    same, however, it breaks for (2)
77601      // 2) In the event of a live stream, the initial live point will make for a somewhat
77602      //    arbitrary mapping. If audio and video streams are not perfectly in-sync, then
77603      //    the mapping will be off for one of the streams, dependent on which one was
77604      //    first saved (see (1)).
77605      // 3) Primary timing goes by video in VHS, so the mapping should be video.
77606      //
77607      // Since the audio loader will wait for the main loader to load the first segment,
77608      // the main loader will save the first timeline mapping, and ensure that there won't
77609      // be a case where audio loads two segments without saving a mapping (thus leading
77610      // to missing segment timing info).
77611      this.syncController_.saveSegmentTimingInfo({
77612        segmentInfo,
77613        shouldSaveTimelineMapping: this.loaderType_ === 'main'
77614      });
77615    }
77616    const segmentDurationMessage = getTroublesomeSegmentDurationMessage(segmentInfo, this.sourceType_);
77617    if (segmentDurationMessage) {
77618      if (segmentDurationMessage.severity === 'warn') {
77619        videojs.log.warn(segmentDurationMessage.message);
77620      } else {
77621        this.logger_(segmentDurationMessage.message);
77622      }
77623    }
77624    this.recordThroughput_(segmentInfo);
77625    this.pendingSegment_ = null;
77626    this.state = 'READY';
77627    if (segmentInfo.isSyncRequest) {
77628      this.trigger('syncinfoupdate'); // if the sync request was not appended
77629      // then it was not the correct segment.
77630      // throw it away and use the data it gave us
77631      // to get the correct one.
77632
77633      if (!segmentInfo.hasAppendedData_) {
77634        this.logger_(`Throwing away un-appended sync request ${segmentInfoString(segmentInfo)}`);
77635        return;
77636      }
77637    }
77638    this.logger_(`Appended ${segmentInfoString(segmentInfo)}`);
77639    this.addSegmentMetadataCue_(segmentInfo);
77640    this.fetchAtBuffer_ = true;
77641    if (this.currentTimeline_ !== segmentInfo.timeline) {
77642      this.timelineChangeController_.lastTimelineChange({
77643        type: this.loaderType_,
77644        from: this.currentTimeline_,
77645        to: segmentInfo.timeline
77646      }); // If audio is not disabled, the main segment loader is responsible for updating
77647      // the audio timeline as well. If the content is video only, this won't have any
77648      // impact.
77649
77650      if (this.loaderType_ === 'main' && !this.audioDisabled_) {
77651        this.timelineChangeController_.lastTimelineChange({
77652          type: 'audio',
77653          from: this.currentTimeline_,
77654          to: segmentInfo.timeline
77655        });
77656      }
77657    }
77658    this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before
77659    // the following conditional otherwise it may consider this a bad "guess"
77660    // and attempt to resync when the post-update seekable window and live
77661    // point would mean that this was the perfect segment to fetch
77662
77663    this.trigger('syncinfoupdate');
77664    const segment = segmentInfo.segment;
77665    const part = segmentInfo.part;
77666    const badSegmentGuess = segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3;
77667    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
77668    // the currentTime_ that means that our conservative guess was too conservative.
77669    // In that case, reset the loader state so that we try to use any information gained
77670    // from the previous request to create a new, more accurate, sync-point.
77671
77672    if (badSegmentGuess || badPartGuess) {
77673      this.logger_(`bad ${badSegmentGuess ? 'segment' : 'part'} ${segmentInfoString(segmentInfo)}`);
77674      this.resetEverything();
77675      return;
77676    }
77677    const isWalkingForward = this.mediaIndex !== null; // Don't do a rendition switch unless we have enough time to get a sync segment
77678    // and conservatively guess
77679
77680    if (isWalkingForward) {
77681      this.trigger('bandwidthupdate');
77682    }
77683    this.trigger('progress');
77684    this.mediaIndex = segmentInfo.mediaIndex;
77685    this.partIndex = segmentInfo.partIndex; // any time an update finishes and the last segment is in the
77686    // buffer, end the stream. this ensures the "ended" event will
77687    // fire if playback reaches that point.
77688
vendor: 4,727 bytes, lines 77689-77815
77689    if (this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist, segmentInfo.partIndex)) {
77690      this.endOfStream();
77691    } // used for testing
77692
77693    this.trigger('appended');
77694    if (segmentInfo.hasAppendedData_) {
77695      this.mediaAppends++;
77696    }
77697    if (!this.paused()) {
77698      this.monitorBuffer_();
77699    }
77700  }
77701  /**
77702   * Records the current throughput of the decrypt, transmux, and append
77703   * portion of the semgment pipeline. `throughput.rate` is a the cumulative
77704   * moving average of the throughput. `throughput.count` is the number of
77705   * data points in the average.
77706   *
77707   * @private
77708   * @param {Object} segmentInfo the object returned by loadSegment
77709   */
77710
77711  recordThroughput_(segmentInfo) {
77712    if (segmentInfo.duration < MIN_SEGMENT_DURATION_TO_SAVE_STATS) {
77713      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}`);
77714      return;
77715    }
77716    const rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide
77717    // by zero in the case where the throughput is ridiculously high
77718
77719    const segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second
77720
77721    const segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation:
77722    //   newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
77723
77724    this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
77725  }
77726  /**
77727   * Adds a cue to the segment-metadata track with some metadata information about the
77728   * segment
77729   *
77730   * @private
77731   * @param {Object} segmentInfo
77732   *        the object returned by loadSegment
77733   * @method addSegmentMetadataCue_
77734   */
77735
77736  addSegmentMetadataCue_(segmentInfo) {
77737    if (!this.segmentMetadataTrack_) {
77738      return;
77739    }
77740    const segment = segmentInfo.segment;
77741    const start = segment.start;
77742    const end = segment.end; // Do not try adding the cue if the start and end times are invalid.
77743
77744    if (!finite(start) || !finite(end)) {
77745      return;
77746    }
77747    removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
77748    const Cue = window__default["default"].WebKitDataCue || window__default["default"].VTTCue;
77749    const value = {
77750      custom: segment.custom,
77751      dateTimeObject: segment.dateTimeObject,
77752      dateTimeString: segment.dateTimeString,
77753      programDateTime: segment.programDateTime,
77754      bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
77755      resolution: segmentInfo.playlist.attributes.RESOLUTION,
77756      codecs: segmentInfo.playlist.attributes.CODECS,
77757      byteLength: segmentInfo.byteLength,
77758      uri: segmentInfo.uri,
77759      timeline: segmentInfo.timeline,
77760      playlist: segmentInfo.playlist.id,
77761      start,
77762      end
77763    };
77764    const data = JSON.stringify(value);
77765    const cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between
77766    // the differences of WebKitDataCue in safari and VTTCue in other browsers
77767
77768    cue.value = value;
77769    this.segmentMetadataTrack_.addCue(cue);
77770  }
77771}
77772function noop() {}
77773const toTitleCase = function (string) {
77774  if (typeof string !== 'string') {
77775    return string;
77776  }
77777  return string.replace(/./, w => w.toUpperCase());
77778};
77779
77780/**
77781 * @file source-updater.js
77782 */
77783const bufferTypes = ['video', 'audio'];
77784const updating = (type, sourceUpdater) => {
77785  const sourceBuffer = sourceUpdater[`${type}Buffer`];
77786  return sourceBuffer && sourceBuffer.updating || sourceUpdater.queuePending[type];
77787};
77788const nextQueueIndexOfType = (type, queue) => {
77789  for (let i = 0; i < queue.length; i++) {
77790    const queueEntry = queue[i];
77791    if (queueEntry.type === 'mediaSource') {
77792      // If the next entry is a media source entry (uses multiple source buffers), block
77793      // processing to allow it to go through first.
77794      return null;
77795    }
77796    if (queueEntry.type === type) {
77797      return i;
77798    }
77799  }
77800  return null;
77801};
77802const shiftQueue = (type, sourceUpdater) => {
77803  if (sourceUpdater.queue.length === 0) {
77804    return;
77805  }
77806  let queueIndex = 0;
77807  let queueEntry = sourceUpdater.queue[queueIndex];
77808  if (queueEntry.type === 'mediaSource') {
77809    if (!sourceUpdater.updating() && sourceUpdater.mediaSource.readyState !== 'closed') {
77810      sourceUpdater.queue.shift();
77811      queueEntry.action(sourceUpdater);
77812      if (queueEntry.doneFn) {
77813        queueEntry.doneFn();
77814      } // Only specific source buffer actions must wait for async updateend events. Media
77815      // Source actions process synchronously. Therefore, both audio and video source
vendor: 4,869 bytes, lines 77816-77921
77816      // buffers are now clear to process the next queue entries.
77817
77818      shiftQueue('audio', sourceUpdater);
77819      shiftQueue('video', sourceUpdater);
77820    } // Media Source actions require both source buffers, so if the media source action
77821    // couldn't process yet (because one or both source buffers are busy), block other
77822    // queue actions until both are available and the media source action can process.
77823
77824    return;
77825  }
77826  if (type === 'mediaSource') {
77827    // If the queue was shifted by a media source action (this happens when pushing a
77828    // media source action onto the queue), then it wasn't from an updateend event from an
77829    // audio or video source buffer, so there's no change from previous state, and no
77830    // processing should be done.
77831    return;
77832  } // Media source queue entries don't need to consider whether the source updater is
77833  // started (i.e., source buffers are created) as they don't need the source buffers, but
77834  // source buffer queue entries do.
77835
77836  if (!sourceUpdater.ready() || sourceUpdater.mediaSource.readyState === 'closed' || updating(type, sourceUpdater)) {
77837    return;
77838  }
77839  if (queueEntry.type !== type) {
77840    queueIndex = nextQueueIndexOfType(type, sourceUpdater.queue);
77841    if (queueIndex === null) {
77842      // Either there's no queue entry that uses this source buffer type in the queue, or
77843      // there's a media source queue entry before the next entry of this type, in which
77844      // case wait for that action to process first.
77845      return;
77846    }
77847    queueEntry = sourceUpdater.queue[queueIndex];
77848  }
77849  sourceUpdater.queue.splice(queueIndex, 1); // Keep a record that this source buffer type is in use.
77850  //
77851  // The queue pending operation must be set before the action is performed in the event
77852  // that the action results in a synchronous event that is acted upon. For instance, if
77853  // an exception is thrown that can be handled, it's possible that new actions will be
77854  // appended to an empty queue and immediately executed, but would not have the correct
77855  // pending information if this property was set after the action was performed.
77856
77857  sourceUpdater.queuePending[type] = queueEntry;
77858  queueEntry.action(type, sourceUpdater);
77859  if (!queueEntry.doneFn) {
77860    // synchronous operation, process next entry
77861    sourceUpdater.queuePending[type] = null;
77862    shiftQueue(type, sourceUpdater);
77863    return;
77864  }
77865};
77866const cleanupBuffer = (type, sourceUpdater) => {
77867  const buffer = sourceUpdater[`${type}Buffer`];
77868  const titleType = toTitleCase(type);
77869  if (!buffer) {
77870    return;
77871  }
77872  buffer.removeEventListener('updateend', sourceUpdater[`on${titleType}UpdateEnd_`]);
77873  buffer.removeEventListener('error', sourceUpdater[`on${titleType}Error_`]);
77874  sourceUpdater.codecs[type] = null;
77875  sourceUpdater[`${type}Buffer`] = null;
77876};
77877const inSourceBuffers = (mediaSource, sourceBuffer) => mediaSource && sourceBuffer && Array.prototype.indexOf.call(mediaSource.sourceBuffers, sourceBuffer) !== -1;
77878const actions = {
77879  appendBuffer: (bytes, segmentInfo, onError) => (type, sourceUpdater) => {
77880    const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
77881    // or the media source does not contain this source buffer.
77882
77883    if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
77884      return;
77885    }
77886    sourceUpdater.logger_(`Appending segment ${segmentInfo.mediaIndex}'s ${bytes.length} bytes to ${type}Buffer`);
77887    try {
77888      sourceBuffer.appendBuffer(bytes);
77889    } catch (e) {
77890      sourceUpdater.logger_(`Error with code ${e.code} ` + (e.code === QUOTA_EXCEEDED_ERR ? '(QUOTA_EXCEEDED_ERR) ' : '') + `when appending segment ${segmentInfo.mediaIndex} to ${type}Buffer`);
77891      sourceUpdater.queuePending[type] = null;
77892      onError(e);
77893    }
77894  },
77895  remove: (start, end) => (type, sourceUpdater) => {
77896    const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
77897    // or the media source does not contain this source buffer.
77898
77899    if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
77900      return;
77901    }
77902    sourceUpdater.logger_(`Removing ${start} to ${end} from ${type}Buffer`);
77903    try {
77904      sourceBuffer.remove(start, end);
77905    } catch (e) {
77906      sourceUpdater.logger_(`Remove ${start} to ${end} from ${type}Buffer failed`);
77907    }
77908  },
77909  timestampOffset: offset => (type, sourceUpdater) => {
77910    const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
77911    // or the media source does not contain this source buffer.
77912
77913    if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
77914      return;
77915    }
77916    sourceUpdater.logger_(`Setting ${type}timestampOffset to ${offset}`);
77917    sourceBuffer.timestampOffset = offset;
77918  },
77919  callback: callback => (type, sourceUpdater) => {
77920    callback();
77921  },
vendor: 10,690 bytes, lines 77922-78239
77922  endOfStream: error => sourceUpdater => {
77923    if (sourceUpdater.mediaSource.readyState !== 'open') {
77924      return;
77925    }
77926    sourceUpdater.logger_(`Calling mediaSource endOfStream(${error || ''})`);
77927    try {
77928      sourceUpdater.mediaSource.endOfStream(error);
77929    } catch (e) {
77930      videojs.log.warn('Failed to call media source endOfStream', e);
77931    }
77932  },
77933  duration: duration => sourceUpdater => {
77934    sourceUpdater.logger_(`Setting mediaSource duration to ${duration}`);
77935    try {
77936      sourceUpdater.mediaSource.duration = duration;
77937    } catch (e) {
77938      videojs.log.warn('Failed to set media source duration', e);
77939    }
77940  },
77941  abort: () => (type, sourceUpdater) => {
77942    if (sourceUpdater.mediaSource.readyState !== 'open') {
77943      return;
77944    }
77945    const sourceBuffer = sourceUpdater[`${type}Buffer`]; // can't do anything if the media source / source buffer is null
77946    // or the media source does not contain this source buffer.
77947
77948    if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
77949      return;
77950    }
77951    sourceUpdater.logger_(`calling abort on ${type}Buffer`);
77952    try {
77953      sourceBuffer.abort();
77954    } catch (e) {
77955      videojs.log.warn(`Failed to abort on ${type}Buffer`, e);
77956    }
77957  },
77958  addSourceBuffer: (type, codec) => sourceUpdater => {
77959    const titleType = toTitleCase(type);
77960    const mime = codecs_js.getMimeForCodec(codec);
77961    sourceUpdater.logger_(`Adding ${type}Buffer with codec ${codec} to mediaSource`);
77962    const sourceBuffer = sourceUpdater.mediaSource.addSourceBuffer(mime);
77963    sourceBuffer.addEventListener('updateend', sourceUpdater[`on${titleType}UpdateEnd_`]);
77964    sourceBuffer.addEventListener('error', sourceUpdater[`on${titleType}Error_`]);
77965    sourceUpdater.codecs[type] = codec;
77966    sourceUpdater[`${type}Buffer`] = sourceBuffer;
77967  },
77968  removeSourceBuffer: type => sourceUpdater => {
77969    const sourceBuffer = sourceUpdater[`${type}Buffer`];
77970    cleanupBuffer(type, sourceUpdater); // can't do anything if the media source / source buffer is null
77971    // or the media source does not contain this source buffer.
77972
77973    if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
77974      return;
77975    }
77976    sourceUpdater.logger_(`Removing ${type}Buffer with codec ${sourceUpdater.codecs[type]} from mediaSource`);
77977    try {
77978      sourceUpdater.mediaSource.removeSourceBuffer(sourceBuffer);
77979    } catch (e) {
77980      videojs.log.warn(`Failed to removeSourceBuffer ${type}Buffer`, e);
77981    }
77982  },
77983  changeType: codec => (type, sourceUpdater) => {
77984    const sourceBuffer = sourceUpdater[`${type}Buffer`];
77985    const mime = codecs_js.getMimeForCodec(codec); // can't do anything if the media source / source buffer is null
77986    // or the media source does not contain this source buffer.
77987
77988    if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) {
77989      return;
77990    } // do not update codec if we don't need to.
77991    // Only update if we change the codec base.
77992    // For example, going from avc1.640028 to avc1.64001f does not require a changeType call.
77993
77994    const newCodecBase = codec.substring(0, codec.indexOf('.'));
77995    const oldCodec = sourceUpdater.codecs[type];
77996    const oldCodecBase = oldCodec.substring(0, oldCodec.indexOf('.'));
77997    if (oldCodecBase === newCodecBase) {
77998      return;
77999    }
78000    const metadata = {
78001      codecsChangeInfo: {
78002        from: oldCodec,
78003        to: codec
78004      }
78005    };
78006    sourceUpdater.trigger({
78007      type: 'codecschange',
78008      metadata
78009    });
78010    sourceUpdater.logger_(`changing ${type}Buffer codec from ${oldCodec} to ${codec}`); // check if change to the provided type is supported
78011
78012    try {
78013      sourceBuffer.changeType(mime);
78014      sourceUpdater.codecs[type] = codec;
78015    } catch (e) {
78016      metadata.errorType = videojs.Error.StreamingCodecsChangeError;
78017      metadata.error = e;
78018      e.metadata = metadata;
78019      sourceUpdater.error_ = e;
78020      sourceUpdater.trigger('error');
78021      videojs.log.warn(`Failed to changeType on ${type}Buffer`, e);
78022    }
78023  }
78024};
78025const pushQueue = ({
78026  type,
78027  sourceUpdater,
78028  action,
78029  doneFn,
78030  name
78031}) => {
78032  sourceUpdater.queue.push({
78033    type,
78034    action,
78035    doneFn,
78036    name
78037  });
78038  shiftQueue(type, sourceUpdater);
78039};
78040const onUpdateend = (type, sourceUpdater) => e => {
78041  // Although there should, in theory, be a pending action for any updateend receieved,
78042  // there are some actions that may trigger updateend events without set definitions in
78043  // the w3c spec. For instance, setting the duration on the media source may trigger
78044  // updateend events on source buffers. This does not appear to be in the spec. As such,
78045  // if we encounter an updateend without a corresponding pending action from our queue
78046  // for that source buffer type, process the next action.
78047  const bufferedRangesForType = sourceUpdater[`${type}Buffered`]();
78048  const descriptiveString = bufferedRangesToString(bufferedRangesForType);
78049  sourceUpdater.logger_(`received "updateend" event for ${type} Source Buffer: `, descriptiveString);
78050  if (sourceUpdater.queuePending[type]) {
78051    const doneFn = sourceUpdater.queuePending[type].doneFn;
78052    sourceUpdater.queuePending[type] = null;
78053    if (doneFn) {
78054      // if there's an error, report it
78055      doneFn(sourceUpdater[`${type}Error_`]);
78056    }
78057  }
78058  shiftQueue(type, sourceUpdater);
78059};
78060/**
78061 * A queue of callbacks to be serialized and applied when a
78062 * MediaSource and its associated SourceBuffers are not in the
78063 * updating state. It is used by the segment loader to update the
78064 * underlying SourceBuffers when new data is loaded, for instance.
78065 *
78066 * @class SourceUpdater
78067 * @param {MediaSource} mediaSource the MediaSource to create the SourceBuffer from
78068 * @param {string} mimeType the desired MIME type of the underlying SourceBuffer
78069 */
78070
78071class SourceUpdater extends videojs.EventTarget {
78072  constructor(mediaSource) {
78073    super();
78074    this.mediaSource = mediaSource;
78075    this.sourceopenListener_ = () => shiftQueue('mediaSource', this);
78076    this.mediaSource.addEventListener('sourceopen', this.sourceopenListener_);
78077    this.logger_ = logger('SourceUpdater'); // initial timestamp offset is 0
78078
78079    this.audioTimestampOffset_ = 0;
78080    this.videoTimestampOffset_ = 0;
78081    this.queue = [];
78082    this.queuePending = {
78083      audio: null,
78084      video: null
78085    };
78086    this.delayedAudioAppendQueue_ = [];
78087    this.videoAppendQueued_ = false;
78088    this.codecs = {};
78089    this.onVideoUpdateEnd_ = onUpdateend('video', this);
78090    this.onAudioUpdateEnd_ = onUpdateend('audio', this);
78091    this.onVideoError_ = e => {
78092      // used for debugging
78093      this.videoError_ = e;
78094    };
78095    this.onAudioError_ = e => {
78096      // used for debugging
78097      this.audioError_ = e;
78098    };
78099    this.createdSourceBuffers_ = false;
78100    this.initializedEme_ = false;
78101    this.triggeredReady_ = false;
78102  }
78103  initializedEme() {
78104    this.initializedEme_ = true;
78105    this.triggerReady();
78106  }
78107  hasCreatedSourceBuffers() {
78108    // if false, likely waiting on one of the segment loaders to get enough data to create
78109    // source buffers
78110    return this.createdSourceBuffers_;
78111  }
78112  hasInitializedAnyEme() {
78113    return this.initializedEme_;
78114  }
78115  ready() {
78116    return this.hasCreatedSourceBuffers() && this.hasInitializedAnyEme();
78117  }
78118  createSourceBuffers(codecs) {
78119    if (this.hasCreatedSourceBuffers()) {
78120      // already created them before
78121      return;
78122    } // the intial addOrChangeSourceBuffers will always be
78123    // two add buffers.
78124
78125    this.addOrChangeSourceBuffers(codecs);
78126    this.createdSourceBuffers_ = true;
78127    this.trigger('createdsourcebuffers');
78128    this.triggerReady();
78129  }
78130  triggerReady() {
78131    // only allow ready to be triggered once, this prevents the case
78132    // where:
78133    // 1. we trigger createdsourcebuffers
78134    // 2. ie 11 synchronously initializates eme
78135    // 3. the synchronous initialization causes us to trigger ready
78136    // 4. We go back to the ready check in createSourceBuffers and ready is triggered again.
78137    if (this.ready() && !this.triggeredReady_) {
78138      this.triggeredReady_ = true;
78139      this.trigger('ready');
78140    }
78141  }
78142  /**
78143   * Add a type of source buffer to the media source.
78144   *
78145   * @param {string} type
78146   *        The type of source buffer to add.
78147   *
78148   * @param {string} codec
78149   *        The codec to add the source buffer with.
78150   */
78151
78152  addSourceBuffer(type, codec) {
78153    pushQueue({
78154      type: 'mediaSource',
78155      sourceUpdater: this,
78156      action: actions.addSourceBuffer(type, codec),
78157      name: 'addSourceBuffer'
78158    });
78159  }
78160  /**
78161   * call abort on a source buffer.
78162   *
78163   * @param {string} type
78164   *        The type of source buffer to call abort on.
78165   */
78166
78167  abort(type) {
78168    pushQueue({
78169      type,
78170      sourceUpdater: this,
78171      action: actions.abort(type),
78172      name: 'abort'
78173    });
78174  }
78175  /**
78176   * Call removeSourceBuffer and remove a specific type
78177   * of source buffer on the mediaSource.
78178   *
78179   * @param {string} type
78180   *        The type of source buffer to remove.
78181   */
78182
78183  removeSourceBuffer(type) {
78184    if (!this.canRemoveSourceBuffer()) {
78185      videojs.log.error('removeSourceBuffer is not supported!');
78186      return;
78187    }
78188    pushQueue({
78189      type: 'mediaSource',
78190      sourceUpdater: this,
78191      action: actions.removeSourceBuffer(type),
78192      name: 'removeSourceBuffer'
78193    });
78194  }
78195  /**
78196   * Whether or not the removeSourceBuffer function is supported
78197   * on the mediaSource.
78198   *
78199   * @return {boolean}
78200   *          if removeSourceBuffer can be called.
78201   */
78202
78203  canRemoveSourceBuffer() {
78204    // As of Firefox 83 removeSourceBuffer
78205    // throws errors, so we report that it does not support this.
78206    return !videojs.browser.IS_FIREFOX && window__default["default"].MediaSource && window__default["default"].MediaSource.prototype && typeof window__default["default"].MediaSource.prototype.removeSourceBuffer === 'function';
78207  }
78208  /**
78209   * Whether or not the changeType function is supported
78210   * on our SourceBuffers.
78211   *
78212   * @return {boolean}
78213   *         if changeType can be called.
78214   */
78215
78216  static canChangeType() {
78217    return window__default["default"].SourceBuffer && window__default["default"].SourceBuffer.prototype && typeof window__default["default"].SourceBuffer.prototype.changeType === 'function';
78218  }
78219  /**
78220   * Whether or not the changeType function is supported
78221   * on our SourceBuffers.
78222   *
78223   * @return {boolean}
78224   *         if changeType can be called.
78225   */
78226
78227  canChangeType() {
78228    return this.constructor.canChangeType();
78229  }
78230  /**
78231   * Call the changeType function on a source buffer, given the code and type.
78232   *
78233   * @param {string} type
78234   *        The type of source buffer to call changeType on.
78235   *
78236   * @param {string} codec
78237   *        The codec string to change type with on the source buffer.
78238   */
78239
vendor: 18,246 bytes, lines 78240-78827
78240  changeType(type, codec) {
78241    if (!this.canChangeType()) {
78242      videojs.log.error('changeType is not supported!');
78243      return;
78244    }
78245    pushQueue({
78246      type,
78247      sourceUpdater: this,
78248      action: actions.changeType(codec),
78249      name: 'changeType'
78250    });
78251  }
78252  /**
78253   * Add source buffers with a codec or, if they are already created,
78254   * call changeType on source buffers using changeType.
78255   *
78256   * @param {Object} codecs
78257   *        Codecs to switch to
78258   */
78259
78260  addOrChangeSourceBuffers(codecs) {
78261    if (!codecs || typeof codecs !== 'object' || Object.keys(codecs).length === 0) {
78262      throw new Error('Cannot addOrChangeSourceBuffers to undefined codecs');
78263    }
78264    Object.keys(codecs).forEach(type => {
78265      const codec = codecs[type];
78266      if (!this.hasCreatedSourceBuffers()) {
78267        return this.addSourceBuffer(type, codec);
78268      }
78269      if (this.canChangeType()) {
78270        this.changeType(type, codec);
78271      }
78272    });
78273  }
78274  /**
78275   * Queue an update to append an ArrayBuffer.
78276   *
78277   * @param {MediaObject} object containing audioBytes and/or videoBytes
78278   * @param {Function} done the function to call when done
78279   * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
78280   */
78281
78282  appendBuffer(options, doneFn) {
78283    const {
78284      segmentInfo,
78285      type,
78286      bytes
78287    } = options;
78288    this.processedAppend_ = true;
78289    if (type === 'audio' && this.videoBuffer && !this.videoAppendQueued_) {
78290      this.delayedAudioAppendQueue_.push([options, doneFn]);
78291      this.logger_(`delayed audio append of ${bytes.length} until video append`);
78292      return;
78293    } // In the case of certain errors, for instance, QUOTA_EXCEEDED_ERR, updateend will
78294    // not be fired. This means that the queue will be blocked until the next action
78295    // taken by the segment-loader. Provide a mechanism for segment-loader to handle
78296    // these errors by calling the doneFn with the specific error.
78297
78298    const onError = doneFn;
78299    pushQueue({
78300      type,
78301      sourceUpdater: this,
78302      action: actions.appendBuffer(bytes, segmentInfo || {
78303        mediaIndex: -1
78304      }, onError),
78305      doneFn,
78306      name: 'appendBuffer'
78307    });
78308    if (type === 'video') {
78309      this.videoAppendQueued_ = true;
78310      if (!this.delayedAudioAppendQueue_.length) {
78311        return;
78312      }
78313      const queue = this.delayedAudioAppendQueue_.slice();
78314      this.logger_(`queuing delayed audio ${queue.length} appendBuffers`);
78315      this.delayedAudioAppendQueue_.length = 0;
78316      queue.forEach(que => {
78317        this.appendBuffer.apply(this, que);
78318      });
78319    }
78320  }
78321  /**
78322   * Get the audio buffer's buffered timerange.
78323   *
78324   * @return {TimeRange}
78325   *         The audio buffer's buffered time range
78326   */
78327
78328  audioBuffered() {
78329    // no media source/source buffer or it isn't in the media sources
78330    // source buffer list
78331    if (!inSourceBuffers(this.mediaSource, this.audioBuffer)) {
78332      return createTimeRanges();
78333    }
78334    return this.audioBuffer.buffered ? this.audioBuffer.buffered : createTimeRanges();
78335  }
78336  /**
78337   * Get the video buffer's buffered timerange.
78338   *
78339   * @return {TimeRange}
78340   *         The video buffer's buffered time range
78341   */
78342
78343  videoBuffered() {
78344    // no media source/source buffer or it isn't in the media sources
78345    // source buffer list
78346    if (!inSourceBuffers(this.mediaSource, this.videoBuffer)) {
78347      return createTimeRanges();
78348    }
78349    return this.videoBuffer.buffered ? this.videoBuffer.buffered : createTimeRanges();
78350  }
78351  /**
78352   * Get a combined video/audio buffer's buffered timerange.
78353   *
78354   * @return {TimeRange}
78355   *         the combined time range
78356   */
78357
78358  buffered() {
78359    const video = inSourceBuffers(this.mediaSource, this.videoBuffer) ? this.videoBuffer : null;
78360    const audio = inSourceBuffers(this.mediaSource, this.audioBuffer) ? this.audioBuffer : null;
78361    if (audio && !video) {
78362      return this.audioBuffered();
78363    }
78364    if (video && !audio) {
78365      return this.videoBuffered();
78366    }
78367    return bufferIntersection(this.audioBuffered(), this.videoBuffered());
78368  }
78369  /**
78370   * Add a callback to the queue that will set duration on the mediaSource.
78371   *
78372   * @param {number} duration
78373   *        The duration to set
78374   *
78375   * @param {Function} [doneFn]
78376   *        function to run after duration has been set.
78377   */
78378
78379  setDuration(duration, doneFn = noop) {
78380    // In order to set the duration on the media source, it's necessary to wait for all
78381    // source buffers to no longer be updating. "If the updating attribute equals true on
78382    // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and
78383    // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes).
78384    pushQueue({
78385      type: 'mediaSource',
78386      sourceUpdater: this,
78387      action: actions.duration(duration),
78388      name: 'duration',
78389      doneFn
78390    });
78391  }
78392  /**
78393   * Add a mediaSource endOfStream call to the queue
78394   *
78395   * @param {Error} [error]
78396   *        Call endOfStream with an error
78397   *
78398   * @param {Function} [doneFn]
78399   *        A function that should be called when the
78400   *        endOfStream call has finished.
78401   */
78402
78403  endOfStream(error = null, doneFn = noop) {
78404    if (typeof error !== 'string') {
78405      error = undefined;
78406    } // In order to set the duration on the media source, it's necessary to wait for all
78407    // source buffers to no longer be updating. "If the updating attribute equals true on
78408    // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and
78409    // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes).
78410
78411    pushQueue({
78412      type: 'mediaSource',
78413      sourceUpdater: this,
78414      action: actions.endOfStream(error),
78415      name: 'endOfStream',
78416      doneFn
78417    });
78418  }
78419  /**
78420   * Queue an update to remove a time range from the buffer.
78421   *
78422   * @param {number} start where to start the removal
78423   * @param {number} end where to end the removal
78424   * @param {Function} [done=noop] optional callback to be executed when the remove
78425   * operation is complete
78426   * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
78427   */
78428
78429  removeAudio(start, end, done = noop) {
78430    if (!this.audioBuffered().length || this.audioBuffered().end(0) === 0) {
78431      done();
78432      return;
78433    }
78434    pushQueue({
78435      type: 'audio',
78436      sourceUpdater: this,
78437      action: actions.remove(start, end),
78438      doneFn: done,
78439      name: 'remove'
78440    });
78441  }
78442  /**
78443   * Queue an update to remove a time range from the buffer.
78444   *
78445   * @param {number} start where to start the removal
78446   * @param {number} end where to end the removal
78447   * @param {Function} [done=noop] optional callback to be executed when the remove
78448   * operation is complete
78449   * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
78450   */
78451
78452  removeVideo(start, end, done = noop) {
78453    if (!this.videoBuffered().length || this.videoBuffered().end(0) === 0) {
78454      done();
78455      return;
78456    }
78457    pushQueue({
78458      type: 'video',
78459      sourceUpdater: this,
78460      action: actions.remove(start, end),
78461      doneFn: done,
78462      name: 'remove'
78463    });
78464  }
78465  /**
78466   * Whether the underlying sourceBuffer is updating or not
78467   *
78468   * @return {boolean} the updating status of the SourceBuffer
78469   */
78470
78471  updating() {
78472    // the audio/video source buffer is updating
78473    if (updating('audio', this) || updating('video', this)) {
78474      return true;
78475    }
78476    return false;
78477  }
78478  /**
78479   * Set/get the timestampoffset on the audio SourceBuffer
78480   *
78481   * @return {number} the timestamp offset
78482   */
78483
78484  audioTimestampOffset(offset) {
78485    if (typeof offset !== 'undefined' && this.audioBuffer &&
78486    // no point in updating if it's the same
78487    this.audioTimestampOffset_ !== offset) {
78488      pushQueue({
78489        type: 'audio',
78490        sourceUpdater: this,
78491        action: actions.timestampOffset(offset),
78492        name: 'timestampOffset'
78493      });
78494      this.audioTimestampOffset_ = offset;
78495    }
78496    return this.audioTimestampOffset_;
78497  }
78498  /**
78499   * Set/get the timestampoffset on the video SourceBuffer
78500   *
78501   * @return {number} the timestamp offset
78502   */
78503
78504  videoTimestampOffset(offset) {
78505    if (typeof offset !== 'undefined' && this.videoBuffer &&
78506    // no point in updating if it's the same
78507    this.videoTimestampOffset_ !== offset) {
78508      pushQueue({
78509        type: 'video',
78510        sourceUpdater: this,
78511        action: actions.timestampOffset(offset),
78512        name: 'timestampOffset'
78513      });
78514      this.videoTimestampOffset_ = offset;
78515    }
78516    return this.videoTimestampOffset_;
78517  }
78518  /**
78519   * Add a function to the queue that will be called
78520   * when it is its turn to run in the audio queue.
78521   *
78522   * @param {Function} callback
78523   *        The callback to queue.
78524   */
78525
78526  audioQueueCallback(callback) {
78527    if (!this.audioBuffer) {
78528      return;
78529    }
78530    pushQueue({
78531      type: 'audio',
78532      sourceUpdater: this,
78533      action: actions.callback(callback),
78534      name: 'callback'
78535    });
78536  }
78537  /**
78538   * Add a function to the queue that will be called
78539   * when it is its turn to run in the video queue.
78540   *
78541   * @param {Function} callback
78542   *        The callback to queue.
78543   */
78544
78545  videoQueueCallback(callback) {
78546    if (!this.videoBuffer) {
78547      return;
78548    }
78549    pushQueue({
78550      type: 'video',
78551      sourceUpdater: this,
78552      action: actions.callback(callback),
78553      name: 'callback'
78554    });
78555  }
78556  /**
78557   * dispose of the source updater and the underlying sourceBuffer
78558   */
78559
78560  dispose() {
78561    this.trigger('dispose');
78562    bufferTypes.forEach(type => {
78563      this.abort(type);
78564      if (this.canRemoveSourceBuffer()) {
78565        this.removeSourceBuffer(type);
78566      } else {
78567        this[`${type}QueueCallback`](() => cleanupBuffer(type, this));
78568      }
78569    });
78570    this.videoAppendQueued_ = false;
78571    this.delayedAudioAppendQueue_.length = 0;
78572    if (this.sourceopenListener_) {
78573      this.mediaSource.removeEventListener('sourceopen', this.sourceopenListener_);
78574    }
78575    this.off();
78576  }
78577}
78578const uint8ToUtf8 = uintArray => decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray)));
78579const bufferToHexString = buffer => {
78580  const uInt8Buffer = new Uint8Array(buffer);
78581  return Array.from(uInt8Buffer).map(byte => byte.toString(16).padStart(2, '0')).join('');
78582};
78583
78584/**
78585 * @file vtt-segment-loader.js
78586 */
78587const VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(char => char.charCodeAt(0)));
78588class NoVttJsError extends Error {
78589  constructor() {
78590    super('Trying to parse received VTT cues, but there is no WebVTT. Make sure vtt.js is loaded.');
78591  }
78592}
78593/**
78594 * An object that manages segment loading and appending.
78595 *
78596 * @class VTTSegmentLoader
78597 * @param {Object} options required and optional options
78598 * @extends videojs.EventTarget
78599 */
78600
78601class VTTSegmentLoader extends SegmentLoader {
78602  constructor(settings, options = {}) {
78603    super(settings, options); // SegmentLoader requires a MediaSource be specified or it will throw an error;
78604    // however, VTTSegmentLoader has no need of a media source, so delete the reference
78605
78606    this.mediaSource_ = null;
78607    this.subtitlesTrack_ = null;
78608    this.featuresNativeTextTracks_ = settings.featuresNativeTextTracks;
78609    this.loadVttJs = settings.loadVttJs; // The VTT segment will have its own time mappings. Saving VTT segment timing info in
78610    // the sync controller leads to improper behavior.
78611
78612    this.shouldSaveSegmentTimingInfo_ = false;
78613  }
78614  /**
78615   * Indicates which time ranges are buffered
78616   *
78617   * @return {TimeRange}
78618   *         TimeRange object representing the current buffered ranges
78619   */
78620
78621  buffered_() {
78622    if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues || !this.subtitlesTrack_.cues.length) {
78623      return createTimeRanges();
78624    }
78625    const cues = this.subtitlesTrack_.cues;
78626    const start = cues[0].startTime;
78627    const end = cues[cues.length - 1].startTime;
78628    return createTimeRanges([[start, end]]);
78629  }
78630  /**
78631   * Gets and sets init segment for the provided map
78632   *
78633   * @param {Object} map
78634   *        The map object representing the init segment to get or set
78635   * @param {boolean=} set
78636   *        If true, the init segment for the provided map should be saved
78637   * @return {Object}
78638   *         map object for desired init segment
78639   */
78640
78641  initSegmentForMap(map, set = false) {
78642    if (!map) {
78643      return null;
78644    }
78645    const id = initSegmentId(map);
78646    let storedMap = this.initSegments_[id];
78647    if (set && !storedMap && map.bytes) {
78648      // append WebVTT line terminators to the media initialization segment if it exists
78649      // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
78650      // requires two or more WebVTT line terminators between the WebVTT header and the
78651      // rest of the file
78652      const combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
78653      const combinedSegment = new Uint8Array(combinedByteLength);
78654      combinedSegment.set(map.bytes);
78655      combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
78656      this.initSegments_[id] = storedMap = {
78657        resolvedUri: map.resolvedUri,
78658        byterange: map.byterange,
78659        bytes: combinedSegment
78660      };
78661    }
78662    return storedMap || map;
78663  }
78664  /**
78665   * Returns true if all configuration required for loading is present, otherwise false.
78666   *
78667   * @return {boolean} True if the all configuration is ready for loading
78668   * @private
78669   */
78670
78671  couldBeginLoading_() {
78672    return this.playlist_ && this.subtitlesTrack_ && !this.paused();
78673  }
78674  /**
78675   * Once all the starting parameters have been specified, begin
78676   * operation. This method should only be invoked from the INIT
78677   * state.
78678   *
78679   * @private
78680   */
78681
78682  init_() {
78683    this.state = 'READY';
78684    this.resetEverything();
78685    return this.monitorBuffer_();
78686  }
78687  /**
78688   * Set a subtitle track on the segment loader to add subtitles to
78689   *
78690   * @param {TextTrack=} track
78691   *        The text track to add loaded subtitles to
78692   * @return {TextTrack}
78693   *        Returns the subtitles track
78694   */
78695
78696  track(track) {
78697    if (typeof track === 'undefined') {
78698      return this.subtitlesTrack_;
78699    }
78700    this.subtitlesTrack_ = track; // if we were unpaused but waiting for a sourceUpdater, start
78701    // buffering now
78702
78703    if (this.state === 'INIT' && this.couldBeginLoading_()) {
78704      this.init_();
78705    }
78706    return this.subtitlesTrack_;
78707  }
78708  /**
78709   * Remove any data in the source buffer between start and end times
78710   *
78711   * @param {number} start - the start time of the region to remove from the buffer
78712   * @param {number} end - the end time of the region to remove from the buffer
78713   */
78714
78715  remove(start, end) {
78716    removeCuesFromTrack(start, end, this.subtitlesTrack_);
78717  }
78718  /**
78719   * fill the buffer with segements unless the sourceBuffers are
78720   * currently updating
78721   *
78722   * Note: this function should only ever be called by monitorBuffer_
78723   * and never directly
78724   *
78725   * @private
78726   */
78727
78728  fillBuffer_() {
78729    // see if we need to begin loading immediately
78730    const segmentInfo = this.chooseNextRequest_();
78731    if (!segmentInfo) {
78732      return;
78733    }
78734    if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
78735      // We don't have the timestamp offset that we need to sync subtitles.
78736      // Rerun on a timestamp offset or user interaction.
78737      const checkTimestampOffset = () => {
78738        this.state = 'READY';
78739        if (!this.paused()) {
78740          // if not paused, queue a buffer check as soon as possible
78741          this.monitorBuffer_();
78742        }
78743      };
78744      this.syncController_.one('timestampoffset', checkTimestampOffset);
78745      this.state = 'WAITING_ON_TIMELINE';
78746      return;
78747    }
78748    this.loadSegment_(segmentInfo);
78749  } // never set a timestamp offset for vtt segments.
78750
78751  timestampOffsetForSegment_() {
78752    return null;
78753  }
78754  chooseNextRequest_() {
78755    return this.skipEmptySegments_(super.chooseNextRequest_());
78756  }
78757  /**
78758   * Prevents the segment loader from requesting segments we know contain no subtitles
78759   * by walking forward until we find the next segment that we don't know whether it is
78760   * empty or not.
78761   *
78762   * @param {Object} segmentInfo
78763   *        a segment info object that describes the current segment
78764   * @return {Object}
78765   *         a segment info object that describes the current segment
78766   */
78767
78768  skipEmptySegments_(segmentInfo) {
78769    while (segmentInfo && segmentInfo.segment.empty) {
78770      // stop at the last possible segmentInfo
78771      if (segmentInfo.mediaIndex + 1 >= segmentInfo.playlist.segments.length) {
78772        segmentInfo = null;
78773        break;
78774      }
78775      segmentInfo = this.generateSegmentInfo_({
78776        playlist: segmentInfo.playlist,
78777        mediaIndex: segmentInfo.mediaIndex + 1,
78778        startOfSegment: segmentInfo.startOfSegment + segmentInfo.duration,
78779        isSyncRequest: segmentInfo.isSyncRequest
78780      });
78781    }
78782    return segmentInfo;
78783  }
78784  stopForError(error) {
78785    this.error(error);
78786    this.state = 'READY';
78787    this.pause();
78788    this.trigger('error');
78789  }
78790  /**
78791   * append a decrypted segement to the SourceBuffer through a SourceUpdater
78792   *
78793   * @private
78794   */
78795
78796  segmentRequestFinished_(error, simpleSegment, result) {
78797    if (!this.subtitlesTrack_) {
78798      this.state = 'READY';
78799      return;
78800    }
78801    this.saveTransferStats_(simpleSegment.stats); // the request was aborted
78802
78803    if (!this.pendingSegment_) {
78804      this.state = 'READY';
78805      this.mediaRequestsAborted += 1;
78806      return;
78807    }
78808    if (error) {
78809      if (error.code === REQUEST_ERRORS.TIMEOUT) {
78810        this.handleTimeout_();
78811      }
78812      if (error.code === REQUEST_ERRORS.ABORTED) {
78813        this.mediaRequestsAborted += 1;
78814      } else {
78815        this.mediaRequestsErrored += 1;
78816      }
78817      this.stopForError(error);
78818      return;
78819    }
78820    const segmentInfo = this.pendingSegment_;
78821    const isMp4WebVttSegmentWithCues = result.mp4VttCues && result.mp4VttCues.length;
78822    if (isMp4WebVttSegmentWithCues) {
78823      segmentInfo.mp4VttCues = result.mp4VttCues;
78824    } // although the VTT segment loader bandwidth isn't really used, it's good to
78825    // maintain functionality between segment loaders
78826
78827    this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats);
vendor: 15,512 bytes, lines 78827-79311
78827 // if this request included a segment key, save that data in the cache
78828
78829    if (simpleSegment.key) {
78830      this.segmentKey(simpleSegment.key, true);
78831    }
78832    this.state = 'APPENDING'; // used for tests
78833
78834    this.trigger('appending');
78835    const segment = segmentInfo.segment;
78836    if (segment.map) {
78837      segment.map.bytes = simpleSegment.map.bytes;
78838    }
78839    segmentInfo.bytes = simpleSegment.bytes; // Make sure that vttjs has loaded, otherwise, load it and wait till it finished loading
78840
78841    if (typeof window__default["default"].WebVTT !== 'function' && typeof this.loadVttJs === 'function') {
78842      this.state = 'WAITING_ON_VTTJS'; // should be fine to call multiple times
78843      // script will be loaded once but multiple listeners will be added to the queue, which is expected.
78844
78845      this.loadVttJs().then(() => this.segmentRequestFinished_(error, simpleSegment, result), () => this.stopForError({
78846        message: 'Error loading vtt.js'
78847      }));
78848      return;
78849    }
78850    segment.requested = true;
78851    try {
78852      this.parseVTTCues_(segmentInfo);
78853    } catch (e) {
78854      this.stopForError({
78855        message: e.message,
78856        metadata: {
78857          errorType: videojs.Error.StreamingVttParserError,
78858          error: e
78859        }
78860      });
78861      return;
78862    }
78863    if (!isMp4WebVttSegmentWithCues) {
78864      this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_);
78865    }
78866    if (segmentInfo.cues.length) {
78867      segmentInfo.timingInfo = {
78868        start: segmentInfo.cues[0].startTime,
78869        end: segmentInfo.cues[segmentInfo.cues.length - 1].endTime
78870      };
78871    } else {
78872      segmentInfo.timingInfo = {
78873        start: segmentInfo.startOfSegment,
78874        end: segmentInfo.startOfSegment + segmentInfo.duration
78875      };
78876    }
78877    if (segmentInfo.isSyncRequest) {
78878      this.trigger('syncinfoupdate');
78879      this.pendingSegment_ = null;
78880      this.state = 'READY';
78881      return;
78882    }
78883    segmentInfo.byteLength = segmentInfo.bytes.byteLength;
78884    this.mediaSecondsLoaded += segment.duration; // Create VTTCue instances for each cue in the new segment and add them to
78885    // the subtitle track
78886
78887    segmentInfo.cues.forEach(cue => {
78888      this.subtitlesTrack_.addCue(this.featuresNativeTextTracks_ ? new window__default["default"].VTTCue(cue.startTime, cue.endTime, cue.text) : cue);
78889    }); // Remove any duplicate cues from the subtitle track. The WebVTT spec allows
78890    // cues to have identical time-intervals, but if the text is also identical
78891    // we can safely assume it is a duplicate that can be removed (ex. when a cue
78892    // "overlaps" VTT segments)
78893
78894    removeDuplicateCuesFromTrack(this.subtitlesTrack_);
78895    this.handleAppendsDone_();
78896  }
78897  handleData_(simpleSegment, result) {
78898    const isVttType = simpleSegment && simpleSegment.type === 'vtt';
78899    const isTextResult = result && result.type === 'text';
78900    const isFmp4VttSegment = isVttType && isTextResult; // handle segment data for fmp4 encapsulated webvtt
78901
78902    if (isFmp4VttSegment) {
78903      super.handleData_(simpleSegment, result);
78904    }
78905  }
78906  updateTimingInfoEnd_() {// noop
78907  }
78908  /**
78909   * Utility function for converting mp4 webvtt cue objects into VTTCues.
78910   *
78911   * @param {Object} segmentInfo with mp4 webvtt cues for parsing into VTTCue objecs
78912   */
78913
78914  parseMp4VttCues_(segmentInfo) {
78915    const timestampOffset = this.sourceUpdater_.videoTimestampOffset() === null ? this.sourceUpdater_.audioTimestampOffset() : this.sourceUpdater_.videoTimestampOffset();
78916    segmentInfo.mp4VttCues.forEach(cue => {
78917      const start = cue.start + timestampOffset;
78918      const end = cue.end + timestampOffset;
78919      const vttCue = new window__default["default"].VTTCue(start, end, cue.cueText);
78920      if (cue.settings) {
78921        cue.settings.split(' ').forEach(cueSetting => {
78922          const keyValString = cueSetting.split(':');
78923          const key = keyValString[0];
78924          const value = keyValString[1];
78925          vttCue[key] = isNaN(value) ? value : Number(value);
78926        });
78927      }
78928      segmentInfo.cues.push(vttCue);
78929    });
78930  }
78931  /**
78932   * Uses the WebVTT parser to parse the segment response
78933   *
78934   * @throws NoVttJsError
78935   *
78936   * @param {Object} segmentInfo
78937   *        a segment info object that describes the current segment
78938   * @private
78939   */
78940
78941  parseVTTCues_(segmentInfo) {
78942    let decoder;
78943    let decodeBytesToString = false;
78944    if (typeof window__default["default"].WebVTT !== 'function') {
78945      // caller is responsible for exception handling.
78946      throw new NoVttJsError();
78947    }
78948    segmentInfo.cues = [];
78949    segmentInfo.timestampmap = {
78950      MPEGTS: 0,
78951      LOCAL: 0
78952    };
78953    if (segmentInfo.mp4VttCues) {
78954      this.parseMp4VttCues_(segmentInfo);
78955      return;
78956    }
78957    if (typeof window__default["default"].TextDecoder === 'function') {
78958      decoder = new window__default["default"].TextDecoder('utf8');
78959    } else {
78960      decoder = window__default["default"].WebVTT.StringDecoder();
78961      decodeBytesToString = true;
78962    }
78963    const parser = new window__default["default"].WebVTT.Parser(window__default["default"], window__default["default"].vttjs, decoder);
78964    parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
78965    parser.ontimestampmap = map => {
78966      segmentInfo.timestampmap = map;
78967    };
78968    parser.onparsingerror = error => {
78969      videojs.log.warn('Error encountered when parsing cues: ' + error.message);
78970    };
78971    if (segmentInfo.segment.map) {
78972      let mapData = segmentInfo.segment.map.bytes;
78973      if (decodeBytesToString) {
78974        mapData = uint8ToUtf8(mapData);
78975      }
78976      parser.parse(mapData);
78977    }
78978    let segmentData = segmentInfo.bytes;
78979    if (decodeBytesToString) {
78980      segmentData = uint8ToUtf8(segmentData);
78981    }
78982    parser.parse(segmentData);
78983    parser.flush();
78984  }
78985  /**
78986   * Updates the start and end times of any cues parsed by the WebVTT parser using
78987   * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
78988   * from the SyncController
78989   *
78990   * @param {Object} segmentInfo
78991   *        a segment info object that describes the current segment
78992   * @param {Object} mappingObj
78993   *        object containing a mapping from TS to media time
78994   * @param {Object} playlist
78995   *        the playlist object containing the segment
78996   * @private
78997   */
78998
78999  updateTimeMapping_(segmentInfo, mappingObj, playlist) {
79000    const segment = segmentInfo.segment;
79001    if (!mappingObj) {
79002      // If the sync controller does not have a mapping of TS to Media Time for the
79003      // timeline, then we don't have enough information to update the cue
79004      // start/end times
79005      return;
79006    }
79007    if (!segmentInfo.cues.length) {
79008      // If there are no cues, we also do not have enough information to figure out
79009      // segment timing. Mark that the segment contains no cues so we don't re-request
79010      // an empty segment.
79011      segment.empty = true;
79012      return;
79013    }
79014    const {
79015      MPEGTS,
79016      LOCAL
79017    } = segmentInfo.timestampmap;
79018    /**
79019     * From the spec:
79020     * The MPEGTS media timestamp MUST use a 90KHz timescale,
79021     * even when non-WebVTT Media Segments use a different timescale.
79022     */
79023
79024    const mpegTsInSeconds = MPEGTS / clock.ONE_SECOND_IN_TS;
79025    const diff = mpegTsInSeconds - LOCAL + mappingObj.mapping;
79026    segmentInfo.cues.forEach(cue => {
79027      const duration = cue.endTime - cue.startTime;
79028      const startTime = this.handleRollover_(cue.startTime + diff, mappingObj.time);
79029      cue.startTime = Math.max(startTime, 0);
79030      cue.endTime = Math.max(startTime + duration, 0);
79031    });
79032    if (!playlist.syncInfo) {
79033      const firstStart = segmentInfo.cues[0].startTime;
79034      const lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
79035      playlist.syncInfo = {
79036        mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
79037        time: Math.min(firstStart, lastStart - segment.duration)
79038      };
79039    }
79040  }
79041  /**
79042   * MPEG-TS PES timestamps are limited to 2^33.
79043   * Once they reach 2^33, they roll over to 0.
79044   * mux.js handles PES timestamp rollover for the following scenarios:
79045   * [forward rollover(right)] ->
79046   *    PES timestamps monotonically increase, and once they reach 2^33, they roll over to 0
79047   * [backward rollover(left)] -->
79048   *    we seek back to position before rollover.
79049   *
79050   * According to the HLS SPEC:
79051   * When synchronizing WebVTT with PES timestamps, clients SHOULD account
79052   * for cases where the 33-bit PES timestamps have wrapped and the WebVTT
79053   * cue times have not.  When the PES timestamp wraps, the WebVTT Segment
79054   * SHOULD have a X-TIMESTAMP-MAP header that maps the current WebVTT
79055   * time to the new (low valued) PES timestamp.
79056   *
79057   * So we want to handle rollover here and align VTT Cue start/end time to the player's time.
79058   */
79059
79060  handleRollover_(value, reference) {
79061    if (reference === null) {
79062      return value;
79063    }
79064    let valueIn90khz = value * clock.ONE_SECOND_IN_TS;
79065    const referenceIn90khz = reference * clock.ONE_SECOND_IN_TS;
79066    let offset;
79067    if (referenceIn90khz < valueIn90khz) {
79068      // - 2^33
79069      offset = -8589934592;
79070    } else {
79071      // + 2^33
79072      offset = 8589934592;
79073    } // distance(value - reference) > 2^32
79074
79075    while (Math.abs(valueIn90khz - referenceIn90khz) > 4294967296) {
79076      valueIn90khz += offset;
79077    }
79078    return valueIn90khz / clock.ONE_SECOND_IN_TS;
79079  }
79080}
79081
79082/**
79083 * @file ad-cue-tags.js
79084 */
79085/**
79086 * Searches for an ad cue that overlaps with the given mediaTime
79087 *
79088 * @param {Object} track
79089 *        the track to find the cue for
79090 *
79091 * @param {number} mediaTime
79092 *        the time to find the cue at
79093 *
79094 * @return {Object|null}
79095 *         the found cue or null
79096 */
79097
79098const findAdCue = function (track, mediaTime) {
79099  const cues = track.cues;
79100  for (let i = 0; i < cues.length; i++) {
79101    const cue = cues[i];
79102    if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
79103      return cue;
79104    }
79105  }
79106  return null;
79107};
79108const updateAdCues = function (media, track, offset = 0) {
79109  if (!media.segments) {
79110    return;
79111  }
79112  let mediaTime = offset;
79113  let cue;
79114  for (let i = 0; i < media.segments.length; i++) {
79115    const segment = media.segments[i];
79116    if (!cue) {
79117      // Since the cues will span for at least the segment duration, adding a fudge
79118      // factor of half segment duration will prevent duplicate cues from being
79119      // created when timing info is not exact (e.g. cue start time initialized
79120      // at 10.006677, but next call mediaTime is 10.003332 )
79121      cue = findAdCue(track, mediaTime + segment.duration / 2);
79122    }
79123    if (cue) {
79124      if ('cueIn' in segment) {
79125        // Found a CUE-IN so end the cue
79126        cue.endTime = mediaTime;
79127        cue.adEndTime = mediaTime;
79128        mediaTime += segment.duration;
79129        cue = null;
79130        continue;
79131      }
79132      if (mediaTime < cue.endTime) {
79133        // Already processed this mediaTime for this cue
79134        mediaTime += segment.duration;
79135        continue;
79136      } // otherwise extend cue until a CUE-IN is found
79137
79138      cue.endTime += segment.duration;
79139    } else {
79140      if ('cueOut' in segment) {
79141        cue = new window__default["default"].VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut);
79142        cue.adStartTime = mediaTime; // Assumes tag format to be
79143        // #EXT-X-CUE-OUT:30
79144
79145        cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
79146        track.addCue(cue);
79147      }
79148      if ('cueOutCont' in segment) {
79149        // Entered into the middle of an ad cue
79150        // Assumes tag formate to be
79151        // #EXT-X-CUE-OUT-CONT:10/30
79152        const [adOffset, adTotal] = segment.cueOutCont.split('/').map(parseFloat);
79153        cue = new window__default["default"].VTTCue(mediaTime, mediaTime + segment.duration, '');
79154        cue.adStartTime = mediaTime - adOffset;
79155        cue.adEndTime = cue.adStartTime + adTotal;
79156        track.addCue(cue);
79157      }
79158    }
79159    mediaTime += segment.duration;
79160  }
79161};
79162class SyncInfo {
79163  /**
79164   * @param {number} start - media sequence start
79165   * @param {number} end - media sequence end
79166   * @param {number} segmentIndex - index for associated segment
79167   * @param {number|null} [partIndex] - index for associated part
79168   * @param {boolean} [appended] - appended indicator
79169   *
79170   */
79171  constructor({
79172    start,
79173    end,
79174    segmentIndex,
79175    partIndex = null,
79176    appended = false
79177  }) {
79178    this.start_ = start;
79179    this.end_ = end;
79180    this.segmentIndex_ = segmentIndex;
79181    this.partIndex_ = partIndex;
79182    this.appended_ = appended;
79183  }
79184  isInRange(targetTime) {
79185    return targetTime >= this.start && targetTime < this.end;
79186  }
79187  markAppended() {
79188    this.appended_ = true;
79189  }
79190  resetAppendedStatus() {
79191    this.appended_ = false;
79192  }
79193  get isAppended() {
79194    return this.appended_;
79195  }
79196  get start() {
79197    return this.start_;
79198  }
79199  get end() {
79200    return this.end_;
79201  }
79202  get segmentIndex() {
79203    return this.segmentIndex_;
79204  }
79205  get partIndex() {
79206    return this.partIndex_;
79207  }
79208}
79209class SyncInfoData {
79210  /**
79211   *
79212   * @param {SyncInfo} segmentSyncInfo - sync info for a given segment
79213   * @param {Array<SyncInfo>} [partsSyncInfo] - sync infos for a list of parts for a given segment
79214   */
79215  constructor(segmentSyncInfo, partsSyncInfo = []) {
79216    this.segmentSyncInfo_ = segmentSyncInfo;
79217    this.partsSyncInfo_ = partsSyncInfo;
79218  }
79219  get segmentSyncInfo() {
79220    return this.segmentSyncInfo_;
79221  }
79222  get partsSyncInfo() {
79223    return this.partsSyncInfo_;
79224  }
79225  get hasPartsSyncInfo() {
79226    return this.partsSyncInfo_.length > 0;
79227  }
79228  resetAppendStatus() {
79229    this.segmentSyncInfo_.resetAppendedStatus();
79230    this.partsSyncInfo_.forEach(partSyncInfo => partSyncInfo.resetAppendedStatus());
79231  }
79232}
79233class MediaSequenceSync {
79234  constructor() {
79235    /**
79236     * @type {Map<number, SyncInfoData>}
79237     * @protected
79238     */
79239    this.storage_ = new Map();
79240    this.diagnostics_ = '';
79241    this.isReliable_ = false;
79242    this.start_ = -Infinity;
79243    this.end_ = Infinity;
79244  }
79245  get start() {
79246    return this.start_;
79247  }
79248  get end() {
79249    return this.end_;
79250  }
79251  get diagnostics() {
79252    return this.diagnostics_;
79253  }
79254  get isReliable() {
79255    return this.isReliable_;
79256  }
79257  resetAppendedStatus() {
79258    this.storage_.forEach(syncInfoData => syncInfoData.resetAppendStatus());
79259  }
79260  /**
79261   * update sync storage
79262   *
79263   * @param {Object} playlist
79264   * @param {number} currentTime
79265   *
79266   * @return {void}
79267   */
79268
79269  update(playlist, currentTime) {
79270    const {
79271      mediaSequence,
79272      segments
79273    } = playlist;
79274    this.isReliable_ = this.isReliablePlaylist_(mediaSequence, segments);
79275    if (!this.isReliable_) {
79276      return;
79277    }
79278    return this.updateStorage_(segments, mediaSequence, this.calculateBaseTime_(mediaSequence, segments, currentTime));
79279  }
79280  /**
79281   * @param {number} targetTime
79282   * @return {SyncInfo|null}
79283   */
79284
79285  getSyncInfoForTime(targetTime) {
79286    for (const {
79287      segmentSyncInfo,
79288      partsSyncInfo
79289    } of this.storage_.values()) {
79290      // Normal segment flow:
79291      if (!partsSyncInfo.length) {
79292        if (segmentSyncInfo.isInRange(targetTime)) {
79293          return segmentSyncInfo;
79294        }
79295      } else {
79296        // Low latency flow:
79297        for (const partSyncInfo of partsSyncInfo) {
79298          if (partSyncInfo.isInRange(targetTime)) {
79299            return partSyncInfo;
79300          }
79301        }
79302      }
79303    }
79304    return null;
79305  }
79306  getSyncInfoForMediaSequence(mediaSequence) {
79307    return this.storage_.get(mediaSequence);
79308  }
79309  updateStorage_(segments, startingMediaSequence, startingTime) {
79310    const newStorage = new Map();
79311    let newDiagnostics = '\n';
vendor: 85,236 bytes, lines 79312-81705
79312    let currentStart = startingTime;
79313    let currentMediaSequence = startingMediaSequence;
79314    this.start_ = currentStart;
79315    segments.forEach((segment, segmentIndex) => {
79316      const prevSyncInfoData = this.storage_.get(currentMediaSequence);
79317      const segmentStart = currentStart;
79318      const segmentEnd = segmentStart + segment.duration;
79319      const segmentIsAppended = Boolean(prevSyncInfoData && prevSyncInfoData.segmentSyncInfo && prevSyncInfoData.segmentSyncInfo.isAppended);
79320      const segmentSyncInfo = new SyncInfo({
79321        start: segmentStart,
79322        end: segmentEnd,
79323        appended: segmentIsAppended,
79324        segmentIndex
79325      });
79326      segment.syncInfo = segmentSyncInfo;
79327      let currentPartStart = currentStart;
79328      const partsSyncInfo = (segment.parts || []).map((part, partIndex) => {
79329        const partStart = currentPartStart;
79330        const partEnd = currentPartStart + part.duration;
79331        const partIsAppended = Boolean(prevSyncInfoData && prevSyncInfoData.partsSyncInfo && prevSyncInfoData.partsSyncInfo[partIndex] && prevSyncInfoData.partsSyncInfo[partIndex].isAppended);
79332        const partSyncInfo = new SyncInfo({
79333          start: partStart,
79334          end: partEnd,
79335          appended: partIsAppended,
79336          segmentIndex,
79337          partIndex
79338        });
79339        currentPartStart = partEnd;
79340        newDiagnostics += `Media Sequence: ${currentMediaSequence}.${partIndex} | Range: ${partStart} --> ${partEnd} | Appended: ${partIsAppended}\n`;
79341        part.syncInfo = partSyncInfo;
79342        return partSyncInfo;
79343      });
79344      newStorage.set(currentMediaSequence, new SyncInfoData(segmentSyncInfo, partsSyncInfo));
79345      newDiagnostics += `${compactSegmentUrlDescription(segment.resolvedUri)} | Media Sequence: ${currentMediaSequence} | Range: ${segmentStart} --> ${segmentEnd} | Appended: ${segmentIsAppended}\n`;
79346      currentMediaSequence++;
79347      currentStart = segmentEnd;
79348    });
79349    this.end_ = currentStart;
79350    this.storage_ = newStorage;
79351    this.diagnostics_ = newDiagnostics;
79352  }
79353  calculateBaseTime_(mediaSequence, segments, fallback) {
79354    if (!this.storage_.size) {
79355      // Initial setup flow.
79356      return 0;
79357    }
79358    if (this.storage_.has(mediaSequence)) {
79359      // Normal flow.
79360      return this.storage_.get(mediaSequence).segmentSyncInfo.start;
79361    }
79362    const minMediaSequenceFromStorage = Math.min(...this.storage_.keys()); // This case captures a race condition that can occur if we switch to a new media playlist that is out of date
79363    // and still has an older Media Sequence. If this occurs, we extrapolate backwards to get the base time.
79364
79365    if (mediaSequence < minMediaSequenceFromStorage) {
79366      const mediaSequenceDiff = minMediaSequenceFromStorage - mediaSequence;
79367      let baseTime = this.storage_.get(minMediaSequenceFromStorage).segmentSyncInfo.start;
79368      for (let i = 0; i < mediaSequenceDiff; i++) {
79369        const segment = segments[i];
79370        baseTime -= segment.duration;
79371      }
79372      return baseTime;
79373    } // Fallback flow.
79374    // There is a gap between last recorded playlist and a new one received.
79375
79376    return fallback;
79377  }
79378  isReliablePlaylist_(mediaSequence, segments) {
79379    return mediaSequence !== undefined && mediaSequence !== null && Array.isArray(segments) && segments.length;
79380  }
79381}
79382class DependantMediaSequenceSync extends MediaSequenceSync {
79383  constructor(parent) {
79384    super();
79385    this.parent_ = parent;
79386  }
79387  calculateBaseTime_(mediaSequence, segments, fallback) {
79388    if (!this.storage_.size) {
79389      const info = this.parent_.getSyncInfoForMediaSequence(mediaSequence);
79390      if (info) {
79391        return info.segmentSyncInfo.start;
79392      }
79393      return 0;
79394    }
79395    return super.calculateBaseTime_(mediaSequence, segments, fallback);
79396  }
79397}
79398
79399/**
79400 * @file sync-controller.js
79401 */
79402// synchronize expired playlist segments.
79403// the max media sequence diff is 48 hours of live stream
79404// content with two second segments. Anything larger than that
79405// will likely be invalid.
79406
79407const MAX_MEDIA_SEQUENCE_DIFF_FOR_SYNC = 86400;
79408const syncPointStrategies = [
79409// Stategy "VOD": Handle the VOD-case where the sync-point is *always*
79410//                the equivalence display-time 0 === segment-index 0
79411{
79412  name: 'VOD',
79413  run: (syncController, playlist, duration, currentTimeline, currentTime) => {
79414    if (duration !== Infinity) {
79415      const syncPoint = {
79416        time: 0,
79417        segmentIndex: 0,
79418        partIndex: null
79419      };
79420      return syncPoint;
79421    }
79422    return null;
79423  }
79424}, {
79425  name: 'MediaSequence',
79426  /**
79427   * run media sequence strategy
79428   *
79429   * @param {SyncController} syncController
79430   * @param {Object} playlist
79431   * @param {number} duration
79432   * @param {number} currentTimeline
79433   * @param {number} currentTime
79434   * @param {string} type
79435   */
79436  run: (syncController, playlist, duration, currentTimeline, currentTime, type) => {
79437    const mediaSequenceSync = syncController.getMediaSequenceSync(type);
79438    if (!mediaSequenceSync) {
79439      return null;
79440    }
79441    if (!mediaSequenceSync.isReliable) {
79442      return null;
79443    }
79444    const syncInfo = mediaSequenceSync.getSyncInfoForTime(currentTime);
79445    if (!syncInfo) {
79446      return null;
79447    }
79448    return {
79449      time: syncInfo.start,
79450      partIndex: syncInfo.partIndex,
79451      segmentIndex: syncInfo.segmentIndex
79452    };
79453  }
79454},
79455// Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
79456{
79457  name: 'ProgramDateTime',
79458  run: (syncController, playlist, duration, currentTimeline, currentTime) => {
79459    if (!Object.keys(syncController.timelineToDatetimeMappings).length) {
79460      return null;
79461    }
79462    let syncPoint = null;
79463    let lastDistance = null;
79464    const partsAndSegments = getPartsAndSegments(playlist);
79465    currentTime = currentTime || 0;
79466    for (let i = 0; i < partsAndSegments.length; i++) {
79467      // start from the end and loop backwards for live
79468      // or start from the front and loop forwards for non-live
79469      const index = playlist.endList || currentTime === 0 ? i : partsAndSegments.length - (i + 1);
79470      const partAndSegment = partsAndSegments[index];
79471      const segment = partAndSegment.segment;
79472      const datetimeMapping = syncController.timelineToDatetimeMappings[segment.timeline];
79473      if (!datetimeMapping || !segment.dateTimeObject) {
79474        continue;
79475      }
79476      const segmentTime = segment.dateTimeObject.getTime() / 1000;
79477      let start = segmentTime + datetimeMapping; // take part duration into account.
79478
79479      if (segment.parts && typeof partAndSegment.partIndex === 'number') {
79480        for (let z = 0; z < partAndSegment.partIndex; z++) {
79481          start += segment.parts[z].duration;
79482        }
79483      }
79484      const distance = Math.abs(currentTime - start); // Once the distance begins to increase, or if distance is 0, we have passed
79485      // currentTime and can stop looking for better candidates
79486
79487      if (lastDistance !== null && (distance === 0 || lastDistance < distance)) {
79488        break;
79489      }
79490      lastDistance = distance;
79491      syncPoint = {
79492        time: start,
79493        segmentIndex: partAndSegment.segmentIndex,
79494        partIndex: partAndSegment.partIndex
79495      };
79496    }
79497    return syncPoint;
79498  }
79499},
79500// Stategy "Segment": We have a known time mapping for a timeline and a
79501//                    segment in the current timeline with timing data
79502{
79503  name: 'Segment',
79504  run: (syncController, playlist, duration, currentTimeline, currentTime) => {
79505    let syncPoint = null;
79506    let lastDistance = null;
79507    currentTime = currentTime || 0;
79508    const partsAndSegments = getPartsAndSegments(playlist);
79509    for (let i = 0; i < partsAndSegments.length; i++) {
79510      // start from the end and loop backwards for live
79511      // or start from the front and loop forwards for non-live
79512      const index = playlist.endList || currentTime === 0 ? i : partsAndSegments.length - (i + 1);
79513      const partAndSegment = partsAndSegments[index];
79514      const segment = partAndSegment.segment;
79515      const start = partAndSegment.part && partAndSegment.part.start || segment && segment.start;
79516      if (segment.timeline === currentTimeline && typeof start !== 'undefined') {
79517        const distance = Math.abs(currentTime - start); // Once the distance begins to increase, we have passed
79518        // currentTime and can stop looking for better candidates
79519
79520        if (lastDistance !== null && lastDistance < distance) {
79521          break;
79522        }
79523        if (!syncPoint || lastDistance === null || lastDistance >= distance) {
79524          lastDistance = distance;
79525          syncPoint = {
79526            time: start,
79527            segmentIndex: partAndSegment.segmentIndex,
79528            partIndex: partAndSegment.partIndex
79529          };
79530        }
79531      }
79532    }
79533    return syncPoint;
79534  }
79535},
79536// Stategy "Discontinuity": We have a discontinuity with a known
79537//                          display-time
79538{
79539  name: 'Discontinuity',
79540  run: (syncController, playlist, duration, currentTimeline, currentTime) => {
79541    let syncPoint = null;
79542    currentTime = currentTime || 0;
79543    if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
79544      let lastDistance = null;
79545      for (let i = 0; i < playlist.discontinuityStarts.length; i++) {
79546        const segmentIndex = playlist.discontinuityStarts[i];
79547        const discontinuity = playlist.discontinuitySequence + i + 1;
79548        const discontinuitySync = syncController.discontinuities[discontinuity];
79549        if (discontinuitySync) {
79550          const distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed
79551          // currentTime and can stop looking for better candidates
79552
79553          if (lastDistance !== null && lastDistance < distance) {
79554            break;
79555          }
79556          if (!syncPoint || lastDistance === null || lastDistance >= distance) {
79557            lastDistance = distance;
79558            syncPoint = {
79559              time: discontinuitySync.time,
79560              segmentIndex,
79561              partIndex: null
79562            };
79563          }
79564        }
79565      }
79566    }
79567    return syncPoint;
79568  }
79569},
79570// Stategy "Playlist": We have a playlist with a known mapping of
79571//                     segment index to display time
79572{
79573  name: 'Playlist',
79574  run: (syncController, playlist, duration, currentTimeline, currentTime) => {
79575    if (playlist.syncInfo) {
79576      const syncPoint = {
79577        time: playlist.syncInfo.time,
79578        segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence,
79579        partIndex: null
79580      };
79581      return syncPoint;
79582    }
79583    return null;
79584  }
79585}];
79586class SyncController extends videojs.EventTarget {
79587  constructor(options = {}) {
79588    super(); // ...for synching across variants
79589
79590    this.timelines = [];
79591    this.discontinuities = [];
79592    this.timelineToDatetimeMappings = {}; // TODO: this map should be only available for HLS. Since only HLS has MediaSequence.
79593    //  For some reason this map helps with syncing between quality switch for MPEG-DASH as well.
79594    //  Moreover if we disable this map for MPEG-DASH - quality switch will be broken.
79595    //  MPEG-DASH should have its own separate sync strategy
79596
79597    const main = new MediaSequenceSync();
79598    const audio = new DependantMediaSequenceSync(main);
79599    const vtt = new DependantMediaSequenceSync(main);
79600    this.mediaSequenceStorage_ = {
79601      main,
79602      audio,
79603      vtt
79604    };
79605    this.logger_ = logger('SyncController');
79606  }
79607  /**
79608   *
79609   * @param {string} loaderType
79610   * @return {MediaSequenceSync|null}
79611   */
79612
79613  getMediaSequenceSync(loaderType) {
79614    return this.mediaSequenceStorage_[loaderType] || null;
79615  }
79616  /**
79617   * Find a sync-point for the playlist specified
79618   *
79619   * A sync-point is defined as a known mapping from display-time to
79620   * a segment-index in the current playlist.
79621   *
79622   * @param {Playlist} playlist
79623   *        The playlist that needs a sync-point
79624   * @param {number} duration
79625   *        Duration of the MediaSource (Infinite if playing a live source)
79626   * @param {number} currentTimeline
79627   *        The last timeline from which a segment was loaded
79628   * @param {number} currentTime
79629   *        Current player's time
79630   * @param {string} type
79631   *        Segment loader type
79632   * @return {Object}
79633   *          A sync-point object
79634   */
79635
79636  getSyncPoint(playlist, duration, currentTimeline, currentTime, type) {
79637    // Always use VOD sync point for VOD
79638    if (duration !== Infinity) {
79639      const vodSyncPointStrategy = syncPointStrategies.find(({
79640        name
79641      }) => name === 'VOD');
79642      return vodSyncPointStrategy.run(this, playlist, duration);
79643    }
79644    const syncPoints = this.runStrategies_(playlist, duration, currentTimeline, currentTime, type);
79645    if (!syncPoints.length) {
79646      // Signal that we need to attempt to get a sync-point manually
79647      // by fetching a segment in the playlist and constructing
79648      // a sync-point from that information
79649      return null;
79650    } // If we have exact match just return it instead of finding the nearest distance
79651
79652    for (const syncPointInfo of syncPoints) {
79653      const {
79654        syncPoint,
79655        strategy
79656      } = syncPointInfo;
79657      const {
79658        segmentIndex,
79659        time
79660      } = syncPoint;
79661      if (segmentIndex < 0) {
79662        continue;
79663      }
79664      const selectedSegment = playlist.segments[segmentIndex];
79665      const start = time;
79666      const end = start + selectedSegment.duration;
79667      this.logger_(`Strategy: ${strategy}. Current time: ${currentTime}. selected segment: ${segmentIndex}. Time: [${start} -> ${end}]}`);
79668      if (currentTime >= start && currentTime < end) {
79669        this.logger_('Found sync point with exact match: ', syncPoint);
79670        return syncPoint;
79671      }
79672    } // Now find the sync-point that is closest to the currentTime because
79673    // that should result in the most accurate guess about which segment
79674    // to fetch
79675
79676    return this.selectSyncPoint_(syncPoints, {
79677      key: 'time',
79678      value: currentTime
79679    });
79680  }
79681  /**
79682   * Calculate the amount of time that has expired off the playlist during playback
79683   *
79684   * @param {Playlist} playlist
79685   *        Playlist object to calculate expired from
79686   * @param {number} duration
79687   *        Duration of the MediaSource (Infinity if playling a live source)
79688   * @return {number|null}
79689   *          The amount of time that has expired off the playlist during playback. Null
79690   *          if no sync-points for the playlist can be found.
79691   */
79692
79693  getExpiredTime(playlist, duration) {
79694    if (!playlist || !playlist.segments) {
79695      return null;
79696    }
79697    const syncPoints = this.runStrategies_(playlist, duration, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time
79698
79699    if (!syncPoints.length) {
79700      return null;
79701    }
79702    const syncPoint = this.selectSyncPoint_(syncPoints, {
79703      key: 'segmentIndex',
79704      value: 0
79705    }); // If the sync-point is beyond the start of the playlist, we want to subtract the
79706    // duration from index 0 to syncPoint.segmentIndex instead of adding.
79707
79708    if (syncPoint.segmentIndex > 0) {
79709      syncPoint.time *= -1;
79710    }
79711    return Math.abs(syncPoint.time + sumDurations({
79712      defaultDuration: playlist.targetDuration,
79713      durationList: playlist.segments,
79714      startIndex: syncPoint.segmentIndex,
79715      endIndex: 0
79716    }));
79717  }
79718  /**
79719   * Runs each sync-point strategy and returns a list of sync-points returned by the
79720   * strategies
79721   *
79722   * @private
79723   * @param {Playlist} playlist
79724   *        The playlist that needs a sync-point
79725   * @param {number} duration
79726   *        Duration of the MediaSource (Infinity if playing a live source)
79727   * @param {number} currentTimeline
79728   *        The last timeline from which a segment was loaded
79729   * @param {number} currentTime
79730   *        Current player's time
79731   * @param {string} type
79732   *        Segment loader type
79733   * @return {Array}
79734   *          A list of sync-point objects
79735   */
79736
79737  runStrategies_(playlist, duration, currentTimeline, currentTime, type) {
79738    const syncPoints = []; // Try to find a sync-point in by utilizing various strategies...
79739
79740    for (let i = 0; i < syncPointStrategies.length; i++) {
79741      const strategy = syncPointStrategies[i];
79742      const syncPoint = strategy.run(this, playlist, duration, currentTimeline, currentTime, type);
79743      if (syncPoint) {
79744        syncPoint.strategy = strategy.name;
79745        syncPoints.push({
79746          strategy: strategy.name,
79747          syncPoint
79748        });
79749      }
79750    }
79751    return syncPoints;
79752  }
79753  /**
79754   * Selects the sync-point nearest the specified target
79755   *
79756   * @private
79757   * @param {Array} syncPoints
79758   *        List of sync-points to select from
79759   * @param {Object} target
79760   *        Object specifying the property and value we are targeting
79761   * @param {string} target.key
79762   *        Specifies the property to target. Must be either 'time' or 'segmentIndex'
79763   * @param {number} target.value
79764   *        The value to target for the specified key.
79765   * @return {Object}
79766   *          The sync-point nearest the target
79767   */
79768
79769  selectSyncPoint_(syncPoints, target) {
79770    let bestSyncPoint = syncPoints[0].syncPoint;
79771    let bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
79772    let bestStrategy = syncPoints[0].strategy;
79773    for (let i = 1; i < syncPoints.length; i++) {
79774      const newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
79775      if (newDistance < bestDistance) {
79776        bestDistance = newDistance;
79777        bestSyncPoint = syncPoints[i].syncPoint;
79778        bestStrategy = syncPoints[i].strategy;
79779      }
79780    }
79781    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}` : '') + ']');
79782    return bestSyncPoint;
79783  }
79784  /**
79785   * Save any meta-data present on the segments when segments leave
79786   * the live window to the playlist to allow for synchronization at the
79787   * playlist level later.
79788   *
79789   * @param {Playlist} oldPlaylist - The previous active playlist
79790   * @param {Playlist} newPlaylist - The updated and most current playlist
79791   */
79792
79793  saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
79794    const mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // Ignore large media sequence gaps
79795
79796    if (mediaSequenceDiff > MAX_MEDIA_SEQUENCE_DIFF_FOR_SYNC) {
79797      videojs.log.warn(`Not saving expired segment info. Media sequence gap ${mediaSequenceDiff} is too large.`);
79798      return;
79799    } // When a segment expires from the playlist and it has a start time
79800    // save that information as a possible sync-point reference in future
79801
79802    for (let i = mediaSequenceDiff - 1; i >= 0; i--) {
79803      const lastRemovedSegment = oldPlaylist.segments[i];
79804      if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
79805        newPlaylist.syncInfo = {
79806          mediaSequence: oldPlaylist.mediaSequence + i,
79807          time: lastRemovedSegment.start
79808        };
79809        this.logger_(`playlist refresh sync: [time:${newPlaylist.syncInfo.time},` + ` mediaSequence: ${newPlaylist.syncInfo.mediaSequence}]`);
79810        this.trigger('syncinfoupdate');
79811        break;
79812      }
79813    }
79814  }
79815  /**
79816   * Save the mapping from playlist's ProgramDateTime to display. This should only happen
79817   * before segments start to load.
79818   *
79819   * @param {Playlist} playlist - The currently active playlist
79820   */
79821
79822  setDateTimeMappingForStart(playlist) {
79823    // It's possible for the playlist to be updated before playback starts, meaning time
79824    // zero is not yet set. If, during these playlist refreshes, a discontinuity is
79825    // crossed, then the old time zero mapping (for the prior timeline) would be retained
79826    // unless the mappings are cleared.
79827    this.timelineToDatetimeMappings = {};
79828    if (playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
79829      const firstSegment = playlist.segments[0];
79830      const playlistTimestamp = firstSegment.dateTimeObject.getTime() / 1000;
79831      this.timelineToDatetimeMappings[firstSegment.timeline] = -playlistTimestamp;
79832    }
79833  }
79834  /**
79835   * Calculates and saves timeline mappings, playlist sync info, and segment timing values
79836   * based on the latest timing information.
79837   *
79838   * @param {Object} options
79839   *        Options object
79840   * @param {SegmentInfo} options.segmentInfo
79841   *        The current active request information
79842   * @param {boolean} options.shouldSaveTimelineMapping
79843   *        If there's a timeline change, determines if the timeline mapping should be
79844   *        saved for timeline mapping and program date time mappings.
79845   */
79846
79847  saveSegmentTimingInfo({
79848    segmentInfo,
79849    shouldSaveTimelineMapping
79850  }) {
79851    const didCalculateSegmentTimeMapping = this.calculateSegmentTimeMapping_(segmentInfo, segmentInfo.timingInfo, shouldSaveTimelineMapping);
79852    const segment = segmentInfo.segment;
79853    if (didCalculateSegmentTimeMapping) {
79854      this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information
79855      // now with segment timing information
79856
79857      if (!segmentInfo.playlist.syncInfo) {
79858        segmentInfo.playlist.syncInfo = {
79859          mediaSequence: segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex,
79860          time: segment.start
79861        };
79862      }
79863    }
79864    const dateTime = segment.dateTimeObject;
79865    if (segment.discontinuity && shouldSaveTimelineMapping && dateTime) {
79866      this.timelineToDatetimeMappings[segment.timeline] = -(dateTime.getTime() / 1000);
79867    }
79868  }
79869  timestampOffsetForTimeline(timeline) {
79870    if (typeof this.timelines[timeline] === 'undefined') {
79871      return null;
79872    }
79873    return this.timelines[timeline].time;
79874  }
79875  mappingForTimeline(timeline) {
79876    if (typeof this.timelines[timeline] === 'undefined') {
79877      return null;
79878    }
79879    return this.timelines[timeline].mapping;
79880  }
79881  /**
79882   * Use the "media time" for a segment to generate a mapping to "display time" and
79883   * save that display time to the segment.
79884   *
79885   * @private
79886   * @param {SegmentInfo} segmentInfo
79887   *        The current active request information
79888   * @param {Object} timingInfo
79889   *        The start and end time of the current segment in "media time"
79890   * @param {boolean} shouldSaveTimelineMapping
79891   *        If there's a timeline change, determines if the timeline mapping should be
79892   *        saved in timelines.
79893   * @return {boolean}
79894   *          Returns false if segment time mapping could not be calculated
79895   */
79896
79897  calculateSegmentTimeMapping_(segmentInfo, timingInfo, shouldSaveTimelineMapping) {
79898    // TODO: remove side effects
79899    const segment = segmentInfo.segment;
79900    const part = segmentInfo.part;
79901    let mappingObj = this.timelines[segmentInfo.timeline];
79902    let start;
79903    let end;
79904    if (typeof segmentInfo.timestampOffset === 'number') {
79905      mappingObj = {
79906        time: segmentInfo.startOfSegment,
79907        mapping: segmentInfo.startOfSegment - timingInfo.start
79908      };
79909      if (shouldSaveTimelineMapping) {
79910        this.timelines[segmentInfo.timeline] = mappingObj;
79911        this.trigger('timestampoffset');
79912        this.logger_(`time mapping for timeline ${segmentInfo.timeline}: ` + `[time: ${mappingObj.time}] [mapping: ${mappingObj.mapping}]`);
79913      }
79914      start = segmentInfo.startOfSegment;
79915      end = timingInfo.end + mappingObj.mapping;
79916    } else if (mappingObj) {
79917      start = timingInfo.start + mappingObj.mapping;
79918      end = timingInfo.end + mappingObj.mapping;
79919    } else {
79920      return false;
79921    }
79922    if (part) {
79923      part.start = start;
79924      part.end = end;
79925    } // If we don't have a segment start yet or the start value we got
79926    // is less than our current segment.start value, save a new start value.
79927    // We have to do this because parts will have segment timing info saved
79928    // multiple times and we want segment start to be the earliest part start
79929    // value for that segment.
79930
79931    if (!segment.start || start < segment.start) {
79932      segment.start = start;
79933    }
79934    segment.end = end;
79935    return true;
79936  }
79937  /**
79938   * Each time we have discontinuity in the playlist, attempt to calculate the location
79939   * in display of the start of the discontinuity and save that. We also save an accuracy
79940   * value so that we save values with the most accuracy (closest to 0.)
79941   *
79942   * @private
79943   * @param {SegmentInfo} segmentInfo - The current active request information
79944   */
79945
79946  saveDiscontinuitySyncInfo_(segmentInfo) {
79947    const playlist = segmentInfo.playlist;
79948    const segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where
79949    // the start of the range and it's accuracy is 0 (greater accuracy values
79950    // mean more approximation)
79951
79952    if (segment.discontinuity) {
79953      this.discontinuities[segment.timeline] = {
79954        time: segment.start,
79955        accuracy: 0
79956      };
79957    } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
79958      // Search for future discontinuities that we can provide better timing
79959      // information for and save that information for sync purposes
79960      for (let i = 0; i < playlist.discontinuityStarts.length; i++) {
79961        const segmentIndex = playlist.discontinuityStarts[i];
79962        const discontinuity = playlist.discontinuitySequence + i + 1;
79963        const mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
79964        const accuracy = Math.abs(mediaIndexDiff);
79965        if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
79966          let time;
79967          if (mediaIndexDiff < 0) {
79968            time = segment.start - sumDurations({
79969              defaultDuration: playlist.targetDuration,
79970              durationList: playlist.segments,
79971              startIndex: segmentInfo.mediaIndex,
79972              endIndex: segmentIndex
79973            });
79974          } else {
79975            time = segment.end + sumDurations({
79976              defaultDuration: playlist.targetDuration,
79977              durationList: playlist.segments,
79978              startIndex: segmentInfo.mediaIndex + 1,
79979              endIndex: segmentIndex
79980            });
79981          }
79982          this.discontinuities[discontinuity] = {
79983            time,
79984            accuracy
79985          };
79986        }
79987      }
79988    }
79989  }
79990  dispose() {
79991    this.trigger('dispose');
79992    this.off();
79993  }
79994}
79995
79996/**
79997 * The TimelineChangeController acts as a source for segment loaders to listen for and
79998 * keep track of latest and pending timeline changes. This is useful to ensure proper
79999 * sync, as each loader may need to make a consideration for what timeline the other
80000 * loader is on before making changes which could impact the other loader's media.
80001 *
80002 * @class TimelineChangeController
80003 * @extends videojs.EventTarget
80004 */
80005
80006class TimelineChangeController extends videojs.EventTarget {
80007  constructor() {
80008    super();
80009    this.pendingTimelineChanges_ = {};
80010    this.lastTimelineChanges_ = {};
80011  }
80012  clearPendingTimelineChange(type) {
80013    this.pendingTimelineChanges_[type] = null;
80014    this.trigger('pendingtimelinechange');
80015  }
80016  pendingTimelineChange({
80017    type,
80018    from,
80019    to
80020  }) {
80021    if (typeof from === 'number' && typeof to === 'number') {
80022      this.pendingTimelineChanges_[type] = {
80023        type,
80024        from,
80025        to
80026      };
80027      this.trigger('pendingtimelinechange');
80028    }
80029    return this.pendingTimelineChanges_[type];
80030  }
80031  lastTimelineChange({
80032    type,
80033    from,
80034    to
80035  }) {
80036    if (typeof from === 'number' && typeof to === 'number') {
80037      this.lastTimelineChanges_[type] = {
80038        type,
80039        from,
80040        to
80041      };
80042      delete this.pendingTimelineChanges_[type];
80043      const metadata = {
80044        timelineChangeInfo: {
80045          from,
80046          to
80047        }
80048      };
80049      this.trigger({
80050        type: 'timelinechange',
80051        metadata
80052      });
80053    }
80054    return this.lastTimelineChanges_[type];
80055  }
80056  dispose() {
80057    this.trigger('dispose');
80058    this.pendingTimelineChanges_ = {};
80059    this.lastTimelineChanges_ = {};
80060    this.off();
80061  }
80062}
80063
80064/* rollup-plugin-worker-factory start for worker!/Users/srimron-soutter/repos/github/vjs/http-streaming/src/decrypter-worker.js */
80065const workerCode = transform(getWorkerString(function () {
80066  /**
80067   * @file stream.js
80068   */
80069
80070  /**
80071   * A lightweight readable stream implemention that handles event dispatching.
80072   *
80073   * @class Stream
80074   */
80075
80076  var Stream = /*#__PURE__*/function () {
80077    function Stream() {
80078      this.listeners = {};
80079    }
80080    /**
80081     * Add a listener for a specified event type.
80082     *
80083     * @param {string} type the event name
80084     * @param {Function} listener the callback to be invoked when an event of
80085     * the specified type occurs
80086     */
80087
80088    var _proto = Stream.prototype;
80089    _proto.on = function on(type, listener) {
80090      if (!this.listeners[type]) {
80091        this.listeners[type] = [];
80092      }
80093      this.listeners[type].push(listener);
80094    }
80095    /**
80096     * Remove a listener for a specified event type.
80097     *
80098     * @param {string} type the event name
80099     * @param {Function} listener  a function previously registered for this
80100     * type of event through `on`
80101     * @return {boolean} if we could turn it off or not
80102     */;
80103    _proto.off = function off(type, listener) {
80104      if (!this.listeners[type]) {
80105        return false;
80106      }
80107      var index = this.listeners[type].indexOf(listener); // TODO: which is better?
80108      // In Video.js we slice listener functions
80109      // on trigger so that it does not mess up the order
80110      // while we loop through.
80111      //
80112      // Here we slice on off so that the loop in trigger
80113      // can continue using it's old reference to loop without
80114      // messing up the order.
80115
80116      this.listeners[type] = this.listeners[type].slice(0);
80117      this.listeners[type].splice(index, 1);
80118      return index > -1;
80119    }
80120    /**
80121     * Trigger an event of the specified type on this stream. Any additional
80122     * arguments to this function are passed as parameters to event listeners.
80123     *
80124     * @param {string} type the event name
80125     */;
80126    _proto.trigger = function trigger(type) {
80127      var callbacks = this.listeners[type];
80128      if (!callbacks) {
80129        return;
80130      } // Slicing the arguments on every invocation of this method
80131      // can add a significant amount of overhead. Avoid the
80132      // intermediate object creation for the common case of a
80133      // single callback argument
80134
80135      if (arguments.length === 2) {
80136        var length = callbacks.length;
80137        for (var i = 0; i < length; ++i) {
80138          callbacks[i].call(this, arguments[1]);
80139        }
80140      } else {
80141        var args = Array.prototype.slice.call(arguments, 1);
80142        var _length = callbacks.length;
80143        for (var _i = 0; _i < _length; ++_i) {
80144          callbacks[_i].apply(this, args);
80145        }
80146      }
80147    }
80148    /**
80149     * Destroys the stream and cleans up.
80150     */;
80151    _proto.dispose = function dispose() {
80152      this.listeners = {};
80153    }
80154    /**
80155     * Forwards all `data` events on this stream to the destination stream. The
80156     * destination stream should provide a method `push` to receive the data
80157     * events as they arrive.
80158     *
80159     * @param {Stream} destination the stream that will receive all `data` events
80160     * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
80161     */;
80162    _proto.pipe = function pipe(destination) {
80163      this.on('data', function (data) {
80164        destination.push(data);
80165      });
80166    };
80167    return Stream;
80168  }();
80169  /*! @name pkcs7 @version 1.0.4 @license Apache-2.0 */
80170
80171  /**
80172   * Returns the subarray of a Uint8Array without PKCS#7 padding.
80173   *
80174   * @param padded {Uint8Array} unencrypted bytes that have been padded
80175   * @return {Uint8Array} the unpadded bytes
80176   * @see http://tools.ietf.org/html/rfc5652
80177   */
80178
80179  function unpad(padded) {
80180    return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
80181  }
80182  /*! @name aes-decrypter @version 4.0.2 @license Apache-2.0 */
80183
80184  /**
80185   * @file aes.js
80186   *
80187   * This file contains an adaptation of the AES decryption algorithm
80188   * from the Standford Javascript Cryptography Library. That work is
80189   * covered by the following copyright and permissions notice:
80190   *
80191   * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
80192   * All rights reserved.
80193   *
80194   * Redistribution and use in source and binary forms, with or without
80195   * modification, are permitted provided that the following conditions are
80196   * met:
80197   *
80198   * 1. Redistributions of source code must retain the above copyright
80199   *    notice, this list of conditions and the following disclaimer.
80200   *
80201   * 2. Redistributions in binary form must reproduce the above
80202   *    copyright notice, this list of conditions and the following
80203   *    disclaimer in the documentation and/or other materials provided
80204   *    with the distribution.
80205   *
80206   * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
80207   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
80208   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
80209   * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
80210   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
80211   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
80212   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
80213   * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
80214   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
80215   * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
80216   * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
80217   *
80218   * The views and conclusions contained in the software and documentation
80219   * are those of the authors and should not be interpreted as representing
80220   * official policies, either expressed or implied, of the authors.
80221   */
80222
80223  /**
80224   * Expand the S-box tables.
80225   *
80226   * @private
80227   */
80228
80229  const precompute = function () {
80230    const tables = [[[], [], [], [], []], [[], [], [], [], []]];
80231    const encTable = tables[0];
80232    const decTable = tables[1];
80233    const sbox = encTable[4];
80234    const sboxInv = decTable[4];
80235    let i;
80236    let x;
80237    let xInv;
80238    const d = [];
80239    const th = [];
80240    let x2;
80241    let x4;
80242    let x8;
80243    let s;
80244    let tEnc;
80245    let tDec; // Compute double and third tables
80246
80247    for (i = 0; i < 256; i++) {
80248      th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
80249    }
80250    for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
80251      // Compute sbox
80252      s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
80253      s = s >> 8 ^ s & 255 ^ 99;
80254      sbox[x] = s;
80255      sboxInv[s] = x; // Compute MixColumns
80256
80257      x8 = d[x4 = d[x2 = d[x]]];
80258      tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
80259      tEnc = d[s] * 0x101 ^ s * 0x1010100;
80260      for (i = 0; i < 4; i++) {
80261        encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
80262        decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
80263      }
80264    } // Compactify. Considerable speedup on Firefox.
80265
80266    for (i = 0; i < 5; i++) {
80267      encTable[i] = encTable[i].slice(0);
80268      decTable[i] = decTable[i].slice(0);
80269    }
80270    return tables;
80271  };
80272  let aesTables = null;
80273  /**
80274   * Schedule out an AES key for both encryption and decryption. This
80275   * is a low-level class. Use a cipher mode to do bulk encryption.
80276   *
80277   * @class AES
80278   * @param key {Array} The key as an array of 4, 6 or 8 words.
80279   */
80280
80281  class AES {
80282    constructor(key) {
80283      /**
80284      * The expanded S-box and inverse S-box tables. These will be computed
80285      * on the client so that we don't have to send them down the wire.
80286      *
80287      * There are two tables, _tables[0] is for encryption and
80288      * _tables[1] is for decryption.
80289      *
80290      * The first 4 sub-tables are the expanded S-box with MixColumns. The
80291      * last (_tables[01][4]) is the S-box itself.
80292      *
80293      * @private
80294      */
80295      // if we have yet to precompute the S-box tables
80296      // do so now
80297      if (!aesTables) {
80298        aesTables = precompute();
80299      } // then make a copy of that object for use
80300
80301      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()]];
80302      let i;
80303      let j;
80304      let tmp;
80305      const sbox = this._tables[0][4];
80306      const decTable = this._tables[1];
80307      const keyLen = key.length;
80308      let rcon = 1;
80309      if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
80310        throw new Error('Invalid aes key size');
80311      }
80312      const encKey = key.slice(0);
80313      const decKey = [];
80314      this._key = [encKey, decKey]; // schedule encryption keys
80315
80316      for (i = keyLen; i < 4 * keyLen + 28; i++) {
80317        tmp = encKey[i - 1]; // apply sbox
80318
80319        if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
80320          tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon
80321
80322          if (i % keyLen === 0) {
80323            tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
80324            rcon = rcon << 1 ^ (rcon >> 7) * 283;
80325          }
80326        }
80327        encKey[i] = encKey[i - keyLen] ^ tmp;
80328      } // schedule decryption keys
80329
80330      for (j = 0; i; j++, i--) {
80331        tmp = encKey[j & 3 ? i : i - 4];
80332        if (i <= 4 || j < 4) {
80333          decKey[j] = tmp;
80334        } else {
80335          decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
80336        }
80337      }
80338    }
80339    /**
80340     * Decrypt 16 bytes, specified as four 32-bit words.
80341     *
80342     * @param {number} encrypted0 the first word to decrypt
80343     * @param {number} encrypted1 the second word to decrypt
80344     * @param {number} encrypted2 the third word to decrypt
80345     * @param {number} encrypted3 the fourth word to decrypt
80346     * @param {Int32Array} out the array to write the decrypted words
80347     * into
80348     * @param {number} offset the offset into the output array to start
80349     * writing results
80350     * @return {Array} The plaintext.
80351     */
80352
80353    decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
80354      const key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data
80355
80356      let a = encrypted0 ^ key[0];
80357      let b = encrypted3 ^ key[1];
80358      let c = encrypted2 ^ key[2];
80359      let d = encrypted1 ^ key[3];
80360      let a2;
80361      let b2;
80362      let c2; // key.length === 2 ?
80363
80364      const nInnerRounds = key.length / 4 - 2;
80365      let i;
80366      let kIndex = 4;
80367      const table = this._tables[1]; // load up the tables
80368
80369      const table0 = table[0];
80370      const table1 = table[1];
80371      const table2 = table[2];
80372      const table3 = table[3];
80373      const sbox = table[4]; // Inner rounds. Cribbed from OpenSSL.
80374
80375      for (i = 0; i < nInnerRounds; i++) {
80376        a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
80377        b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
80378        c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
80379        d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
80380        kIndex += 4;
80381        a = a2;
80382        b = b2;
80383        c = c2;
80384      } // Last round.
80385
80386      for (i = 0; i < 4; i++) {
80387        out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
80388        a2 = a;
80389        a = b;
80390        b = c;
80391        c = d;
80392        d = a2;
80393      }
80394    }
80395  }
80396  /**
80397   * @file async-stream.js
80398   */
80399
80400  /**
80401   * A wrapper around the Stream class to use setTimeout
80402   * and run stream "jobs" Asynchronously
80403   *
80404   * @class AsyncStream
80405   * @extends Stream
80406   */
80407
80408  class AsyncStream extends Stream {
80409    constructor() {
80410      super(Stream);
80411      this.jobs = [];
80412      this.delay = 1;
80413      this.timeout_ = null;
80414    }
80415    /**
80416     * process an async job
80417     *
80418     * @private
80419     */
80420
80421    processJob_() {
80422      this.jobs.shift()();
80423      if (this.jobs.length) {
80424        this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
80425      } else {
80426        this.timeout_ = null;
80427      }
80428    }
80429    /**
80430     * push a job into the stream
80431     *
80432     * @param {Function} job the job to push into the stream
80433     */
80434
80435    push(job) {
80436      this.jobs.push(job);
80437      if (!this.timeout_) {
80438        this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
80439      }
80440    }
80441  }
80442  /**
80443   * @file decrypter.js
80444   *
80445   * An asynchronous implementation of AES-128 CBC decryption with
80446   * PKCS#7 padding.
80447   */
80448
80449  /**
80450   * Convert network-order (big-endian) bytes into their little-endian
80451   * representation.
80452   */
80453
80454  const ntoh = function (word) {
80455    return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
80456  };
80457  /**
80458   * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
80459   *
80460   * @param {Uint8Array} encrypted the encrypted bytes
80461   * @param {Uint32Array} key the bytes of the decryption key
80462   * @param {Uint32Array} initVector the initialization vector (IV) to
80463   * use for the first round of CBC.
80464   * @return {Uint8Array} the decrypted bytes
80465   *
80466   * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
80467   * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
80468   * @see https://tools.ietf.org/html/rfc2315
80469   */
80470
80471  const decrypt = function (encrypted, key, initVector) {
80472    // word-level access to the encrypted bytes
80473    const encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
80474    const decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output
80475
80476    const decrypted = new Uint8Array(encrypted.byteLength);
80477    const decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and
80478    // decrypted data
80479
80480    let init0;
80481    let init1;
80482    let init2;
80483    let init3;
80484    let encrypted0;
80485    let encrypted1;
80486    let encrypted2;
80487    let encrypted3; // iteration variable
80488
80489    let wordIx; // pull out the words of the IV to ensure we don't modify the
80490    // passed-in reference and easier access
80491
80492    init0 = initVector[0];
80493    init1 = initVector[1];
80494    init2 = initVector[2];
80495    init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC)
80496    // to each decrypted block
80497
80498    for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
80499      // convert big-endian (network order) words into little-endian
80500      // (javascript order)
80501      encrypted0 = ntoh(encrypted32[wordIx]);
80502      encrypted1 = ntoh(encrypted32[wordIx + 1]);
80503      encrypted2 = ntoh(encrypted32[wordIx + 2]);
80504      encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block
80505
80506      decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the
80507      // plaintext
80508
80509      decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
80510      decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
80511      decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
80512      decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round
80513
80514      init0 = encrypted0;
80515      init1 = encrypted1;
80516      init2 = encrypted2;
80517      init3 = encrypted3;
80518    }
80519    return decrypted;
80520  };
80521  /**
80522   * The `Decrypter` class that manages decryption of AES
80523   * data through `AsyncStream` objects and the `decrypt`
80524   * function
80525   *
80526   * @param {Uint8Array} encrypted the encrypted bytes
80527   * @param {Uint32Array} key the bytes of the decryption key
80528   * @param {Uint32Array} initVector the initialization vector (IV) to
80529   * @param {Function} done the function to run when done
80530   * @class Decrypter
80531   */
80532
80533  class Decrypter {
80534    constructor(encrypted, key, initVector, done) {
80535      const step = Decrypter.STEP;
80536      const encrypted32 = new Int32Array(encrypted.buffer);
80537      const decrypted = new Uint8Array(encrypted.byteLength);
80538      let i = 0;
80539      this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously
80540
80541      this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
80542      for (i = step; i < encrypted32.length; i += step) {
80543        initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
80544        this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
80545      } // invoke the done() callback when everything is finished
80546
80547      this.asyncStream_.push(function () {
80548        // remove pkcs#7 padding from the decrypted bytes
80549        done(null, unpad(decrypted));
80550      });
80551    }
80552    /**
80553     * a getter for step the maximum number of bytes to process at one time
80554     *
80555     * @return {number} the value of step 32000
80556     */
80557
80558    static get STEP() {
80559      // 4 * 8000;
80560      return 32000;
80561    }
80562    /**
80563     * @private
80564     */
80565
80566    decryptChunk_(encrypted, key, initVector, decrypted) {
80567      return function () {
80568        const bytes = decrypt(encrypted, key, initVector);
80569        decrypted.set(bytes, encrypted.byteOffset);
80570      };
80571    }
80572  }
80573  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
80574  var win;
80575  if (typeof window !== "undefined") {
80576    win = window;
80577  } else if (typeof commonjsGlobal !== "undefined") {
80578    win = commonjsGlobal;
80579  } else if (typeof self !== "undefined") {
80580    win = self;
80581  } else {
80582    win = {};
80583  }
80584  var window_1 = win;
80585  var isArrayBufferView = function isArrayBufferView(obj) {
80586    if (ArrayBuffer.isView === 'function') {
80587      return ArrayBuffer.isView(obj);
80588    }
80589    return obj && obj.buffer instanceof ArrayBuffer;
80590  };
80591  var BigInt = window_1.BigInt || Number;
80592  [BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')];
80593  (function () {
80594    var a = new Uint16Array([0xFFCC]);
80595    var b = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
80596    if (b[0] === 0xFF) {
80597      return 'big';
80598    }
80599    if (b[0] === 0xCC) {
80600      return 'little';
80601    }
80602    return 'unknown';
80603  })();
80604  /**
80605   * Creates an object for sending to a web worker modifying properties that are TypedArrays
80606   * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
80607   *
80608   * @param {Object} message
80609   *        Object of properties and values to send to the web worker
80610   * @return {Object}
80611   *         Modified message with TypedArray values expanded
80612   * @function createTransferableMessage
80613   */
80614
80615  const createTransferableMessage = function (message) {
80616    const transferable = {};
80617    Object.keys(message).forEach(key => {
80618      const value = message[key];
80619      if (isArrayBufferView(value)) {
80620        transferable[key] = {
80621          bytes: value.buffer,
80622          byteOffset: value.byteOffset,
80623          byteLength: value.byteLength
80624        };
80625      } else {
80626        transferable[key] = value;
80627      }
80628    });
80629    return transferable;
80630  };
80631  /* global self */
80632
80633  /**
80634   * Our web worker interface so that things can talk to aes-decrypter
80635   * that will be running in a web worker. the scope is passed to this by
80636   * webworkify.
80637   */
80638
80639  self.onmessage = function (event) {
80640    const data = event.data;
80641    const encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength);
80642    const key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
80643    const iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
80644    /* eslint-disable no-new, handle-callback-err */
80645
80646    new Decrypter(encrypted, key, iv, function (err, bytes) {
80647      self.postMessage(createTransferableMessage({
80648        source: data.source,
80649        decrypted: bytes
80650      }), [bytes.buffer]);
80651    });
80652    /* eslint-enable */
80653  };
80654}));
80655var Decrypter = factory(workerCode);
80656/* rollup-plugin-worker-factory end for worker!/Users/srimron-soutter/repos/github/vjs/http-streaming/src/decrypter-worker.js */
80657
80658/**
80659 * Convert the properties of an HLS track into an audioTrackKind.
80660 *
80661 * @private
80662 */
80663
80664const audioTrackKind_ = properties => {
80665  let kind = properties.default ? 'main' : 'alternative';
80666  if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
80667    kind = 'main-desc';
80668  }
80669  return kind;
80670};
80671/**
80672 * Pause provided segment loader and playlist loader if active
80673 *
80674 * @param {SegmentLoader} segmentLoader
80675 *        SegmentLoader to pause
80676 * @param {Object} mediaType
80677 *        Active media type
80678 * @function stopLoaders
80679 */
80680
80681const stopLoaders = (segmentLoader, mediaType) => {
80682  segmentLoader.abort();
80683  segmentLoader.pause();
80684  if (mediaType && mediaType.activePlaylistLoader) {
80685    mediaType.activePlaylistLoader.pause();
80686    mediaType.activePlaylistLoader = null;
80687  }
80688};
80689/**
80690 * Start loading provided segment loader and playlist loader
80691 *
80692 * @param {PlaylistLoader} playlistLoader
80693 *        PlaylistLoader to start loading
80694 * @param {Object} mediaType
80695 *        Active media type
80696 * @function startLoaders
80697 */
80698
80699const startLoaders = (playlistLoader, mediaType) => {
80700  // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
80701  // playlist loader
80702  mediaType.activePlaylistLoader = playlistLoader;
80703  playlistLoader.load();
80704};
80705/**
80706 * Returns a function to be called when the media group changes. It performs a
80707 * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
80708 * change of group is merely a rendition switch of the same content at another encoding,
80709 * rather than a change of content, such as switching audio from English to Spanish.
80710 *
80711 * @param {string} type
80712 *        MediaGroup type
80713 * @param {Object} settings
80714 *        Object containing required information for media groups
80715 * @return {Function}
80716 *         Handler for a non-destructive resync of SegmentLoader when the active media
80717 *         group changes.
80718 * @function onGroupChanged
80719 */
80720
80721const onGroupChanged = (type, settings) => () => {
80722  const {
80723    segmentLoaders: {
80724      [type]: segmentLoader,
80725      main: mainSegmentLoader
80726    },
80727    mediaTypes: {
80728      [type]: mediaType
80729    }
80730  } = settings;
80731  const activeTrack = mediaType.activeTrack();
80732  const activeGroup = mediaType.getActiveGroup();
80733  const previousActiveLoader = mediaType.activePlaylistLoader;
80734  const lastGroup = mediaType.lastGroup_; // the group did not change do nothing
80735
80736  if (activeGroup && lastGroup && activeGroup.id === lastGroup.id) {
80737    return;
80738  }
80739  mediaType.lastGroup_ = activeGroup;
80740  mediaType.lastTrack_ = activeTrack;
80741  stopLoaders(segmentLoader, mediaType);
80742  if (!activeGroup || activeGroup.isMainPlaylist) {
80743    // there is no group active or active group is a main playlist and won't change
80744    return;
80745  }
80746  if (!activeGroup.playlistLoader) {
80747    if (previousActiveLoader) {
80748      // The previous group had a playlist loader but the new active group does not
80749      // this means we are switching from demuxed to muxed audio. In this case we want to
80750      // do a destructive reset of the main segment loader and not restart the audio
80751      // loaders.
80752      mainSegmentLoader.resetEverything();
80753    }
80754    return;
80755  } // Non-destructive resync
80756
80757  segmentLoader.resyncLoader();
80758  startLoaders(activeGroup.playlistLoader, mediaType);
80759};
80760const onGroupChanging = (type, settings) => () => {
80761  const {
80762    segmentLoaders: {
80763      [type]: segmentLoader
80764    },
80765    mediaTypes: {
80766      [type]: mediaType
80767    }
80768  } = settings;
80769  mediaType.lastGroup_ = null;
80770  segmentLoader.abort();
80771  segmentLoader.pause();
80772};
80773/**
80774 * Returns a function to be called when the media track changes. It performs a
80775 * destructive reset of the SegmentLoader to ensure we start loading as close to
80776 * currentTime as possible.
80777 *
80778 * @param {string} type
80779 *        MediaGroup type
80780 * @param {Object} settings
80781 *        Object containing required information for media groups
80782 * @return {Function}
80783 *         Handler for a destructive reset of SegmentLoader when the active media
80784 *         track changes.
80785 * @function onTrackChanged
80786 */
80787
80788const onTrackChanged = (type, settings) => () => {
80789  const {
80790    mainPlaylistLoader,
80791    segmentLoaders: {
80792      [type]: segmentLoader,
80793      main: mainSegmentLoader
80794    },
80795    mediaTypes: {
80796      [type]: mediaType
80797    }
80798  } = settings;
80799  const activeTrack = mediaType.activeTrack();
80800  const activeGroup = mediaType.getActiveGroup();
80801  const previousActiveLoader = mediaType.activePlaylistLoader;
80802  const lastTrack = mediaType.lastTrack_; // track did not change, do nothing
80803
80804  if (lastTrack && activeTrack && lastTrack.id === activeTrack.id) {
80805    return;
80806  }
80807  mediaType.lastGroup_ = activeGroup;
80808  mediaType.lastTrack_ = activeTrack;
80809  stopLoaders(segmentLoader, mediaType);
80810  if (!activeGroup) {
80811    // there is no group active so we do not want to restart loaders
80812    return;
80813  }
80814  if (activeGroup.isMainPlaylist) {
80815    // track did not change, do nothing
80816    if (!activeTrack || !lastTrack || activeTrack.id === lastTrack.id) {
80817      return;
80818    }
80819    const pc = settings.vhs.playlistController_;
80820    const newPlaylist = pc.selectPlaylist(); // media will not change do nothing
80821
80822    if (pc.media() === newPlaylist) {
80823      return;
80824    }
80825    mediaType.logger_(`track change. Switching main audio from ${lastTrack.id} to ${activeTrack.id}`);
80826    mainPlaylistLoader.pause();
80827    mainSegmentLoader.resetEverything();
80828    pc.fastQualityChange_(newPlaylist);
80829    return;
80830  }
80831  if (type === 'AUDIO') {
80832    if (!activeGroup.playlistLoader) {
80833      // when switching from demuxed audio/video to muxed audio/video (noted by no
80834      // playlist loader for the audio group), we want to do a destructive reset of the
80835      // main segment loader and not restart the audio loaders
80836      mainSegmentLoader.setAudio(true); // don't have to worry about disabling the audio of the audio segment loader since
80837      // it should be stopped
80838
80839      mainSegmentLoader.resetEverything();
80840      return;
80841    } // although the segment loader is an audio segment loader, call the setAudio
80842    // function to ensure it is prepared to re-append the init segment (or handle other
80843    // config changes)
80844
80845    segmentLoader.setAudio(true);
80846    mainSegmentLoader.setAudio(false);
80847  }
80848  if (previousActiveLoader === activeGroup.playlistLoader) {
80849    // Nothing has actually changed. This can happen because track change events can fire
80850    // multiple times for a "single" change. One for enabling the new active track, and
80851    // one for disabling the track that was active
80852    startLoaders(activeGroup.playlistLoader, mediaType);
80853    return;
80854  }
80855  if (segmentLoader.track) {
80856    // For WebVTT, set the new text track in the segmentloader
80857    segmentLoader.track(activeTrack);
80858  } // destructive reset
80859
80860  segmentLoader.resetEverything();
80861  startLoaders(activeGroup.playlistLoader, mediaType);
80862};
80863const onError = {
80864  /**
80865   * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
80866   * an error.
80867   *
80868   * @param {string} type
80869   *        MediaGroup type
80870   * @param {Object} settings
80871   *        Object containing required information for media groups
80872   * @return {Function}
80873   *         Error handler. Logs warning (or error if the playlist is excluded) to
80874   *         console and switches back to default audio track.
80875   * @function onError.AUDIO
80876   */
80877  AUDIO: (type, settings) => () => {
80878    const {
80879      mediaTypes: {
80880        [type]: mediaType
80881      },
80882      excludePlaylist
80883    } = settings; // switch back to default audio track
80884
80885    const activeTrack = mediaType.activeTrack();
80886    const activeGroup = mediaType.activeGroup();
80887    const id = (activeGroup.filter(group => group.default)[0] || activeGroup[0]).id;
80888    const defaultTrack = mediaType.tracks[id];
80889    if (activeTrack === defaultTrack) {
80890      // Default track encountered an error. All we can do now is exclude the current
80891      // rendition and hope another will switch audio groups
80892      excludePlaylist({
80893        error: {
80894          message: 'Problem encountered loading the default audio track.'
80895        }
80896      });
80897      return;
80898    }
80899    videojs.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
80900    for (const trackId in mediaType.tracks) {
80901      mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
80902    }
80903    mediaType.onTrackChanged();
80904  },
80905  /**
80906   * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
80907   * an error.
80908   *
80909   * @param {string} type
80910   *        MediaGroup type
80911   * @param {Object} settings
80912   *        Object containing required information for media groups
80913   * @return {Function}
80914   *         Error handler. Logs warning to console and disables the active subtitle track
80915   * @function onError.SUBTITLES
80916   */
80917  SUBTITLES: (type, settings) => () => {
80918    const {
80919      mediaTypes: {
80920        [type]: mediaType
80921      }
80922    } = settings;
80923    videojs.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
80924    const track = mediaType.activeTrack();
80925    if (track) {
80926      track.mode = 'disabled';
80927    }
80928    mediaType.onTrackChanged();
80929  }
80930};
80931const setupListeners = {
80932  /**
80933   * Setup event listeners for audio playlist loader
80934   *
80935   * @param {string} type
80936   *        MediaGroup type
80937   * @param {PlaylistLoader|null} playlistLoader
80938   *        PlaylistLoader to register listeners on
80939   * @param {Object} settings
80940   *        Object containing required information for media groups
80941   * @function setupListeners.AUDIO
80942   */
80943  AUDIO: (type, playlistLoader, settings) => {
80944    if (!playlistLoader) {
80945      // no playlist loader means audio will be muxed with the video
80946      return;
80947    }
80948    const {
80949      tech,
80950      requestOptions,
80951      segmentLoaders: {
80952        [type]: segmentLoader
80953      }
80954    } = settings;
80955    playlistLoader.on('loadedmetadata', () => {
80956      const media = playlistLoader.media();
80957      segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload
80958      // permits, start downloading segments
80959
80960      if (!tech.paused() || media.endList && tech.preload() !== 'none') {
80961        segmentLoader.load();
80962      }
80963    });
80964    playlistLoader.on('loadedplaylist', () => {
80965      segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
80966
80967      if (!tech.paused()) {
80968        segmentLoader.load();
80969      }
80970    });
80971    playlistLoader.on('error', onError[type](type, settings));
80972  },
80973  /**
80974   * Setup event listeners for subtitle playlist loader
80975   *
80976   * @param {string} type
80977   *        MediaGroup type
80978   * @param {PlaylistLoader|null} playlistLoader
80979   *        PlaylistLoader to register listeners on
80980   * @param {Object} settings
80981   *        Object containing required information for media groups
80982   * @function setupListeners.SUBTITLES
80983   */
80984  SUBTITLES: (type, playlistLoader, settings) => {
80985    const {
80986      tech,
80987      requestOptions,
80988      segmentLoaders: {
80989        [type]: segmentLoader
80990      },
80991      mediaTypes: {
80992        [type]: mediaType
80993      }
80994    } = settings;
80995    playlistLoader.on('loadedmetadata', () => {
80996      const media = playlistLoader.media();
80997      segmentLoader.playlist(media, requestOptions);
80998      segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload
80999      // permits, start downloading segments
81000
81001      if (!tech.paused() || media.endList && tech.preload() !== 'none') {
81002        segmentLoader.load();
81003      }
81004    });
81005    playlistLoader.on('loadedplaylist', () => {
81006      segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running
81007
81008      if (!tech.paused()) {
81009        segmentLoader.load();
81010      }
81011    });
81012    playlistLoader.on('error', onError[type](type, settings));
81013  }
81014};
81015const initialize = {
81016  /**
81017   * Setup PlaylistLoaders and AudioTracks for the audio groups
81018   *
81019   * @param {string} type
81020   *        MediaGroup type
81021   * @param {Object} settings
81022   *        Object containing required information for media groups
81023   * @function initialize.AUDIO
81024   */
81025  'AUDIO': (type, settings) => {
81026    const {
81027      vhs,
81028      sourceType,
81029      segmentLoaders: {
81030        [type]: segmentLoader
81031      },
81032      requestOptions,
81033      main: {
81034        mediaGroups
81035      },
81036      mediaTypes: {
81037        [type]: {
81038          groups,
81039          tracks,
81040          logger_
81041        }
81042      },
81043      mainPlaylistLoader
81044    } = settings;
81045    const audioOnlyMain = isAudioOnly(mainPlaylistLoader.main); // force a default if we have none
81046
81047    if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
81048      mediaGroups[type] = {
81049        main: {
81050          default: {
81051            default: true
81052          }
81053        }
81054      };
81055      if (audioOnlyMain) {
81056        mediaGroups[type].main.default.playlists = mainPlaylistLoader.main.playlists;
81057      }
81058    }
81059    for (const groupId in mediaGroups[type]) {
81060      if (!groups[groupId]) {
81061        groups[groupId] = [];
81062      }
81063      for (const variantLabel in mediaGroups[type][groupId]) {
81064        let properties = mediaGroups[type][groupId][variantLabel];
81065        let playlistLoader;
81066        if (audioOnlyMain) {
81067          logger_(`AUDIO group '${groupId}' label '${variantLabel}' is a main playlist`);
81068          properties.isMainPlaylist = true;
81069          playlistLoader = null; // if vhs-json was provided as the source, and the media playlist was resolved,
81070          // use the resolved media playlist object
81071        } else if (sourceType === 'vhs-json' && properties.playlists) {
81072          playlistLoader = new PlaylistLoader(properties.playlists[0], vhs, requestOptions);
81073        } else if (properties.resolvedUri) {
81074          playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions); // TODO: dash isn't the only type with properties.playlists
81075          // should we even have properties.playlists in this check.
81076        } else if (properties.playlists && sourceType === 'dash') {
81077          playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, mainPlaylistLoader);
81078        } else {
81079          // no resolvedUri means the audio is muxed with the video when using this
81080          // audio track
81081          playlistLoader = null;
81082        }
81083        properties = merge({
81084          id: variantLabel,
81085          playlistLoader
81086        }, properties);
81087        setupListeners[type](type, properties.playlistLoader, settings);
81088        groups[groupId].push(properties);
81089        if (typeof tracks[variantLabel] === 'undefined') {
81090          const track = new videojs.AudioTrack({
81091            id: variantLabel,
81092            kind: audioTrackKind_(properties),
81093            enabled: false,
81094            language: properties.language,
81095            default: properties.default,
81096            label: variantLabel
81097          });
81098          tracks[variantLabel] = track;
81099        }
81100      }
81101    } // setup single error event handler for the segment loader
81102
81103    segmentLoader.on('error', onError[type](type, settings));
81104  },
81105  /**
81106   * Setup PlaylistLoaders and TextTracks for the subtitle groups
81107   *
81108   * @param {string} type
81109   *        MediaGroup type
81110   * @param {Object} settings
81111   *        Object containing required information for media groups
81112   * @function initialize.SUBTITLES
81113   */
81114  'SUBTITLES': (type, settings) => {
81115    const {
81116      tech,
81117      vhs,
81118      sourceType,
81119      segmentLoaders: {
81120        [type]: segmentLoader
81121      },
81122      requestOptions,
81123      main: {
81124        mediaGroups
81125      },
81126      mediaTypes: {
81127        [type]: {
81128          groups,
81129          tracks
81130        }
81131      },
81132      mainPlaylistLoader
81133    } = settings;
81134    for (const groupId in mediaGroups[type]) {
81135      if (!groups[groupId]) {
81136        groups[groupId] = [];
81137      }
81138      for (const variantLabel in mediaGroups[type][groupId]) {
81139        if (!vhs.options_.useForcedSubtitles && mediaGroups[type][groupId][variantLabel].forced) {
81140          // Subtitle playlists with the forced attribute are not selectable in Safari.
81141          // According to Apple's HLS Authoring Specification:
81142          //   If content has forced subtitles and regular subtitles in a given language,
81143          //   the regular subtitles track in that language MUST contain both the forced
81144          //   subtitles and the regular subtitles for that language.
81145          // Because of this requirement and that Safari does not add forced subtitles,
81146          // forced subtitles are skipped here to maintain consistent experience across
81147          // all platforms
81148          continue;
81149        }
81150        let properties = mediaGroups[type][groupId][variantLabel];
81151        let playlistLoader;
81152        if (sourceType === 'hls') {
81153          playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions);
81154        } else if (sourceType === 'dash') {
81155          const playlists = properties.playlists.filter(p => p.excludeUntil !== Infinity);
81156          if (!playlists.length) {
81157            return;
81158          }
81159          playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, mainPlaylistLoader);
81160        } else if (sourceType === 'vhs-json') {
81161          playlistLoader = new PlaylistLoader(
81162          // if the vhs-json object included the media playlist, use the media playlist
81163          // as provided, otherwise use the resolved URI to load the playlist
81164          properties.playlists ? properties.playlists[0] : properties.resolvedUri, vhs, requestOptions);
81165        }
81166        properties = merge({
81167          id: variantLabel,
81168          playlistLoader
81169        }, properties);
81170        setupListeners[type](type, properties.playlistLoader, settings);
81171        groups[groupId].push(properties);
81172        if (typeof tracks[variantLabel] === 'undefined') {
81173          const track = tech.addRemoteTextTrack({
81174            id: variantLabel,
81175            kind: 'subtitles',
81176            default: properties.default && properties.autoselect,
81177            language: properties.language,
81178            label: variantLabel
81179          }, false).track;
81180          tracks[variantLabel] = track;
81181        }
81182      }
81183    } // setup single error event handler for the segment loader
81184
81185    segmentLoader.on('error', onError[type](type, settings));
81186  },
81187  /**
81188   * Setup TextTracks for the closed-caption groups
81189   *
81190   * @param {String} type
81191   *        MediaGroup type
81192   * @param {Object} settings
81193   *        Object containing required information for media groups
81194   * @function initialize['CLOSED-CAPTIONS']
81195   */
81196  'CLOSED-CAPTIONS': (type, settings) => {
81197    const {
81198      tech,
81199      main: {
81200        mediaGroups
81201      },
81202      mediaTypes: {
81203        [type]: {
81204          groups,
81205          tracks
81206        }
81207      }
81208    } = settings;
81209    for (const groupId in mediaGroups[type]) {
81210      if (!groups[groupId]) {
81211        groups[groupId] = [];
81212      }
81213      for (const variantLabel in mediaGroups[type][groupId]) {
81214        const properties = mediaGroups[type][groupId][variantLabel]; // Look for either 608 (CCn) or 708 (SERVICEn) caption services
81215
81216        if (!/^(?:CC|SERVICE)/.test(properties.instreamId)) {
81217          continue;
81218        }
81219        const captionServices = tech.options_.vhs && tech.options_.vhs.captionServices || {};
81220        let newProps = {
81221          label: variantLabel,
81222          language: properties.language,
81223          instreamId: properties.instreamId,
81224          default: properties.default && properties.autoselect
81225        };
81226        if (captionServices[newProps.instreamId]) {
81227          newProps = merge(newProps, captionServices[newProps.instreamId]);
81228        }
81229        if (newProps.default === undefined) {
81230          delete newProps.default;
81231        } // No PlaylistLoader is required for Closed-Captions because the captions are
81232        // embedded within the video stream
81233
81234        groups[groupId].push(merge({
81235          id: variantLabel
81236        }, properties));
81237        if (typeof tracks[variantLabel] === 'undefined') {
81238          const track = tech.addRemoteTextTrack({
81239            id: newProps.instreamId,
81240            kind: 'captions',
81241            default: newProps.default,
81242            language: newProps.language,
81243            label: newProps.label
81244          }, false).track;
81245          tracks[variantLabel] = track;
81246        }
81247      }
81248    }
81249  }
81250};
81251const groupMatch = (list, media) => {
81252  for (let i = 0; i < list.length; i++) {
81253    if (playlistMatch(media, list[i])) {
81254      return true;
81255    }
81256    if (list[i].playlists && groupMatch(list[i].playlists, media)) {
81257      return true;
81258    }
81259  }
81260  return false;
81261};
81262/**
81263 * Returns a function used to get the active group of the provided type
81264 *
81265 * @param {string} type
81266 *        MediaGroup type
81267 * @param {Object} settings
81268 *        Object containing required information for media groups
81269 * @return {Function}
81270 *         Function that returns the active media group for the provided type. Takes an
81271 *         optional parameter {TextTrack} track. If no track is provided, a list of all
81272 *         variants in the group, otherwise the variant corresponding to the provided
81273 *         track is returned.
81274 * @function activeGroup
81275 */
81276
81277const activeGroup = (type, settings) => track => {
81278  const {
81279    mainPlaylistLoader,
81280    mediaTypes: {
81281      [type]: {
81282        groups
81283      }
81284    }
81285  } = settings;
81286  const media = mainPlaylistLoader.media();
81287  if (!media) {
81288    return null;
81289  }
81290  let variants = null; // set to variants to main media active group
81291
81292  if (media.attributes[type]) {
81293    variants = groups[media.attributes[type]];
81294  }
81295  const groupKeys = Object.keys(groups);
81296  if (!variants) {
81297    // find the mainPlaylistLoader media
81298    // that is in a media group if we are dealing
81299    // with audio only
81300    if (type === 'AUDIO' && groupKeys.length > 1 && isAudioOnly(settings.main)) {
81301      for (let i = 0; i < groupKeys.length; i++) {
81302        const groupPropertyList = groups[groupKeys[i]];
81303        if (groupMatch(groupPropertyList, media)) {
81304          variants = groupPropertyList;
81305          break;
81306        }
81307      } // use the main group if it exists
81308    } else if (groups.main) {
81309      variants = groups.main; // only one group, use that one
81310    } else if (groupKeys.length === 1) {
81311      variants = groups[groupKeys[0]];
81312    }
81313  }
81314  if (typeof track === 'undefined') {
81315    return variants;
81316  }
81317  if (track === null || !variants) {
81318    // An active track was specified so a corresponding group is expected. track === null
81319    // means no track is currently active so there is no corresponding group
81320    return null;
81321  }
81322  return variants.filter(props => props.id === track.id)[0] || null;
81323};
81324const activeTrack = {
81325  /**
81326   * Returns a function used to get the active track of type provided
81327   *
81328   * @param {string} type
81329   *        MediaGroup type
81330   * @param {Object} settings
81331   *        Object containing required information for media groups
81332   * @return {Function}
81333   *         Function that returns the active media track for the provided type. Returns
81334   *         null if no track is active
81335   * @function activeTrack.AUDIO
81336   */
81337  AUDIO: (type, settings) => () => {
81338    const {
81339      mediaTypes: {
81340        [type]: {
81341          tracks
81342        }
81343      }
81344    } = settings;
81345    for (const id in tracks) {
81346      if (tracks[id].enabled) {
81347        return tracks[id];
81348      }
81349    }
81350    return null;
81351  },
81352  /**
81353   * Returns a function used to get the active track of type provided
81354   *
81355   * @param {string} type
81356   *        MediaGroup type
81357   * @param {Object} settings
81358   *        Object containing required information for media groups
81359   * @return {Function}
81360   *         Function that returns the active media track for the provided type. Returns
81361   *         null if no track is active
81362   * @function activeTrack.SUBTITLES
81363   */
81364  SUBTITLES: (type, settings) => () => {
81365    const {
81366      mediaTypes: {
81367        [type]: {
81368          tracks
81369        }
81370      }
81371    } = settings;
81372    for (const id in tracks) {
81373      if (tracks[id].mode === 'showing' || tracks[id].mode === 'hidden') {
81374        return tracks[id];
81375      }
81376    }
81377    return null;
81378  }
81379};
81380const getActiveGroup = (type, {
81381  mediaTypes
81382}) => () => {
81383  const activeTrack_ = mediaTypes[type].activeTrack();
81384  if (!activeTrack_) {
81385    return null;
81386  }
81387  return mediaTypes[type].activeGroup(activeTrack_);
81388};
81389/**
81390 * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
81391 * Closed-Captions) specified in the main manifest.
81392 *
81393 * @param {Object} settings
81394 *        Object containing required information for setting up the media groups
81395 * @param {Tech} settings.tech
81396 *        The tech of the player
81397 * @param {Object} settings.requestOptions
81398 *        XHR request options used by the segment loaders
81399 * @param {PlaylistLoader} settings.mainPlaylistLoader
81400 *        PlaylistLoader for the main source
81401 * @param {VhsHandler} settings.vhs
81402 *        VHS SourceHandler
81403 * @param {Object} settings.main
81404 *        The parsed main manifest
81405 * @param {Object} settings.mediaTypes
81406 *        Object to store the loaders, tracks, and utility methods for each media type
81407 * @param {Function} settings.excludePlaylist
81408 *        Excludes the current rendition and forces a rendition switch.
81409 * @function setupMediaGroups
81410 */
81411
81412const setupMediaGroups = settings => {
81413  ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(type => {
81414    initialize[type](type, settings);
81415  });
81416  const {
81417    mediaTypes,
81418    mainPlaylistLoader,
81419    tech,
81420    vhs,
81421    segmentLoaders: {
81422      ['AUDIO']: audioSegmentLoader,
81423      main: mainSegmentLoader
81424    }
81425  } = settings; // setup active group and track getters and change event handlers
81426
81427  ['AUDIO', 'SUBTITLES'].forEach(type => {
81428    mediaTypes[type].activeGroup = activeGroup(type, settings);
81429    mediaTypes[type].activeTrack = activeTrack[type](type, settings);
81430    mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
81431    mediaTypes[type].onGroupChanging = onGroupChanging(type, settings);
81432    mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
81433    mediaTypes[type].getActiveGroup = getActiveGroup(type, settings);
81434  }); // DO NOT enable the default subtitle or caption track.
81435  // DO enable the default audio track
81436
81437  const audioGroup = mediaTypes.AUDIO.activeGroup();
81438  if (audioGroup) {
81439    const groupId = (audioGroup.filter(group => group.default)[0] || audioGroup[0]).id;
81440    mediaTypes.AUDIO.tracks[groupId].enabled = true;
81441    mediaTypes.AUDIO.onGroupChanged();
81442    mediaTypes.AUDIO.onTrackChanged();
81443    const activeAudioGroup = mediaTypes.AUDIO.getActiveGroup(); // a similar check for handling setAudio on each loader is run again each time the
81444    // track is changed, but needs to be handled here since the track may not be considered
81445    // changed on the first call to onTrackChanged
81446
81447    if (!activeAudioGroup.playlistLoader) {
81448      // either audio is muxed with video or the stream is audio only
81449      mainSegmentLoader.setAudio(true);
81450    } else {
81451      // audio is demuxed
81452      mainSegmentLoader.setAudio(false);
81453      audioSegmentLoader.setAudio(true);
81454    }
81455  }
81456  mainPlaylistLoader.on('mediachange', () => {
81457    ['AUDIO', 'SUBTITLES'].forEach(type => mediaTypes[type].onGroupChanged());
81458  });
81459  mainPlaylistLoader.on('mediachanging', () => {
81460    ['AUDIO', 'SUBTITLES'].forEach(type => mediaTypes[type].onGroupChanging());
81461  }); // custom audio track change event handler for usage event
81462
81463  const onAudioTrackChanged = () => {
81464    mediaTypes.AUDIO.onTrackChanged();
81465    tech.trigger({
81466      type: 'usage',
81467      name: 'vhs-audio-change'
81468    });
81469  };
81470  tech.audioTracks().addEventListener('change', onAudioTrackChanged);
81471  tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
81472  vhs.on('dispose', () => {
81473    tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
81474    tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
81475  }); // clear existing audio tracks and add the ones we just created
81476
81477  tech.clearTracks('audio');
81478  for (const id in mediaTypes.AUDIO.tracks) {
81479    tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
81480  }
81481};
81482/**
81483 * Creates skeleton object used to store the loaders, tracks, and utility methods for each
81484 * media type
81485 *
81486 * @return {Object}
81487 *         Object to store the loaders, tracks, and utility methods for each media type
81488 * @function createMediaTypes
81489 */
81490
81491const createMediaTypes = () => {
81492  const mediaTypes = {};
81493  ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(type => {
81494    mediaTypes[type] = {
81495      groups: {},
81496      tracks: {},
81497      activePlaylistLoader: null,
81498      activeGroup: noop,
81499      activeTrack: noop,
81500      getActiveGroup: noop,
81501      onGroupChanged: noop,
81502      onTrackChanged: noop,
81503      lastTrack_: null,
81504      logger_: logger(`MediaGroups[${type}]`)
81505    };
81506  });
81507  return mediaTypes;
81508};
81509
81510/**
81511 * A utility class for setting properties and maintaining the state of the content steering manifest.
81512 *
81513 * Content Steering manifest format:
81514 * VERSION: number (required) currently only version 1 is supported.
81515 * TTL: number in seconds (optional) until the next content steering manifest reload.
81516 * RELOAD-URI: string (optional) uri to fetch the next content steering manifest.
81517 * SERVICE-LOCATION-PRIORITY or PATHWAY-PRIORITY a non empty array of unique string values.
81518 * PATHWAY-CLONES: array (optional) (HLS only) pathway clone objects to copy from other playlists.
81519 */
81520
81521class SteeringManifest {
81522  constructor() {
81523    this.priority_ = [];
81524    this.pathwayClones_ = new Map();
81525  }
81526  set version(number) {
81527    // Only version 1 is currently supported for both DASH and HLS.
81528    if (number === 1) {
81529      this.version_ = number;
81530    }
81531  }
81532  set ttl(seconds) {
81533    // TTL = time-to-live, default = 300 seconds.
81534    this.ttl_ = seconds || 300;
81535  }
81536  set reloadUri(uri) {
81537    if (uri) {
81538      // reload URI can be relative to the previous reloadUri.
81539      this.reloadUri_ = resolveUrl(this.reloadUri_, uri);
81540    }
81541  }
81542  set priority(array) {
81543    // priority must be non-empty and unique values.
81544    if (array && array.length) {
81545      this.priority_ = array;
81546    }
81547  }
81548  set pathwayClones(array) {
81549    // pathwayClones must be non-empty.
81550    if (array && array.length) {
81551      this.pathwayClones_ = new Map(array.map(clone => [clone.ID, clone]));
81552    }
81553  }
81554  get version() {
81555    return this.version_;
81556  }
81557  get ttl() {
81558    return this.ttl_;
81559  }
81560  get reloadUri() {
81561    return this.reloadUri_;
81562  }
81563  get priority() {
81564    return this.priority_;
81565  }
81566  get pathwayClones() {
81567    return this.pathwayClones_;
81568  }
81569}
81570/**
81571 * This class represents a content steering manifest and associated state. See both HLS and DASH specifications.
81572 * HLS: https://developer.apple.com/streaming/HLSContentSteeringSpecification.pdf and
81573 * https://datatracker.ietf.org/doc/draft-pantos-hls-rfc8216bis/ section 4.4.6.6.
81574 * DASH: https://dashif.org/docs/DASH-IF-CTS-00XX-Content-Steering-Community-Review.pdf
81575 *
81576 * @param {function} xhr for making a network request from the browser.
81577 * @param {function} bandwidth for fetching the current bandwidth from the main segment loader.
81578 */
81579
81580class ContentSteeringController extends videojs.EventTarget {
81581  constructor(xhr, bandwidth) {
81582    super();
81583    this.currentPathway = null;
81584    this.defaultPathway = null;
81585    this.queryBeforeStart = false;
81586    this.availablePathways_ = new Set();
81587    this.steeringManifest = new SteeringManifest();
81588    this.proxyServerUrl_ = null;
81589    this.manifestType_ = null;
81590    this.ttlTimeout_ = null;
81591    this.request_ = null;
81592    this.currentPathwayClones = new Map();
81593    this.nextPathwayClones = new Map();
81594    this.excludedSteeringManifestURLs = new Set();
81595    this.logger_ = logger('Content Steering');
81596    this.xhr_ = xhr;
81597    this.getBandwidth_ = bandwidth;
81598  }
81599  /**
81600   * Assigns the content steering tag properties to the steering controller
81601   *
81602   * @param {string} baseUrl the baseURL from the main manifest for resolving the steering manifest url
81603   * @param {Object} steeringTag the content steering tag from the main manifest
81604   */
81605
81606  assignTagProperties(baseUrl, steeringTag) {
81607    this.manifestType_ = steeringTag.serverUri ? 'HLS' : 'DASH'; // serverUri is HLS serverURL is DASH
81608
81609    const steeringUri = steeringTag.serverUri || steeringTag.serverURL;
81610    if (!steeringUri) {
81611      this.logger_(`steering manifest URL is ${steeringUri}, cannot request steering manifest.`);
81612      this.trigger('error');
81613      return;
81614    } // Content steering manifests can be encoded as a data URI. We can decode, parse and return early if that's the case.
81615
81616    if (steeringUri.startsWith('data:')) {
81617      this.decodeDataUriManifest_(steeringUri.substring(steeringUri.indexOf(',') + 1));
81618      return;
81619    } // reloadUri is the resolution of the main manifest URL and steering URL.
81620
81621    this.steeringManifest.reloadUri = resolveUrl(baseUrl, steeringUri); // pathwayId is HLS defaultServiceLocation is DASH
81622
81623    this.defaultPathway = steeringTag.pathwayId || steeringTag.defaultServiceLocation; // currently only DASH supports the following properties on <ContentSteering> tags.
81624
81625    this.queryBeforeStart = steeringTag.queryBeforeStart;
81626    this.proxyServerUrl_ = steeringTag.proxyServerURL; // trigger a steering event if we have a pathway from the content steering tag.
81627    // this tells VHS which segment pathway to start with.
81628    // If queryBeforeStart is true we need to wait for the steering manifest response.
81629
81630    if (this.defaultPathway && !this.queryBeforeStart) {
81631      this.trigger('content-steering');
81632    }
81633  }
81634  /**
81635   * Requests the content steering manifest and parse the response. This should only be called after
81636   * assignTagProperties was called with a content steering tag.
81637   *
81638   * @param {string} initialUri The optional uri to make the request with.
81639   *    If set, the request should be made with exactly what is passed in this variable.
81640   *    This scenario should only happen once on initalization.
81641   */
81642
81643  requestSteeringManifest(initial) {
81644    const reloadUri = this.steeringManifest.reloadUri;
81645    if (!reloadUri) {
81646      return;
81647    } // We currently don't support passing MPD query parameters directly to the content steering URL as this requires
81648    // ExtUrlQueryInfo tag support. See the DASH content steering spec section 8.1.
81649    // This request URI accounts for manifest URIs that have been excluded.
81650
81651    const uri = initial ? reloadUri : this.getRequestURI(reloadUri); // If there are no valid manifest URIs, we should stop content steering.
81652
81653    if (!uri) {
81654      this.logger_('No valid content steering manifest URIs. Stopping content steering.');
81655      this.trigger('error');
81656      this.dispose();
81657      return;
81658    }
81659    const metadata = {
81660      contentSteeringInfo: {
81661        uri
81662      }
81663    };
81664    this.trigger({
81665      type: 'contentsteeringloadstart',
81666      metadata
81667    });
81668    this.request_ = this.xhr_({
81669      uri,
81670      requestType: 'content-steering-manifest'
81671    }, (error, errorInfo) => {
81672      if (error) {
81673        // If the client receives HTTP 410 Gone in response to a manifest request,
81674        // it MUST NOT issue another request for that URI for the remainder of the
81675        // playback session. It MAY continue to use the most-recently obtained set
81676        // of Pathways.
81677        if (errorInfo.status === 410) {
81678          this.logger_(`manifest request 410 ${error}.`);
81679          this.logger_(`There will be no more content steering requests to ${uri} this session.`);
81680          this.excludedSteeringManifestURLs.add(uri);
81681          return;
81682        } // If the client receives HTTP 429 Too Many Requests with a Retry-After
81683        // header in response to a manifest request, it SHOULD wait until the time
81684        // specified by the Retry-After header to reissue the request.
81685
81686        if (errorInfo.status === 429) {
81687          const retrySeconds = errorInfo.responseHeaders['retry-after'];
81688          this.logger_(`manifest request 429 ${error}.`);
81689          this.logger_(`content steering will retry in ${retrySeconds} seconds.`);
81690          this.startTTLTimeout_(parseInt(retrySeconds, 10));
81691          return;
81692        } // If the Steering Manifest cannot be loaded and parsed correctly, the
81693        // client SHOULD continue to use the previous values and attempt to reload
81694        // it after waiting for the previously-specified TTL (or 5 minutes if
81695        // none).
81696
81697        this.logger_(`manifest failed to load ${error}.`);
81698        this.startTTLTimeout_();
81699        return;
81700      }
81701      this.trigger({
81702        type: 'contentsteeringloadcomplete',
81703        metadata
81704      });
81705      let steeringManifestJson;
vendor: 8,938 bytes, lines 81706-81961
81706      try {
81707        steeringManifestJson = JSON.parse(this.request_.responseText);
81708      } catch (parseError) {
81709        const errorMetadata = {
81710          errorType: videojs.Error.StreamingContentSteeringParserError,
81711          error: parseError
81712        };
81713        this.trigger({
81714          type: 'error',
81715          metadata: errorMetadata
81716        });
81717      }
81718      this.assignSteeringProperties_(steeringManifestJson);
81719      const parsedMetadata = {
81720        contentSteeringInfo: metadata.contentSteeringInfo,
81721        contentSteeringManifest: {
81722          version: this.steeringManifest.version,
81723          reloadUri: this.steeringManifest.reloadUri,
81724          priority: this.steeringManifest.priority
81725        }
81726      };
81727      this.trigger({
81728        type: 'contentsteeringparsed',
81729        metadata: parsedMetadata
81730      });
81731      this.startTTLTimeout_();
81732    });
81733  }
81734  /**
81735   * Set the proxy server URL and add the steering manifest url as a URI encoded parameter.
81736   *
81737   * @param {string} steeringUrl the steering manifest url
81738   * @return the steering manifest url to a proxy server with all parameters set
81739   */
81740
81741  setProxyServerUrl_(steeringUrl) {
81742    const steeringUrlObject = new window__default["default"].URL(steeringUrl);
81743    const proxyServerUrlObject = new window__default["default"].URL(this.proxyServerUrl_);
81744    proxyServerUrlObject.searchParams.set('url', encodeURI(steeringUrlObject.toString()));
81745    return this.setSteeringParams_(proxyServerUrlObject.toString());
81746  }
81747  /**
81748   * Decodes and parses the data uri encoded steering manifest
81749   *
81750   * @param {string} dataUri the data uri to be decoded and parsed.
81751   */
81752
81753  decodeDataUriManifest_(dataUri) {
81754    const steeringManifestJson = JSON.parse(window__default["default"].atob(dataUri));
81755    this.assignSteeringProperties_(steeringManifestJson);
81756  }
81757  /**
81758   * Set the HLS or DASH content steering manifest request query parameters. For example:
81759   * _HLS_pathway="<CURRENT-PATHWAY-ID>" and _HLS_throughput=<THROUGHPUT>
81760   * _DASH_pathway and _DASH_throughput
81761   *
81762   * @param {string} uri to add content steering server parameters to.
81763   * @return a new uri as a string with the added steering query parameters.
81764   */
81765
81766  setSteeringParams_(url) {
81767    const urlObject = new window__default["default"].URL(url);
81768    const path = this.getPathway();
81769    const networkThroughput = this.getBandwidth_();
81770    if (path) {
81771      const pathwayKey = `_${this.manifestType_}_pathway`;
81772      urlObject.searchParams.set(pathwayKey, path);
81773    }
81774    if (networkThroughput) {
81775      const throughputKey = `_${this.manifestType_}_throughput`;
81776      urlObject.searchParams.set(throughputKey, networkThroughput);
81777    }
81778    return urlObject.toString();
81779  }
81780  /**
81781   * Assigns the current steering manifest properties and to the SteeringManifest object
81782   *
81783   * @param {Object} steeringJson the raw JSON steering manifest
81784   */
81785
81786  assignSteeringProperties_(steeringJson) {
81787    this.steeringManifest.version = steeringJson.VERSION;
81788    if (!this.steeringManifest.version) {
81789      this.logger_(`manifest version is ${steeringJson.VERSION}, which is not supported.`);
81790      this.trigger('error');
81791      return;
81792    }
81793    this.steeringManifest.ttl = steeringJson.TTL;
81794    this.steeringManifest.reloadUri = steeringJson['RELOAD-URI']; // HLS = PATHWAY-PRIORITY required. DASH = SERVICE-LOCATION-PRIORITY optional
81795
81796    this.steeringManifest.priority = steeringJson['PATHWAY-PRIORITY'] || steeringJson['SERVICE-LOCATION-PRIORITY']; // Pathway clones to be created/updated in HLS.
81797    // See section 7.2 https://datatracker.ietf.org/doc/draft-pantos-hls-rfc8216bis/
81798
81799    this.steeringManifest.pathwayClones = steeringJson['PATHWAY-CLONES'];
81800    this.nextPathwayClones = this.steeringManifest.pathwayClones; // 1. apply first pathway from the array.
81801    // 2. if first pathway doesn't exist in manifest, try next pathway.
81802    //    a. if all pathways are exhausted, ignore the steering manifest priority.
81803    // 3. if segments fail from an established pathway, try all variants/renditions, then exclude the failed pathway.
81804    //    a. exclude a pathway for a minimum of the last TTL duration. Meaning, from the next steering response,
81805    //       the excluded pathway will be ignored.
81806    //       See excludePathway usage in excludePlaylist().
81807    // If there are no available pathways, we need to stop content steering.
81808
81809    if (!this.availablePathways_.size) {
81810      this.logger_('There are no available pathways for content steering. Ending content steering.');
81811      this.trigger('error');
81812      this.dispose();
81813    }
81814    const chooseNextPathway = pathwaysByPriority => {
81815      for (const path of pathwaysByPriority) {
81816        if (this.availablePathways_.has(path)) {
81817          return path;
81818        }
81819      } // If no pathway matches, ignore the manifest and choose the first available.
81820
81821      return [...this.availablePathways_][0];
81822    };
81823    const nextPathway = chooseNextPathway(this.steeringManifest.priority);
81824    if (this.currentPathway !== nextPathway) {
81825      this.currentPathway = nextPathway;
81826      this.trigger('content-steering');
81827    }
81828  }
81829  /**
81830   * Returns the pathway to use for steering decisions
81831   *
81832   * @return {string} returns the current pathway or the default
81833   */
81834
81835  getPathway() {
81836    return this.currentPathway || this.defaultPathway;
81837  }
81838  /**
81839   * Chooses the manifest request URI based on proxy URIs and server URLs.
81840   * Also accounts for exclusion on certain manifest URIs.
81841   *
81842   * @param {string} reloadUri the base uri before parameters
81843   *
81844   * @return {string} the final URI for the request to the manifest server.
81845   */
81846
81847  getRequestURI(reloadUri) {
81848    if (!reloadUri) {
81849      return null;
81850    }
81851    const isExcluded = uri => this.excludedSteeringManifestURLs.has(uri);
81852    if (this.proxyServerUrl_) {
81853      const proxyURI = this.setProxyServerUrl_(reloadUri);
81854      if (!isExcluded(proxyURI)) {
81855        return proxyURI;
81856      }
81857    }
81858    const steeringURI = this.setSteeringParams_(reloadUri);
81859    if (!isExcluded(steeringURI)) {
81860      return steeringURI;
81861    } // Return nothing if all valid manifest URIs are excluded.
81862
81863    return null;
81864  }
81865  /**
81866   * Start the timeout for re-requesting the steering manifest at the TTL interval.
81867   *
81868   * @param {number} ttl time in seconds of the timeout. Defaults to the
81869   *        ttl interval in the steering manifest
81870   */
81871
81872  startTTLTimeout_(ttl = this.steeringManifest.ttl) {
81873    // 300 (5 minutes) is the default value.
81874    const ttlMS = ttl * 1000;
81875    this.ttlTimeout_ = window__default["default"].setTimeout(() => {
81876      this.requestSteeringManifest();
81877    }, ttlMS);
81878  }
81879  /**
81880   * Clear the TTL timeout if necessary.
81881   */
81882
81883  clearTTLTimeout_() {
81884    window__default["default"].clearTimeout(this.ttlTimeout_);
81885    this.ttlTimeout_ = null;
81886  }
81887  /**
81888   * aborts any current steering xhr and sets the current request object to null
81889   */
81890
81891  abort() {
81892    if (this.request_) {
81893      this.request_.abort();
81894    }
81895    this.request_ = null;
81896  }
81897  /**
81898   * aborts steering requests clears the ttl timeout and resets all properties.
81899   */
81900
81901  dispose() {
81902    this.off('content-steering');
81903    this.off('error');
81904    this.abort();
81905    this.clearTTLTimeout_();
81906    this.currentPathway = null;
81907    this.defaultPathway = null;
81908    this.queryBeforeStart = null;
81909    this.proxyServerUrl_ = null;
81910    this.manifestType_ = null;
81911    this.ttlTimeout_ = null;
81912    this.request_ = null;
81913    this.excludedSteeringManifestURLs = new Set();
81914    this.availablePathways_ = new Set();
81915    this.steeringManifest = new SteeringManifest();
81916  }
81917  /**
81918   * adds a pathway to the available pathways set
81919   *
81920   * @param {string} pathway the pathway string to add
81921   */
81922
81923  addAvailablePathway(pathway) {
81924    if (pathway) {
81925      this.availablePathways_.add(pathway);
81926    }
81927  }
81928  /**
81929   * Clears all pathways from the available pathways set
81930   */
81931
81932  clearAvailablePathways() {
81933    this.availablePathways_.clear();
81934  }
81935  /**
81936   * Removes a pathway from the available pathways set.
81937   */
81938
81939  excludePathway(pathway) {
81940    return this.availablePathways_.delete(pathway);
81941  }
81942  /**
81943   * Checks the refreshed DASH manifest content steering tag for changes.
81944   *
81945   * @param {string} baseURL new steering tag on DASH manifest refresh
81946   * @param {Object} newTag the new tag to check for changes
81947   * @return a true or false whether the new tag has different values
81948   */
81949
81950  didDASHTagChange(baseURL, newTag) {
81951    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_);
81952  }
81953  getAvailablePathways() {
81954    return this.availablePathways_;
81955  }
81956}
81957const debounce = (callback, wait) => {
81958  let timeoutId = null;
81959  return (...args) => {
81960    clearTimeout(timeoutId);
81961    timeoutId = setTimeout(() => {
vendor: 5,642 bytes, lines 81962-82102
81962      callback.apply(null, args);
81963    }, wait);
81964  };
81965};
81966const ABORT_EARLY_EXCLUSION_SECONDS = 10;
81967let Vhs$1; // SegmentLoader stats that need to have each loader's
81968// values summed to calculate the final value
81969
81970const loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred', 'mediaAppends'];
81971const sumLoaderStat = function (stat) {
81972  return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
81973};
81974const shouldSwitchToMedia = function ({
81975  currentPlaylist,
81976  buffered,
81977  currentTime,
81978  nextPlaylist,
81979  bufferLowWaterLine,
81980  bufferHighWaterLine,
81981  duration,
81982  bufferBasedABR,
81983  log
81984}) {
81985  // we have no other playlist to switch to
81986  if (!nextPlaylist) {
81987    videojs.log.warn('We received no playlist to switch to. Please check your stream.');
81988    return false;
81989  }
81990  const sharedLogLine = `allowing switch ${currentPlaylist && currentPlaylist.id || 'null'} -> ${nextPlaylist.id}`;
81991  if (!currentPlaylist) {
81992    log(`${sharedLogLine} as current playlist is not set`);
81993    return true;
81994  } // no need to switch if playlist is the same
81995
81996  if (nextPlaylist.id === currentPlaylist.id) {
81997    return false;
81998  } // determine if current time is in a buffered range.
81999
82000  const isBuffered = Boolean(findRange(buffered, currentTime).length); // If the playlist is live, then we want to not take low water line into account.
82001  // This is because in LIVE, the player plays 3 segments from the end of the
82002  // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
82003  // in those segments, a viewer will never experience a rendition upswitch.
82004
82005  if (!currentPlaylist.endList) {
82006    // For LLHLS live streams, don't switch renditions before playback has started, as it almost
82007    // doubles the time to first playback.
82008    if (!isBuffered && typeof currentPlaylist.partTargetDuration === 'number') {
82009      log(`not ${sharedLogLine} as current playlist is live llhls, but currentTime isn't in buffered.`);
82010      return false;
82011    }
82012    log(`${sharedLogLine} as current playlist is live`);
82013    return true;
82014  }
82015  const forwardBuffer = timeAheadOf(buffered, currentTime);
82016  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
82017  // duration is below the max potential low water line
82018
82019  if (duration < maxBufferLowWaterLine) {
82020    log(`${sharedLogLine} as duration < max low water line (${duration} < ${maxBufferLowWaterLine})`);
82021    return true;
82022  }
82023  const nextBandwidth = nextPlaylist.attributes.BANDWIDTH;
82024  const currBandwidth = currentPlaylist.attributes.BANDWIDTH; // when switching down, if our buffer is lower than the high water line,
82025  // we can switch down
82026
82027  if (nextBandwidth < currBandwidth && (!bufferBasedABR || forwardBuffer < bufferHighWaterLine)) {
82028    let logLine = `${sharedLogLine} as next bandwidth < current bandwidth (${nextBandwidth} < ${currBandwidth})`;
82029    if (bufferBasedABR) {
82030      logLine += ` and forwardBuffer < bufferHighWaterLine (${forwardBuffer} < ${bufferHighWaterLine})`;
82031    }
82032    log(logLine);
82033    return true;
82034  } // and if our buffer is higher than the low water line,
82035  // we can switch up
82036
82037  if ((!bufferBasedABR || nextBandwidth > currBandwidth) && forwardBuffer >= bufferLowWaterLine) {
82038    let logLine = `${sharedLogLine} as forwardBuffer >= bufferLowWaterLine (${forwardBuffer} >= ${bufferLowWaterLine})`;
82039    if (bufferBasedABR) {
82040      logLine += ` and next bandwidth > current bandwidth (${nextBandwidth} > ${currBandwidth})`;
82041    }
82042    log(logLine);
82043    return true;
82044  }
82045  log(`not ${sharedLogLine} as no switching criteria met`);
82046  return false;
82047};
82048/**
82049 * the main playlist controller controller all interactons
82050 * between playlists and segmentloaders. At this time this mainly
82051 * involves a main playlist and a series of audio playlists
82052 * if they are available
82053 *
82054 * @class PlaylistController
82055 * @extends videojs.EventTarget
82056 */
82057
82058class PlaylistController extends videojs.EventTarget {
82059  constructor(options) {
82060    super(); // Adding a slight debounce to avoid duplicate calls during rapid quality changes, for example:
82061    // When selecting quality from the quality list,
82062    // where we may have multiple bandwidth profiles for the same vertical resolution.
82063
82064    this.fastQualityChange_ = debounce(this.fastQualityChange_.bind(this), 100);
82065    const {
82066      src,
82067      withCredentials,
82068      tech,
82069      bandwidth,
82070      externVhs,
82071      useCueTags,
82072      playlistExclusionDuration,
82073      enableLowInitialPlaylist,
82074      sourceType,
82075      cacheEncryptionKeys,
82076      bufferBasedABR,
82077      leastPixelDiffSelector,
82078      captionServices,
82079      experimentalUseMMS
82080    } = options;
82081    if (!src) {
82082      throw new Error('A non-empty playlist URL or JSON manifest string is required');
82083    }
82084    let {
82085      maxPlaylistRetries
82086    } = options;
82087    if (maxPlaylistRetries === null || typeof maxPlaylistRetries === 'undefined') {
82088      maxPlaylistRetries = Infinity;
82089    }
82090    Vhs$1 = externVhs;
82091    this.bufferBasedABR = Boolean(bufferBasedABR);
82092    this.leastPixelDiffSelector = Boolean(leastPixelDiffSelector);
82093    this.withCredentials = withCredentials;
82094    this.tech_ = tech;
82095    this.vhs_ = tech.vhs;
82096    this.player_ = options.player_;
82097    this.sourceType_ = sourceType;
82098    this.useCueTags_ = useCueTags;
82099    this.playlistExclusionDuration = playlistExclusionDuration;
82100    this.maxPlaylistRetries = maxPlaylistRetries;
82101    this.enableLowInitialPlaylist = enableLowInitialPlaylist;
82102    this.usingManagedMediaSource_ = false;
vendor: 11,904 bytes, lines 82103-82406
82103    if (this.useCueTags_) {
82104      this.cueTagsTrack_ = this.tech_.addTextTrack('metadata', 'ad-cues');
82105      this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
82106    }
82107    this.requestOptions_ = {
82108      withCredentials,
82109      maxPlaylistRetries,
82110      timeout: null
82111    };
82112    this.on('error', this.pauseLoading);
82113    this.mediaTypes_ = createMediaTypes();
82114    if (experimentalUseMMS && window__default["default"].ManagedMediaSource) {
82115      // Airplay source not yet implemented. Remote playback must be disabled.
82116      this.tech_.el_.disableRemotePlayback = true;
82117      this.mediaSource = new window__default["default"].ManagedMediaSource();
82118      this.usingManagedMediaSource_ = true;
82119      videojs.log('Using ManagedMediaSource');
82120    } else if (window__default["default"].MediaSource) {
82121      this.mediaSource = new window__default["default"].MediaSource();
82122    }
82123    this.handleDurationChange_ = this.handleDurationChange_.bind(this);
82124    this.handleSourceOpen_ = this.handleSourceOpen_.bind(this);
82125    this.handleSourceEnded_ = this.handleSourceEnded_.bind(this);
82126    this.load = this.load.bind(this);
82127    this.pause = this.pause.bind(this);
82128    this.mediaSource.addEventListener('durationchange', this.handleDurationChange_); // load the media source into the player
82129
82130    this.mediaSource.addEventListener('sourceopen', this.handleSourceOpen_);
82131    this.mediaSource.addEventListener('sourceended', this.handleSourceEnded_);
82132    this.mediaSource.addEventListener('startstreaming', this.load);
82133    this.mediaSource.addEventListener('endstreaming', this.pause); // we don't have to handle sourceclose since dispose will handle termination of
82134    // everything, and the MediaSource should not be detached without a proper disposal
82135
82136    this.seekable_ = createTimeRanges();
82137    this.hasPlayed_ = false;
82138    this.syncController_ = new SyncController(options);
82139    this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
82140      kind: 'metadata',
82141      label: 'segment-metadata'
82142    }, false).track;
82143    this.segmentMetadataTrack_.mode = 'hidden';
82144    this.decrypter_ = new Decrypter();
82145    this.sourceUpdater_ = new SourceUpdater(this.mediaSource);
82146    this.inbandTextTracks_ = {};
82147    this.timelineChangeController_ = new TimelineChangeController();
82148    this.keyStatusMap_ = new Map();
82149    const segmentLoaderSettings = {
82150      vhs: this.vhs_,
82151      parse708captions: options.parse708captions,
82152      useDtsForTimestampOffset: options.useDtsForTimestampOffset,
82153      captionServices,
82154      mediaSource: this.mediaSource,
82155      currentTime: this.tech_.currentTime.bind(this.tech_),
82156      seekable: () => this.seekable(),
82157      seeking: () => this.tech_.seeking(),
82158      duration: () => this.duration(),
82159      hasPlayed: () => this.hasPlayed_,
82160      goalBufferLength: () => this.goalBufferLength(),
82161      bandwidth,
82162      syncController: this.syncController_,
82163      decrypter: this.decrypter_,
82164      sourceType: this.sourceType_,
82165      inbandTextTracks: this.inbandTextTracks_,
82166      cacheEncryptionKeys,
82167      sourceUpdater: this.sourceUpdater_,
82168      timelineChangeController: this.timelineChangeController_,
82169      exactManifestTimings: options.exactManifestTimings,
82170      addMetadataToTextTrack: this.addMetadataToTextTrack.bind(this)
82171    }; // The source type check not only determines whether a special DASH playlist loader
82172    // should be used, but also covers the case where the provided src is a vhs-json
82173    // manifest object (instead of a URL). In the case of vhs-json, the default
82174    // PlaylistLoader should be used.
82175
82176    this.mainPlaylistLoader_ = this.sourceType_ === 'dash' ? new DashPlaylistLoader(src, this.vhs_, merge(this.requestOptions_, {
82177      addMetadataToTextTrack: this.addMetadataToTextTrack.bind(this)
82178    })) : new PlaylistLoader(src, this.vhs_, merge(this.requestOptions_, {
82179      addDateRangesToTextTrack: this.addDateRangesToTextTrack_.bind(this)
82180    }));
82181    this.setupMainPlaylistLoaderListeners_(); // setup segment loaders
82182    // combined audio/video or just video when alternate audio track is selected
82183
82184    this.mainSegmentLoader_ = new SegmentLoader(merge(segmentLoaderSettings, {
82185      segmentMetadataTrack: this.segmentMetadataTrack_,
82186      loaderType: 'main'
82187    }), options); // alternate audio track
82188
82189    this.audioSegmentLoader_ = new SegmentLoader(merge(segmentLoaderSettings, {
82190      loaderType: 'audio'
82191    }), options);
82192    this.subtitleSegmentLoader_ = new VTTSegmentLoader(merge(segmentLoaderSettings, {
82193      loaderType: 'vtt',
82194      featuresNativeTextTracks: this.tech_.featuresNativeTextTracks,
82195      loadVttJs: () => new Promise((resolve, reject) => {
82196        function onLoad() {
82197          tech.off('vttjserror', onError);
82198          resolve();
82199        }
82200        function onError() {
82201          tech.off('vttjsloaded', onLoad);
82202          reject();
82203        }
82204        tech.one('vttjsloaded', onLoad);
82205        tech.one('vttjserror', onError); // safe to call multiple times, script will be loaded only once:
82206
82207        tech.addWebVttScript_();
82208      })
82209    }), options);
82210    const getBandwidth = () => {
82211      return this.mainSegmentLoader_.bandwidth;
82212    };
82213    this.contentSteeringController_ = new ContentSteeringController(this.vhs_.xhr, getBandwidth);
82214    this.setupSegmentLoaderListeners_();
82215    if (this.bufferBasedABR) {
82216      this.mainPlaylistLoader_.one('loadedplaylist', () => this.startABRTimer_());
82217      this.tech_.on('pause', () => this.stopABRTimer_());
82218      this.tech_.on('play', () => this.startABRTimer_());
82219    } // Create SegmentLoader stat-getters
82220    // mediaRequests_
82221    // mediaRequestsAborted_
82222    // mediaRequestsTimedout_
82223    // mediaRequestsErrored_
82224    // mediaTransferDuration_
82225    // mediaBytesTransferred_
82226    // mediaAppends_
82227
82228    loaderStats.forEach(stat => {
82229      this[stat + '_'] = sumLoaderStat.bind(this, stat);
82230    });
82231    this.logger_ = logger('pc');
82232    this.triggeredFmp4Usage = false;
82233    if (this.tech_.preload() === 'none') {
82234      this.loadOnPlay_ = () => {
82235        this.loadOnPlay_ = null;
82236        this.mainPlaylistLoader_.load();
82237      };
82238      this.tech_.one('play', this.loadOnPlay_);
82239    } else {
82240      this.mainPlaylistLoader_.load();
82241    }
82242    this.timeToLoadedData__ = -1;
82243    this.mainAppendsToLoadedData__ = -1;
82244    this.audioAppendsToLoadedData__ = -1;
82245    const event = this.tech_.preload() === 'none' ? 'play' : 'loadstart'; // start the first frame timer on loadstart or play (for preload none)
82246
82247    this.tech_.one(event, () => {
82248      const timeToLoadedDataStart = Date.now();
82249      this.tech_.one('loadeddata', () => {
82250        this.timeToLoadedData__ = Date.now() - timeToLoadedDataStart;
82251        this.mainAppendsToLoadedData__ = this.mainSegmentLoader_.mediaAppends;
82252        this.audioAppendsToLoadedData__ = this.audioSegmentLoader_.mediaAppends;
82253      });
82254    });
82255  }
82256  mainAppendsToLoadedData_() {
82257    return this.mainAppendsToLoadedData__;
82258  }
82259  audioAppendsToLoadedData_() {
82260    return this.audioAppendsToLoadedData__;
82261  }
82262  appendsToLoadedData_() {
82263    const main = this.mainAppendsToLoadedData_();
82264    const audio = this.audioAppendsToLoadedData_();
82265    if (main === -1 || audio === -1) {
82266      return -1;
82267    }
82268    return main + audio;
82269  }
82270  timeToLoadedData_() {
82271    return this.timeToLoadedData__;
82272  }
82273  /**
82274   * Run selectPlaylist and switch to the new playlist if we should
82275   *
82276   * @param {string} [reason=abr] a reason for why the ABR check is made
82277   * @private
82278   */
82279
82280  checkABR_(reason = 'abr') {
82281    const nextPlaylist = this.selectPlaylist();
82282    if (nextPlaylist && this.shouldSwitchToMedia_(nextPlaylist)) {
82283      this.switchMedia_(nextPlaylist, reason);
82284    }
82285  }
82286  switchMedia_(playlist, cause, delay) {
82287    const oldMedia = this.media();
82288    const oldId = oldMedia && (oldMedia.id || oldMedia.uri);
82289    const newId = playlist && (playlist.id || playlist.uri);
82290    if (oldId && oldId !== newId) {
82291      this.logger_(`switch media ${oldId} -> ${newId} from ${cause}`);
82292      const metadata = {
82293        renditionInfo: {
82294          id: newId,
82295          bandwidth: playlist.attributes.BANDWIDTH,
82296          resolution: playlist.attributes.RESOLUTION,
82297          codecs: playlist.attributes.CODECS
82298        },
82299        cause
82300      };
82301      this.trigger({
82302        type: 'renditionselected',
82303        metadata
82304      });
82305      this.tech_.trigger({
82306        type: 'usage',
82307        name: `vhs-rendition-change-${cause}`
82308      });
82309    }
82310    this.mainPlaylistLoader_.media(playlist, delay);
82311  }
82312  /**
82313   * A function that ensures we switch our playlists inside of `mediaTypes`
82314   * to match the current `serviceLocation` provided by the contentSteering controller.
82315   * We want to check media types of `AUDIO`, `SUBTITLES`, and `CLOSED-CAPTIONS`.
82316   *
82317   * This should only be called on a DASH playback scenario while using content steering.
82318   * This is necessary due to differences in how media in HLS manifests are generally tied to
82319   * a video playlist, where in DASH that is not always the case.
82320   */
82321
82322  switchMediaForDASHContentSteering_() {
82323    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(type => {
82324      const mediaType = this.mediaTypes_[type];
82325      const activeGroup = mediaType ? mediaType.activeGroup() : null;
82326      const pathway = this.contentSteeringController_.getPathway();
82327      if (activeGroup && pathway) {
82328        // activeGroup can be an array or a single group
82329        const mediaPlaylists = activeGroup.length ? activeGroup[0].playlists : activeGroup.playlists;
82330        const dashMediaPlaylists = mediaPlaylists.filter(p => p.attributes.serviceLocation === pathway); // Switch the current active playlist to the correct CDN
82331
82332        if (dashMediaPlaylists.length) {
82333          this.mediaTypes_[type].activePlaylistLoader.media(dashMediaPlaylists[0]);
82334        }
82335      }
82336    });
82337  }
82338  /**
82339   * Start a timer that periodically calls checkABR_
82340   *
82341   * @private
82342   */
82343
82344  startABRTimer_() {
82345    this.stopABRTimer_();
82346    this.abrTimer_ = window__default["default"].setInterval(() => this.checkABR_(), 250);
82347  }
82348  /**
82349   * Stop the timer that periodically calls checkABR_
82350   *
82351   * @private
82352   */
82353
82354  stopABRTimer_() {
82355    // if we're scrubbing, we don't need to pause.
82356    // This getter will be added to Video.js in version 7.11.
82357    if (this.tech_.scrubbing && this.tech_.scrubbing()) {
82358      return;
82359    }
82360    window__default["default"].clearInterval(this.abrTimer_);
82361    this.abrTimer_ = null;
82362  }
82363  /**
82364   * Get a list of playlists for the currently selected audio playlist
82365   *
82366   * @return {Array} the array of audio playlists
82367   */
82368
82369  getAudioTrackPlaylists_() {
82370    const main = this.main();
82371    const defaultPlaylists = main && main.playlists || []; // if we don't have any audio groups then we can only
82372    // assume that the audio tracks are contained in main
82373    // playlist array, use that or an empty array.
82374
82375    if (!main || !main.mediaGroups || !main.mediaGroups.AUDIO) {
82376      return defaultPlaylists;
82377    }
82378    const AUDIO = main.mediaGroups.AUDIO;
82379    const groupKeys = Object.keys(AUDIO);
82380    let track; // get the current active track
82381
82382    if (Object.keys(this.mediaTypes_.AUDIO.groups).length) {
82383      track = this.mediaTypes_.AUDIO.activeTrack(); // or get the default track from main if mediaTypes_ isn't setup yet
82384    } else {
82385      // default group is `main` or just the first group.
82386      const defaultGroup = AUDIO.main || groupKeys.length && AUDIO[groupKeys[0]];
82387      for (const label in defaultGroup) {
82388        if (defaultGroup[label].default) {
82389          track = {
82390            label
82391          };
82392          break;
82393        }
82394      }
82395    } // no active track no playlists.
82396
82397    if (!track) {
82398      return defaultPlaylists;
82399    }
82400    const playlists = []; // get all of the playlists that are possible for the
82401    // active track.
82402
82403    for (const group in AUDIO) {
82404      if (AUDIO[group][track.label]) {
82405        const properties = AUDIO[group][track.label];
82406        if (properties.playlists && properties.playlists.length) {
vendor: 5,469 bytes, lines 82407-82539
82407          playlists.push.apply(playlists, properties.playlists);
82408        } else if (properties.uri) {
82409          playlists.push(properties);
82410        } else if (main.playlists.length) {
82411          // if an audio group does not have a uri
82412          // see if we have main playlists that use it as a group.
82413          // if we do then add those to the playlists list.
82414          for (let i = 0; i < main.playlists.length; i++) {
82415            const playlist = main.playlists[i];
82416            if (playlist.attributes && playlist.attributes.AUDIO && playlist.attributes.AUDIO === group) {
82417              playlists.push(playlist);
82418            }
82419          }
82420        }
82421      }
82422    }
82423    if (!playlists.length) {
82424      return defaultPlaylists;
82425    }
82426    return playlists;
82427  }
82428  /**
82429   * Register event handlers on the main playlist loader. A helper
82430   * function for construction time.
82431   *
82432   * @private
82433   */
82434
82435  setupMainPlaylistLoaderListeners_() {
82436    this.mainPlaylistLoader_.on('loadedmetadata', () => {
82437      const media = this.mainPlaylistLoader_.media();
82438      const requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
82439      // timeout the request.
82440
82441      if (isLowestEnabledRendition(this.mainPlaylistLoader_.main, this.mainPlaylistLoader_.media())) {
82442        this.requestOptions_.timeout = 0;
82443      } else {
82444        this.requestOptions_.timeout = requestTimeout;
82445      } // if this isn't a live video and preload permits, start
82446      // downloading segments
82447
82448      if (media.endList && this.tech_.preload() !== 'none') {
82449        this.mainSegmentLoader_.playlist(media, this.requestOptions_);
82450        this.mainSegmentLoader_.load();
82451      }
82452      setupMediaGroups({
82453        sourceType: this.sourceType_,
82454        segmentLoaders: {
82455          AUDIO: this.audioSegmentLoader_,
82456          SUBTITLES: this.subtitleSegmentLoader_,
82457          main: this.mainSegmentLoader_
82458        },
82459        tech: this.tech_,
82460        requestOptions: this.requestOptions_,
82461        mainPlaylistLoader: this.mainPlaylistLoader_,
82462        vhs: this.vhs_,
82463        main: this.main(),
82464        mediaTypes: this.mediaTypes_,
82465        excludePlaylist: this.excludePlaylist.bind(this)
82466      });
82467      this.triggerPresenceUsage_(this.main(), media);
82468      this.setupFirstPlay();
82469      if (!this.mediaTypes_.AUDIO.activePlaylistLoader || this.mediaTypes_.AUDIO.activePlaylistLoader.media()) {
82470        this.trigger('selectedinitialmedia');
82471      } else {
82472        // We must wait for the active audio playlist loader to
82473        // finish setting up before triggering this event so the
82474        // representations API and EME setup is correct
82475        this.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', () => {
82476          this.trigger('selectedinitialmedia');
82477        });
82478      }
82479    });
82480    this.mainPlaylistLoader_.on('loadedplaylist', () => {
82481      if (this.loadOnPlay_) {
82482        this.tech_.off('play', this.loadOnPlay_);
82483      }
82484      let updatedPlaylist = this.mainPlaylistLoader_.media();
82485      if (!updatedPlaylist) {
82486        // Add content steering listeners on first load and init.
82487        this.attachContentSteeringListeners_();
82488        this.initContentSteeringController_(); // exclude any variants that are not supported by the browser before selecting
82489        // an initial media as the playlist selectors do not consider browser support
82490
82491        this.excludeUnsupportedVariants_();
82492        let selectedMedia;
82493        if (this.enableLowInitialPlaylist) {
82494          selectedMedia = this.selectInitialPlaylist();
82495        }
82496        if (!selectedMedia) {
82497          selectedMedia = this.selectPlaylist();
82498        }
82499        if (!selectedMedia || !this.shouldSwitchToMedia_(selectedMedia)) {
82500          return;
82501        }
82502        this.initialMedia_ = selectedMedia;
82503        this.switchMedia_(this.initialMedia_, 'initial'); // Under the standard case where a source URL is provided, loadedplaylist will
82504        // fire again since the playlist will be requested. In the case of vhs-json
82505        // (where the manifest object is provided as the source), when the media
82506        // playlist's `segments` list is already available, a media playlist won't be
82507        // requested, and loadedplaylist won't fire again, so the playlist handler must be
82508        // called on its own here.
82509
82510        const haveJsonSource = this.sourceType_ === 'vhs-json' && this.initialMedia_.segments;
82511        if (!haveJsonSource) {
82512          return;
82513        }
82514        updatedPlaylist = this.initialMedia_;
82515      }
82516      this.handleUpdatedMediaPlaylist(updatedPlaylist);
82517    });
82518    this.mainPlaylistLoader_.on('error', () => {
82519      const error = this.mainPlaylistLoader_.error;
82520      this.excludePlaylist({
82521        playlistToExclude: error.playlist,
82522        error
82523      });
82524    });
82525    this.mainPlaylistLoader_.on('mediachanging', () => {
82526      this.mainSegmentLoader_.abort();
82527      this.mainSegmentLoader_.pause();
82528    });
82529    this.mainPlaylistLoader_.on('mediachange', () => {
82530      const media = this.mainPlaylistLoader_.media();
82531      const requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to
82532      // timeout the request.
82533
82534      if (isLowestEnabledRendition(this.mainPlaylistLoader_.main, this.mainPlaylistLoader_.media())) {
82535        this.requestOptions_.timeout = 0;
82536      } else {
82537        this.requestOptions_.timeout = requestTimeout;
82538      }
82539      if (this.sourceType_ === 'dash') {
vendor: 4,996 bytes, lines 82540-82659
82540        // we don't want to re-request the same hls playlist right after it was changed
82541        // Initially it was implemented as workaround to restart playlist loader for live
82542        // when playlist loader is paused because of playlist exclusions:
82543        // see: https://github.com/videojs/http-streaming/pull/1339
82544        // but this introduces duplicate "loadedplaylist" event.
82545        // Ideally we want to re-think playlist loader life-cycle events,
82546        // but simply checking "paused" state should help a lot
82547        if (this.mainPlaylistLoader_.isPaused) {
82548          this.mainPlaylistLoader_.load();
82549        }
82550      } // TODO: Create a new event on the PlaylistLoader that signals
82551      // that the segments have changed in some way and use that to
82552      // update the SegmentLoader instead of doing it twice here and
82553      // on `loadedplaylist`
82554
82555      this.mainSegmentLoader_.pause();
82556      this.mainSegmentLoader_.playlist(media, this.requestOptions_);
82557      if (this.waitingForFastQualityPlaylistReceived_) {
82558        this.runFastQualitySwitch_();
82559      } else {
82560        this.mainSegmentLoader_.load();
82561      }
82562      this.tech_.trigger({
82563        type: 'mediachange',
82564        bubbles: true
82565      });
82566    });
82567    this.mainPlaylistLoader_.on('playlistunchanged', () => {
82568      const updatedPlaylist = this.mainPlaylistLoader_.media(); // ignore unchanged playlists that have already been
82569      // excluded for not-changing. We likely just have a really slowly updating
82570      // playlist.
82571
82572      if (updatedPlaylist.lastExcludeReason_ === 'playlist-unchanged') {
82573        return;
82574      }
82575      const playlistOutdated = this.stuckAtPlaylistEnd_(updatedPlaylist);
82576      if (playlistOutdated) {
82577        // Playlist has stopped updating and we're stuck at its end. Try to
82578        // exclude it and switch to another playlist in the hope that that
82579        // one is updating (and give the player a chance to re-adjust to the
82580        // safe live point).
82581        this.excludePlaylist({
82582          error: {
82583            message: 'Playlist no longer updating.',
82584            reason: 'playlist-unchanged'
82585          }
82586        }); // useful for monitoring QoS
82587
82588        this.tech_.trigger('playliststuck');
82589      }
82590    });
82591    this.mainPlaylistLoader_.on('renditiondisabled', () => {
82592      this.tech_.trigger({
82593        type: 'usage',
82594        name: 'vhs-rendition-disabled'
82595      });
82596    });
82597    this.mainPlaylistLoader_.on('renditionenabled', () => {
82598      this.tech_.trigger({
82599        type: 'usage',
82600        name: 'vhs-rendition-enabled'
82601      });
82602    });
82603    const playlistLoaderEvents = ['manifestrequeststart', 'manifestrequestcomplete', 'manifestparsestart', 'manifestparsecomplete', 'playlistrequeststart', 'playlistrequestcomplete', 'playlistparsestart', 'playlistparsecomplete', 'renditiondisabled', 'renditionenabled'];
82604    playlistLoaderEvents.forEach(eventName => {
82605      this.mainPlaylistLoader_.on(eventName, metadata => {
82606        // trigger directly on the player to ensure early events are fired.
82607        this.player_.trigger(_extends__default["default"]({}, metadata));
82608      });
82609    });
82610  }
82611  /**
82612   * Given an updated media playlist (whether it was loaded for the first time, or
82613   * refreshed for live playlists), update any relevant properties and state to reflect
82614   * changes in the media that should be accounted for (e.g., cues and duration).
82615   *
82616   * @param {Object} updatedPlaylist the updated media playlist object
82617   *
82618   * @private
82619   */
82620
82621  handleUpdatedMediaPlaylist(updatedPlaylist) {
82622    if (this.useCueTags_) {
82623      this.updateAdCues_(updatedPlaylist);
82624    } // TODO: Create a new event on the PlaylistLoader that signals
82625    // that the segments have changed in some way and use that to
82626    // update the SegmentLoader instead of doing it twice here and
82627    // on `mediachange`
82628
82629    this.mainSegmentLoader_.pause();
82630    this.mainSegmentLoader_.playlist(updatedPlaylist, this.requestOptions_);
82631    if (this.waitingForFastQualityPlaylistReceived_) {
82632      this.runFastQualitySwitch_();
82633    }
82634    this.updateDuration(!updatedPlaylist.endList); // If the player isn't paused, ensure that the segment loader is running,
82635    // as it is possible that it was temporarily stopped while waiting for
82636    // a playlist (e.g., in case the playlist errored and we re-requested it).
82637
82638    if (!this.tech_.paused()) {
82639      this.mainSegmentLoader_.load();
82640      if (this.audioSegmentLoader_) {
82641        this.audioSegmentLoader_.load();
82642      }
82643    }
82644  }
82645  /**
82646   * A helper function for triggerring presence usage events once per source
82647   *
82648   * @private
82649   */
82650
82651  triggerPresenceUsage_(main, media) {
82652    const mediaGroups = main.mediaGroups || {};
82653    let defaultDemuxed = true;
82654    const audioGroupKeys = Object.keys(mediaGroups.AUDIO);
82655    for (const mediaGroup in mediaGroups.AUDIO) {
82656      for (const label in mediaGroups.AUDIO[mediaGroup]) {
82657        const properties = mediaGroups.AUDIO[mediaGroup][label];
82658        if (!properties.uri) {
82659          defaultDemuxed = false;
vendor: 6,718 bytes, lines 82660-82850
82660        }
82661      }
82662    }
82663    if (defaultDemuxed) {
82664      this.tech_.trigger({
82665        type: 'usage',
82666        name: 'vhs-demuxed'
82667      });
82668    }
82669    if (Object.keys(mediaGroups.SUBTITLES).length) {
82670      this.tech_.trigger({
82671        type: 'usage',
82672        name: 'vhs-webvtt'
82673      });
82674    }
82675    if (Vhs$1.Playlist.isAes(media)) {
82676      this.tech_.trigger({
82677        type: 'usage',
82678        name: 'vhs-aes'
82679      });
82680    }
82681    if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
82682      this.tech_.trigger({
82683        type: 'usage',
82684        name: 'vhs-alternate-audio'
82685      });
82686    }
82687    if (this.useCueTags_) {
82688      this.tech_.trigger({
82689        type: 'usage',
82690        name: 'vhs-playlist-cue-tags'
82691      });
82692    }
82693  }
82694  shouldSwitchToMedia_(nextPlaylist) {
82695    const currentPlaylist = this.mainPlaylistLoader_.media() || this.mainPlaylistLoader_.pendingMedia_;
82696    const currentTime = this.tech_.currentTime();
82697    const bufferLowWaterLine = this.bufferLowWaterLine();
82698    const bufferHighWaterLine = this.bufferHighWaterLine();
82699    const buffered = this.tech_.buffered();
82700    return shouldSwitchToMedia({
82701      buffered,
82702      currentTime,
82703      currentPlaylist,
82704      nextPlaylist,
82705      bufferLowWaterLine,
82706      bufferHighWaterLine,
82707      duration: this.duration(),
82708      bufferBasedABR: this.bufferBasedABR,
82709      log: this.logger_
82710    });
82711  }
82712  /**
82713   * Register event handlers on the segment loaders. A helper function
82714   * for construction time.
82715   *
82716   * @private
82717   */
82718
82719  setupSegmentLoaderListeners_() {
82720    this.mainSegmentLoader_.on('bandwidthupdate', () => {
82721      // Whether or not buffer based ABR or another ABR is used, on a bandwidth change it's
82722      // useful to check to see if a rendition switch should be made.
82723      this.checkABR_('bandwidthupdate');
82724      this.tech_.trigger('bandwidthupdate');
82725    });
82726    this.mainSegmentLoader_.on('timeout', () => {
82727      if (this.bufferBasedABR) {
82728        // If a rendition change is needed, then it would've be done on `bandwidthupdate`.
82729        // Here the only consideration is that for buffer based ABR there's no guarantee
82730        // of an immediate switch (since the bandwidth is averaged with a timeout
82731        // bandwidth value of 1), so force a load on the segment loader to keep it going.
82732        this.mainSegmentLoader_.load();
82733      }
82734    }); // `progress` events are not reliable enough of a bandwidth measure to trigger buffer
82735    // based ABR.
82736
82737    if (!this.bufferBasedABR) {
82738      this.mainSegmentLoader_.on('progress', () => {
82739        this.trigger('progress');
82740      });
82741    }
82742    this.mainSegmentLoader_.on('error', () => {
82743      const error = this.mainSegmentLoader_.error();
82744      this.excludePlaylist({
82745        playlistToExclude: error.playlist,
82746        error
82747      });
82748    });
82749    this.mainSegmentLoader_.on('appenderror', () => {
82750      this.error = this.mainSegmentLoader_.error_;
82751      this.trigger('error');
82752    });
82753    this.mainSegmentLoader_.on('syncinfoupdate', () => {
82754      this.onSyncInfoUpdate_();
82755    });
82756    this.mainSegmentLoader_.on('timestampoffset', () => {
82757      this.tech_.trigger({
82758        type: 'usage',
82759        name: 'vhs-timestamp-offset'
82760      });
82761    });
82762    this.audioSegmentLoader_.on('syncinfoupdate', () => {
82763      this.onSyncInfoUpdate_();
82764    });
82765    this.audioSegmentLoader_.on('appenderror', () => {
82766      this.error = this.audioSegmentLoader_.error_;
82767      this.trigger('error');
82768    });
82769    this.mainSegmentLoader_.on('ended', () => {
82770      this.logger_('main segment loader ended');
82771      this.onEndOfStream();
82772    }); // There is the possibility of the video segment and the audio segment
82773    // at a current time to be on different timelines. When this occurs, the player
82774    // forwards playback to a point where these two segment types are back on the same
82775    // timeline. This time will be just after the end of the audio segment that is on
82776    // a previous timeline.
82777
82778    this.timelineChangeController_.on('audioTimelineBehind', () => {
82779      const segmentInfo = this.audioSegmentLoader_.pendingSegment_;
82780      if (!segmentInfo || !segmentInfo.segment || !segmentInfo.segment.syncInfo) {
82781        return;
82782      } // Update the current time to just after the faulty audio segment.
82783      // This moves playback to a spot where both audio and video segments
82784      // are on the same timeline.
82785
82786      const newTime = segmentInfo.segment.syncInfo.end + 0.01;
82787      this.tech_.setCurrentTime(newTime);
82788    });
82789    this.timelineChangeController_.on('fixBadTimelineChange', () => {
82790      // pause, reset-everything and load for all segment-loaders
82791      this.logger_('Fix bad timeline change. Restarting al segment loaders...');
82792      this.mainSegmentLoader_.pause();
82793      this.mainSegmentLoader_.resetEverything();
82794      if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
82795        this.audioSegmentLoader_.pause();
82796        this.audioSegmentLoader_.resetEverything();
82797      }
82798      if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
82799        this.subtitleSegmentLoader_.pause();
82800        this.subtitleSegmentLoader_.resetEverything();
82801      } // start segment loader loading in case they are paused
82802
82803      this.load();
82804    });
82805    this.mainSegmentLoader_.on('earlyabort', event => {
82806      // never try to early abort with the new ABR algorithm
82807      if (this.bufferBasedABR) {
82808        return;
82809      }
82810      this.delegateLoaders_('all', ['abort']);
82811      this.excludePlaylist({
82812        error: {
82813          message: 'Aborted early because there isn\'t enough bandwidth to complete ' + 'the request without rebuffering.'
82814        },
82815        playlistExclusionDuration: ABORT_EARLY_EXCLUSION_SECONDS
82816      });
82817    });
82818    const updateCodecs = () => {
82819      if (!this.sourceUpdater_.hasCreatedSourceBuffers()) {
82820        return this.tryToCreateSourceBuffers_();
82821      }
82822      const codecs = this.getCodecsOrExclude_(); // no codecs means that the playlist was excluded
82823
82824      if (!codecs) {
82825        return;
82826      }
82827      this.sourceUpdater_.addOrChangeSourceBuffers(codecs);
82828    };
82829    this.mainSegmentLoader_.on('trackinfo', updateCodecs);
82830    this.audioSegmentLoader_.on('trackinfo', updateCodecs);
82831    this.mainSegmentLoader_.on('fmp4', () => {
82832      if (!this.triggeredFmp4Usage) {
82833        this.tech_.trigger({
82834          type: 'usage',
82835          name: 'vhs-fmp4'
82836        });
82837        this.triggeredFmp4Usage = true;
82838      }
82839    });
82840    this.audioSegmentLoader_.on('fmp4', () => {
82841      if (!this.triggeredFmp4Usage) {
82842        this.tech_.trigger({
82843          type: 'usage',
82844          name: 'vhs-fmp4'
82845        });
82846        this.triggeredFmp4Usage = true;
82847      }
82848    });
82849    this.audioSegmentLoader_.on('ended', () => {
82850      this.logger_('audioSegmentLoader ended');
vendor: 4,177 bytes, lines 82851-82965
82851      this.onEndOfStream();
82852    });
82853    const segmentLoaderEvents = ['segmentselected', 'segmentloadstart', 'segmentloaded', 'segmentkeyloadstart', 'segmentkeyloadcomplete', 'segmentdecryptionstart', 'segmentdecryptioncomplete', 'segmenttransmuxingstart', 'segmenttransmuxingcomplete', 'segmenttransmuxingtrackinfoavailable', 'segmenttransmuxingtiminginfoavailable', 'segmentappendstart', 'appendsdone', 'bandwidthupdated', 'timelinechange', 'codecschange'];
82854    segmentLoaderEvents.forEach(eventName => {
82855      this.mainSegmentLoader_.on(eventName, metadata => {
82856        this.player_.trigger(_extends__default["default"]({}, metadata));
82857      });
82858      this.audioSegmentLoader_.on(eventName, metadata => {
82859        this.player_.trigger(_extends__default["default"]({}, metadata));
82860      });
82861      this.subtitleSegmentLoader_.on(eventName, metadata => {
82862        this.player_.trigger(_extends__default["default"]({}, metadata));
82863      });
82864    });
82865  }
82866  mediaSecondsLoaded_() {
82867    return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
82868  }
82869  /**
82870   * Call load on our SegmentLoaders
82871   */
82872
82873  load() {
82874    this.mainSegmentLoader_.load();
82875    if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
82876      this.audioSegmentLoader_.load();
82877    }
82878    if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
82879      this.subtitleSegmentLoader_.load();
82880    }
82881  }
82882  /**
82883   * Call pause on our SegmentLoaders
82884   */
82885
82886  pause() {
82887    this.mainSegmentLoader_.pause();
82888    if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
82889      this.audioSegmentLoader_.pause();
82890    }
82891    if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
82892      this.subtitleSegmentLoader_.pause();
82893    }
82894  }
82895  /**
82896   * Re-tune playback quality level for the current player
82897   * conditions. This method will perform destructive actions like removing
82898   * already buffered content in order to readjust the currently active
82899   * playlist quickly. This is good for manual quality changes
82900   *
82901   * @private
82902   */
82903
82904  fastQualityChange_(media = this.selectPlaylist()) {
82905    if (media && media === this.mainPlaylistLoader_.media()) {
82906      this.logger_('skipping fastQualityChange because new media is same as old');
82907      return;
82908    }
82909    this.switchMedia_(media, 'fast-quality'); // we would like to avoid race condition when we call fastQuality,
82910    // reset everything and start loading segments from prev segments instead of new because new playlist is not received yet
82911
82912    this.waitingForFastQualityPlaylistReceived_ = true;
82913  }
82914  runFastQualitySwitch_() {
82915    this.waitingForFastQualityPlaylistReceived_ = false;
82916    this.mainSegmentLoader_.pause();
82917    this.mainSegmentLoader_.resetEverything();
82918    if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
82919      this.audioSegmentLoader_.pause();
82920      this.audioSegmentLoader_.resetEverything();
82921    }
82922    if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
82923      this.subtitleSegmentLoader_.pause();
82924      this.subtitleSegmentLoader_.resetEverything();
82925    } // start segment loader loading in case they are paused
82926
82927    this.load();
82928  }
82929  /**
82930   * Begin playback.
82931   */
82932
82933  play() {
82934    if (this.setupFirstPlay()) {
82935      return;
82936    }
82937    if (this.tech_.ended()) {
82938      this.tech_.setCurrentTime(0);
82939    }
82940    if (this.hasPlayed_) {
82941      this.load();
82942    }
82943    const seekable = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window,
82944    // seek forward to the live point
82945
82946    if (this.tech_.duration() === Infinity) {
82947      if (this.tech_.currentTime() < seekable.start(0)) {
82948        return this.tech_.setCurrentTime(seekable.end(seekable.length - 1));
82949      }
82950    }
82951  }
82952  /**
82953   * Seek to the latest media position if this is a live video and the
82954   * player and video are loaded and initialized.
82955   */
82956
82957  setupFirstPlay() {
82958    const media = this.mainPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play
82959    //  If 1) there is no active media
82960    //     2) the player is paused
82961    //     3) the first play has already been setup
82962    // then exit early
82963
82964    if (!media || this.tech_.paused() || this.hasPlayed_) {
82965      return false;
vendor: 25,607 bytes, lines 82966-83592
82966    } // when the video is a live stream and/or has a start time
82967
82968    if (!media.endList || media.start) {
82969      const seekable = this.seekable();
82970      if (!seekable.length) {
82971        // without a seekable range, the player cannot seek to begin buffering at the
82972        // live or start point
82973        return false;
82974      }
82975      const seekableEnd = seekable.end(0);
82976      let startPoint = seekableEnd;
82977      if (media.start) {
82978        const offset = media.start.timeOffset;
82979        if (offset < 0) {
82980          // Per HLS spec, negative TIME-OFFSET is from the end of the last Media Segment.
82981          // For live streams, seekableEnd already has liveEdgeDelay subtracted,
82982          // so we need to add it back to get the actual playlist end.
82983          // Clamp to seekableEnd to avoid seeking into the unsafe live edge zone.
82984          const main = this.mainPlaylistLoader_.main;
82985          const liveEdgeDelay = Vhs$1.Playlist.liveEdgeDelay(main, media);
82986          const actualPlaylistEnd = seekableEnd + liveEdgeDelay;
82987          startPoint = Math.max(Math.min(actualPlaylistEnd + offset, seekableEnd), seekable.start(0));
82988        } else {
82989          startPoint = Math.min(seekableEnd, offset);
82990        }
82991      } // trigger firstplay to inform the source handler to ignore the next seek event
82992
82993      this.trigger('firstplay'); // seek to the live point
82994
82995      this.tech_.setCurrentTime(startPoint);
82996    }
82997    this.hasPlayed_ = true; // we can begin loading now that everything is ready
82998
82999    this.load();
83000    return true;
83001  }
83002  /**
83003   * handle the sourceopen event on the MediaSource
83004   *
83005   * @private
83006   */
83007
83008  handleSourceOpen_() {
83009    // Only attempt to create the source buffer if none already exist.
83010    // handleSourceOpen is also called when we are "re-opening" a source buffer
83011    // after `endOfStream` has been called (in response to a seek for instance)
83012    this.tryToCreateSourceBuffers_(); // if autoplay is enabled, begin playback. This is duplicative of
83013    // code in video.js but is required because play() must be invoked
83014    // *after* the media source has opened.
83015
83016    if (this.tech_.autoplay()) {
83017      const playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request
83018      // on browsers which return a promise
83019
83020      if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
83021        playPromise.then(null, e => {});
83022      }
83023    }
83024    this.trigger('sourceopen');
83025  }
83026  /**
83027   * handle the sourceended event on the MediaSource
83028   *
83029   * @private
83030   */
83031
83032  handleSourceEnded_() {
83033    if (!this.inbandTextTracks_.metadataTrack_) {
83034      return;
83035    }
83036    const cues = this.inbandTextTracks_.metadataTrack_.cues;
83037    if (!cues || !cues.length) {
83038      return;
83039    }
83040    const duration = this.duration();
83041    cues[cues.length - 1].endTime = isNaN(duration) || Math.abs(duration) === Infinity ? Number.MAX_VALUE : duration;
83042  }
83043  /**
83044   * handle the durationchange event on the MediaSource
83045   *
83046   * @private
83047   */
83048
83049  handleDurationChange_() {
83050    this.tech_.trigger('durationchange');
83051  }
83052  /**
83053   * Calls endOfStream on the media source when all active stream types have called
83054   * endOfStream
83055   *
83056   * @param {string} streamType
83057   *        Stream type of the segment loader that called endOfStream
83058   * @private
83059   */
83060
83061  onEndOfStream() {
83062    let isEndOfStream = this.mainSegmentLoader_.ended_;
83063    if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
83064      const mainMediaInfo = this.mainSegmentLoader_.getCurrentMediaInfo_(); // if the audio playlist loader exists, then alternate audio is active
83065
83066      if (!mainMediaInfo || mainMediaInfo.hasVideo) {
83067        // if we do not know if the main segment loader contains video yet or if we
83068        // definitively know the main segment loader contains video, then we need to wait
83069        // for both main and audio segment loaders to call endOfStream
83070        isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
83071      } else {
83072        // otherwise just rely on the audio loader
83073        isEndOfStream = this.audioSegmentLoader_.ended_;
83074      }
83075    }
83076    if (!isEndOfStream) {
83077      return;
83078    }
83079    this.stopABRTimer_();
83080    this.sourceUpdater_.endOfStream();
83081  }
83082  /**
83083   * Check if a playlist has stopped being updated
83084   *
83085   * @param {Object} playlist the media playlist object
83086   * @return {boolean} whether the playlist has stopped being updated or not
83087   */
83088
83089  stuckAtPlaylistEnd_(playlist) {
83090    const seekable = this.seekable();
83091    if (!seekable.length) {
83092      // playlist doesn't have enough information to determine whether we are stuck
83093      return false;
83094    }
83095    const expired = this.syncController_.getExpiredTime(playlist, this.duration());
83096    if (expired === null) {
83097      return false;
83098    } // does not use the safe live end to calculate playlist end, since we
83099    // don't want to say we are stuck while there is still content
83100
83101    const absolutePlaylistEnd = Vhs$1.Playlist.playlistEnd(playlist, expired);
83102    const currentTime = this.tech_.currentTime();
83103    const buffered = this.tech_.buffered();
83104    if (!buffered.length) {
83105      // return true if the playhead reached the absolute end of the playlist
83106      return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
83107    }
83108    const bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute
83109    // end of playlist
83110
83111    return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA;
83112  }
83113  /**
83114   * Exclude a playlist for a set amount of time, making it unavailable for selection by
83115   * the rendition selection algorithm, then force a new playlist (rendition) selection.
83116   *
83117   * @param {Object=} playlistToExclude
83118   *                  the playlist to exclude, defaults to the currently selected playlist
83119   * @param {Object=} error
83120   *                  an optional error
83121   * @param {number=} playlistExclusionDuration
83122   *                  an optional number of seconds to exclude the playlist
83123   */
83124
83125  excludePlaylist({
83126    playlistToExclude = this.mainPlaylistLoader_.media(),
83127    error = {},
83128    playlistExclusionDuration
83129  }) {
83130    // If the `error` was generated by the playlist loader, it will contain
83131    // the playlist we were trying to load (but failed) and that should be
83132    // excluded instead of the currently selected playlist which is likely
83133    // out-of-date in this scenario
83134    playlistToExclude = playlistToExclude || this.mainPlaylistLoader_.media();
83135    playlistExclusionDuration = playlistExclusionDuration || error.playlistExclusionDuration || this.playlistExclusionDuration; // If there is no current playlist, then an error occurred while we were
83136    // trying to load the main OR while we were disposing of the tech
83137
83138    if (!playlistToExclude) {
83139      this.error = error;
83140      if (this.mediaSource.readyState !== 'open') {
83141        this.trigger('error');
83142      } else {
83143        this.sourceUpdater_.endOfStream('network');
83144      }
83145      return;
83146    }
83147    playlistToExclude.playlistErrors_++;
83148    const playlists = this.mainPlaylistLoader_.main.playlists;
83149    const enabledPlaylists = playlists.filter(isEnabled);
83150    const isFinalRendition = enabledPlaylists.length === 1 && enabledPlaylists[0] === playlistToExclude; // Don't exclude the only playlist unless it was excluded
83151    // forever
83152
83153    if (playlists.length === 1 && playlistExclusionDuration !== Infinity) {
83154      videojs.log.warn(`Problem encountered with playlist ${playlistToExclude.id}. ` + 'Trying again since it is the only playlist.');
83155      this.tech_.trigger('retryplaylist'); // if this is a final rendition, we should delay
83156
83157      return this.mainPlaylistLoader_.load(isFinalRendition);
83158    }
83159    if (isFinalRendition) {
83160      // If we're content steering, try other pathways.
83161      if (this.main().contentSteering) {
83162        const pathway = this.pathwayAttribute_(playlistToExclude); // Ignore at least 1 steering manifest refresh.
83163
83164        const reIncludeDelay = this.contentSteeringController_.steeringManifest.ttl * 1000;
83165        this.contentSteeringController_.excludePathway(pathway);
83166        this.excludeThenChangePathway_();
83167        setTimeout(() => {
83168          this.contentSteeringController_.addAvailablePathway(pathway);
83169        }, reIncludeDelay);
83170        return;
83171      } // Since we're on the final non-excluded playlist, and we're about to exclude
83172      // it, instead of erring the player or retrying this playlist, clear out the current
83173      // exclusion list. This allows other playlists to be attempted in case any have been
83174      // fixed.
83175
83176      let reincluded = false;
83177      playlists.forEach(playlist => {
83178        // skip current playlist which is about to be excluded
83179        if (playlist === playlistToExclude) {
83180          return;
83181        }
83182        const excludeUntil = playlist.excludeUntil; // a playlist cannot be reincluded if it wasn't excluded to begin with.
83183
83184        if (typeof excludeUntil !== 'undefined' && excludeUntil !== Infinity) {
83185          reincluded = true;
83186          delete playlist.excludeUntil;
83187        }
83188      });
83189      if (reincluded) {
83190        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
83191        // playlist. This is needed for users relying on the retryplaylist event to catch a
83192        // case where the player might be stuck and looping through "dead" playlists.
83193
83194        this.tech_.trigger('retryplaylist');
83195      }
83196    } // Exclude this playlist
83197
83198    let excludeUntil;
83199    if (playlistToExclude.playlistErrors_ > this.maxPlaylistRetries) {
83200      excludeUntil = Infinity;
83201    } else {
83202      excludeUntil = Date.now() + playlistExclusionDuration * 1000;
83203    }
83204    playlistToExclude.excludeUntil = excludeUntil;
83205    if (error.reason) {
83206      playlistToExclude.lastExcludeReason_ = error.reason;
83207    }
83208    this.tech_.trigger('excludeplaylist');
83209    this.tech_.trigger({
83210      type: 'usage',
83211      name: 'vhs-rendition-excluded'
83212    }); // TODO: only load a new playlist if we're excluding the current playlist
83213    // If this function was called with a playlist that's not the current active playlist
83214    // (e.g., media().id !== playlistToExclude.id),
83215    // then a new playlist should not be selected and loaded, as there's nothing wrong with the current playlist.
83216
83217    const nextPlaylist = this.selectPlaylist();
83218    if (!nextPlaylist) {
83219      this.error = 'Playback cannot continue. No available working or supported playlists.';
83220      this.trigger('error');
83221      return;
83222    }
83223    const logFn = error.internal ? this.logger_ : videojs.log.warn;
83224    const errorMessage = error.message ? ' ' + error.message : '';
83225    logFn(`${error.internal ? 'Internal problem' : 'Problem'} encountered with playlist ${playlistToExclude.id}.` + `${errorMessage} Switching to playlist ${nextPlaylist.id}.`); // if audio group changed reset audio loaders
83226
83227    if (nextPlaylist.attributes.AUDIO !== playlistToExclude.attributes.AUDIO) {
83228      this.delegateLoaders_('audio', ['abort', 'pause']);
83229    } // if subtitle group changed reset subtitle loaders
83230
83231    if (nextPlaylist.attributes.SUBTITLES !== playlistToExclude.attributes.SUBTITLES) {
83232      this.delegateLoaders_('subtitle', ['abort', 'pause']);
83233    }
83234    this.delegateLoaders_('main', ['abort', 'pause']);
83235    const delayDuration = nextPlaylist.targetDuration / 2 * 1000 || 5 * 1000;
83236    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
83237
83238    return this.switchMedia_(nextPlaylist, 'exclude', isFinalRendition || shouldDelay);
83239  }
83240  /**
83241   * Pause all segment/playlist loaders
83242   */
83243
83244  pauseLoading() {
83245    this.delegateLoaders_('all', ['abort', 'pause']);
83246    this.stopABRTimer_();
83247  }
83248  /**
83249   * Call a set of functions in order on playlist loaders, segment loaders,
83250   * or both types of loaders.
83251   *
83252   * @param {string} filter
83253   *        Filter loaders that should call fnNames using a string. Can be:
83254   *        * all - run on all loaders
83255   *        * audio - run on all audio loaders
83256   *        * subtitle - run on all subtitle loaders
83257   *        * main - run on the main loaders
83258   *
83259   * @param {Array|string} fnNames
83260   *        A string or array of function names to call.
83261   */
83262
83263  delegateLoaders_(filter, fnNames) {
83264    const loaders = [];
83265    const dontFilterPlaylist = filter === 'all';
83266    if (dontFilterPlaylist || filter === 'main') {
83267      loaders.push(this.mainPlaylistLoader_);
83268    }
83269    const mediaTypes = [];
83270    if (dontFilterPlaylist || filter === 'audio') {
83271      mediaTypes.push('AUDIO');
83272    }
83273    if (dontFilterPlaylist || filter === 'subtitle') {
83274      mediaTypes.push('CLOSED-CAPTIONS');
83275      mediaTypes.push('SUBTITLES');
83276    }
83277    mediaTypes.forEach(mediaType => {
83278      const loader = this.mediaTypes_[mediaType] && this.mediaTypes_[mediaType].activePlaylistLoader;
83279      if (loader) {
83280        loaders.push(loader);
83281      }
83282    });
83283    ['main', 'audio', 'subtitle'].forEach(name => {
83284      const loader = this[`${name}SegmentLoader_`];
83285      if (loader && (filter === name || filter === 'all')) {
83286        loaders.push(loader);
83287      }
83288    });
83289    loaders.forEach(loader => fnNames.forEach(fnName => {
83290      if (typeof loader[fnName] === 'function') {
83291        loader[fnName]();
83292      }
83293    }));
83294  }
83295  /**
83296   * set the current time on all segment loaders
83297   *
83298   * @param {TimeRange} currentTime the current time to set
83299   * @return {TimeRange} the current time
83300   */
83301
83302  setCurrentTime(currentTime) {
83303    const buffered = findRange(this.tech_.buffered(), currentTime);
83304    if (!(this.mainPlaylistLoader_ && this.mainPlaylistLoader_.media())) {
83305      // return immediately if the metadata is not ready yet
83306      return 0;
83307    } // it's clearly an edge-case but don't thrown an error if asked to
83308    // seek within an empty playlist
83309
83310    if (!this.mainPlaylistLoader_.media().segments) {
83311      return 0;
83312    } // if the seek location is already buffered, continue buffering as usual
83313
83314    if (buffered && buffered.length) {
83315      return currentTime;
83316    } // cancel outstanding requests so we begin buffering at the new
83317    // location
83318
83319    this.mainSegmentLoader_.pause();
83320    this.mainSegmentLoader_.resetEverything();
83321    if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
83322      this.audioSegmentLoader_.pause();
83323      this.audioSegmentLoader_.resetEverything();
83324    }
83325    if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
83326      this.subtitleSegmentLoader_.pause();
83327      this.subtitleSegmentLoader_.resetEverything();
83328    } // start segment loader loading in case they are paused
83329
83330    this.load();
83331  }
83332  /**
83333   * get the current duration
83334   *
83335   * @return {TimeRange} the duration
83336   */
83337
83338  duration() {
83339    if (!this.mainPlaylistLoader_) {
83340      return 0;
83341    }
83342    const media = this.mainPlaylistLoader_.media();
83343    if (!media) {
83344      // no playlists loaded yet, so can't determine a duration
83345      return 0;
83346    } // Don't rely on the media source for duration in the case of a live playlist since
83347    // setting the native MediaSource's duration to infinity ends up with consequences to
83348    // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details.
83349    //
83350    // This is resolved in the spec by https://github.com/w3c/media-source/pull/92,
83351    // however, few browsers have support for setLiveSeekableRange()
83352    // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange
83353    //
83354    // Until a time when the duration of the media source can be set to infinity, and a
83355    // seekable range specified across browsers, just return Infinity.
83356
83357    if (!media.endList) {
83358      return Infinity;
83359    } // Since this is a VOD video, it is safe to rely on the media source's duration (if
83360    // available). If it's not available, fall back to a playlist-calculated estimate.
83361
83362    if (this.mediaSource) {
83363      return this.mediaSource.duration;
83364    }
83365    return Vhs$1.Playlist.duration(media);
83366  }
83367  /**
83368   * check the seekable range
83369   *
83370   * @return {TimeRange} the seekable range
83371   */
83372
83373  seekable() {
83374    return this.seekable_;
83375  }
83376  getSeekableRange_(playlistLoader, mediaType) {
83377    const media = playlistLoader.media();
83378    if (!media) {
83379      return null;
83380    }
83381    const mediaSequenceSync = this.syncController_.getMediaSequenceSync(mediaType);
83382    if (mediaSequenceSync && mediaSequenceSync.isReliable) {
83383      const start = mediaSequenceSync.start;
83384      const end = mediaSequenceSync.end;
83385      if (!isFinite(start) || !isFinite(end)) {
83386        return null;
83387      }
83388      const liveEdgeDelay = Vhs$1.Playlist.liveEdgeDelay(this.mainPlaylistLoader_.main, media); // Make sure our seekable end is not less than the seekable start
83389
83390      const calculatedEnd = Math.max(start, end - liveEdgeDelay);
83391      return createTimeRanges([[start, calculatedEnd]]);
83392    }
83393    const expired = this.syncController_.getExpiredTime(media, this.duration());
83394    if (expired === null) {
83395      return null;
83396    }
83397    const seekable = Vhs$1.Playlist.seekable(media, expired, Vhs$1.Playlist.liveEdgeDelay(this.mainPlaylistLoader_.main, media));
83398    return seekable.length ? seekable : null;
83399  }
83400  computeFinalSeekable_(mainSeekable, audioSeekable) {
83401    if (!audioSeekable) {
83402      return mainSeekable;
83403    }
83404    const mainStart = mainSeekable.start(0);
83405    const mainEnd = mainSeekable.end(0);
83406    const audioStart = audioSeekable.start(0);
83407    const audioEnd = audioSeekable.end(0);
83408    if (audioStart > mainEnd || mainStart > audioEnd) {
83409      // Seekables are far apart, rely on main
83410      return mainSeekable;
83411    } // Return the overlapping seekable range
83412
83413    return createTimeRanges([[Math.max(mainStart, audioStart), Math.min(mainEnd, audioEnd)]]);
83414  }
83415  onSyncInfoUpdate_() {
83416    // TODO check for creation of both source buffers before updating seekable
83417    //
83418    // A fix was made to this function where a check for
83419    // this.sourceUpdater_.hasCreatedSourceBuffers
83420    // was added to ensure that both source buffers were created before seekable was
83421    // updated. However, it originally had a bug where it was checking for a true and
83422    // returning early instead of checking for false. Setting it to check for false to
83423    // return early though created other issues. A call to play() would check for seekable
83424    // end without verifying that a seekable range was present. In addition, even checking
83425    // for that didn't solve some issues, as handleFirstPlay is sometimes worked around
83426    // due to a media update calling load on the segment loaders, skipping a seek to live,
83427    // thereby starting live streams at the beginning of the stream rather than at the end.
83428    //
83429    // This conditional should be fixed to wait for the creation of two source buffers at
83430    // the same time as the other sections of code are fixed to properly seek to live and
83431    // not throw an error due to checking for a seekable end when no seekable range exists.
83432    //
83433    // For now, fall back to the older behavior, with the understanding that the seekable
83434    // range may not be completely correct, leading to a suboptimal initial live point.
83435    if (!this.mainPlaylistLoader_) {
83436      return;
83437    }
83438    const mainSeekable = this.getSeekableRange_(this.mainPlaylistLoader_, 'main');
83439    if (!mainSeekable) {
83440      return;
83441    }
83442    let audioSeekable;
83443    if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
83444      audioSeekable = this.getSeekableRange_(this.mediaTypes_.AUDIO.activePlaylistLoader, 'audio');
83445      if (!audioSeekable) {
83446        return;
83447      }
83448    }
83449    const oldSeekable = this.seekable_;
83450    this.seekable_ = this.computeFinalSeekable_(mainSeekable, audioSeekable);
83451    if (!this.seekable_) {
83452      return;
83453    }
83454    if (oldSeekable && oldSeekable.length && this.seekable_.length) {
83455      if (oldSeekable.start(0) === this.seekable_.start(0) && oldSeekable.end(0) === this.seekable_.end(0)) {
83456        // Seekable range hasn't changed
83457        return;
83458      }
83459    }
83460    this.logger_(`seekable updated [${printableRange(this.seekable_)}]`);
83461    const metadata = {
83462      seekableRanges: this.seekable_
83463    };
83464    this.trigger({
83465      type: 'seekablerangeschanged',
83466      metadata
83467    });
83468    this.tech_.trigger('seekablechanged');
83469  }
83470  /**
83471   * Update the player duration
83472   */
83473
83474  updateDuration(isLive) {
83475    if (this.updateDuration_) {
83476      this.mediaSource.removeEventListener('sourceopen', this.updateDuration_);
83477      this.updateDuration_ = null;
83478    }
83479    if (this.mediaSource.readyState !== 'open') {
83480      this.updateDuration_ = this.updateDuration.bind(this, isLive);
83481      this.mediaSource.addEventListener('sourceopen', this.updateDuration_);
83482      return;
83483    }
83484    if (isLive) {
83485      const seekable = this.seekable();
83486      if (!seekable.length) {
83487        return;
83488      } // Even in the case of a live playlist, the native MediaSource's duration should not
83489      // be set to Infinity (even though this would be expected for a live playlist), since
83490      // setting the native MediaSource's duration to infinity ends up with consequences to
83491      // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details.
83492      //
83493      // This is resolved in the spec by https://github.com/w3c/media-source/pull/92,
83494      // however, few browsers have support for setLiveSeekableRange()
83495      // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange
83496      //
83497      // Until a time when the duration of the media source can be set to infinity, and a
83498      // seekable range specified across browsers, the duration should be greater than or
83499      // equal to the last possible seekable value.
83500      // MediaSource duration starts as NaN
83501      // It is possible (and probable) that this case will never be reached for many
83502      // sources, since the MediaSource reports duration as the highest value without
83503      // accounting for timestamp offset. For example, if the timestamp offset is -100 and
83504      // we buffered times 0 to 100 with real times of 100 to 200, even though current
83505      // time will be between 0 and 100, the native media source may report the duration
83506      // as 200. However, since we report duration separate from the media source (as
83507      // Infinity), and as long as the native media source duration value is greater than
83508      // our reported seekable range, seeks will work as expected. The large number as
83509      // duration for live is actually a strategy used by some players to work around the
83510      // issue of live seekable ranges cited above.
83511
83512      if (isNaN(this.mediaSource.duration) || this.mediaSource.duration < seekable.end(seekable.length - 1)) {
83513        this.sourceUpdater_.setDuration(seekable.end(seekable.length - 1));
83514      }
83515      return;
83516    }
83517    const buffered = this.tech_.buffered();
83518    let duration = Vhs$1.Playlist.duration(this.mainPlaylistLoader_.media());
83519    if (buffered.length > 0) {
83520      duration = Math.max(duration, buffered.end(buffered.length - 1));
83521    }
83522    if (this.mediaSource.duration !== duration) {
83523      this.sourceUpdater_.setDuration(duration);
83524    }
83525  }
83526  /**
83527   * dispose of the PlaylistController and everything
83528   * that it controls
83529   */
83530
83531  dispose() {
83532    this.trigger('dispose');
83533    this.decrypter_.terminate();
83534    this.mainPlaylistLoader_.dispose();
83535    this.mainSegmentLoader_.dispose();
83536    this.contentSteeringController_.dispose();
83537    this.keyStatusMap_.clear();
83538    if (this.loadOnPlay_) {
83539      this.tech_.off('play', this.loadOnPlay_);
83540    }
83541    ['AUDIO', 'SUBTITLES'].forEach(type => {
83542      const groups = this.mediaTypes_[type].groups;
83543      for (const id in groups) {
83544        groups[id].forEach(group => {
83545          if (group.playlistLoader) {
83546            group.playlistLoader.dispose();
83547          }
83548        });
83549      }
83550    });
83551    this.audioSegmentLoader_.dispose();
83552    this.subtitleSegmentLoader_.dispose();
83553    this.sourceUpdater_.dispose();
83554    this.timelineChangeController_.dispose();
83555    this.stopABRTimer_();
83556    if (this.updateDuration_) {
83557      this.mediaSource.removeEventListener('sourceopen', this.updateDuration_);
83558    }
83559    this.mediaSource.removeEventListener('durationchange', this.handleDurationChange_); // load the media source into the player
83560
83561    this.mediaSource.removeEventListener('sourceopen', this.handleSourceOpen_);
83562    this.mediaSource.removeEventListener('sourceended', this.handleSourceEnded_);
83563    this.off();
83564  }
83565  /**
83566   * return the main playlist object if we have one
83567   *
83568   * @return {Object} the main playlist object that we parsed
83569   */
83570
83571  main() {
83572    return this.mainPlaylistLoader_.main;
83573  }
83574  /**
83575   * return the currently selected playlist
83576   *
83577   * @return {Object} the currently selected playlist object that we parsed
83578   */
83579
83580  media() {
83581    // playlist loader will not return media if it has not been fully loaded
83582    return this.mainPlaylistLoader_.media() || this.initialMedia_;
83583  }
83584  areMediaTypesKnown_() {
83585    const usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader;
83586    const hasMainMediaInfo = !!this.mainSegmentLoader_.getCurrentMediaInfo_(); // if we are not using an audio loader, then we have audio media info
83587    // otherwise check on the segment loader.
83588
83589    const hasAudioMediaInfo = !usingAudioLoader ? true : !!this.audioSegmentLoader_.getCurrentMediaInfo_(); // one or both loaders has not loaded sufficently to get codecs
83590
83591    if (!hasMainMediaInfo || !hasAudioMediaInfo) {
83592      return false;
vendor: 17,483 bytes, lines 83593-84020
83593    }
83594    return true;
83595  } // find from and to for codec switch event
83596
83597  getCodecsOrExclude_() {
83598    const media = {
83599      main: this.mainSegmentLoader_.getCurrentMediaInfo_() || {},
83600      audio: this.audioSegmentLoader_.getCurrentMediaInfo_() || {}
83601    };
83602    const playlist = this.mainSegmentLoader_.getPendingSegmentPlaylist() || this.media(); // set "main" media equal to video
83603
83604    media.video = media.main;
83605    const playlistCodecs = codecsForPlaylist(this.main(), playlist);
83606    const codecs = {};
83607    const usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader;
83608    if (media.main.hasVideo) {
83609      codecs.video = playlistCodecs.video || media.main.videoCodec || codecs_js.DEFAULT_VIDEO_CODEC;
83610    }
83611    if (media.main.isMuxed) {
83612      codecs.video += `,${playlistCodecs.audio || media.main.audioCodec || codecs_js.DEFAULT_AUDIO_CODEC}`;
83613    }
83614    if (media.main.hasAudio && !media.main.isMuxed || media.audio.hasAudio || usingAudioLoader) {
83615      codecs.audio = playlistCodecs.audio || media.main.audioCodec || media.audio.audioCodec || codecs_js.DEFAULT_AUDIO_CODEC; // set audio isFmp4 so we use the correct "supports" function below
83616
83617      media.audio.isFmp4 = media.main.hasAudio && !media.main.isMuxed ? media.main.isFmp4 : media.audio.isFmp4;
83618    } // no codecs, no playback.
83619
83620    if (!codecs.audio && !codecs.video) {
83621      this.excludePlaylist({
83622        playlistToExclude: playlist,
83623        error: {
83624          message: 'Could not determine codecs for playlist.'
83625        },
83626        playlistExclusionDuration: Infinity
83627      });
83628      return;
83629    } // fmp4 relies on browser support, while ts relies on muxer support
83630
83631    const supportFunction = (isFmp4, codec) => isFmp4 ? codecs_js.browserSupportsCodec(codec, this.usingManagedMediaSource_) : codecs_js.muxerSupportsCodec(codec);
83632    const unsupportedCodecs = {};
83633    let unsupportedAudio;
83634    ['video', 'audio'].forEach(function (type) {
83635      if (codecs.hasOwnProperty(type) && !supportFunction(media[type].isFmp4, codecs[type])) {
83636        const supporter = media[type].isFmp4 ? 'browser' : 'muxer';
83637        unsupportedCodecs[supporter] = unsupportedCodecs[supporter] || [];
83638        unsupportedCodecs[supporter].push(codecs[type]);
83639        if (type === 'audio') {
83640          unsupportedAudio = supporter;
83641        }
83642      }
83643    });
83644    if (usingAudioLoader && unsupportedAudio && playlist.attributes.AUDIO) {
83645      const audioGroup = playlist.attributes.AUDIO;
83646      this.main().playlists.forEach(variant => {
83647        const variantAudioGroup = variant.attributes && variant.attributes.AUDIO;
83648        if (variantAudioGroup === audioGroup && variant !== playlist) {
83649          variant.excludeUntil = Infinity;
83650        }
83651      });
83652      this.logger_(`excluding audio group ${audioGroup} as ${unsupportedAudio} does not support codec(s): "${codecs.audio}"`);
83653    } // if we have any unsupported codecs exclude this playlist.
83654
83655    if (Object.keys(unsupportedCodecs).length) {
83656      const message = Object.keys(unsupportedCodecs).reduce((acc, supporter) => {
83657        if (acc) {
83658          acc += ', ';
83659        }
83660        acc += `${supporter} does not support codec(s): "${unsupportedCodecs[supporter].join(',')}"`;
83661        return acc;
83662      }, '') + '.';
83663      this.excludePlaylist({
83664        playlistToExclude: playlist,
83665        error: {
83666          internal: true,
83667          message
83668        },
83669        playlistExclusionDuration: Infinity
83670      });
83671      return;
83672    } // check if codec switching is happening
83673
83674    if (this.sourceUpdater_.hasCreatedSourceBuffers() && !this.sourceUpdater_.canChangeType()) {
83675      const switchMessages = [];
83676      ['video', 'audio'].forEach(type => {
83677        const newCodec = (codecs_js.parseCodecs(this.sourceUpdater_.codecs[type] || '')[0] || {}).type;
83678        const oldCodec = (codecs_js.parseCodecs(codecs[type] || '')[0] || {}).type;
83679        if (newCodec && oldCodec && newCodec.toLowerCase() !== oldCodec.toLowerCase()) {
83680          switchMessages.push(`"${this.sourceUpdater_.codecs[type]}" -> "${codecs[type]}"`);
83681        }
83682      });
83683      if (switchMessages.length) {
83684        this.excludePlaylist({
83685          playlistToExclude: playlist,
83686          error: {
83687            message: `Codec switching not supported: ${switchMessages.join(', ')}.`,
83688            internal: true
83689          },
83690          playlistExclusionDuration: Infinity
83691        });
83692        return;
83693      }
83694    } // TODO: when using the muxer shouldn't we just return
83695    // the codecs that the muxer outputs?
83696
83697    return codecs;
83698  }
83699  /**
83700   * Create source buffers and exlude any incompatible renditions.
83701   *
83702   * @private
83703   */
83704
83705  tryToCreateSourceBuffers_() {
83706    // media source is not ready yet or sourceBuffers are already
83707    // created.
83708    if (this.mediaSource.readyState !== 'open' || this.sourceUpdater_.hasCreatedSourceBuffers()) {
83709      return;
83710    }
83711    if (!this.areMediaTypesKnown_()) {
83712      return;
83713    }
83714    const codecs = this.getCodecsOrExclude_(); // no codecs means that the playlist was excluded
83715
83716    if (!codecs) {
83717      return;
83718    }
83719    this.sourceUpdater_.createSourceBuffers(codecs);
83720    const codecString = [codecs.video, codecs.audio].filter(Boolean).join(',');
83721    this.excludeIncompatibleVariants_(codecString);
83722  }
83723  /**
83724   * Excludes playlists with codecs that are unsupported by the muxer and browser.
83725   */
83726
83727  excludeUnsupportedVariants_() {
83728    const playlists = this.main().playlists;
83729    const ids = []; // TODO: why don't we have a property to loop through all
83730    // playlist? Why did we ever mix indexes and keys?
83731
83732    Object.keys(playlists).forEach(key => {
83733      const variant = playlists[key]; // check if we already processed this playlist.
83734
83735      if (ids.indexOf(variant.id) !== -1) {
83736        return;
83737      }
83738      ids.push(variant.id);
83739      const codecs = codecsForPlaylist(this.main, variant);
83740      const unsupported = [];
83741      if (codecs.audio && !codecs_js.muxerSupportsCodec(codecs.audio) && !codecs_js.browserSupportsCodec(codecs.audio, this.usingManagedMediaSource_)) {
83742        unsupported.push(`audio codec ${codecs.audio}`);
83743      }
83744      if (codecs.video && !codecs_js.muxerSupportsCodec(codecs.video) && !codecs_js.browserSupportsCodec(codecs.video, this.usingManagedMediaSource_)) {
83745        unsupported.push(`video codec ${codecs.video}`);
83746      }
83747      if (codecs.text && codecs.text === 'stpp.ttml.im1t') {
83748        unsupported.push(`text codec ${codecs.text}`);
83749      }
83750      if (unsupported.length) {
83751        variant.excludeUntil = Infinity;
83752        this.logger_(`excluding ${variant.id} for unsupported: ${unsupported.join(', ')}`);
83753      }
83754    });
83755  }
83756  /**
83757   * Exclude playlists that are known to be codec or
83758   * stream-incompatible with the SourceBuffer configuration. For
83759   * instance, Media Source Extensions would cause the video element to
83760   * stall waiting for video data if you switched from a variant with
83761   * video and audio to an audio-only one.
83762   *
83763   * @param {Object} media a media playlist compatible with the current
83764   * set of SourceBuffers. Variants in the current main playlist that
83765   * do not appear to have compatible codec or stream configurations
83766   * will be excluded from the default playlist selection algorithm
83767   * indefinitely.
83768   * @private
83769   */
83770
83771  excludeIncompatibleVariants_(codecString) {
83772    const ids = [];
83773    const playlists = this.main().playlists;
83774    const codecs = unwrapCodecList(codecs_js.parseCodecs(codecString));
83775    const codecCount_ = codecCount(codecs);
83776    const videoDetails = codecs.video && codecs_js.parseCodecs(codecs.video)[0] || null;
83777    const audioDetails = codecs.audio && codecs_js.parseCodecs(codecs.audio)[0] || null;
83778    Object.keys(playlists).forEach(key => {
83779      const variant = playlists[key]; // check if we already processed this playlist.
83780      // or it if it is already excluded forever.
83781
83782      if (ids.indexOf(variant.id) !== -1 || variant.excludeUntil === Infinity) {
83783        return;
83784      }
83785      ids.push(variant.id);
83786      const exclusionReasons = []; // get codecs from the playlist for this variant
83787
83788      const variantCodecs = codecsForPlaylist(this.mainPlaylistLoader_.main, variant);
83789      const variantCodecCount = codecCount(variantCodecs); // if no codecs are listed, we cannot determine that this
83790      // variant is incompatible. Wait for mux.js to probe
83791
83792      if (!variantCodecs.audio && !variantCodecs.video) {
83793        return;
83794      } // TODO: we can support this by removing the
83795      // old media source and creating a new one, but it will take some work.
83796      // The number of streams cannot change
83797
83798      if (variantCodecCount !== codecCount_) {
83799        exclusionReasons.push(`codec count "${variantCodecCount}" !== "${codecCount_}"`);
83800      } // only exclude playlists by codec change, if codecs cannot switch
83801      // during playback.
83802
83803      if (!this.sourceUpdater_.canChangeType()) {
83804        const variantVideoDetails = variantCodecs.video && codecs_js.parseCodecs(variantCodecs.video)[0] || null;
83805        const variantAudioDetails = variantCodecs.audio && codecs_js.parseCodecs(variantCodecs.audio)[0] || null; // the video codec cannot change
83806
83807        if (variantVideoDetails && videoDetails && variantVideoDetails.type.toLowerCase() !== videoDetails.type.toLowerCase()) {
83808          exclusionReasons.push(`video codec "${variantVideoDetails.type}" !== "${videoDetails.type}"`);
83809        } // the audio codec cannot change
83810
83811        if (variantAudioDetails && audioDetails && variantAudioDetails.type.toLowerCase() !== audioDetails.type.toLowerCase()) {
83812          exclusionReasons.push(`audio codec "${variantAudioDetails.type}" !== "${audioDetails.type}"`);
83813        }
83814      }
83815      if (exclusionReasons.length) {
83816        variant.excludeUntil = Infinity;
83817        this.logger_(`excluding ${variant.id}: ${exclusionReasons.join(' && ')}`);
83818      }
83819    });
83820  }
83821  updateAdCues_(media) {
83822    let offset = 0;
83823    const seekable = this.seekable();
83824    if (seekable.length) {
83825      offset = seekable.start(0);
83826    }
83827    updateAdCues(media, this.cueTagsTrack_, offset);
83828  }
83829  /**
83830   * Calculates the desired forward buffer length based on current time
83831   *
83832   * @return {number} Desired forward buffer length in seconds
83833   */
83834
83835  goalBufferLength() {
83836    const currentTime = this.tech_.currentTime();
83837    const initial = Config.GOAL_BUFFER_LENGTH;
83838    const rate = Config.GOAL_BUFFER_LENGTH_RATE;
83839    const max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
83840    return Math.min(initial + currentTime * rate, max);
83841  }
83842  /**
83843   * Calculates the desired buffer low water line based on current time
83844   *
83845   * @return {number} Desired buffer low water line in seconds
83846   */
83847
83848  bufferLowWaterLine() {
83849    const currentTime = this.tech_.currentTime();
83850    const initial = Config.BUFFER_LOW_WATER_LINE;
83851    const rate = Config.BUFFER_LOW_WATER_LINE_RATE;
83852    const max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
83853    const newMax = Math.max(initial, Config.EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE);
83854    return Math.min(initial + currentTime * rate, this.bufferBasedABR ? newMax : max);
83855  }
83856  bufferHighWaterLine() {
83857    return Config.BUFFER_HIGH_WATER_LINE;
83858  }
83859  addDateRangesToTextTrack_(dateRanges) {
83860    createMetadataTrackIfNotExists(this.inbandTextTracks_, 'com.apple.streaming', this.tech_);
83861    addDateRangeMetadata({
83862      inbandTextTracks: this.inbandTextTracks_,
83863      dateRanges
83864    });
83865  }
83866  addMetadataToTextTrack(dispatchType, metadataArray, videoDuration) {
83867    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
83868    // audio/video source with a metadata track, and an alt audio with a metadata track.
83869    // However, this probably won't happen, and if it does it can be handled then.
83870
83871    createMetadataTrackIfNotExists(this.inbandTextTracks_, dispatchType, this.tech_);
83872    addMetadata({
83873      inbandTextTracks: this.inbandTextTracks_,
83874      metadataArray,
83875      timestampOffset,
83876      videoDuration
83877    });
83878  }
83879  /**
83880   * Utility for getting the pathway or service location from an HLS or DASH playlist.
83881   *
83882   * @param {Object} playlist for getting pathway from.
83883   * @return the pathway attribute of a playlist
83884   */
83885
83886  pathwayAttribute_(playlist) {
83887    return playlist.attributes['PATHWAY-ID'] || playlist.attributes.serviceLocation;
83888  }
83889  /**
83890   * Initialize available pathways and apply the tag properties.
83891   */
83892
83893  initContentSteeringController_() {
83894    const main = this.main();
83895    if (!main.contentSteering) {
83896      return;
83897    }
83898    for (const playlist of main.playlists) {
83899      this.contentSteeringController_.addAvailablePathway(this.pathwayAttribute_(playlist));
83900    }
83901    this.contentSteeringController_.assignTagProperties(main.uri, main.contentSteering); // request the steering manifest immediately if queryBeforeStart is set.
83902
83903    if (this.contentSteeringController_.queryBeforeStart) {
83904      // When queryBeforeStart is true, initial request should omit steering parameters.
83905      this.contentSteeringController_.requestSteeringManifest(true);
83906      return;
83907    } // otherwise start content steering after playback starts
83908
83909    this.tech_.one('canplay', () => {
83910      this.contentSteeringController_.requestSteeringManifest();
83911    });
83912  }
83913  /**
83914   * Reset the content steering controller and re-init.
83915   */
83916
83917  resetContentSteeringController_() {
83918    this.contentSteeringController_.clearAvailablePathways();
83919    this.contentSteeringController_.dispose();
83920    this.initContentSteeringController_();
83921  }
83922  /**
83923   * Attaches the listeners for content steering.
83924   */
83925
83926  attachContentSteeringListeners_() {
83927    this.contentSteeringController_.on('content-steering', this.excludeThenChangePathway_.bind(this));
83928    const contentSteeringEvents = ['contentsteeringloadstart', 'contentsteeringloadcomplete', 'contentsteeringparsed'];
83929    contentSteeringEvents.forEach(eventName => {
83930      this.contentSteeringController_.on(eventName, metadata => {
83931        this.trigger(_extends__default["default"]({}, metadata));
83932      });
83933    });
83934    if (this.sourceType_ === 'dash') {
83935      this.mainPlaylistLoader_.on('loadedplaylist', () => {
83936        const main = this.main(); // check if steering tag or pathways changed.
83937
83938        const didDashTagChange = this.contentSteeringController_.didDASHTagChange(main.uri, main.contentSteering);
83939        const didPathwaysChange = () => {
83940          const availablePathways = this.contentSteeringController_.getAvailablePathways();
83941          const newPathways = [];
83942          for (const playlist of main.playlists) {
83943            const serviceLocation = playlist.attributes.serviceLocation;
83944            if (serviceLocation) {
83945              newPathways.push(serviceLocation);
83946              if (!availablePathways.has(serviceLocation)) {
83947                return true;
83948              }
83949            }
83950          } // If we have no new serviceLocations and previously had availablePathways
83951
83952          if (!newPathways.length && availablePathways.size) {
83953            return true;
83954          }
83955          return false;
83956        };
83957        if (didDashTagChange || didPathwaysChange()) {
83958          this.resetContentSteeringController_();
83959        }
83960      });
83961    }
83962  }
83963  /**
83964   * Simple exclude and change playlist logic for content steering.
83965   */
83966
83967  excludeThenChangePathway_() {
83968    const currentPathway = this.contentSteeringController_.getPathway();
83969    if (!currentPathway) {
83970      return;
83971    }
83972    this.handlePathwayClones_();
83973    const main = this.main();
83974    const playlists = main.playlists;
83975    const ids = new Set();
83976    let didEnablePlaylists = false;
83977    Object.keys(playlists).forEach(key => {
83978      const variant = playlists[key];
83979      const pathwayId = this.pathwayAttribute_(variant);
83980      const differentPathwayId = pathwayId && currentPathway !== pathwayId;
83981      const steeringExclusion = variant.excludeUntil === Infinity && variant.lastExcludeReason_ === 'content-steering';
83982      if (steeringExclusion && !differentPathwayId) {
83983        delete variant.excludeUntil;
83984        delete variant.lastExcludeReason_;
83985        didEnablePlaylists = true;
83986      }
83987      const noExcludeUntil = !variant.excludeUntil && variant.excludeUntil !== Infinity;
83988      const shouldExclude = !ids.has(variant.id) && differentPathwayId && noExcludeUntil;
83989      if (!shouldExclude) {
83990        return;
83991      }
83992      ids.add(variant.id);
83993      variant.excludeUntil = Infinity;
83994      variant.lastExcludeReason_ = 'content-steering'; // TODO: kind of spammy, maybe move this.
83995
83996      this.logger_(`excluding ${variant.id} for ${variant.lastExcludeReason_}`);
83997    });
83998    if (this.contentSteeringController_.manifestType_ === 'DASH') {
83999      Object.keys(this.mediaTypes_).forEach(key => {
84000        const type = this.mediaTypes_[key];
84001        if (type.activePlaylistLoader) {
84002          const currentPlaylist = type.activePlaylistLoader.media_; // Check if the current media playlist matches the current CDN
84003
84004          if (currentPlaylist && currentPlaylist.attributes.serviceLocation !== currentPathway) {
84005            didEnablePlaylists = true;
84006          }
84007        }
84008      });
84009    }
84010    if (didEnablePlaylists) {
84011      this.changeSegmentPathway_();
84012    }
84013  }
84014  /**
84015   * Add, update, or delete playlists and media groups for
84016   * the pathway clones for HLS Content Steering.
84017   *
84018   * See https://datatracker.ietf.org/doc/draft-pantos-hls-rfc8216bis/
84019   *
84020   * NOTE: Pathway cloning does not currently support the `PER_VARIA
vendor: 12,780 bytes, lines 84020-84342
84020NT_URIS` and
84021   * `PER_RENDITION_URIS` as we do not handle `STABLE-VARIANT-ID` or
84022   * `STABLE-RENDITION-ID` values.
84023   */
84024
84025  handlePathwayClones_() {
84026    const main = this.main();
84027    const playlists = main.playlists;
84028    const currentPathwayClones = this.contentSteeringController_.currentPathwayClones;
84029    const nextPathwayClones = this.contentSteeringController_.nextPathwayClones;
84030    const hasClones = currentPathwayClones && currentPathwayClones.size || nextPathwayClones && nextPathwayClones.size;
84031    if (!hasClones) {
84032      return;
84033    }
84034    for (const [id, clone] of currentPathwayClones.entries()) {
84035      const newClone = nextPathwayClones.get(id); // Delete the old pathway clone.
84036
84037      if (!newClone) {
84038        this.mainPlaylistLoader_.updateOrDeleteClone(clone);
84039        this.contentSteeringController_.excludePathway(id);
84040      }
84041    }
84042    for (const [id, clone] of nextPathwayClones.entries()) {
84043      const oldClone = currentPathwayClones.get(id); // Create a new pathway if it is a new pathway clone object.
84044
84045      if (!oldClone) {
84046        const playlistsToClone = playlists.filter(p => {
84047          return p.attributes['PATHWAY-ID'] === clone['BASE-ID'];
84048        });
84049        playlistsToClone.forEach(p => {
84050          this.mainPlaylistLoader_.addClonePathway(clone, p);
84051        });
84052        this.contentSteeringController_.addAvailablePathway(id);
84053        continue;
84054      } // There have not been changes to the pathway clone object, so skip.
84055
84056      if (this.equalPathwayClones_(oldClone, clone)) {
84057        continue;
84058      } // Update a preexisting cloned pathway.
84059      // True is set for the update flag.
84060
84061      this.mainPlaylistLoader_.updateOrDeleteClone(clone, true);
84062      this.contentSteeringController_.addAvailablePathway(id);
84063    } // Deep copy contents of next to current pathways.
84064
84065    this.contentSteeringController_.currentPathwayClones = new Map(JSON.parse(JSON.stringify([...nextPathwayClones])));
84066  }
84067  /**
84068   * Determines whether two pathway clone objects are equivalent.
84069   *
84070   * @param {Object} a The first pathway clone object.
84071   * @param {Object} b The second pathway clone object.
84072   * @return {boolean} True if the pathway clone objects are equal, false otherwise.
84073   */
84074
84075  equalPathwayClones_(a, b) {
84076    if (a['BASE-ID'] !== b['BASE-ID'] || a.ID !== b.ID || a['URI-REPLACEMENT'].HOST !== b['URI-REPLACEMENT'].HOST) {
84077      return false;
84078    }
84079    const aParams = a['URI-REPLACEMENT'].PARAMS;
84080    const bParams = b['URI-REPLACEMENT'].PARAMS; // We need to iterate through both lists of params because one could be
84081    // missing a parameter that the other has.
84082
84083    for (const p in aParams) {
84084      if (aParams[p] !== bParams[p]) {
84085        return false;
84086      }
84087    }
84088    for (const p in bParams) {
84089      if (aParams[p] !== bParams[p]) {
84090        return false;
84091      }
84092    }
84093    return true;
84094  }
84095  /**
84096   * Changes the current playlists for audio, video and subtitles after a new pathway
84097   * is chosen from content steering.
84098   */
84099
84100  changeSegmentPathway_() {
84101    const nextPlaylist = this.selectPlaylist();
84102    this.pauseLoading(); // Switch audio and text track playlists if necessary in DASH
84103
84104    if (this.contentSteeringController_.manifestType_ === 'DASH') {
84105      this.switchMediaForDASHContentSteering_();
84106    }
84107    this.switchMedia_(nextPlaylist, 'content-steering');
84108  }
84109  /**
84110   * Iterates through playlists and check their keyId set and compare with the
84111   * keyStatusMap, only enable playlists that have a usable key. If the playlist
84112   * has no keyId leave it enabled by default.
84113   */
84114
84115  excludeNonUsablePlaylistsByKeyId_() {
84116    if (!this.mainPlaylistLoader_ || !this.mainPlaylistLoader_.main) {
84117      return;
84118    }
84119    let nonUsableKeyStatusCount = 0;
84120    const NON_USABLE = 'non-usable';
84121    this.mainPlaylistLoader_.main.playlists.forEach(playlist => {
84122      const keyIdSet = this.mainPlaylistLoader_.getKeyIdSet(playlist); // If the playlist doesn't have keyIDs lets not exclude it.
84123
84124      if (!keyIdSet || !keyIdSet.size) {
84125        return;
84126      }
84127      keyIdSet.forEach(key => {
84128        const USABLE = 'usable';
84129        const hasUsableKeyStatus = this.keyStatusMap_.has(key) && this.keyStatusMap_.get(key) === USABLE;
84130        const nonUsableExclusion = playlist.lastExcludeReason_ === NON_USABLE && playlist.excludeUntil === Infinity;
84131        if (!hasUsableKeyStatus) {
84132          // Only exclude playlists that haven't already been excluded as non-usable.
84133          if (playlist.excludeUntil !== Infinity && playlist.lastExcludeReason_ !== NON_USABLE) {
84134            playlist.excludeUntil = Infinity;
84135            playlist.lastExcludeReason_ = NON_USABLE;
84136            this.logger_(`excluding playlist ${playlist.id} because the key ID ${key} doesn't exist in the keyStatusMap or is not ${USABLE}`);
84137          } // count all nonUsableKeyStatus
84138
84139          nonUsableKeyStatusCount++;
84140        } else if (hasUsableKeyStatus && nonUsableExclusion) {
84141          delete playlist.excludeUntil;
84142          delete playlist.lastExcludeReason_;
84143          this.logger_(`enabling playlist ${playlist.id} because key ID ${key} is ${USABLE}`);
84144        }
84145      });
84146    }); // If for whatever reason every playlist has a non usable key status. Lets try re-including the SD renditions as a failsafe.
84147
84148    if (nonUsableKeyStatusCount >= this.mainPlaylistLoader_.main.playlists.length) {
84149      this.mainPlaylistLoader_.main.playlists.forEach(playlist => {
84150        const isNonHD = playlist && playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height < 720;
84151        const excludedForNonUsableKey = playlist.excludeUntil === Infinity && playlist.lastExcludeReason_ === NON_USABLE;
84152        if (isNonHD && excludedForNonUsableKey) {
84153          // Only delete the excludeUntil so we don't try and re-exclude these playlists.
84154          delete playlist.excludeUntil;
84155          videojs.log.warn(`enabling non-HD playlist ${playlist.id} because all playlists were excluded due to ${NON_USABLE} key IDs`);
84156        }
84157      });
84158    }
84159  }
84160  /**
84161   * Adds a keystatus to the keystatus map, tries to convert to string if necessary.
84162   *
84163   * @param {any} keyId the keyId to add a status for
84164   * @param {string} status the status of the keyId
84165   */
84166
84167  addKeyStatus_(keyId, status) {
84168    const isString = typeof keyId === 'string';
84169    const keyIdHexString = isString ? keyId : bufferToHexString(keyId);
84170    const formattedKeyIdString = keyIdHexString.slice(0, 32).toLowerCase();
84171    this.logger_(`KeyStatus '${status}' with key ID ${formattedKeyIdString} added to the keyStatusMap`);
84172    this.keyStatusMap_.set(formattedKeyIdString, status);
84173  }
84174  /**
84175   * Utility function for adding key status to the keyStatusMap and filtering usable encrypted playlists.
84176   *
84177   * @param {any} keyId the keyId from the keystatuschange event
84178   * @param {string} status the key status string
84179   */
84180
84181  updatePlaylistByKeyStatus(keyId, status) {
84182    this.addKeyStatus_(keyId, status);
84183    if (!this.waitingForFastQualityPlaylistReceived_) {
84184      this.excludeNonUsableThenChangePlaylist_();
84185    } // Listen to loadedplaylist with a single listener and check for new contentProtection elements when a playlist is updated.
84186
84187    this.mainPlaylistLoader_.off('loadedplaylist', this.excludeNonUsableThenChangePlaylist_.bind(this));
84188    this.mainPlaylistLoader_.on('loadedplaylist', this.excludeNonUsableThenChangePlaylist_.bind(this));
84189  }
84190  excludeNonUsableThenChangePlaylist_() {
84191    this.excludeNonUsablePlaylistsByKeyId_();
84192    this.fastQualityChange_();
84193  }
84194}
84195
84196/**
84197 * Returns a function that acts as the Enable/disable playlist function.
84198 *
84199 * @param {PlaylistLoader} loader - The main playlist loader
84200 * @param {string} playlistID - id of the playlist
84201 * @param {Function} changePlaylistFn - A function to be called after a
84202 * playlist's enabled-state has been changed. Will NOT be called if a
84203 * playlist's enabled-state is unchanged
84204 * @param {boolean=} enable - Value to set the playlist enabled-state to
84205 * or if undefined returns the current enabled-state for the playlist
84206 * @return {Function} Function for setting/getting enabled
84207 */
84208
84209const enableFunction = (loader, playlistID, changePlaylistFn) => enable => {
84210  const playlist = loader.main.playlists[playlistID];
84211  const incompatible = isIncompatible(playlist);
84212  const currentlyEnabled = isEnabled(playlist);
84213  if (typeof enable === 'undefined') {
84214    return currentlyEnabled;
84215  }
84216  if (enable) {
84217    delete playlist.disabled;
84218  } else {
84219    playlist.disabled = true;
84220  }
84221  const metadata = {
84222    renditionInfo: {
84223      id: playlistID,
84224      bandwidth: playlist.attributes.BANDWIDTH,
84225      resolution: playlist.attributes.RESOLUTION,
84226      codecs: playlist.attributes.CODECS
84227    },
84228    cause: 'fast-quality'
84229  };
84230  if (enable !== currentlyEnabled && !incompatible) {
84231    // Ensure the outside world knows about our changes
84232    if (enable) {
84233      // call fast quality change only when the playlist is enabled
84234      changePlaylistFn(playlist);
84235      loader.trigger({
84236        type: 'renditionenabled',
84237        metadata
84238      });
84239    } else {
84240      loader.trigger({
84241        type: 'renditiondisabled',
84242        metadata
84243      });
84244    }
84245  }
84246  return enable;
84247};
84248/**
84249 * The representation object encapsulates the publicly visible information
84250 * in a media playlist along with a setter/getter-type function (enabled)
84251 * for changing the enabled-state of a particular playlist entry
84252 *
84253 * @class Representation
84254 */
84255
84256class Representation {
84257  constructor(vhsHandler, playlist, id) {
84258    const {
84259      playlistController_: pc
84260    } = vhsHandler;
84261    const qualityChangeFunction = pc.fastQualityChange_.bind(pc); // some playlist attributes are optional
84262
84263    if (playlist.attributes) {
84264      const resolution = playlist.attributes.RESOLUTION;
84265      this.width = resolution && resolution.width;
84266      this.height = resolution && resolution.height;
84267      this.bandwidth = playlist.attributes.BANDWIDTH;
84268      this.frameRate = playlist.attributes['FRAME-RATE'];
84269    }
84270    this.codecs = codecsForPlaylist(pc.main(), playlist);
84271    this.playlist = playlist; // The id is simply the ordinality of the media playlist
84272    // within the main playlist
84273
84274    this.id = id; // Partially-apply the enableFunction to create a playlist-
84275    // specific variant
84276
84277    this.enabled = enableFunction(vhsHandler.playlists, playlist.id, qualityChangeFunction);
84278  }
84279}
84280/**
84281 * A mixin function that adds the `representations` api to an instance
84282 * of the VhsHandler class
84283 *
84284 * @param {VhsHandler} vhsHandler - An instance of VhsHandler to add the
84285 * representation API into
84286 */
84287
84288const renditionSelectionMixin = function (vhsHandler) {
84289  // Add a single API-specific function to the VhsHandler instance
84290  vhsHandler.representations = () => {
84291    const main = vhsHandler.playlistController_.main();
84292    const playlists = isAudioOnly(main) ? vhsHandler.playlistController_.getAudioTrackPlaylists_() : main.playlists;
84293    if (!playlists) {
84294      return [];
84295    }
84296    return playlists.filter(media => !isIncompatible(media)).map((e, i) => new Representation(vhsHandler, e, e.id));
84297  };
84298};
84299
84300/**
84301 * @file playback-watcher.js
84302 *
84303 * Playback starts, and now my watch begins. It shall not end until my death. I shall
84304 * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
84305 * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
84306 * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
84307 * my life and honor to the Playback Watch, for this Player and all the Players to come.
84308 */
84309
84310const timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
84311/**
84312 * @class PlaybackWatcher
84313 */
84314
84315class PlaybackWatcher extends videojs.EventTarget {
84316  /**
84317   * Represents an PlaybackWatcher object.
84318   *
84319   * @class
84320   * @param {Object} options an object that includes the tech and settings
84321   */
84322  constructor(options) {
84323    super();
84324    this.playlistController_ = options.playlistController;
84325    this.tech_ = options.tech;
84326    this.seekable = options.seekable;
84327    this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow;
84328    this.liveRangeSafeTimeDelta = options.liveRangeSafeTimeDelta;
84329    this.media = options.media;
84330    this.playedRanges_ = [];
84331    this.consecutiveUpdates = 0;
84332    this.lastRecordedTime = null;
84333    this.checkCurrentTimeTimeout_ = null;
84334    this.logger_ = logger('PlaybackWatcher');
84335    this.logger_('initialize');
84336    const playHandler = () => this.monitorCurrentTime_();
84337    const canPlayHandler = () => this.monitorCurrentTime_();
84338    const waitingHandler = () => this.techWaiting_();
84339    const cancelTimerHandler = () => this.resetTimeUpdate_();
84340    const pc = this.playlistController_;
84341    const loaderTypes = ['main', 'subtitle', 'audio'];
84342    const loaderChecks = {};
vendor: 8,881 bytes, lines 84343-84573
84343    loaderTypes.forEach(type => {
84344      loaderChecks[type] = {
84345        reset: () => this.resetSegmentDownloads_(type),
84346        updateend: () => this.checkSegmentDownloads_(type)
84347      };
84348      pc[`${type}SegmentLoader_`].on('appendsdone', loaderChecks[type].updateend); // If a rendition switch happens during a playback stall where the buffer
84349      // isn't changing we want to reset. We cannot assume that the new rendition
84350      // will also be stalled, until after new appends.
84351
84352      pc[`${type}SegmentLoader_`].on('playlistupdate', loaderChecks[type].reset); // Playback stalls should not be detected right after seeking.
84353      // This prevents one segment playlists (single vtt or single segment content)
84354      // from being detected as stalling. As the buffer will not change in those cases, since
84355      // the buffer is the entire video duration.
84356
84357      this.tech_.on(['seeked', 'seeking'], loaderChecks[type].reset);
84358    });
84359    /**
84360     * We check if a seek was into a gap through the following steps:
84361     * 1. We get a seeking event and we do not get a seeked event. This means that
84362     *    a seek was attempted but not completed.
84363     * 2. We run `fixesBadSeeks_` on segment loader appends. This means that we already
84364     *    removed everything from our buffer and appended a segment, and should be ready
84365     *    to check for gaps.
84366     */
84367
84368    const setSeekingHandlers = fn => {
84369      ['main', 'audio'].forEach(type => {
84370        pc[`${type}SegmentLoader_`][fn]('appended', this.seekingAppendCheck_);
84371      });
84372    };
84373    this.seekingAppendCheck_ = () => {
84374      if (this.fixesBadSeeks_()) {
84375        this.consecutiveUpdates = 0;
84376        this.lastRecordedTime = this.tech_.currentTime();
84377        setSeekingHandlers('off');
84378      }
84379    };
84380    this.clearSeekingAppendCheck_ = () => setSeekingHandlers('off');
84381    this.watchForBadSeeking_ = () => {
84382      this.clearSeekingAppendCheck_();
84383      setSeekingHandlers('on');
84384    };
84385    this.tech_.on('seeked', this.clearSeekingAppendCheck_);
84386    this.tech_.on('seeking', this.watchForBadSeeking_);
84387    this.tech_.on('waiting', waitingHandler);
84388    this.tech_.on(timerCancelEvents, cancelTimerHandler);
84389    this.tech_.on('canplay', canPlayHandler);
84390    /*
84391      An edge case exists that results in gaps not being skipped when they exist at the beginning of a stream. This case
84392      is surfaced in one of two ways:
84393       1)  The `waiting` event is fired before the player has buffered content, making it impossible
84394          to find or skip the gap. The `waiting` event is followed by a `play` event. On first play
84395          we can check if playback is stalled due to a gap, and skip the gap if necessary.
84396      2)  A source with a gap at the beginning of the stream is loaded programatically while the player
84397          is in a playing state. To catch this case, it's important that our one-time play listener is setup
84398          even if the player is in a playing state
84399    */
84400
84401    this.tech_.one('play', playHandler); // Define the dispose function to clean up our events
84402
84403    this.dispose = () => {
84404      this.clearSeekingAppendCheck_();
84405      this.logger_('dispose');
84406      this.tech_.off('waiting', waitingHandler);
84407      this.tech_.off(timerCancelEvents, cancelTimerHandler);
84408      this.tech_.off('canplay', canPlayHandler);
84409      this.tech_.off('play', playHandler);
84410      this.tech_.off('seeking', this.watchForBadSeeking_);
84411      this.tech_.off('seeked', this.clearSeekingAppendCheck_);
84412      loaderTypes.forEach(type => {
84413        pc[`${type}SegmentLoader_`].off('appendsdone', loaderChecks[type].updateend);
84414        pc[`${type}SegmentLoader_`].off('playlistupdate', loaderChecks[type].reset);
84415        this.tech_.off(['seeked', 'seeking'], loaderChecks[type].reset);
84416      });
84417      if (this.checkCurrentTimeTimeout_) {
84418        window__default["default"].clearTimeout(this.checkCurrentTimeTimeout_);
84419      }
84420      this.resetTimeUpdate_();
84421    };
84422  }
84423  /**
84424   * Periodically check current time to see if playback stopped
84425   *
84426   * @private
84427   */
84428
84429  monitorCurrentTime_() {
84430    this.checkCurrentTime_();
84431    if (this.checkCurrentTimeTimeout_) {
84432      window__default["default"].clearTimeout(this.checkCurrentTimeTimeout_);
84433    } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
84434
84435    this.checkCurrentTimeTimeout_ = window__default["default"].setTimeout(this.monitorCurrentTime_.bind(this), 250);
84436  }
84437  /**
84438   * Reset stalled download stats for a specific type of loader
84439   *
84440   * @param {string} type
84441   *        The segment loader type to check.
84442   *
84443   * @listens SegmentLoader#playlistupdate
84444   * @listens Tech#seeking
84445   * @listens Tech#seeked
84446   */
84447
84448  resetSegmentDownloads_(type) {
84449    const loader = this.playlistController_[`${type}SegmentLoader_`];
84450    if (this[`${type}StalledDownloads_`] > 0) {
84451      this.logger_(`resetting possible stalled download count for ${type} loader`);
84452    }
84453    this[`${type}StalledDownloads_`] = 0;
84454    this[`${type}Buffered_`] = loader.buffered_();
84455  }
84456  /**
84457   * Checks on every segment `appendsdone` to see
84458   * if segment appends are making progress. If they are not
84459   * and we are still downloading bytes. We exclude the playlist.
84460   *
84461   * @param {string} type
84462   *        The segment loader type to check.
84463   *
84464   * @listens SegmentLoader#appendsdone
84465   */
84466
84467  checkSegmentDownloads_(type) {
84468    const pc = this.playlistController_;
84469    const loader = pc[`${type}SegmentLoader_`];
84470    const buffered = loader.buffered_();
84471    const isBufferedDifferent = isRangeDifferent(this[`${type}Buffered_`], buffered);
84472    this[`${type}Buffered_`] = buffered; // if another watcher is going to fix the issue or
84473    // the buffered value for this loader changed
84474    // appends are working
84475
84476    if (isBufferedDifferent) {
84477      const metadata = {
84478        bufferedRanges: buffered
84479      };
84480      pc.trigger({
84481        type: 'bufferedrangeschanged',
84482        metadata
84483      });
84484      this.resetSegmentDownloads_(type);
84485      return;
84486    }
84487    this[`${type}StalledDownloads_`]++;
84488    this.logger_(`found #${this[`${type}StalledDownloads_`]} ${type} appends that did not increase buffer (possible stalled download)`, {
84489      playlistId: loader.playlist_ && loader.playlist_.id,
84490      buffered: timeRangesToArray(buffered)
84491    }); // after 10 possibly stalled appends with no reset, exclude
84492
84493    if (this[`${type}StalledDownloads_`] < 10) {
84494      return;
84495    }
84496    this.logger_(`${type} loader stalled download exclusion`);
84497    this.resetSegmentDownloads_(type);
84498    this.tech_.trigger({
84499      type: 'usage',
84500      name: `vhs-${type}-download-exclusion`
84501    });
84502    if (type === 'subtitle') {
84503      return;
84504    } // TODO: should we exclude audio tracks rather than main tracks
84505    // when type is audio?
84506
84507    pc.excludePlaylist({
84508      error: {
84509        message: `Excessive ${type} segment downloading detected.`
84510      },
84511      playlistExclusionDuration: Infinity
84512    });
84513  }
84514  /**
84515   * The purpose of this function is to emulate the "waiting" event on
84516   * browsers that do not emit it when they are waiting for more
84517   * data to continue playback
84518   *
84519   * @private
84520   */
84521
84522  checkCurrentTime_() {
84523    if (this.tech_.paused() || this.tech_.seeking()) {
84524      return;
84525    }
84526    const currentTime = this.tech_.currentTime();
84527    const buffered = this.tech_.buffered();
84528    if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) {
84529      // If current time is at the end of the final buffered region, then any playback
84530      // stall is most likely caused by buffering in a low bandwidth environment. The tech
84531      // should fire a `waiting` event in this scenario, but due to browser and tech
84532      // inconsistencies. Calling `techWaiting_` here allows us to simulate
84533      // responding to a native `waiting` event when the tech fails to emit one.
84534      return this.techWaiting_();
84535    }
84536    if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
84537      this.consecutiveUpdates++;
84538      this.waiting_();
84539    } else if (currentTime === this.lastRecordedTime) {
84540      this.consecutiveUpdates++;
84541    } else {
84542      this.playedRanges_.push(createTimeRanges([this.lastRecordedTime, currentTime]));
84543      const metadata = {
84544        playedRanges: this.playedRanges_
84545      };
84546      this.playlistController_.trigger({
84547        type: 'playedrangeschanged',
84548        metadata
84549      });
84550      this.consecutiveUpdates = 0;
84551      this.lastRecordedTime = currentTime;
84552    }
84553  }
84554  /**
84555   * Resets the 'timeupdate' mechanism designed to detect that we are stalled
84556   *
84557   * @private
84558   */
84559
84560  resetTimeUpdate_() {
84561    this.consecutiveUpdates = 0;
84562  }
84563  /**
84564   * Fixes situations where there's a bad seek
84565   *
84566   * @return {boolean} whether an action was taken to fix the seek
84567   * @private
84568   */
84569
84570  fixesBadSeeks_() {
84571    const seeking = this.tech_.seeking();
84572    if (!seeking) {
84573      return false;
vendor: 13,565 bytes, lines 84574-84919
84574    } // TODO: It's possible that these seekable checks should be moved out of this function
84575    // and into a function that runs on seekablechange. It's also possible that we only need
84576    // afterSeekableWindow as the buffered check at the bottom is good enough to handle before
84577    // seekable range.
84578
84579    const seekable = this.seekable();
84580    const currentTime = this.tech_.currentTime();
84581    const isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow);
84582    let seekTo;
84583    if (isAfterSeekableRange) {
84584      const seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
84585
84586      seekTo = seekableEnd;
84587    }
84588    if (this.beforeSeekableWindow_(seekable, currentTime)) {
84589      const seekableStart = seekable.start(0); // sync to the beginning of the live window
84590      // provide a buffer of .1 seconds to handle rounding/imprecise numbers
84591
84592      seekTo = seekableStart + (
84593      // if the playlist is too short and the seekable range is an exact time (can
84594      // happen in live with a 3 segment playlist), then don't use a time delta
84595      seekableStart === seekable.end(0) ? 0 : SAFE_TIME_DELTA);
84596    }
84597    if (typeof seekTo !== 'undefined') {
84598      this.logger_(`Trying to seek outside of seekable at time ${currentTime} with ` + `seekable range ${printableRange(seekable)}. Seeking to ` + `${seekTo}.`);
84599      this.tech_.setCurrentTime(seekTo);
84600      return true;
84601    }
84602    const sourceUpdater = this.playlistController_.sourceUpdater_;
84603    const buffered = this.tech_.buffered();
84604    const audioBuffered = sourceUpdater.audioBuffer ? sourceUpdater.audioBuffered() : null;
84605    const videoBuffered = sourceUpdater.videoBuffer ? sourceUpdater.videoBuffered() : null;
84606    const media = this.media(); // verify that at least two segment durations or one part duration have been
84607    // appended before checking for a gap.
84608
84609    const minAppendedDuration = media.partTargetDuration ? media.partTargetDuration : (media.targetDuration - TIME_FUDGE_FACTOR) * 2; // verify that at least two segment durations have been
84610    // appended before checking for a gap.
84611
84612    const bufferedToCheck = [audioBuffered, videoBuffered];
84613    for (let i = 0; i < bufferedToCheck.length; i++) {
84614      // skip null buffered
84615      if (!bufferedToCheck[i]) {
84616        continue;
84617      }
84618      const timeAhead = timeAheadOf(bufferedToCheck[i], currentTime); // if we are less than two video/audio segment durations or one part
84619      // duration behind we haven't appended enough to call this a bad seek.
84620
84621      if (timeAhead < minAppendedDuration) {
84622        return false;
84623      }
84624    }
84625    const nextRange = findNextRange(buffered, currentTime); // we have appended enough content, but we don't have anything buffered
84626    // to seek over the gap
84627
84628    if (nextRange.length === 0) {
84629      return false;
84630    }
84631    seekTo = nextRange.start(0) + SAFE_TIME_DELTA;
84632    this.logger_(`Buffered region starts (${nextRange.start(0)}) ` + ` just beyond seek point (${currentTime}). Seeking to ${seekTo}.`);
84633    this.tech_.setCurrentTime(seekTo);
84634    return true;
84635  }
84636  /**
84637   * Handler for situations when we determine the player is waiting.
84638   *
84639   * @private
84640   */
84641
84642  waiting_() {
84643    if (this.techWaiting_()) {
84644      return;
84645    } // All tech waiting checks failed. Use last resort correction
84646
84647    const currentTime = this.tech_.currentTime();
84648    const buffered = this.tech_.buffered();
84649    const currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered
84650    // region with no indication that anything is amiss (seen in Firefox). Seeking to
84651    // currentTime is usually enough to kickstart the player. This checks that the player
84652    // is currently within a buffered region before attempting a corrective seek.
84653    // Chrome does not appear to continue `timeupdate` events after a `waiting` event
84654    // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
84655    // make sure there is ~3 seconds of forward buffer before taking any corrective action
84656    // to avoid triggering an `unknownwaiting` event when the network is slow.
84657
84658    if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
84659      this.resetTimeUpdate_();
84660      this.tech_.setCurrentTime(currentTime);
84661      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
84662
84663      this.tech_.trigger({
84664        type: 'usage',
84665        name: 'vhs-unknown-waiting'
84666      });
84667      return;
84668    }
84669  }
84670  /**
84671   * Handler for situations when the tech fires a `waiting` event
84672   *
84673   * @return {boolean}
84674   *         True if an action (or none) was needed to correct the waiting. False if no
84675   *         checks passed
84676   * @private
84677   */
84678
84679  techWaiting_() {
84680    const seekable = this.seekable();
84681    const currentTime = this.tech_.currentTime();
84682    if (this.tech_.seeking()) {
84683      // Tech is seeking or already waiting on another action, no action needed
84684      return true;
84685    }
84686    if (this.beforeSeekableWindow_(seekable, currentTime)) {
84687      const livePoint = seekable.end(seekable.length - 1);
84688      this.logger_(`Fell out of live window at time ${currentTime}. Seeking to ` + `live point (seekable end) ${livePoint}`);
84689      this.resetTimeUpdate_();
84690      this.tech_.setCurrentTime(livePoint); // live window resyncs may be useful for monitoring QoS
84691
84692      this.tech_.trigger({
84693        type: 'usage',
84694        name: 'vhs-live-resync'
84695      });
84696      return true;
84697    }
84698    const sourceUpdater = this.tech_.vhs.playlistController_.sourceUpdater_;
84699    const buffered = this.tech_.buffered();
84700    const videoUnderflow = this.videoUnderflow_({
84701      audioBuffered: sourceUpdater.audioBuffered(),
84702      videoBuffered: sourceUpdater.videoBuffered(),
84703      currentTime
84704    });
84705    if (videoUnderflow) {
84706      // Even though the video underflowed and was stuck in a gap, the audio overplayed
84707      // the gap, leading currentTime into a buffered range. Seeking to currentTime
84708      // allows the video to catch up to the audio position without losing any audio
84709      // (only suffering ~3 seconds of frozen video and a pause in audio playback).
84710      this.resetTimeUpdate_();
84711      this.tech_.setCurrentTime(currentTime); // video underflow may be useful for monitoring QoS
84712
84713      this.tech_.trigger({
84714        type: 'usage',
84715        name: 'vhs-video-underflow'
84716      });
84717      return true;
84718    }
84719    const nextRange = findNextRange(buffered, currentTime); // check for gap
84720
84721    if (nextRange.length > 0) {
84722      this.logger_(`Stopped at ${currentTime} and seeking to ${nextRange.start(0)}`);
84723      this.resetTimeUpdate_();
84724      this.skipTheGap_(currentTime);
84725      return true;
84726    } // All checks failed. Returning false to indicate failure to correct waiting
84727
84728    return false;
84729  }
84730  afterSeekableWindow_(seekable, currentTime, playlist, allowSeeksWithinUnsafeLiveWindow = false) {
84731    if (!seekable.length) {
84732      // we can't make a solid case if there's no seekable, default to false
84733      return false;
84734    }
84735    let allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA;
84736    const isLive = !playlist.endList;
84737    const isLLHLS = typeof playlist.partTargetDuration === 'number';
84738    if (isLive && (isLLHLS || allowSeeksWithinUnsafeLiveWindow)) {
84739      allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3;
84740    }
84741    if (currentTime > allowedEnd) {
84742      return true;
84743    }
84744    return false;
84745  }
84746  beforeSeekableWindow_(seekable, currentTime) {
84747    if (seekable.length &&
84748    // can't fall before 0 and 0 seekable start identifies VOD stream
84749    seekable.start(0) > 0 && currentTime < seekable.start(0) - this.liveRangeSafeTimeDelta) {
84750      return true;
84751    }
84752    return false;
84753  }
84754  videoUnderflow_({
84755    videoBuffered,
84756    audioBuffered,
84757    currentTime
84758  }) {
84759    // audio only content will not have video underflow :)
84760    if (!videoBuffered) {
84761      return;
84762    }
84763    let gap; // find a gap in demuxed content.
84764
84765    if (videoBuffered.length && audioBuffered.length) {
84766      // in Chrome audio will continue to play for ~3s when we run out of video
84767      // so we have to check that the video buffer did have some buffer in the
84768      // past.
84769      const lastVideoRange = findRange(videoBuffered, currentTime - 3);
84770      const videoRange = findRange(videoBuffered, currentTime);
84771      const audioRange = findRange(audioBuffered, currentTime);
84772      if (audioRange.length && !videoRange.length && lastVideoRange.length) {
84773        gap = {
84774          start: lastVideoRange.end(0),
84775          end: audioRange.end(0)
84776        };
84777      } // find a gap in muxed content.
84778    } else {
84779      const nextRange = findNextRange(videoBuffered, currentTime); // Even if there is no available next range, there is still a possibility we are
84780      // stuck in a gap due to video underflow.
84781
84782      if (!nextRange.length) {
84783        gap = this.gapFromVideoUnderflow_(videoBuffered, currentTime);
84784      }
84785    }
84786    if (gap) {
84787      this.logger_(`Encountered a gap in video from ${gap.start} to ${gap.end}. ` + `Seeking to current time ${currentTime}`);
84788      return true;
84789    }
84790    return false;
84791  }
84792  /**
84793   * Timer callback. If playback still has not proceeded, then we seek
84794   * to the start of the next buffered region.
84795   *
84796   * @private
84797   */
84798
84799  skipTheGap_(scheduledCurrentTime) {
84800    const buffered = this.tech_.buffered();
84801    const currentTime = this.tech_.currentTime();
84802    const nextRange = findNextRange(buffered, currentTime);
84803    this.resetTimeUpdate_();
84804    if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
84805      return;
84806    }
84807    this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played
84808
84809    this.tech_.setCurrentTime(nextRange.start(0) + TIME_FUDGE_FACTOR);
84810    const metadata = {
84811      gapInfo: {
84812        from: currentTime,
84813        to: nextRange.start(0)
84814      }
84815    };
84816    this.playlistController_.trigger({
84817      type: 'gapjumped',
84818      metadata
84819    });
84820    this.tech_.trigger({
84821      type: 'usage',
84822      name: 'vhs-gap-skip'
84823    });
84824  }
84825  gapFromVideoUnderflow_(buffered, currentTime) {
84826    // At least in Chrome, if there is a gap in the video buffer, the audio will continue
84827    // playing for ~3 seconds after the video gap starts. This is done to account for
84828    // video buffer underflow/underrun (note that this is not done when there is audio
84829    // buffer underflow/underrun -- in that case the video will stop as soon as it
84830    // encounters the gap, as audio stalls are more noticeable/jarring to a user than
84831    // video stalls). The player's time will reflect the playthrough of audio, so the
84832    // time will appear as if we are in a buffered region, even if we are stuck in a
84833    // "gap."
84834    //
84835    // Example:
84836    // video buffer:   0 => 10.1, 10.2 => 20
84837    // audio buffer:   0 => 20
84838    // overall buffer: 0 => 10.1, 10.2 => 20
84839    // current time: 13
84840    //
84841    // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
84842    // however, the audio continued playing until it reached ~3 seconds past the gap
84843    // (13 seconds), at which point it stops as well. Since current time is past the
84844    // gap, findNextRange will return no ranges.
84845    //
84846    // To check for this issue, we see if there is a gap that starts somewhere within
84847    // a 3 second range (3 seconds +/- 1 second) back from our current time.
84848    const gaps = findGaps(buffered);
84849    for (let i = 0; i < gaps.length; i++) {
84850      const start = gaps.start(i);
84851      const end = gaps.end(i); // gap is starts no more than 4 seconds back
84852
84853      if (currentTime - start < 4 && currentTime - start > 2) {
84854        return {
84855          start,
84856          end
84857        };
84858      }
84859    }
84860    return null;
84861  }
84862}
84863const defaultOptions = {
84864  errorInterval: 30,
84865  getSource(next) {
84866    const tech = this.tech({
84867      IWillNotUseThisInPlugins: true
84868    });
84869    const sourceObj = tech.currentSource_ || this.currentSource();
84870    return next(sourceObj);
84871  }
84872};
84873/**
84874 * Main entry point for the plugin
84875 *
84876 * @param {Player} player a reference to a videojs Player instance
84877 * @param {Object} [options] an object with plugin options
84878 * @private
84879 */
84880
84881const initPlugin = function (player, options) {
84882  let lastCalled = 0;
84883  let seekTo = 0;
84884  const localOptions = merge(defaultOptions, options);
84885  player.ready(() => {
84886    player.trigger({
84887      type: 'usage',
84888      name: 'vhs-error-reload-initialized'
84889    });
84890  });
84891  /**
84892   * Player modifications to perform that must wait until `loadedmetadata`
84893   * has been triggered
84894   *
84895   * @private
84896   */
84897
84898  const loadedMetadataHandler = function () {
84899    if (seekTo) {
84900      player.currentTime(seekTo);
84901    }
84902  };
84903  /**
84904   * Set the source on the player element, play, and seek if necessary
84905   *
84906   * @param {Object} sourceObj An object specifying the source url and mime-type to play
84907   * @private
84908   */
84909
84910  const setSource = function (sourceObj) {
84911    if (sourceObj === null || sourceObj === undefined) {
84912      return;
84913    }
84914    seekTo = player.duration() !== Infinity && player.currentTime() || 0;
84915    player.one('loadedmetadata', loadedMetadataHandler);
84916    player.src(sourceObj);
84917    player.trigger({
84918      type: 'usage',
84919      name: 'vhs-error-reload'
vendor: 6,080 bytes, lines 84920-85093
84920    });
84921    player.play();
84922  };
84923  /**
84924   * Attempt to get a source from either the built-in getSource function
84925   * or a custom function provided via the options
84926   *
84927   * @private
84928   */
84929
84930  const errorHandler = function () {
84931    // Do not attempt to reload the source if a source-reload occurred before
84932    // 'errorInterval' time has elapsed since the last source-reload
84933    if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
84934      player.trigger({
84935        type: 'usage',
84936        name: 'vhs-error-reload-canceled'
84937      });
84938      return;
84939    }
84940    if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
84941      videojs.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
84942      return;
84943    }
84944    lastCalled = Date.now();
84945    return localOptions.getSource.call(player, setSource);
84946  };
84947  /**
84948   * Unbind any event handlers that were bound by the plugin
84949   *
84950   * @private
84951   */
84952
84953  const cleanupEvents = function () {
84954    player.off('loadedmetadata', loadedMetadataHandler);
84955    player.off('error', errorHandler);
84956    player.off('dispose', cleanupEvents);
84957  };
84958  /**
84959   * Cleanup before re-initializing the plugin
84960   *
84961   * @param {Object} [newOptions] an object with plugin options
84962   * @private
84963   */
84964
84965  const reinitPlugin = function (newOptions) {
84966    cleanupEvents();
84967    initPlugin(player, newOptions);
84968  };
84969  player.on('error', errorHandler);
84970  player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before
84971  // initializing the plugin
84972
84973  player.reloadSourceOnError = reinitPlugin;
84974};
84975/**
84976 * Reload the source when an error is detected as long as there
84977 * wasn't an error previously within the last 30 seconds
84978 *
84979 * @param {Object} [options] an object with plugin options
84980 */
84981
84982const reloadSourceOnError = function (options) {
84983  initPlugin(this, options);
84984};
84985var version$4 = "3.17.5";
84986var version$3 = "7.1.0";
84987var version$2 = "1.4.0";
84988var version$1 = "7.2.0";
84989var version = "4.0.2";
84990const Vhs = {
84991  PlaylistLoader,
84992  Playlist,
84993  utils,
84994  STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
84995  INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
84996  lastBandwidthSelector,
84997  movingAverageBandwidthSelector,
84998  comparePlaylistBandwidth,
84999  comparePlaylistResolution,
85000  xhr: xhrFactory()
85001}; // Define getter/setters for config properties
85002
85003Object.keys(Config).forEach(prop => {
85004  Object.defineProperty(Vhs, prop, {
85005    get() {
85006      videojs.log.warn(`using Vhs.${prop} is UNSAFE be sure you know what you are doing`);
85007      return Config[prop];
85008    },
85009    set(value) {
85010      videojs.log.warn(`using Vhs.${prop} is UNSAFE be sure you know what you are doing`);
85011      if (typeof value !== 'number' || value < 0) {
85012        videojs.log.warn(`value of Vhs.${prop} must be greater than or equal to 0`);
85013        return;
85014      }
85015      Config[prop] = value;
85016    }
85017  });
85018});
85019const LOCAL_STORAGE_KEY = 'videojs-vhs';
85020/**
85021 * Updates the selectedIndex of the QualityLevelList when a mediachange happens in vhs.
85022 *
85023 * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
85024 * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
85025 * @function handleVhsMediaChange
85026 */
85027
85028const handleVhsMediaChange = function (qualityLevels, playlistLoader) {
85029  const newPlaylist = playlistLoader.media();
85030  let selectedIndex = -1;
85031  for (let i = 0; i < qualityLevels.length; i++) {
85032    if (qualityLevels[i].id === newPlaylist.id) {
85033      selectedIndex = i;
85034      break;
85035    }
85036  }
85037  qualityLevels.selectedIndex_ = selectedIndex;
85038  qualityLevels.trigger({
85039    selectedIndex,
85040    type: 'change'
85041  });
85042};
85043/**
85044 * Adds quality levels to list once playlist metadata is available
85045 *
85046 * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
85047 * @param {Object} vhs Vhs object to listen to for media events.
85048 * @function handleVhsLoadedMetadata
85049 */
85050
85051const handleVhsLoadedMetadata = function (qualityLevels, vhs) {
85052  vhs.representations().forEach(rep => {
85053    qualityLevels.addQualityLevel(rep);
85054  });
85055  handleVhsMediaChange(qualityLevels, vhs.playlists);
85056}; // VHS is a source handler, not a tech. Make sure attempts to use it
85057// as one do not cause exceptions.
85058
85059Vhs.canPlaySource = function () {
85060  return videojs.log.warn('VHS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
85061};
85062const emeKeySystems = (keySystemOptions, mainPlaylist, audioPlaylist) => {
85063  if (!keySystemOptions) {
85064    return keySystemOptions;
85065  }
85066  let codecs = {};
85067  if (mainPlaylist && mainPlaylist.attributes && mainPlaylist.attributes.CODECS) {
85068    codecs = unwrapCodecList(codecs_js.parseCodecs(mainPlaylist.attributes.CODECS));
85069  }
85070  if (audioPlaylist && audioPlaylist.attributes && audioPlaylist.attributes.CODECS) {
85071    codecs.audio = audioPlaylist.attributes.CODECS;
85072  }
85073  const videoContentType = codecs_js.getMimeForCodec(codecs.video);
85074  const audioContentType = codecs_js.getMimeForCodec(codecs.audio); // upsert the content types based on the selected playlist
85075
85076  const keySystemContentTypes = {};
85077  for (const keySystem in keySystemOptions) {
85078    keySystemContentTypes[keySystem] = {};
85079    if (audioContentType) {
85080      keySystemContentTypes[keySystem].audioContentType = audioContentType;
85081    }
85082    if (videoContentType) {
85083      keySystemContentTypes[keySystem].videoContentType = videoContentType;
85084    } // Default to using the video playlist's PSSH even though they may be different, as
85085    // videojs-contrib-eme will only accept one in the options.
85086    //
85087    // This shouldn't be an issue for most cases as early intialization will handle all
85088    // unique PSSH values, and if they aren't, then encrypted events should have the
85089    // specific information needed for the unique license.
85090
85091    if (mainPlaylist.contentProtection && mainPlaylist.contentProtection[keySystem] && mainPlaylist.contentProtection[keySystem].pssh) {
85092      keySystemContentTypes[keySystem].pssh = mainPlaylist.contentProtection[keySystem].pssh;
85093    } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
vendor: 4,102 bytes, lines 85094-85200
85094    // so we need to prevent overwriting the URL entirely
85095
85096    if (typeof keySystemOptions[keySystem] === 'string') {
85097      keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
85098    }
85099  }
85100  return merge(keySystemOptions, keySystemContentTypes);
85101};
85102/**
85103 * @typedef {Object} KeySystems
85104 *
85105 * keySystems configuration for https://github.com/videojs/videojs-contrib-eme
85106 * Note: not all options are listed here.
85107 *
85108 * @property {Uint8Array} [pssh]
85109 *           Protection System Specific Header
85110 */
85111
85112/**
85113 * Goes through all the playlists and collects an array of KeySystems options objects
85114 * containing each playlist's keySystems and their pssh values, if available.
85115 *
85116 * @param {Object[]} playlists
85117 *        The playlists to look through
85118 * @param {string[]} keySystems
85119 *        The keySystems to collect pssh values for
85120 *
85121 * @return {KeySystems[]}
85122 *         An array of KeySystems objects containing available key systems and their
85123 *         pssh values
85124 */
85125
85126const getAllPsshKeySystemsOptions = (playlists, keySystems) => {
85127  return playlists.reduce((keySystemsArr, playlist) => {
85128    if (!playlist.contentProtection) {
85129      return keySystemsArr;
85130    }
85131    const keySystemsOptions = keySystems.reduce((keySystemsObj, keySystem) => {
85132      const keySystemOptions = playlist.contentProtection[keySystem];
85133      if (keySystemOptions && keySystemOptions.pssh) {
85134        keySystemsObj[keySystem] = {
85135          pssh: keySystemOptions.pssh
85136        };
85137      }
85138      return keySystemsObj;
85139    }, {});
85140    if (Object.keys(keySystemsOptions).length) {
85141      keySystemsArr.push(keySystemsOptions);
85142    }
85143    return keySystemsArr;
85144  }, []);
85145};
85146/**
85147 * Returns a promise that waits for the
85148 * [eme plugin](https://github.com/videojs/videojs-contrib-eme) to create a key session.
85149 *
85150 * Works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449 in non-IE11
85151 * browsers.
85152 *
85153 * As per the above ticket, this is particularly important for Chrome, where, if
85154 * unencrypted content is appended before encrypted content and the key session has not
85155 * been created, a MEDIA_ERR_DECODE will be thrown once the encrypted content is reached
85156 * during playback.
85157 *
85158 * @param {Object} player
85159 *        The player instance
85160 * @param {Object[]} sourceKeySystems
85161 *        The key systems options from the player source
85162 * @param {Object} [audioMedia]
85163 *        The active audio media playlist (optional)
85164 * @param {Object[]} mainPlaylists
85165 *        The playlists found on the main playlist object
85166 *
85167 * @return {Object}
85168 *         Promise that resolves when the key session has been created
85169 */
85170
85171const waitForKeySessionCreation = ({
85172  player,
85173  sourceKeySystems,
85174  audioMedia,
85175  mainPlaylists
85176}) => {
85177  if (!player.eme.initializeMediaKeys) {
85178    return Promise.resolve();
85179  } // TODO should all audio PSSH values be initialized for DRM?
85180  //
85181  // All unique video rendition pssh values are initialized for DRM, but here only
85182  // the initial audio playlist license is initialized. In theory, an encrypted
85183  // event should be fired if the user switches to an alternative audio playlist
85184  // where a license is required, but this case hasn't yet been tested. In addition, there
85185  // may be many alternate audio playlists unlikely to be used (e.g., multiple different
85186  // languages).
85187
85188  const playlists = audioMedia ? mainPlaylists.concat([audioMedia]) : mainPlaylists;
85189  const keySystemsOptionsArr = getAllPsshKeySystemsOptions(playlists, Object.keys(sourceKeySystems));
85190  const initializationFinishedPromises = [];
85191  const keySessionCreatedPromises = []; // Since PSSH values are interpreted as initData, EME will dedupe any duplicates. The
85192  // only place where it should not be deduped is for ms-prefixed APIs, but
85193  // the existence of modern EME APIs in addition to
85194  // ms-prefixed APIs on Edge should prevent this from being a concern.
85195  // initializeMediaKeys also won't use the webkit-prefixed APIs.
85196
85197  keySystemsOptionsArr.forEach(keySystemsOptions => {
85198    keySessionCreatedPromises.push(new Promise((resolve, reject) => {
85199      player.tech_.one('keysessioncreated', resolve);
85200    }));
vendor: 28,867 bytes, lines 85201-85949
85201    initializationFinishedPromises.push(new Promise((resolve, reject) => {
85202      player.eme.initializeMediaKeys({
85203        keySystems: keySystemsOptions
85204      }, err => {
85205        if (err) {
85206          reject(err);
85207          return;
85208        }
85209        resolve();
85210      });
85211    }));
85212  }); // The reasons Promise.race is chosen over Promise.any:
85213  //
85214  // * Promise.any is only available in Safari 14+.
85215  // * None of these promises are expected to reject. If they do reject, it might be
85216  //   better here for the race to surface the rejection, rather than mask it by using
85217  //   Promise.any.
85218
85219  return Promise.race([
85220  // If a session was previously created, these will all finish resolving without
85221  // creating a new session, otherwise it will take until the end of all license
85222  // requests, which is why the key session check is used (to make setup much faster).
85223  Promise.all(initializationFinishedPromises),
85224  // Once a single session is created, the browser knows DRM will be used.
85225  Promise.race(keySessionCreatedPromises)]);
85226};
85227/**
85228 * If the [eme](https://github.com/videojs/videojs-contrib-eme) plugin is available, and
85229 * there are keySystems on the source, sets up source options to prepare the source for
85230 * eme.
85231 *
85232 * @param {Object} player
85233 *        The player instance
85234 * @param {Object[]} sourceKeySystems
85235 *        The key systems options from the player source
85236 * @param {Object} media
85237 *        The active media playlist
85238 * @param {Object} [audioMedia]
85239 *        The active audio media playlist (optional)
85240 *
85241 * @return {boolean}
85242 *         Whether or not options were configured and EME is available
85243 */
85244
85245const setupEmeOptions = ({
85246  player,
85247  sourceKeySystems,
85248  media,
85249  audioMedia
85250}) => {
85251  const sourceOptions = emeKeySystems(sourceKeySystems, media, audioMedia);
85252  if (!sourceOptions) {
85253    return false;
85254  }
85255  player.currentSource().keySystems = sourceOptions; // eme handles the rest of the setup, so if it is missing
85256  // do nothing.
85257
85258  if (sourceOptions && !player.eme) {
85259    videojs.log.warn('DRM encrypted source cannot be decrypted without a DRM plugin');
85260    return false;
85261  }
85262  return true;
85263};
85264const getVhsLocalStorage = () => {
85265  if (!window__default["default"].localStorage) {
85266    return null;
85267  }
85268  const storedObject = window__default["default"].localStorage.getItem(LOCAL_STORAGE_KEY);
85269  if (!storedObject) {
85270    return null;
85271  }
85272  try {
85273    return JSON.parse(storedObject);
85274  } catch (e) {
85275    // someone may have tampered with the value
85276    return null;
85277  }
85278};
85279const updateVhsLocalStorage = options => {
85280  if (!window__default["default"].localStorage) {
85281    return false;
85282  }
85283  let objectToStore = getVhsLocalStorage();
85284  objectToStore = objectToStore ? merge(objectToStore, options) : options;
85285  try {
85286    window__default["default"].localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(objectToStore));
85287  } catch (e) {
85288    // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where
85289    // storage is set to 0).
85290    // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions
85291    // No need to perform any operation.
85292    return false;
85293  }
85294  return objectToStore;
85295};
85296/**
85297 * Parses VHS-supported media types from data URIs. See
85298 * https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
85299 * for information on data URIs.
85300 *
85301 * @param {string} dataUri
85302 *        The data URI
85303 *
85304 * @return {string|Object}
85305 *         The parsed object/string, or the original string if no supported media type
85306 *         was found
85307 */
85308
85309const expandDataUri = dataUri => {
85310  if (dataUri.toLowerCase().indexOf('data:application/vnd.videojs.vhs+json,') === 0) {
85311    return JSON.parse(dataUri.substring(dataUri.indexOf(',') + 1));
85312  } // no known case for this data URI, return the string as-is
85313
85314  return dataUri;
85315};
85316/**
85317 * Adds a request hook to an xhr object
85318 *
85319 * @param {Object} xhr object to add the onRequest hook to
85320 * @param {function} callback hook function for an xhr request
85321 */
85322
85323const addOnRequestHook = (xhr, callback) => {
85324  if (!xhr._requestCallbackSet) {
85325    xhr._requestCallbackSet = new Set();
85326  }
85327  xhr._requestCallbackSet.add(callback);
85328};
85329/**
85330 * Adds a response hook to an xhr object
85331 *
85332 * @param {Object} xhr object to add the onResponse hook to
85333 * @param {function} callback hook function for an xhr response
85334 */
85335
85336const addOnResponseHook = (xhr, callback) => {
85337  if (!xhr._responseCallbackSet) {
85338    xhr._responseCallbackSet = new Set();
85339  }
85340  xhr._responseCallbackSet.add(callback);
85341};
85342/**
85343 * Removes a request hook on an xhr object, deletes the onRequest set if empty.
85344 *
85345 * @param {Object} xhr object to remove the onRequest hook from
85346 * @param {function} callback hook function to remove
85347 */
85348
85349const removeOnRequestHook = (xhr, callback) => {
85350  if (!xhr._requestCallbackSet) {
85351    return;
85352  }
85353  xhr._requestCallbackSet.delete(callback);
85354  if (!xhr._requestCallbackSet.size) {
85355    delete xhr._requestCallbackSet;
85356  }
85357};
85358/**
85359 * Removes a response hook on an xhr object, deletes the onResponse set if empty.
85360 *
85361 * @param {Object} xhr object to remove the onResponse hook from
85362 * @param {function} callback hook function to remove
85363 */
85364
85365const removeOnResponseHook = (xhr, callback) => {
85366  if (!xhr._responseCallbackSet) {
85367    return;
85368  }
85369  xhr._responseCallbackSet.delete(callback);
85370  if (!xhr._responseCallbackSet.size) {
85371    delete xhr._responseCallbackSet;
85372  }
85373};
85374/**
85375 * Whether the browser has built-in HLS support.
85376 */
85377
85378Vhs.supportsNativeHls = function () {
85379  if (!document__default["default"] || !document__default["default"].createElement) {
85380    return false;
85381  }
85382  const video = document__default["default"].createElement('video'); // native HLS is definitely not supported if HTML5 video isn't
85383
85384  if (!videojs.getTech('Html5').isSupported()) {
85385    return false;
85386  } // HLS manifests can go by many mime-types
85387
85388  const canPlay = [
85389  // Apple santioned
85390  'application/vnd.apple.mpegurl',
85391  // Apple sanctioned for backwards compatibility
85392  'audio/mpegurl',
85393  // Very common
85394  'audio/x-mpegurl',
85395  // Very common
85396  'application/x-mpegurl',
85397  // Included for completeness
85398  'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
85399  return canPlay.some(function (canItPlay) {
85400    return /maybe|probably/i.test(video.canPlayType(canItPlay));
85401  });
85402}();
85403Vhs.supportsNativeDash = function () {
85404  if (!document__default["default"] || !document__default["default"].createElement || !videojs.getTech('Html5').isSupported()) {
85405    return false;
85406  }
85407  return /maybe|probably/i.test(document__default["default"].createElement('video').canPlayType('application/dash+xml'));
85408}();
85409Vhs.supportsTypeNatively = type => {
85410  if (type === 'hls') {
85411    return Vhs.supportsNativeHls;
85412  }
85413  if (type === 'dash') {
85414    return Vhs.supportsNativeDash;
85415  }
85416  return false;
85417};
85418/**
85419 * VHS is a source handler, not a tech. Make sure attempts to use it
85420 * as one do not cause exceptions.
85421 */
85422
85423Vhs.isSupported = function () {
85424  return videojs.log.warn('VHS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
85425};
85426/**
85427 * A global function for setting an onRequest hook
85428 *
85429 * @param {function} callback for request modifiction
85430 */
85431
85432Vhs.xhr.onRequest = function (callback) {
85433  addOnRequestHook(Vhs.xhr, callback);
85434};
85435/**
85436 * A global function for setting an onResponse hook
85437 *
85438 * @param {callback} callback for response data retrieval
85439 */
85440
85441Vhs.xhr.onResponse = function (callback) {
85442  addOnResponseHook(Vhs.xhr, callback);
85443};
85444/**
85445 * Deletes a global onRequest callback if it exists
85446 *
85447 * @param {function} callback to delete from the global set
85448 */
85449
85450Vhs.xhr.offRequest = function (callback) {
85451  removeOnRequestHook(Vhs.xhr, callback);
85452};
85453/**
85454 * Deletes a global onResponse callback if it exists
85455 *
85456 * @param {function} callback to delete from the global set
85457 */
85458
85459Vhs.xhr.offResponse = function (callback) {
85460  removeOnResponseHook(Vhs.xhr, callback);
85461};
85462const Component = videojs.getComponent('Component');
85463/**
85464 * The Vhs Handler object, where we orchestrate all of the parts
85465 * of VHS to interact with video.js
85466 *
85467 * @class VhsHandler
85468 * @extends videojs.Component
85469 * @param {Object} source the soruce object
85470 * @param {Tech} tech the parent tech object
85471 * @param {Object} options optional and required options
85472 */
85473
85474class VhsHandler extends Component {
85475  constructor(source, tech, options) {
85476    super(tech, options.vhs); // if a tech level `initialBandwidth` option was passed
85477    // use that over the VHS level `bandwidth` option
85478
85479    if (typeof options.initialBandwidth === 'number') {
85480      this.options_.bandwidth = options.initialBandwidth;
85481    }
85482    this.logger_ = logger('VhsHandler'); // we need access to the player in some cases,
85483    // so, get it from Video.js via the `playerId`
85484
85485    if (tech.options_ && tech.options_.playerId) {
85486      const _player = videojs.getPlayer(tech.options_.playerId);
85487      this.player_ = _player;
85488    }
85489    this.tech_ = tech;
85490    this.source_ = source;
85491    this.stats = {};
85492    this.ignoreNextSeekingEvent_ = false;
85493    this.setOptions_();
85494    if (this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) {
85495      tech.overrideNativeAudioTracks(true);
85496      tech.overrideNativeVideoTracks(true);
85497    } else if (this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
85498      // overriding native VHS only works if audio tracks have been emulated
85499      // error early if we're misconfigured
85500      throw new Error('Overriding native VHS requires emulated tracks. ' + 'See https://git.io/vMpjB');
85501    } // listen for fullscreenchange events for this player so that we
85502    // can adjust our quality selection quickly
85503
85504    this.on(document__default["default"], ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], event => {
85505      const fullscreenElement = document__default["default"].fullscreenElement || document__default["default"].webkitFullscreenElement || document__default["default"].mozFullScreenElement || document__default["default"].msFullscreenElement;
85506      if (fullscreenElement && fullscreenElement.contains(this.tech_.el())) {
85507        this.playlistController_.fastQualityChange_();
85508      } else {
85509        // When leaving fullscreen, since the in page pixel dimensions should be smaller
85510        // than full screen, see if there should be a rendition switch down to preserve
85511        // bandwidth.
85512        this.playlistController_.checkABR_();
85513      }
85514    });
85515    this.on(this.tech_, 'seeking', function () {
85516      if (this.ignoreNextSeekingEvent_) {
85517        this.ignoreNextSeekingEvent_ = false;
85518        return;
85519      }
85520      this.setCurrentTime(this.tech_.currentTime());
85521    });
85522    this.on(this.tech_, 'error', function () {
85523      // verify that the error was real and we are loaded
85524      // enough to have pc loaded.
85525      if (this.tech_.error() && this.playlistController_) {
85526        this.playlistController_.pauseLoading();
85527      }
85528    });
85529    this.on(this.tech_, 'play', this.play);
85530  }
85531  /**
85532   * Set VHS options based on options from configuration, as well as partial
85533   * options to be passed at a later time.
85534   *
85535   * @param {Object} options A partial chunk of config options
85536   */
85537
85538  setOptions_(options = {}) {
85539    this.options_ = merge(this.options_, options); // defaults
85540
85541    this.options_.withCredentials = this.options_.withCredentials || false;
85542    this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
85543    this.options_.useDevicePixelRatio = this.options_.useDevicePixelRatio || false;
85544    this.options_.usePlayerObjectFit = this.options_.usePlayerObjectFit || false;
85545    this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false;
85546    this.options_.useForcedSubtitles = this.options_.useForcedSubtitles || false;
85547    this.options_.useNetworkInformationApi = typeof this.options_.useNetworkInformationApi !== 'undefined' ? this.options_.useNetworkInformationApi : true;
85548    this.options_.useDtsForTimestampOffset = this.options_.useDtsForTimestampOffset || false;
85549    this.options_.customTagParsers = this.options_.customTagParsers || [];
85550    this.options_.customTagMappers = this.options_.customTagMappers || [];
85551    this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;
85552    this.options_.llhls = this.options_.llhls === false ? false : true;
85553    this.options_.bufferBasedABR = this.options_.bufferBasedABR || false;
85554    if (typeof this.options_.playlistExclusionDuration !== 'number') {
85555      this.options_.playlistExclusionDuration = 60;
85556    }
85557    if (typeof this.options_.bandwidth !== 'number') {
85558      if (this.options_.useBandwidthFromLocalStorage) {
85559        const storedObject = getVhsLocalStorage();
85560        if (storedObject && storedObject.bandwidth) {
85561          this.options_.bandwidth = storedObject.bandwidth;
85562          this.tech_.trigger({
85563            type: 'usage',
85564            name: 'vhs-bandwidth-from-local-storage'
85565          });
85566        }
85567        if (storedObject && storedObject.throughput) {
85568          this.options_.throughput = storedObject.throughput;
85569          this.tech_.trigger({
85570            type: 'usage',
85571            name: 'vhs-throughput-from-local-storage'
85572          });
85573        }
85574      }
85575    } // if bandwidth was not set by options or pulled from local storage, start playlist
85576    // selection at a reasonable bandwidth
85577
85578    if (typeof this.options_.bandwidth !== 'number') {
85579      this.options_.bandwidth = Config.INITIAL_BANDWIDTH;
85580    } // If the bandwidth number is unchanged from the initial setting
85581    // then this takes precedence over the enableLowInitialPlaylist option
85582
85583    this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src
85584
85585    ['withCredentials', 'useDevicePixelRatio', 'usePlayerObjectFit', 'customPixelRatio', 'limitRenditionByPlayerDimensions', 'bandwidth', 'customTagParsers', 'customTagMappers', 'cacheEncryptionKeys', 'playlistSelector', 'initialPlaylistSelector', 'bufferBasedABR', 'liveRangeSafeTimeDelta', 'llhls', 'useForcedSubtitles', 'useNetworkInformationApi', 'useDtsForTimestampOffset', 'exactManifestTimings', 'leastPixelDiffSelector'].forEach(option => {
85586      if (typeof this.source_[option] !== 'undefined') {
85587        this.options_[option] = this.source_[option];
85588      }
85589    });
85590    this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
85591    this.useDevicePixelRatio = this.options_.useDevicePixelRatio;
85592    this.usePlayerObjectFit = this.options_.usePlayerObjectFit;
85593    const customPixelRatio = this.options_.customPixelRatio; // Ensure the custom pixel ratio is a number greater than or equal to 0
85594
85595    if (typeof customPixelRatio === 'number' && customPixelRatio >= 0) {
85596      this.customPixelRatio = customPixelRatio;
85597    }
85598  } // alias for public method to set options
85599
85600  setOptions(options = {}) {
85601    this.setOptions_(options);
85602  }
85603  /**
85604   * called when player.src gets called, handle a new source
85605   *
85606   * @param {Object} src the source object to handle
85607   */
85608
85609  src(src, type) {
85610    // do nothing if the src is falsey
85611    if (!src) {
85612      return;
85613    }
85614    this.setOptions_(); // add main playlist controller options
85615
85616    this.options_.src = expandDataUri(this.source_.src);
85617    this.options_.tech = this.tech_;
85618    this.options_.externVhs = Vhs;
85619    this.options_.sourceType = mediaTypes_js.simpleTypeFromSourceType(type); // Whenever we seek internally, we should update the tech
85620
85621    this.options_.seekTo = time => {
85622      this.tech_.setCurrentTime(time);
85623    }; // pass player to allow for player level eventing on construction.
85624
85625    this.options_.player_ = this.player_;
85626    this.playlistController_ = new PlaylistController(this.options_);
85627    const playbackWatcherOptions = merge({
85628      liveRangeSafeTimeDelta: SAFE_TIME_DELTA
85629    }, this.options_, {
85630      seekable: () => this.seekable(),
85631      media: () => this.playlistController_.media(),
85632      playlistController: this.playlistController_
85633    });
85634    this.playbackWatcher_ = new PlaybackWatcher(playbackWatcherOptions);
85635    this.attachStreamingEventListeners_();
85636    this.playlistController_.on('error', () => {
85637      const player = videojs.players[this.tech_.options_.playerId];
85638      let error = this.playlistController_.error;
85639      if (typeof error === 'object' && !error.code) {
85640        error.code = 3;
85641      } else if (typeof error === 'string') {
85642        error = {
85643          message: error,
85644          code: 3
85645        };
85646      }
85647      player.error(error);
85648    });
85649    const defaultSelector = this.options_.bufferBasedABR ? Vhs.movingAverageBandwidthSelector(0.55) : Vhs.STANDARD_PLAYLIST_SELECTOR; // `this` in selectPlaylist should be the VhsHandler for backwards
85650    // compatibility with < v2
85651
85652    this.playlistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : defaultSelector.bind(this);
85653    this.playlistController_.selectInitialPlaylist = Vhs.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2
85654
85655    this.playlists = this.playlistController_.mainPlaylistLoader_;
85656    this.mediaSource = this.playlistController_.mediaSource; // Proxy assignment of some properties to the main playlist
85657    // controller. Using a custom property for backwards compatibility
85658    // with < v2
85659
85660    Object.defineProperties(this, {
85661      selectPlaylist: {
85662        get() {
85663          return this.playlistController_.selectPlaylist;
85664        },
85665        set(selectPlaylist) {
85666          this.playlistController_.selectPlaylist = selectPlaylist.bind(this);
85667        }
85668      },
85669      throughput: {
85670        get() {
85671          return this.playlistController_.mainSegmentLoader_.throughput.rate;
85672        },
85673        set(throughput) {
85674          this.playlistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value
85675          // for the cumulative average
85676
85677          this.playlistController_.mainSegmentLoader_.throughput.count = 1;
85678        }
85679      },
85680      bandwidth: {
85681        get() {
85682          let playerBandwidthEst = this.playlistController_.mainSegmentLoader_.bandwidth;
85683          const networkInformation = window__default["default"].navigator.connection || window__default["default"].navigator.mozConnection || window__default["default"].navigator.webkitConnection;
85684          const tenMbpsAsBitsPerSecond = 10e6;
85685          if (this.options_.useNetworkInformationApi && networkInformation) {
85686            // downlink returns Mbps
85687            // https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/downlink
85688            const networkInfoBandwidthEstBitsPerSec = networkInformation.downlink * 1000 * 1000; // downlink maxes out at 10 Mbps. In the event that both networkInformationApi and the player
85689            // estimate a bandwidth greater than 10 Mbps, use the larger of the two estimates to ensure that
85690            // high quality streams are not filtered out.
85691
85692            if (networkInfoBandwidthEstBitsPerSec >= tenMbpsAsBitsPerSecond && playerBandwidthEst >= tenMbpsAsBitsPerSecond) {
85693              playerBandwidthEst = Math.max(playerBandwidthEst, networkInfoBandwidthEstBitsPerSec);
85694            } else {
85695              playerBandwidthEst = networkInfoBandwidthEstBitsPerSec;
85696            }
85697          }
85698          return playerBandwidthEst;
85699        },
85700        set(bandwidth) {
85701          this.playlistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter
85702          // `count` is set to zero that current value of `rate` isn't included
85703          // in the cumulative average
85704
85705          this.playlistController_.mainSegmentLoader_.throughput = {
85706            rate: 0,
85707            count: 0
85708          };
85709        }
85710      },
85711      /**
85712       * `systemBandwidth` is a combination of two serial processes bit-rates. The first
85713       * is the network bitrate provided by `bandwidth` and the second is the bitrate of
85714       * the entire process after that - decryption, transmuxing, and appending - provided
85715       * by `throughput`.
85716       *
85717       * Since the two process are serial, the overall system bandwidth is given by:
85718       *   sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
85719       */
85720      systemBandwidth: {
85721        get() {
85722          const invBandwidth = 1 / (this.bandwidth || 1);
85723          let invThroughput;
85724          if (this.throughput > 0) {
85725            invThroughput = 1 / this.throughput;
85726          } else {
85727            invThroughput = 0;
85728          }
85729          const systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
85730          return systemBitrate;
85731        },
85732        set() {
85733          videojs.log.error('The "systemBandwidth" property is read-only');
85734        }
85735      }
85736    });
85737    if (this.options_.bandwidth) {
85738      this.bandwidth = this.options_.bandwidth;
85739    }
85740    if (this.options_.throughput) {
85741      this.throughput = this.options_.throughput;
85742    }
85743    Object.defineProperties(this.stats, {
85744      bandwidth: {
85745        get: () => this.bandwidth || 0,
85746        enumerable: true
85747      },
85748      mediaRequests: {
85749        get: () => this.playlistController_.mediaRequests_() || 0,
85750        enumerable: true
85751      },
85752      mediaRequestsAborted: {
85753        get: () => this.playlistController_.mediaRequestsAborted_() || 0,
85754        enumerable: true
85755      },
85756      mediaRequestsTimedout: {
85757        get: () => this.playlistController_.mediaRequestsTimedout_() || 0,
85758        enumerable: true
85759      },
85760      mediaRequestsErrored: {
85761        get: () => this.playlistController_.mediaRequestsErrored_() || 0,
85762        enumerable: true
85763      },
85764      mediaTransferDuration: {
85765        get: () => this.playlistController_.mediaTransferDuration_() || 0,
85766        enumerable: true
85767      },
85768      mediaBytesTransferred: {
85769        get: () => this.playlistController_.mediaBytesTransferred_() || 0,
85770        enumerable: true
85771      },
85772      mediaSecondsLoaded: {
85773        get: () => this.playlistController_.mediaSecondsLoaded_() || 0,
85774        enumerable: true
85775      },
85776      mediaAppends: {
85777        get: () => this.playlistController_.mediaAppends_() || 0,
85778        enumerable: true
85779      },
85780      mainAppendsToLoadedData: {
85781        get: () => this.playlistController_.mainAppendsToLoadedData_() || 0,
85782        enumerable: true
85783      },
85784      audioAppendsToLoadedData: {
85785        get: () => this.playlistController_.audioAppendsToLoadedData_() || 0,
85786        enumerable: true
85787      },
85788      appendsToLoadedData: {
85789        get: () => this.playlistController_.appendsToLoadedData_() || 0,
85790        enumerable: true
85791      },
85792      timeToLoadedData: {
85793        get: () => this.playlistController_.timeToLoadedData_() || 0,
85794        enumerable: true
85795      },
85796      buffered: {
85797        get: () => timeRangesToArray(this.tech_.buffered()),
85798        enumerable: true
85799      },
85800      currentTime: {
85801        get: () => this.tech_.currentTime(),
85802        enumerable: true
85803      },
85804      currentSource: {
85805        get: () => this.tech_.currentSource_,
85806        enumerable: true
85807      },
85808      currentTech: {
85809        get: () => this.tech_.name_,
85810        enumerable: true
85811      },
85812      duration: {
85813        get: () => this.tech_.duration(),
85814        enumerable: true
85815      },
85816      main: {
85817        get: () => this.playlists.main,
85818        enumerable: true
85819      },
85820      playerDimensions: {
85821        get: () => this.tech_.currentDimensions(),
85822        enumerable: true
85823      },
85824      seekable: {
85825        get: () => timeRangesToArray(this.tech_.seekable()),
85826        enumerable: true
85827      },
85828      timestamp: {
85829        get: () => Date.now(),
85830        enumerable: true
85831      },
85832      videoPlaybackQuality: {
85833        get: () => this.tech_.getVideoPlaybackQuality(),
85834        enumerable: true
85835      }
85836    });
85837    this.tech_.one('canplay', this.playlistController_.setupFirstPlay.bind(this.playlistController_));
85838    this.tech_.on('bandwidthupdate', () => {
85839      if (this.options_.useBandwidthFromLocalStorage) {
85840        updateVhsLocalStorage({
85841          bandwidth: this.bandwidth,
85842          throughput: Math.round(this.throughput)
85843        });
85844      }
85845    });
85846    this.playlistController_.on('selectedinitialmedia', () => {
85847      // Add the manual rendition mix-in to VhsHandler
85848      renditionSelectionMixin(this);
85849    });
85850    this.playlistController_.sourceUpdater_.on('createdsourcebuffers', () => {
85851      this.setupEme_();
85852    }); // the bandwidth of the primary segment loader is our best
85853    // estimate of overall bandwidth
85854
85855    this.on(this.playlistController_, 'progress', function () {
85856      this.tech_.trigger('progress');
85857    }); // In the live case, we need to ignore the very first `seeking` event since
85858    // that will be the result of the seek-to-live behavior
85859
85860    this.on(this.playlistController_, 'firstplay', function () {
85861      this.ignoreNextSeekingEvent_ = true;
85862    });
85863    this.setupQualityLevels_(); // do nothing if the tech has been disposed already
85864    // this can occur if someone sets the src in player.ready(), for instance
85865
85866    if (!this.tech_.el()) {
85867      return;
85868    }
85869    this.mediaSourceUrl_ = window__default["default"].URL.createObjectURL(this.playlistController_.mediaSource); // If we are playing HLS with MSE in Safari, add source elements for both the blob and manifest URLs.
85870    // The latter will enable Airplay playback on receiver devices.
85871
85872    if ((videojs.browser.IS_ANY_SAFARI || videojs.browser.IS_IOS) && this.options_.overrideNative && this.options_.sourceType === 'hls' && typeof this.tech_.addSourceElement === 'function') {
85873      this.tech_.addSourceElement(this.mediaSourceUrl_);
85874      this.tech_.addSourceElement(this.source_.src);
85875    } else {
85876      this.tech_.src(this.mediaSourceUrl_);
85877    }
85878  }
85879  createKeySessions_() {
85880    const audioPlaylistLoader = this.playlistController_.mediaTypes_.AUDIO.activePlaylistLoader;
85881    this.logger_('waiting for EME key session creation');
85882    waitForKeySessionCreation({
85883      player: this.player_,
85884      sourceKeySystems: this.source_.keySystems,
85885      audioMedia: audioPlaylistLoader && audioPlaylistLoader.media(),
85886      mainPlaylists: this.playlists.main.playlists
85887    }).then(() => {
85888      this.logger_('created EME key session');
85889      this.playlistController_.sourceUpdater_.initializedEme();
85890    }).catch(err => {
85891      this.logger_('error while creating EME key session', err);
85892      this.player_.error({
85893        message: 'Failed to initialize media keys for EME',
85894        code: 3
85895      });
85896    });
85897  }
85898  handleWaitingForKey_() {
85899    // If waitingforkey is fired, it's possible that the data that's necessary to retrieve
85900    // the key is in the manifest. While this should've happened on initial source load, it
85901    // may happen again in live streams where the keys change, and the manifest info
85902    // reflects the update.
85903    //
85904    // Because videojs-contrib-eme compares the PSSH data we send to that of PSSH data it's
85905    // already requested keys for, we don't have to worry about this generating extraneous
85906    // requests.
85907    this.logger_('waitingforkey fired, attempting to create any new key sessions');
85908    this.createKeySessions_();
85909  }
85910  /**
85911   * If necessary and EME is available, sets up EME options and waits for key session
85912   * creation.
85913   *
85914   * This function also updates the source updater so taht it can be used, as for some
85915   * browsers, EME must be configured before content is appended (if appending unencrypted
85916   * content before encrypted content).
85917   */
85918
85919  setupEme_() {
85920    const audioPlaylistLoader = this.playlistController_.mediaTypes_.AUDIO.activePlaylistLoader;
85921    const didSetupEmeOptions = setupEmeOptions({
85922      player: this.player_,
85923      sourceKeySystems: this.source_.keySystems,
85924      media: this.playlists.media(),
85925      audioMedia: audioPlaylistLoader && audioPlaylistLoader.media()
85926    });
85927    this.player_.tech_.on('keystatuschange', e => {
85928      this.playlistController_.updatePlaylistByKeyStatus(e.keyId, e.status);
85929    });
85930    this.handleWaitingForKey_ = this.handleWaitingForKey_.bind(this);
85931    this.player_.tech_.on('waitingforkey', this.handleWaitingForKey_);
85932    if (!didSetupEmeOptions) {
85933      // If EME options were not set up, we've done all we could to initialize EME.
85934      this.playlistController_.sourceUpdater_.initializedEme();
85935      return;
85936    }
85937    this.createKeySessions_();
85938  }
85939  /**
85940   * Initializes the quality levels and sets listeners to update them.
85941   *
85942   * @method setupQualityLevels_
85943   * @private
85944   */
85945
85946  setupQualityLevels_() {
85947    const player = videojs.players[this.tech_.options_.playerId]; // if there isn't a player or there isn't a qualityLevels plugin
85948    // or qualityLevels_ listeners have already been setup, do nothing.
85949
vendor: 1,750 bytes, lines 85950-86029
85950    if (!player || !player.qualityLevels || this.qualityLevels_) {
85951      return;
85952    }
85953    this.qualityLevels_ = player.qualityLevels();
85954    this.playlistController_.on('selectedinitialmedia', () => {
85955      handleVhsLoadedMetadata(this.qualityLevels_, this);
85956    });
85957    this.playlists.on('mediachange', () => {
85958      handleVhsMediaChange(this.qualityLevels_, this.playlists);
85959    });
85960  }
85961  /**
85962   * return the version
85963   */
85964
85965  static version() {
85966    return {
85967      '@videojs/http-streaming': version$4,
85968      'mux.js': version$3,
85969      'mpd-parser': version$2,
85970      'm3u8-parser': version$1,
85971      'aes-decrypter': version
85972    };
85973  }
85974  /**
85975   * return the version
85976   */
85977
85978  version() {
85979    return this.constructor.version();
85980  }
85981  canChangeType() {
85982    return SourceUpdater.canChangeType();
85983  }
85984  /**
85985   * Begin playing the video.
85986   */
85987
85988  play() {
85989    this.playlistController_.play();
85990  }
85991  /**
85992   * a wrapper around the function in PlaylistController
85993   */
85994
85995  setCurrentTime(currentTime) {
85996    this.playlistController_.setCurrentTime(currentTime);
85997  }
85998  /**
85999   * a wrapper around the function in PlaylistController
86000   */
86001
86002  duration() {
86003    return this.playlistController_.duration();
86004  }
86005  /**
86006   * a wrapper around the function in PlaylistController
86007   */
86008
86009  seekable() {
86010    return this.playlistController_.seekable();
86011  }
86012  /**
86013   * Abort all outstanding work and cleanup.
86014   */
86015
86016  dispose() {
86017    if (this.playbackWatcher_) {
86018      this.playbackWatcher_.dispose();
86019    }
86020    if (this.playlistController_) {
86021      this.playlistController_.dispose();
86022    }
86023    if (this.qualityLevels_) {
86024      this.qualityLevels_.dispose();
86025    }
86026    if (this.tech_ && this.tech_.vhs) {
86027      delete this.tech_.vhs;
86028    }
86029    if (this.mediaSourceUrl_ && window
vendor: 6,191 bytes, lines 86029-86195
86029__default["default"].URL.revokeObjectURL) {
86030      window__default["default"].URL.revokeObjectURL(this.mediaSourceUrl_);
86031      this.mediaSourceUrl_ = null;
86032    }
86033    if (this.tech_) {
86034      this.tech_.off('waitingforkey', this.handleWaitingForKey_);
86035    }
86036    super.dispose();
86037  }
86038  convertToProgramTime(time, callback) {
86039    return getProgramTime({
86040      playlist: this.playlistController_.media(),
86041      time,
86042      callback
86043    });
86044  } // the player must be playing before calling this
86045
86046  seekToProgramTime(programTime, callback, pauseAfterSeek = true, retryCount = 2) {
86047    return seekToProgramTime({
86048      programTime,
86049      playlist: this.playlistController_.media(),
86050      retryCount,
86051      pauseAfterSeek,
86052      seekTo: this.options_.seekTo,
86053      tech: this.options_.tech,
86054      callback
86055    });
86056  }
86057  /**
86058   * Adds the onRequest, onResponse, offRequest and offResponse functions
86059   * to the VhsHandler xhr Object.
86060   */
86061
86062  setupXhrHooks_() {
86063    /**
86064     * A player function for setting an onRequest hook
86065     *
86066     * @param {function} callback for request modifiction
86067     */
86068    this.xhr.onRequest = callback => {
86069      addOnRequestHook(this.xhr, callback);
86070    };
86071    /**
86072     * A player function for setting an onResponse hook
86073     *
86074     * @param {callback} callback for response data retrieval
86075     */
86076
86077    this.xhr.onResponse = callback => {
86078      addOnResponseHook(this.xhr, callback);
86079    };
86080    /**
86081     * Deletes a player onRequest callback if it exists
86082     *
86083     * @param {function} callback to delete from the player set
86084     */
86085
86086    this.xhr.offRequest = callback => {
86087      removeOnRequestHook(this.xhr, callback);
86088    };
86089    /**
86090     * Deletes a player onResponse callback if it exists
86091     *
86092     * @param {function} callback to delete from the player set
86093     */
86094
86095    this.xhr.offResponse = callback => {
86096      removeOnResponseHook(this.xhr, callback);
86097    }; // Trigger an event on the player to notify the user that vhs is ready to set xhr hooks.
86098    // This allows hooks to be set before the source is set to vhs when handleSource is called.
86099
86100    this.player_.trigger('xhr-hooks-ready');
86101  }
86102  attachStreamingEventListeners_() {
86103    const playlistControllerEvents = ['seekablerangeschanged', 'bufferedrangeschanged', 'contentsteeringloadstart', 'contentsteeringloadcomplete', 'contentsteeringparsed'];
86104    const playbackWatcher = ['gapjumped', 'playedrangeschanged']; // re-emit streaming events and payloads on the player.
86105
86106    playlistControllerEvents.forEach(eventName => {
86107      this.playlistController_.on(eventName, metadata => {
86108        this.player_.trigger(_extends__default["default"]({}, metadata));
86109      });
86110    });
86111    playbackWatcher.forEach(eventName => {
86112      this.playbackWatcher_.on(eventName, metadata => {
86113        this.player_.trigger(_extends__default["default"]({}, metadata));
86114      });
86115    });
86116  }
86117}
86118/**
86119 * The Source Handler object, which informs video.js what additional
86120 * MIME types are supported and sets up playback. It is registered
86121 * automatically to the appropriate tech based on the capabilities of
86122 * the browser it is running in. It is not necessary to use or modify
86123 * this object in normal usage.
86124 */
86125
86126const VhsSourceHandler = {
86127  name: 'videojs-http-streaming',
86128  VERSION: version$4,
86129  canHandleSource(srcObj, options = {}) {
86130    const localOptions = merge(videojs.options, options); // If not opting to experimentalUseMMS, and playback is only supported with MediaSource, cannot handle source
86131
86132    if (!localOptions.vhs.experimentalUseMMS && !codecs_js.browserSupportsCodec('avc1.4d400d,mp4a.40.2', false)) {
86133      return false;
86134    }
86135    return VhsSourceHandler.canPlayType(srcObj.type, localOptions);
86136  },
86137  handleSource(source, tech, options = {}) {
86138    const localOptions = merge(videojs.options, options);
86139    tech.vhs = new VhsHandler(source, tech, localOptions);
86140    tech.vhs.xhr = xhrFactory();
86141    tech.vhs.setupXhrHooks_();
86142    tech.vhs.src(source.src, source.type);
86143    return tech.vhs;
86144  },
86145  canPlayType(type, options) {
86146    const simpleType = mediaTypes_js.simpleTypeFromSourceType(type);
86147    if (!simpleType) {
86148      return '';
86149    }
86150    const overrideNative = VhsSourceHandler.getOverrideNative(options);
86151    const supportsTypeNatively = Vhs.supportsTypeNatively(simpleType);
86152    const canUseMsePlayback = !supportsTypeNatively || overrideNative;
86153    return canUseMsePlayback ? 'maybe' : '';
86154  },
86155  getOverrideNative(options = {}) {
86156    const {
86157      vhs = {}
86158    } = options;
86159    const defaultOverrideNative = !(videojs.browser.IS_ANY_SAFARI || videojs.browser.IS_IOS);
86160    const {
86161      overrideNative = defaultOverrideNative
86162    } = vhs;
86163    return overrideNative;
86164  }
86165};
86166/**
86167 * Check to see if either the native MediaSource or ManagedMediaSource
86168 * objectx exist and support an MP4 container with both H.264 video
86169 * and AAC-LC audio.
86170 *
86171 * @return {boolean} if  native media sources are supported
86172 */
86173
86174const supportsNativeMediaSources = () => {
86175  return codecs_js.browserSupportsCodec('avc1.4d400d,mp4a.40.2', true);
86176}; // register source handlers with the appropriate techs
86177
86178if (supportsNativeMediaSources()) {
86179  videojs.getTech('Html5').registerSourceHandler(VhsSourceHandler, 0);
86180}
86181videojs.VhsHandler = VhsHandler;
86182videojs.VhsSourceHandler = VhsSourceHandler;
86183videojs.Vhs = Vhs;
86184if (!videojs.use) {
86185  videojs.registerComponent('Vhs', Vhs);
86186}
86187videojs.options.vhs = videojs.options.vhs || {};
86188if (!videojs.getPlugin || !videojs.getPlugin('reloadSourceOnError')) {
86189  videojs.registerPlugin('reloadSourceOnError', reloadSourceOnError);
86190}
86191
86192module.exports = videojs;
86193
86194}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
86195},{"@babel/runtime/helpers/extends":9,"@videojs/vhs-utils/cjs/byte-helpers":11,"@videojs/vhs-utils/cjs/codecs.js":13,"@videojs/vhs-utils/cjs/containers":14,"@videojs/vhs-utils/cjs/id3-helpers":17,"@videojs/vhs-utils/cjs/media-types.js":19,"@videojs/vhs-utils/cjs/resolve-url.js":23,"@videojs/xhr":26,"global/document":38,"global/window":39,"m3u8-parser":44,"mpd-parser":45,"mux.js/lib/tools/parse-sidx":46,"mux.js/lib/utils/clock":47,"videojs-vtt.js":53}],51:[function(require,module,exports){
vendor: 116,961 bytes, lines 86196-89346
86196/*! @name videojs-contrib-ads @version 7.5.2 @license Apache-2.0 */
86197(function (global, factory) {
86198  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js'), require('global/window'), require('global/document')) :
86199  typeof define === 'function' && define.amd ? define(['video.js', 'global/window', 'global/document'], factory) :
86200  (global = global || self, global.videojsContribAds = factory(global.videojs, global.window, global.document));
86201}(this, function (videojs, window, document) { 'use strict';
86202
86203  videojs = videojs && videojs.hasOwnProperty('default') ? videojs['default'] : videojs;
86204  window = window && window.hasOwnProperty('default') ? window['default'] : window;
86205  document = document && document.hasOwnProperty('default') ? document['default'] : document;
86206
86207  var version = "7.5.2";
86208
86209  /*
86210   * Implements the public API available in `player.ads` as well as application state.
86211   */
86212  function getAds(player) {
86213    return {
86214      disableNextSnapshotRestore: false,
86215      // This is true if we have finished actual content playback but haven't
86216      // dealt with postrolls and officially ended yet
86217      _contentEnding: false,
86218      // This is set to true if the content has officially ended at least once.
86219      // After that, the user can seek backwards and replay content, but _contentHasEnded
86220      // remains true.
86221      _contentHasEnded: false,
86222      // Tracks if loadstart has happened yet for the initial source. It is not reset
86223      // on source changes because loadstart is the event that signals to the ad plugin
86224      // that the source has changed. Therefore, no special signaling is needed to know
86225      // that there has been one for subsequent sources.
86226      _hasThereBeenALoadStartDuringPlayerLife: false,
86227      // Tracks if loadeddata has happened yet for the current source.
86228      _hasThereBeenALoadedData: false,
86229      // Tracks if loadedmetadata has happened yet for the current source.
86230      _hasThereBeenALoadedMetaData: false,
86231      // Are we after startLinearAdMode and before endLinearAdMode?
86232      _inLinearAdMode: false,
86233      // Should we block calls to play on the content player?
86234      _shouldBlockPlay: false,
86235      // Was play blocked by the plugin's playMiddleware feature?
86236      _playBlocked: false,
86237      // Tracks whether play has been requested for this source,
86238      // either by the play method or user interaction
86239      _playRequested: false,
86240      // Contains error information when an error occurs in contrib-ads.
86241      // When set, will contain an object with a type and metadata.
86242      _error: null,
86243      // This is an estimation of the current ad type being played
86244      // This is experimental currently. Do not rely on its presence or behavior!
86245      adType: null,
86246      VERSION: version,
86247      reset: function reset() {
86248        player.ads.disableNextSnapshotRestore = false;
86249        player.ads._contentEnding = false;
86250        player.ads._contentHasEnded = false;
86251        player.ads.snapshot = null;
86252        player.ads.adType = null;
86253        player.ads._hasThereBeenALoadedData = false;
86254        player.ads._hasThereBeenALoadedMetaData = false;
86255        player.ads._cancelledPlay = false;
86256        player.ads._shouldBlockPlay = false;
86257        player.ads._playBlocked = false;
86258        player.ads.nopreroll_ = false;
86259        player.ads.nopostroll_ = false;
86260        player.ads._playRequested = false;
86261        player.ads._error = null;
86262      },
86263      // Call this when an ad response has been received and there are
86264      // linear ads ready to be played.
86265      startLinearAdMode: function startLinearAdMode() {
86266        player.ads._state.startLinearAdMode();
86267      },
86268      // Call this when a linear ad pod has finished playing.
86269      endLinearAdMode: function endLinearAdMode() {
86270        player.ads._state.endLinearAdMode();
86271      },
86272      // Call this when an ad response has been received but there are no
86273      // linear ads to be played (i.e. no ads available, or overlays).
86274      // This has no effect if we are already in an ad break.  Always
86275      // use endLinearAdMode() to exit from linear ad-playback state.
86276      skipLinearAdMode: function skipLinearAdMode() {
86277        player.ads._state.skipLinearAdMode();
86278      },
86279      // With no arguments, returns a boolean value indicating whether or not
86280      // contrib-ads is set to treat ads as stitched with content in a single
86281      // stream. With arguments, treated as a setter, but this behavior is
86282      // deprecated.
86283      stitchedAds: function stitchedAds(arg) {
86284        if (arg !== undefined) {
86285          videojs.log.warn('Using player.ads.stitchedAds() as a setter is deprecated, ' + 'it should be set as an option upon initialization of contrib-ads.');
86286
86287          // Keep the private property and the settings in sync. When this
86288          // setter is removed, we can probably stop using the private property.
86289          this.settings.stitchedAds = !!arg;
86290        }
86291        return this.settings.stitchedAds;
86292      },
86293      // Returns whether the video element has been modified since the
86294      // snapshot was taken.
86295      // We test both src and currentSrc because changing the src attribute to a URL that
86296      // AdBlocker is intercepting doesn't update currentSrc.
86297      videoElementRecycled: function videoElementRecycled() {
86298        if (player.ads.shouldPlayContentBehindAd(player)) {
86299          return false;
86300        }
86301        if (!this.snapshot) {
86302          throw new Error('You cannot use videoElementRecycled while there is no snapshot.');
86303        }
86304        var srcChanged = player.tech_.src() !== this.snapshot.src;
86305        var currentSrcChanged = player.currentSrc() !== this.snapshot.currentSrc;
86306        return srcChanged || currentSrcChanged;
86307      },
86308      // Returns a boolean indicating if given player is in live mode.
86309      // One reason for this: https://github.com/videojs/video.js/issues/3262
86310      // Also, some live content can have a duration.
86311      isLive: function isLive(somePlayer) {
86312        if (somePlayer === void 0) {
86313          somePlayer = player;
86314        }
86315        if (typeof somePlayer.ads.settings.contentIsLive === 'boolean') {
86316          return somePlayer.ads.settings.contentIsLive;
86317        } else if (somePlayer.duration() === Infinity) {
86318          return true;
86319        } else if (videojs.browser.IOS_VERSION === '8' && somePlayer.duration() === 0) {
86320          return true;
86321        }
86322        return false;
86323      },
86324      // Return true if content playback should mute and continue during ad breaks.
86325      // This is only done during live streams on platforms where it's supported.
86326      // This improves speed and accuracy when returning from an ad break.
86327      shouldPlayContentBehindAd: function shouldPlayContentBehindAd(somePlayer) {
86328        if (somePlayer === void 0) {
86329          somePlayer = player;
86330        }
86331        if (!somePlayer) {
86332          throw new Error('shouldPlayContentBehindAd requires a player as a param');
86333        } else if (!somePlayer.ads.settings.liveCuePoints) {
86334          return false;
86335        } else {
86336          return !videojs.browser.IS_IOS && !videojs.browser.IS_ANDROID && somePlayer.duration() === Infinity;
86337        }
86338      },
86339      // Return true if the ads plugin should save and restore snapshots of the
86340      // player state when moving into and out of ad mode.
86341      shouldTakeSnapshots: function shouldTakeSnapshots(somePlayer) {
86342        if (somePlayer === void 0) {
86343          somePlayer = player;
86344        }
86345        return !this.shouldPlayContentBehindAd(somePlayer) && !this.stitchedAds();
86346      },
86347      // Returns true if player is in ad mode.
86348      //
86349      // Ad mode definition:
86350      // If content playback is blocked by the ad plugin.
86351      //
86352      // Examples of ad mode:
86353      //
86354      // * Waiting to find out if an ad is going to play while content would normally be
86355      //   playing.
86356      // * Waiting for an ad to start playing while content would normally be playing.
86357      // * An ad is playing (even if content is also playing)
86358      // * An ad has completed and content is about to resume, but content has not resumed
86359      //   yet.
86360      //
86361      // Examples of not ad mode:
86362      //
86363      // * Content playback has not been requested
86364      // * Content playback is paused
86365      // * An asynchronous ad request is ongoing while content is playing
86366      // * A non-linear ad is active
86367      isInAdMode: function isInAdMode() {
86368        return this._state.isAdState();
86369      },
86370      // Returns true if in ad mode but an ad break hasn't started yet.
86371      isWaitingForAdBreak: function isWaitingForAdBreak() {
86372        return this._state.isWaitingForAdBreak();
86373      },
86374      // Returns true if content is resuming after an ad. This is part of ad mode.
86375      isContentResuming: function isContentResuming() {
86376        return this._state.isContentResuming();
86377      },
86378      // Deprecated because the name was misleading. Use inAdBreak instead.
86379      isAdPlaying: function isAdPlaying() {
86380        return this._state.inAdBreak();
86381      },
86382      // Returns true if an ad break is ongoing. This is part of ad mode.
86383      // An ad break is the time between startLinearAdMode and endLinearAdMode.
86384      inAdBreak: function inAdBreak() {
86385        return this._state.inAdBreak();
86386      },
86387      /*
86388       * Remove the poster attribute from the video element tech, if present. When
86389       * reusing a video element for multiple videos, the poster image will briefly
86390       * reappear while the new source loads. Removing the attribute ahead of time
86391       * prevents the poster from showing up between videos.
86392       *
86393       * @param {Object} player The videojs player object
86394       */
86395      removeNativePoster: function removeNativePoster() {
86396        var tech = player.$('.vjs-tech');
86397        if (tech) {
86398          tech.removeAttribute('poster');
86399        }
86400      },
86401      debug: function debug() {
86402        if (this.settings.debug) {
86403          for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
86404            args[_key] = arguments[_key];
86405          }
86406          if (args.length === 1 && typeof args[0] === 'string') {
86407            videojs.log('ADS: ' + args[0]);
86408          } else {
86409            videojs.log.apply(videojs, ['ADS:'].concat(args));
86410          }
86411        }
86412      },
86413      /**
86414       * Set or get the current ads error.
86415       *
86416       * @fires Player#vjsadserror
86417       *
86418       * @param {import('video.js/dist/types/media-error').ErrorMetadata|null} [err]
86419       *      The error to be set. This can either be a string
86420       *      of the error type, or it can be an object containing the
86421       *      type, along with error metadata.
86422       *      If nothing is passed in, this function will return the current error.
86423       * @param {string} err.errorType The error type.
86424       *
86425       * @return {Object|null|undefined}
86426       *      The current ads error. The function will return undefined if the function
86427       *      is used to set an error. It will return null if there is no error.
86428       */
86429      error: function error(err) {
86430        // If `err` doesn't exist, return the current error.
86431        if (err === undefined) {
86432          return this._error || null;
86433        }
86434
86435        // If `err` is null or not a valid error, reset the ads error.
86436        if (err === null || !err.errorType) {
86437          this._error = null;
86438          return;
86439        }
86440        this._error = err;
86441        videojs.log.error("An error with Ads occured. Type: " + err.errorType + ".");
86442
86443        /**
86444         * @event Player#vjsadserror
86445         * @type {Event}
86446         */
86447        player.trigger({
86448          type: 'vjsadserror',
86449          error: this._error
86450        });
86451      }
86452    };
86453  }
86454
86455  /*
86456  The goal of this feature is to make player events work as an integrator would
86457  expect despite the presense of ads. For example, an integrator would expect
86458  an `ended` event to happen once the content is ended. If an `ended` event is sent
86459  as a result of a preroll ending, that is a bug. The `redispatch` method should recognize
86460  such `ended` events and prefix them so they are sent as `adended`, and so on with
86461  all other player events.
86462  */
86463
86464  // Cancel an event.
86465  // Video.js wraps native events. This technique stops propagation for the Video.js event
86466  // (AKA player event or wrapper event) while native events continue propagating.
86467  var cancelEvent = function cancelEvent(player, event) {
86468    event.isImmediatePropagationStopped = function () {
86469      return true;
86470    };
86471    event.cancelBubble = true;
86472    event.isPropagationStopped = function () {
86473      return true;
86474    };
86475  };
86476
86477  // Redispatch an event with a prefix.
86478  // Cancels the event, then sends a new event with the type of the original
86479  // event with the given prefix added.
86480  // The inclusion of the "state" property should be removed in a future
86481  // major version update with instructions to migrate any code that relies on it.
86482  // It is an implementation detail and relying on it creates fragility.
86483  var prefixEvent = function prefixEvent(player, prefix, event) {
86484    cancelEvent(player, event);
86485    player.trigger({
86486      type: prefix + event.type,
86487      originalEvent: event
86488    });
86489  };
86490
86491  // Playing event
86492  // Requirements:
86493  // * Normal playing event when there is no preroll
86494  // * No playing event before preroll
86495  // * At least one playing event after preroll
86496  var handlePlaying = function handlePlaying(player, event) {
86497    if (player.ads.isInAdMode()) {
86498      if (player.ads.isContentResuming()) {
86499        // Prefix playing event when switching back to content after postroll.
86500        if (player.ads._contentEnding) {
86501          prefixEvent(player, 'content', event);
86502        }
86503
86504        // Prefix all other playing events during ads.
86505      } else {
86506        prefixEvent(player, 'ad', event);
86507      }
86508    }
86509  };
86510
86511  // Ended event
86512  // Requirements:
86513  // * A single ended event when there is no postroll
86514  // * No ended event before postroll
86515  // * A single ended event after postroll
86516  var handleEnded = function handleEnded(player, event) {
86517    if (player.ads.isInAdMode()) {
86518      // Cancel ended events during content resuming. Normally we would
86519      // prefix them, but `contentended` has a special meaning. In the
86520      // future we'd like to rename the existing `contentended` to
86521      // `readyforpostroll`, then we could remove the special `resumeended`
86522      // and do a conventional content prefix here.
86523      if (player.ads.isContentResuming()) {
86524        cancelEvent(player, event);
86525
86526        // Important: do not use this event outside of videojs-contrib-ads.
86527        // It will be removed and your code will break.
86528        // Ideally this would simply be `contentended`, but until
86529        // `contentended` no longer has a special meaning it cannot be
86530        // changed.
86531        player.trigger('resumeended');
86532
86533        // Ad prefix in ad mode
86534      } else {
86535        prefixEvent(player, 'ad', event);
86536      }
86537
86538      // Prefix ended due to content ending before postroll check
86539    } else if (!player.ads._contentHasEnded && !player.ads.stitchedAds()) {
86540      // This will change to cancelEvent after the contentended deprecation
86541      // period (contrib-ads 7)
86542      prefixEvent(player, 'content', event);
86543
86544      // Content ended for the first time, time to check for postrolls
86545      player.trigger('readyforpostroll');
86546    }
86547  };
86548
86549  // handleLoadEvent is used for loadstart, loadeddata, and loadedmetadata
86550  // Requirements:
86551  // * Initial event is not prefixed
86552  // * Event due to ad loading is prefixed
86553  // * Event due to content source change is not prefixed
86554  // * Event due to content resuming is prefixed
86555  var handleLoadEvent = function handleLoadEvent(player, event) {
86556    // Initial event
86557    if (event.type === 'loadstart' && !player.ads._hasThereBeenALoadStartDuringPlayerLife || event.type === 'loadeddata' && !player.ads._hasThereBeenALoadedData || event.type === 'loadedmetadata' && !player.ads._hasThereBeenALoadedMetaData) {
86558      return;
86559
86560      // Ad playing
86561    } else if (player.ads.inAdBreak()) {
86562      prefixEvent(player, 'ad', event);
86563
86564      // Source change
86565    } else if (player.currentSrc() !== player.ads.contentSrc) {
86566      return;
86567
86568      // Content resuming
86569    } else {
86570      prefixEvent(player, 'content', event);
86571    }
86572  };
86573
86574  // Play event
86575  // Requirements:
86576  // * Play events have the "ad" prefix when an ad is playing
86577  // * Play events have the "content" prefix when content is resuming
86578  // Play requests are unique because they represent user intention to play. They happen
86579  // because the user clicked play, or someone called player.play(), etc. It could happen
86580  // multiple times during ad loading, regardless of where we are in the process. With our
86581  // current architecture, this could cause the content to start playing.
86582  // Therefore, contrib-ads must always either:
86583  //   - cancelContentPlay if there is any possible chance the play caused the
86584  //     content to start playing, even if we are technically in ad mode. In order for
86585  //     that to happen, play events need to be unprefixed until the last possible moment.
86586  //   - use playMiddleware to stop the play from reaching the Tech so there is no risk
86587  //     of the content starting to play.
86588  // Currently, playMiddleware is only supported on desktop browsers with
86589  // video.js after version 6.7.1.
86590  var handlePlay = function handlePlay(player, event) {
86591    if (player.ads.inAdBreak()) {
86592      prefixEvent(player, 'ad', event);
86593
86594      // Content resuming
86595    } else if (player.ads.isContentResuming()) {
86596      prefixEvent(player, 'content', event);
86597    }
86598  };
86599
86600  // Handle a player event, either by redispatching it with a prefix, or by
86601  // letting it go on its way without any meddling.
86602  function redispatch(event) {
86603    // Events with special treatment
86604    if (event.type === 'playing') {
86605      handlePlaying(this, event);
86606    } else if (event.type === 'ended') {
86607      handleEnded(this, event);
86608    } else if (event.type === 'loadstart' || event.type === 'loadeddata' || event.type === 'loadedmetadata') {
86609      handleLoadEvent(this, event);
86610    } else if (event.type === 'play') {
86611      handlePlay(this, event);
86612
86613      // Standard handling for all other events
86614    } else if (this.ads.isInAdMode()) {
86615      if (this.ads.isContentResuming()) {
86616        // Event came from snapshot restore after an ad, use "content" prefix
86617        prefixEvent(this, 'content', event);
86618      } else {
86619        // Event came from ad playback, use "ad" prefix
86620        prefixEvent(this, 'ad', event);
86621      }
86622    }
86623  }
86624
86625  /*
86626  This feature sends a `contentupdate` event when the player source changes.
86627  */
86628
86629  // Start sending contentupdate events
86630  function initializeContentupdate(player) {
86631    // Keep track of the current content source
86632    // If you want to change the src of the video without triggering
86633    // the ad workflow to restart, you can update this variable before
86634    // modifying the player's source
86635    player.ads.contentSrc = player.currentSrc();
86636    player.ads._seenInitialLoadstart = false;
86637
86638    // Check if a new src has been set, if so, trigger contentupdate
86639    var checkSrc = function checkSrc() {
86640      if (!player.ads.inAdBreak()) {
86641        var src = player.currentSrc();
86642        if (src !== player.ads.contentSrc) {
86643          if (player.ads._seenInitialLoadstart) {
86644            player.trigger({
86645              type: 'contentchanged'
86646            });
86647          }
86648          player.trigger({
86649            type: 'contentupdate',
86650            oldValue: player.ads.contentSrc,
86651            newValue: src
86652          });
86653          player.ads.contentSrc = src;
86654        }
86655        player.ads._seenInitialLoadstart = true;
86656      }
86657    };
86658
86659    // loadstart reliably indicates a new src has been set
86660    player.on('loadstart', checkSrc);
86661  }
86662
86663  function _extends() {
86664    _extends = Object.assign ? Object.assign.bind() : function (target) {
86665      for (var i = 1; i < arguments.length; i++) {
86666        var source = arguments[i];
86667        for (var key in source) {
86668          if (Object.prototype.hasOwnProperty.call(source, key)) {
86669            target[key] = source[key];
86670          }
86671        }
86672      }
86673      return target;
86674    };
86675    return _extends.apply(this, arguments);
86676  }
86677  function _inheritsLoose(subClass, superClass) {
86678    subClass.prototype = Object.create(superClass.prototype);
86679    subClass.prototype.constructor = subClass;
86680    _setPrototypeOf(subClass, superClass);
86681  }
86682  function _setPrototypeOf(o, p) {
86683    _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {
86684      o.__proto__ = p;
86685      return o;
86686    };
86687    return _setPrototypeOf(o, p);
86688  }
86689
86690  /**
86691   * Current tcfData returned from CMP
86692   * Updated on event listener rather than having to make an asyc
86693   * check within the macro resolver
86694   */
86695  var tcData = {};
86696
86697  /**
86698   * Sets up a proxy for the TCF API in an iframed player, if a parent frame
86699   * that has implemented the TCF API is detected
86700   * https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md#is-there-a-sample-iframe-script-call-to-the-cmp-api
86701   */
86702  var proxyTcfApi = function proxyTcfApi(_) {
86703    if (videojs.dom.isInFrame() && typeof window.__tcfapi !== 'function') {
86704      var frame = window;
86705      var cmpFrame;
86706      var cmpCallbacks = {};
86707      while (frame) {
86708        try {
86709          if (frame.frames.__tcfapiLocator) {
86710            cmpFrame = frame;
86711            break;
86712          }
86713        } catch (ignore) {
86714          // empty
86715        }
86716        if (frame === window.top) {
86717          break;
86718        }
86719        frame = frame.parent;
86720      }
86721      if (!cmpFrame) {
86722        return;
86723      }
86724      window.__tcfapi = function (cmd, version, callback, arg) {
86725        var callId = Math.random() + '';
86726        var msg = {
86727          __tcfapiCall: {
86728            command: cmd,
86729            parameter: arg,
86730            version: version,
86731            callId: callId
86732          }
86733        };
86734        cmpCallbacks[callId] = callback;
86735        cmpFrame.postMessage(msg, '*');
86736      };
86737      window.addEventListener('message', function (event) {
86738        var json = {};
86739        try {
86740          json = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
86741        } catch (ignore) {
86742          // empty
86743        }
86744        var payload = json.__tcfapiReturn;
86745        if (payload) {
86746          if (typeof cmpCallbacks[payload.callId] === 'function') {
86747            cmpCallbacks[payload.callId](payload.returnValue, payload.success);
86748            cmpCallbacks[payload.callId] = null;
86749          }
86750        }
86751      }, false);
86752    }
86753  };
86754
86755  /**
86756   * Sets up event listener for changes to consent data.
86757   */
86758  var listenToTcf = function listenToTcf() {
86759    proxyTcfApi();
86760    if (typeof window.__tcfapi === 'function') {
86761      window.__tcfapi('addEventListener', 2, function (data, success) {
86762        if (success) {
86763          tcData = data;
86764        }
86765      });
86766    }
86767  };
86768
86769  var findUspApiLocatorWindow = function findUspApiLocatorWindow(windowObj) {
86770    var targetWindow = windowObj.parent;
86771    while (targetWindow !== windowObj.top) {
86772      try {
86773        if (targetWindow.frames && targetWindow.frames.__uspapiLocator) {
86774          return targetWindow;
86775        }
86776      } catch (ignore) {
86777        // do nothing
86778      }
86779      targetWindow = targetWindow.parent;
86780    }
86781
86782    // Check for the __uspapiLocator frame in the top window
86783    try {
86784      if (windowObj.top.frames && windowObj.top.frames.__uspapiLocator) {
86785        return windowObj.top;
86786      }
86787    } catch (ignore) {
86788      // do nothing
86789    }
86790
86791    // Return null if no __uspapiLocator frame is found in any window
86792    return null;
86793  };
86794  var uspString = '';
86795  var getCurrentUspString = function getCurrentUspString() {
86796    return uspString;
86797  };
86798
86799  // Call the USP API to get the US Privacy String, either by invoking it directly or via postMessage() if inside an iframe.
86800  // In the former case the callback is synchronous, if the latter it is asynchronous, so to be safe it should always be
86801  // assumed to be asynchronous.
86802  // The window is passable as an argument for ease of testing
86803  var obtainUsPrivacyString = function obtainUsPrivacyString(callback, windowObj) {
86804    if (windowObj === void 0) {
86805      windowObj = window;
86806    }
86807    if (windowObj.__uspapi) {
86808      windowObj.__uspapi('getUSPData', 1, function (uspData, success) {
86809        var privacyString = success ? uspData.uspString : null;
86810        uspString = privacyString;
86811        callback(privacyString);
86812      });
86813    } else {
86814      var targetWindow = findUspApiLocatorWindow(windowObj);
86815
86816      // If no __uspapiLocator frame is found, execute the callback with a null privacy string
86817      if (!targetWindow) {
86818        callback(null);
86819        return;
86820      }
86821      var uniqueId = Math.random().toString(36).substring(2);
86822      var message = {
86823        __uspapiCall: {
86824          command: 'getUSPData',
86825          version: 1,
86826          callId: uniqueId
86827        }
86828      };
86829      var handleMessageEvent = function handleMessageEvent(event) {
86830        if (event && event.data && event.data.__uspapiReturn && event.data.__uspapiReturn.callId === uniqueId) {
86831          windowObj.removeEventListener('message', handleMessageEvent, false);
86832          var _event$data$__uspapiR = event.data.__uspapiReturn,
86833            returnValue = _event$data$__uspapiR.returnValue,
86834            success = _event$data$__uspapiR.success;
86835          var privacyString = success ? returnValue.uspString : null;
86836          uspString = privacyString;
86837          callback(privacyString);
86838        }
86839      };
86840      windowObj.addEventListener('message', handleMessageEvent, false);
86841      targetWindow.postMessage(message, '*');
86842    }
86843  };
86844
86845  var Error$1 = {
86846    AdsBeforePrerollError: 'ads-before-preroll-error',
86847    AdsPrerollError: 'ads-preroll-error',
86848    AdsMidrollError: 'ads-midroll-error',
86849    AdsPostrollError: 'ads-postroll-error',
86850    AdsMacroReplacementFailed: 'ads-macro-replacement-failed',
86851    AdsResumeContentFailed: 'ads-resume-content-failed'
86852  };
86853
86854  var uriEncodeIfNeeded = function uriEncodeIfNeeded(value, uriEncode) {
86855    return uriEncode ? encodeURIComponent(value) : value;
86856  };
86857
86858  // Add custom field macros to macros object
86859  // based on given name for custom fields property of mediainfo object.
86860  var customFields = function customFields(mediainfo, macros, customFieldsName) {
86861    if (mediainfo && mediainfo[customFieldsName]) {
86862      var fields = mediainfo[customFieldsName];
86863      var fieldNames = Object.keys(fields);
86864      for (var i = 0; i < fieldNames.length; i++) {
86865        var tag = '{mediainfo.' + customFieldsName + '.' + fieldNames[i] + '}';
86866        macros[tag] = fields[fieldNames[i]];
86867      }
86868    }
86869  };
86870  var getMediaInfoMacros = function getMediaInfoMacros(mediainfo, defaults) {
86871    var macros = {};
86872    ['description', 'tags', 'reference_id', 'ad_keys'].forEach(function (prop) {
86873      if (mediainfo && mediainfo[prop]) {
86874        macros["{mediainfo." + prop + "}"] = mediainfo[prop];
86875      } else if (defaults["{mediainfo." + prop + "}"]) {
86876        macros["{mediainfo." + prop + "}"] = defaults["{mediainfo." + prop + "}"];
86877      } else {
86878        macros["{mediainfo." + prop + "}"] = '';
86879      }
86880    });
86881    ['custom_fields', 'customFields'].forEach(function (customFieldProp) {
86882      customFields(mediainfo, macros, customFieldProp);
86883    });
86884    return macros;
86885  };
86886  var getDefaultValues = function getDefaultValues(string) {
86887    var defaults = {};
86888    var modifiedString = string.replace(/{([^}=]+)=([^}]*)}/g, function (match, name, defaultVal) {
86889      defaults["{" + name + "}"] = defaultVal;
86890      return "{" + name + "}";
86891    });
86892    return {
86893      defaults: defaults,
86894      modifiedString: modifiedString
86895    };
86896  };
86897  var getStaticMacros = function getStaticMacros(player) {
86898    return {
86899      '{player.id}': player.options_['data-player'] || player.id_,
86900      '{player.height}': player.currentHeight(),
86901      '{player.width}': player.currentWidth(),
86902      '{player.heightInt}': Math.round(player.currentHeight()),
86903      '{player.widthInt}': Math.round(player.currentWidth()),
86904      '{player.autoplay}': player.autoplay() ? 1 : 0,
86905      '{player.muted}': player.muted() ? 1 : 0,
86906      '{player.language}': player.language() || '',
86907      '{mediainfo.id}': player.mediainfo ? player.mediainfo.id : '',
86908      '{mediainfo.name}': player.mediainfo ? player.mediainfo.name : '',
86909      '{mediainfo.duration}': player.mediainfo ? player.mediainfo.duration : '',
86910      '{player.duration}': player.duration(),
86911      '{player.durationInt}': Math.round(player.duration()),
86912      '{player.live}': player.duration() === Infinity ? 1 : 0,
86913      '{player.pageUrl}': videojs.dom.isInFrame() ? document.referrer : window.location.href,
86914      '{playlistinfo.id}': player.playlistinfo ? player.playlistinfo.id : '',
86915      '{playlistinfo.name}': player.playlistinfo ? player.playlistinfo.name : '',
86916      '{timestamp}': new Date().getTime(),
86917      '{document.referrer}': document.referrer,
86918      '{window.location.href}': window.location.href,
86919      '{random}': Math.floor(Math.random() * 1000000000000)
86920    };
86921  };
86922  var getTcfMacros = function getTcfMacros(tcDataObj) {
86923    var tcfMacros = {};
86924    Object.keys(tcDataObj).forEach(function (key) {
86925      tcfMacros["{tcf." + key + "}"] = tcDataObj[key];
86926    });
86927    tcfMacros['{tcf.gdprAppliesInt}'] = tcDataObj.gdprApplies ? 1 : 0;
86928    return tcfMacros;
86929  };
86930  var getUspMacros = function getUspMacros() {
86931    return {
86932      '{usp.uspString}': getCurrentUspString()
86933    };
86934  };
86935
86936  // This extracts and evaluates variables from the `window` object for macro replacement. While replaceMacros() handles generic macro name
86937  // overriding for other macro types, this function also needs to reference the overrides in order to map custom macro names in the string
86938  // to their corresponding default pageVariable names, so they can be evaluated on the `window` and stored for later replacement in replaceMacros().
86939  var getPageVariableMacros = function getPageVariableMacros(string, defaults, macroNameOverrides) {
86940    var pageVarRegex = new RegExp('{pageVariable\\.([^}]+)}', 'g');
86941    var pageVariablesMacros = {};
86942
86943    // Aggregate any default pageVariable macros found in the string with any pageVariable macros that have custom names specified in
86944    // macroNameOverrides.
86945    var pageVariables = (string.match(pageVarRegex) || []).concat(Object.keys(macroNameOverrides).filter(function (macroName) {
86946      return pageVarRegex.test(macroName) && string.includes(macroNameOverrides[macroName]);
86947    }));
86948    if (!pageVariables) {
86949      return;
86950    }
86951    pageVariables.forEach(function (pageVar) {
86952      var key = pageVar;
86953      var name = pageVar.slice(14, -1);
86954      var names = name.split('.');
86955      var context = window;
86956      var value;
86957
86958      // Iterate down multiple levels of selector without using eval
86959      // This makes things like pageVariable.foo.bar work
86960      for (var i = 0; i < names.length; i++) {
86961        if (i === names.length - 1) {
86962          value = context[names[i]];
86963        } else {
86964          context = context[names[i]];
86965          if (typeof context === 'undefined') {
86966            break;
86967          }
86968        }
86969      }
86970      var type = typeof value;
86971
86972      // Only allow certain types of values. Anything else is probably a mistake.
86973      if (value === null) {
86974        pageVariablesMacros[key] = 'null';
86975      } else if (value === undefined) {
86976        if (defaults[key]) {
86977          pageVariablesMacros[key] = defaults[key];
86978        } else {
86979          videojs.log.warn("Page variable \"" + name + "\" not found");
86980          pageVariablesMacros[key] = '';
86981        }
86982      } else if (type !== 'string' && type !== 'number' && type !== 'boolean') {
86983        videojs.log.warn("Page variable \"" + name + "\" is not a supported type");
86984        pageVariablesMacros[key] = '';
86985      } else {
86986        pageVariablesMacros[key] = value;
86987      }
86988    });
86989    return pageVariablesMacros;
86990  };
86991  var replaceMacros = function replaceMacros(string, macros, uriEncode, overrides, player) {
86992    if (overrides === void 0) {
86993      overrides = {};
86994    }
86995    for (var macroName in macros) {
86996      // The resolvedMacroName is the macro as it is expected to appear in the actual string, or regex if it has been provided.
86997      var resolvedMacroName = overrides.hasOwnProperty(macroName) ? overrides[macroName] : macroName;
86998      if (resolvedMacroName.startsWith('r:')) {
86999        try {
87000          var regex = new RegExp(resolvedMacroName.slice(2), 'g');
87001          string = string.replace(regex, uriEncodeIfNeeded(macros[macroName], uriEncode));
87002        } catch (error) {
87003          player.ads.error({
87004            errorType: Error$1.AdsMacroReplacementFailed,
87005            macro: macroName,
87006            error: error
87007          });
87008          videojs.log.warn("Unable to replace macro with regex \"" + resolvedMacroName + "\". The provided regex may be invalid.");
87009        }
87010      } else {
87011        string = string.split(resolvedMacroName).join(uriEncodeIfNeeded(macros[macroName], uriEncode));
87012      }
87013    }
87014    return string;
87015  };
87016
87017  /**
87018   *
87019   * @param {string} string
87020   *                 Any string with macros to be replaced
87021   * @param {boolean} uriEncode
87022   *                  A Boolean value indicating whether the macros should be replaced with URI-encoded values
87023   * @param {object} customMacros
87024   *                 An object with custom macros and values to map them to. For example: {'{five}': 5}
87025   * @param {boolean} customMacros.disableDefaultMacros
87026   *                  A boolean indicating whether replacement of default macros should be forgone in favor of only customMacros
87027   * @param {object} customMacros.macroNameOverrides
87028   *                 An object that specifies custom names for default macros, following the following format:
87029   *                 // {'{default-macro-name}': '{new-macro-name}'}
87030   *                 {'{player.id}': '{{PLAYER_ID}}', ...}
87031   * @returns {string}
87032   *          The provided string with all macros replaced. For example: adMacroReplacement('{player.id}') returns a string of the player id
87033   */
87034  function adMacroReplacement(string, uriEncode, customMacros) {
87035    if (uriEncode === void 0) {
87036      uriEncode = false;
87037    }
87038    if (customMacros === void 0) {
87039      customMacros = {};
87040    }
87041    var disableDefaultMacros = customMacros.disableDefaultMacros || false;
87042    var macroNameOverrides = customMacros.macroNameOverrides || {};
87043
87044    // Remove special properties from customMacros
87045    delete customMacros.disableDefaultMacros;
87046    delete customMacros.macroNameOverrides;
87047    var macros = customMacros;
87048    if (disableDefaultMacros) {
87049      return replaceMacros(string, macros, uriEncode, macroNameOverrides);
87050    }
87051
87052    // Get macros with defaults e.g. {x=y}, store the values in `defaults` and replace with standard macros in the string
87053    var _getDefaultValues = getDefaultValues(string),
87054      defaults = _getDefaultValues.defaults,
87055      modifiedString = _getDefaultValues.modifiedString;
87056    string = modifiedString;
87057
87058    // Get all macro values
87059    _extends(macros, getStaticMacros(this), getMediaInfoMacros(this.mediainfo, defaults), getTcfMacros(tcData), getUspMacros(), getPageVariableMacros(string, defaults, macroNameOverrides));
87060
87061    // Perform macro replacement
87062    string = replaceMacros(string, macros, uriEncode, macroNameOverrides, this);
87063
87064    // Replace any remaining default values that have not already been replaced. This includes mediainfo custom fields.
87065    for (var macro in defaults) {
87066      string = string.replace(macro, defaults[macro]);
87067    }
87068    return string;
87069  }
87070
87071  /*
87072  * This feature allows metadata text tracks to be manipulated once available
87073  * @see processMetadataTracks.
87074  * It also allows ad implementations to leverage ad cues coming through
87075  * text tracks, @see processAdTrack
87076  **/
87077  var cueTextTracks = {};
87078
87079  /*
87080  * This feature allows metadata text tracks to be manipulated once they are available,
87081  * usually after the 'loadstart' event is observed on the player
87082  * @param player A reference to a player
87083  * @param processMetadataTrack A callback that performs some operations on a
87084  * metadata text track
87085  **/
87086  cueTextTracks.processMetadataTracks = function (player, processMetadataTrack) {
87087    var tracks = player.textTracks();
87088    var setModeAndProcess = function setModeAndProcess(track) {
87089      if (track.kind === 'metadata') {
87090        player.ads.cueTextTracks.setMetadataTrackMode(track);
87091        processMetadataTrack(player, track);
87092      }
87093    };
87094
87095    // Text tracks are available
87096    for (var i = 0; i < tracks.length; i++) {
87097      setModeAndProcess(tracks[i]);
87098    }
87099
87100    // Wait until text tracks are added
87101    tracks.addEventListener('addtrack', function (event) {
87102      setModeAndProcess(event.track);
87103    });
87104  };
87105
87106  /*
87107  * Sets the track mode to one of 'disabled', 'hidden' or 'showing'
87108  * @see https://github.com/videojs/video.js/blob/master/docs/guides/text-tracks.md
87109  * Default behavior is to do nothing, @override if this is not desired
87110  * @param track The text track to set the mode on
87111  */
87112  cueTextTracks.setMetadataTrackMode = function (track) {
87113    return;
87114  };
87115
87116  /*
87117  * Determines whether cue is an ad cue and returns the cue data.
87118  * @param player A reference to the player
87119  * @param cue The full cue object
87120  * Returns the given cue by default @override if futher processing is required
87121  * @return {Object} a useable ad cue or null if not supported
87122  **/
87123  cueTextTracks.getSupportedAdCue = function (player, cue) {
87124    return cue;
87125  };
87126
87127  /*
87128  * Defines whether a cue is supported or not, potentially
87129  * based on the player settings
87130  * @param player A reference to the player
87131  * @param cue The cue to be checked
87132  * Default behavior is to return true, @override if this is not desired
87133  * @return {Boolean}
87134  */
87135  cueTextTracks.isSupportedAdCue = function (player, cue) {
87136    return true;
87137  };
87138
87139  /*
87140  * Gets the id associated with a cue.
87141  * @param cue The cue to extract an ID from
87142  * @returns The first occurance of 'id' in the object,
87143  * @override if this is not the desired cue id
87144  **/
87145  cueTextTracks.getCueId = function (player, cue) {
87146    return cue.id;
87147  };
87148
87149  /*
87150  * Checks whether a cue has already been used
87151  * @param cueId The Id associated with a cue
87152  **/
87153  var cueAlreadySeen = function cueAlreadySeen(player, cueId) {
87154    return cueId !== undefined && player.ads.includedCues[cueId];
87155  };
87156
87157  /*
87158  * Indicates that a cue has been used
87159  * @param cueId The Id associated with a cue
87160  **/
87161  var setCueAlreadySeen = function setCueAlreadySeen(player, cueId) {
87162    if (cueId !== undefined && cueId !== '') {
87163      player.ads.includedCues[cueId] = true;
87164    }
87165  };
87166
87167  /*
87168  * This feature allows ad metadata tracks to be manipulated in ad implementations
87169  * @param player A reference to the player
87170  * @param cues The set of cues to work with
87171  * @param processCue A method that uses a cue to make some
87172  * ad request in the ad implementation
87173  * @param [cancelAdsHandler] A method that dynamically cancels ads in the ad implementation
87174  **/
87175  cueTextTracks.processAdTrack = function (player, cues, processCue, cancelAdsHandler) {
87176    player.ads.includedCues = {};
87177
87178    // loop over set of cues
87179    for (var i = 0; i < cues.length; i++) {
87180      var cue = cues[i];
87181      var cueData = this.getSupportedAdCue(player, cue);
87182
87183      // Exit if this is not a supported cue
87184      if (!this.isSupportedAdCue(player, cue)) {
87185        videojs.log.warn('Skipping as this is not a supported ad cue.', cue);
87186        return;
87187      }
87188
87189      // Continue processing supported cue
87190      var cueId = this.getCueId(player, cue);
87191      var startTime = cue.startTime;
87192
87193      // Skip ad if cue was already used
87194      if (cueAlreadySeen(player, cueId)) {
87195        videojs.log('Skipping ad already seen with ID ' + cueId);
87196        return;
87197      }
87198
87199      // Optional dynamic ad cancellation
87200      if (cancelAdsHandler) {
87201        cancelAdsHandler(player, cueData, cueId, startTime);
87202      }
87203
87204      // Process cue as an ad cue
87205      processCue(player, cueData, cueId, startTime);
87206
87207      // Indicate that this cue has been used
87208      setCueAlreadySeen(player, cueId);
87209    }
87210  };
87211
87212  function initCancelContentPlay(player, debug) {
87213    if (debug) {
87214      videojs.log('Using cancelContentPlay to block content playback');
87215    }
87216
87217    // Listen to play events to "cancel" them afterward
87218    player.on('play', cancelContentPlay);
87219  }
87220
87221  /*
87222  This feature makes sure the player is paused during ad loading.
87223
87224  It does this by pausing the player immediately after a "play" where ads will be requested,
87225  then signalling that we should play after the ad is done.
87226  */
87227
87228  function cancelContentPlay() {
87229    // this function is in the player's context
87230
87231    if (this.ads._shouldBlockPlay === false) {
87232      // Only block play if the ad plugin is in a state when content
87233      // playback should be blocked. This currently means during
87234      // BeforePrerollState and PrerollState.
87235      return;
87236    }
87237
87238    // pause playback so ads can be handled.
87239    if (!this.paused()) {
87240      this.ads.debug('Playback was canceled by cancelContentPlay');
87241      this.pause();
87242    }
87243
87244    // When the 'content-playback' state is entered, this will let us know to play.
87245    // This is needed if there is no preroll or if it errors, times out, etc.
87246    this.ads._cancelledPlay = true;
87247  }
87248
87249  var obj = {};
87250  // This reference allows videojs to be mocked in unit tests
87251  // while still using the available videojs import in the source code
87252  // @see obj.testHook
87253  var videojsReference = videojs;
87254
87255  /**
87256   * Checks if middleware mediators are available and
87257   * can be used on this platform.
87258   * Currently we can only use mediators on desktop platforms.
87259   */
87260  obj.isMiddlewareMediatorSupported = function () {
87261    if (videojsReference.browser.IS_IOS || videojsReference.browser.IS_ANDROID) {
87262      return false;
87263    } else if (
87264    // added when middleware was introduced in video.js
87265    videojsReference.use &&
87266    // added when mediators were introduced in video.js
87267    videojsReference.middleware && videojsReference.middleware.TERMINATOR) {
87268      return true;
87269    }
87270    return false;
87271  };
87272  obj.playMiddleware = function (player) {
87273    return {
87274      setSource: function setSource(srcObj, next) {
87275        next(null, srcObj);
87276      },
87277      callPlay: function callPlay() {
87278        // Block play calls while waiting for an ad, only if this is an
87279        // ad supported player
87280        if (player.ads && player.ads._shouldBlockPlay === true) {
87281          player.ads.debug('Using playMiddleware to block content playback');
87282          player.ads._playBlocked = true;
87283          return videojsReference.middleware.TERMINATOR;
87284        }
87285      },
87286      play: function play(terminated, playPromise) {
87287        if (player.ads && player.ads._playBlocked && terminated) {
87288          player.ads.debug('Play call to Tech was terminated.');
87289          // Trigger play event to match the user's intent to play.
87290          // The call to play on the Tech has been blocked, so triggering
87291          // the event on the Player will not affect the Tech's playback state.
87292          player.trigger('play');
87293          // At this point the player has technically started
87294          player.addClass('vjs-has-started');
87295          // Reset playBlocked
87296          player.ads._playBlocked = false;
87297
87298          // Safari issues a pause event when autoplay is blocked but other browsers
87299          // do not, so we send a pause for consistency in those cases. This keeps the
87300          // play button in the correct state if play is rejected.
87301        } else if (playPromise && playPromise.catch) {
87302          playPromise.catch(function (e) {
87303            if (e.name === 'NotAllowedError' && !videojs.browser.IS_SAFARI) {
87304              player.trigger('pause');
87305            }
87306          });
87307        }
87308      }
87309    };
87310  };
87311  obj.testHook = function (testVjs) {
87312    videojsReference = testVjs;
87313  };
87314
87315  var playMiddleware = obj.playMiddleware,
87316    isMiddlewareMediatorSupported = obj.isMiddlewareMediatorSupported;
87317
87318  /**
87319   * Whether or not this copy of Video.js has the ads plugin.
87320   *
87321   * @return {boolean}
87322   *         If `true`, has the plugin. `false` otherwise.
87323   */
87324  var hasAdsPlugin = function hasAdsPlugin() {
87325    // Video.js 6 and 7 have a getPlugin method.
87326    if (videojs.getPlugin) {
87327      return Boolean(videojs.getPlugin('ads'));
87328    }
87329
87330    // Video.js 5 does not have a getPlugin method, so check the player prototype.
87331    var Player = videojs.getComponent('Player');
87332    return Boolean(Player && Player.prototype.ads);
87333  };
87334
87335  /**
87336   * Register contrib-ads with Video.js, but provide protection for duplicate
87337   * copies of the plugin. This could happen if, for example, a stitched ads
87338   * plugin and a client-side ads plugin are included simultaneously with their
87339   * own copies of contrib-ads.
87340   *
87341   * If contrib-ads detects a pre-existing duplicate, it will not register
87342   * itself.
87343   *
87344   * Ad plugins using contrib-ads and anticipating that this could come into
87345   * effect should verify that the contrib-ads they are using is of a compatible
87346   * version.
87347   *
87348   * @param  {Function} contribAdsPlugin
87349   *         The plugin function.
87350   *
87351   * @return {boolean}
87352   *         When `true`, the plugin was registered. When `false`, the plugin
87353   *         was not registered.
87354   */
87355  function register(contribAdsPlugin) {
87356    // If the ads plugin already exists, do not overwrite it.
87357    if (hasAdsPlugin(videojs)) {
87358      return false;
87359    }
87360
87361    // Cross-compatibility with Video.js 6/7 and 5.
87362    var registerPlugin = videojs.registerPlugin || videojs.plugin;
87363
87364    // Register this plugin with Video.js.
87365    registerPlugin('ads', contribAdsPlugin);
87366
87367    // Register the play middleware with Video.js on script execution,
87368    // to avoid a new playMiddleware factory being added for each player.
87369    // The `usingContribAdsMiddleware_` flag is used to ensure that we only ever
87370    // register the middleware once - despite the ability to de-register and
87371    // re-register the plugin itself.
87372    if (isMiddlewareMediatorSupported() && !videojs.usingContribAdsMiddleware_) {
87373      // Register the play middleware
87374      videojs.use('*', playMiddleware);
87375      videojs.usingContribAdsMiddleware_ = true;
87376      videojs.log.debug('Play middleware has been registered with videojs');
87377    }
87378    return true;
87379  }
87380
87381  var OUTSTREAM_VIDEO = {
87382    src: 'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAxJtZGF03ABMYXZjNjEuMy4xMDAAQiAIwRg4AAACrgYF//+q3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE2NCByMzEwOCAzMWUxOWY5IC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAyMyAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTcgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTI1IHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAOWWIhAAn//71sXwKaunqigzoMi7hlyTJrrYi4m0AwABFxzcqWKGahTaoBcQAAMWK4AlcFlIICBeldSEQBGCMHAAABVNtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAAIgABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAACQXRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAAIgAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAABQAAAAPAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAACIAAAAAAAEAAAAAAbltZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAADwAAAACAFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAFkbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABJHN0YmwAAADAc3RzZAAAAAAAAAABAAAAsGF2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAABQADwAEgAAABIAAAAAAAAAAEUTGF2YzYxLjMuMTAwIGxpYngyNjQAAAAAAAAAAAAAAAAY//8AAAA2YXZjQwFkAA3/4QAZZ2QADazZQUH7ARAAAAMAEAAAAwPA8UKZYAEABmjr48siwP34+AAAAAAQcGFzcAAAAAEAAAABAAAAFGJ0cnQAAAAAAALAEAACwBAAAAAYc3R0cwAAAAAAAAABAAAAAQAAAgAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAEAAAABAAAAFHN0c3oAAAAAAAAC7wAAAAEAAAAUc3RjbwAAAAAAAAABAAAARQAAAj10cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAACAAAAAAAAABgAAAAAAAAAAAAAAAEBAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAAAXAAAEAAABAAAAAAG1bWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAACsRAAACABVxAAAAAAALWhkbHIAAAAAAAAAAHNvdW4AAAAAAAAAAAAAAABTb3VuZEhhbmRsZXIAAAABYG1pbmYAAAAQc21oZAAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABJHN0YmwAAAB+c3RzZAAAAAAAAAABAAAAbm1wNGEAAAAAAAAAAQAAAAAAAAAAAAIAEAAAAACsRAAAAAAANmVzZHMAAAAAA4CAgCUAAgAEgICAF0AVAAAAAAH0AAAAEisFgICABRIQVuUABoCAgAECAAAAFGJ0cnQAAAAAAAH0AAAAEisAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAEAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAAVAAAABgAAABhzdGNvAAAAAAAAAAIAAAAwAAADNAAAABpzZ3BkAQAAAHJvbGwAAAACAAAAAf//AAAAHHNiZ3AAAAAAcm9sbAAAAAEAAAACAAAAAQAAAGF1ZHRhAAAAWW1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALGlsc3QAAAAkqXRvbwAAABxkYXRhAAAAAQAAAABMYXZmNjEuMS4xMDA=',
87383    type: 'video/mp4'
87384  };
87385
87386  var States = /*#__PURE__*/function () {
87387    function States() {}
87388    States.getState = function getState(name) {
87389      if (!name) {
87390        return;
87391      }
87392      if (States.states_ && States.states_[name]) {
87393        return States.states_[name];
87394      }
87395    };
87396    States.registerState = function registerState(name, StateToRegister) {
87397      if (typeof name !== 'string' || !name) {
87398        throw new Error("Illegal state name, \"" + name + "\"; must be a non-empty string.");
87399      }
87400      if (!States.states_) {
87401        States.states_ = {};
87402      }
87403      States.states_[name] = StateToRegister;
87404      return StateToRegister;
87405    };
87406    return States;
87407  }();
87408
87409  var State = /*#__PURE__*/function () {
87410    State._getName = function _getName() {
87411      return 'Anonymous State';
87412    };
87413    function State(player) {
87414      this.player = player;
87415    }
87416
87417    /*
87418     * This is the only allowed way to perform state transitions. State transitions usually
87419     * happen in player event handlers. They can also happen recursively in `init`. They
87420     * should _not_ happen in `cleanup`.
87421     */
87422    var _proto = State.prototype;
87423    _proto.transitionTo = function transitionTo(NewState) {
87424      var player = this.player;
87425
87426      // Since State is an abstract class, this will refer to
87427      // the state that is extending this class
87428      this.cleanup(player);
87429      var newState = new NewState(player);
87430      player.ads._state = newState;
87431      player.ads.debug(this.constructor._getName() + ' -> ' + newState.constructor._getName());
87432      for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
87433        args[_key - 1] = arguments[_key];
87434      }
87435      newState.init.apply(newState, [player].concat(args));
87436    }
87437
87438    /*
87439     * Implemented by subclasses to provide initialization logic when transitioning
87440     * to a new state.
87441     */;
87442    _proto.init = function init() {}
87443
87444    /*
87445     * Implemented by subclasses to provide cleanup logic when transitioning
87446     * to a new state.
87447     */;
87448    _proto.cleanup = function cleanup() {}
87449
87450    /*
87451     * Default event handlers. Different states can override these to provide behaviors.
87452     */;
87453    _proto.onPlay = function onPlay() {};
87454    _proto.onPlaying = function onPlaying() {};
87455    _proto.onEnded = function onEnded() {};
87456    _proto.onAdEnded = function onAdEnded() {};
87457    _proto.onAdsReady = function onAdsReady() {
87458      videojs.log.warn('Unexpected adsready event');
87459    };
87460    _proto.onAdsError = function onAdsError() {};
87461    _proto.onAdsCanceled = function onAdsCanceled() {};
87462    _proto.onAdTimeout = function onAdTimeout() {};
87463    _proto.onAdStarted = function onAdStarted() {};
87464    _proto.onAdSkipped = function onAdSkipped() {};
87465    _proto.onContentChanged = function onContentChanged() {};
87466    _proto.onContentResumed = function onContentResumed() {};
87467    _proto.onReadyForPostroll = function onReadyForPostroll() {
87468      videojs.log.warn('Unexpected readyforpostroll event');
87469    };
87470    _proto.onNoPreroll = function onNoPreroll() {};
87471    _proto.onNoPostroll = function onNoPostroll() {}
87472
87473    /*
87474     * Method handlers. Different states can override these to provide behaviors.
87475     */;
87476    _proto.startLinearAdMode = function startLinearAdMode() {
87477      videojs.log.warn('Unexpected startLinearAdMode invocation ' + '(State via ' + this.constructor._getName() + ')');
87478    };
87479    _proto.endLinearAdMode = function endLinearAdMode() {
87480      videojs.log.warn('Unexpected endLinearAdMode invocation ' + '(State via ' + this.constructor._getName() + ')');
87481    };
87482    _proto.skipLinearAdMode = function skipLinearAdMode() {
87483      videojs.log.warn('Unexpected skipLinearAdMode invocation ' + '(State via ' + this.constructor._getName() + ')');
87484    }
87485
87486    /*
87487     * Overridden by ContentState and AdState. Should not be overriden elsewhere.
87488     */;
87489    _proto.isAdState = function isAdState() {
87490      throw new Error('isAdState unimplemented for ' + this.constructor._getName());
87491    }
87492
87493    /*
87494     * Overridden by Preroll and Postroll. Midrolls jump right into the ad break
87495     * so there is no "waiting" state for them.
87496     */;
87497    _proto.isWaitingForAdBreak = function isWaitingForAdBreak() {
87498      return false;
87499    }
87500
87501    /*
87502     * Overridden by Preroll, Midroll, and Postroll.
87503     */;
87504    _proto.isContentResuming = function isContentResuming() {
87505      return false;
87506    };
87507    _proto.inAdBreak = function inAdBreak() {
87508      return false;
87509    }
87510
87511    /*
87512     * Invoke event handler methods when events come in.
87513     */;
87514    _proto.handleEvent = function handleEvent(type) {
87515      var player = this.player;
87516      if (type === 'play') {
87517        this.onPlay(player);
87518      } else if (type === 'adsready') {
87519        this.onAdsReady(player);
87520      } else if (type === 'adserror') {
87521        this.onAdsError(player);
87522      } else if (type === 'adscanceled') {
87523        this.onAdsCanceled(player);
87524      } else if (type === 'adtimeout') {
87525        this.onAdTimeout(player);
87526      } else if (type === 'ads-ad-started') {
87527        this.onAdStarted(player);
87528      } else if (type === 'ads-ad-skipped') {
87529        this.onAdSkipped(player);
87530      } else if (type === 'contentchanged') {
87531        this.onContentChanged(player);
87532      } else if (type === 'contentresumed') {
87533        this.onContentResumed(player);
87534      } else if (type === 'readyforpostroll') {
87535        this.onReadyForPostroll(player);
87536      } else if (type === 'playing') {
87537        this.onPlaying(player);
87538      } else if (type === 'ended') {
87539        this.onEnded(player);
87540      } else if (type === 'nopreroll') {
87541        this.onNoPreroll(player);
87542      } else if (type === 'nopostroll') {
87543        this.onNoPostroll(player);
87544      } else if (type === 'adended') {
87545        this.onAdEnded(player);
87546      }
87547    };
87548    return State;
87549  }();
87550  States.registerState('State', State);
87551
87552  /*
87553   * This class contains logic for all ads, be they prerolls, midrolls, or postrolls.
87554   * Primarily, this involves handling startLinearAdMode and endLinearAdMode.
87555   * It also handles content resuming.
87556   */
87557  var AdState = /*#__PURE__*/function (_State) {
87558    _inheritsLoose(AdState, _State);
87559    function AdState(player) {
87560      var _this;
87561      _this = _State.call(this, player) || this;
87562      _this.contentResuming = false;
87563      _this.waitingForAdBreak = false;
87564      return _this;
87565    }
87566
87567    /*
87568     * Overrides State.isAdState
87569     */
87570    var _proto = AdState.prototype;
87571    _proto.isAdState = function isAdState() {
87572      return true;
87573    }
87574
87575    /*
87576     * We end the content-resuming process on the playing event because this is the exact
87577     * moment that content playback is no longer blocked by ads.
87578     */;
87579    _proto.onPlaying = function onPlaying() {
87580      var ContentPlayback = States.getState('ContentPlayback');
87581      if (this.contentResuming) {
87582        this.transitionTo(ContentPlayback);
87583      }
87584    }
87585
87586    /*
87587     * If the ad plugin does not result in a playing event when resuming content after an
87588     * ad, they should instead trigger a contentresumed event to signal that content should
87589     * resume. The main use case for this is when ads are stitched into the content video.
87590     */;
87591    _proto.onContentResumed = function onContentResumed() {
87592      var ContentPlayback = States.getState('ContentPlayback');
87593      if (this.contentResuming) {
87594        this.transitionTo(ContentPlayback);
87595      }
87596    }
87597
87598    /*
87599     * Check if we are in an ad state waiting for the ad plugin to start
87600     * an ad break.
87601     */;
87602    _proto.isWaitingForAdBreak = function isWaitingForAdBreak() {
87603      return this.waitingForAdBreak;
87604    }
87605
87606    /*
87607     * Allows you to check if content is currently resuming after an ad break.
87608     */;
87609    _proto.isContentResuming = function isContentResuming() {
87610      return this.contentResuming;
87611    }
87612
87613    /*
87614     * Allows you to check if an ad break is in progress.
87615     */;
87616    _proto.inAdBreak = function inAdBreak() {
87617      return this.player.ads._inLinearAdMode === true;
87618    };
87619    return AdState;
87620  }(State);
87621  States.registerState('AdState', AdState);
87622
87623  var ContentState = /*#__PURE__*/function (_State) {
87624    _inheritsLoose(ContentState, _State);
87625    function ContentState() {
87626      return _State.apply(this, arguments) || this;
87627    }
87628    var _proto = ContentState.prototype;
87629    /*
87630     * Overrides State.isAdState
87631     */
87632    _proto.isAdState = function isAdState() {
87633      return false;
87634    }
87635
87636    /*
87637     * Source change sends you back to preroll checks. contentchanged does not
87638     * fire during ad breaks, so we don't need to worry about that.
87639     */;
87640    _proto.onContentChanged = function onContentChanged(player) {
87641      var BeforePreroll = States.getState('BeforePreroll');
87642      var Preroll = States.getState('Preroll');
87643      player.ads.debug('Received contentchanged event (ContentState)');
87644      if (player.paused()) {
87645        this.transitionTo(BeforePreroll);
87646      } else {
87647        this.transitionTo(Preroll, false);
87648        player.pause();
87649        player.ads._pausedOnContentupdate = true;
87650      }
87651    };
87652    return ContentState;
87653  }(State);
87654  States.registerState('ContentState', ContentState);
87655
87656  var ContentState$1 = States.getState('ContentState');
87657  var AdsDone = /*#__PURE__*/function (_ContentState) {
87658    _inheritsLoose(AdsDone, _ContentState);
87659    function AdsDone() {
87660      return _ContentState.apply(this, arguments) || this;
87661    }
87662    /*
87663     * Allows state name to be logged even after minification.
87664     */
87665    AdsDone._getName = function _getName() {
87666      return 'AdsDone';
87667    }
87668
87669    /*
87670     * For state transitions to work correctly, initialization should
87671     * happen here, not in a constructor.
87672     */;
87673    var _proto = AdsDone.prototype;
87674    _proto.init = function init(player) {
87675      // From now on, `ended` events won't be redispatched
87676      player.ads._contentHasEnded = true;
87677      player.trigger('ended');
87678    }
87679
87680    /*
87681     * Midrolls do not play after ads are done.
87682     */;
87683    _proto.startLinearAdMode = function startLinearAdMode() {
87684      videojs.log.warn('Unexpected startLinearAdMode invocation (AdsDone)');
87685    };
87686    return AdsDone;
87687  }(ContentState$1);
87688  States.registerState('AdsDone', AdsDone);
87689
87690  /*
87691  The snapshot feature is responsible for saving the player state before an ad, then
87692  restoring the player state after an ad.
87693  */
87694  var tryToResumeTimeout_;
87695
87696  /*
87697   * Returns an object that captures the portions of player state relevant to
87698   * video playback. The result of this function can be passed to
87699   * restorePlayerSnapshot with a player to return the player to the state it
87700   * was in when this function was invoked.
87701   * @param {Object} player The videojs player object
87702   */
87703  function getPlayerSnapshot(player) {
87704    var currentTime;
87705    if (videojs.browser.IS_IOS && player.ads.isLive(player)) {
87706      // Record how far behind live we are
87707      if (player.seekable().length > 0) {
87708        currentTime = player.currentTime() - player.seekable().end(0);
87709      } else {
87710        currentTime = player.currentTime();
87711      }
87712    } else {
87713      currentTime = player.currentTime();
87714    }
87715    var tech = player.$('.vjs-tech');
87716    var tracks = player.textTracks ? player.textTracks() : [];
87717    var suppressedTracks = [];
87718    var snapshotObject = {
87719      ended: player.ended(),
87720      currentSrc: player.currentSrc(),
87721      sources: player.currentSources(),
87722      src: player.tech_.src(),
87723      currentTime: currentTime,
87724      type: player.currentType()
87725    };
87726    if (tech) {
87727      snapshotObject.style = tech.getAttribute('style');
87728    }
87729    for (var i = 0; i < tracks.length; i++) {
87730      var track = tracks[i];
87731      suppressedTracks.push({
87732        track: track,
87733        mode: track.mode
87734      });
87735      track.mode = 'disabled';
87736    }
87737    snapshotObject.suppressedTracks = suppressedTracks;
87738    return snapshotObject;
87739  }
87740
87741  /*
87742   * Attempts to modify the specified player so that its state is equivalent to
87743   * the state of the snapshot.
87744   * @param {Object} player - the videojs player object
87745   * @param {Object} snapshotObject - the player state to apply
87746   */
87747  function restorePlayerSnapshot(player, callback) {
87748    var snapshotObject = player.ads.snapshot;
87749    if (callback === undefined) {
87750      callback = function callback() {};
87751    }
87752    if (player.ads.disableNextSnapshotRestore === true) {
87753      player.ads.disableNextSnapshotRestore = false;
87754      delete player.ads.snapshot;
87755      callback();
87756      return;
87757    }
87758
87759    // The playback tech
87760    var tech = player.$('.vjs-tech');
87761
87762    // the number of[ remaining attempts to restore the snapshot
87763    var attempts = 20;
87764    var suppressedTracks = snapshotObject.suppressedTracks;
87765    var trackSnapshot;
87766    var restoreTracks = function restoreTracks() {
87767      for (var i = 0; i < suppressedTracks.length; i++) {
87768        trackSnapshot = suppressedTracks[i];
87769        trackSnapshot.track.mode = trackSnapshot.mode;
87770      }
87771    };
87772
87773    // Finish restoring the playback state.
87774    // This only happens if the content video element was reused for ad playback.
87775    var resume = function resume() {
87776      var currentTime;
87777
87778      // Live video on iOS has special logic to try to seek to the right place after
87779      // an ad.
87780      if (videojs.browser.IS_IOS && player.ads.isLive(player)) {
87781        if (snapshotObject.currentTime < 0) {
87782          // Playback was behind real time, so seek backwards to match
87783          if (player.seekable().length > 0) {
87784            currentTime = player.seekable().end(0) + snapshotObject.currentTime;
87785          } else {
87786            currentTime = player.currentTime();
87787          }
87788          player.currentTime(currentTime);
87789        }
87790
87791        // iOS live play after restore if player was paused (would not be paused if
87792        // ad played muted behind ad)
87793        if (player.paused()) {
87794          var playPromise = player.play();
87795          if (playPromise && playPromise.catch) {
87796            playPromise.catch(function (error) {
87797              videojs.log.warn('Play promise rejected in IOS snapshot resume', error);
87798            });
87799          }
87800        }
87801
87802        // Restore the video position after an ad.
87803        // We check snapshotObject.ended because the content starts at the beginning again
87804        // after being restored.
87805      } else if (snapshotObject.ended) {
87806        // For postrolls, seek to the player's current duration.
87807        // It could be different from the snapshot's currentTime due to
87808        // inaccuracy in HLS.
87809        player.currentTime(player.duration());
87810      } else {
87811        // Prerolls and midrolls, just seek to the player time before the ad.
87812        player.currentTime(snapshotObject.currentTime);
87813        var _playPromise = player.play();
87814        if (_playPromise && _playPromise.catch) {
87815          _playPromise.catch(function (error) {
87816            videojs.log.warn('Play promise rejected in snapshot resume', error);
87817          });
87818        }
87819      }
87820
87821      // if we added autoplay to force content loading on iOS, remove it now
87822      // that it has served its purpose
87823      if (player.ads.shouldRemoveAutoplay_) {
87824        player.autoplay(false);
87825        player.ads.shouldRemoveAutoplay_ = false;
87826      }
87827    };
87828
87829    // Determine if the video element has loaded enough of the snapshot source
87830    // to be ready to apply the rest of the state.
87831    // This only happens if the content video element was reused for ad playback.
87832    var tryToResume = function tryToResume() {
87833      // tryToResume can either have been called through the `contentcanplay`
87834      // event or fired through setTimeout.
87835      // When tryToResume is called, we should make sure to clear out the other
87836      // way it could've been called by removing the listener and clearing out
87837      // the timeout.
87838      player.off('contentcanplay', tryToResume);
87839      if (tryToResumeTimeout_) {
87840        player.clearTimeout(tryToResumeTimeout_);
87841      }
87842
87843      // Tech may have changed depending on the differences in sources of the
87844      // original video and that of the ad
87845      tech = player.el().querySelector('.vjs-tech');
87846      if (tech.readyState > 1) {
87847        // some browsers and media aren't "seekable".
87848        // readyState greater than 1 allows for seeking without exceptions
87849        return resume();
87850      }
87851      if (tech.seekable === undefined) {
87852        // if the tech doesn't expose the seekable time ranges, try to
87853        // resume playback immediately
87854        return resume();
87855      }
87856      if (tech.seekable.length > 0) {
87857        // if some period of the video is seekable, resume playback
87858        return resume();
87859      }
87860
87861      // delay a bit and then check again unless we're out of attempts
87862      if (attempts--) {
87863        player.setTimeout(tryToResume, 50);
87864      } else {
87865        try {
87866          resume();
87867        } catch (e) {
87868          player.ads.error({
87869            errorType: Error$1.AdsResumeContentFailed,
87870            error: e
87871          });
87872          videojs.log.warn('Failed to resume the content after an advertisement', e);
87873        }
87874      }
87875    };
87876    if ('style' in snapshotObject) {
87877      // overwrite all css style properties to restore state precisely
87878      tech.setAttribute('style', snapshotObject.style || '');
87879    }
87880
87881    // Determine whether the player needs to be restored to its state
87882    // before ad playback began. With a custom ad display or burned-in
87883    // ads, the content player state hasn't been modified and so no
87884    // restoration is required
87885
87886    if (player.ads.videoElementRecycled()) {
87887      // Snapshot restore is done, so now we're really finished.
87888      player.one('resumeended', function () {
87889        delete player.ads.snapshot;
87890        callback();
87891      });
87892
87893      // on ios7, fiddling with textTracks too early will cause safari to crash
87894      player.one('contentloadedmetadata', restoreTracks);
87895
87896      // adding autoplay guarantees that Safari will load the content so we can
87897      // seek back to the correct time after ads.
87898      // This is done directly on the html5 tech because if the integration has set
87899      // normalizeAutoplay to true, the async play request via autoplay -> manualAutoplay_
87900      // causes a visible skipback of the content after the ad break
87901      if (videojs.browser.IS_IOS && !player.autoplay() && typeof player.techCall_ === 'function') {
87902        player.techCall_('setAutoplay', true);
87903
87904        // if we get here, the player was not originally configured to autoplay,
87905        // so we should remove it after it has served its purpose
87906        player.ads.shouldRemoveAutoplay_ = true;
87907      }
87908
87909      // if the src changed for ad playback, reset it
87910      player.src(snapshotObject.sources);
87911
87912      // and then resume from the snapshots time once the original src has loaded
87913      // in some browsers (firefox) `canplay` may not fire correctly.
87914      // Reace the `canplay` event with a timeout.
87915      player.one('contentcanplay', tryToResume);
87916      tryToResumeTimeout_ = player.setTimeout(tryToResume, 2000);
87917    } else {
87918      // if we didn't change the src, just restore the tracks
87919      restoreTracks();
87920
87921      // we don't need to check snapshotObject.ended here because the content video
87922      // element wasn't recycled
87923      if (!player.ended()) {
87924        // the src didn't change and this wasn't a postroll
87925        // just resume playback at the current time.
87926        var playPromise = player.play();
87927        if (playPromise && playPromise.catch) {
87928          playPromise.catch(function (error) {
87929            videojs.log.warn('Play promise rejected in snapshot restore', error);
87930          });
87931        }
87932      }
87933
87934      // snapshot restore is complete
87935      delete player.ads.snapshot;
87936      callback();
87937    }
87938  }
87939
87940  /*
87941   * Encapsulates logic for starting and ending ad breaks. An ad break
87942   * is the time between startLinearAdMode and endLinearAdMode. The ad
87943   * plugin may play 0 or more ads during this time.
87944   */
87945  function start(player) {
87946    player.ads.debug('Starting ad break');
87947    player.ads._inLinearAdMode = true;
87948
87949    // No longer does anything, used to move us to ad-playback
87950    player.trigger('adstart');
87951
87952    // Capture current player state snapshot
87953    if (player.ads.shouldTakeSnapshots()) {
87954      player.ads.snapshot = getPlayerSnapshot(player);
87955    }
87956
87957    // Mute the player behind the ad
87958    if (player.ads.shouldPlayContentBehindAd(player) && !player.ads.settings.stitchedAds) {
87959      player.ads.preAdVolume_ = player.volume();
87960      player.volume(0);
87961    }
87962
87963    // Add css to the element to indicate and ad is playing.
87964    player.addClass('vjs-ad-playing');
87965
87966    // We should remove the vjs-live class if it has been added in order to
87967    // show the adprogress control bar on Android devices for falsely
87968    // determined LIVE videos due to the duration incorrectly reported as Infinity
87969    if (player.hasClass('vjs-live')) {
87970      player.removeClass('vjs-live');
87971    }
87972
87973    // This removes the native poster so the ads don't show the content
87974    // poster if content element is reused for ad playback.
87975    player.ads.removeNativePoster();
87976
87977    // Ensure ads are watched at x1 speed and speed cannot be changed for the duration of the ad
87978    player.ads.preAdPlaybackRate_ = player.playbackRate();
87979    player.playbackRate(1);
87980    if (player.controlBar && player.controlBar.playbackRateMenuButton && player.controlBar.playbackRateMenuButton.playbackRateSupported && !player.controlBar.playbackRateMenuButton.hasClass('vjs-hidden')) {
87981      player.controlBar.playbackRateMenuButton.hide();
87982      player.ads.showPlaybackMenuOnAdEnd_ = true;
87983    } else {
87984      player.ads.showPlaybackMenuOnAdEnd_ = false;
87985    }
87986  }
87987  function end(player, callback) {
87988    player.ads.debug('Ending ad break');
87989    if (callback === undefined) {
87990      callback = function callback() {};
87991    }
87992    player.ads.adType = null;
87993    player.ads._inLinearAdMode = false;
87994
87995    // Signals the end of the ad break to anyone listening.
87996    player.trigger('adend');
87997    player.removeClass('vjs-ad-playing');
87998
87999    // We should add the vjs-live class back if the video is a LIVE video
88000    // If we dont do this, then for a LIVE Video, we will get an incorrect
88001    // styled control, which displays the time for the video
88002    if (player.ads.isLive(player)) {
88003      player.addClass('vjs-live');
88004    }
88005
88006    // Restore snapshot
88007    if (player.ads.shouldTakeSnapshots()) {
88008      restorePlayerSnapshot(player, callback);
88009
88010      // Reset the volume to pre-ad levels
88011    } else {
88012      if (player.ads.preAdVolume_) {
88013        player.volume(player.ads.preAdVolume_);
88014      }
88015      callback();
88016    }
88017
88018    // Reset playback
88019    player.playbackRate(player.ads.preAdPlaybackRate_);
88020    if (player.ads.showPlaybackMenuOnAdEnd_) {
88021      player.controlBar.playbackRateMenuButton.show();
88022    }
88023  }
88024  var obj$1 = {
88025    start: start,
88026    end: end
88027  };
88028
88029  var AdState$1 = States.getState('AdState');
88030
88031  /*
88032   * This state encapsulates waiting for prerolls, preroll playback, and
88033   * content restoration after a preroll.
88034   */
88035  var Preroll = /*#__PURE__*/function (_AdState) {
88036    _inheritsLoose(Preroll, _AdState);
88037    function Preroll() {
88038      return _AdState.apply(this, arguments) || this;
88039    }
88040    /*
88041     * Allows state name to be logged even after minification.
88042     */
88043    Preroll._getName = function _getName() {
88044      return 'Preroll';
88045    }
88046
88047    /*
88048     * For state transitions to work correctly, initialization should
88049     * happen here, not in a constructor.
88050     */;
88051    var _proto = Preroll.prototype;
88052    _proto.init = function init(player, adsReady, shouldResumeToContent) {
88053      this.waitingForAdBreak = true;
88054
88055      // Loading spinner from now until ad start or end of ad break.
88056      player.addClass('vjs-ad-loading');
88057
88058      // If adserror, adscanceled, nopreroll or skipLinearAdMode already
88059      // ocurred, resume to content immediately
88060      if (shouldResumeToContent || player.ads.nopreroll_) {
88061        return this.resumeAfterNoPreroll(player);
88062      }
88063
88064      // Determine preroll timeout based on plugin settings
88065      var timeout = player.ads.settings.timeout;
88066      if (typeof player.ads.settings.prerollTimeout === 'number') {
88067        timeout = player.ads.settings.prerollTimeout;
88068      }
88069
88070      // Start the clock ticking for ad timeout
88071      this._timeout = player.setTimeout(function () {
88072        player.trigger('adtimeout');
88073      }, timeout);
88074
88075      // If adsready already happened, lets get started. Otherwise,
88076      // wait until onAdsReady.
88077      if (adsReady) {
88078        this.handleAdsReady();
88079      } else {
88080        this.adsReady = false;
88081      }
88082    }
88083
88084    /*
88085     * Adsready event after play event.
88086     */;
88087    _proto.onAdsReady = function onAdsReady(player) {
88088      if (!player.ads.inAdBreak()) {
88089        player.ads.debug('Received adsready event (Preroll)');
88090        this.handleAdsReady();
88091      } else {
88092        videojs.log.warn('Unexpected adsready event (Preroll)');
88093      }
88094    }
88095
88096    /*
88097     * Ad plugin is ready. Let's get started on this preroll.
88098     */;
88099    _proto.handleAdsReady = function handleAdsReady() {
88100      this.adsReady = true;
88101      this.readyForPreroll();
88102    }
88103
88104    /*
88105     * Helper to call a callback only after a loadstart event.
88106     * If we start content or ads before loadstart, loadstart
88107     * will not be prefixed correctly.
88108     */;
88109    _proto.afterLoadStart = function afterLoadStart(callback) {
88110      var player = this.player;
88111      if (player.ads._hasThereBeenALoadStartDuringPlayerLife) {
88112        callback();
88113      } else {
88114        player.ads.debug('Waiting for loadstart...');
88115        player.one('loadstart', function () {
88116          player.ads.debug('Received loadstart event');
88117          callback();
88118        });
88119      }
88120    }
88121
88122    /*
88123     * If there is no preroll, play content instead.
88124     */;
88125    _proto.noPreroll = function noPreroll() {
88126      var _this = this;
88127      this.afterLoadStart(function () {
88128        _this.player.ads.debug('Skipping prerolls due to nopreroll event (Preroll)');
88129        _this.resumeAfterNoPreroll(_this.player);
88130      });
88131    }
88132
88133    /*
88134     * Fire the readyforpreroll event. If loadstart hasn't happened yet,
88135     * wait until loadstart first.
88136     */;
88137    _proto.readyForPreroll = function readyForPreroll() {
88138      var player = this.player;
88139      this.afterLoadStart(function () {
88140        player.ads.debug('Triggered readyforpreroll event (Preroll)');
88141        player.trigger('readyforpreroll');
88142      });
88143    }
88144
88145    /*
88146     * adscanceled cancels all ads for the source. Play content now.
88147     */;
88148    _proto.onAdsCanceled = function onAdsCanceled(player) {
88149      var _this2 = this;
88150      player.ads.debug('adscanceled (Preroll)');
88151      this.afterLoadStart(function () {
88152        _this2.resumeAfterNoPreroll(player);
88153      });
88154    }
88155
88156    /*
88157     * An ad error occured. Play content instead.
88158     */;
88159    _proto.onAdsError = function onAdsError(player) {
88160      var _this3 = this;
88161      videojs.log('adserror (Preroll)');
88162      player.ads.error({
88163        errorType: Error$1.AdsPrerollError
88164      });
88165
88166      // In the future, we may not want to do this automatically.
88167      // Ad plugins should be able to choose to continue the ad break
88168      // if there was an error.
88169      if (this.inAdBreak()) {
88170        player.ads.endLinearAdMode();
88171      } else {
88172        this.afterLoadStart(function () {
88173          _this3.resumeAfterNoPreroll(player);
88174        });
88175      }
88176    }
88177    /*
88178     * Ad plugin invoked startLinearAdMode, the ad break starts now.
88179     */;
88180    _proto.startLinearAdMode = function startLinearAdMode() {
88181      var player = this.player;
88182      if (this.adsReady && !player.ads.inAdBreak() && !this.isContentResuming()) {
88183        this.clearTimeout(player);
88184        player.ads.adType = 'preroll';
88185        this.waitingForAdBreak = false;
88186        obj$1.start(player);
88187
88188        // We don't need to block play calls anymore
88189        player.ads._shouldBlockPlay = false;
88190      } else {
88191        videojs.log.warn('Unexpected startLinearAdMode invocation (Preroll)');
88192      }
88193    }
88194
88195    /*
88196     * An ad has actually started playing.
88197     * Remove the loading spinner.
88198     */;
88199    _proto.onAdStarted = function onAdStarted(player) {
88200      player.removeClass('vjs-ad-loading');
88201    }
88202
88203    /*
88204     * Ad plugin invoked endLinearAdMode, the ad break ends now.
88205     */;
88206    _proto.endLinearAdMode = function endLinearAdMode() {
88207      var player = this.player;
88208      if (this.inAdBreak()) {
88209        player.removeClass('vjs-ad-loading');
88210        player.addClass('vjs-ad-content-resuming');
88211        this.contentResuming = true;
88212        obj$1.end(player);
88213      }
88214    }
88215
88216    /*
88217     * Ad skipped by ad plugin. Play content instead.
88218     */;
88219    _proto.skipLinearAdMode = function skipLinearAdMode() {
88220      var _this4 = this;
88221      var player = this.player;
88222      if (player.ads.inAdBreak() || this.isContentResuming()) {
88223        videojs.log.warn('Unexpected skipLinearAdMode invocation');
88224      } else {
88225        this.afterLoadStart(function () {
88226          player.trigger('adskip');
88227          player.ads.debug('skipLinearAdMode (Preroll)');
88228          _this4.resumeAfterNoPreroll(player);
88229        });
88230      }
88231    }
88232
88233    /*
88234     * Prerolls took too long! Play content instead.
88235     */;
88236    _proto.onAdTimeout = function onAdTimeout(player) {
88237      var _this5 = this;
88238      this.afterLoadStart(function () {
88239        player.ads.debug('adtimeout (Preroll)');
88240        _this5.resumeAfterNoPreroll(player);
88241      });
88242    }
88243
88244    /*
88245     * Check if nopreroll event was too late before handling it.
88246     */;
88247    _proto.onNoPreroll = function onNoPreroll(player) {
88248      if (player.ads.inAdBreak() || this.isContentResuming()) {
88249        videojs.log.warn('Unexpected nopreroll event (Preroll)');
88250      } else {
88251        this.noPreroll();
88252      }
88253    };
88254    _proto.resumeAfterNoPreroll = function resumeAfterNoPreroll(player) {
88255      // Resume to content and unblock play as there is no preroll ad
88256      this.contentResuming = true;
88257      player.ads._shouldBlockPlay = false;
88258      this.cleanupPartial(player);
88259
88260      // Play the content if we had requested play or we paused on 'contentupdate'
88261      // and we haven't played yet. This happens if there was no preroll or if it
88262      // errored, timed out, etc. Otherwise snapshot restore would play.
88263      if (player.ads._playRequested || player.ads._pausedOnContentupdate) {
88264        if (player.paused()) {
88265          player.ads.debug('resumeAfterNoPreroll: attempting to resume playback (Preroll)');
88266          var playPromise = player.play();
88267          if (playPromise && playPromise.then) {
88268            playPromise.then(null, function (e) {});
88269          }
88270        } else {
88271          player.ads.debug('resumeAfterNoPreroll: already playing (Preroll)');
88272          player.trigger('play');
88273          player.trigger('playing');
88274        }
88275      }
88276    }
88277
88278    /*
88279     * Cleanup timeouts and spinner.
88280     */;
88281    _proto.cleanup = function cleanup(player) {
88282      if (!player.ads._hasThereBeenALoadStartDuringPlayerLife) {
88283        videojs.log.warn('Leaving Preroll state before loadstart event can cause issues.');
88284      }
88285      this.cleanupPartial(player);
88286    }
88287
88288    /*
88289     * Performs cleanup tasks without depending on a state transition. This is
88290     * used mainly in cases where a preroll failed.
88291     */;
88292    _proto.cleanupPartial = function cleanupPartial(player) {
88293      player.removeClass('vjs-ad-loading');
88294      player.removeClass('vjs-ad-content-resuming');
88295      this.clearTimeout(player);
88296    }
88297
88298    /*
88299     * Clear the preroll timeout and nulls out the pointer.
88300     */;
88301    _proto.clearTimeout = function clearTimeout(player) {
88302      player.clearTimeout(this._timeout);
88303      this._timeout = null;
88304    };
88305    return Preroll;
88306  }(AdState$1);
88307  States.registerState('Preroll', Preroll);
88308
88309  var ContentState$2 = States.getState('ContentState');
88310
88311  /*
88312   * This is the initial state for a player with an ad plugin. Normally, it remains in this
88313   * state until a "play" event is seen. After that, we enter the Preroll state to check for
88314   * prerolls. This happens regardless of whether or not any prerolls ultimately will play.
88315   * Errors and other conditions may lead us directly from here to ContentPlayback.
88316   */
88317  var BeforePreroll = /*#__PURE__*/function (_ContentState) {
88318    _inheritsLoose(BeforePreroll, _ContentState);
88319    function BeforePreroll() {
88320      return _ContentState.apply(this, arguments) || this;
88321    }
88322    /*
88323     * Allows state name to be logged even after minification.
88324     */
88325    BeforePreroll._getName = function _getName() {
88326      return 'BeforePreroll';
88327    }
88328
88329    /*
88330     * For state transitions to work correctly, initialization should
88331     * happen here, not in a constructor.
88332     */;
88333    var _proto = BeforePreroll.prototype;
88334    _proto.init = function init(player) {
88335      this.adsReady = false;
88336      this.shouldResumeToContent = false;
88337
88338      // Content playback should be blocked by callPlay() middleware if the allowVjsAutoplay
88339      // option hasn't been provided and autoplay is not desired.
88340      player.ads._shouldBlockPlay = player.ads.settings.allowVjsAutoplay ? !player.autoplay() : true;
88341    }
88342
88343    /*
88344     * The ad plugin may trigger adsready before the play request. If so,
88345     * we record that adsready already happened so the Preroll state will know.
88346     */;
88347    _proto.onAdsReady = function onAdsReady(player) {
88348      player.ads.debug('Received adsready event (BeforePreroll)');
88349      this.adsReady = true;
88350    }
88351
88352    /*
88353     * Ad mode officially begins on the play request, because at this point
88354     * content playback is blocked by the ad plugin.
88355     */;
88356    _proto.onPlay = function onPlay(player) {
88357      var Preroll = States.getState('Preroll');
88358      player.ads.debug('Received play event (BeforePreroll)');
88359
88360      // Check for prerolls
88361      this.transitionTo(Preroll, this.adsReady, this.shouldResumeToContent);
88362    }
88363
88364    /*
88365     * All ads for the entire video are canceled.
88366     */;
88367    _proto.onAdsCanceled = function onAdsCanceled(player) {
88368      player.ads.debug('adscanceled (BeforePreroll)');
88369      this.shouldResumeToContent = true;
88370    }
88371
88372    /*
88373     * An ad error occured. Play content instead.
88374     */;
88375    _proto.onAdsError = function onAdsError() {
88376      this.player.ads.debug('adserror (BeforePreroll)');
88377      this.player.ads.error({
88378        errorType: Error$1.AdsBeforePrerollError
88379      });
88380      this.shouldResumeToContent = true;
88381    }
88382
88383    /*
88384     * If there is no preroll, don't wait for a play event to move forward.
88385     */;
88386    _proto.onNoPreroll = function onNoPreroll() {
88387      this.player.ads.debug('Skipping prerolls due to nopreroll event (BeforePreroll)');
88388      this.shouldResumeToContent = true;
88389    }
88390
88391    /*
88392     * Prerolls skipped by ad plugin. Play content instead.
88393     */;
88394    _proto.skipLinearAdMode = function skipLinearAdMode() {
88395      var player = this.player;
88396      player.trigger('adskip');
88397      player.ads.debug('skipLinearAdMode (BeforePreroll)');
88398      this.shouldResumeToContent = true;
88399    };
88400    _proto.onContentChanged = function onContentChanged() {
88401      this.init(this.player);
88402    };
88403    return BeforePreroll;
88404  }(ContentState$2);
88405  States.registerState('BeforePreroll', BeforePreroll);
88406
88407  var AdState$2 = States.getState('AdState');
88408  var Midroll = /*#__PURE__*/function (_AdState) {
88409    _inheritsLoose(Midroll, _AdState);
88410    function Midroll() {
88411      return _AdState.apply(this, arguments) || this;
88412    }
88413    /*
88414     * Allows state name to be logged even after minification.
88415     */
88416    Midroll._getName = function _getName() {
88417      return 'Midroll';
88418    }
88419
88420    /*
88421     * Midroll breaks happen when the ad plugin calls startLinearAdMode,
88422     * which can happen at any time during content playback.
88423     */;
88424    var _proto = Midroll.prototype;
88425    _proto.init = function init(player) {
88426      player.ads.adType = 'midroll';
88427      obj$1.start(player);
88428      player.addClass('vjs-ad-loading');
88429    }
88430
88431    /*
88432     * An ad has actually started playing.
88433     * Remove the loading spinner.
88434     */;
88435    _proto.onAdStarted = function onAdStarted(player) {
88436      player.removeClass('vjs-ad-loading');
88437    }
88438
88439    /*
88440     * Midroll break is done.
88441     */;
88442    _proto.endLinearAdMode = function endLinearAdMode() {
88443      var player = this.player;
88444      if (this.inAdBreak()) {
88445        this.contentResuming = true;
88446        player.addClass('vjs-ad-content-resuming');
88447        player.removeClass('vjs-ad-loading');
88448        obj$1.end(player);
88449      }
88450    }
88451
88452    /*
88453     * End midroll break if there is an error.
88454     */;
88455    _proto.onAdsError = function onAdsError(player) {
88456      player.ads.error({
88457        errorType: Error$1.AdsMidrollError
88458      });
88459
88460      // In the future, we may not want to do this automatically.
88461      // Ad plugins should be able to choose to continue the ad break
88462      // if there was an error.
88463      if (this.inAdBreak()) {
88464        player.ads.endLinearAdMode();
88465      }
88466    }
88467
88468    /*
88469     * Cleanup CSS classes.
88470     */;
88471    _proto.cleanup = function cleanup(player) {
88472      player.removeClass('vjs-ad-loading');
88473      player.removeClass('vjs-ad-content-resuming');
88474    };
88475    return Midroll;
88476  }(AdState$2);
88477  States.registerState('Midroll', Midroll);
88478
88479  var AdState$3 = States.getState('AdState');
88480  var Postroll = /*#__PURE__*/function (_AdState) {
88481    _inheritsLoose(Postroll, _AdState);
88482    function Postroll() {
88483      return _AdState.apply(this, arguments) || this;
88484    }
88485    /*
88486     * Allows state name to be logged even after minification.
88487     */
88488    Postroll._getName = function _getName() {
88489      return 'Postroll';
88490    }
88491
88492    /*
88493     * For state transitions to work correctly, initialization should
88494     * happen here, not in a constructor.
88495     */;
88496    var _proto = Postroll.prototype;
88497    _proto.init = function init(player) {
88498      this.waitingForAdBreak = true;
88499
88500      // Legacy name that now simply means "handling postrolls".
88501      player.ads._contentEnding = true;
88502
88503      // Start postroll process.
88504      if (!player.ads.nopostroll_) {
88505        player.addClass('vjs-ad-loading');
88506
88507        // Determine postroll timeout based on plugin settings
88508        var timeout = player.ads.settings.timeout;
88509        if (typeof player.ads.settings.postrollTimeout === 'number') {
88510          timeout = player.ads.settings.postrollTimeout;
88511        }
88512        this._postrollTimeout = player.setTimeout(function () {
88513          player.trigger('adtimeout');
88514        }, timeout);
88515
88516        // No postroll, ads are done
88517      } else {
88518        this.resumeContent(player);
88519        var AdsDone = States.getState('AdsDone');
88520        this.transitionTo(AdsDone);
88521      }
88522    }
88523
88524    /*
88525     * Start the postroll if it's not too late.
88526     */;
88527    _proto.startLinearAdMode = function startLinearAdMode() {
88528      var player = this.player;
88529      if (!player.ads.inAdBreak() && !this.isContentResuming()) {
88530        player.ads.adType = 'postroll';
88531        player.clearTimeout(this._postrollTimeout);
88532        this.waitingForAdBreak = false;
88533        obj$1.start(player);
88534      } else {
88535        videojs.log.warn('Unexpected startLinearAdMode invocation (Postroll)');
88536      }
88537    }
88538
88539    /*
88540     * An ad has actually started playing.
88541     * Remove the loading spinner.
88542     */;
88543    _proto.onAdStarted = function onAdStarted(player) {
88544      player.removeClass('vjs-ad-loading');
88545    }
88546
88547    /*
88548     * Ending a postroll triggers the ended event.
88549     */;
88550    _proto.endLinearAdMode = function endLinearAdMode() {
88551      var _this = this;
88552      var player = this.player;
88553      var AdsDone = States.getState('AdsDone');
88554      if (this.inAdBreak()) {
88555        player.removeClass('vjs-ad-loading');
88556        this.resumeContent(player);
88557        obj$1.end(player, function () {
88558          _this.transitionTo(AdsDone);
88559        });
88560      }
88561    }
88562
88563    /*
88564     * Postroll skipped, time to clean up.
88565     */;
88566    _proto.skipLinearAdMode = function skipLinearAdMode() {
88567      var player = this.player;
88568      if (player.ads.inAdBreak() || this.isContentResuming()) {
88569        videojs.log.warn('Unexpected skipLinearAdMode invocation');
88570      } else {
88571        player.ads.debug('Postroll abort (skipLinearAdMode)');
88572        player.trigger('adskip');
88573        this.abort(player);
88574      }
88575    }
88576
88577    /*
88578     * Postroll timed out, time to clean up.
88579     */;
88580    _proto.onAdTimeout = function onAdTimeout(player) {
88581      player.ads.debug('Postroll abort (adtimeout)');
88582      this.abort(player);
88583    }
88584
88585    /*
88586     * Postroll errored out, time to clean up.
88587     */;
88588    _proto.onAdsError = function onAdsError(player) {
88589      player.ads.debug('Postroll abort (adserror)');
88590      player.ads.error({
88591        errorType: Error$1.AdsPostrollError
88592      });
88593
88594      // In the future, we may not want to do this automatically.
88595      // Ad plugins should be able to choose to continue the ad break
88596      // if there was an error.
88597      if (player.ads.inAdBreak()) {
88598        player.ads.endLinearAdMode();
88599      } else {
88600        this.abort(player);
88601      }
88602    }
88603
88604    /*
88605     * Handle content change if we're not in an ad break.
88606     */;
88607    _proto.onContentChanged = function onContentChanged(player) {
88608      // Content resuming after Postroll. Content is paused
88609      // at this point, since it is done playing.
88610      if (this.isContentResuming()) {
88611        var BeforePreroll = States.getState('BeforePreroll');
88612        this.transitionTo(BeforePreroll);
88613
88614        // Waiting for postroll to start. Content is considered playing
88615        // at this point, since it had to be playing to start the postroll.
88616      } else if (!this.inAdBreak()) {
88617        var Preroll = States.getState('Preroll');
88618        this.transitionTo(Preroll);
88619      }
88620    }
88621
88622    /*
88623     * Wrap up if there is no postroll.
88624     */;
88625    _proto.onNoPostroll = function onNoPostroll(player) {
88626      if (!this.isContentResuming() && !this.inAdBreak()) {
88627        this.abort(player);
88628      } else {
88629        videojs.log.warn('Unexpected nopostroll event (Postroll)');
88630      }
88631    };
88632    _proto.resumeContent = function resumeContent(player) {
88633      this.contentResuming = true;
88634      player.addClass('vjs-ad-content-resuming');
88635    }
88636
88637    /*
88638     * Helper for ending Postrolls. In the future we may want to
88639     * refactor this class so that `cleanup` handles all of this.
88640     */;
88641    _proto.abort = function abort(player) {
88642      var AdsDone = States.getState('AdsDone');
88643      this.resumeContent(player);
88644      player.removeClass('vjs-ad-loading');
88645      this.transitionTo(AdsDone);
88646    }
88647
88648    /*
88649     * Cleanup timeouts and state.
88650     */;
88651    _proto.cleanup = function cleanup(player) {
88652      player.removeClass('vjs-ad-content-resuming');
88653      player.clearTimeout(this._postrollTimeout);
88654      player.ads._contentEnding = false;
88655    };
88656    return Postroll;
88657  }(AdState$3);
88658  States.registerState('Postroll', Postroll);
88659
88660  var ContentState$3 = States.getState('ContentState');
88661
88662  /*
88663   * This state represents content playback the first time through before
88664   * content ends. After content has ended once, we check for postrolls and
88665   * move on to the AdsDone state rather than returning here.
88666   */
88667  var ContentPlayback = /*#__PURE__*/function (_ContentState) {
88668    _inheritsLoose(ContentPlayback, _ContentState);
88669    function ContentPlayback() {
88670      return _ContentState.apply(this, arguments) || this;
88671    }
88672    /*
88673     * Allows state name to be logged even after minification.
88674     */
88675    ContentPlayback._getName = function _getName() {
88676      return 'ContentPlayback';
88677    }
88678
88679    /*
88680     * For state transitions to work correctly, initialization should
88681     * happen here, not in a constructor.
88682     */;
88683    var _proto = ContentPlayback.prototype;
88684    _proto.init = function init(player) {
88685      // Don't block calls to play in content playback
88686      player.ads._shouldBlockPlay = false;
88687    }
88688
88689    /*
88690     * In the case of a timeout, adsready might come in late. This assumes the behavior
88691     * that if an ad times out, it could still interrupt the content and start playing.
88692     * An ad plugin could behave otherwise by ignoring this event.
88693     */;
88694    _proto.onAdsReady = function onAdsReady(player) {
88695      player.ads.debug('Received adsready event (ContentPlayback)');
88696      if (!player.ads.nopreroll_) {
88697        player.ads.debug('Triggered readyforpreroll event (ContentPlayback)');
88698        player.trigger('readyforpreroll');
88699      }
88700    }
88701
88702    /*
88703     * Content ended before postroll checks.
88704     */;
88705    _proto.onReadyForPostroll = function onReadyForPostroll(player) {
88706      var Postroll = States.getState('Postroll');
88707      player.ads.debug('Received readyforpostroll event');
88708      this.transitionTo(Postroll);
88709    }
88710
88711    /*
88712     * This is how midrolls start.
88713     */;
88714    _proto.startLinearAdMode = function startLinearAdMode() {
88715      var Midroll = States.getState('Midroll');
88716      this.transitionTo(Midroll);
88717    };
88718    return ContentPlayback;
88719  }(ContentState$3);
88720  States.registerState('ContentPlayback', ContentPlayback);
88721
88722  var ContentState$4 = States.getState('ContentState');
88723
88724  /*
88725   * This state represents content playback when stitched ads are in play.
88726   */
88727  var StitchedContentPlayback = /*#__PURE__*/function (_ContentState) {
88728    _inheritsLoose(StitchedContentPlayback, _ContentState);
88729    function StitchedContentPlayback() {
88730      return _ContentState.apply(this, arguments) || this;
88731    }
88732    /*
88733     * Allows state name to be logged even after minification.
88734     */
88735    StitchedContentPlayback._getName = function _getName() {
88736      return 'StitchedContentPlayback';
88737    }
88738
88739    /*
88740     * For state transitions to work correctly, initialization should
88741     * happen here, not in a constructor.
88742     */;
88743    var _proto = StitchedContentPlayback.prototype;
88744    _proto.init = function init() {
88745      // Don't block calls to play in stitched ad players, ever.
88746      this.player.ads._shouldBlockPlay = false;
88747    }
88748
88749    /*
88750     * Source change does not do anything for stitched ad players.
88751     * contentchanged does not fire during ad breaks, so we don't need to
88752     * worry about that.
88753     */;
88754    _proto.onContentChanged = function onContentChanged() {
88755      this.player.ads.debug("Received contentchanged event (" + this.constructor._getName() + ")");
88756    }
88757
88758    /*
88759     * This is how stitched ads start.
88760     */;
88761    _proto.startLinearAdMode = function startLinearAdMode() {
88762      var StitchedAdRoll = States.getState('StitchedAdRoll');
88763      this.transitionTo(StitchedAdRoll);
88764    };
88765    return StitchedContentPlayback;
88766  }(ContentState$4);
88767  States.registerState('StitchedContentPlayback', StitchedContentPlayback);
88768
88769  var AdState$4 = States.getState('AdState');
88770  var StitchedAdRoll = /*#__PURE__*/function (_AdState) {
88771    _inheritsLoose(StitchedAdRoll, _AdState);
88772    function StitchedAdRoll() {
88773      return _AdState.apply(this, arguments) || this;
88774    }
88775    /*
88776     * Allows state name to be logged even after minification.
88777     */
88778    StitchedAdRoll._getName = function _getName() {
88779      return 'StitchedAdRoll';
88780    }
88781
88782    /*
88783     * StitchedAdRoll breaks happen when the ad plugin calls startLinearAdMode,
88784     * which can happen at any time during content playback.
88785     */;
88786    var _proto = StitchedAdRoll.prototype;
88787    _proto.init = function init() {
88788      this.waitingForAdBreak = false;
88789      this.contentResuming = false;
88790      this.player.ads.adType = 'stitched';
88791      obj$1.start(this.player);
88792    }
88793
88794    /*
88795     * For stitched ads, there is no "content resuming" scenario, so a "playing"
88796     * event is not relevant.
88797     */;
88798    _proto.onPlaying = function onPlaying() {}
88799
88800    /*
88801     * For stitched ads, there is no "content resuming" scenario, so a
88802     * "contentresumed" event is not relevant.
88803     */;
88804    _proto.onContentResumed = function onContentResumed() {}
88805
88806    /*
88807     * When we see an "adended" event, it means that we are in a postroll that
88808     * has ended (because the media ended and we are still in an ad state).
88809     *
88810     * In these cases, we transition back to content mode and fire ended.
88811     */;
88812    _proto.onAdEnded = function onAdEnded() {
88813      this.endLinearAdMode();
88814      this.player.trigger('ended');
88815    }
88816
88817    /*
88818     * StitchedAdRoll break is done.
88819     */;
88820    _proto.endLinearAdMode = function endLinearAdMode() {
88821      var StitchedContentPlayback = States.getState('StitchedContentPlayback');
88822      obj$1.end(this.player);
88823      this.transitionTo(StitchedContentPlayback);
88824    };
88825    return StitchedAdRoll;
88826  }(AdState$4);
88827  States.registerState('StitchedAdRoll', StitchedAdRoll);
88828
88829  var AdState$5 = States.getState('AdState');
88830
88831  /**
88832   * This is the initial state for a player in outstream mode. Once a 'play' event
88833   * is seen, we enter the OutstreamPlayback state. If any errors occur, we go
88834   * straight from OutstreamPlayback to OutstreamDone.
88835   */
88836  var OutstreamPending = /*#__PURE__*/function (_AdState) {
88837    _inheritsLoose(OutstreamPending, _AdState);
88838    function OutstreamPending() {
88839      return _AdState.apply(this, arguments) || this;
88840    }
88841    /**
88842     * Allows state name to be logged after minification
88843     */
88844    OutstreamPending._getName = function _getName() {
88845      return 'OutstreamPending';
88846    }
88847
88848    /*
88849     * For state transitions to work correctly, initialization should
88850     * happen here, not in a constructor.
88851     */;
88852    var _proto = OutstreamPending.prototype;
88853    _proto.init = function init(player) {
88854      this.adsReady = false;
88855    };
88856    _proto.onPlay = function onPlay(player) {
88857      var OutstreamPlayback = States.getState('OutstreamPlayback');
88858      player.ads.debug('Received play event (OutstreamPending)');
88859      this.transitionTo(OutstreamPlayback, this.adsReady);
88860    };
88861    _proto.onAdsReady = function onAdsReady(player) {
88862      player.ads.debug('Received adsready event (OutstreamPending)');
88863      this.adsReady = true;
88864    };
88865    _proto.onAdsError = function onAdsError() {
88866      this.player.ads.debug('adserror (OutstreamPending)');
88867      this.adsReady = false;
88868    };
88869    return OutstreamPending;
88870  }(AdState$5);
88871  States.registerState('OutstreamPending', OutstreamPending);
88872
88873  var AdState$6 = States.getState('AdState');
88874
88875  /**
88876   * This state is for waiting for ads in an outstream player,
88877   * playing ads, and transitioning to OutstreamDone after ads have played.
88878   */
88879  var OutstreamPlayback = /*#__PURE__*/function (_AdState) {
88880    _inheritsLoose(OutstreamPlayback, _AdState);
88881    function OutstreamPlayback() {
88882      return _AdState.apply(this, arguments) || this;
88883    }
88884    /**
88885     * Allows state name to be logged after minification
88886     */
88887    OutstreamPlayback._getName = function _getName() {
88888      return 'OutstreamPlayback';
88889    }
88890
88891    /*
88892     * For state transitions to work correctly, initialization should
88893     * happen here, not in a constructor.
88894     */;
88895    var _proto = OutstreamPlayback.prototype;
88896    _proto.init = function init(player, adsReady) {
88897      player.addClass('vjs-ad-loading');
88898      if (adsReady) {
88899        this.handleAdsReady();
88900      } else {
88901        this.abort(player);
88902      }
88903    };
88904    _proto.onAdsReady = function onAdsReady(player) {
88905      if (!player.ads.inAdBreak()) {
88906        player.ads.debug('Received adsready event (Preroll)');
88907        this.handleAdsReady();
88908      } else {
88909        videojs.log.warn('Unexpected adsready event (Preroll)');
88910      }
88911    };
88912    _proto.abort = function abort(player) {
88913      var OutstreamDone = States.getState('OutstreamDone');
88914      player.removeClass('vjs-ad-loading');
88915      this.transitionTo(OutstreamDone);
88916    }
88917
88918    /*
88919     * An ad has actually started playing.
88920     * Remove the loading spinner.
88921     */;
88922    _proto.onAdStarted = function onAdStarted(player) {
88923      player.removeClass('vjs-ad-loading');
88924    };
88925    _proto.handleAdsReady = function handleAdsReady() {
88926      this.adsReady = true;
88927      this.readyForOutstreamPlayback();
88928    };
88929    _proto.readyForOutstreamPlayback = function readyForOutstreamPlayback() {
88930      var player = this.player;
88931      this.afterLoadStart(function () {
88932        player.trigger('readyforoutstream');
88933      });
88934    };
88935    _proto.startLinearAdMode = function startLinearAdMode() {
88936      var player = this.player;
88937      if (this.adsReady && !player.ads.inAdBreak()) {
88938        obj$1.start(player);
88939      }
88940    }
88941
88942    /*
88943     * Cleanup timeouts and spinner.
88944     */;
88945    _proto.cleanup = function cleanup(player) {
88946      if (!player.ads._hasThereBeenALoadStartDuringPlayerLife) {
88947        videojs.log.warn('Leaving OutstreamPlayback state before loadstart event can cause issues.');
88948      }
88949      this.cleanupPartial(player);
88950    }
88951
88952    /*
88953     * Performs cleanup tasks without depending on a state transition. This is
88954     * used mainly in cases where a preroll failed.
88955     */;
88956    _proto.cleanupPartial = function cleanupPartial(player) {
88957      player.removeClass('vjs-ad-loading');
88958      player.removeClass('vjs-ad-content-resuming');
88959      this.clearTimeout(player);
88960    }
88961
88962    /*
88963     * Clear the outstream ad timeout and nulls out the pointer.
88964     */;
88965    _proto.clearTimeout = function clearTimeout(player) {
88966      player.clearTimeout(this._timeout);
88967      this._timeout = null;
88968    };
88969    _proto.onAdStarted = function onAdStarted(player) {
88970      player.removeClass('vjs-ad-loading');
88971    }
88972
88973    /*
88974     * An ad error occured. Transition straight to OutstreamDone
88975     */;
88976    _proto.onAdsError = function onAdsError(player) {
88977      var _this = this;
88978      videojs.log('adserror (OutstreamPlayback)');
88979      if (this.inAdBreak()) {
88980        player.ads.endLinearAdMode();
88981      } else {
88982        this.afterLoadStart(function () {
88983          _this.abort(player);
88984        });
88985      }
88986    }
88987
88988    /*
88989     * Outstream ad took too long! Play content instead.
88990     */;
88991    _proto.onAdTimeout = function onAdTimeout(player) {
88992      var _this2 = this;
88993      this.afterLoadStart(function () {
88994        player.ads.debug('adtimeout (OutstreamPlayback)');
88995        _this2.abort(player);
88996      });
88997    };
88998    _proto.onAdsCanceled = function onAdsCanceled(player) {
88999      var _this3 = this;
89000      player.ads.debug('adscanceled (OutstreamPlaybac)');
89001      this.afterLoadStart(function () {
89002        _this3.abort(player);
89003      });
89004    };
89005    _proto.endLinearAdMode = function endLinearAdMode() {
89006      var _this4 = this;
89007      if (this.inAdBreak()) {
89008        this.player.removeClass('vjs-ad-loading');
89009        var OutstreamDone = States.getState('OutstreamDone');
89010        obj$1.end(this.player, function () {
89011          _this4.transitionTo(OutstreamDone);
89012        });
89013      }
89014    };
89015    _proto.afterLoadStart = function afterLoadStart(callback) {
89016      var player = this.player;
89017      if (player.ads._hasThereBeenALoadStartDuringPlayerLife) {
89018        callback();
89019      } else {
89020        player.ads.debug('Waiting for loadstart...');
89021        player.one('loadstart', function () {
89022          player.ads.debug('Received loadstart event');
89023          callback();
89024        });
89025      }
89026    }
89027
89028    /**
89029     * Ad plugin has skipped ad - transition to OutstreamDone
89030     */;
89031    _proto.skipLinearAdMode = function skipLinearAdMode() {
89032      var _this5 = this;
89033      var player = this.player;
89034      var OutstreamDone = States.getState('OutstreamDone');
89035      if (this.inAdBreak()) {
89036        videojs.log.warn('Unexpected skipLinearAdMode invocation');
89037      } else {
89038        this.afterLoadStart(function () {
89039          player.trigger('adskip');
89040          player.ads.debug('skipLinearAdMode (OutstreamPlayback)');
89041          _this5.transitionTo(OutstreamDone);
89042        });
89043      }
89044    };
89045    return OutstreamPlayback;
89046  }(AdState$6);
89047  States.registerState('OutstreamPlayback', OutstreamPlayback);
89048
89049  var AdState$7 = States.getState('AdState');
89050
89051  /**
89052   * This is the final state for a player in outstream mode. There
89053   * should be no more ads playing once the player has transitioned
89054   * to this state.
89055   */
89056  var OutstreamDone = /*#__PURE__*/function (_AdState) {
89057    _inheritsLoose(OutstreamDone, _AdState);
89058    function OutstreamDone() {
89059      return _AdState.apply(this, arguments) || this;
89060    }
89061    /**
89062     * Allows state name to be logged after minification
89063     */
89064    OutstreamDone._getName = function _getName() {
89065      return 'OutstreamDone';
89066    }
89067
89068    /*
89069     * For state transitions to work correctly, initialization should
89070     * happen here, not in a constructor.
89071     */;
89072    var _proto = OutstreamDone.prototype;
89073    _proto.init = function init(player) {
89074      player.trigger('ended');
89075    }
89076
89077    /*
89078     * No more ads should play after this state.
89079     */;
89080    _proto.startLinearAdMode = function startLinearAdMode() {
89081      videojs.log.warn('Unexpected startLinearAdMode invocation (OutstreamDone)');
89082    };
89083    return OutstreamDone;
89084  }(AdState$7);
89085  States.registerState('OutstreamDone', OutstreamDone);
89086
89087  /*
89088  This main plugin file is responsible for the public API and enabling the features
89089  that live in in separate files.
89090  */
89091  var isMiddlewareMediatorSupported$1 = obj.isMiddlewareMediatorSupported;
89092  var VIDEO_EVENTS = videojs.getTech('Html5').Events;
89093
89094  // Default settings
89095  var defaults = {
89096    // Maximum amount of time in ms to wait to receive `adsready` from the ad
89097    // implementation after play has been requested. Ad implementations are
89098    // expected to load any dynamic libraries and make any requests to determine
89099    // ad policies for a video during this time.
89100    timeout: 5000,
89101    // Maximum amount of time in ms to wait for the ad implementation to start
89102    // linear ad mode after `readyforpreroll` has fired. This is in addition to
89103    // the standard timeout.
89104    prerollTimeout: undefined,
89105    // Maximum amount of time in ms to wait for the ad implementation to start
89106    // linear ad mode after `readyforpostroll` has fired.
89107    postrollTimeout: undefined,
89108    // When truthy, instructs the plugin to output additional information about
89109    // plugin state to the video.js log. On most devices, the video.js log is
89110    // the same as the developer console.
89111    debug: false,
89112    // Set this to true when using ads that are part of the content video
89113    stitchedAds: false,
89114    // Force content to be treated as live or not live
89115    // if not defined, the code will try to infer if content is live,
89116    // which can have limitations.
89117    contentIsLive: undefined,
89118    // If set to true, content will play muted behind ads on supported platforms. This is
89119    // to support ads on video metadata cuepoints during a live stream. It also results in
89120    // more precise resumes after ads during a live stream.
89121    liveCuePoints: true,
89122    // If set to true, callPlay middleware will not terminate the first play request in
89123    // BeforePreroll if the player intends to autoplay. This allows the manual autoplay
89124    // attempt made by video.js to resolve/reject naturally and trigger an 'autoplay-success'
89125    // or 'autoplay-failure' event with which other plugins can interface.
89126    allowVjsAutoplay: videojs.options.normalizeAutoplay || false
89127  };
89128  var contribAdsPlugin = function contribAdsPlugin(options) {
89129    var player = this; // eslint-disable-line consistent-this
89130
89131    var settings = videojs.obj.merge(defaults, options);
89132
89133    // Prefix all video element events during ad playback
89134    // if the video element emits ad-related events directly,
89135    // plugins that aren't ad-aware will break. prefixing allows
89136    // plugins that wish to handle ad events to do so while
89137    // avoiding the complexity for common usage
89138    var videoEvents = [];
89139
89140    // dedupe event names
89141    VIDEO_EVENTS.concat(['firstplay', 'loadedalldata']).forEach(function (eventName) {
89142      if (videoEvents.indexOf(eventName) === -1) {
89143        videoEvents.push(eventName);
89144      }
89145    });
89146
89147    // Set up redispatching of player events
89148    player.on(videoEvents, redispatch);
89149
89150    // Set up features to block content playback while waiting for ads.
89151    // Play middleware is only supported on later versions of video.js
89152    // and on desktop currently(as the user-gesture requirement on mobile
89153    // will disallow calling play once play blocking is lifted)
89154    // The middleware must also be registered outside of the plugin,
89155    // to avoid a middleware factory being created for each player
89156    if (!isMiddlewareMediatorSupported$1()) {
89157      initCancelContentPlay(player, settings.debug);
89158    }
89159
89160    // If we haven't seen a loadstart after 5 seconds, the plugin was not initialized
89161    // correctly.
89162    player.setTimeout(function () {
89163      if (!player.ads._hasThereBeenALoadStartDuringPlayerLife && player.src() !== '') {
89164        videojs.log.error('videojs-contrib-ads has not seen a loadstart event 5 seconds ' + 'after being initialized, but a source is present. This indicates that ' + 'videojs-contrib-ads was initialized too late. It must be initialized ' + 'immediately after video.js in the same tick. As a result, some ads will not ' + 'play and some media events will be incorrect. For more information, see ' + 'http://videojs.github.io/videojs-contrib-ads/integrator/getting-started.html');
89165      }
89166    }, 5000);
89167
89168    // "vjs-has-started" should be present at the end of a video. This makes sure it's
89169    // always there.
89170    player.on('ended', function () {
89171      if (!player.hasClass('vjs-has-started')) {
89172        player.addClass('vjs-has-started');
89173      }
89174    });
89175
89176    // video.js removes the vjs-waiting class on timeupdate. We want
89177    // to make sure this still happens during content restoration.
89178    player.on('contenttimeupdate', function () {
89179      player.removeClass('vjs-waiting');
89180    });
89181
89182    // We now auto-play when an ad gets loaded if we're playing ads in the same video
89183    // element as the content.
89184    // The problem is that in IE11, we cannot play in addurationchange but in iOS8, we
89185    // cannot play from adcanplay.
89186    // This will prevent ad plugins from needing to do this themselves.
89187    player.on(['addurationchange', 'adcanplay'], function () {
89188      // We don't need to handle this for stitched ads because
89189      // linear ads in such cases are stitched into the content.
89190      if (player.ads.settings.stitchedAds) {
89191        return;
89192      }
89193      // Some techs may retrigger canplay after playback has begun.
89194      // So we want to procceed only if playback hasn't started.
89195      if (player.hasStarted()) {
89196        return;
89197      }
89198      if (player.ads.snapshot && player.currentSrc() === player.ads.snapshot.currentSrc) {
89199        return;
89200      }
89201
89202      // If an ad isn't playing, don't try to play an ad. This could result from prefixed
89203      // events when the player is blocked by a preroll check, but there is no preroll.
89204      if (!player.ads.inAdBreak()) {
89205        return;
89206      }
89207      var playPromise = player.play();
89208      if (playPromise && playPromise.catch) {
89209        playPromise.catch(function (error) {
89210          videojs.log.warn('Play promise rejected when playing ad', error);
89211        });
89212      }
89213    });
89214    player.on('nopreroll', function () {
89215      player.ads.debug('Received nopreroll event');
89216      player.ads.nopreroll_ = true;
89217    });
89218    player.on('nopostroll', function () {
89219      player.ads.debug('Received nopostroll event');
89220      player.ads.nopostroll_ = true;
89221    });
89222
89223    // Restart the cancelContentPlay process.
89224    player.on('playing', function () {
89225      player.ads._cancelledPlay = false;
89226      player.ads._pausedOnContentupdate = false;
89227    });
89228
89229    // Keep track of whether a play event has happened
89230    player.on('play', function () {
89231      player.ads._playRequested = true;
89232    });
89233    player.one('loadstart', function () {
89234      player.ads._hasThereBeenALoadStartDuringPlayerLife = true;
89235    });
89236    player.on('loadeddata', function () {
89237      player.ads._hasThereBeenALoadedData = true;
89238    });
89239    player.on('loadedmetadata', function () {
89240      player.ads._hasThereBeenALoadedMetaData = true;
89241    });
89242
89243    // Replace the plugin constructor with the ad namespace
89244    player.ads = getAds(player);
89245    player.ads.settings = settings;
89246
89247    // Set the stitched ads state. This needs to happen before the `_state` is
89248    // initialized below - BeforePreroll needs to know whether contrib-ads is
89249    // playing stitched ads or not.
89250    // The setter is deprecated, so this does not use it.
89251    // But first, cast to boolean.
89252    settings.stitchedAds = !!settings.stitchedAds;
89253    if (settings.playerMode === 'outstream') {
89254      // Set a 0s mp4 file to enable ads to play
89255      player.src(OUTSTREAM_VIDEO);
89256      player.ads._state = new (States.getState('OutstreamPending'))(player);
89257    } else if (settings.stitchedAds) {
89258      player.ads._state = new (States.getState('StitchedContentPlayback'))(player);
89259    } else {
89260      player.ads._state = new (States.getState('BeforePreroll'))(player);
89261    }
89262    player.ads._state.init(player);
89263    player.ads.cueTextTracks = cueTextTracks;
89264    player.ads.adMacroReplacement = adMacroReplacement.bind(player);
89265
89266    // Start sending contentupdate and contentchanged events for this player
89267    initializeContentupdate(player);
89268
89269    // Global contentchanged handler for resetting plugin state
89270    player.on('contentchanged', player.ads.reset);
89271
89272    // A utility method for textTrackChangeHandler to define the conditions
89273    // when text tracks should be disabled.
89274    // Currently this includes:
89275    //  - on iOS with native text tracks, during an ad playing
89276    var shouldDisableTracks = function shouldDisableTracks() {
89277      // If the platform matches iOS with native text tracks
89278      // and this occurs during ad playback, we should disable tracks again.
89279      // If shouldPlayContentBehindAd, no special handling is needed.
89280      return !player.ads.shouldPlayContentBehindAd(player) && player.ads.inAdBreak() && player.tech_.featuresNativeTextTracks && videojs.browser.IS_IOS &&
89281      // older versions of video.js did not use an emulated textTrackList
89282      !Array.isArray(player.textTracks());
89283    };
89284
89285    /*
89286     * iOS Safari will change caption mode to 'showing' if a user previously
89287     * turned captions on manually for that video source, so this TextTrackList
89288     * 'change' event handler will re-disable them in case that occurs during ad playback
89289     */
89290    var textTrackChangeHandler = function textTrackChangeHandler() {
89291      var textTrackList = player.textTracks();
89292      if (shouldDisableTracks()) {
89293        // We must double check all tracks
89294        for (var i = 0; i < textTrackList.length; i++) {
89295          var track = textTrackList[i];
89296          if (track.mode === 'showing') {
89297            track.mode = 'disabled';
89298          }
89299        }
89300      }
89301    };
89302
89303    // Add the listener to the text track list
89304    player.ready(function () {
89305      player.textTracks().addEventListener('change', textTrackChangeHandler);
89306    });
89307
89308    // Event handling for the current state.
89309    player.on(['play', 'playing', 'ended', 'adsready', 'adscanceled', 'adskip', 'adserror', 'adtimeout', 'adended', 'ads-ad-started', 'ads-ad-skipped', 'contentchanged', 'dispose', 'contentresumed', 'readyforpostroll', 'nopreroll', 'nopostroll'], function (e) {
89310      player.ads._state.handleEvent(e.type);
89311    });
89312
89313    // Clear timeouts and handlers when player is disposed
89314    player.on('dispose', function () {
89315      player.ads.reset();
89316      player.textTracks().removeEventListener('change', textTrackChangeHandler);
89317    });
89318
89319    // Listen to TCF changes
89320    listenToTcf();
89321
89322    // Initialize the US Privacy string
89323    obtainUsPrivacyString(function () {});
89324
89325    // Can be called for testing, or if the TCF CMP has loaded late
89326    player.ads.listenToTcf = listenToTcf;
89327
89328    // Expose so the US privacy string can be updated as needed
89329    player.ads.updateUsPrivacyString = function (callback) {
89330      return obtainUsPrivacyString(callback);
89331    };
89332  };
89333
89334  // contrib-ads specific error const
89335  contribAdsPlugin.Error = Error$1;
89336
89337  // Expose the contrib-ads version before it is initialized. Will be replaced
89338  // after initialization in ads.js
89339  contribAdsPlugin.VERSION = version;
89340
89341  // Attempt to register the plugin, if we can.
89342  register(contribAdsPlugin);
89343
89344  return contribAdsPlugin;
89345
89346}));
89347
89348},{"global/document":38,"global/window":39,"video.js":50}],52:[function(require,module,exports){
vendor: 118,074 bytes, lines 89349-93100
89349(function (global, factory) {
89350  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('video.js')) :
89351  typeof define === 'function' && define.amd ? define(['exports', 'video.js'], factory) :
89352  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.videojsIma = {}, global.videojs));
89353})(this, (function (exports, videojs) { 'use strict';
89354
89355  function _arrayLikeToArray(r, a) {
89356    (null == a || a > r.length) && (a = r.length);
89357    for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
89358    return n;
89359  }
89360  function _classCallCheck(a, n) {
89361    if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function");
89362  }
89363  function _createClass(e, r, t) {
89364    return Object.defineProperty(e, "prototype", {
89365      writable: false
89366    }), e;
89367  }
89368  function _createForOfIteratorHelper(r, e) {
89369    var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
89370    if (!t) {
89371      if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) {
89372        t && (r = t);
89373        var n = 0,
89374          F = function () {};
89375        return {
89376          s: F,
89377          n: function () {
89378            return n >= r.length ? {
89379              done: true
89380            } : {
89381              done: false,
89382              value: r[n++]
89383            };
89384          },
89385          e: function (r) {
89386            throw r;
89387          },
89388          f: F
89389        };
89390      }
89391      throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
89392    }
89393    var o,
89394      a = true,
89395      u = false;
89396    return {
89397      s: function () {
89398        t = t.call(r);
89399      },
89400      n: function () {
89401        var r = t.next();
89402        return a = r.done, r;
89403      },
89404      e: function (r) {
89405        u = true, o = r;
89406      },
89407      f: function () {
89408        try {
89409          a || null == t.return || t.return();
89410        } finally {
89411          if (u) throw o;
89412        }
89413      }
89414    };
89415  }
89416  function _defineProperty(e, r, t) {
89417    return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
89418      value: t,
89419      enumerable: true,
89420      configurable: true,
89421      writable: true
89422    }) : e[r] = t, e;
89423  }
89424  function _toPrimitive(t, r) {
89425    if ("object" != typeof t || !t) return t;
89426    var e = t[Symbol.toPrimitive];
89427    if (void 0 !== e) {
89428      var i = e.call(t, r);
89429      if ("object" != typeof i) return i;
89430      throw new TypeError("@@toPrimitive must return a primitive value.");
89431    }
89432    return ("string" === r ? String : Number)(t);
89433  }
89434  function _toPropertyKey(t) {
89435    var i = _toPrimitive(t, "string");
89436    return "symbol" == typeof i ? i : i + "";
89437  }
89438  function _typeof(o) {
89439    "@babel/helpers - typeof";
89440
89441    return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
89442      return typeof o;
89443    } : function (o) {
89444      return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
89445    }, _typeof(o);
89446  }
89447  function _unsupportedIterableToArray(r, a) {
89448    if (r) {
89449      if ("string" == typeof r) return _arrayLikeToArray(r, a);
89450      var t = {}.toString.call(r).slice(8, -1);
89451      return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
89452    }
89453  }
89454
89455  /**
89456   * Copyright 2017 Google Inc.
89457   *
89458   * Licensed under the Apache License, Version 2.0 (the "License");
89459   * you may not use this file except in compliance with the License.
89460   * You may obtain a copy of the License at
89461   *
89462   *     http://www.apache.org/licenses/LICENSE-2.0
89463   *
89464   * Unless required by applicable law or agreed to in writing, software
89465   * distributed under the License is distributed on an "AS IS" BASIS,
89466   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
89467   * See the License for the specific language governing permissions and
89468   * limitations under the License.
89469   *
89470   * IMA SDK integration plugin for Video.js. For more information see
89471   * https://www.github.com/googleads/videojs-ima
89472   */
89473
89474  /**
89475   * Wraps the video.js player for the plugin.
89476   *
89477   * @param {Object} player Video.js player instance.
89478   * @param {Object} adsPluginSettings Settings for the contrib-ads plugin.
89479   * @param {Controller} controller Reference to the parent controller.
89480   */
89481  var PlayerWrapper$1 = function PlayerWrapper(player, adsPluginSettings, controller) {
89482    /**
89483     * Instance of the video.js player.
89484     */
89485    this.vjsPlayer = player;
89486
89487    /**
89488     * Plugin controller.
89489     */
89490    this.controller = controller;
89491
89492    /**
89493     * Timer used to track content progress.
89494     */
89495    this.contentTrackingTimer = null;
89496
89497    /**
89498     * True if our content video has completed, false otherwise.
89499     */
89500    this.contentComplete = false;
89501
89502    /**
89503     * Handle to interval that repeatedly updates current time.
89504     */
89505    this.updateTimeIntervalHandle = null;
89506
89507    /**
89508     * Interval (ms) to check for player resize for fluid support.
89509     */
89510    this.updateTimeInterval = 1000;
89511
89512    /**
89513     * Handle to interval that repeatedly checks for seeking.
89514     */
89515    this.seekCheckIntervalHandle = null;
89516
89517    /**
89518     * Interval (ms) on which to check if the user is seeking through the
89519     * content.
89520     */
89521    this.seekCheckInterval = 1000;
89522
89523    /**
89524     * Handle to interval that repeatedly checks for player resize.
89525     */
89526    this.resizeCheckIntervalHandle = null;
89527
89528    /**
89529     * Interval (ms) to check for player resize for fluid support.
89530     */
89531    this.resizeCheckInterval = 250;
89532
89533    /**
89534     * Threshold by which to judge user seeking. We check every 1000 ms to see
89535     * if the user is seeking. In order for us to decide that they are *not*
89536     * seeking, the content video playhead must only change by 900-1100 ms
89537     * between checks. Any greater change and we assume the user is seeking
89538     * through the video.
89539     */
89540    this.seekThreshold = 100;
89541
89542    /**
89543     * Content ended listeners passed by the publisher to the plugin. Publishers
89544     * should allow the plugin to handle content ended to ensure proper support
89545     * of custom ad playback.
89546     */
89547    this.contentEndedListeners = [];
89548
89549    /**
89550     * Stores the content source so we can re-populate it manually after a
89551     * post-roll on iOS.
89552     */
89553    this.contentSource = '';
89554
89555    /**
89556     * Stores the content source type so we can re-populate it manually after a
89557     * post-roll.
89558     */
89559    this.contentSourceType = '';
89560
89561    /**
89562     * Stores data for the content playhead tracker.
89563     */
89564    this.contentPlayheadTracker = {
89565      currentTime: 0,
89566      previousTime: 0,
89567      seeking: false,
89568      duration: 0
89569    };
89570
89571    /**
89572     * Player dimensions. Used in our resize check.
89573     */
89574    this.vjsPlayerDimensions = {
89575      width: this.getPlayerWidth(),
89576      height: this.getPlayerHeight()
89577    };
89578
89579    /**
89580     * Video.js control bar.
89581     */
89582    this.vjsControls = this.vjsPlayer.getChild('controlBar');
89583
89584    /**
89585     * Vanilla HTML5 video player underneath the video.js player.
89586     */
89587    this.h5Player = null;
89588    this.vjsPlayer.one('play', this.setUpPlayerIntervals.bind(this));
89589    this.boundContentEndedListener = this.localContentEndedListener.bind(this);
89590    this.vjsPlayer.on('readyforpostroll', this.boundContentEndedListener);
89591    this.vjsPlayer.on('dispose', this.playerDisposedListener.bind(this));
89592    this.vjsPlayer.on('readyforpreroll', this.onReadyForPreroll.bind(this));
89593    this.vjsPlayer.on('adtimeout', this.onAdTimeout.bind(this));
89594    this.vjsPlayer.ready(this.onPlayerReady.bind(this));
89595    if (this.controller.getSettings().requestMode === 'onPlay') {
89596      this.vjsPlayer.one('play', this.controller.requestAds.bind(this.controller));
89597    }
89598    if (!this.vjsPlayer.ads) {
89599      window.console.warn('You may be using a version of videojs-contrib-ads ' + 'that is not compatible with your version of video.js.');
89600    }
89601    this.vjsPlayer.ads(adsPluginSettings);
89602  };
89603
89604  /**
89605   * Set up the intervals we use on the player.
89606   */
89607  PlayerWrapper$1.prototype.setUpPlayerIntervals = function () {
89608    /**
89609     * Clear old interval handers in case the method was called more than once
89610     */
89611    if (this.updateTimeIntervalHandle) {
89612      clearInterval(this.updateTimeIntervalHandle);
89613    }
89614    if (this.seekCheckIntervalHandle) {
89615      clearInterval(this.seekCheckIntervalHandle);
89616    }
89617    if (this.resizeCheckIntervalHandle) {
89618      clearInterval(this.resizeCheckIntervalHandle);
89619    }
89620    this.updateTimeIntervalHandle = setInterval(this.updateCurrentTime.bind(this), this.updateTimeInterval);
89621    this.seekCheckIntervalHandle = setInterval(this.checkForSeeking.bind(this), this.seekCheckInterval);
89622    this.resizeCheckIntervalHandle = setInterval(this.checkForResize.bind(this), this.resizeCheckInterval);
89623  };
89624
89625  /**
89626   * Updates the current time of the video
89627   */
89628  PlayerWrapper$1.prototype.updateCurrentTime = function () {
89629    if (!this.contentPlayheadTracker.seeking) {
89630      this.contentPlayheadTracker.currentTime = this.vjsPlayer.currentTime();
89631    }
89632  };
89633
89634  /**
89635   * Detects when the user is seeking through a video.
89636   * This is used to prevent mid-rolls from playing while a user is seeking.
89637   *
89638   * There *is* a seeking property of the HTML5 video element, but it's not
89639   * properly implemented on all platforms (e.g. mobile safari), so we have to
89640   * check ourselves to be sure.
89641   */
89642  PlayerWrapper$1.prototype.checkForSeeking = function () {
89643    var tempCurrentTime = this.vjsPlayer.currentTime();
89644    var diff = (tempCurrentTime - this.contentPlayheadTracker.previousTime) * 1000;
89645    if (Math.abs(diff) > this.seekCheckInterval + this.seekThreshold) {
89646      this.contentPlayheadTracker.seeking = true;
89647    } else {
89648      this.contentPlayheadTracker.seeking = false;
89649    }
89650    this.contentPlayheadTracker.previousTime = this.vjsPlayer.currentTime();
89651  };
89652
89653  /**
89654   * Detects when the player is resized (for fluid support) and resizes the
89655   * ads manager to match.
89656   */
89657  PlayerWrapper$1.prototype.checkForResize = function () {
89658    var currentWidth = this.getPlayerWidth();
89659    var currentHeight = this.getPlayerHeight();
89660    if (currentWidth != this.vjsPlayerDimensions.width || currentHeight != this.vjsPlayerDimensions.height) {
89661      this.vjsPlayerDimensions.width = currentWidth;
89662      this.vjsPlayerDimensions.height = currentHeight;
89663      this.controller.onPlayerResize(currentWidth, currentHeight);
89664    }
89665  };
89666
89667  /**
89668   * Local content ended listener for contentComplete.
89669   */
89670  PlayerWrapper$1.prototype.localContentEndedListener = function () {
89671    if (!this.contentComplete) {
89672      this.contentComplete = true;
89673      this.controller.onContentComplete();
89674    }
89675    for (var index in this.contentEndedListeners) {
89676      if (typeof this.contentEndedListeners[index] === 'function') {
89677        this.contentEndedListeners[index]();
89678      }
89679    }
89680    if (this.vjsPlayer.el()) {
89681      this.vjsPlayer.one('play', this.setUpPlayerIntervals.bind(this));
89682    }
89683  };
89684
89685  /**
89686   * Called when it's time to play a post-roll but we don't have one to play.
89687   */
89688  PlayerWrapper$1.prototype.onNoPostroll = function () {
89689    this.vjsPlayer.trigger('nopostroll');
89690  };
89691
89692  /**
89693   * Detects when the video.js player has been disposed.
89694   */
89695  PlayerWrapper$1.prototype.playerDisposedListener = function () {
89696    this.contentEndedListeners = [];
89697    this.controller.onPlayerDisposed();
89698    this.contentComplete = true;
89699    this.vjsPlayer.off('readyforpostroll', this.boundContentEndedListener);
89700
89701    // Bug fix: https://github.com/googleads/videojs-ima/issues/306
89702    if (this.vjsPlayer.ads.adTimeoutTimeout) {
89703      clearTimeout(this.vjsPlayer.ads.adTimeoutTimeout);
89704    }
89705    var intervalsToClear = [this.updateTimeIntervalHandle, this.seekCheckIntervalHandle, this.resizeCheckIntervalHandle];
89706    for (var index in intervalsToClear) {
89707      if (intervalsToClear[index]) {
89708        clearInterval(intervalsToClear[index]);
89709      }
89710    }
89711  };
89712
89713  /**
89714   * Start ad playback, or content video playback in the absence of a
89715   * pre-roll.
89716   */
89717  PlayerWrapper$1.prototype.onReadyForPreroll = function () {
89718    this.controller.onPlayerReadyForPreroll();
89719  };
89720
89721  /**
89722   * Detects if the ad has timed out.
89723   */
89724  PlayerWrapper$1.prototype.onAdTimeout = function () {
89725    this.controller.onAdTimeout();
89726  };
89727
89728  /**
89729   * Called when the player fires its 'ready' event.
89730   */
89731  PlayerWrapper$1.prototype.onPlayerReady = function () {
89732    this.h5Player = document.getElementById(this.getPlayerId()).getElementsByClassName('vjs-tech')[0];
89733
89734    // Detect inline options
89735    if (this.h5Player.hasAttribute('autoplay')) {
89736      this.controller.setSetting('adWillAutoPlay', true);
89737    }
89738
89739    // Sync ad volume with player volume.
89740    this.onVolumeChange();
89741    this.vjsPlayer.on('fullscreenchange', this.onFullscreenChange.bind(this));
89742    this.vjsPlayer.on('volumechange', this.onVolumeChange.bind(this));
89743    this.controller.onPlayerReady();
89744  };
89745
89746  /**
89747   * Listens for the video.js player to change its fullscreen status. This
89748   * keeps the fullscreen-ness of the AdContainer in sync with the player.
89749   */
89750  PlayerWrapper$1.prototype.onFullscreenChange = function () {
89751    if (this.vjsPlayer.isFullscreen()) {
89752      this.controller.onPlayerEnterFullscreen();
89753    } else {
89754      this.controller.onPlayerExitFullscreen();
89755    }
89756  };
89757
89758  /**
89759   * Listens for the video.js player to change its volume. This keeps the ad
89760   * volume in sync with the content volume if the volume of the player is
89761   * changed while content is playing.
89762   */
89763  PlayerWrapper$1.prototype.onVolumeChange = function () {
89764    var newVolume = this.vjsPlayer.muted() ? 0 : this.vjsPlayer.volume();
89765    this.controller.onPlayerVolumeChanged(newVolume);
89766  };
89767
89768  /**
89769   * Inject the ad container div into the DOM.
89770   *
89771   * @param{HTMLElement} adContainerDiv The ad container div.
89772   */
89773  PlayerWrapper$1.prototype.injectAdContainerDiv = function (adContainerDiv) {
89774    this.vjsControls.el().parentNode.appendChild(adContainerDiv);
89775  };
89776
89777  /**
89778   * @return {Object} The content player.
89779   */
89780  PlayerWrapper$1.prototype.getContentPlayer = function () {
89781    return this.h5Player;
89782  };
89783
89784  /**
89785   * @return {number} The volume, 0-1.
89786   */
89787  PlayerWrapper$1.prototype.getVolume = function () {
89788    return this.vjsPlayer.muted() ? 0 : this.vjsPlayer.volume();
89789  };
89790
89791  /**
89792   * Set the volume of the player. 0-1.
89793   *
89794   * @param {number} volume The new volume.
89795   */
89796  PlayerWrapper$1.prototype.setVolume = function (volume) {
89797    this.vjsPlayer.volume(volume);
89798    if (volume == 0) {
89799      this.vjsPlayer.muted(true);
89800    } else {
89801      this.vjsPlayer.muted(false);
89802    }
89803  };
89804
89805  /**
89806   * Ummute the player.
89807   */
89808  PlayerWrapper$1.prototype.unmute = function () {
89809    this.vjsPlayer.muted(false);
89810  };
89811
89812  /**
89813   * Mute the player.
89814   */
89815  PlayerWrapper$1.prototype.mute = function () {
89816    this.vjsPlayer.muted(true);
89817  };
89818
89819  /**
89820   * Play the video.
89821   */
89822  PlayerWrapper$1.prototype.play = function () {
89823    this.vjsPlayer.play();
89824  };
89825
89826  /**
89827   * Toggles playback of the video.
89828   */
89829  PlayerWrapper$1.prototype.togglePlayback = function () {
89830    if (this.vjsPlayer.paused()) {
89831      this.vjsPlayer.play();
89832    } else {
89833      this.vjsPlayer.pause();
89834    }
89835  };
89836
89837  /**
89838   * Get the player width.
89839   *
89840   * @return {number} The player's width.
89841   */
89842  PlayerWrapper$1.prototype.getPlayerWidth = function () {
89843    var width = (getComputedStyle(this.vjsPlayer.el()) || {}).width;
89844    if (!width || parseFloat(width) === 0) {
89845      width = (this.vjsPlayer.el().getBoundingClientRect() || {}).width;
89846    }
89847    return parseFloat(width) || this.vjsPlayer.width();
89848  };
89849
89850  /**
89851   * Get the player height.
89852   *
89853   * @return {number} The player's height.
89854   */
89855  PlayerWrapper$1.prototype.getPlayerHeight = function () {
89856    var height = (getComputedStyle(this.vjsPlayer.el()) || {}).height;
89857    if (!height || parseFloat(height) === 0) {
89858      height = (this.vjsPlayer.el().getBoundingClientRect() || {}).height;
89859    }
89860    return parseFloat(height) || this.vjsPlayer.height();
89861  };
89862
89863  /**
89864   * @return {Object} The vjs player's options object.
89865   */
89866  PlayerWrapper$1.prototype.getPlayerOptions = function () {
89867    return this.vjsPlayer.options_;
89868  };
89869
89870  /**
89871   * Returns the instance of the player id.
89872   * @return {string} The player id.
89873   */
89874  PlayerWrapper$1.prototype.getPlayerId = function () {
89875    return this.vjsPlayer.id();
89876  };
89877
89878  /**
89879   * Toggle fullscreen state.
89880   */
89881  PlayerWrapper$1.prototype.toggleFullscreen = function () {
89882    if (this.vjsPlayer.isFullscreen()) {
89883      this.vjsPlayer.exitFullscreen();
89884    } else {
89885      this.vjsPlayer.requestFullscreen();
89886    }
89887  };
89888
89889  /**
89890   * Returns the content playhead tracker.
89891   *
89892   * @return {Object} The content playhead tracker.
89893   */
89894  PlayerWrapper$1.prototype.getContentPlayheadTracker = function () {
89895    return this.contentPlayheadTracker;
89896  };
89897
89898  /**
89899   * Handles ad errors.
89900   *
89901   * @param {Object} adErrorEvent The ad error event thrown by the IMA SDK.
89902   */
89903  PlayerWrapper$1.prototype.onAdError = function (adErrorEvent) {
89904    this.vjsControls.show();
89905    var errorMessage = adErrorEvent.getError !== undefined ? adErrorEvent.getError() : adErrorEvent.stack;
89906    this.vjsPlayer.trigger({
89907      type: 'adserror',
89908      data: {
89909        AdError: errorMessage,
89910        AdErrorEvent: adErrorEvent
89911      }
89912    });
89913  };
89914
89915  /**
89916   * Handles ad log messages.
89917   * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the IMA SDK.
89918   */
89919  PlayerWrapper$1.prototype.onAdLog = function (adEvent) {
89920    var adData = adEvent.getAdData();
89921    var errorMessage = adData['adError'] !== undefined ? adData['adError'].getMessage() : undefined;
89922    this.vjsPlayer.trigger({
89923      type: 'adslog',
89924      data: {
89925        AdError: errorMessage,
89926        AdEvent: adEvent
89927      }
89928    });
89929  };
89930
89931  /**
89932   * Handles ad break starting.
89933   */
89934  PlayerWrapper$1.prototype.onAdBreakStart = function () {
89935    this.contentSource = this.vjsPlayer.currentSrc();
89936    this.contentSourceType = this.vjsPlayer.currentType();
89937    this.vjsPlayer.off('readyforpostroll', this.boundContentEndedListener);
89938    this.vjsPlayer.ads.startLinearAdMode();
89939    this.vjsControls.hide();
89940    this.vjsPlayer.pause();
89941  };
89942
89943  /**
89944   * Handles ad break ending.
89945   */
89946  PlayerWrapper$1.prototype.onAdBreakEnd = function () {
89947    this.vjsPlayer.on('readyforpostroll', this.boundContentEndedListener);
89948    if (this.vjsPlayer.ads.inAdBreak()) {
89949      this.vjsPlayer.ads.endLinearAdMode();
89950    }
89951    this.vjsControls.show();
89952  };
89953
89954  /**
89955   * Handles an individual ad start.
89956   */
89957  PlayerWrapper$1.prototype.onAdStart = function () {
89958    this.vjsPlayer.trigger('ads-ad-started');
89959  };
89960
89961  /**
89962   * Handles when all ads have finished playing.
89963   */
89964  PlayerWrapper$1.prototype.onAllAdsCompleted = function () {
89965    if (this.contentComplete == true) {
89966      // The null check on this.contentSource was added to fix
89967      // an error when the post-roll was an empty VAST tag.
89968      if (this.contentSource && this.vjsPlayer.currentSrc() != this.contentSource) {
89969        this.vjsPlayer.src({
89970          src: this.contentSource,
89971          type: this.contentSourceType
89972        });
89973      }
89974      this.controller.onContentAndAdsCompleted();
89975    }
89976  };
89977
89978  /**
89979   * Triggers adsready for contrib-ads.
89980   */
89981  PlayerWrapper$1.prototype.onAdsReady = function () {
89982    this.vjsPlayer.trigger('adsready');
89983  };
89984
89985  /**
89986   * Changes the player source.
89987   * @param {?string} contentSrc The URI for the content to be played. Leave
89988   *     blank to use the existing content.
89989   */
89990  PlayerWrapper$1.prototype.changeSource = function (contentSrc) {
89991    // Only try to pause the player when initialised with a source already
89992    if (this.vjsPlayer.currentSrc()) {
89993      this.vjsPlayer.currentTime(0);
89994      this.vjsPlayer.pause();
89995    }
89996    if (contentSrc) {
89997      this.vjsPlayer.src(contentSrc);
89998    }
89999    this.vjsPlayer.one('loadedmetadata', this.seekContentToZero.bind(this));
90000  };
90001
90002  /**
90003   * Seeks content to 00:00:00. This is used as an event handler for the
90004   * loadedmetadata event, since seeking is not possible until that event has
90005   * fired.
90006   */
90007  PlayerWrapper$1.prototype.seekContentToZero = function () {
90008    this.vjsPlayer.currentTime(0);
90009  };
90010
90011  /**
90012   * Triggers an event on the VJS player
90013   * @param  {string} name The event name.
90014   * @param  {Object} data The event data.
90015   */
90016  PlayerWrapper$1.prototype.triggerPlayerEvent = function (name, data) {
90017    this.vjsPlayer.trigger(name, data);
90018  };
90019
90020  /**
90021   * Listener JSDoc for ESLint. This listener can be passed to
90022   * addContentEndedListener.
90023   * @callback listener
90024   */
90025
90026  /**
90027   * Adds a listener for the 'readyforpostroll' event of the video player. This
90028   * should be used instead of setting an 'readyforpostroll' listener directly to
90029   * ensure that the ima can do proper cleanup of the SDK before other event
90030   * listeners are called.
90031   * @param {listener} listener The listener to be called when content
90032   *     completes.
90033   */
90034  PlayerWrapper$1.prototype.addContentEndedListener = function (listener) {
90035    this.contentEndedListeners.push(listener);
90036  };
90037
90038  /**
90039   * Reset the player.
90040   */
90041  PlayerWrapper$1.prototype.reset = function () {
90042    // Attempts to remove the contentEndedListener before adding it.
90043    // This is to prevent an error where an erroring video caused multiple
90044    // contentEndedListeners to be added.
90045    this.vjsPlayer.off('readyforpostroll', this.boundContentEndedListener);
90046    this.vjsPlayer.on('readyforpostroll', this.boundContentEndedListener);
90047    this.vjsControls.show();
90048    if (this.vjsPlayer.ads.inAdBreak()) {
90049      this.vjsPlayer.ads.endLinearAdMode();
90050    }
90051    // Reset the content time we give the SDK. Fixes an issue where requesting
90052    // VMAP followed by VMAP would play the second mid-rolls as pre-rolls if
90053    // the first playthrough of the video passed the second response's
90054    // mid-roll time.
90055    this.contentPlayheadTracker.currentTime = 0;
90056    this.contentComplete = false;
90057  };
90058
90059  /**
90060   * Copyright 2017 Google Inc.
90061   *
90062   * Licensed under the Apache License, Version 2.0 (the "License");
90063   * you may not use this file except in compliance with the License.
90064   * You may obtain a copy of the License at
90065   *
90066   *     http://www.apache.org/licenses/LICENSE-2.0
90067   *
90068   * Unless required by applicable law or agreed to in writing, software
90069   * distributed under the License is distributed on an "AS IS" BASIS,
90070   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
90071   * See the License for the specific language governing permissions and
90072   * limitations under the License.
90073   *
90074   * IMA SDK integration plugin for Video.js. For more information see
90075   * https://www.github.com/googleads/videojs-ima
90076   */
90077
90078  /**
90079   * Ad UI implementation.
90080   *
90081   * @param {Controller} controller Plugin controller.
90082   * @constructor
90083   * @struct
90084   * @final
90085   */
90086  var AdUi = function AdUi(controller) {
90087    /**
90088     * Plugin controller.
90089     */
90090    this.controller = controller;
90091
90092    /**
90093     * Div used as an ad container.
90094     */
90095    this.adContainerDiv = document.createElement('div');
90096
90097    /**
90098     * Div used to display ad controls.
90099     */
90100    this.controlsDiv = document.createElement('div');
90101
90102    /**
90103     * Div used to display ad countdown timer.
90104     */
90105    this.countdownDiv = document.createElement('div');
90106
90107    /**
90108     * Div used to display add seek bar.
90109     */
90110    this.seekBarDiv = document.createElement('div');
90111
90112    /**
90113     * Div used to display ad progress (in seek bar).
90114     */
90115    this.progressDiv = document.createElement('div');
90116
90117    /**
90118     * Div used to display ad play/pause button.
90119     */
90120    this.playPauseDiv = document.createElement('div');
90121
90122    /**
90123     * Div used to display ad mute button.
90124     */
90125    this.muteDiv = document.createElement('div');
90126
90127    /**
90128     * Div used by the volume slider.
90129     */
90130    this.sliderDiv = document.createElement('div');
90131
90132    /**
90133     * Volume slider level visuals
90134     */
90135    this.sliderLevelDiv = document.createElement('div');
90136
90137    /**
90138     * Div used to display ad fullscreen button.
90139     */
90140    this.fullscreenDiv = document.createElement('div');
90141
90142    /**
90143     * Bound event handler for onMouseUp.
90144     */
90145    this.boundOnMouseUp = this.onMouseUp.bind(this);
90146
90147    /**
90148     * Bound event handler for onMouseMove.
90149     */
90150    this.boundOnMouseMove = this.onMouseMove.bind(this);
90151
90152    /**
90153     * Stores data for the ad playhead tracker.
90154     */
90155    this.adPlayheadTracker = {
90156      'currentTime': 0,
90157      'duration': 0,
90158      'isPod': false,
90159      'adPosition': 0,
90160      'totalAds': 0
90161    };
90162
90163    /**
90164     * Used to prefix videojs ima controls.
90165     */
90166    this.controlPrefix = this.controller.getPlayerId() + '_';
90167
90168    /**
90169     * Boolean flag to show or hide the ad countdown timer.
90170     */
90171    this.showCountdown = true;
90172    if (this.controller.getSettings().showCountdown === false) {
90173      this.showCountdown = false;
90174    }
90175
90176    /**
90177     * Boolean flag if the current ad is nonlinear.
90178     */
90179    this.isAdNonlinear = false;
90180    this.createAdContainer();
90181  };
90182
90183  /**
90184   * Creates the ad container.
90185   */
90186  AdUi.prototype.createAdContainer = function () {
90187    this.assignControlAttributes(this.adContainerDiv, 'ima-ad-container');
90188    // Hide the ad container until an ad starts.
90189    this.adContainerDiv.classList.add('hide-ad-container');
90190    this.adContainerDiv.addEventListener('mouseenter', this.showAdControls.bind(this), false);
90191    this.adContainerDiv.addEventListener('mouseleave', this.hideAdControls.bind(this), false);
90192    this.adContainerDiv.addEventListener('click', this.onAdContainerClick.bind(this), false);
90193    this.createControls();
90194    this.controller.injectAdContainerDiv(this.adContainerDiv);
90195  };
90196
90197  /**
90198   * Create the controls.
90199   */
90200  AdUi.prototype.createControls = function () {
90201    this.assignControlAttributes(this.controlsDiv, 'ima-controls-div');
90202    this.controlsDiv.style.width = '100%';
90203    if (!this.controller.getIsMobile()) {
90204      this.assignControlAttributes(this.countdownDiv, 'ima-countdown-div');
90205      this.countdownDiv.innerHTML = this.controller.getSettings().adLabel;
90206      this.countdownDiv.style.display = this.showCountdown ? 'block' : 'none';
90207    } else {
90208      this.countdownDiv.style.display = 'none';
90209    }
90210    this.assignControlAttributes(this.seekBarDiv, 'ima-seek-bar-div');
90211    this.seekBarDiv.style.width = '100%';
90212    this.assignControlAttributes(this.progressDiv, 'ima-progress-div');
90213    this.assignControlAttributes(this.playPauseDiv, 'ima-play-pause-div');
90214    this.addClass(this.playPauseDiv, 'ima-playing');
90215    this.playPauseDiv.addEventListener('click', this.onAdPlayPauseClick.bind(this), false);
90216    this.assignControlAttributes(this.muteDiv, 'ima-mute-div');
90217    this.addClass(this.muteDiv, 'ima-non-muted');
90218    this.muteDiv.addEventListener('click', this.onAdMuteClick.bind(this), false);
90219    this.assignControlAttributes(this.sliderDiv, 'ima-slider-div');
90220    this.sliderDiv.addEventListener('mousedown', this.onAdVolumeSliderMouseDown.bind(this), false);
90221
90222    // Hide volume slider controls on iOS as they aren't supported.
90223    if (this.controller.getIsIos()) {
90224      this.sliderDiv.style.display = 'none';
90225    }
90226    this.assignControlAttributes(this.sliderLevelDiv, 'ima-slider-level-div');
90227    this.assignControlAttributes(this.fullscreenDiv, 'ima-fullscreen-div');
90228    this.addClass(this.fullscreenDiv, 'ima-non-fullscreen');
90229    this.fullscreenDiv.addEventListener('click', this.onAdFullscreenClick.bind(this), false);
90230    this.adContainerDiv.appendChild(this.controlsDiv);
90231    this.controlsDiv.appendChild(this.countdownDiv);
90232    this.controlsDiv.appendChild(this.seekBarDiv);
90233    this.controlsDiv.appendChild(this.playPauseDiv);
90234    this.controlsDiv.appendChild(this.muteDiv);
90235    this.controlsDiv.appendChild(this.sliderDiv);
90236    this.controlsDiv.appendChild(this.fullscreenDiv);
90237    this.seekBarDiv.appendChild(this.progressDiv);
90238    this.sliderDiv.appendChild(this.sliderLevelDiv);
90239  };
90240
90241  /**
90242   * Listener for clicks on the play/pause button during ad playback.
90243   */
90244  AdUi.prototype.onAdPlayPauseClick = function () {
90245    this.controller.onAdPlayPauseClick();
90246  };
90247
90248  /**
90249   * Listener for clicks on the play/pause button during ad playback.
90250   */
90251  AdUi.prototype.onAdMuteClick = function () {
90252    this.controller.onAdMuteClick();
90253  };
90254
90255  /**
90256   * Listener for clicks on the fullscreen button during ad playback.
90257   */
90258  AdUi.prototype.onAdFullscreenClick = function () {
90259    this.controller.toggleFullscreen();
90260  };
90261
90262  /**
90263   * Show pause and hide play button
90264   */
90265  AdUi.prototype.onAdsPaused = function () {
90266    this.controller.sdkImpl.adPlaying = false;
90267    this.addClass(this.playPauseDiv, 'ima-paused');
90268    this.removeClass(this.playPauseDiv, 'ima-playing');
90269    this.showAdControls();
90270  };
90271
90272  /**
90273   * Show pause and hide play button
90274   */
90275  AdUi.prototype.onAdsResumed = function () {
90276    this.onAdsPlaying();
90277    this.showAdControls();
90278  };
90279
90280  /**
90281   * Show play and hide pause button
90282   */
90283  AdUi.prototype.onAdsPlaying = function () {
90284    this.controller.sdkImpl.adPlaying = true;
90285    this.addClass(this.playPauseDiv, 'ima-playing');
90286    this.removeClass(this.playPauseDiv, 'ima-paused');
90287  };
90288
90289  /**
90290   * Takes data from the controller to update the UI.
90291   *
90292   * @param {number} currentTime Current time of the ad.
90293   * @param {number} remainingTime Remaining time of the ad.
90294   * @param {number} duration Duration of the ad.
90295   * @param {number} adPosition Index of the ad in the pod.
90296   * @param {number} totalAds Total number of ads in the pod.
90297   */
90298  AdUi.prototype.updateAdUi = function (currentTime, remainingTime, duration, adPosition, totalAds) {
90299    // Update countdown timer data
90300    var remainingMinutes = Math.floor(remainingTime / 60);
90301    var remainingSeconds = Math.floor(remainingTime % 60);
90302    if (remainingSeconds.toString().length < 2) {
90303      remainingSeconds = '0' + remainingSeconds;
90304    }
90305    var podCount = ': ';
90306    if (totalAds > 1) {
90307      podCount = ' (' + adPosition + ' ' + this.controller.getSettings().adLabelNofN + ' ' + totalAds + '): ';
90308    }
90309    this.countdownDiv.innerHTML = this.controller.getSettings().adLabel + podCount + remainingMinutes + ':' + remainingSeconds;
90310
90311    // Update UI
90312    var playProgressRatio = currentTime / duration;
90313    var playProgressPercent = playProgressRatio * 100;
90314    this.progressDiv.style.width = playProgressPercent + '%';
90315  };
90316
90317  /**
90318   * Handles UI changes when the ad is unmuted.
90319   */
90320  AdUi.prototype.unmute = function () {
90321    this.addClass(this.muteDiv, 'ima-non-muted');
90322    this.removeClass(this.muteDiv, 'ima-muted');
90323    this.sliderLevelDiv.style.width = this.controller.getPlayerVolume() * 100 + '%';
90324  };
90325
90326  /**
90327   * Handles UI changes when the ad is muted.
90328   */
90329  AdUi.prototype.mute = function () {
90330    this.addClass(this.muteDiv, 'ima-muted');
90331    this.removeClass(this.muteDiv, 'ima-non-muted');
90332    this.sliderLevelDiv.style.width = '0%';
90333  };
90334
90335  /*
90336   * Listener for mouse down events during ad playback. Used for volume.
90337   */
90338  AdUi.prototype.onAdVolumeSliderMouseDown = function () {
90339    document.addEventListener('mouseup', this.boundOnMouseUp, false);
90340    document.addEventListener('mousemove', this.boundOnMouseMove, false);
90341  };
90342
90343  /*
90344   * Mouse movement listener used for volume slider.
90345   */
90346  AdUi.prototype.onMouseMove = function (event) {
90347    this.changeVolume(event);
90348  };
90349
90350  /*
90351   * Mouse release listener used for volume slider.
90352   */
90353  AdUi.prototype.onMouseUp = function (event) {
90354    this.changeVolume(event);
90355    document.removeEventListener('mouseup', this.boundOnMouseUp);
90356    document.removeEventListener('mousemove', this.boundOnMouseMove);
90357  };
90358
90359  /*
90360   * Utility function to set volume and associated UI
90361   */
90362  AdUi.prototype.changeVolume = function (event) {
90363    var percent = (event.clientX - this.sliderDiv.getBoundingClientRect().left) / this.sliderDiv.offsetWidth;
90364    percent *= 100;
90365    // Bounds value 0-100 if mouse is outside slider region.
90366    percent = Math.min(Math.max(percent, 0), 100);
90367    this.sliderLevelDiv.style.width = percent + '%';
90368    if (this.percent == 0) {
90369      this.addClass(this.muteDiv, 'ima-muted');
90370      this.removeClass(this.muteDiv, 'ima-non-muted');
90371    } else {
90372      this.addClass(this.muteDiv, 'ima-non-muted');
90373      this.removeClass(this.muteDiv, 'ima-muted');
90374    }
90375    this.controller.setVolume(percent / 100); // 0-1
90376  };
90377
90378  /**
90379   * Show the ad container.
90380   */
90381  AdUi.prototype.showAdContainer = function () {
90382    this.adContainerDiv.classList.remove('hide-ad-container');
90383  };
90384
90385  /**
90386   * Hide the ad container.
90387   */
90388  AdUi.prototype.hideAdContainer = function () {
90389    this.adContainerDiv.classList.add('hide-ad-container');
90390  };
90391
90392  /**
90393   * Handles clicks on the ad container.
90394   */
90395  AdUi.prototype.onAdContainerClick = function () {
90396    if (this.isAdNonlinear) {
90397      this.controller.togglePlayback();
90398    }
90399  };
90400
90401  /**
90402   * Resets the state of the ad ui.
90403   */
90404  AdUi.prototype.reset = function () {
90405    this.hideAdContainer();
90406  };
90407
90408  /**
90409   * Handles ad errors.
90410   */
90411  AdUi.prototype.onAdError = function () {
90412    this.hideAdContainer();
90413  };
90414
90415  /**
90416   * Handles ad break starting.
90417   *
90418   * @param {Object} adEvent The event fired by the IMA SDK.
90419   */
90420  AdUi.prototype.onAdBreakStart = function (adEvent) {
90421    this.showAdContainer();
90422    var contentType = adEvent.getAd().getContentType();
90423    if (contentType === 'application/javascript' && !this.controller.getSettings().showControlsForJSAds) {
90424      this.controlsDiv.style.display = 'none';
90425    } else {
90426      this.controlsDiv.style.display = 'block';
90427    }
90428    this.onAdsPlaying();
90429    // Start with the ad controls minimized.
90430    this.hideAdControls();
90431  };
90432
90433  /**
90434   * Handles ad break ending.
90435   */
90436  AdUi.prototype.onAdBreakEnd = function () {
90437    var currentAd = this.controller.getCurrentAd();
90438    if (currentAd == null ||
90439    // hide for post-roll only playlist
90440    currentAd.isLinear()) {
90441      // don't hide for non-linear ads
90442      this.hideAdContainer();
90443    }
90444    this.controlsDiv.style.display = 'none';
90445    this.countdownDiv.innerHTML = '';
90446  };
90447
90448  /**
90449   * Handles when all ads have finished playing.
90450   */
90451  AdUi.prototype.onAllAdsCompleted = function () {
90452    this.hideAdContainer();
90453  };
90454
90455  /**
90456   * Handles when a linear ad starts.
90457   */
90458  AdUi.prototype.onLinearAdStart = function () {
90459    // Don't bump container when controls are shown
90460    this.removeClass(this.adContainerDiv, 'bumpable-ima-ad-container');
90461    this.isAdNonlinear = false;
90462  };
90463
90464  /**
90465   * Handles when a non-linear ad starts.
90466   */
90467  AdUi.prototype.onNonLinearAdLoad = function () {
90468    // Bump container when controls are shown
90469    this.addClass(this.adContainerDiv, 'bumpable-ima-ad-container');
90470    this.isAdNonlinear = true;
90471  };
90472  AdUi.prototype.onPlayerEnterFullscreen = function () {
90473    this.addClass(this.fullscreenDiv, 'ima-fullscreen');
90474    this.removeClass(this.fullscreenDiv, 'ima-non-fullscreen');
90475  };
90476  AdUi.prototype.onPlayerExitFullscreen = function () {
90477    this.addClass(this.fullscreenDiv, 'ima-non-fullscreen');
90478    this.removeClass(this.fullscreenDiv, 'ima-fullscreen');
90479  };
90480
90481  /**
90482   * Called when the player volume changes.
90483   *
90484   * @param {number} volume The new player volume.
90485   */
90486  AdUi.prototype.onPlayerVolumeChanged = function (volume) {
90487    if (volume == 0) {
90488      this.addClass(this.muteDiv, 'ima-muted');
90489      this.removeClass(this.muteDiv, 'ima-non-muted');
90490      this.sliderLevelDiv.style.width = '0%';
90491    } else {
90492      this.addClass(this.muteDiv, 'ima-non-muted');
90493      this.removeClass(this.muteDiv, 'ima-muted');
90494      this.sliderLevelDiv.style.width = volume * 100 + '%';
90495    }
90496  };
90497
90498  /**
90499   * Shows ad controls on mouseover.
90500   */
90501  AdUi.prototype.showAdControls = function () {
90502    var _this$controller$getS = this.controller.getSettings(),
90503      disableAdControls = _this$controller$getS.disableAdControls;
90504    if (!disableAdControls) {
90505      this.addClass(this.controlsDiv, 'ima-controls-div-showing');
90506    }
90507  };
90508
90509  /**
90510   * Hide the ad controls.
90511   */
90512  AdUi.prototype.hideAdControls = function () {
90513    this.removeClass(this.controlsDiv, 'ima-controls-div-showing');
90514  };
90515
90516  /**
90517   * Assigns the unique id and class names to the given element as well as the
90518   * style class.
90519   * @param {HTMLElement} element Element that needs the controlName assigned.
90520   * @param {string} controlName Control name to assign.
90521   */
90522  AdUi.prototype.assignControlAttributes = function (element, controlName) {
90523    element.id = this.controlPrefix + controlName;
90524    element.className = this.controlPrefix + controlName + ' ' + controlName;
90525  };
90526
90527  /**
90528   * Returns a regular expression to test a string for the given className.
90529   *
90530   * @param {string} className The name of the class.
90531   * @return {RegExp} The regular expression used to test for that class.
90532   */
90533  AdUi.prototype.getClassRegexp = function (className) {
90534    // Matches on
90535    // (beginning of string OR NOT word char)
90536    // classname
90537    // (negative lookahead word char OR end of string)
90538    return new RegExp('(^|[^A-Za-z-])' + className + '((?![A-Za-z-])|$)', 'gi');
90539  };
90540
90541  /**
90542   * Returns whether or not the provided element has the provied class in its
90543   * className.
90544   * @param {HTMLElement} element Element to tes.t
90545   * @param {string} className Class to look for.
90546   * @return {boolean} True if element has className in class list. False
90547   *     otherwise.
90548   */
90549  AdUi.prototype.elementHasClass = function (element, className) {
90550    var classRegexp = this.getClassRegexp(className);
90551    return classRegexp.test(element.className);
90552  };
90553
90554  /**
90555   * Adds a class to the given element if it doesn't already have the class
90556   * @param {HTMLElement} element Element to which the class will be added.
90557   * @param {string} classToAdd Class to add.
90558   */
90559  AdUi.prototype.addClass = function (element, classToAdd) {
90560    element.className = element.className.trim() + ' ' + classToAdd;
90561  };
90562
90563  /**
90564   * Removes a class from the given element if it has the given class
90565   *
90566   * @param {HTMLElement} element Element from which the class will be removed.
90567   * @param {string} classToRemove Class to remove.
90568   */
90569  AdUi.prototype.removeClass = function (element, classToRemove) {
90570    var classRegexp = this.getClassRegexp(classToRemove);
90571    element.className = element.className.trim().replace(classRegexp, '');
90572  };
90573
90574  /**
90575   * @return {HTMLElement} The div for the ad container.
90576   */
90577  AdUi.prototype.getAdContainerDiv = function () {
90578    return this.adContainerDiv;
90579  };
90580
90581  /**
90582   * Changes the flag to show or hide the ad countdown timer.
90583   *
90584   * @param {boolean} showCountdownIn Show or hide the countdown timer.
90585   */
90586  AdUi.prototype.setShowCountdown = function (showCountdownIn) {
90587    this.showCountdown = showCountdownIn;
90588    this.countdownDiv.style.display = this.showCountdown ? 'block' : 'none';
90589  };
90590
90591  var version = "2.6.0";
90592  var pkg = {
90593  	version: version};
90594
90595  /**
90596   * Implementation of the IMA SDK for the plugin.
90597   *
90598   * @param {Object} controller Reference to the parent controller.
90599   *
90600   * @constructor
90601   * @struct
90602   * @final
90603   */
90604  var SdkImpl$1 = function SdkImpl(controller) {
90605    /**
90606     * Plugin controller.
90607     */
90608    this.controller = controller;
90609
90610    /**
90611     * IMA SDK AdDisplayContainer.
90612     */
90613    this.adDisplayContainer = null;
90614
90615    /**
90616     * True if the AdDisplayContainer has been initialized. False otherwise.
90617     */
90618    this.adDisplayContainerInitialized = false;
90619
90620    /**
90621     * IMA SDK AdsLoader
90622     */
90623    this.adsLoader = null;
90624
90625    /**
90626     * IMA SDK AdsManager
90627     */
90628    this.adsManager = null;
90629
90630    /**
90631     * IMA SDK AdsRenderingSettings.
90632     */
90633    this.adsRenderingSettings = null;
90634
90635    /**
90636     * VAST, VMAP, or ad rules response. Used in lieu of fetching a response
90637     * from an ad tag URL.
90638     */
90639    this.adsResponse = null;
90640
90641    /**
90642     * Current IMA SDK Ad.
90643     */
90644    this.currentAd = null;
90645
90646    /**
90647     * Timer used to track ad progress.
90648     */
90649    this.adTrackingTimer = null;
90650
90651    /**
90652     * True if ALL_ADS_COMPLETED has fired, false until then.
90653     */
90654    this.allAdsCompleted = false;
90655
90656    /**
90657     * True if ads are currently displayed, false otherwise.
90658     * True regardless of ad pause state if an ad is currently being displayed.
90659     */
90660    this.adsActive = false;
90661
90662    /**
90663     * True if ad is currently playing, false if ad is paused or ads are not
90664     * currently displayed.
90665     */
90666    this.adPlaying = false;
90667
90668    /**
90669     * True if the ad is muted, false otherwise.
90670     */
90671    this.adMuted = false;
90672
90673    /**
90674     * Listener to be called to trigger manual ad break playback.
90675     */
90676    this.adBreakReadyListener = undefined;
90677
90678    /**
90679     * Tracks whether or not we have already called adsLoader.contentComplete().
90680     */
90681    this.contentCompleteCalled = false;
90682
90683    /**
90684     * True if the ad has timed out.
90685     */
90686    this.isAdTimedOut = false;
90687
90688    /**
90689     * Stores the dimensions for the ads manager.
90690     */
90691    this.adsManagerDimensions = {
90692      width: 0,
90693      height: 0
90694    };
90695
90696    /**
90697     * Boolean flag to enable manual ad break playback.
90698     */
90699    this.autoPlayAdBreaks = true;
90700    if (this.controller.getSettings().autoPlayAdBreaks === false) {
90701      this.autoPlayAdBreaks = false;
90702    }
90703
90704    // Set SDK settings from plugin settings.
90705    if (this.controller.getSettings().locale) {
90706      /* eslint no-undef: 'error' */
90707      /* global google */
90708      google.ima.settings.setLocale(this.controller.getSettings().locale);
90709    }
90710    if (this.controller.getSettings().disableFlashAds) {
90711      google.ima.settings.setDisableFlashAds(this.controller.getSettings().disableFlashAds);
90712    }
90713    if (this.controller.getSettings().disableCustomPlaybackForIOS10Plus) {
90714      google.ima.settings.setDisableCustomPlaybackForIOS10Plus(this.controller.getSettings().disableCustomPlaybackForIOS10Plus);
90715    }
90716    if (this.controller.getSettings().ppid) {
90717      google.ima.settings.setPpid(this.controller.getSettings().ppid);
90718    }
90719    if (this.controller.getSettings().featureFlags) {
90720      google.ima.settings.setFeatureFlags(this.controller.getSettings().featureFlags);
90721    }
90722  };
90723
90724  /**
90725   * Creates and initializes the IMA SDK objects.
90726   */
90727  SdkImpl$1.prototype.initAdObjects = function () {
90728    this.adDisplayContainer = new google.ima.AdDisplayContainer(this.controller.getAdContainerDiv(), this.controller.getContentPlayer());
90729    this.adsLoader = new google.ima.AdsLoader(this.adDisplayContainer);
90730    this.adsLoader.getSettings().setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED);
90731    if (this.controller.getSettings().vpaidAllowed == false) {
90732      this.adsLoader.getSettings().setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.DISABLED);
90733    }
90734    if (this.controller.getSettings().vpaidMode !== undefined) {
90735      this.adsLoader.getSettings().setVpaidMode(this.controller.getSettings().vpaidMode);
90736    }
90737    if (this.controller.getSettings().locale) {
90738      this.adsLoader.getSettings().setLocale(this.controller.getSettings().locale);
90739    }
90740    if (this.controller.getSettings().numRedirects) {
90741      this.adsLoader.getSettings().setNumRedirects(this.controller.getSettings().numRedirects);
90742    }
90743    if (this.controller.getSettings().sessionId) {
90744      this.adsLoader.getSettings().setSessionId(this.controller.getSettings().sessionId);
90745    }
90746    this.adsLoader.getSettings().setPlayerType('videojs-ima');
90747    this.adsLoader.getSettings().setPlayerVersion(pkg.version);
90748    this.adsLoader.getSettings().setAutoPlayAdBreaks(this.autoPlayAdBreaks);
90749    this.adsLoader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, this.onAdsManagerLoaded.bind(this), false);
90750    this.adsLoader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, this.onAdsLoaderError.bind(this), false);
90751    this.controller.playerWrapper.vjsPlayer.trigger({
90752      type: 'ads-loader',
90753      adsLoader: this.adsLoader
90754    });
90755  };
90756
90757  /**
90758   * Creates the AdsRequest and request ads through the AdsLoader.
90759   */
90760  SdkImpl$1.prototype.requestAds = function () {
90761    var adsRequest = new google.ima.AdsRequest();
90762    if (this.controller.getSettings().adTagUrl) {
90763      adsRequest.adTagUrl = this.controller.getSettings().adTagUrl;
90764    } else {
90765      adsRequest.adsResponse = this.controller.getSettings().adsResponse;
90766    }
90767    if (this.controller.getSettings().forceNonLinearFullSlot) {
90768      adsRequest.forceNonLinearFullSlot = true;
90769    }
90770    if (this.controller.getSettings().vastLoadTimeout) {
90771      adsRequest.vastLoadTimeout = this.controller.getSettings().vastLoadTimeout;
90772    }
90773    if (this.controller.getSettings().omidMode) {
90774      window.console.warn('The additional setting `omidMode` has been removed. ' + 'Use `omidVendorAccess` instead.');
90775    }
90776    if (this.controller.getSettings().omidVendorAccess) {
90777      adsRequest.omidAccessModeRules = {};
90778      var omidVendorValues = this.controller.getSettings().omidVendorAccess;
90779      Object.keys(omidVendorValues).forEach(function (vendorKey) {
90780        adsRequest.omidAccessModeRules[vendorKey] = omidVendorValues[vendorKey];
90781      });
90782    }
90783    adsRequest.linearAdSlotWidth = this.controller.getPlayerWidth();
90784    adsRequest.linearAdSlotHeight = this.controller.getPlayerHeight();
90785    adsRequest.nonLinearAdSlotWidth = this.controller.getSettings().nonLinearWidth || this.controller.getPlayerWidth();
90786    adsRequest.nonLinearAdSlotHeight = this.controller.getSettings().nonLinearHeight || this.controller.getPlayerHeight();
90787    adsRequest.setAdWillAutoPlay(this.controller.adsWillAutoplay());
90788    adsRequest.setAdWillPlayMuted(this.controller.adsWillPlayMuted());
90789
90790    // Populate the adsRequestproperties with those provided in the AdsRequest
90791    // object in the settings.
90792    var providedAdsRequest = this.controller.getSettings().adsRequest;
90793    if (providedAdsRequest && _typeof(providedAdsRequest) === 'object') {
90794      Object.keys(providedAdsRequest).forEach(function (key) {
90795        adsRequest[key] = providedAdsRequest[key];
90796      });
90797    }
90798    this.adsLoader.requestAds(adsRequest);
90799    this.controller.playerWrapper.vjsPlayer.trigger({
90800      type: 'ads-request',
90801      AdsRequest: adsRequest
90802    });
90803  };
90804
90805  /**
90806   * Listener for the ADS_MANAGER_LOADED event. Creates the AdsManager,
90807   * sets up event listeners, and triggers the 'adsready' event for
90808   * videojs-ads-contrib.
90809   *
90810   * @param {google.ima.AdsManagerLoadedEvent} adsManagerLoadedEvent Fired when
90811   *     the AdsManager loads.
90812   */
90813  SdkImpl$1.prototype.onAdsManagerLoaded = function (adsManagerLoadedEvent) {
90814    this.createAdsRenderingSettings();
90815    this.adsManager = adsManagerLoadedEvent.getAdsManager(this.controller.getContentPlayheadTracker(), this.adsRenderingSettings);
90816    this.adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, this.onAdError.bind(this));
90817    this.adsManager.addEventListener(google.ima.AdEvent.Type.AD_BREAK_READY, this.onAdBreakReady.bind(this));
90818    this.adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, this.onContentPauseRequested.bind(this));
90819    this.adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, this.onContentResumeRequested.bind(this));
90820    this.adsManager.addEventListener(google.ima.AdEvent.Type.ALL_ADS_COMPLETED, this.onAllAdsCompleted.bind(this));
90821    this.adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, this.onAdLoaded.bind(this));
90822    this.adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, this.onAdStarted.bind(this));
90823    this.adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, this.onAdComplete.bind(this));
90824    this.adsManager.addEventListener(google.ima.AdEvent.Type.SKIPPED, this.onAdComplete.bind(this));
90825    this.adsManager.addEventListener(google.ima.AdEvent.Type.LOG, this.onAdLog.bind(this));
90826    this.adsManager.addEventListener(google.ima.AdEvent.Type.PAUSED, this.onAdPaused.bind(this));
90827    this.adsManager.addEventListener(google.ima.AdEvent.Type.RESUMED, this.onAdResumed.bind(this));
90828    this.controller.playerWrapper.vjsPlayer.trigger({
90829      type: 'ads-manager',
90830      adsManager: this.adsManager
90831    });
90832    if (!this.autoPlayAdBreaks) {
90833      this.initAdsManager();
90834    }
90835    var _this$controller$getS = this.controller.getSettings(),
90836      preventLateAdStart = _this$controller$getS.preventLateAdStart;
90837    if (!preventLateAdStart) {
90838      this.controller.onAdsReady();
90839    } else if (preventLateAdStart && !this.isAdTimedOut) {
90840      this.controller.onAdsReady();
90841    }
90842    if (this.controller.getSettings().adsManagerLoadedCallback) {
90843      this.controller.getSettings().adsManagerLoadedCallback();
90844    }
90845  };
90846
90847  /**
90848   * Listener for errors fired by the AdsLoader.
90849   * @param {google.ima.AdErrorEvent} event The error event thrown by the
90850   *     AdsLoader. See
90851   *     https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/reference/js/google.ima.AdError#.Type
90852   */
90853  SdkImpl$1.prototype.onAdsLoaderError = function (event) {
90854    window.console.warn('AdsLoader error: ' + event.getError());
90855    this.controller.onErrorLoadingAds(event);
90856    if (this.adsManager) {
90857      this.adsManager.destroy();
90858    }
90859  };
90860
90861  /**
90862   * Initialize the ads manager.
90863   */
90864  SdkImpl$1.prototype.initAdsManager = function () {
90865    try {
90866      var initWidth = this.controller.getPlayerWidth();
90867      var initHeight = this.controller.getPlayerHeight();
90868      this.adsManagerDimensions.width = initWidth;
90869      this.adsManagerDimensions.height = initHeight;
90870      this.adsManager.init(initWidth, initHeight);
90871      this.adsManager.setVolume(this.controller.getPlayerVolume());
90872      this.initializeAdDisplayContainer();
90873    } catch (adError) {
90874      this.onAdError(adError);
90875    }
90876  };
90877
90878  /**
90879   * Create AdsRenderingSettings for the IMA SDK.
90880   */
90881  SdkImpl$1.prototype.createAdsRenderingSettings = function () {
90882    this.adsRenderingSettings = new google.ima.AdsRenderingSettings();
90883    this.adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true;
90884    if (this.controller.getSettings().adsRenderingSettings) {
90885      for (var setting in this.controller.getSettings().adsRenderingSettings) {
90886        if (setting !== '') {
90887          this.adsRenderingSettings[setting] = this.controller.getSettings().adsRenderingSettings[setting];
90888        }
90889      }
90890    }
90891  };
90892
90893  /**
90894   * Listener for errors thrown by the AdsManager.
90895   * @param {google.ima.AdErrorEvent} adErrorEvent The error event thrown by
90896   *     the AdsManager.
90897   */
90898  SdkImpl$1.prototype.onAdError = function (adErrorEvent) {
90899    var errorMessage = adErrorEvent.getError !== undefined ? adErrorEvent.getError() : adErrorEvent.stack;
90900    window.console.warn('Ad error: ' + errorMessage);
90901    this.adsManager.destroy();
90902    this.controller.onAdError(adErrorEvent);
90903
90904    // reset these so consumers don't think we are still in an ad break,
90905    // but reset them after any prior cleanup happens
90906    this.adsActive = false;
90907    this.adPlaying = false;
90908  };
90909
90910  /**
90911   * Listener for AD_BREAK_READY. Passes event on to publisher's listener.
90912   * @param {google.ima.AdEvent} adEvent AdEvent thrown by the AdsManager.
90913   */
90914  SdkImpl$1.prototype.onAdBreakReady = function (adEvent) {
90915    this.adBreakReadyListener(adEvent);
90916  };
90917
90918  /**
90919   * Pauses the content video and displays the ad container so ads can play.
90920   * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager.
90921   */
90922  SdkImpl$1.prototype.onContentPauseRequested = function (adEvent) {
90923    this.adsActive = true;
90924    this.adPlaying = true;
90925    this.controller.onAdBreakStart(adEvent);
90926  };
90927
90928  /**
90929   * Resumes content video and hides the ad container.
90930   * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager.
90931   */
90932  SdkImpl$1.prototype.onContentResumeRequested = function (adEvent) {
90933    this.adsActive = false;
90934    this.adPlaying = false;
90935    this.controller.onAdBreakEnd();
90936    // Hide controls in case of future non-linear ads. They'll be unhidden in
90937    // content_pause_requested.
90938  };
90939
90940  /**
90941   * Records that ads have completed and calls contentAndAdsEndedListeners
90942   * if content is also complete.
90943   * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager.
90944   */
90945  SdkImpl$1.prototype.onAllAdsCompleted = function (adEvent) {
90946    this.allAdsCompleted = true;
90947    this.controller.onAllAdsCompleted();
90948  };
90949
90950  /**
90951   * Starts the content video when a non-linear ad is loaded.
90952   * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager.
90953   */
90954  SdkImpl$1.prototype.onAdLoaded = function (adEvent) {
90955    if (!adEvent.getAd().isLinear()) {
90956      this.controller.onNonLinearAdLoad();
90957      this.controller.playContent();
90958    }
90959  };
90960
90961  /**
90962   * Starts the interval timer to check the current ad time when an ad starts
90963   * playing.
90964   * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager.
90965   */
90966  SdkImpl$1.prototype.onAdStarted = function (adEvent) {
90967    this.currentAd = adEvent.getAd();
90968    if (this.currentAd.isLinear()) {
90969      this.adTrackingTimer = setInterval(this.onAdPlayheadTrackerInterval.bind(this), 250);
90970      this.controller.onLinearAdStart();
90971    } else {
90972      this.controller.onNonLinearAdStart();
90973    }
90974  };
90975
90976  /**
90977   * Handles an ad click. Puts the player UI in a paused state.
90978   */
90979  SdkImpl$1.prototype.onAdPaused = function () {
90980    this.controller.onAdsPaused();
90981  };
90982
90983  /**
90984   * Syncs controls when an ad resumes.
90985   * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager.
90986   */
90987  SdkImpl$1.prototype.onAdResumed = function (adEvent) {
90988    this.controller.onAdsResumed();
90989  };
90990
90991  /**
90992   * Clears the interval timer for current ad time when an ad completes.
90993   */
90994  SdkImpl$1.prototype.onAdComplete = function () {
90995    if (this.currentAd.isLinear()) {
90996      clearInterval(this.adTrackingTimer);
90997    }
90998  };
90999
91000  /**
91001   * Handles ad log messages.
91002   * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager.
91003   */
91004  SdkImpl$1.prototype.onAdLog = function (adEvent) {
91005    this.controller.onAdLog(adEvent);
91006  };
91007
91008  /**
91009   * Gets the current time and duration of the ad and calls the method to
91010   * update the ad UI.
91011   */
91012  SdkImpl$1.prototype.onAdPlayheadTrackerInterval = function () {
91013    if (this.adsManager === null) return;
91014    var remainingTime = this.adsManager.getRemainingTime();
91015    var duration = this.currentAd.getDuration();
91016    var currentTime = duration - remainingTime;
91017    currentTime = currentTime > 0 ? currentTime : 0;
91018    var totalAds = 0;
91019    var adPosition;
91020    if (this.currentAd.getAdPodInfo()) {
91021      adPosition = this.currentAd.getAdPodInfo().getAdPosition();
91022      totalAds = this.currentAd.getAdPodInfo().getTotalAds();
91023    }
91024    this.controller.onAdPlayheadUpdated(currentTime, remainingTime, duration, adPosition, totalAds);
91025  };
91026
91027  /**
91028   * Called by the player wrapper when content completes.
91029   */
91030  SdkImpl$1.prototype.onContentComplete = function () {
91031    if (this.adsLoader) {
91032      this.adsLoader.contentComplete();
91033      this.contentCompleteCalled = true;
91034    }
91035    if (this.adsManager && this.adsManager.getCuePoints() && !this.adsManager.getCuePoints().includes(-1) || !this.adsManager) {
91036      this.controller.onNoPostroll();
91037    }
91038    if (this.allAdsCompleted) {
91039      this.controller.onContentAndAdsCompleted();
91040    }
91041  };
91042
91043  /**
91044   * Called when the player is disposed.
91045   */
91046  SdkImpl$1.prototype.onPlayerDisposed = function () {
91047    if (this.adTrackingTimer) {
91048      clearInterval(this.adTrackingTimer);
91049    }
91050    if (this.adsManager) {
91051      this.adsManager.destroy();
91052      this.adsManager = null;
91053    }
91054  };
91055  SdkImpl$1.prototype.onPlayerReadyForPreroll = function () {
91056    if (this.autoPlayAdBreaks) {
91057      this.initAdsManager();
91058      try {
91059        this.controller.showAdContainer();
91060        // Sync ad volume with content volume.
91061        this.adsManager.setVolume(this.controller.getPlayerVolume());
91062        this.adsManager.start();
91063      } catch (adError) {
91064        this.onAdError(adError);
91065      }
91066    }
91067  };
91068  SdkImpl$1.prototype.onAdTimeout = function () {
91069    this.isAdTimedOut = true;
91070  };
91071  SdkImpl$1.prototype.onPlayerReady = function () {
91072    this.initAdObjects();
91073    if ((this.controller.getSettings().adTagUrl || this.controller.getSettings().adsResponse) && this.controller.getSettings().requestMode === 'onLoad') {
91074      this.requestAds();
91075    }
91076  };
91077  SdkImpl$1.prototype.onPlayerEnterFullscreen = function () {
91078    if (this.adsManager) {
91079      this.adsManager.resize(window.screen.width, window.screen.height);
91080    }
91081  };
91082  SdkImpl$1.prototype.onPlayerExitFullscreen = function () {
91083    if (this.adsManager) {
91084      this.adsManager.resize(this.controller.getPlayerWidth(), this.controller.getPlayerHeight());
91085    }
91086  };
91087
91088  /**
91089   * Called when the player volume changes.
91090   *
91091   * @param {number} volume The new player volume.
91092   */
91093  SdkImpl$1.prototype.onPlayerVolumeChanged = function (volume) {
91094    if (this.adsManager) {
91095      this.adsManager.setVolume(volume);
91096    }
91097    if (volume == 0) {
91098      this.adMuted = true;
91099    } else {
91100      this.adMuted = false;
91101    }
91102  };
91103
91104  /**
91105   * Called when the player wrapper detects that the player has been resized.
91106   *
91107   * @param {number} width The post-resize width of the player.
91108   * @param {number} height The post-resize height of the player.
91109   */
91110  SdkImpl$1.prototype.onPlayerResize = function (width, height) {
91111    if (this.adsManager) {
91112      this.adsManagerDimensions.width = width;
91113      this.adsManagerDimensions.height = height;
91114      /* eslint no-undef: 'error' */
91115      this.adsManager.resize(width, height);
91116    }
91117  };
91118
91119  /**
91120   * @return {Object} The current ad.
91121   */
91122  SdkImpl$1.prototype.getCurrentAd = function () {
91123    return this.currentAd;
91124  };
91125
91126  /**
91127   * Listener JSDoc for ESLint. This listener can be passed to
91128   * setAdBreakReadyListener.
91129   * @callback listener
91130   */
91131
91132  /**
91133   * Sets the listener to be called to trigger manual ad break playback.
91134   * @param {listener} listener The listener to be called to trigger manual ad
91135   *     break playback.
91136   */
91137  SdkImpl$1.prototype.setAdBreakReadyListener = function (listener) {
91138    this.adBreakReadyListener = listener;
91139  };
91140
91141  /**
91142   * @return {boolean} True if an ad is currently playing. False otherwise.
91143   */
91144  SdkImpl$1.prototype.isAdPlaying = function () {
91145    return this.adPlaying;
91146  };
91147
91148  /**
91149   * @return {boolean} True if an ad is currently playing. False otherwise.
91150   */
91151  SdkImpl$1.prototype.isAdMuted = function () {
91152    return this.adMuted;
91153  };
91154
91155  /**
91156   * Pause ads.
91157   */
91158  SdkImpl$1.prototype.pauseAds = function () {
91159    this.adsManager.pause();
91160    this.adPlaying = false;
91161  };
91162
91163  /**
91164   * Resume ads.
91165   */
91166  SdkImpl$1.prototype.resumeAds = function () {
91167    this.adsManager.resume();
91168    this.adPlaying = true;
91169  };
91170
91171  /**
91172   * Unmute ads.
91173   */
91174  SdkImpl$1.prototype.unmute = function () {
91175    this.adsManager.setVolume(1);
91176    this.adMuted = false;
91177  };
91178
91179  /**
91180   * Mute ads.
91181   */
91182  SdkImpl$1.prototype.mute = function () {
91183    this.adsManager.setVolume(0);
91184    this.adMuted = true;
91185  };
91186
91187  /**
91188   * Set the volume of the ads. 0-1.
91189   *
91190   * @param {number} volume The new volume.
91191   */
91192  SdkImpl$1.prototype.setVolume = function (volume) {
91193    this.adsManager.setVolume(volume);
91194    if (volume == 0) {
91195      this.adMuted = true;
91196    } else {
91197      this.adMuted = false;
91198    }
91199  };
91200
91201  /**
91202   * Initializes the AdDisplayContainer. On mobile, this must be done as a
91203   * result of user action.
91204   */
91205  SdkImpl$1.prototype.initializeAdDisplayContainer = function () {
91206    if (this.adDisplayContainer) {
91207      if (!this.adDisplayContainerInitialized) {
91208        this.adDisplayContainer.initialize();
91209        this.adDisplayContainerInitialized = true;
91210      }
91211    }
91212  };
91213
91214  /**
91215   * Called by publishers in manual ad break playback mode to start an ad
91216   * break.
91217   */
91218  SdkImpl$1.prototype.playAdBreak = function () {
91219    if (!this.autoPlayAdBreaks) {
91220      this.controller.showAdContainer();
91221      // Sync ad volume with content volume.
91222      this.adsManager.setVolume(this.controller.getPlayerVolume());
91223      this.adsManager.start();
91224    }
91225  };
91226
91227  /**
91228   * Callback JSDoc for ESLint. This callback can be passed to addEventListener.
91229   * @callback callback
91230   */
91231
91232  /**
91233   * Adds an EventListener to the AdsManager. For a list of available events,
91234   * see
91235   * https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/reference/js/google.ima.AdEvent#.Type
91236   * @param {google.ima.AdEvent.Type} event The AdEvent.Type for which to
91237   *     listen.
91238   * @param {callback} callback The method to call when the event is fired.
91239   */
91240  SdkImpl$1.prototype.addEventListener = function (event, callback) {
91241    if (this.adsManager) {
91242      this.adsManager.addEventListener(event, callback);
91243    }
91244  };
91245
91246  /**
91247   * Returns the instance of the AdsManager.
91248   * @return {google.ima.AdsManager} The AdsManager being used by the plugin.
91249   */
91250  SdkImpl$1.prototype.getAdsManager = function () {
91251    return this.adsManager;
91252  };
91253
91254  /**
91255   * Reset the SDK implementation.
91256   */
91257  SdkImpl$1.prototype.reset = function () {
91258    this.adsActive = false;
91259    this.adPlaying = false;
91260    if (this.adTrackingTimer) {
91261      // If this is called while an ad is playing, stop trying to get that
91262      // ad's current time.
91263      clearInterval(this.adTrackingTimer);
91264    }
91265    if (this.adsManager) {
91266      this.adsManager.destroy();
91267      this.adsManager = null;
91268    }
91269    if (this.adsLoader && !this.contentCompleteCalled) {
91270      this.adsLoader.contentComplete();
91271    }
91272    this.contentCompleteCalled = false;
91273    this.allAdsCompleted = false;
91274  };
91275
91276  /**
91277   * Copyright 2017 Google Inc.
91278   *
91279   * Licensed under the Apache License, Version 2.0 (the "License");
91280   * you may not use this file except in compliance with the License.
91281   * You may obtain a copy of the License at
91282   *
91283   *     http://www.apache.org/licenses/LICENSE-2.0
91284   *
91285   * Unless required by applicable law or agreed to in writing, software
91286   * distributed under the License is distributed on an "AS IS" BASIS,
91287   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
91288   * See the License for the specific language governing permissions and
91289   * limitations under the License.
91290   *
91291   * IMA SDK integration plugin for Video.js. For more information see
91292   * https://www.github.com/googleads/videojs-ima
91293   */
91294
91295  /**
91296   * The grand coordinator of the plugin. Facilitates communication between all
91297   * other plugin classes.
91298   *
91299   * @param {Object} player Instance of the video.js player.
91300   * @param {Object} options Options provided by the implementation.
91301   * @constructor
91302   * @struct
91303   * @final
91304   */
91305  var Controller = function Controller(player, options) {
91306    /**
91307     * Stores user-provided settings.
91308     * @type {Object}
91309     */
91310    this.settings = {};
91311
91312    /**
91313     * Content and ads ended listeners passed by the publisher to the plugin.
91314     * These will be called when the plugin detects that content *and all
91315     * ads* have completed. This differs from the contentEndedListeners in that
91316     * contentEndedListeners will fire between content ending and a post-roll
91317     * playing, whereas the contentAndAdsEndedListeners will fire after the
91318     * post-roll completes.
91319     */
91320    this.contentAndAdsEndedListeners = [];
91321
91322    /**
91323     * Whether or not we are running on a mobile platform.
91324     */
91325    this.isMobile = navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i) || navigator.userAgent.match(/Android/i);
91326
91327    /**
91328     * Whether or not we are running on an iOS platform.
91329     */
91330    this.isIos = navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i);
91331    this.initWithSettings(options);
91332
91333    /**
91334     * Stores contrib-ads default settings.
91335     */
91336    var contribAdsDefaults = {
91337      debug: this.settings.debug,
91338      timeout: this.settings.timeout,
91339      prerollTimeout: this.settings.prerollTimeout
91340    };
91341    var adsPluginSettings = Object.assign({}, contribAdsDefaults, options.contribAdsSettings || {});
91342    this.playerWrapper = new PlayerWrapper$1(player, adsPluginSettings, this);
91343    this.adUi = new AdUi(this);
91344    this.sdkImpl = new SdkImpl$1(this);
91345  };
91346  Controller.IMA_DEFAULTS = {
91347    adLabel: 'Advertisement',
91348    adLabelNofN: 'of',
91349    debug: false,
91350    disableAdControls: false,
91351    prerollTimeout: 1000,
91352    preventLateAdStart: false,
91353    requestMode: 'onLoad',
91354    showControlsForJSAds: true,
91355    timeout: 5000
91356  };
91357
91358  /**
91359   * Extends the settings to include user-provided settings.
91360   *
91361   * @param {Object} options Options to be used in initialization.
91362   */
91363  Controller.prototype.initWithSettings = function (options) {
91364    this.settings = Object.assign({}, Controller.IMA_DEFAULTS, options || {});
91365    this.warnAboutDeprecatedSettings();
91366
91367    // Default showing countdown timer to true.
91368    this.showCountdown = true;
91369    if (this.settings.showCountdown === false) {
91370      this.showCountdown = false;
91371    }
91372  };
91373
91374  /**
91375   * Logs console warnings when deprecated settings are used.
91376   */
91377  Controller.prototype.warnAboutDeprecatedSettings = function () {
91378    var _this = this;
91379    var deprecatedSettings = ['adWillAutoplay', 'adsWillAutoplay', 'adWillPlayMuted', 'adsWillPlayMuted'];
91380    deprecatedSettings.forEach(function (setting) {
91381      if (_this.settings[setting] !== undefined) {
91382        console.warn('WARNING: videojs.ima setting ' + setting + ' is deprecated');
91383      }
91384    });
91385  };
91386
91387  /**
91388   * Return the settings object.
91389   *
91390   * @return {Object} The settings object.
91391   */
91392  Controller.prototype.getSettings = function () {
91393    return this.settings;
91394  };
91395
91396  /**
91397   * Return whether or not we're in a mobile environment.
91398   *
91399   * @return {boolean} True if running on mobile, false otherwise.
91400   */
91401  Controller.prototype.getIsMobile = function () {
91402    return this.isMobile;
91403  };
91404
91405  /**
91406   * Return whether or not we're in an iOS environment.
91407   *
91408   * @return {boolean} True if running on iOS, false otherwise.
91409   */
91410  Controller.prototype.getIsIos = function () {
91411    return this.isIos;
91412  };
91413
91414  /**
91415   * Inject the ad container div into the DOM.
91416   *
91417   * @param{HTMLElement} adContainerDiv The ad container div.
91418   */
91419  Controller.prototype.injectAdContainerDiv = function (adContainerDiv) {
91420    this.playerWrapper.injectAdContainerDiv(adContainerDiv);
91421  };
91422
91423  /**
91424   * @return {HTMLElement} The div for the ad container.
91425   */
91426  Controller.prototype.getAdContainerDiv = function () {
91427    return this.adUi.getAdContainerDiv();
91428  };
91429
91430  /**
91431   * @return {Object} The content player.
91432   */
91433  Controller.prototype.getContentPlayer = function () {
91434    return this.playerWrapper.getContentPlayer();
91435  };
91436
91437  /**
91438   * Returns the content playhead tracker.
91439   *
91440   * @return {Object} The content playhead tracker.
91441   */
91442  Controller.prototype.getContentPlayheadTracker = function () {
91443    return this.playerWrapper.getContentPlayheadTracker();
91444  };
91445
91446  /**
91447   * Requests ads.
91448   */
91449  Controller.prototype.requestAds = function () {
91450    this.sdkImpl.requestAds();
91451  };
91452
91453  /**
91454   * Add or modify a setting.
91455   *
91456   * @param {string} key Key to modify
91457   * @param {Object} value Value to set at key.
91458   */
91459  Controller.prototype.setSetting = function (key, value) {
91460    this.settings[key] = value;
91461  };
91462
91463  /**
91464   * Called when there is an error loading ads.
91465   *
91466   * @param {Object} adErrorEvent The ad error event thrown by the IMA SDK.
91467   */
91468  Controller.prototype.onErrorLoadingAds = function (adErrorEvent) {
91469    this.adUi.onAdError();
91470    this.playerWrapper.onAdError(adErrorEvent);
91471  };
91472
91473  /**
91474   * Called by the ad UI when the play/pause button is clicked.
91475   */
91476  Controller.prototype.onAdPlayPauseClick = function () {
91477    if (this.sdkImpl.isAdPlaying()) {
91478      this.adUi.onAdsPaused();
91479      this.sdkImpl.pauseAds();
91480    } else {
91481      this.adUi.onAdsPlaying();
91482      this.sdkImpl.resumeAds();
91483    }
91484  };
91485
91486  /**
91487   * Called by the ad UI when the mute button is clicked.
91488   *
91489   */
91490  Controller.prototype.onAdMuteClick = function () {
91491    if (this.sdkImpl.isAdMuted()) {
91492      this.playerWrapper.unmute();
91493      this.adUi.unmute();
91494      this.sdkImpl.unmute();
91495    } else {
91496      this.playerWrapper.mute();
91497      this.adUi.mute();
91498      this.sdkImpl.mute();
91499    }
91500  };
91501
91502  /**
91503   * Set the volume of the player and ads. 0-1.
91504   *
91505   * @param {number} volume The new volume.
91506   */
91507  Controller.prototype.setVolume = function (volume) {
91508    this.playerWrapper.setVolume(volume);
91509    this.sdkImpl.setVolume(volume);
91510  };
91511
91512  /**
91513   * @return {number} The volume of the content player.
91514   */
91515  Controller.prototype.getPlayerVolume = function () {
91516    return this.playerWrapper.getVolume();
91517  };
91518
91519  /**
91520   * Toggle fullscreen state.
91521   */
91522  Controller.prototype.toggleFullscreen = function () {
91523    this.playerWrapper.toggleFullscreen();
91524  };
91525
91526  /**
91527   * Relays ad errors to the player wrapper.
91528   *
91529   * @param {Object} adErrorEvent The ad error event thrown by the IMA SDK.
91530   */
91531  Controller.prototype.onAdError = function (adErrorEvent) {
91532    this.adUi.onAdError();
91533    this.playerWrapper.onAdError(adErrorEvent);
91534  };
91535
91536  /**
91537   * Handles ad break starting.
91538   *
91539   * @param {Object} adEvent The event fired by the IMA SDK.
91540   */
91541  Controller.prototype.onAdBreakStart = function (adEvent) {
91542    this.playerWrapper.onAdBreakStart();
91543    this.adUi.onAdBreakStart(adEvent);
91544  };
91545
91546  /**
91547   * Show the ad container.
91548   */
91549  Controller.prototype.showAdContainer = function () {
91550    this.adUi.showAdContainer();
91551  };
91552
91553  /**
91554   * Handles ad break ending.
91555   */
91556  Controller.prototype.onAdBreakEnd = function () {
91557    this.playerWrapper.onAdBreakEnd();
91558    this.adUi.onAdBreakEnd();
91559  };
91560
91561  /**
91562   * Handles when all ads have finished playing.
91563   */
91564  Controller.prototype.onAllAdsCompleted = function () {
91565    this.adUi.onAllAdsCompleted();
91566    this.playerWrapper.onAllAdsCompleted();
91567  };
91568
91569  /**
91570   * Handles the SDK firing an ad paused event.
91571   */
91572  Controller.prototype.onAdsPaused = function () {
91573    this.adUi.onAdsPaused();
91574  };
91575
91576  /**
91577   * Handles the SDK firing an ad resumed event.
91578   */
91579  Controller.prototype.onAdsResumed = function () {
91580    this.adUi.onAdsResumed();
91581  };
91582
91583  /**
91584   * Takes data from the sdk impl and passes it to the ad UI to update the UI.
91585   *
91586   * @param {number} currentTime Current time of the ad.
91587   * @param {number} remainingTime Remaining time of the ad.
91588   * @param {number} duration Duration of the ad.
91589   * @param {number} adPosition Index of the ad in the pod.
91590   * @param {number} totalAds Total number of ads in the pod.
91591   */
91592  Controller.prototype.onAdPlayheadUpdated = function (currentTime, remainingTime, duration, adPosition, totalAds) {
91593    this.adUi.updateAdUi(currentTime, remainingTime, duration, adPosition, totalAds);
91594  };
91595
91596  /**
91597   * Handles ad log messages.
91598   * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the IMA SDK.
91599   */
91600  Controller.prototype.onAdLog = function (adEvent) {
91601    this.playerWrapper.onAdLog(adEvent);
91602  };
91603
91604  /**
91605   * @return {Object} The current ad.
91606   */
91607  Controller.prototype.getCurrentAd = function () {
91608    return this.sdkImpl.getCurrentAd();
91609  };
91610
91611  /**
91612   * Play content.
91613   */
91614  Controller.prototype.playContent = function () {
91615    this.playerWrapper.play();
91616  };
91617
91618  /**
91619   * Handles when a linear ad starts.
91620   */
91621  Controller.prototype.onLinearAdStart = function () {
91622    this.adUi.onLinearAdStart();
91623    this.playerWrapper.onAdStart();
91624  };
91625
91626  /**
91627   * Handles when a non-linear ad loads.
91628   */
91629  Controller.prototype.onNonLinearAdLoad = function () {
91630    this.adUi.onNonLinearAdLoad();
91631  };
91632
91633  /**
91634   * Handles when a non-linear ad starts.
91635   */
91636  Controller.prototype.onNonLinearAdStart = function () {
91637    this.adUi.onNonLinearAdLoad();
91638    this.playerWrapper.onAdStart();
91639  };
91640
91641  /**
91642   * Get the player width.
91643   *
91644   * @return {number} The width of the player.
91645   */
91646  Controller.prototype.getPlayerWidth = function () {
91647    return this.playerWrapper.getPlayerWidth();
91648  };
91649
91650  /**
91651   * Get the player height.
91652   *
91653   * @return {number} The height of the player.
91654   */
91655  Controller.prototype.getPlayerHeight = function () {
91656    return this.playerWrapper.getPlayerHeight();
91657  };
91658
91659  /**
91660   * Tells the player wrapper that ads are ready.
91661   */
91662  Controller.prototype.onAdsReady = function () {
91663    this.playerWrapper.onAdsReady();
91664  };
91665
91666  /**
91667   * Called when the player wrapper detects that the player has been resized.
91668   *
91669   * @param {number} width The post-resize width of the player.
91670   * @param {number} height The post-resize height of the player.
91671   */
91672  Controller.prototype.onPlayerResize = function (width, height) {
91673    this.sdkImpl.onPlayerResize(width, height);
91674  };
91675
91676  /**
91677   * Called by the player wrapper when content completes.
91678   */
91679  Controller.prototype.onContentComplete = function () {
91680    this.sdkImpl.onContentComplete();
91681  };
91682
91683  /**
91684   * Called by the player wrapper when it's time to play a post-roll but we don't
91685   * have one to play.
91686   */
91687  Controller.prototype.onNoPostroll = function () {
91688    this.playerWrapper.onNoPostroll();
91689  };
91690
91691  /**
91692   * Called when content and all ads have completed.
91693   */
91694  Controller.prototype.onContentAndAdsCompleted = function () {
91695    for (var index in this.contentAndAdsEndedListeners) {
91696      if (typeof this.contentAndAdsEndedListeners[index] === 'function') {
91697        this.contentAndAdsEndedListeners[index]();
91698      }
91699    }
91700  };
91701
91702  /**
91703   * Called when the player is disposed.
91704   */
91705  Controller.prototype.onPlayerDisposed = function () {
91706    this.contentAndAdsEndedListeners = [];
91707    this.sdkImpl.onPlayerDisposed();
91708  };
91709
91710  /**
91711   * Called when the player is ready to play a pre-roll.
91712   */
91713  Controller.prototype.onPlayerReadyForPreroll = function () {
91714    this.sdkImpl.onPlayerReadyForPreroll();
91715  };
91716
91717  /**
91718   * Called if the ad times out.
91719   */
91720  Controller.prototype.onAdTimeout = function () {
91721    this.sdkImpl.onAdTimeout();
91722  };
91723
91724  /**
91725   * Called when the player is ready.
91726   */
91727  Controller.prototype.onPlayerReady = function () {
91728    this.sdkImpl.onPlayerReady();
91729  };
91730
91731  /**
91732   * Called when the player enters fullscreen.
91733   */
91734  Controller.prototype.onPlayerEnterFullscreen = function () {
91735    this.adUi.onPlayerEnterFullscreen();
91736    this.sdkImpl.onPlayerEnterFullscreen();
91737  };
91738
91739  /**
91740   * Called when the player exits fullscreen.
91741   */
91742  Controller.prototype.onPlayerExitFullscreen = function () {
91743    this.adUi.onPlayerExitFullscreen();
91744    this.sdkImpl.onPlayerExitFullscreen();
91745  };
91746
91747  /**
91748   * Called when the player volume changes.
91749   *
91750   * @param {number} volume The new player volume.
91751   */
91752  Controller.prototype.onPlayerVolumeChanged = function (volume) {
91753    this.adUi.onPlayerVolumeChanged(volume);
91754    this.sdkImpl.onPlayerVolumeChanged(volume);
91755  };
91756
91757  /**
91758   * Sets the content of the video player. You should use this method instead
91759   * of setting the content src directly to ensure the proper ad tag is
91760   * requested when the video content is loaded.
91761   * @param {?string} contentSrc The URI for the content to be played. Leave
91762   *     blank to use the existing content.
91763   * @param {?string} adTag The ad tag to be requested when the content loads.
91764   *     Leave blank to use the existing ad tag.
91765   */
91766  Controller.prototype.setContentWithAdTag = function (contentSrc, adTag) {
91767    this.reset();
91768    this.settings.adTagUrl = adTag ? adTag : this.settings.adTagUrl;
91769    this.playerWrapper.changeSource(contentSrc);
91770  };
91771
91772  /**
91773   * Sets the content of the video player. You should use this method instead
91774   * of setting the content src directly to ensure the proper ads response is
91775   * used when the video content is loaded.
91776   * @param {?string} contentSrc The URI for the content to be played. Leave
91777   *     blank to use the existing content.
91778   * @param {?string} adsResponse The ads response to be requested when the
91779   *     content loads. Leave blank to use the existing ads response.
91780   */
91781  Controller.prototype.setContentWithAdsResponse = function (contentSrc, adsResponse) {
91782    this.reset();
91783    this.settings.adsResponse = adsResponse ? adsResponse : this.settings.adsResponse;
91784    this.playerWrapper.changeSource(contentSrc);
91785  };
91786
91787  /**
91788   * Sets the content of the video player. You should use this method instead
91789   * of setting the content src directly to ensure the proper ads request is
91790   * used when the video content is loaded.
91791   * @param {?string} contentSrc The URI for the content to be played. Leave
91792   *     blank to use the existing content.
91793   * @param {?Object} adsRequest The ads request to be requested when the
91794   *     content loads. Leave blank to use the existing ads request.
91795   */
91796  Controller.prototype.setContentWithAdsRequest = function (contentSrc, adsRequest) {
91797    this.reset();
91798    this.settings.adsRequest = adsRequest ? adsRequest : this.settings.adsRequest;
91799    this.playerWrapper.changeSource(contentSrc);
91800  };
91801
91802  /**
91803   * Resets the state of the plugin.
91804   */
91805  Controller.prototype.reset = function () {
91806    this.sdkImpl.reset();
91807    this.playerWrapper.reset();
91808    this.adUi.reset();
91809  };
91810
91811  /**
91812   * Listener JSDoc for ESLint. This listener can be passed to
91813   * (add|remove)ContentEndedListener.
91814   * @callback listener
91815   */
91816
91817  /**
91818   * Adds a listener for the 'readyforpostroll' event of the video player. This
91819   * should be used instead of setting an 'readyforpostroll' listener directly to
91820   * ensure that the ima can do proper cleanup of the SDK before other event
91821   * listeners are called.
91822   * @param {listener} listener The listener to be called when content
91823   *     completes.
91824   */
91825  Controller.prototype.addContentEndedListener = function (listener) {
91826    this.playerWrapper.addContentEndedListener(listener);
91827  };
91828
91829  /**
91830   * Adds a listener that will be called when content and all ads have
91831   * finished playing.
91832   * @param {listener} listener The listener to be called when content and ads
91833   *     complete.
91834   */
91835  Controller.prototype.addContentAndAdsEndedListener = function (listener) {
91836    this.contentAndAdsEndedListeners.push(listener);
91837  };
91838
91839  /**
91840   * Sets the listener to be called to trigger manual ad break playback.
91841   * @param {listener} listener The listener to be called to trigger manual ad
91842   *     break playback.
91843   */
91844  Controller.prototype.setAdBreakReadyListener = function (listener) {
91845    this.sdkImpl.setAdBreakReadyListener(listener);
91846  };
91847
91848  /**
91849   * Changes the flag to show or hide the ad countdown timer.
91850   *
91851   * @param {boolean} showCountdownIn Show or hide the countdown timer.
91852   */
91853  Controller.prototype.setShowCountdown = function (showCountdownIn) {
91854    this.adUi.setShowCountdown(showCountdownIn);
91855    this.showCountdown = showCountdownIn;
91856    this.adUi.countdownDiv.style.display = this.showCountdown ? 'block' : 'none';
91857  };
91858
91859  /**
91860   * Initializes the AdDisplayContainer. On mobile, this must be done as a
91861   * result of user action.
91862   */
91863  Controller.prototype.initializeAdDisplayContainer = function () {
91864    this.sdkImpl.initializeAdDisplayContainer();
91865  };
91866
91867  /**
91868   * Called by publishers in manual ad break playback mode to start an ad
91869   * break.
91870   */
91871  Controller.prototype.playAdBreak = function () {
91872    this.sdkImpl.playAdBreak();
91873  };
91874
91875  /**
91876   * Callback JSDoc for ESLint. This callback can be passed to addEventListener.
91877   * @callback callback
91878   */
91879
91880  /**
91881   * Ads an EventListener to the AdsManager. For a list of available events,
91882   * see
91883   * https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/reference/js/google.ima.AdEvent#.Type
91884   * @param {google.ima.AdEvent.Type} event The AdEvent.Type for which to
91885   *     listen.
91886   * @param {callback} callback The method to call when the event is fired.
91887   */
91888  Controller.prototype.addEventListener = function (event, callback) {
91889    this.sdkImpl.addEventListener(event, callback);
91890  };
91891
91892  /**
91893   * Returns the instance of the AdsManager.
91894   * @return {google.ima.AdsManager} The AdsManager being used by the plugin.
91895   */
91896  Controller.prototype.getAdsManager = function () {
91897    return this.sdkImpl.getAdsManager();
91898  };
91899
91900  /**
91901   * Returns the instance of the player id.
91902   * @return {string} The player id.
91903   */
91904  Controller.prototype.getPlayerId = function () {
91905    return this.playerWrapper.getPlayerId();
91906  };
91907
91908  /**
91909   * Changes the ad tag. You will need to call requestAds after this method
91910   * for the new ads to be requested.
91911   * @param {?string} adTag The ad tag to be requested the next time
91912   *     requestAds is called.
91913   */
91914  Controller.prototype.changeAdTag = function (adTag) {
91915    this.reset();
91916    this.settings.adTagUrl = adTag;
91917  };
91918
91919  /**
91920   * Pauses the ad.
91921   */
91922  Controller.prototype.pauseAd = function () {
91923    this.adUi.onAdsPaused();
91924    this.sdkImpl.pauseAds();
91925  };
91926
91927  /**
91928   * Resumes the ad.
91929   */
91930  Controller.prototype.resumeAd = function () {
91931    this.adUi.onAdsPlaying();
91932    this.sdkImpl.resumeAds();
91933  };
91934
91935  /**
91936   * Toggles video/ad playback.
91937   */
91938  Controller.prototype.togglePlayback = function () {
91939    this.playerWrapper.togglePlayback();
91940  };
91941
91942  /**
91943   * @return {boolean} true if we expect that ads will autoplay. false otherwise.
91944   */
91945  Controller.prototype.adsWillAutoplay = function () {
91946    if (this.settings.adsWillAutoplay !== undefined) {
91947      return this.settings.adsWillAutoplay;
91948    } else if (this.settings.adWillAutoplay !== undefined) {
91949      return this.settings.adWillAutoplay;
91950    } else {
91951      return !!this.playerWrapper.getPlayerOptions().autoplay;
91952    }
91953  };
91954
91955  /**
91956   * @return {boolean} true if we expect that ads will autoplay. false otherwise.
91957   */
91958  Controller.prototype.adsWillPlayMuted = function () {
91959    if (this.settings.adsWillPlayMuted !== undefined) {
91960      return this.settings.adsWillPlayMuted;
91961    } else if (this.settings.adWillPlayMuted !== undefined) {
91962      return this.settings.adWillPlayMuted;
91963    } else if (this.playerWrapper.getPlayerOptions().muted !== undefined) {
91964      return this.playerWrapper.getPlayerOptions().muted;
91965    } else {
91966      return this.playerWrapper.getVolume() == 0;
91967    }
91968  };
91969
91970  /**
91971   * Triggers an event on the VJS player
91972   * @param  {string} name The event name.
91973   * @param  {Object} data The event data.
91974   */
91975  Controller.prototype.triggerPlayerEvent = function (name, data) {
91976    this.playerWrapper.triggerPlayerEvent(name, data);
91977  };
91978
91979  /**
91980   * Copyright 2021 Google Inc.
91981   *
91982   * Licensed under the Apache License, Version 2.0 (the "License");
91983   * you may not use this file except in compliance with the License.
91984   * You may obtain a copy of the License at
91985   *
91986   *     http://www.apache.org/licenses/LICENSE-2.0
91987   *
91988   * Unless required by applicable law or agreed to in writing, software
91989   * distributed under the License is distributed on an "AS IS" BASIS,
91990   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
91991   * See the License for the specific language governing permissions and
91992   * limitations under the License.
91993   *
91994   * IMA SDK integration plugin for Video.js. For more information see
91995   * https://www.github.com/googleads/videojs-ima
91996   */
91997
91998  /**
91999   * Wraps the video.js stream player for the plugin.
92000   *
92001   * @param {!Object} player Video.js player instance.
92002   * @param {!Object} adsPluginSettings Settings for the contrib-ads plugin.
92003   * @param {!DaiController} daiController Reference to the parent controller.
92004   */
92005  var PlayerWrapper = function PlayerWrapper(player, adsPluginSettings, daiController) {
92006    /**
92007     * Instance of the video.js player.
92008     */
92009    this.vjsPlayer = player;
92010
92011    /**
92012     * Plugin DAI controller.
92013     */
92014    this.daiController = daiController;
92015
92016    /**
92017     * Video.js control bar.
92018     */
92019    this.vjsControls = this.vjsPlayer.getChild('controlBar');
92020
92021    /**
92022     * Vanilla HTML5 video player underneath the video.js player.
92023     */
92024    this.h5Player = null;
92025    this.vjsPlayer.on('dispose', this.playerDisposedListener.bind(this));
92026    this.vjsPlayer.on('pause', this.onPause.bind(this));
92027    this.vjsPlayer.on('play', this.onPlay.bind(this));
92028    this.vjsPlayer.on('seeked', this.onSeekEnd.bind(this));
92029    this.vjsPlayer.ready(this.onPlayerReady.bind(this));
92030    if (!this.vjsPlayer.ads) {
92031      window.console.warn('You may be using a version of videojs-contrib-ads ' + 'that is not compatible with your version of video.js.');
92032    }
92033    this.vjsPlayer.ads(adsPluginSettings);
92034  };
92035
92036  /**
92037   * Called in response to the video.js player's 'disposed' event.
92038   */
92039  PlayerWrapper.prototype.playerDisposedListener = function () {
92040    this.contentEndedListeners = [];
92041    this.daiController.onPlayerDisposed();
92042  };
92043
92044  /**
92045   * Called on the player 'pause' event. Handles displaying controls during
92046   * paused ad breaks.
92047   */
92048  PlayerWrapper.prototype.onPause = function () {
92049    // This code will run if the stream is paused during an ad break. Since
92050    // controls are usually hidden during ads, they will now show to allow
92051    // users to resume ad playback.
92052    if (this.daiController.isInAdBreak()) {
92053      this.vjsControls.show();
92054    }
92055  };
92056
92057  /**
92058   * Called on the player 'play' event. Handles hiding controls during
92059   * ad breaks while playing.
92060   */
92061  PlayerWrapper.prototype.onPlay = function () {
92062    if (this.daiController.isInAdBreak()) {
92063      this.vjsControls.hide();
92064    }
92065  };
92066
92067  /**
92068   * Called on the player's 'seeked' event. Sets up handling for ad break
92069   * snapback for VOD streams.
92070   */
92071  PlayerWrapper.prototype.onSeekEnd = function () {
92072    this.daiController.onSeekEnd(this.vjsPlayer.currentTime());
92073  };
92074
92075  /**
92076   * Called on the player's 'ready' event to begin initiating IMA.
92077   */
92078  PlayerWrapper.prototype.onPlayerReady = function () {
92079    this.h5Player = document.getElementById(this.getPlayerId()).getElementsByClassName('vjs-tech')[0];
92080    this.daiController.onPlayerReady();
92081  };
92082
92083  /**
92084   * @return {!Object} The stream player.
92085   */
92086  PlayerWrapper.prototype.getStreamPlayer = function () {
92087    return this.h5Player;
92088  };
92089
92090  /**
92091   * @return {!Object} The video.js player.
92092   */
92093  PlayerWrapper.prototype.getVjsPlayer = function () {
92094    return this.vjsPlayer;
92095  };
92096
92097  /**
92098   * @return {!Object} The vjs player's options object.
92099   */
92100  PlayerWrapper.prototype.getPlayerOptions = function () {
92101    return this.vjsPlayer.options_;
92102  };
92103
92104  /**
92105   * Returns the instance of the player id.
92106   * @return {string} The player id.
92107   */
92108  PlayerWrapper.prototype.getPlayerId = function () {
92109    return this.vjsPlayer.id();
92110  };
92111
92112  /**
92113   * Handles ad errors.
92114   *
92115   * @param {!Object} adErrorEvent The ad error event thrown by the IMA SDK.
92116   */
92117  PlayerWrapper.prototype.onAdError = function (adErrorEvent) {
92118    this.vjsControls.show();
92119    var errorMessage = adErrorEvent.getError !== undefined ? adErrorEvent.getError() : adErrorEvent.stack;
92120    this.vjsPlayer.trigger({
92121      type: 'adserror',
92122      data: {
92123        AdError: errorMessage,
92124        AdErrorEvent: adErrorEvent
92125      }
92126    });
92127  };
92128
92129  /**
92130   * Handles ad break starting.
92131   */
92132  PlayerWrapper.prototype.onAdBreakStart = function () {
92133    this.vjsControls.hide();
92134  };
92135
92136  /**
92137   * Handles ad break ending.
92138   */
92139  PlayerWrapper.prototype.onAdBreakEnd = function () {
92140    this.vjsControls.show();
92141  };
92142
92143  /**
92144   * Reset the player.
92145   */
92146  PlayerWrapper.prototype.reset = function () {
92147    this.vjsControls.show();
92148  };
92149
92150  /**
92151   * Copyright 2021 Google Inc.
92152   *
92153   * Licensed under the Apache License, Version 2.0 (the "License");
92154   * you may not use this file except in compliance with the License.
92155   * You may obtain a copy of the License at
92156   *
92157   *     http://www.apache.org/licenses/LICENSE-2.0
92158   *
92159   * Unless required by applicable law or agreed to in writing, software
92160   * distributed under the License is distributed on an "AS IS" BASIS,
92161   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
92162   * See the License for the specific language governing permissions and
92163   * limitations under the License.
92164   *
92165   * IMA SDK integration plugin for Video.js. For more information see
92166   * https://www.github.com/googleads/videojs-ima
92167   */
92168
92169  var GOOGLE_DAI_TIMED_METADATA_KEY = 'TXXX';
92170
92171  /**
92172   * Implementation of the IMA DAI SDK for the plugin.
92173   *
92174   * @param {DaiController!} daiController Reference to the parent DAI
92175   * controller.
92176   *
92177   * @constructor
92178   * @struct
92179   * @final
92180   */
92181  var SdkImpl = function SdkImpl(daiController) {
92182    /**
92183     * Plugin DAI controller.
92184     */
92185    this.daiController = daiController;
92186
92187    /**
92188     * The html5 stream player.
92189     */
92190    this.streamPlayer = null;
92191
92192    /**
92193     * The videoJS stream player.
92194     */
92195    this.vjsPlayer = null;
92196
92197    /**
92198     * IMA SDK StreamManager
92199     */
92200    this.streamManager = null;
92201
92202    /**
92203     * IMA stream UI settings.
92204     */
92205    /* eslint no-undef: 'error' */
92206    /* global google */
92207    this.uiSettings = new google.ima.dai.api.UiSettings();
92208
92209    /**
92210     * If the stream is currently in an ad break.
92211     */
92212    this.isAdBreak = false;
92213
92214    /**
92215     * If the stream is currently seeking from a snapback.
92216     */
92217    this.isSnapback = false;
92218
92219    /**
92220     * Originally seeked to time, to return stream to after ads.
92221     */
92222    this.snapForwardTime = 0;
92223
92224    /**
92225     * Timed metadata for the stream.
92226     */
92227    this.timedMetadata;
92228
92229    /**
92230     * Timed metadata record.
92231     */
92232    this.metadataLoaded = {};
92233    this.SOURCE_TYPES = {
92234      hls: 'application/x-mpegURL',
92235      dash: 'application/dash+xml'
92236    };
92237  };
92238
92239  /**
92240   * Creates and initializes the IMA DAI SDK objects.
92241   */
92242  SdkImpl.prototype.initImaDai = function () {
92243    var _this = this;
92244    this.streamPlayer = this.daiController.getStreamPlayer();
92245    this.vjsPlayer = this.daiController.getVjsPlayer();
92246    this.createAdUiDiv();
92247    if (this.daiController.getSettings().locale) {
92248      this.uiSettings.setLocale(this.daiController.getSettings().locale);
92249    }
92250    this.streamManager = new google.ima.dai.api.StreamManager(this.streamPlayer, this.adUiDiv, this.uiSettings);
92251    this.streamPlayer.addEventListener('pause', this.onStreamPause);
92252    this.streamPlayer.addEventListener('play', this.onStreamPlay);
92253    var imaEvents = [google.ima.dai.api.StreamEvent.Type.LOADED, google.ima.dai.api.StreamEvent.Type.ERROR, google.ima.dai.api.StreamEvent.Type.AD_BREAK_STARTED, google.ima.dai.api.StreamEvent.Type.AD_BREAK_ENDED];
92254    imaEvents.forEach(function (event) {
92255      _this.streamManager.addEventListener(event, _this.onStreamEvent.bind(_this));
92256    });
92257    this.vjsPlayer.textTracks().onaddtrack = this.onAddTrack.bind(this);
92258    this.vjsPlayer.trigger({
92259      type: 'stream-manager',
92260      StreamManager: this.streamManager
92261    });
92262    this.requestStream();
92263  };
92264
92265  /**
92266   * Called when the video player has metadata to process.
92267   * @param {Event!} event The event that triggered this call.
92268   */
92269  SdkImpl.prototype.onAddTrack = function (event) {
92270    var _this2 = this;
92271    var track = event.track;
92272    if (track.kind === 'metadata') {
92273      track.mode = 'hidden';
92274      track.addEventListener('cuechange', function (e) {
92275        // Check for both `activeCues` and `activeCues_` properties
92276        // which are available depending on the browser.
92277        var activeCues = track.activeCues_ || track.activeCues;
92278        if (!activeCues) {
92279          return;
92280        }
92281        var _iterator = _createForOfIteratorHelper(activeCues),
92282          _step;
92283        try {
92284          for (_iterator.s(); !(_step = _iterator.n()).done;) {
92285            var cue = _step.value;
92286            if (cue.value && cue.value.key === GOOGLE_DAI_TIMED_METADATA_KEY) {
92287              _this2.streamManager.onTimedMetadata(_defineProperty({}, GOOGLE_DAI_TIMED_METADATA_KEY, cue.value.data));
92288            }
92289          }
92290        } catch (err) {
92291          _iterator.e(err);
92292        } finally {
92293          _iterator.f();
92294        }
92295      });
92296    }
92297  };
92298
92299  /**
92300   * Creates the ad UI container.
92301   */
92302  SdkImpl.prototype.createAdUiDiv = function () {
92303    var uiDiv = document.createElement('div');
92304    uiDiv.id = 'ad-ui';
92305    // 3em is the height of the control bar.
92306    uiDiv.style.height = 'calc(100% - 3em)';
92307    this.streamPlayer.parentNode.appendChild(uiDiv);
92308    this.adUiDiv = uiDiv;
92309  };
92310
92311  /**
92312   * Called on pause to update the ad UI.
92313   */
92314  SdkImpl.prototype.onStreamPause = function () {
92315    if (this.isAdBreak) {
92316      this.adUiDiv.style.display = 'none';
92317    }
92318  };
92319
92320  /**
92321   * Called on play to update the ad UI.
92322   */
92323  SdkImpl.prototype.onStreamPlay = function () {
92324    if (this.isAdBreak) {
92325      this.adUiDiv.style.display = 'block';
92326    }
92327  };
92328
92329  /**
92330   * Called on play to update the ad UI.
92331   * @param {number} currentTime the current time of the stream.
92332   */
92333  SdkImpl.prototype.onSeekEnd = function (currentTime) {
92334    var streamType = this.daiController.getSettings().streamType;
92335    if (streamType === 'live') {
92336      return;
92337    }
92338    if (this.isSnapback) {
92339      this.isSnapback = false;
92340      return;
92341    }
92342    var previousCuePoint = this.streamManager.previousCuePointForStreamTime(currentTime);
92343    if (previousCuePoint && !previousCuePoint.played) {
92344      this.isSnapback = true;
92345      this.snapForwardTime = currentTime;
92346      this.vjsPlayer.currentTime(previousCuePoint.start);
92347    }
92348  };
92349
92350  /**
92351   * Handles IMA events.
92352   * @param {google.ima.StreamEvent!} event the IMA event
92353   */
92354  SdkImpl.prototype.onStreamEvent = function (event) {
92355    switch (event.type) {
92356      case google.ima.dai.api.StreamEvent.Type.LOADED:
92357        this.loadUrl(event.getStreamData().url);
92358        break;
92359      case google.ima.dai.api.StreamEvent.Type.ERROR:
92360        window.console.warn('Error loading stream, attempting to play backup ' + 'stream. ' + event.getStreamData().errorMessage);
92361        this.daiController.onErrorLoadingAds(event);
92362        if (this.daiController.getSettings().fallbackStreamUrl) {
92363          this.loadUrl(this.daiController.getSettings().fallbackStreamUrl);
92364        }
92365        break;
92366      case google.ima.dai.api.StreamEvent.Type.AD_BREAK_STARTED:
92367        this.isAdBreak = true;
92368        this.adUiDiv.style.display = 'block';
92369        this.daiController.onAdBreakStart();
92370        break;
92371      case google.ima.dai.api.StreamEvent.Type.AD_BREAK_ENDED:
92372        this.isAdBreak = false;
92373        this.adUiDiv.style.display = 'none';
92374        this.daiController.onAdBreakEnd();
92375        if (this.snapForwardTime && this.snapForwardTime > this.vjsPlayer.currentTime()) {
92376          this.vjsPlayer.currentTime(this.snapForwardTime);
92377          this.snapForwardTime = 0;
92378        }
92379        break;
92380    }
92381  };
92382
92383  /**
92384   * Loads the stream URL .
92385   * @param {string} streamUrl the URL for the stream being loaded.
92386   */
92387  SdkImpl.prototype.loadUrl = function (streamUrl) {
92388    this.vjsPlayer.ready(function () {
92389      var streamFormat = this.daiController.getSettings().streamFormat;
92390      this.vjsPlayer.src({
92391        src: streamUrl,
92392        type: this.SOURCE_TYPES[streamFormat]
92393      });
92394      var bookmarkTime = this.daiController.getSettings().bookmarkTime;
92395      if (bookmarkTime) {
92396        var startTime = this.streamManager.streamTimeForContentTime(bookmarkTime);
92397        // Seeking on load triggers the onSeekEnd event, so treat this seek as
92398        // if it's snapback. Without this, resuming at a bookmark kicks you
92399        // back to the ad before the bookmark.
92400        this.isSnapback = true;
92401        this.vjsPlayer.currentTime(startTime);
92402      }
92403    }.bind(this));
92404  };
92405
92406  /**
92407   * Creates the AdsRequest and request ads through the AdsLoader.
92408   */
92409  SdkImpl.prototype.requestStream = function () {
92410    var streamRequest;
92411    var streamType = this.daiController.getSettings().streamType;
92412    if (streamType === 'vod') {
92413      streamRequest = new google.ima.dai.api.VODStreamRequest();
92414      streamRequest.contentSourceId = this.daiController.getSettings().cmsId;
92415      streamRequest.videoId = this.daiController.getSettings().videoId;
92416    } else if (streamType === 'live') {
92417      streamRequest = new google.ima.dai.api.LiveStreamRequest();
92418      streamRequest.assetKey = this.daiController.getSettings().assetKey;
92419    } else {
92420      window.console.warn('No valid stream type selected');
92421    }
92422    streamRequest.format = this.daiController.getSettings().streamFormat;
92423    if (this.daiController.getSettings().apiKey) {
92424      streamRequest.apiKey = this.daiController.getSettings().apiKey;
92425    }
92426    if (this.daiController.getSettings().authToken) {
92427      streamRequest.authToken = this.daiController.getSettings().authToken;
92428    }
92429    if (this.daiController.getSettings().adTagParameters) {
92430      streamRequest.adTagParameters = this.daiController.getSettings().adTagParameters;
92431    }
92432    if (this.daiController.getSettings().streamActivityMonitorId) {
92433      streamRequest.streamActivityMonitorId = this.daiController.getSettings().streamActivityMonitorId;
92434    }
92435    if (this.daiController.getSettings().omidMode) {
92436      streamRequest.omidAccessModeRules = {};
92437      var omidValues = this.daiController.getSettings().omidMode;
92438      if (omidValues.FULL) {
92439        streamRequest.omidAccessModeRules[google.ima.OmidAccessMode.FULL] = omidValues.FULL;
92440      }
92441      if (omidValues.DOMAIN) {
92442        streamRequest.omidAccessModeRules[google.ima.OmidAccessMode.DOMAIN] = omidValues.DOMAIN;
92443      }
92444      if (omidValues.LIMITED) {
92445        streamRequest.omidAccessModeRules[google.ima.OmidAccessMode.LIMITED] = omidValues.LIMITED;
92446      }
92447    }
92448    this.streamManager.requestStream(streamRequest);
92449    this.vjsPlayer.trigger({
92450      type: 'stream-request',
92451      StreamRequest: streamRequest
92452    });
92453  };
92454
92455  /**
92456   * Initiates IMA when the player is ready.
92457   */
92458  SdkImpl.prototype.onPlayerReady = function () {
92459    this.initImaDai();
92460  };
92461
92462  /**
92463   * Reset the StreamManager when the player is disposed.
92464   */
92465  SdkImpl.prototype.onPlayerDisposed = function () {
92466    if (this.streamManager) {
92467      this.streamManager.reset();
92468    }
92469  };
92470
92471  /**
92472   * Adds an EventListener to the StreamManager. For a list of available events,
92473   * see
92474   * https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/html5/reference/js/StreamEvent#.Type
92475   * @param {google.ima.dai.api.StreamEvent.Type} event The StreamEvent.Type for
92476   *     which to listen.
92477   * @param {callback} callback The method to call when the event is fired.
92478   */
92479  SdkImpl.prototype.addEventListener = function (event, callback) {
92480    if (this.streamManager) {
92481      this.streamManager.addEventListener(event, callback);
92482    }
92483  };
92484
92485  /**
92486   * Returns the instance of the StreamManager.
92487   * @return {google.ima.StreamManager!} The StreamManager being used by the
92488   * plugin.
92489   */
92490  SdkImpl.prototype.getStreamManager = function () {
92491    return this.streamManager;
92492  };
92493
92494  /**
92495   * Reset the SDK implementation.
92496   */
92497  SdkImpl.prototype.reset = function () {
92498    if (this.streamManager) {
92499      this.streamManager.reset();
92500    }
92501  };
92502
92503  /**
92504   * Copyright 2021 Google Inc.
92505   *
92506   * Licensed under the Apache License, Version 2.0 (the "License");
92507   * you may not use this file except in compliance with the License.
92508   * You may obtain a copy of the License at
92509   *
92510   *     http://www.apache.org/licenses/LICENSE-2.0
92511   *
92512   * Unless required by applicable law or agreed to in writing, software
92513   * distributed under the License is distributed on an "AS IS" BASIS,
92514   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
92515   * See the License for the specific language governing permissions and
92516   * limitations under the License.
92517   *
92518   * IMA SDK integration plugin for Video.js. For more information see
92519   * https://www.github.com/googleads/videojs-ima
92520   */
92521
92522  /**
92523   * The coordinator for the DAI portion of the plugin. Facilitates
92524   * communication between all other plugin classes.
92525   *
92526   * @param {Object!} player Instance of the video.js player.
92527   * @param {Object!} options Options provided by the implementation.
92528   * @constructor
92529   * @struct
92530   * @final
92531   */
92532  var DaiController = function DaiController(player, options) {
92533    /**
92534    * If the stream is currently in an ad break.
92535    * @type {boolean}
92536    */
92537    this.inAdBreak = false;
92538
92539    /**
92540    * Stores user-provided settings.
92541    * @type {Object!}
92542    */
92543    this.settings = {};
92544
92545    /**
92546    * Whether or not we are running on a mobile platform.
92547    */
92548    this.isMobile = navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i) || navigator.userAgent.match(/Android/i);
92549
92550    /**
92551    * Whether or not we are running on an iOS platform.
92552    */
92553    this.isIos = navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i);
92554    this.initWithSettings(options);
92555
92556    /**
92557    * Stores contrib-ads default settings.
92558    */
92559    var contribAdsDefaults = {
92560      debug: this.settings.debug,
92561      timeout: this.settings.timeout,
92562      prerollTimeout: this.settings.prerollTimeout
92563    };
92564    var adsPluginSettings = Object.assign({}, contribAdsDefaults, options.contribAdsSettings || {});
92565    this.playerWrapper = new PlayerWrapper(player, adsPluginSettings, this);
92566    this.sdkImpl = new SdkImpl(this);
92567  };
92568  DaiController.IMA_DEFAULTS = {
92569    adLabel: 'Advertisement',
92570    adLabelNofN: 'of',
92571    debug: false,
92572    disableAdControls: false,
92573    showControlsForJSAds: true
92574  };
92575
92576  /**
92577   * Extends the settings to include user-provided settings.
92578   *
92579   * @param {Object!} options Options to be used in initialization.
92580   */
92581  DaiController.prototype.initWithSettings = function (options) {
92582    this.settings = Object.assign({}, DaiController.IMA_DEFAULTS, options || {});
92583    this.warnAboutDeprecatedSettings();
92584
92585    // Default showing countdown timer to true.
92586    this.showCountdown = true;
92587    if (this.settings.showCountdown === false) {
92588      this.showCountdown = false;
92589    }
92590  };
92591
92592  /**
92593   * Logs console warnings when deprecated settings are used.
92594   */
92595  DaiController.prototype.warnAboutDeprecatedSettings = function () {
92596    var _this = this;
92597    var deprecatedSettings = [
92598      // Currently no DAI plugin settings are deprecated.
92599    ];
92600    deprecatedSettings.forEach(function (setting) {
92601      if (_this.settings[setting] !== undefined) {
92602        console.warn('WARNING: videojs.imaDai setting ' + setting + ' is deprecated');
92603      }
92604    });
92605  };
92606
92607  /**
92608   * Return the settings object.
92609   *
92610   * @return {Object!} The settings object.
92611   */
92612  DaiController.prototype.getSettings = function () {
92613    return this.settings;
92614  };
92615
92616  /**
92617   * Return whether or not we're in a mobile environment.
92618   *
92619   * @return {boolean} True if running on mobile, false otherwise.
92620   */
92621  DaiController.prototype.getIsMobile = function () {
92622    return this.isMobile;
92623  };
92624
92625  /**
92626   * Return whether or not we're in an iOS environment.
92627   *
92628   * @return {boolean} True if running on iOS, false otherwise.
92629   */
92630  DaiController.prototype.getIsIos = function () {
92631    return this.isIos;
92632  };
92633
92634  /**
92635   * @return {Object!} The html5 player.
92636   */
92637  DaiController.prototype.getStreamPlayer = function () {
92638    return this.playerWrapper.getStreamPlayer();
92639  };
92640
92641  /**
92642   * @return {Object!} The video.js player.
92643   */
92644  DaiController.prototype.getVjsPlayer = function () {
92645    return this.playerWrapper.getVjsPlayer();
92646  };
92647
92648  /**
92649   * Requests the stream.
92650   */
92651  DaiController.prototype.requestStream = function () {
92652    this.sdkImpl.requestStream();
92653  };
92654
92655  /**
92656   * Add or modify a setting.
92657   *
92658   * @param {string} key Key to modify
92659   * @param {Object!} value Value to set at key.
92660  */
92661  DaiController.prototype.setSetting = function (key, value) {
92662    this.settings[key] = value;
92663  };
92664
92665  /**
92666   * Called when there is an error loading ads.
92667   *
92668   * @param {Object!} adErrorEvent The ad error event thrown by the IMA SDK.
92669   */
92670  DaiController.prototype.onErrorLoadingAds = function (adErrorEvent) {
92671    this.playerWrapper.onAdError(adErrorEvent);
92672  };
92673
92674  /**
92675   * Relays ad errors to the player wrapper.
92676   *
92677   * @param {Object!} adErrorEvent The ad error event thrown by the IMA SDK.
92678   */
92679  DaiController.prototype.onAdError = function (adErrorEvent) {
92680    this.playerWrapper.onAdError(adErrorEvent);
92681  };
92682
92683  /**
92684   * Signals player that an ad break has started.
92685   */
92686  DaiController.prototype.onAdBreakStart = function () {
92687    this.inAdBreak = true;
92688    this.playerWrapper.onAdBreakStart();
92689  };
92690
92691  /**
92692   * Signals player that an ad break has ended.
92693   */
92694  DaiController.prototype.onAdBreakEnd = function () {
92695    this.inAdBreak = false;
92696    this.playerWrapper.onAdBreakEnd();
92697  };
92698
92699  /**
92700   * Called when the player is disposed.
92701   */
92702  DaiController.prototype.onPlayerDisposed = function () {
92703    this.contentAndAdsEndedListeners = [];
92704    this.sdkImpl.onPlayerDisposed();
92705  };
92706
92707  /**
92708   * Returns if the stream is currently in an ad break.
92709   * @return {boolean} If the stream is currently in an ad break.
92710   */
92711  DaiController.prototype.isInAdBreak = function () {
92712    return this.inAdBreak;
92713  };
92714
92715  /**
92716   * Called on seek end to check for ad snapback.
92717   * @param {number} currentTime the current time of the stream.
92718   */
92719  DaiController.prototype.onSeekEnd = function (currentTime) {
92720    this.sdkImpl.onSeekEnd(currentTime);
92721  };
92722
92723  /**
92724   * Called when the player is ready.
92725   */
92726  DaiController.prototype.onPlayerReady = function () {
92727    this.sdkImpl.onPlayerReady();
92728  };
92729
92730  /**
92731   * Resets the state of the plugin.
92732   */
92733  DaiController.prototype.reset = function () {
92734    this.sdkImpl.reset();
92735    this.playerWrapper.reset();
92736  };
92737
92738  /**
92739   * Adds an EventListener to the StreamManager. For a list of available events,
92740   * see
92741   * https://developers.google.com/ad-manager/dynamic-ad-insertion/sdk/html5/reference/js/StreamEvent
92742   * @param {google.ima.StreamEvent.Type!} event The AdEvent.Type for which to
92743   *     listen.
92744   * @param {callback!} callback The method to call when the event is fired.
92745   */
92746  DaiController.prototype.addEventListener = function (event, callback) {
92747    this.sdkImpl.addEventListener(event, callback);
92748  };
92749
92750  /**
92751   * Returns the instance of the StreamManager.
92752   * @return {google.ima.StreamManager!} The StreamManager being used by the
92753   * plugin.
92754   */
92755  DaiController.prototype.getStreamManager = function () {
92756    return this.sdkImpl.getStreamManager();
92757  };
92758
92759  /**
92760   * Returns the instance of the player id.
92761   * @return {string} The player id.
92762   */
92763  DaiController.prototype.getPlayerId = function () {
92764    return this.playerWrapper.getPlayerId();
92765  };
92766
92767  /**
92768   * @return {boolean} true if we expect that the stream will autoplay. false
92769   * otherwise.
92770   */
92771  DaiController.prototype.streamWillAutoplay = function () {
92772    if (this.settings.streamWillAutoplay !== undefined) {
92773      return this.settings.streamWillAutoplay;
92774    } else {
92775      return !!this.playerWrapper.getPlayerOptions().autoplay;
92776    }
92777  };
92778
92779  /**
92780   * Triggers an event on the VJS player
92781   * @param  {string} name The event name.
92782   * @param  {Object!} data The event data.
92783   */
92784  DaiController.prototype.triggerPlayerEvent = function (name, data) {
92785    this.playerWrapper.triggerPlayerEvent(name, data);
92786  };
92787
92788  /**
92789   * Exposes the ImaPlugin to a publisher implementation.
92790   *
92791   * @param {Object} player Instance of the video.js player to which this plugin
92792   *     will be added.
92793   * @param {Object} options Options provided by the implementation.
92794   * @constructor
92795   * @struct
92796   * @final
92797   */
92798  var ImaPlugin = function ImaPlugin(player, options) {
92799    this.controller = new Controller(player, options);
92800
92801    /**
92802     * Listener JSDoc for ESLint. This listener can be passed to
92803     * addContent(AndAds)EndedListener.
92804     * @callback listener
92805     */
92806
92807    /**
92808     * Adds a listener that will be called when content and all ads have
92809     * finished playing.
92810     * @param {listener} listener The listener to be called when content and ads
92811     *     complete.
92812     */
92813    this.addContentAndAdsEndedListener = function (listener) {
92814      this.controller.addContentAndAdsEndedListener(listener);
92815    }.bind(this);
92816
92817    /**
92818     * Adds a listener for the 'readyforpostroll' event of the video player. This
92819     * should be used instead of setting an 'readyforpostroll' listener directly
92820     * to ensure that the ima can do proper cleanup of the SDK before other event
92821     * listeners are called.
92822     * @param {listener} listener The listener to be called when content
92823     *     completes.
92824     */
92825    this.addContentEndedListener = function (listener) {
92826      this.controller.addContentEndedListener(listener);
92827    }.bind(this);
92828
92829    /**
92830     * Callback JSDoc for ESLint. This callback can be passed to addEventListener.
92831     * @callback callback
92832     */
92833
92834    /**
92835     * Ads an EventListener to the AdsManager. For a list of available events,
92836     * see
92837     * https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/reference/js/google.ima.AdEvent#.Type
92838     * @param {google.ima.AdEvent.Type} event The AdEvent.Type for which to
92839     *     listen.
92840     * @param {callback} callback The method to call when the event is fired.
92841     */
92842    this.addEventListener = function (event, callback) {
92843      this.controller.addEventListener(event, callback);
92844    }.bind(this);
92845
92846    /**
92847     * Changes the ad tag. You will need to call requestAds after this method
92848     * for the new ads to be requested.
92849     * @param {?string} adTag The ad tag to be requested the next time requestAds
92850     *     is called.
92851     */
92852    this.changeAdTag = function (adTag) {
92853      this.controller.changeAdTag(adTag);
92854    }.bind(this);
92855
92856    /**
92857     * Returns the instance of the AdsManager.
92858     * @return {google.ima.AdsManager} The AdsManager being used by the plugin.
92859     */
92860    this.getAdsManager = function () {
92861      return this.controller.getAdsManager();
92862    }.bind(this);
92863
92864    /**
92865     * Initializes the AdDisplayContainer. On mobile, this must be done as a
92866     * result of user action.
92867     */
92868    this.initializeAdDisplayContainer = function () {
92869      this.controller.initializeAdDisplayContainer();
92870    }.bind(this);
92871
92872    /**
92873     * Pauses the ad.
92874     */
92875    this.pauseAd = function () {
92876      this.controller.pauseAd();
92877    }.bind(this);
92878
92879    /**
92880     * Called by publishers in manual ad break playback mode to start an ad
92881     * break.
92882     */
92883    this.playAdBreak = function () {
92884      this.controller.playAdBreak();
92885    }.bind(this);
92886
92887    /**
92888     * Creates the AdsRequest and request ads through the AdsLoader.
92889     */
92890    this.requestAds = function () {
92891      this.controller.requestAds();
92892    }.bind(this);
92893
92894    /**
92895     * Resumes the ad.
92896     */
92897    this.resumeAd = function () {
92898      this.controller.resumeAd();
92899    }.bind(this);
92900
92901    /**
92902     * Sets the listener to be called to trigger manual ad break playback.
92903     * @param {listener} listener The listener to be called to trigger manual ad
92904     *     break playback.
92905     */
92906    this.setAdBreakReadyListener = function (listener) {
92907      this.controller.setAdBreakReadyListener(listener);
92908    }.bind(this);
92909
92910    /**
92911     * Sets the content of the video player. You should use this method instead
92912     * of setting the content src directly to ensure the proper ad tag is
92913     * requested when the video content is loaded.
92914     * @param {?string} contentSrc The URI for the content to be played. Leave
92915     *     blank to use the existing content.
92916     * @param {?string} adTag The ad tag to be requested when the content loads.
92917     *     Leave blank to use the existing ad tag.
92918     */
92919    this.setContentWithAdTag = function (contentSrc, adTag) {
92920      this.controller.setContentWithAdTag(contentSrc, adTag);
92921    }.bind(this);
92922
92923    /**
92924     * Sets the content of the video player. You should use this method instead
92925     * of setting the content src directly to ensure the proper ads response is
92926     * used when the video content is loaded.
92927     * @param {?string} contentSrc The URI for the content to be played. Leave
92928     *     blank to use the existing content.
92929     * @param {?string} adsResponse The ads response to be requested when the
92930     *     content loads. Leave blank to use the existing ads response.
92931     */
92932    this.setContentWithAdsResponse = function (contentSrc, adsResponse) {
92933      this.controller.setContentWithAdsResponse(contentSrc, adsResponse);
92934    }.bind(this);
92935
92936    /**
92937     * Sets the content of the video player. You should use this method instead
92938     * of setting the content src directly to ensure the proper ads request is
92939     * used when the video content is loaded.
92940     * @param {?string} contentSrc The URI for the content to be played. Leave
92941     *     blank to use the existing content.
92942     * @param {?Object} adsRequest The ads request to be requested when the
92943     *     content loads. Leave blank to use the existing ads request.
92944     */
92945    this.setContentWithAdsRequest = function (contentSrc, adsRequest) {
92946      this.controller.setContentWithAdsRequest(contentSrc, adsRequest);
92947    }.bind(this);
92948
92949    /**
92950     * Changes the flag to show or hide the ad countdown timer.
92951     *
92952     * @param {boolean} showCountdownIn Show or hide the countdown timer.
92953     */
92954    this.setShowCountdown = function (showCountdownIn) {
92955      this.controller.setShowCountdown(showCountdownIn);
92956    }.bind(this);
92957  };
92958
92959  /**
92960   * Exposes the ImaDaiPlugin to a publisher implementation.
92961   *
92962   * @param {Object} player Instance of the video.js player to which this plugin
92963   *     will be added.
92964   * @param {Object} options Options provided by the implementation.
92965   * @constructor
92966   * @struct
92967   * @final
92968   */
92969  var ImaDaiPlugin = function ImaDaiPlugin(player, options) {
92970    this.controller = new DaiController(player, options);
92971
92972    /**
92973     * Adds a listener that will be called when content and all ads in the
92974     * stream have finished playing. VOD stream only.
92975     * @param {listener} listener The listener to be called when content and ads
92976     *     complete.
92977     */
92978    this.streamEndedListener = function (listener) {
92979      this.controller.addStreamEndedListener(listener);
92980    }.bind(this);
92981
92982    /**
92983     * Adds an EventListener to the StreamManager.
92984     * @param {google.ima.StreamEvent.Type} event The StreamEvent.Type for which
92985     * to listen.
92986     * @param {callback} callback The method to call when the event is fired.
92987     */
92988    this.addEventListener = function (event, callback) {
92989      this.controller.addEventListener(event, callback);
92990    }.bind(this);
92991
92992    /**
92993     * Returns the instance of the StreamManager.
92994     * @return {google.ima.StreamManager} The StreamManager being used by the
92995     * plugin.
92996     */
92997    this.getStreamManager = function () {
92998      return this.controller.getStreamManager();
92999    }.bind(this);
93000  };
93001
93002  /**
93003   * Initializes the plugin for client-side ads.
93004   * @param {Object} options Plugin option set on initiation.
93005   */
93006  var init = function init(options) {
93007    /* eslint no-invalid-this: 'off' */
93008    this.ima = new ImaPlugin(this, options);
93009  };
93010
93011  /**
93012   * LiveStream class used for DAI live streams.
93013   */
93014  var LiveStream = /*#__PURE__*/_createClass(
93015  /**
93016   * LiveStream class constructor used for DAI live streams.
93017   * @param {string} streamFormat stream format, plugin currently supports only
93018   * 'hls' streams.
93019   * @param {string} assetKey live stream's asset key.
93020   */
93021  function LiveStream(streamFormat, assetKey) {
93022    _classCallCheck(this, LiveStream);
93023    streamFormat = streamFormat.toLowerCase();
93024    if (streamFormat !== 'hls' && streamFormat !== 'dash') {
93025      window.console.error('VodStream error: incorrect streamFormat.');
93026      return;
93027    } else if (streamFormat === 'dash') {
93028      window.console.error('streamFormat error: DASH streams are not' + 'currently supported by this plugin.');
93029      return;
93030    } else if (typeof assetKey !== 'string') {
93031      window.console.error('assetKey error: value must be string.');
93032      return;
93033    }
93034    this.streamFormat = streamFormat;
93035    this.assetKey = assetKey;
93036  });
93037  /**
93038   * VodStream class used for DAI VOD streams.
93039   */
93040  var VodStream = /*#__PURE__*/_createClass(
93041  /**
93042   * VodStream class constructor used for DAI VOD streams.
93043   * @param {string} streamFormat stream format, plugin currently supports only
93044   * 'hls' streams.
93045   * @param {string} cmsId VOD stream's CMS ID.
93046   * @param {string} videoId VOD stream's video ID.
93047   */
93048  function VodStream(streamFormat, cmsId, videoId) {
93049    _classCallCheck(this, VodStream);
93050    streamFormat = streamFormat.toLowerCase();
93051    if (streamFormat !== 'hls' && streamFormat !== 'dash') {
93052      window.console.error('VodStream error: incorrect streamFormat.');
93053      return;
93054    } else if (streamFormat === 'dash') {
93055      window.console.error('streamFormat error: DASH streams are not' + 'currently supported by this plugin.');
93056      return;
93057    } else if (typeof cmsId !== 'string') {
93058      window.console.error('cmsId error: value must be string.');
93059      return;
93060    } else if (typeof videoId !== 'string') {
93061      window.console.error('videoId error: value must be string.');
93062      return;
93063    }
93064    this.streamFormat = streamFormat;
93065    this.cmsId = cmsId;
93066    this.videoId = videoId;
93067  });
93068  /**
93069   * Initializes the plugin for DAI ads.
93070   * @param {Object} stream Accepts either an instance of the LiveStream or
93071   * VodStream classes.
93072   * @param {Object} options Plugin option set on initiation.
93073   */
93074  var initDai = function initDai(stream, options) {
93075    if (stream instanceof LiveStream) {
93076      options.streamType = 'live';
93077      options.assetKey = stream.assetKey;
93078    } else if (stream instanceof VodStream) {
93079      options.streamType = 'vod';
93080      options.cmsId = stream.cmsId;
93081      options.videoId = stream.videoId;
93082    } else {
93083      window.console.error('initDai() first parameter must be an instance of LiveStream or ' + 'VodStream.');
93084      return;
93085    }
93086    options.streamFormat = stream.streamFormat;
93087    /* eslint no-invalid-this: 'off' */
93088    this.imaDai = new ImaDaiPlugin(this, options);
93089  };
93090  var registerPlugin = videojs.registerPlugin || videojs.plugin;
93091  registerPlugin('ima', init);
93092  registerPlugin('imaDai', initDai);
93093
93094  exports.LiveStream = LiveStream;
93095  exports.VodStream = VodStream;
93096  exports.default = ImaPlugin;
93097
93098  Object.defineProperty(exports, '__esModule', { value: true });
93099
93100}));
93101
93102},{"video.js":50}],53:[function(require,module,exports){
93103/**
93104 * Copyright 2013 vtt.js Contributors
93105 *
93106 * Licensed under the Apache License, Version 2.0 (the "License");
93107 * you may not use this file except in compliance with the License.
93108 * You may obtain a copy of the License at
93109 *
93110 *   http://www.apache.org/licenses/LICENSE-2.0
93111 *
93112 * Unless required by applicable law or agreed to in writing, software
93113 * distributed under the License is distributed on an "AS IS" BASIS,
93114 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
93115 * See the License for the specific language governing permissions and
93116 * limitations under the License.
93117 */
93118
93119// Default exports for Node. Export the extended versions of VTTCue and
93120// VTTRegion in Node since we likely want the capability to convert back and
93121// forth between JSON. If we don't then it's not that big of a deal since we're
93122// off browser.
93123
93124var window = require('global/window');
93125
93126var vttjs = module.exports = {
93127  WebVTT: require("./vtt.js"),
93128  VTTCue: require("./vttcue.js"),
vendor: 4,757 bytes, lines 93129-93288
93129  VTTRegion: require("./vttregion.js")
93130};
93131
93132window.vttjs = vttjs;
93133window.WebVTT = vttjs.WebVTT;
93134
93135var cueShim = vttjs.VTTCue;
93136var regionShim = vttjs.VTTRegion;
93137var nativeVTTCue = window.VTTCue;
93138var nativeVTTRegion = window.VTTRegion;
93139
93140vttjs.shim = function() {
93141  window.VTTCue = cueShim;
93142  window.VTTRegion = regionShim;
93143};
93144
93145vttjs.restore = function() {
93146  window.VTTCue = nativeVTTCue;
93147  window.VTTRegion = nativeVTTRegion;
93148};
93149
93150if (!window.VTTCue) {
93151  vttjs.shim();
93152}
93153
93154},{"./vtt.js":54,"./vttcue.js":55,"./vttregion.js":56,"global/window":39}],54:[function(require,module,exports){
93155/**
93156 * Copyright 2013 vtt.js Contributors
93157 *
93158 * Licensed under the Apache License, Version 2.0 (the "License");
93159 * you may not use this file except in compliance with the License.
93160 * You may obtain a copy of the License at
93161 *
93162 *   http://www.apache.org/licenses/LICENSE-2.0
93163 *
93164 * Unless required by applicable law or agreed to in writing, software
93165 * distributed under the License is distributed on an "AS IS" BASIS,
93166 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
93167 * See the License for the specific language governing permissions and
93168 * limitations under the License.
93169 */
93170
93171/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
93172/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
93173var document = require('global/document');
93174
93175var _objCreate = Object.create || (function() {
93176  function F() {}
93177  return function(o) {
93178    if (arguments.length !== 1) {
93179      throw new Error('Object.create shim only accepts one parameter.');
93180    }
93181    F.prototype = o;
93182    return new F();
93183  };
93184})();
93185
93186// Creates a new ParserError object from an errorData object. The errorData
93187// object should have default code and message properties. The default message
93188// property can be overriden by passing in a message parameter.
93189// See ParsingError.Errors below for acceptable errors.
93190function ParsingError(errorData, message) {
93191  this.name = "ParsingError";
93192  this.code = errorData.code;
93193  this.message = message || errorData.message;
93194}
93195ParsingError.prototype = _objCreate(Error.prototype);
93196ParsingError.prototype.constructor = ParsingError;
93197
93198// ParsingError metadata for acceptable ParsingErrors.
93199ParsingError.Errors = {
93200  BadSignature: {
93201    code: 0,
93202    message: "Malformed WebVTT signature."
93203  },
93204  BadTimeStamp: {
93205    code: 1,
93206    message: "Malformed time stamp."
93207  }
93208};
93209
93210// Try to parse input as a time stamp.
93211function parseTimeStamp(input) {
93212
93213  function computeSeconds(h, m, s, f) {
93214    return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
93215  }
93216
93217  var m = input.match(/^(\d+):(\d{1,2})(:\d{1,2})?\.(\d{3})/);
93218  if (!m) {
93219    return null;
93220  }
93221
93222  if (m[3]) {
93223    // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
93224    return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
93225  } else if (m[1] > 59) {
93226    // Timestamp takes the form of [hours]:[minutes].[milliseconds]
93227    // First position is hours as it's over 59.
93228    return computeSeconds(m[1], m[2], 0,  m[4]);
93229  } else {
93230    // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
93231    return computeSeconds(0, m[1], m[2], m[4]);
93232  }
93233}
93234
93235// A settings object holds key/value pairs and will ignore anything but the first
93236// assignment to a specific key.
93237function Settings() {
93238  this.values = _objCreate(null);
93239}
93240
93241Settings.prototype = {
93242  // Only accept the first assignment to any key.
93243  set: function(k, v) {
93244    if (!this.get(k) && v !== "") {
93245      this.values[k] = v;
93246    }
93247  },
93248  // Return the value for a key, or a default value.
93249  // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
93250  // a number of possible default values as properties where 'defaultKey' is
93251  // the key of the property that will be chosen; otherwise it's assumed to be
93252  // a single value.
93253  get: function(k, dflt, defaultKey) {
93254    if (defaultKey) {
93255      return this.has(k) ? this.values[k] : dflt[defaultKey];
93256    }
93257    return this.has(k) ? this.values[k] : dflt;
93258  },
93259  // Check whether we have a value for a key.
93260  has: function(k) {
93261    return k in this.values;
93262  },
93263  // Accept a setting if its one of the given alternatives.
93264  alt: function(k, v, a) {
93265    for (var n = 0; n < a.length; ++n) {
93266      if (v === a[n]) {
93267        this.set(k, v);
93268        break;
93269      }
93270    }
93271  },
93272  // Accept a setting if its a valid (signed) integer.
93273  integer: function(k, v) {
93274    if (/^-?\d+$/.test(v)) { // integer
93275      this.set(k, parseInt(v, 10));
93276    }
93277  },
93278  // Accept a setting if its a valid percentage.
93279  percent: function(k, v) {
93280    var m;
93281    if ((m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/))) {
93282      v = parseFloat(v);
93283      if (v >= 0 && v <= 100) {
93284        this.set(k, v);
93285        return true;
93286      }
93287    }
93288    return false;
vendor: 9,603 bytes, lines 93289-93602
93289  }
93290};
93291
93292// Helper function to parse input into groups separated by 'groupDelim', and
93293// interprete each group as a key/value pair separated by 'keyValueDelim'.
93294function parseOptions(input, callback, keyValueDelim, groupDelim) {
93295  var groups = groupDelim ? input.split(groupDelim) : [input];
93296  for (var i in groups) {
93297    if (typeof groups[i] !== "string") {
93298      continue;
93299    }
93300    var kv = groups[i].split(keyValueDelim);
93301    if (kv.length !== 2) {
93302      continue;
93303    }
93304    var k = kv[0].trim();
93305    var v = kv[1].trim();
93306    callback(k, v);
93307  }
93308}
93309
93310function parseCue(input, cue, regionList) {
93311  // Remember the original input if we need to throw an error.
93312  var oInput = input;
93313  // 4.1 WebVTT timestamp
93314  function consumeTimeStamp() {
93315    var ts = parseTimeStamp(input);
93316    if (ts === null) {
93317      throw new ParsingError(ParsingError.Errors.BadTimeStamp,
93318                            "Malformed timestamp: " + oInput);
93319    }
93320    // Remove time stamp from input.
93321    input = input.replace(/^[^\sa-zA-Z-]+/, "");
93322    return ts;
93323  }
93324
93325  // 4.4.2 WebVTT cue settings
93326  function consumeCueSettings(input, cue) {
93327    var settings = new Settings();
93328
93329    parseOptions(input, function (k, v) {
93330      switch (k) {
93331      case "region":
93332        // Find the last region we parsed with the same region id.
93333        for (var i = regionList.length - 1; i >= 0; i--) {
93334          if (regionList[i].id === v) {
93335            settings.set(k, regionList[i].region);
93336            break;
93337          }
93338        }
93339        break;
93340      case "vertical":
93341        settings.alt(k, v, ["rl", "lr"]);
93342        break;
93343      case "line":
93344        var vals = v.split(","),
93345            vals0 = vals[0];
93346        settings.integer(k, vals0);
93347        settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
93348        settings.alt(k, vals0, ["auto"]);
93349        if (vals.length === 2) {
93350          settings.alt("lineAlign", vals[1], ["start", "center", "end"]);
93351        }
93352        break;
93353      case "position":
93354        vals = v.split(",");
93355        settings.percent(k, vals[0]);
93356        if (vals.length === 2) {
93357          settings.alt("positionAlign", vals[1], ["start", "center", "end"]);
93358        }
93359        break;
93360      case "size":
93361        settings.percent(k, v);
93362        break;
93363      case "align":
93364        settings.alt(k, v, ["start", "center", "end", "left", "right"]);
93365        break;
93366      }
93367    }, /:/, /\s/);
93368
93369    // Apply default values for any missing fields.
93370    cue.region = settings.get("region", null);
93371    cue.vertical = settings.get("vertical", "");
93372    try {
93373      cue.line = settings.get("line", "auto");
93374    } catch (e) {}
93375    cue.lineAlign = settings.get("lineAlign", "start");
93376    cue.snapToLines = settings.get("snapToLines", true);
93377    cue.size = settings.get("size", 100);
93378    // Safari still uses the old middle value and won't accept center
93379    try {
93380      cue.align = settings.get("align", "center");
93381    } catch (e) {
93382      cue.align = settings.get("align", "middle");
93383    }
93384    try {
93385      cue.position = settings.get("position", "auto");
93386    } catch (e) {
93387      cue.position = settings.get("position", {
93388        start: 0,
93389        left: 0,
93390        center: 50,
93391        middle: 50,
93392        end: 100,
93393        right: 100
93394      }, cue.align);
93395    }
93396
93397
93398    cue.positionAlign = settings.get("positionAlign", {
93399      start: "start",
93400      left: "start",
93401      center: "center",
93402      middle: "center",
93403      end: "end",
93404      right: "end"
93405    }, cue.align);
93406  }
93407
93408  function skipWhitespace() {
93409    input = input.replace(/^\s+/, "");
93410  }
93411
93412  // 4.1 WebVTT cue timings.
93413  skipWhitespace();
93414  cue.startTime = consumeTimeStamp();   // (1) collect cue start time
93415  skipWhitespace();
93416  if (input.substr(0, 3) !== "-->") {     // (3) next characters must match "-->"
93417    throw new ParsingError(ParsingError.Errors.BadTimeStamp,
93418                           "Malformed time stamp (time stamps must be separated by '-->'): " +
93419                           oInput);
93420  }
93421  input = input.substr(3);
93422  skipWhitespace();
93423  cue.endTime = consumeTimeStamp();     // (5) collect cue end time
93424
93425  // 4.1 WebVTT cue settings list.
93426  skipWhitespace();
93427  consumeCueSettings(input, cue);
93428}
93429
93430// When evaluating this file as part of a Webpack bundle for server
93431// side rendering, `document` is an empty object.
93432var TEXTAREA_ELEMENT = document.createElement && document.createElement("textarea");
93433
93434var TAG_NAME = {
93435  c: "span",
93436  i: "i",
93437  b: "b",
93438  u: "u",
93439  ruby: "ruby",
93440  rt: "rt",
93441  v: "span",
93442  lang: "span"
93443};
93444
93445// 5.1 default text color
93446// 5.2 default text background color is equivalent to text color with bg_ prefix
93447var DEFAULT_COLOR_CLASS = {
93448  white: 'rgba(255,255,255,1)',
93449  lime: 'rgba(0,255,0,1)',
93450  cyan: 'rgba(0,255,255,1)',
93451  red: 'rgba(255,0,0,1)',
93452  yellow: 'rgba(255,255,0,1)',
93453  magenta: 'rgba(255,0,255,1)',
93454  blue: 'rgba(0,0,255,1)',
93455  black: 'rgba(0,0,0,1)'
93456};
93457
93458var TAG_ANNOTATION = {
93459  v: "title",
93460  lang: "lang"
93461};
93462
93463var NEEDS_PARENT = {
93464  rt: "ruby"
93465};
93466
93467// Parse content into a document fragment.
93468function parseContent(window, input) {
93469  function nextToken() {
93470    // Check for end-of-string.
93471    if (!input) {
93472      return null;
93473    }
93474
93475    // Consume 'n' characters from the input.
93476    function consume(result) {
93477      input = input.substr(result.length);
93478      return result;
93479    }
93480
93481    var m = input.match(/^([^<]*)(<[^>]*>?)?/);
93482    // If there is some text before the next tag, return it, otherwise return
93483    // the tag.
93484    return consume(m[1] ? m[1] : m[2]);
93485  }
93486
93487  function unescape(s) {
93488    TEXTAREA_ELEMENT.innerHTML = s;
93489    s = TEXTAREA_ELEMENT.textContent;
93490    TEXTAREA_ELEMENT.textContent = "";
93491    return s;
93492  }
93493
93494  function shouldAdd(current, element) {
93495    return !NEEDS_PARENT[element.localName] ||
93496           NEEDS_PARENT[element.localName] === current.localName;
93497  }
93498
93499  // Create an element for this tag.
93500  function createElement(type, annotation) {
93501    var tagName = TAG_NAME[type];
93502    if (!tagName) {
93503      return null;
93504    }
93505    var element = window.document.createElement(tagName);
93506    var name = TAG_ANNOTATION[type];
93507    if (name && annotation) {
93508      element[name] = annotation.trim();
93509    }
93510    return element;
93511  }
93512
93513  var rootDiv = window.document.createElement("div"),
93514      current = rootDiv,
93515      t,
93516      tagStack = [];
93517
93518  while ((t = nextToken()) !== null) {
93519    if (t[0] === '<') {
93520      if (t[1] === "/") {
93521        // If the closing tag matches, move back up to the parent node.
93522        if (tagStack.length &&
93523            tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
93524          tagStack.pop();
93525          current = current.parentNode;
93526        }
93527        // Otherwise just ignore the end tag.
93528        continue;
93529      }
93530      var ts = parseTimeStamp(t.substr(1, t.length - 2));
93531      var node;
93532      if (ts) {
93533        // Timestamps are lead nodes as well.
93534        node = window.document.createProcessingInstruction("timestamp", ts);
93535        current.appendChild(node);
93536        continue;
93537      }
93538      var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);
93539      // If we can't parse the tag, skip to the next tag.
93540      if (!m) {
93541        continue;
93542      }
93543      // Try to construct an element, and ignore the tag if we couldn't.
93544      node = createElement(m[1], m[3]);
93545      if (!node) {
93546        continue;
93547      }
93548      // Determine if the tag should be added based on the context of where it
93549      // is placed in the cuetext.
93550      if (!shouldAdd(current, node)) {
93551        continue;
93552      }
93553      // Set the class list (as a list of classes, separated by space).
93554      if (m[2]) {
93555        var classes = m[2].split('.');
93556
93557        classes.forEach(function(cl) {
93558          var bgColor = /^bg_/.test(cl);
93559          // slice out `bg_` if it's a background color
93560          var colorName = bgColor ? cl.slice(3) : cl;
93561
93562          if (DEFAULT_COLOR_CLASS.hasOwnProperty(colorName)) {
93563            var propName = bgColor ? 'background-color' : 'color';
93564            var propValue = DEFAULT_COLOR_CLASS[colorName];
93565
93566            node.style[propName] = propValue;
93567          }
93568        });
93569
93570        node.className = classes.join(' ');
93571      }
93572      // Append the node to the current node, and enter the scope of the new
93573      // node.
93574      tagStack.push(m[1]);
93575      current.appendChild(node);
93576      current = node;
93577      continue;
93578    }
93579
93580    // Text nodes are leaf nodes.
93581    current.appendChild(window.document.createTextNode(unescape(t)));
93582  }
93583
93584  return rootDiv;
93585}
93586
93587// This is a list of all the Unicode characters that have a strong
93588// right-to-left category. What this means is that these characters are
93589// written right-to-left for sure. It was generated by pulling all the strong
93590// right-to-left characters out of the Unicode data table. That table can
93591// found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
93592var strongRTLRanges = [[0x5be, 0x5be], [0x5c0, 0x5c0], [0x5c3, 0x5c3], [0x5c6, 0x5c6],
93593 [0x5d0, 0x5ea], [0x5f0, 0x5f4], [0x608, 0x608], [0x60b, 0x60b], [0x60d, 0x60d],
93594 [0x61b, 0x61b], [0x61e, 0x64a], [0x66d, 0x66f], [0x671, 0x6d5], [0x6e5, 0x6e6],
93595 [0x6ee, 0x6ef], [0x6fa, 0x70d], [0x70f, 0x710], [0x712, 0x72f], [0x74d, 0x7a5],
93596 [0x7b1, 0x7b1], [0x7c0, 0x7ea], [0x7f4, 0x7f5], [0x7fa, 0x7fa], [0x800, 0x815],
93597 [0x81a, 0x81a], [0x824, 0x824], [0x828, 0x828], [0x830, 0x83e], [0x840, 0x858],
93598 [0x85e, 0x85e], [0x8a0, 0x8a0], [0x8a2, 0x8ac], [0x200f, 0x200f],
93599 [0xfb1d, 0xfb1d], [0xfb1f, 0xfb28], [0xfb2a, 0xfb36], [0xfb38, 0xfb3c],
93600 [0xfb3e, 0xfb3e], [0xfb40, 0xfb41], [0xfb43, 0xfb44], [0xfb46, 0xfbc1],
93601 [0xfbd3, 0xfd3d], [0xfd50, 0xfd8f], [0xfd92, 0xfdc7], [0xfdf0, 0xfdfc],
93602 [0xfe70, 0xfe74], [0xfe76, 0xfefc], [0x10800, 0x10805], [0x10808, 
vendor: 18,179 bytes, lines 93602-94152
936020x10808],
93603 [0x1080a, 0x10835], [0x10837, 0x10838], [0x1083c, 0x1083c], [0x1083f, 0x10855],
93604 [0x10857, 0x1085f], [0x10900, 0x1091b], [0x10920, 0x10939], [0x1093f, 0x1093f],
93605 [0x10980, 0x109b7], [0x109be, 0x109bf], [0x10a00, 0x10a00], [0x10a10, 0x10a13],
93606 [0x10a15, 0x10a17], [0x10a19, 0x10a33], [0x10a40, 0x10a47], [0x10a50, 0x10a58],
93607 [0x10a60, 0x10a7f], [0x10b00, 0x10b35], [0x10b40, 0x10b55], [0x10b58, 0x10b72],
93608 [0x10b78, 0x10b7f], [0x10c00, 0x10c48], [0x1ee00, 0x1ee03], [0x1ee05, 0x1ee1f],
93609 [0x1ee21, 0x1ee22], [0x1ee24, 0x1ee24], [0x1ee27, 0x1ee27], [0x1ee29, 0x1ee32],
93610 [0x1ee34, 0x1ee37], [0x1ee39, 0x1ee39], [0x1ee3b, 0x1ee3b], [0x1ee42, 0x1ee42],
93611 [0x1ee47, 0x1ee47], [0x1ee49, 0x1ee49], [0x1ee4b, 0x1ee4b], [0x1ee4d, 0x1ee4f],
93612 [0x1ee51, 0x1ee52], [0x1ee54, 0x1ee54], [0x1ee57, 0x1ee57], [0x1ee59, 0x1ee59],
93613 [0x1ee5b, 0x1ee5b], [0x1ee5d, 0x1ee5d], [0x1ee5f, 0x1ee5f], [0x1ee61, 0x1ee62],
93614 [0x1ee64, 0x1ee64], [0x1ee67, 0x1ee6a], [0x1ee6c, 0x1ee72], [0x1ee74, 0x1ee77],
93615 [0x1ee79, 0x1ee7c], [0x1ee7e, 0x1ee7e], [0x1ee80, 0x1ee89], [0x1ee8b, 0x1ee9b],
93616 [0x1eea1, 0x1eea3], [0x1eea5, 0x1eea9], [0x1eeab, 0x1eebb], [0x10fffd, 0x10fffd]];
93617
93618function isStrongRTLChar(charCode) {
93619  for (var i = 0; i < strongRTLRanges.length; i++) {
93620    var currentRange = strongRTLRanges[i];
93621    if (charCode >= currentRange[0] && charCode <= currentRange[1]) {
93622      return true;
93623    }
93624  }
93625
93626  return false;
93627}
93628
93629function determineBidi(cueDiv) {
93630  var nodeStack = [],
93631      text = "",
93632      charCode;
93633
93634  if (!cueDiv || !cueDiv.childNodes) {
93635    return "ltr";
93636  }
93637
93638  function pushNodes(nodeStack, node) {
93639    for (var i = node.childNodes.length - 1; i >= 0; i--) {
93640      nodeStack.push(node.childNodes[i]);
93641    }
93642  }
93643
93644  function nextTextNode(nodeStack) {
93645    if (!nodeStack || !nodeStack.length) {
93646      return null;
93647    }
93648
93649    var node = nodeStack.pop(),
93650        text = node.textContent || node.innerText;
93651    if (text) {
93652      // TODO: This should match all unicode type B characters (paragraph
93653      // separator characters). See issue #115.
93654      var m = text.match(/^.*(\n|\r)/);
93655      if (m) {
93656        nodeStack.length = 0;
93657        return m[0];
93658      }
93659      return text;
93660    }
93661    if (node.tagName === "ruby") {
93662      return nextTextNode(nodeStack);
93663    }
93664    if (node.childNodes) {
93665      pushNodes(nodeStack, node);
93666      return nextTextNode(nodeStack);
93667    }
93668  }
93669
93670  pushNodes(nodeStack, cueDiv);
93671  while ((text = nextTextNode(nodeStack))) {
93672    for (var i = 0; i < text.length; i++) {
93673      charCode = text.charCodeAt(i);
93674      if (isStrongRTLChar(charCode)) {
93675        return "rtl";
93676      }
93677    }
93678  }
93679  return "ltr";
93680}
93681
93682function computeLinePos(cue) {
93683  if (typeof cue.line === "number" &&
93684      (cue.snapToLines || (cue.line >= 0 && cue.line <= 100))) {
93685    return cue.line;
93686  }
93687  if (!cue.track || !cue.track.textTrackList ||
93688      !cue.track.textTrackList.mediaElement) {
93689    return -1;
93690  }
93691  var track = cue.track,
93692      trackList = track.textTrackList,
93693      count = 0;
93694  for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
93695    if (trackList[i].mode === "showing") {
93696      count++;
93697    }
93698  }
93699  return ++count * -1;
93700}
93701
93702function StyleBox() {
93703}
93704
93705// Apply styles to a div. If there is no div passed then it defaults to the
93706// div on 'this'.
93707StyleBox.prototype.applyStyles = function(styles, div) {
93708  div = div || this.div;
93709  for (var prop in styles) {
93710    if (styles.hasOwnProperty(prop)) {
93711      div.style[prop] = styles[prop];
93712    }
93713  }
93714};
93715
93716StyleBox.prototype.formatStyle = function(val, unit) {
93717  return val === 0 ? 0 : val + unit;
93718};
93719
93720// Constructs the computed display state of the cue (a div). Places the div
93721// into the overlay which should be a block level element (usually a div).
93722function CueStyleBox(window, cue, styleOptions) {
93723  StyleBox.call(this);
93724  this.cue = cue;
93725
93726  // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
93727  // have inline positioning and will function as the cue background box.
93728  this.cueDiv = parseContent(window, cue.text);
93729  var styles = {
93730    color: "rgba(255, 255, 255, 1)",
93731    backgroundColor:  "rgba(0, 0, 0, 0.8)",
93732    position: "relative",
93733    left: 0,
93734    right: 0,
93735    top: 0,
93736    bottom: 0,
93737    display: "inline",
93738    writingMode: cue.vertical === "" ? "horizontal-tb"
93739                                     : cue.vertical === "lr" ? "vertical-lr"
93740                                                             : "vertical-rl",
93741    unicodeBidi: "plaintext"
93742  };
93743
93744  this.applyStyles(styles, this.cueDiv);
93745
93746  // Create an absolutely positioned div that will be used to position the cue
93747  // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
93748  // mirrors of them except middle instead of center on Safari.
93749  this.div = window.document.createElement("div");
93750  styles = {
93751    direction: determineBidi(this.cueDiv),
93752    writingMode: cue.vertical === "" ? "horizontal-tb"
93753                                     : cue.vertical === "lr" ? "vertical-lr"
93754                                                             : "vertical-rl",
93755    unicodeBidi: "plaintext",
93756    textAlign: cue.align === "middle" ? "center" : cue.align,
93757    font: styleOptions.font,
93758    whiteSpace: "pre-line",
93759    position: "absolute"
93760  };
93761
93762  this.applyStyles(styles);
93763  this.div.appendChild(this.cueDiv);
93764
93765  // Calculate the distance from the reference edge of the viewport to the text
93766  // position of the cue box. The reference edge will be resolved later when
93767  // the box orientation styles are applied.
93768  var textPos = 0;
93769  switch (cue.positionAlign) {
93770  case "start":
93771  case "line-left":
93772    textPos = cue.position;
93773    break;
93774  case "center":
93775    textPos = cue.position - (cue.size / 2);
93776    break;
93777  case "end":
93778  case "line-right":
93779    textPos = cue.position - cue.size;
93780    break;
93781  }
93782
93783  // Horizontal box orientation; textPos is the distance from the left edge of the
93784  // area to the left edge of the box and cue.size is the distance extending to
93785  // the right from there.
93786  if (cue.vertical === "") {
93787    this.applyStyles({
93788      left:  this.formatStyle(textPos, "%"),
93789      width: this.formatStyle(cue.size, "%")
93790    });
93791  // Vertical box orientation; textPos is the distance from the top edge of the
93792  // area to the top edge of the box and cue.size is the height extending
93793  // downwards from there.
93794  } else {
93795    this.applyStyles({
93796      top: this.formatStyle(textPos, "%"),
93797      height: this.formatStyle(cue.size, "%")
93798    });
93799  }
93800
93801  this.move = function(box) {
93802    this.applyStyles({
93803      top: this.formatStyle(box.top, "px"),
93804      bottom: this.formatStyle(box.bottom, "px"),
93805      left: this.formatStyle(box.left, "px"),
93806      right: this.formatStyle(box.right, "px"),
93807      height: this.formatStyle(box.height, "px"),
93808      width: this.formatStyle(box.width, "px")
93809    });
93810  };
93811}
93812CueStyleBox.prototype = _objCreate(StyleBox.prototype);
93813CueStyleBox.prototype.constructor = CueStyleBox;
93814
93815// Represents the co-ordinates of an Element in a way that we can easily
93816// compute things with such as if it overlaps or intersects with another Element.
93817// Can initialize it with either a StyleBox or another BoxPosition.
93818function BoxPosition(obj) {
93819  // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
93820  // was passed in and we need to copy the results of 'getBoundingClientRect'
93821  // as the object returned is readonly. All co-ordinate values are in reference
93822  // to the viewport origin (top left).
93823  var lh, height, width, top;
93824  if (obj.div) {
93825    height = obj.div.offsetHeight;
93826    width = obj.div.offsetWidth;
93827    top = obj.div.offsetTop;
93828
93829    var rects = (rects = obj.div.childNodes) && (rects = rects[0]) &&
93830                rects.getClientRects && rects.getClientRects();
93831    obj = obj.div.getBoundingClientRect();
93832    // In certain cases the outter div will be slightly larger then the sum of
93833    // the inner div's lines. This could be due to bold text, etc, on some platforms.
93834    // In this case we should get the average line height and use that. This will
93835    // result in the desired behaviour.
93836    lh = rects ? Math.max((rects[0] && rects[0].height) || 0, obj.height / rects.length)
93837               : 0;
93838
93839  }
93840  this.left = obj.left;
93841  this.right = obj.right;
93842  this.top = obj.top || top;
93843  this.height = obj.height || height;
93844  this.bottom = obj.bottom || (top + (obj.height || height));
93845  this.width = obj.width || width;
93846  this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
93847}
93848
93849// Move the box along a particular axis. Optionally pass in an amount to move
93850// the box. If no amount is passed then the default is the line height of the
93851// box.
93852BoxPosition.prototype.move = function(axis, toMove) {
93853  toMove = toMove !== undefined ? toMove : this.lineHeight;
93854  switch (axis) {
93855  case "+x":
93856    this.left += toMove;
93857    this.right += toMove;
93858    break;
93859  case "-x":
93860    this.left -= toMove;
93861    this.right -= toMove;
93862    break;
93863  case "+y":
93864    this.top += toMove;
93865    this.bottom += toMove;
93866    break;
93867  case "-y":
93868    this.top -= toMove;
93869    this.bottom -= toMove;
93870    break;
93871  }
93872};
93873
93874// Check if this box overlaps another box, b2.
93875BoxPosition.prototype.overlaps = function(b2) {
93876  return this.left < b2.right &&
93877         this.right > b2.left &&
93878         this.top < b2.bottom &&
93879         this.bottom > b2.top;
93880};
93881
93882// Check if this box overlaps any other boxes in boxes.
93883BoxPosition.prototype.overlapsAny = function(boxes) {
93884  for (var i = 0; i < boxes.length; i++) {
93885    if (this.overlaps(boxes[i])) {
93886      return true;
93887    }
93888  }
93889  return false;
93890};
93891
93892// Check if this box is within another box.
93893BoxPosition.prototype.within = function(container) {
93894  return this.top >= container.top &&
93895         this.bottom <= container.bottom &&
93896         this.left >= container.left &&
93897         this.right <= container.right;
93898};
93899
93900// Check if this box is entirely within the container or it is overlapping
93901// on the edge opposite of the axis direction passed. For example, if "+x" is
93902// passed and the box is overlapping on the left edge of the container, then
93903// return true.
93904BoxPosition.prototype.overlapsOppositeAxis = function(container, axis) {
93905  switch (axis) {
93906  case "+x":
93907    return this.left < container.left;
93908  case "-x":
93909    return this.right > container.right;
93910  case "+y":
93911    return this.top < container.top;
93912  case "-y":
93913    return this.bottom > container.bottom;
93914  }
93915};
93916
93917// Find the percentage of the area that this box is overlapping with another
93918// box.
93919BoxPosition.prototype.intersectPercentage = function(b2) {
93920  var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
93921      y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
93922      intersectArea = x * y;
93923  return intersectArea / (this.height * this.width);
93924};
93925
93926// Convert the positions from this box to CSS compatible positions using
93927// the reference container's positions. This has to be done because this
93928// box's positions are in reference to the viewport origin, whereas, CSS
93929// values are in referecne to their respective edges.
93930BoxPosition.prototype.toCSSCompatValues = function(reference) {
93931  return {
93932    top: this.top - reference.top,
93933    bottom: reference.bottom - this.bottom,
93934    left: this.left - reference.left,
93935    right: reference.right - this.right,
93936    height: this.height,
93937    width: this.width
93938  };
93939};
93940
93941// Get an object that represents the box's position without anything extra.
93942// Can pass a StyleBox, HTMLElement, or another BoxPositon.
93943BoxPosition.getSimpleBoxPosition = function(obj) {
93944  var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
93945  var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
93946  var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;
93947
93948  obj = obj.div ? obj.div.getBoundingClientRect() :
93949                obj.tagName ? obj.getBoundingClientRect() : obj;
93950  var ret = {
93951    left: obj.left,
93952    right: obj.right,
93953    top: obj.top || top,
93954    height: obj.height || height,
93955    bottom: obj.bottom || (top + (obj.height || height)),
93956    width: obj.width || width
93957  };
93958  return ret;
93959};
93960
93961// Move a StyleBox to its specified, or next best, position. The containerBox
93962// is the box that contains the StyleBox, such as a div. boxPositions are
93963// a list of other boxes that the styleBox can't overlap with.
93964function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
93965
93966  // Find the best position for a cue box, b, on the video. The axis parameter
93967  // is a list of axis, the order of which, it will move the box along. For example:
93968  // Passing ["+x", "-x"] will move the box first along the x axis in the positive
93969  // direction. If it doesn't find a good position for it there it will then move
93970  // it along the x axis in the negative direction.
93971  function findBestPosition(b, axis) {
93972    var bestPosition,
93973        specifiedPosition = new BoxPosition(b),
93974        percentage = 1; // Highest possible so the first thing we get is better.
93975
93976    for (var i = 0; i < axis.length; i++) {
93977      while (b.overlapsOppositeAxis(containerBox, axis[i]) ||
93978             (b.within(containerBox) && b.overlapsAny(boxPositions))) {
93979        b.move(axis[i]);
93980      }
93981      // We found a spot where we aren't overlapping anything. This is our
93982      // best position.
93983      if (b.within(containerBox)) {
93984        return b;
93985      }
93986      var p = b.intersectPercentage(containerBox);
93987      // If we're outside the container box less then we were on our last try
93988      // then remember this position as the best position.
93989      if (percentage > p) {
93990        bestPosition = new BoxPosition(b);
93991        percentage = p;
93992      }
93993      // Reset the box position to the specified position.
93994      b = new BoxPosition(specifiedPosition);
93995    }
93996    return bestPosition || specifiedPosition;
93997  }
93998
93999  var boxPosition = new BoxPosition(styleBox),
94000      cue = styleBox.cue,
94001      linePos = computeLinePos(cue),
94002      axis = [];
94003
94004  // If we have a line number to align the cue to.
94005  if (cue.snapToLines) {
94006    var size;
94007    switch (cue.vertical) {
94008    case "":
94009      axis = [ "+y", "-y" ];
94010      size = "height";
94011      break;
94012    case "rl":
94013      axis = [ "+x", "-x" ];
94014      size = "width";
94015      break;
94016    case "lr":
94017      axis = [ "-x", "+x" ];
94018      size = "width";
94019      break;
94020    }
94021
94022    var step = boxPosition.lineHeight,
94023        position = step * Math.round(linePos),
94024        maxPosition = containerBox[size] + step,
94025        initialAxis = axis[0];
94026
94027    // If the specified intial position is greater then the max position then
94028    // clamp the box to the amount of steps it would take for the box to
94029    // reach the max position.
94030    if (Math.abs(position) > maxPosition) {
94031      position = position < 0 ? -1 : 1;
94032      position *= Math.ceil(maxPosition / step) * step;
94033    }
94034
94035    // If computed line position returns negative then line numbers are
94036    // relative to the bottom of the video instead of the top. Therefore, we
94037    // need to increase our initial position by the length or width of the
94038    // video, depending on the writing direction, and reverse our axis directions.
94039    if (linePos < 0) {
94040      position += cue.vertical === "" ? containerBox.height : containerBox.width;
94041      axis = axis.reverse();
94042    }
94043
94044    // Move the box to the specified position. This may not be its best
94045    // position.
94046    boxPosition.move(initialAxis, position);
94047
94048  } else {
94049    // If we have a percentage line value for the cue.
94050    var calculatedPercentage = (boxPosition.lineHeight / containerBox.height) * 100;
94051
94052    switch (cue.lineAlign) {
94053    case "center":
94054      linePos -= (calculatedPercentage / 2);
94055      break;
94056    case "end":
94057      linePos -= calculatedPercentage;
94058      break;
94059    }
94060
94061    // Apply initial line position to the cue box.
94062    switch (cue.vertical) {
94063    case "":
94064      styleBox.applyStyles({
94065        top: styleBox.formatStyle(linePos, "%")
94066      });
94067      break;
94068    case "rl":
94069      styleBox.applyStyles({
94070        left: styleBox.formatStyle(linePos, "%")
94071      });
94072      break;
94073    case "lr":
94074      styleBox.applyStyles({
94075        right: styleBox.formatStyle(linePos, "%")
94076      });
94077      break;
94078    }
94079
94080    axis = [ "+y", "-x", "+x", "-y" ];
94081
94082    // Get the box position again after we've applied the specified positioning
94083    // to it.
94084    boxPosition = new BoxPosition(styleBox);
94085  }
94086
94087  var bestPosition = findBestPosition(boxPosition, axis);
94088  styleBox.move(bestPosition.toCSSCompatValues(containerBox));
94089}
94090
94091function WebVTT() {
94092  // Nothing
94093}
94094
94095// Helper to allow strings to be decoded instead of the default binary utf8 data.
94096WebVTT.StringDecoder = function() {
94097  return {
94098    decode: function(data) {
94099      if (!data) {
94100        return "";
94101      }
94102      if (typeof data !== "string") {
94103        throw new Error("Error - expected string data.");
94104      }
94105      return decodeURIComponent(encodeURIComponent(data));
94106    }
94107  };
94108};
94109
94110WebVTT.convertCueToDOMTree = function(window, cuetext) {
94111  if (!window || !cuetext) {
94112    return null;
94113  }
94114  return parseContent(window, cuetext);
94115};
94116
94117var FONT_SIZE_PERCENT = 0.05;
94118var FONT_STYLE = "sans-serif";
94119var CUE_BACKGROUND_PADDING = "1.5%";
94120
94121// Runs the processing model over the cues and regions passed to it.
94122// @param overlay A block level element (usually a div) that the computed cues
94123//                and regions will be placed into.
94124WebVTT.processCues = function(window, cues, overlay) {
94125  if (!window || !cues || !overlay) {
94126    return null;
94127  }
94128
94129  // Remove all previous children.
94130  while (overlay.firstChild) {
94131    overlay.removeChild(overlay.firstChild);
94132  }
94133
94134  var paddedOverlay = window.document.createElement("div");
94135  paddedOverlay.style.position = "absolute";
94136  paddedOverlay.style.left = "0";
94137  paddedOverlay.style.right = "0";
94138  paddedOverlay.style.top = "0";
94139  paddedOverlay.style.bottom = "0";
94140  paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
94141  overlay.appendChild(paddedOverlay);
94142
94143  // Determine if we need to compute the display states of the cues. This could
94144  // be the case if a cue's state has been changed since the last computation or
94145  // if it has not been computed yet.
94146  function shouldCompute(cues) {
94147    for (var i = 0; i < cues.length; i++) {
94148      if (cues[i].hasBeenReset || !cues[i].displayState) {
94149        return true;
94150      }
94151    }
94152    return false;
vendor: 10,899 bytes, lines 94153-94511
94153  }
94154
94155  // We don't need to recompute the cues' display states. Just reuse them.
94156  if (!shouldCompute(cues)) {
94157    for (var i = 0; i < cues.length; i++) {
94158      paddedOverlay.appendChild(cues[i].displayState);
94159    }
94160    return;
94161  }
94162
94163  var boxPositions = [],
94164      containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
94165      fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
94166  var styleOptions = {
94167    font: fontSize + "px " + FONT_STYLE
94168  };
94169
94170  (function() {
94171    var styleBox, cue;
94172
94173    for (var i = 0; i < cues.length; i++) {
94174      cue = cues[i];
94175
94176      // Compute the intial position and styles of the cue div.
94177      styleBox = new CueStyleBox(window, cue, styleOptions);
94178      paddedOverlay.appendChild(styleBox.div);
94179
94180      // Move the cue div to it's correct line position.
94181      moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);
94182
94183      // Remember the computed div so that we don't have to recompute it later
94184      // if we don't have too.
94185      cue.displayState = styleBox.div;
94186
94187      boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
94188    }
94189  })();
94190};
94191
94192WebVTT.Parser = function(window, vttjs, decoder) {
94193  if (!decoder) {
94194    decoder = vttjs;
94195    vttjs = {};
94196  }
94197  if (!vttjs) {
94198    vttjs = {};
94199  }
94200
94201  this.window = window;
94202  this.vttjs = vttjs;
94203  this.state = "INITIAL";
94204  this.buffer = "";
94205  this.decoder = decoder || new TextDecoder("utf8");
94206  this.regionList = [];
94207};
94208
94209WebVTT.Parser.prototype = {
94210  // If the error is a ParsingError then report it to the consumer if
94211  // possible. If it's not a ParsingError then throw it like normal.
94212  reportOrThrowError: function(e) {
94213    if (e instanceof ParsingError) {
94214      this.onparsingerror && this.onparsingerror(e);
94215    } else {
94216      throw e;
94217    }
94218  },
94219  parse: function (data) {
94220    var self = this;
94221
94222    // If there is no data then we won't decode it, but will just try to parse
94223    // whatever is in buffer already. This may occur in circumstances, for
94224    // example when flush() is called.
94225    if (data) {
94226      // Try to decode the data that we received.
94227      self.buffer += self.decoder.decode(data, {stream: true});
94228    }
94229
94230    function collectNextLine() {
94231      var buffer = self.buffer;
94232      var pos = 0;
94233      while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
94234        ++pos;
94235      }
94236      var line = buffer.substr(0, pos);
94237      // Advance the buffer early in case we fail below.
94238      if (buffer[pos] === '\r') {
94239        ++pos;
94240      }
94241      if (buffer[pos] === '\n') {
94242        ++pos;
94243      }
94244      self.buffer = buffer.substr(pos);
94245      return line;
94246    }
94247
94248    // 3.4 WebVTT region and WebVTT region settings syntax
94249    function parseRegion(input) {
94250      var settings = new Settings();
94251
94252      parseOptions(input, function (k, v) {
94253        switch (k) {
94254        case "id":
94255          settings.set(k, v);
94256          break;
94257        case "width":
94258          settings.percent(k, v);
94259          break;
94260        case "lines":
94261          settings.integer(k, v);
94262          break;
94263        case "regionanchor":
94264        case "viewportanchor":
94265          var xy = v.split(',');
94266          if (xy.length !== 2) {
94267            break;
94268          }
94269          // We have to make sure both x and y parse, so use a temporary
94270          // settings object here.
94271          var anchor = new Settings();
94272          anchor.percent("x", xy[0]);
94273          anchor.percent("y", xy[1]);
94274          if (!anchor.has("x") || !anchor.has("y")) {
94275            break;
94276          }
94277          settings.set(k + "X", anchor.get("x"));
94278          settings.set(k + "Y", anchor.get("y"));
94279          break;
94280        case "scroll":
94281          settings.alt(k, v, ["up"]);
94282          break;
94283        }
94284      }, /=/, /\s/);
94285
94286      // Create the region, using default values for any values that were not
94287      // specified.
94288      if (settings.has("id")) {
94289        var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
94290        region.width = settings.get("width", 100);
94291        region.lines = settings.get("lines", 3);
94292        region.regionAnchorX = settings.get("regionanchorX", 0);
94293        region.regionAnchorY = settings.get("regionanchorY", 100);
94294        region.viewportAnchorX = settings.get("viewportanchorX", 0);
94295        region.viewportAnchorY = settings.get("viewportanchorY", 100);
94296        region.scroll = settings.get("scroll", "");
94297        // Register the region.
94298        self.onregion && self.onregion(region);
94299        // Remember the VTTRegion for later in case we parse any VTTCues that
94300        // reference it.
94301        self.regionList.push({
94302          id: settings.get("id"),
94303          region: region
94304        });
94305      }
94306    }
94307
94308    // draft-pantos-http-live-streaming-20
94309    // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5
94310    // 3.5 WebVTT
94311    function parseTimestampMap(input) {
94312      var settings = new Settings();
94313
94314      parseOptions(input, function(k, v) {
94315        switch(k) {
94316        case "MPEGT":
94317          settings.integer(k + 'S', v);
94318          break;
94319        case "LOCA":
94320          settings.set(k + 'L', parseTimeStamp(v));
94321          break;
94322        }
94323      }, /[^\d]:/, /,/);
94324
94325      self.ontimestampmap && self.ontimestampmap({
94326        "MPEGTS": settings.get("MPEGTS"),
94327        "LOCAL": settings.get("LOCAL")
94328      });
94329    }
94330
94331    // 3.2 WebVTT metadata header syntax
94332    function parseHeader(input) {
94333      if (input.match(/X-TIMESTAMP-MAP/)) {
94334        // This line contains HLS X-TIMESTAMP-MAP metadata
94335        parseOptions(input, function(k, v) {
94336          switch(k) {
94337          case "X-TIMESTAMP-MAP":
94338            parseTimestampMap(v);
94339            break;
94340          }
94341        }, /=/);
94342      } else {
94343        parseOptions(input, function (k, v) {
94344          switch (k) {
94345          case "Region":
94346            // 3.3 WebVTT region metadata header syntax
94347            parseRegion(v);
94348            break;
94349          }
94350        }, /:/);
94351      }
94352
94353    }
94354
94355    // 5.1 WebVTT file parsing.
94356    try {
94357      var line;
94358      if (self.state === "INITIAL") {
94359        // We can't start parsing until we have the first line.
94360        if (!/\r\n|\n/.test(self.buffer)) {
94361          return this;
94362        }
94363
94364        line = collectNextLine();
94365
94366        var m = line.match(/^WEBVTT([ \t].*)?$/);
94367        if (!m || !m[0]) {
94368          throw new ParsingError(ParsingError.Errors.BadSignature);
94369        }
94370
94371        self.state = "HEADER";
94372      }
94373
94374      var alreadyCollectedLine = false;
94375      while (self.buffer) {
94376        // We can't parse a line until we have the full line.
94377        if (!/\r\n|\n/.test(self.buffer)) {
94378          return this;
94379        }
94380
94381        if (!alreadyCollectedLine) {
94382          line = collectNextLine();
94383        } else {
94384          alreadyCollectedLine = false;
94385        }
94386
94387        switch (self.state) {
94388        case "HEADER":
94389          // 13-18 - Allow a header (metadata) under the WEBVTT line.
94390          if (/:/.test(line)) {
94391            parseHeader(line);
94392          } else if (!line) {
94393            // An empty line terminates the header and starts the body (cues).
94394            self.state = "ID";
94395          }
94396          continue;
94397        case "NOTE":
94398          // Ignore NOTE blocks.
94399          if (!line) {
94400            self.state = "ID";
94401          }
94402          continue;
94403        case "ID":
94404          // Check for the start of NOTE blocks.
94405          if (/^NOTE($|[ \t])/.test(line)) {
94406            self.state = "NOTE";
94407            break;
94408          }
94409          // 19-29 - Allow any number of line terminators, then initialize new cue values.
94410          if (!line) {
94411            continue;
94412          }
94413          self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, "");
94414          // Safari still uses the old middle value and won't accept center
94415          try {
94416            self.cue.align = "center";
94417          } catch (e) {
94418            self.cue.align = "middle";
94419          }
94420          self.state = "CUE";
94421          // 30-39 - Check if self line contains an optional identifier or timing data.
94422          if (line.indexOf("-->") === -1) {
94423            self.cue.id = line;
94424            continue;
94425          }
94426          // Process line as start of a cue.
94427          /*falls through*/
94428        case "CUE":
94429          // 40 - Collect cue timings and settings.
94430          try {
94431            parseCue(line, self.cue, self.regionList);
94432          } catch (e) {
94433            self.reportOrThrowError(e);
94434            // In case of an error ignore rest of the cue.
94435            self.cue = null;
94436            self.state = "BADCUE";
94437            continue;
94438          }
94439          self.state = "CUETEXT";
94440          continue;
94441        case "CUETEXT":
94442          var hasSubstring = line.indexOf("-->") !== -1;
94443          // 34 - If we have an empty line then report the cue.
94444          // 35 - If we have the special substring '-->' then report the cue,
94445          // but do not collect the line as we need to process the current
94446          // one as a new cue.
94447          if (!line || hasSubstring && (alreadyCollectedLine = true)) {
94448            // We are done parsing self cue.
94449            self.oncue && self.oncue(self.cue);
94450            self.cue = null;
94451            self.state = "ID";
94452            continue;
94453          }
94454          if (self.cue.text) {
94455            self.cue.text += "\n";
94456          }
94457          self.cue.text += line.replace(/\u2028/g, '\n').replace(/u2029/g, '\n');
94458          continue;
94459        case "BADCUE": // BADCUE
94460          // 54-62 - Collect and discard the remaining cue.
94461          if (!line) {
94462            self.state = "ID";
94463          }
94464          continue;
94465        }
94466      }
94467    } catch (e) {
94468      self.reportOrThrowError(e);
94469
94470      // If we are currently parsing a cue, report what we have.
94471      if (self.state === "CUETEXT" && self.cue && self.oncue) {
94472        self.oncue(self.cue);
94473      }
94474      self.cue = null;
94475      // Enter BADWEBVTT state if header was not parsed correctly otherwise
94476      // another exception occurred so enter BADCUE state.
94477      self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
94478    }
94479    return this;
94480  },
94481  flush: function () {
94482    var self = this;
94483    try {
94484      // Finish decoding the stream.
94485      self.buffer += self.decoder.decode();
94486      // Synthesize the end of the current cue or region.
94487      if (self.cue || self.state === "HEADER") {
94488        self.buffer += "\n\n";
94489        self.parse();
94490      }
94491      // If we've flushed, parsed, and we're still on the INITIAL state then
94492      // that means we don't have enough of the stream to parse the first
94493      // line.
94494      if (self.state === "INITIAL") {
94495        throw new ParsingError(ParsingError.Errors.BadSignature);
94496      }
94497    } catch(e) {
94498      self.reportOrThrowError(e);
94499    }
94500    self.onflush && self.onflush();
94501    return this;
94502  }
94503};
94504
94505module.exports = WebVTT;
94506
94507},{"global/document":38}],55:[function(require,module,exports){
94508/**
94509 * Copyright 2013 vtt.js Contributors
94510 *
94511 * Licensed under the Apache License, Version 2.0 (the "License");
94512 * you may not use this file except in compliance with the License.
94513 * You may obtain a copy of the License at
94514 *
94515 *   http://www.apache.org/licenses/LICENSE-2.0
94516 *
94517 * Unless required by applicable law or agreed to in writing, software
94518 * distributed under the License is distributed on an "AS IS" BASIS,
94519 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
94520 * See the License for the specific language governing permissions and
94521 * limitations under the License.
94522 */
94523
94524var autoKeyword = "auto";
94525var directionSetting = {
94526  "": 1,
94527  "lr": 1,
94528  "rl": 1
94529};
94530var alignSetting = {
94531  "start": 1,
94532  "center": 1,
94533  "end": 1,
94534  "left": 1,
94535  "right": 1,
94536  "auto": 1,
94537  "line-left": 1,
94538  "line-right": 1
94539};
94540
94541function findDirectionSetting(value) {
94542  if (typeof value !== "string") {
94543    return false;
94544  }
94545  var dir = directionSetting[value.toLowerCase()];
94546  return dir ? value.toLowerCase() : false;
94547}
94548
94549function findAlignSetting(value) {
94550  if (typeof value !== "string") {
94551    return false;
94552  }
94553  var align = alignSetting[value.toLowerCase()];
94554  return align ? value.toLowerCase() : false;
94555}
94556
94557function VTTCue(startTime, endTime, text) {
94558  /**
94559   * Shim implementation specific properties. These properties are not in
94560   * the spec.
94561   */
94562
94563  // Lets us know when the VTTCue's data has changed in such a way that we need
94564  // to recompute its display state. This lets us compute its display state
94565  // lazily.
94566  this.hasBeenReset = false;
94567
94568  /**
94569   * VTTCue and TextTrackCue properties
94570   * http://dev.w3.org/html5/webvtt/#vttcue-interface
94571   */
94572
94573  var _id = "";
94574  var _pauseOnExit = false;
94575  var _startTime = startTime;
94576  var _endTime = endTime;
94577  var _text = text;
94578  var _region = null;
94579  var _vertical = "";
94580  var _snapToLines = true;
94581  var _line = "auto";
94582  var _lineAlign = "start";
94583  var _position = "auto";
94584  var _positionAlign = "auto";
94585  var _size = 100;
94586  var _align = "center";
94587
94588  Object.defineProperties(this, {
94589    "id": {
94590      enumerable: true,
94591      get: function() {
94592        return _id;
94593      },
94594      set: function(value) {
94595        _id = "" + value;
94596      }
94597    },
94598
94599    "pauseOnExit": {
94600      enumerable: true,
94601      get: function() {
94602        return _pauseOnExit;
94603      },
94604      set: function(value) {
94605        _pauseOnExit = !!value;
94606      }
94607    },
94608
94609    "startTime": {
94610      enumerable: true,
94611      get: function() {
94612        return _startTime;
94613      },
94614      set: function(value) {
94615        if (typeof value !== "number") {
94616          throw new TypeError("Start time must be set to a number.");
94617        }
94618        _startTime = value;
94619        this.hasBeenReset = true;
94620      }
94621    },
94622
94623    "endTime": {
94624      enumerable: true,
94625      get: function() {
94626        return _endTime;
94627      },
94628      set: function(value) {
94629        if (typeof value !== "number") {
94630          throw new TypeError("End time must be set to a number.");
94631        }
94632        _endTime = value;
94633        this.hasBeenReset = true;
94634      }
94635    },
94636
94637    "text": {
94638      enumerable: true,
94639      get: function() {
94640        return _text;
94641      },
94642      set: function(value) {
94643        _text = "" + value;
94644        this.hasBeenReset = true;
94645      }
94646    },
94647
94648    "region": {
94649      enumerable: true,
94650      get: function() {
94651        return _region;
94652      },
94653      set: function(value) {
94654        _region = value;
94655        this.hasBeenReset = true;
94656      }
94657    },
94658
94659    "vertical": {
94660      enumerable: true,
94661      get: function() {
94662        return _vertical;
94663      },
94664      set: function(value) {
94665        var setting = findDirectionSetting(value);
94666        // Have to check for false because the setting an be an empty string.
94667        if (setting === false) {
94668          throw new SyntaxError("Vertical: an invalid or illegal direction string was specified.");
94669        }
94670        _vertical = setting;
94671        this.hasBeenReset = true;
94672      }
94673    },
94674
94675    "snapToLines": {
94676      enumerable: true,
94677      get: function() {
94678        return _snapToLines;
94679      },
94680      set: function(value) {
94681        _snapToLines = !!value;
94682        this.hasBeenReset = true;
94683      }
94684    },
94685
94686    "line": {
94687      enumerable: true,
94688      get: function() {
94689        return _line;
94690      },
94691      set: function(value) {
vendor: 4,189 bytes, lines 94692-94857
94692        if (typeof value !== "number" && value !== autoKeyword) {
94693          throw new SyntaxError("Line: an invalid number or illegal string was specified.");
94694        }
94695        _line = value;
94696        this.hasBeenReset = true;
94697      }
94698    },
94699
94700    "lineAlign": {
94701      enumerable: true,
94702      get: function() {
94703        return _lineAlign;
94704      },
94705      set: function(value) {
94706        var setting = findAlignSetting(value);
94707        if (!setting) {
94708          console.warn("lineAlign: an invalid or illegal string was specified.");
94709        } else {
94710          _lineAlign = setting;
94711          this.hasBeenReset = true;
94712        }
94713      }
94714    },
94715
94716    "position": {
94717      enumerable: true,
94718      get: function() {
94719        return _position;
94720      },
94721      set: function(value) {
94722        if (value < 0 || value > 100) {
94723          throw new Error("Position must be between 0 and 100.");
94724        }
94725        _position = value;
94726        this.hasBeenReset = true;
94727      }
94728    },
94729
94730    "positionAlign": {
94731      enumerable: true,
94732      get: function() {
94733        return _positionAlign;
94734      },
94735      set: function(value) {
94736        var setting = findAlignSetting(value);
94737        if (!setting) {
94738          console.warn("positionAlign: an invalid or illegal string was specified.");
94739        } else {
94740          _positionAlign = setting;
94741          this.hasBeenReset = true;
94742        }
94743      }
94744    },
94745
94746    "size": {
94747      enumerable: true,
94748      get: function() {
94749        return _size;
94750      },
94751      set: function(value) {
94752        if (value < 0 || value > 100) {
94753          throw new Error("Size must be between 0 and 100.");
94754        }
94755        _size = value;
94756        this.hasBeenReset = true;
94757      }
94758    },
94759
94760    "align": {
94761      enumerable: true,
94762      get: function() {
94763        return _align;
94764      },
94765      set: function(value) {
94766        var setting = findAlignSetting(value);
94767        if (!setting) {
94768          throw new SyntaxError("align: an invalid or illegal alignment string was specified.");
94769        }
94770        _align = setting;
94771        this.hasBeenReset = true;
94772      }
94773    }
94774  });
94775
94776  /**
94777   * Other <track> spec defined properties
94778   */
94779
94780  // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
94781  this.displayState = undefined;
94782}
94783
94784/**
94785 * VTTCue methods
94786 */
94787
94788VTTCue.prototype.getCueAsHTML = function() {
94789  // Assume WebVTT.convertCueToDOMTree is on the global.
94790  return WebVTT.convertCueToDOMTree(window, this.text);
94791};
94792
94793module.exports = VTTCue;
94794
94795},{}],56:[function(require,module,exports){
94796/**
94797 * Copyright 2013 vtt.js Contributors
94798 *
94799 * Licensed under the Apache License, Version 2.0 (the "License");
94800 * you may not use this file except in compliance with the License.
94801 * You may obtain a copy of the License at
94802 *
94803 *   http://www.apache.org/licenses/LICENSE-2.0
94804 *
94805 * Unless required by applicable law or agreed to in writing, software
94806 * distributed under the License is distributed on an "AS IS" BASIS,
94807 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
94808 * See the License for the specific language governing permissions and
94809 * limitations under the License.
94810 */
94811
94812var scrollSetting = {
94813  "": true,
94814  "up": true
94815};
94816
94817function findScrollSetting(value) {
94818  if (typeof value !== "string") {
94819    return false;
94820  }
94821  var scroll = scrollSetting[value.toLowerCase()];
94822  return scroll ? value.toLowerCase() : false;
94823}
94824
94825function isValidPercentValue(value) {
94826  return typeof value === "number" && (value >= 0 && value <= 100);
94827}
94828
94829// VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface
94830function VTTRegion() {
94831  var _width = 100;
94832  var _lines = 3;
94833  var _regionAnchorX = 0;
94834  var _regionAnchorY = 100;
94835  var _viewportAnchorX = 0;
94836  var _viewportAnchorY = 100;
94837  var _scroll = "";
94838
94839  Object.defineProperties(this, {
94840    "width": {
94841      enumerable: true,
94842      get: function() {
94843        return _width;
94844      },
94845      set: function(value) {
94846        if (!isValidPercentValue(value)) {
94847          throw new Error("Width must be between 0 and 100.");
94848        }
94849        _width = value;
94850      }
94851    },
94852    "lines": {
94853      enumerable: true,
94854      get: function() {
94855        return _lines;
94856      },
94857      set: function(value) {
94858        if (typeof value !== "number") {
94859          throw new TypeError("Lines must be set to a number.");
94860        }
94861        _lines = value;
94862      }
94863    },
94864    "regionAnchorY": {
94865      enumerable: true,
94866      get: function() {
94867        return _regionAnchorY;
94868      },
94869      set: function(value) {
94870        if (!isValidPercentValue(value)) {
94871          throw new Error("RegionAnchorX must be between 0 and 100.");
94872        }
94873        _regionAnchorY = value;
94874      }
94875    },
94876    "regionAnchorX": {
94877      enumerable: true,
94878      get: function() {
94879        return _regionAnchorX;
94880      },
94881      set: function(value) {
94882        if(!isValidPercentValue(value)) {
94883          throw new Error("RegionAnchorY must be between 0 and 100.");
94884        }
94885        _regionAnchorX = value;
94886      }
94887    },
94888    "viewportAnchorY": {
94889      enumerable: true,
94890      get: function() {
94891        return _viewportAnchorY;
94892      },
94893      set: function(value) {
94894        if (!isValidPercentValue(value)) {
94895          throw new Error("ViewportAnchorY must be between 0 and 100.");
94896        }
94897        _viewportAnchorY = value;
94898      }
94899    },
94900    "viewportAnchorX": {
94901      enumerable: true,
94902      get: function() {
94903        return _viewportAnchorX;
94904      },
94905      set: function(value) {
94906        if (!isValidPercentValue(value)) {
94907          throw new Error("ViewportAnchorX must be between 0 and 100.");
94908        }
94909        _viewportAnchorX = value;
94910      }
94911    },
94912    "scroll": {
94913      enumerable: true,
94914      get: function() {
94915        return _scroll;
94916      },
94917      set: function(value) {
94918        var setting = findScrollSetting(value);
94919        // Have to check for false as an empty string is a legal value.
94920        if (setting === false) {
94921          console.warn("Scroll: an invalid or illegal string was specified.");
94922        } else {
94923          _scroll = setting;
94924        }
94925      }
94926    }
94927  });
94928}
94929
94930module.exports = VTTRegion;
94931
94932},{}]},{},[1]);

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