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 == '<' && '<' || 7971 c == '>' && '>' || 7972 c == '&' && '&' || 7973 c == '"' && '"' || 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 * `&` and `<` respectively. 9293 * The right angle bracket (>) may be represented using the string " > ", and must, for compatibility, 9294 * be escaped using either `>` 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.